├── LICENSE ├── README.md ├── arachno ├── Readme.md └── arachno.py ├── dfview └── dfview.py ├── idacoffee ├── idacoffee.py └── readme.md ├── kloppy ├── klop.py ├── readme.md └── rsrc │ └── kloppy.png ├── ricky ├── readme.md └── ricky.py ├── screenrecorder ├── readme.md └── screenrecorder.py └── shuffle ├── readme.md ├── rsrc └── shuffle.gif └── shuffle.py /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hexrays_scripts 2 | Various scripts for the IDA Pro and the Hexrays decompiler 3 | -------------------------------------------------------------------------------- /arachno/Readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Arachno for IDA Pro / HexRays 3 | 4 | Arachno is an IDAPython script that enhances productivity by semi-automating and simplifying repetitive tasks. 5 | 6 | It currently adds the following convenience keyboard shortcuts to IDA/HexRays: 7 | 8 | ``` 9 | ---------------------------------------- arachno help ---------------------------------------- 10 | Ctrl-Shift-C: copy current identifier to clipboard 11 | Ctrl-Shift-F: search the Internet for occurences of the current identifier 12 | Ctrl-Shift-N: rename function, suggests current identifier as function name 13 | Ctrl-Shift-V: rename current item, suggests name from clipboard contents 14 | Ctrl-Shift-E: copy current effective address to clipboard 15 | Ctrl-Shift-H: print this help 16 | Alt-Left: jump to previous navigation history location 17 | Alt-Right: jump to next navigation history location 18 | Ctrl-Alt-Up: jump to previous function 19 | Ctrl-Alt-Down: jump to next functionm 20 | ``` 21 | 22 | ## Usage 23 | 24 | Running the script from within IDA installs a number of keyboard shortcuts by which above functionality is made available. 25 | -------------------------------------------------------------------------------- /arachno/arachno.py: -------------------------------------------------------------------------------- 1 | from PyQt5.Qt import QApplication 2 | import webbrowser 3 | import ida_kernwin, ida_name, ida_funcs, ida_hexrays 4 | from ida_idaapi import BADADDR 5 | 6 | __author__ = "https://github.com/patois" 7 | 8 | SCRIPT_NAME = "arachno" 9 | SEARCH_ENGINES = [ 10 | ("DuckDuckGo", "https://duckduckgo.com/?q="), 11 | ("Google", "https://google.com/search?q="), 12 | ("Bing", "https://www.bing.com/search?q=")] 13 | ACTIVE_SEARCH_ENGINE_IDX = None 14 | 15 | # ---------------------------------------------------------------------------- 16 | class search_engine_chooser_t(ida_kernwin.Form): 17 | 18 | def __init__(self, engines): 19 | F = ida_kernwin.Form 20 | F.__init__( 21 | self, 22 | r"""STARTITEM 0 23 | Search Engine 24 | 25 | 26 | """, { 27 | 'dropDown1': F.DropdownListControl( 28 | engines, 29 | readonly=True) 30 | }) 31 | 32 | @staticmethod 33 | def select(engines): 34 | f = search_engine_chooser_t(engines) 35 | f.Compile() 36 | ok = f.Execute() 37 | result = None if not ok else f.dropDown1.value 38 | f.Free() 39 | return result 40 | 41 | # ---------------------------------------------------------------------------- 42 | def _get_identifier(): 43 | """helper function""" 44 | 45 | r = ida_kernwin.get_highlight(ida_kernwin.get_current_viewer()) 46 | return r[0] if r else None 47 | 48 | # ---------------------------------------------------------------------------- 49 | def copy_item_to_clipboard(): 50 | """copy current identifier to clipboard""" 51 | 52 | name = _get_identifier() 53 | if name: 54 | print("[%s]: copied item to clipboard:'%s'" % (SCRIPT_NAME, name)) 55 | QApplication.clipboard().setText(name) 56 | return 57 | 58 | # ---------------------------------------------------------------------------- 59 | def copy_ea_to_clipboard(): 60 | """copy current effective address to clipboard""" 61 | 62 | fmt = "%x" % ida_kernwin.get_screen_ea() 63 | QApplication.clipboard().setText(fmt) 64 | print("[%s]: copied ea to clipboard: '%s'" % (SCRIPT_NAME, fmt)) 65 | return 66 | 67 | # ---------------------------------------------------------------------------- 68 | def make_name(): 69 | """rename current item, suggests name from clipboard contents""" 70 | 71 | cv = ida_kernwin.get_current_viewer() 72 | if cv: 73 | hx = ida_kernwin.get_widget_type(cv) == ida_kernwin.BWN_PSEUDOCODE 74 | name = QApplication.clipboard().text() 75 | if hx and len(name): 76 | vd = ida_hexrays.get_widget_vdui(cv) 77 | if vd.get_current_item(ida_hexrays.USE_KEYBOARD): 78 | e = vd.item.e if vd.item.is_citem() else None 79 | ea = ida_kernwin.get_screen_ea() 80 | if e: 81 | if e.op is ida_hexrays.cot_var: 82 | var = vd.cfunc.mba.vars[e.v.idx] 83 | old_name = var.name 84 | eff_name = ida_kernwin.ask_str(name, 0, "new name for %s? " % old_name) 85 | if eff_name and vd.rename_lvar(var, eff_name, True): 86 | print("renamed: \"%s\" -> \"%s\"" % (old_name, name)) 87 | return 88 | elif e.op is ida_hexrays.cot_obj: 89 | ea = e.obj_ea 90 | else: 91 | ea = e.ea 92 | if ea != BADADDR: 93 | old_name = ida_name.get_name(ea) 94 | if old_name: 95 | eff_name = ida_kernwin.ask_str(name, 0, "new name for %s? " % old_name) 96 | if ida_name.set_name(ea, eff_name): 97 | print("renamed: \"%s\" -> \"%s\"" % (old_name, name)) 98 | return 99 | ida_kernwin.process_ui_action("hx:Rename" if hx else "MakeName") 100 | return 101 | 102 | # ---------------------------------------------------------------------------- 103 | def rename_func(do_refresh=True): 104 | """rename function, suggests current identifier as function name""" 105 | 106 | f = ida_funcs.get_func(ida_kernwin.get_screen_ea()) 107 | if f: 108 | name = _get_identifier() 109 | if not name: 110 | name = ida_funcs.get_func_name(f.start_ea) 111 | _str = ida_kernwin.ask_str(name, -1, "Rename function") 112 | 113 | if ida_name.set_name(f.start_ea, _str, ida_name.SN_NOCHECK): 114 | print("renamed: %x -> \"%s\"" % (f.start_ea, _str)) 115 | if do_refresh: 116 | cv = ida_kernwin.get_current_viewer() 117 | if ida_kernwin.get_widget_type(cv) == ida_kernwin.BWN_PSEUDOCODE: 118 | vd = ida_hexrays.get_widget_vdui(cv) 119 | if vd: 120 | vd.refresh_view(True) 121 | # else ... 122 | # according to IDAPython docs, a warning is displayed upon failure 123 | return 124 | 125 | # ---------------------------------------------------------------------------- 126 | def search_internet(): 127 | """search the Internet for occurences of the currently selected identifier""" 128 | 129 | global ACTIVE_SEARCH_ENGINE_IDX 130 | if ACTIVE_SEARCH_ENGINE_IDX is None: 131 | idx = search_engine_chooser_t.select([engine[0] for engine in SEARCH_ENGINES]) 132 | if idx is None: 133 | return 134 | ACTIVE_SEARCH_ENGINE_IDX = idx 135 | 136 | name = _get_identifier() 137 | if name: 138 | webbrowser.open("%s\"%s\"" % ( 139 | SEARCH_ENGINES[ACTIVE_SEARCH_ENGINE_IDX][1], 140 | name), 141 | new=2) 142 | 143 | # ---------------------------------------------------------------------------- 144 | def print_help(): 145 | """show this help screen""" 146 | global INSTALLED_HOTKEYS 147 | 148 | s = list() 149 | for _, item in INSTALLED_HOTKEYS.items(): 150 | hotkey, func = item 151 | s.append("%s:\t%s" % (hotkey+(max(20, len(hotkey))-len(hotkey))*" ", func.__doc__.replace("\n", " "))) 152 | print("\n%s %s help %s\n%s" % (40*"-", SCRIPT_NAME, 40*"-", "\n".join(s))) 153 | return 154 | 155 | # ---------------------------------------------------------------------------- 156 | def navhistory_prev(): 157 | """jump to previous navigation history location""" 158 | ida_kernwin.process_ui_action("Return") 159 | 160 | # ---------------------------------------------------------------------------- 161 | def navhistory_next(): 162 | """jump to next navigation history location""" 163 | ida_kernwin.process_ui_action("UndoReturn") 164 | 165 | # ---------------------------------------------------------------------------- 166 | def func_prev(): 167 | """jump to previous function""" 168 | ida_kernwin.process_ui_action("JumpPrevFunc") 169 | 170 | # ---------------------------------------------------------------------------- 171 | def func_next(): 172 | """jump to next function""" 173 | ida_kernwin.process_ui_action("JumpNextFunc") 174 | 175 | # ---------------------------------------------------------------------------- 176 | def install_hotkey(item): 177 | global INSTALLED_HOTKEYS 178 | 179 | hotkey, func = item 180 | handler = ida_kernwin.add_hotkey(hotkey, func) 181 | if handler: 182 | INSTALLED_HOTKEYS[handler] = (hotkey, func) 183 | return handler != None 184 | 185 | # ---------------------------------------------------------------------------- 186 | def install_hotkeys(): 187 | global INSTALLED_HOTKEYS 188 | 189 | INSTALLED_HOTKEYS = {} 190 | items = [("Ctrl-Shift-C", copy_item_to_clipboard), 191 | ("Ctrl-Shift-F", search_internet), 192 | ("Ctrl-Shift-N", rename_func), 193 | ("Ctrl-Shift-V", make_name), 194 | ("Ctrl-Shift-E", copy_ea_to_clipboard), 195 | ("Ctrl-Shift-H", print_help), 196 | ("Alt-Left", navhistory_prev), 197 | ("Alt-Right", navhistory_next), 198 | ("Ctrl-Alt-Up", func_prev), 199 | ("Ctrl-Alt-Down", func_next)] 200 | for item in items: 201 | if not install_hotkey(item): 202 | print("[%s]: failed installing hotkey %s" % (SCRIPT_NAME, item[0])) 203 | return 204 | 205 | # ---------------------------------------------------------------------------- 206 | def remove_hotkeys(): 207 | global INSTALLED_HOTKEYS 208 | 209 | for i in INSTALLED_HOTKEYS: 210 | ida_kernwin.del_hotkey(i) 211 | del INSTALLED_HOTKEYS 212 | return 213 | 214 | # ---------------------------------------------------------------------------- 215 | def toggle_install(): 216 | global INSTALLED_HOTKEYS 217 | 218 | activated = False 219 | 220 | try: 221 | INSTALLED_HOTKEYS 222 | remove_hotkeys() 223 | except: 224 | install_hotkeys() 225 | activated = True 226 | 227 | return activated 228 | 229 | # ---------------------------------------------------------------------------- 230 | if __name__ == "__main__": 231 | active = toggle_install() 232 | msg = "[%s]: hotkeys %sinstalled%s." % ( 233 | SCRIPT_NAME, 234 | "" if active else "un", 235 | " (press Ctrl-Shift-H for a list of hotkeys)" if active else "") 236 | print("%s" % msg) 237 | -------------------------------------------------------------------------------- /dfview/dfview.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script is based on the vds12 example of the Hexrays SDK, 3 | and has been turned into an interactive context viewer. 4 | The script displays def/use lists of variables and highlights 5 | them in the disassembly view. 6 | 7 | author: de 8 | """ 9 | 10 | import ida_pro 11 | import ida_hexrays 12 | import ida_kernwin 13 | import ida_funcs 14 | import ida_bytes 15 | import ida_lines 16 | 17 | DEBUG = False 18 | LOG_WARNINGS = True 19 | DECOMP_USE_CACHE = False 20 | 21 | MC_USEDEF = 3 22 | MC_DEF = 1 23 | MC_USE = 2 24 | 25 | # ---------------------------------------------------------------------------- 26 | def log_warning(s): 27 | if LOG_WARNINGS: 28 | print("[W]: %s" % s) 29 | return 30 | 31 | # ---------------------------------------------------------------------------- 32 | def collect_block_xrefs(out, mlist, blk, ins, find_uses): 33 | p = ins 34 | while p and not mlist.empty(): 35 | use = blk.build_use_list(p, ida_hexrays.MUST_ACCESS); # things used by the insn 36 | _def = blk.build_def_list(p, ida_hexrays.MUST_ACCESS); # things defined by the insn 37 | plst = use if find_uses else _def 38 | if mlist.has_common(plst): 39 | if not p.ea in out: 40 | out.append(p.ea) # this microinstruction seems to use our operand 41 | mlist.sub(_def) 42 | p = p.next if find_uses else p.prev 43 | 44 | # ---------------------------------------------------------------------------- 45 | def collect_xrefs(out, ctx, mop, mlist, du, find_uses): 46 | # first collect the references in the current block 47 | start = ctx.topins.next if find_uses else ctx.topins.prev 48 | collect_block_xrefs(out, mlist, ctx.blk, start, find_uses) 49 | 50 | # then find references in other blocks 51 | serial = ctx.blk.serial; # block number of the operand 52 | bc = du[serial] # chains of that block 53 | voff = ida_hexrays.voff_t(mop) 54 | ch = bc.get_chain(voff) # chain of the operand 55 | if not ch: 56 | return # odd 57 | for bn in ch: 58 | b = ctx.mba.get_mblock(bn) 59 | ins = b.head if find_uses else b.tail 60 | tmp = ida_hexrays.mlist_t() 61 | tmp.add(mlist) 62 | collect_block_xrefs(out, tmp, b, ins, find_uses) 63 | 64 | # ---------------------------------------------------------------------------- 65 | def get_xrefs(ea): 66 | result = None 67 | pfn = ida_funcs.get_func(ea) 68 | if pfn: 69 | F = ida_bytes.get_flags(ea) 70 | if ida_bytes.is_code(F): 71 | gco = ida_hexrays.gco_info_t() 72 | if ida_hexrays.get_current_operand(gco): 73 | # generate microcode 74 | decomp_flags = ida_hexrays.DECOMP_NO_WAIT 75 | if not DECOMP_USE_CACHE: 76 | decomp_flags |= ida_hexrays.DECOMP_NO_CACHE 77 | if DEBUG: 78 | decomp_flags |= ida_hexrays.DECOMP_WARNINGS 79 | hf = ida_hexrays.hexrays_failure_t() 80 | mbr = ida_hexrays.mba_ranges_t(pfn) 81 | mba = ida_hexrays.gen_microcode( 82 | mbr, 83 | hf, 84 | None, 85 | decomp_flags, 86 | ida_hexrays.MMAT_PREOPTIMIZED) 87 | if mba: 88 | merr = mba.build_graph() 89 | if merr == ida_hexrays.MERR_OK: 90 | ncalls = mba.analyze_calls(ida_hexrays.ACFL_GUESS) 91 | if ncalls < 0: 92 | print("%08x: failed to determine some calling conventions", pfn.start_ea) 93 | mlist = ida_hexrays.mlist_t() 94 | if gco.append_to_list(mlist, mba): 95 | print("mlist: %s" % mlist._print()) 96 | ctx = ida_hexrays.op_parent_info_t() 97 | mop = mba.find_mop(ctx, ea, gco.is_def(), mlist) 98 | if mop: 99 | xrefs = ida_pro.eavec_t() 100 | ndefs = 0 101 | graph = mba.get_graph() 102 | ud = graph.get_ud(ida_hexrays.GC_REGS_AND_STKVARS) 103 | du = graph.get_du(ida_hexrays.GC_REGS_AND_STKVARS) 104 | if gco.is_use(): 105 | collect_xrefs(xrefs, ctx, mop, mlist, ud, False) 106 | ndefs = xrefs.size() 107 | if ea not in xrefs: 108 | xrefs.append(ea) 109 | 110 | if gco.is_def(): 111 | if ea not in xrefs: 112 | xrefs.append(ea) 113 | ndefs = len(xrefs) 114 | collect_xrefs(xrefs, ctx, mop, mlist, du, True) 115 | result = (ea, gco, xrefs, ndefs) 116 | else: 117 | log_warning("Could not find the operand in the microcode, sorry") 118 | else: 119 | log_warning("Failed to represent %s as microcode list" % gco.name) 120 | else: 121 | log_warning("%08x: %s" % (hf.errea, ida_hexrays.get_merror_desc(merr, mba))) 122 | else: 123 | log_warning("%08x: %s" % (hf.errea, hf.str)) 124 | else: 125 | log_warning("Could not find a register or stkvar in the current operand") 126 | else: 127 | log_warning("Please position the cursor on an instruction") 128 | else: 129 | log_warning("Please position the cursor within a function") 130 | return result 131 | 132 | # ---------------------------------------------------------------------------- 133 | class df_info_t(): 134 | def __init__(self, type_id, ea, insn): 135 | self.type_id = type_id 136 | self.ea = ea 137 | self.insn = insn 138 | self.color = { 139 | MC_USEDEF:ida_kernwin.CK_EXTRA4, 140 | MC_DEF:ida_kernwin.CK_EXTRA3, 141 | MC_USE:ida_kernwin.CK_EXTRA2 142 | }[type_id] 143 | 144 | # ---------------------------------------------------------------------------- 145 | class xref_chooser_t(ida_kernwin.Choose): 146 | 147 | class view_hooks_t(ida_kernwin.View_Hooks): 148 | def __init__(self, v): 149 | self.v = v 150 | ida_kernwin.View_Hooks.__init__(self) 151 | 152 | def view_curpos(self, widget): 153 | # we can safely skip this callback if 154 | # the cursor isn't placed on an operand 155 | if ida_kernwin.get_opnum() != -1: 156 | wt = ida_kernwin.get_widget_type(widget) 157 | if wt == ida_kernwin.BWN_DISASM: 158 | uie = ida_kernwin.input_event_t() 159 | if ida_kernwin.get_user_input_event(uie): 160 | if uie.kind == ida_kernwin.iek_mouse_button_press: 161 | ea = ida_kernwin.get_screen_ea() 162 | result = get_xrefs(ea)#self.v.do_xrefs(ea) 163 | if result: 164 | ea, gco, xrefs, ndefs = result 165 | self.v.update(ea, gco, xrefs, ndefs) 166 | else: 167 | self.v.clear() 168 | ida_kernwin.request_refresh(ida_kernwin.IWID_DISASM) 169 | return 170 | 171 | class ui_hooks_t(ida_kernwin.UI_Hooks): 172 | def __init__(self, v): 173 | self.v = v 174 | ida_kernwin.UI_Hooks.__init__(self) 175 | 176 | def get_lines_rendering_info(self, out, widget, rin): 177 | if ida_kernwin.get_widget_type(widget) == ida_kernwin.BWN_DISASM: 178 | for section_lines in rin.sections_lines: 179 | for line in section_lines: 180 | line_ea = line.at.toea() 181 | for dfi in self.v.data: 182 | if dfi.ea == line_ea: 183 | e = ida_kernwin.line_rendering_output_entry_t(line) 184 | e.bg_color = dfi.color 185 | out.entries.push_back(e) 186 | 187 | #elif ida_kernwin.get_widget_type(widget) == ida_kernwin.BWN_PSEUDOCODE: 188 | # pass 189 | 190 | def __init__(self): 191 | self.view_hooks = None 192 | self.ui_hooks = None 193 | self._init(list(), 0, None, None) 194 | ida_kernwin.Choose.__init__( 195 | self, 196 | "Dataflow Context Viewer", 197 | [["Type", 6], ["Address", 16], ["Instruction", 60]]) 198 | 199 | def _init(self, xrefs, n, ea, gco): 200 | self.xrefs = xrefs 201 | self.ndefs = n 202 | self.curr_ea = ea 203 | self.gco = gco 204 | self.data = [ self._make_dfi(idx) for idx in range(len(xrefs)) ] 205 | self.items = [ self._make_item(dfi) for dfi in self.data ] 206 | if self._check_uninit_var(): 207 | print("%x: found 'use' without 'def': potentially unititalized variable!" % ea) 208 | 209 | def update(self, ea, gco, xrefs, ndefs): 210 | self._init(xrefs, ndefs, ea, gco) 211 | self.Refresh() 212 | 213 | def clear(self): 214 | self.data = list() 215 | self.items = list([]) 216 | self.Refresh() 217 | return 218 | 219 | def _check_uninit_var(self): 220 | has_use = False 221 | has_def = False 222 | for dfi in self.data: 223 | has_use |= dfi.type_id in [MC_USE, MC_USEDEF] 224 | has_def |= dfi.type_id in [MC_DEF, MC_USEDEF] 225 | return has_use and not has_def 226 | 227 | def _make_dfi(self, idx): 228 | ea = self.xrefs[idx] 229 | both_mask = ida_hexrays.GCO_USE|ida_hexrays.GCO_DEF 230 | both = (self.gco.flags & both_mask) == both_mask 231 | if ea == self.curr_ea and both: 232 | type_id = MC_USEDEF 233 | elif idx < self.ndefs: 234 | type_id = MC_DEF 235 | else: 236 | type_id = MC_USE 237 | insn = ida_lines.generate_disasm_line(ea, ida_lines.GENDSM_REMOVE_TAGS) 238 | return df_info_t(type_id, ea, insn) 239 | 240 | def _make_item(self, dfi): 241 | type_str = { 242 | MC_USEDEF:"use/def", 243 | MC_DEF:"def", 244 | MC_USE:"use" 245 | }[dfi.type_id] 246 | return [type_str, "%x" % dfi.ea, dfi.insn] 247 | 248 | def show(self): 249 | self.view_hooks = self.view_hooks_t(self) 250 | self.ui_hooks = self.ui_hooks_t(self) 251 | self.view_hooks.hook() 252 | self.ui_hooks.hook() 253 | self.Show(False) 254 | return 255 | 256 | #def OnGetLineAttr(self, n): 257 | # print(self.data[n].type_id) 258 | # col = [0xff0000,0x00ff00, 0x0000ff][self.data[n].type_id] 259 | # return [col, 0] 260 | 261 | def OnGetSize(self): 262 | return len(self.items) 263 | 264 | def OnGetLine(self, n): 265 | return self.items[n] 266 | 267 | def OnSelectLine(self, n): 268 | ida_kernwin.jumpto(self.xrefs[n]) 269 | return (ida_kernwin.Choose.NOTHING_CHANGED, ) 270 | 271 | def OnClose(self): 272 | # re-running the script with a viewer still 273 | # opened will cause OnClose() to be called, 274 | # so hooks are uninstalled safely 275 | if self.view_hooks: 276 | self.view_hooks.unhook() 277 | self.view_hooks = None 278 | if self.ui_hooks: 279 | self.ui_hooks.unhook() 280 | self.ui_hooks = None 281 | 282 | # ---------------------------------------------------------------------------- 283 | def main(): 284 | if ida_hexrays.init_hexrays_plugin(): 285 | xc = xref_chooser_t() 286 | xc.show() 287 | 288 | # ---------------------------------------------------------------------------- 289 | if __name__ == "__main__": 290 | main() 291 | -------------------------------------------------------------------------------- /idacoffee/idacoffee.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5 import QtGui 3 | from PyQt5 import QtWidgets 4 | 5 | import ida_kernwin 6 | 7 | # IDA Coffee 8 | __author__ = "patois" 9 | 10 | HOTKEY = "Ctrl-Shift-C" 11 | 12 | # ------------------------------------------------------------------------- 13 | class painter_t(QtCore.QObject): 14 | def __init__(self): 15 | QtCore.QObject.__init__(self) 16 | self.dead = False 17 | name = "Coffee" 18 | w = ida_kernwin.find_widget("IDA View-%s" % name) 19 | if not w: 20 | w = ida_kernwin.get_current_widget() 21 | if not w: 22 | name = "Coffee" 23 | w = ida_kernwin.open_disasm_window("IDA View-%s" % name) 24 | self.painting = False 25 | self.transform = False 26 | self.target = ida_kernwin.PluginForm.FormToPyQtWidget(w).viewport() 27 | self.pm = QtGui.QPixmap(self.target.size()) 28 | 29 | self.target.installEventFilter(self) 30 | self.timer = self.timercallback_t(self.target, 2) 31 | 32 | class timercallback_t(object): 33 | def __init__(self, target, max_val): 34 | self.interval = 10 35 | self.max_val = max_val 36 | self.target = target 37 | self.lane = [i for i in range(-max_val, max_val+1)] + [i for i in range(max_val-1, -max_val, -1)] 38 | self.n = len(self.lane) 39 | self.i = 0 40 | self.obj = ida_kernwin.register_timer(self.interval, self) 41 | if self.obj is None: 42 | raise RuntimeError("Failed to register timer") 43 | 44 | def get_val(self): 45 | return self.lane[self.i] 46 | 47 | def die(self): 48 | ida_kernwin.unregister_timer(self.obj) 49 | 50 | def __call__(self): 51 | self.i = (self.i + 1) % self.n 52 | try: 53 | self.target.repaint() 54 | except: 55 | return -1 56 | return self.interval 57 | 58 | def die(self): 59 | self.dead = True 60 | self.timer.die() 61 | try: 62 | self.target.removeEventFilter(self) 63 | except: 64 | pass 65 | 66 | def is_dead(self): 67 | return self.dead 68 | 69 | 70 | def eventFilter(self, receiver, event): 71 | if not self.painting and \ 72 | self.target == receiver and \ 73 | event.type() == QtCore.QEvent.Paint: 74 | 75 | if self.transform: 76 | painter = QtGui.QPainter(receiver) 77 | #painter.setRenderHints(QtGui.QPainter.Antialiasing) 78 | t = QtGui.QTransform() 79 | t.rotate(self.timer.get_val()) 80 | pixmap_rotated = self.pm.transformed(t) 81 | painter.drawPixmap(self.target.rect(), pixmap_rotated) 82 | painter.end() 83 | 84 | self.transform = False 85 | # prevent the widget form painting itself again 86 | return True 87 | 88 | else: 89 | # Send a paint event that we won't intercept 90 | self.painting = True 91 | try: 92 | self.pm = QtGui.QPixmap(self.target.size()) 93 | # render widget to pixmap, side-effect: repaints widget :( 94 | self.target.render(self.pm) 95 | finally: 96 | self.painting = False 97 | self.transform = True 98 | """workaround! 99 | widget.render() causes widget to be repainted. 100 | In order to deal with this situation, we'll issue 101 | another repaint() and transform the widget""" 102 | self.target.repaint() 103 | elif event.type() in [QtCore.QEvent.Close, QtCore.QEvent.Hide]: 104 | self.die() 105 | 106 | return QtCore.QObject.eventFilter(self, receiver, event) 107 | 108 | def coffee_main(): 109 | global coffee 110 | 111 | if coffee and not coffee.is_dead(): 112 | coffee.die() 113 | coffee = None 114 | return 115 | coffee = painter_t() 116 | ida_kernwin.msg("Caffeinated\n") 117 | 118 | try: 119 | coffee 120 | ida_kernwin.info("Already installed. Press %s" % HOTKEY) 121 | except: 122 | coffee = None 123 | print("Press %s for coffee overload" % HOTKEY) 124 | ida_kernwin.add_hotkey(HOTKEY, coffee_main) -------------------------------------------------------------------------------- /idacoffee/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # IDA Coffee for IDA Pro 3 | 4 | IDAPython script that demonstrates how to paint on / modify a widget's viewport. 5 | Based on the IDAPython example script "paint_over_navbar.py". 6 | -------------------------------------------------------------------------------- /kloppy/klop.py: -------------------------------------------------------------------------------- 1 | from idaapi import * 2 | 3 | __KLOPPY__ = r""" 4 | __ 5 | / \ 6 | + + It looks like you're inspecting 7 | @ @ '%s', which is a %s variable. 8 | | || 9 | || || It is defined at 0x%x. 10 | |\_/| 11 | \___/ 12 | """ 13 | class kloppy_t(Hexrays_Hooks): 14 | def __init__(self): 15 | Hexrays_Hooks.__init__(self) 16 | 17 | def _get_vtype(self, var): 18 | if not var: 19 | return "unknown" 20 | if var.is_stk_var(): 21 | return "stack" 22 | if var.is_reg_var(): 23 | return "reg" 24 | return "unknown" 25 | 26 | def create_hint(self, vd): 27 | cmts = [] 28 | if vd: 29 | i = vd.get_current_item(USE_MOUSE) 30 | if i and vd.item.citype == VDI_EXPR: 31 | if vd.item.e.op == cot_var: 32 | lvars = vd.cfunc.get_lvars() 33 | if lvars: 34 | var = lvars[vd.item.e.v.idx] 35 | if var: 36 | cmts.append(__KLOPPY__ % (var.name, self._get_vtype(var), var.defea)) 37 | if var.is_stk_var(): 38 | offs = var.get_stkoff() 39 | frsize = get_frame_lvar_size(vd.cfunc.entry_ea) 40 | frregs = get_frame_regs_size(vd.cfunc.entry_ea) 41 | cmts.append("Shh! It can hold ~%d bytes until\n" 42 | "it hits the end of the frame!" % (frsize + frregs - offs)) 43 | if var.is_arg_var: 44 | cmts.append("Shh! This is a function argument variable!") 45 | if var.is_result_var: 46 | cmts.append("Shh! This is a result variable!") 47 | 48 | cmts.append(8 * "-") 49 | cmts.append("") 50 | custom_hint = "\n".join(cmts) 51 | return (2, custom_hint, len(cmts)) 52 | return 0 53 | try: 54 | kloppy.unhook() 55 | print("kloppy disappears :(") 56 | del kloppy 57 | except: 58 | kloppy = kloppy_t() 59 | kloppy.hook() 60 | print("kloppy is here!") -------------------------------------------------------------------------------- /kloppy/readme.md: -------------------------------------------------------------------------------- 1 | # Kloppy 2 | 3 | Clippy for IDA Pro / HexRays 4 | 5 | ![photo taken from kloppy on caffeine](./rsrc/kloppy.png?raw=true) 6 | -------------------------------------------------------------------------------- /kloppy/rsrc/kloppy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patois/hexrays_scripts/7dbecbe2a255ff3310048b0aca672da21a5704e8/kloppy/rsrc/kloppy.png -------------------------------------------------------------------------------- /ricky/readme.md: -------------------------------------------------------------------------------- 1 | # Ricky for IDA Pro / HexRays 2 | 3 | This IDAPython script demonstrates how to paint onto an IDA Qt widget. It can be used to play a sequence of PNG images in a row. -------------------------------------------------------------------------------- /ricky/ricky.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | 4 | from PyQt5 import QtCore 5 | from PyQt5 import QtGui 6 | from PyQt5 import QtWidgets 7 | 8 | import ida_kernwin 9 | 10 | # Ricky, a PNG sequence player for IDA Pro 11 | __author__ = "patois" 12 | 13 | HOTKEY = "Ctrl-Shift-P" 14 | 15 | # ------------------------------------------------------------------------- 16 | def find_files(folder, wc): 17 | stuff = os.path.join(folder, wc) 18 | print("Finding %s" % stuff) 19 | return glob.glob(stuff) 20 | 21 | # ------------------------------------------------------------------------- 22 | class png_player_t(QtCore.QObject): 23 | def __init__(self, title, file_list, interval=200): 24 | QtCore.QObject.__init__(self) 25 | self.dead = False 26 | w = ida_kernwin.find_widget(title) 27 | if not w: 28 | raise RuntimeError("Could not find %s" % title) 29 | self.target = ida_kernwin.PluginForm.FormToPyQtWidget(w).viewport() 30 | 31 | self.title = title 32 | self.file_list = file_list 33 | self.interval = interval 34 | 35 | self.painting = False 36 | self.anim = None 37 | self._load_img_files(self.file_list) 38 | self.target.installEventFilter(self) 39 | self.t = self.timercallback_t(self.target, len(self.anim), interval) 40 | 41 | class timercallback_t(object): 42 | def __init__(self, target, n_frames, interval=200): 43 | self.interval = interval 44 | 45 | self.lane = [i for i in range(n_frames)] + [i for i in range(n_frames-1, -1, -1)] 46 | self.n = len(self.lane) 47 | self.i = 0 48 | self.target = target 49 | self.obj = ida_kernwin.register_timer(self.interval, self) 50 | if self.obj is None: 51 | raise RuntimeError("Failed to register timer") 52 | 53 | def get_frame(self): 54 | return self.lane[self.i] 55 | 56 | def die(self): 57 | ida_kernwin.unregister_timer(self.obj) 58 | 59 | def __call__(self): 60 | self.i = (self.i + 1) % self.n 61 | try: 62 | self.target.repaint() 63 | except: 64 | return -1 65 | return self.interval 66 | 67 | def _load_img_files(self, files): 68 | self.anim = [] 69 | i = 1 70 | ida_kernwin.show_wait_box("HIDECANCEL\nLoading files") 71 | try: 72 | for file in files: 73 | ida_kernwin.replace_wait_box("Loading file %d/%d" % (i, len(files))) 74 | self.anim.append(QtGui.QPixmap(file)) 75 | i += 1 76 | except: 77 | print("Failed loading file %d/%d" % (i, len(files))) 78 | finally: 79 | ida_kernwin.hide_wait_box() 80 | return 81 | 82 | def die(self): 83 | self.dead = True 84 | self.t.die() 85 | self.target.removeEventFilter(self) 86 | 87 | def is_dead(self): 88 | return self.dead 89 | 90 | def eventFilter(self, receiver, event): 91 | if not self.painting and \ 92 | self.target == receiver and \ 93 | event.type() == QtCore.QEvent.Paint: 94 | 95 | # Send a paint event that we won't intercept 96 | self.painting = True 97 | try: 98 | pev = QtGui.QPaintEvent(self.target.rect()) 99 | QtWidgets.QApplication.instance().sendEvent(self.target, pev) 100 | finally: 101 | self.painting = False 102 | 103 | painter = QtGui.QPainter(receiver) 104 | painter.setRenderHints(QtGui.QPainter.Antialiasing) 105 | 106 | # adjust opacity 107 | #painter.setOpacity(0.5) 108 | 109 | # feel free to experiment with the various compositionModes 110 | # ---> https://doc.qt.io/qt-5/qpainter.html#composition-modes 111 | painter.setCompositionMode(QtGui.QPainter.CompositionMode_SoftLight) 112 | painter.drawPixmap(self.target.rect(), self.anim[self.t.get_frame()]) 113 | painter.end() 114 | 115 | # ...and prevent the widget form painting itself again 116 | return True 117 | 118 | elif event.type() in [QtCore.QEvent.Close, QtCore.QEvent.Hide]: 119 | self.die() 120 | 121 | return QtCore.QObject.eventFilter(self, receiver, event) 122 | 123 | def pp_main(): 124 | global pp 125 | 126 | if pp and not pp.is_dead(): 127 | pp.die() 128 | pp = None 129 | return 130 | w = ida_kernwin.get_current_widget() 131 | title = "IDA View-A" 132 | if w: 133 | title = ida_kernwin.get_widget_title(w) 134 | title = ida_kernwin.ask_str(title, 0, "Please specify title of widget") 135 | if title: 136 | path = ida_kernwin.ask_str("", ida_kernwin.HIST_DIR, "Please specify path containing png files to play back") 137 | if path and os.path.exists(path): 138 | files = find_files(path, "*.png") 139 | print("found %d files" % len(files)) 140 | if len(files): 141 | interval = ida_kernwin.ask_long(100, "Please specify timer interval") 142 | if interval: 143 | pp = png_player_t(title, files, interval=interval) 144 | print("PNGs playing in widget %s" % title) 145 | 146 | try: 147 | pp 148 | ida_kernwin.info("Already installed. Press %s to start/stop playback." % HOTKEY) 149 | except: 150 | pp = None 151 | print("Press %s to start/stop playing PNG files" % HOTKEY) 152 | ida_kernwin.add_hotkey(HOTKEY, pp_main) -------------------------------------------------------------------------------- /screenrecorder/readme.md: -------------------------------------------------------------------------------- 1 | # Screenrecorder for IDA Pro / HexRays 2 | 3 | This IDAPython script demonstrates how to intercept an IDA Qt widget's paint events. It can be used to record an IDA session into a series of PNG images. Based on the IDAPython example script "paint_over_navbar.py". 4 | -------------------------------------------------------------------------------- /screenrecorder/screenrecorder.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from PyQt5 import QtCore 4 | from PyQt5 import QtGui 5 | from PyQt5 import QtWidgets 6 | 7 | import ida_kernwin 8 | import ida_diskio 9 | from datetime import datetime 10 | 11 | # Screen (widget) recorder for IDA Pro 12 | __author__ = "patois" 13 | 14 | HOTKEY = "Ctrl-Shift-R" 15 | 16 | class screen_record_t(QtCore.QObject): 17 | def __init__(self, title, path): 18 | QtCore.QObject.__init__(self) 19 | self.target = ida_kernwin.PluginForm.FormToPyQtWidget(ida_kernwin.find_widget(title)).viewport() 20 | self.target.installEventFilter(self) 21 | self.painting = False 22 | self.title = title 23 | self.path = path 24 | 25 | def eventFilter(self, receiver, event): 26 | if not self.painting and \ 27 | self.target == receiver and \ 28 | event.type() == QtCore.QEvent.Paint: 29 | 30 | # Send a paint event that we won't intercept 31 | self.painting = True 32 | try: 33 | pev = QtGui.QPaintEvent(self.target.rect()) 34 | QtWidgets.QApplication.instance().sendEvent(self.target, pev) 35 | self.pm = QtGui.QPixmap(self.target.size()) 36 | self.target.render(self.pm) 37 | finally: 38 | self.painting = False 39 | 40 | try: 41 | filename = "%s_%s" % (self.title, datetime.utcnow().strftime("%Y_%m_%d_%H_%M_%S_%f")) 42 | dst = "%s.png" % (os.path.join(self.path, filename)) 43 | print("Saving %s" % dst) 44 | self.pm.save(dst, "PNG") 45 | except: 46 | print("[!] Error saving file") 47 | return QtCore.QObject.eventFilter(self, receiver, event) 48 | 49 | def sr_main(): 50 | global sr 51 | 52 | if sr: 53 | del sr 54 | sr = None 55 | print("Stopped recording") 56 | else: 57 | w = ida_kernwin.get_current_widget() 58 | title = "IDA View-A" 59 | if w: 60 | title = ida_kernwin.get_widget_title(w) 61 | title = ida_kernwin.ask_str(title, 0, "Please specify title of widget to capture") 62 | if title: 63 | path = ida_kernwin.ask_str("", ida_kernwin.HIST_DIR, "Please specify destination path") 64 | if path and os.path.exists(path): 65 | sr = screen_record_t(title, path) 66 | print("Started recording") 67 | 68 | try: 69 | sr 70 | ida_kernwin.info("Already installed. Press %s to start/stop recording." % HOTKEY) 71 | except: 72 | sr = None 73 | sr_hotkey = ida_kernwin.add_hotkey(HOTKEY, sr_main) 74 | print("Press %s to start/stop recording" % HOTKEY) -------------------------------------------------------------------------------- /shuffle/readme.md: -------------------------------------------------------------------------------- 1 | # shuffle 2 | 3 | shuffle for IDA Pro / HexRays 4 | 5 | adds a visual effect to the decompiler 6 | 7 | ![shuffle animated gif](./rsrc/shuffle.gif?raw=true) -------------------------------------------------------------------------------- /shuffle/rsrc/shuffle.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patois/hexrays_scripts/7dbecbe2a255ff3310048b0aca672da21a5704e8/shuffle/rsrc/shuffle.gif -------------------------------------------------------------------------------- /shuffle/shuffle.py: -------------------------------------------------------------------------------- 1 | import ida_hexrays 2 | import ida_lines 3 | import ida_kernwin as kw 4 | import random 5 | import datetime 6 | 7 | __author__ = "https://github.com/patois" 8 | 9 | '''I recommend against productive use -> timers are not thread-safe''' 10 | 11 | # try messing with the following constants: 12 | 13 | # apply effect to decompiled text with up to 'MAX_LINES_THRESHOLD' number of lines 14 | MAX_LINES_THRESHOLD = 250 15 | # initial effect interval 16 | INITIAL_INTERVAL = 50 17 | # common effect interval 18 | COMMON_INTERVAL = 50 19 | # colors 20 | COLOR_MATCH_CHAR = ida_lines.SCOLOR_DREF 21 | COLOR_NOMATCH_CHAR = ida_lines.SCOLOR_DEFAULT 22 | 23 | class TextModifier: 24 | def __init__(self, n, iv, vu): 25 | self.count = n 26 | self.n = n 27 | self.iv = iv 28 | self.vu = vu 29 | self.is_disabled = False 30 | self.ref_pc = [(ida_lines.tag_remove(sl.line).lstrip(),[]) for sl in self.vu.cfunc.get_pseudocode()] 31 | self.timer = kw.register_timer(0, self.timer_cb) 32 | 33 | def shuffle_text(self): 34 | pc = self.vu.cfunc.get_pseudocode() 35 | 36 | if len(pc) > MAX_LINES_THRESHOLD or self.is_disabled: 37 | return 38 | 39 | random.seed(datetime.datetime.now()) 40 | lines = [ida_lines.tag_remove(sl.line) for sl in pc] 41 | 42 | pc.clear() 43 | sl = kw.simpleline_t() 44 | try: 45 | for line_no in range(len(lines)): 46 | line = lines[line_no] 47 | code = line.strip() 48 | prefix = " " * (len(line) - len(code)) 49 | if self.n != 1: 50 | shuffled = random.sample(code, len(code)) 51 | else: 52 | shuffled = list(code) 53 | 54 | for char_no in range(len(shuffled)): 55 | if shuffled[char_no] == self.ref_pc[line_no][0][char_no]: 56 | self.ref_pc[line_no][1].append(char_no) 57 | 58 | if self.n != self.count: 59 | for char_no in self.ref_pc[line_no][1]: 60 | shuffled[char_no] = ida_lines.COLSTR(self.ref_pc[line_no][0][char_no], COLOR_MATCH_CHAR) 61 | 62 | sl.line = prefix + ida_lines.COLSTR("".join(shuffled), COLOR_NOMATCH_CHAR) 63 | pc.push_back(sl) 64 | except: 65 | pass 66 | return 67 | 68 | def set_disabled(self, disabled): 69 | self.is_disabled = disabled 70 | kw.unregister_timer(self.timer) 71 | 72 | def get_cur_interval(self): 73 | cur_iv = -1 74 | 75 | # initial inverval 76 | if self.n == self.count: 77 | cur_iv = INITIAL_INTERVAL 78 | # common 79 | elif self.n == 1: 80 | cur_iv = COMMON_INTERVAL 81 | else: 82 | cur_iv = self.iv if self.n else -1 83 | return cur_iv 84 | 85 | def timer_cb(self): 86 | if self.is_disabled: 87 | return -1 88 | try: 89 | self.vu.refresh_view(False) 90 | except: 91 | pass 92 | cur_iv = self.get_cur_interval() 93 | self.n -= 1 94 | return cur_iv 95 | 96 | class shuffle_t(ida_hexrays.Hexrays_Hooks): 97 | def __init__(self): 98 | ida_hexrays.Hexrays_Hooks.__init__(self) 99 | self.t = None 100 | 101 | def text_ready(self, vu): 102 | if not self.t: 103 | self.t = TextModifier(20, 1, vu) 104 | else: 105 | if self.t.n: 106 | self.t.shuffle_text() 107 | else: 108 | self.t = None 109 | return 0 110 | 111 | def switch_pseudocode(self, vu): 112 | if self.t: 113 | self.t.set_disabled(True) 114 | self.t = None 115 | return 0 116 | 117 | def close_pseudocode(self, vu): 118 | if self.t: 119 | self.t.set_disabled(True) 120 | self.t = None 121 | return 0 122 | 123 | if ida_hexrays.init_hexrays_plugin(): 124 | try: 125 | shuffle 126 | shuffle.unhook() 127 | print("shuffle: removing hook") 128 | del shuffle 129 | except: 130 | shuffle = shuffle_t() 131 | shuffle.hook() 132 | print("shuffle: hook installed") 133 | else: 134 | print("shuffle: hexrays unavailable.") 135 | --------------------------------------------------------------------------------