├── ida-plugin.json ├── LICENSE ├── README.md ├── .gitignore ├── CHANGELOG.md └── LazyIDA.py /ida-plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "IDAMetadataDescriptorVersion": 1, 3 | "plugin": { 4 | "name": "LazyIDA", 5 | "entryPoint": "LazyIDA.py", 6 | "categories": ["api-scripting-and-automation"], 7 | "logoPath": "logo.png", 8 | "idaVersions": ">=7.4", 9 | "description" : "Make your IDA Lazy!", 10 | "version": "1.1.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Lays 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LazyIDA 2 | Make your IDA Lazy! 3 | 4 | # Install 5 | 1. put `LazyIDA.py` into `plugins` folder under your IDA Pro installation path. 6 | 7 | # Features 8 | - Remove function return type in Hex-Rays: 9 | 10 | ![2016-06-12 11 05 29](https://cloud.githubusercontent.com/assets/5360374/15991889/2dad5d62-30f2-11e6-8d4b-e4efb0b73c77.png) 11 | 12 | - Convert data into different formats, output will also be automatically copied to the clipboard: 13 | 14 | ![2016-06-12 11 01 57](https://cloud.githubusercontent.com/assets/5360374/15991854/b813070a-30f1-11e6-931e-08ae85355cca.png) 15 | ![2016-06-12 11 03 18](https://cloud.githubusercontent.com/assets/5360374/15991863/e5271146-30f1-11e6-89ac-bafd46eb1e45.png) 16 | - Scan for format string vulnerabilities: 17 | 18 | ![2016-06-15 8 19 03](https://cloud.githubusercontent.com/assets/5360374/16064234/da39aa8c-32d1-11e6-89b8-1709cef270f5.png) 19 | - Jump to vtable functions by double clicking 20 | - Lazy shortcuts: 21 | - Disasm Window: 22 | - `w`: Copy address of current line into clipboard 23 | - Hex-rays Window: 24 | - `w`: Copy address of current item into clipboard 25 | - `c`: Copy name of current item into clipboard 26 | - `v`: Remove return type of current item 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## April 2025 (v1.0.0.5) 2 | ### Bug Fixes 3 | - Corrected variable in "Goto location" print statement to display accurate file offset information. 4 | 5 | ## March 2025 (v1.0.0.4) 6 | ### New Features 7 | - Added "Copy FO" command (Shift-W) 8 | - Added "Goto clipboard FO" command (Ctrl-Shift-G) 9 | 10 | ### Improvements 11 | - Enhanced output to display whether you are going to EA, FO or named function 12 | - Renamed "Convert to" -> "Dump as" for more accurate functionality description 13 | 14 | ### Bug Fixes 15 | - Fixed parse_location, restoring the ability to navigate to named functions 16 | - Fixed missing badaddr check in HexRaysView 17 | - Normalized command descriptions across IDAView and HexRaysView 18 | 19 | ## August 2024 - November 2024 (v1.0.0.3) 20 | ### Major Changes 21 | - Added support for IDA 9.0 22 | 23 | ### Bug Fixes 24 | - Fixed widget type checks for IDA 9.0 compatibility 25 | - Added try/except handling for BWN_HEXVIEW/BWN_DUMP compatibility 26 | - Fixed typo in inf_is_16bit function 27 | 28 | ## October 2022 - February 2022 29 | ### Bug Fixes 30 | - Fixed TypeError when get_highlight returns None 31 | - Corrected selection size calculation for proper data extraction 32 | 33 | ## June 2021 34 | ### Improvements 35 | - Implemented automatic copying of conversion results to clipboard 36 | - Removed dependency on external clipboard libraries 37 | 38 | ## September 2017 - April 2018 (v1.0.0.2) 39 | ### Major Changes 40 | - Added full support for IDA 7.0 and 7.1 41 | - Added Python 3 compatibility 42 | 43 | ### Bug Fixes 44 | - Fixed UI element access methods for IDA 7.0+ 45 | - Replaced deprecated IDA API attributes 46 | - Fixed bug in copy function name feature 47 | 48 | ## December 2016 - February 2018 (v1.0.0.1) 49 | ### Improvements 50 | - Added toggle functionality to remove/restore function return types 51 | - Enhanced format string vulnerability detection on different architectures 52 | - Fixed various memory handling issues for data conversion 53 | 54 | ## June 2016 - August 2016 55 | ### New Features 56 | - Added "Get xored data" feature for binary analysis 57 | - Added "Fill with NOPs" functionality for code patching 58 | - Implemented auto-jump to virtual functions on double click 59 | - Added support for word-sized data in conversion functions 60 | - Enhanced format string vulnerability detection for x86 and ARM 61 | 62 | ## Initial Release (June 12, 2016) 63 | ### Features 64 | - Remove function return type in Hex-Rays decompiler 65 | - Convert data into different formats (strings, hex strings, C arrays, Python lists) 66 | - Scan for format string vulnerabilities 67 | - Lazy shortcuts for common operations 68 | -------------------------------------------------------------------------------- /LazyIDA.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from __future__ import print_function 3 | from struct import unpack 4 | 5 | # this plugin requires IDA 7.4 or newer 6 | try: 7 | import idaapi 8 | import idautils 9 | import idc 10 | import ida_pro 11 | SUPPORTED_IDA = ida_pro.IDA_SDK_VERSION >= 740 12 | 13 | if ida_pro.IDA_SDK_VERSION >= 920: 14 | from PySide6.QtWidgets import QApplication 15 | else: 16 | from PyQt5.Qt import QApplication 17 | except Exception as e: 18 | print(e) 19 | SUPPORTED_IDA = False 20 | 21 | # is this deemed to be a compatible environment for the plugin to load? 22 | if not SUPPORTED_IDA: 23 | print("LazyIDA plugin is not compatible with this IDA version") 24 | 25 | ACTION_CONVERT = ["lazyida:convert%d" % i for i in range(10)] 26 | ACTION_SCANVUL = "lazyida:scanvul" 27 | ACTION_COPYEA = "lazyida:copyea" 28 | ACTION_COPYFO = "lazyida:copyfo" 29 | ACTION_GOTOCLIPEA = "lazyida:gotoclipea" 30 | ACTION_GOTOCLIPFO = "lazyida:gotoclipfo" 31 | ACTION_XORDATA = "lazyida:xordata" 32 | ACTION_FILLNOP = "lazyida:fillnop" 33 | 34 | ACTION_HX_REMOVERETTYPE = "lazyida:hx_removerettype" 35 | ACTION_HX_COPYEA = "lazyida:hx_copyea" 36 | ACTION_HX_COPYFO = "lazyida:hx_copyfo" 37 | ACTION_HX_COPYNAME = "lazyida:hx_copyname" 38 | ACTION_HX_GOTOCLIPEA = "lazyida:hx_gotoclipea" 39 | ACTION_HX_GOTOCLIPFO = "lazyida:hx_gotoclipfo" 40 | 41 | u16 = lambda x: unpack("= 770: 138 | target_attr = "widget_type" 139 | else: 140 | target_attr = "form_type" 141 | 142 | if idaapi.IDA_SDK_VERSION >= 900: 143 | try: 144 | dump_type = idaapi.BWN_HEXVIEW 145 | except: 146 | dump_type = idaapi.BWN_DUMP 147 | else: 148 | dump_type = idaapi.BWN_DUMP 149 | 150 | if ctx.__getattribute__(target_attr) in (idaapi.BWN_DISASM, dump_type): 151 | return idaapi.AST_ENABLE_FOR_WIDGET 152 | else: 153 | return idaapi.AST_DISABLE_FOR_WIDGET 154 | 155 | class menu_action_handler_t(idaapi.action_handler_t): 156 | """ 157 | Action handler for menu actions 158 | """ 159 | def __init__(self, action): 160 | idaapi.action_handler_t.__init__(self) 161 | self.action = action 162 | 163 | def activate(self, ctx): 164 | if self.action in ACTION_CONVERT: 165 | # convert (dump as) 166 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 167 | if idaapi.read_selection(view, t0, t1): 168 | start, end = t0.place(view).toea(), t1.place(view).toea() 169 | size = end - start + 1 170 | elif idc.get_item_size(idc.get_screen_ea()) > 1: 171 | start = idc.get_screen_ea() 172 | size = idc.get_item_size(start) 173 | end = start + size 174 | else: 175 | return False 176 | 177 | data = idc.get_bytes(start, size) 178 | if isinstance(data, str): # python2 compatibility 179 | data = bytearray(data) 180 | name = idc.get_name(start, idc.GN_VISIBLE) 181 | if not name: 182 | name = "data" 183 | if data: 184 | print("\n[+] Dump 0x%X - 0x%X (%u bytes) :" % (start, end, size)) 185 | if self.action == ACTION_CONVERT[0]: 186 | # escaped string 187 | output = '"%s"' % "".join("\\x%02X" % b for b in data) 188 | elif self.action == ACTION_CONVERT[1]: 189 | # hex string 190 | output = "".join("%02X" % b for b in data) 191 | elif self.action == ACTION_CONVERT[2]: 192 | # C array 193 | output = "unsigned char %s[%d] = {" % (name, size) 194 | for i in range(size): 195 | if i % 16 == 0: 196 | output += "\n " 197 | output += "0x%02X, " % data[i] 198 | output = output[:-2] + "\n};" 199 | elif self.action == ACTION_CONVERT[3]: 200 | # C array word 201 | data += b"\x00" 202 | array_size = (size + 1) // 2 203 | output = "unsigned short %s[%d] = {" % (name, array_size) 204 | for i in range(0, size, 2): 205 | if i % 16 == 0: 206 | output += "\n " 207 | output += "0x%04X, " % u16(data[i:i+2]) 208 | output = output[:-2] + "\n};" 209 | elif self.action == ACTION_CONVERT[4]: 210 | # C array dword 211 | data += b"\x00" * 3 212 | array_size = (size + 3) // 4 213 | output = "unsigned int %s[%d] = {" % (name, array_size) 214 | for i in range(0, size, 4): 215 | if i % 32 == 0: 216 | output += "\n " 217 | output += "0x%08X, " % u32(data[i:i+4]) 218 | output = output[:-2] + "\n};" 219 | elif self.action == ACTION_CONVERT[5]: 220 | # C array qword 221 | data += b"\x00" * 7 222 | array_size = (size + 7) // 8 223 | output = "unsigned long %s[%d] = {" % (name, array_size) 224 | for i in range(0, size, 8): 225 | if i % 32 == 0: 226 | output += "\n " 227 | output += "0x%016X, " % u64(data[i:i+8]) 228 | output = output[:-2] + "\n};" 229 | elif self.action == ACTION_CONVERT[6]: 230 | # python list 231 | output = "[%s]" % ", ".join("0x%02X" % b for b in data) 232 | elif self.action == ACTION_CONVERT[7]: 233 | # python list word 234 | data += b"\x00" 235 | output = "[%s]" % ", ".join("0x%04X" % u16(data[i:i+2]) for i in range(0, size, 2)) 236 | elif self.action == ACTION_CONVERT[8]: 237 | # python list dword 238 | data += b"\x00" * 3 239 | output = "[%s]" % ", ".join("0x%08X" % u32(data[i:i+4]) for i in range(0, size, 4)) 240 | elif self.action == ACTION_CONVERT[9]: 241 | # python list qword 242 | data += b"\x00" * 7 243 | output = "[%s]" % ", ".join("%#018X" % u64(data[i:i+8]) for i in range(0, size, 8)).replace("0X", "0x") 244 | copy_to_clip(output) 245 | print(output) 246 | elif self.action == ACTION_XORDATA: 247 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 248 | if idaapi.read_selection(view, t0, t1): 249 | start, end = t0.place(view).toea(), t1.place(view).toea() 250 | else: 251 | if idc.get_item_size(idc.get_screen_ea()) > 1: 252 | start = idc.get_screen_ea() 253 | end = start + idc.get_item_size(start) 254 | else: 255 | return False 256 | 257 | data = idc.get_bytes(start, end - start) 258 | if isinstance(data, str): # python2 compatibility 259 | data = bytearray(data) 260 | x = idaapi.ask_long(0, "Xor with...") 261 | if x: 262 | x &= 0xFF 263 | print("\n[+] Xor 0x%X - 0x%X (%u bytes) with 0x%02X:" % (start, end, end - start, x)) 264 | print(repr("".join(chr(b ^ x) for b in data))) 265 | elif self.action == ACTION_FILLNOP: 266 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 267 | if idaapi.read_selection(view, t0, t1): 268 | start, end = t0.place(view).toea(), t1.place(view).toea() 269 | idaapi.patch_bytes(start, b"\x90" * (end - start)) 270 | print("\n[+] Fill 0x%X - 0x%X (%u bytes) with NOPs" % (start, end, end - start)) 271 | elif self.action == ACTION_SCANVUL: 272 | print("\n[+] Finding Format String Vulnerability...") 273 | found = [] 274 | for addr in idautils.Functions(): 275 | name = idc.get_func_name(addr) 276 | if "printf" in name and "v" not in name and idc.get_segm_name(addr) in (".text", ".plt", ".idata", ".plt.got"): 277 | xrefs = idautils.CodeRefsTo(addr, False) 278 | for xref in xrefs: 279 | vul = self.check_fmt_function(name, xref) 280 | if vul: 281 | found.append(vul) 282 | if found: 283 | print("[!] Done! %d possible vulnerabilities found." % len(found)) 284 | ch = VulnChoose("Vulnerability", found, None, False) 285 | ch.Show() 286 | else: 287 | print("[-] No format string vulnerabilities found.") 288 | else: 289 | return 0 290 | 291 | return 1 292 | 293 | def update(self, ctx): 294 | return idaapi.AST_ENABLE_ALWAYS 295 | 296 | @staticmethod 297 | def check_fmt_function(name, addr): 298 | """ 299 | Check if the format string argument is not valid 300 | """ 301 | function_head = idc.get_func_attr(addr, idc.FUNCATTR_START) 302 | 303 | while True: 304 | addr = idc.prev_head(addr) 305 | op = idc.print_insn_mnem(addr).lower() 306 | dst = idc.print_operand(addr, 0) 307 | 308 | if op in ("ret", "retn", "jmp", "b") or addr < function_head: 309 | return 310 | 311 | c = idc.get_cmt(addr, 0) 312 | if c and c.lower() == "format": 313 | break 314 | elif name.endswith(("snprintf_chk",)): 315 | if op in ("mov", "lea") and dst.endswith(("r8", "r8d", "[esp+10h]")): 316 | break 317 | elif name.endswith(("sprintf_chk",)): 318 | if op in ("mov", "lea") and (dst.endswith(("rcx", "[esp+0Ch]", "R3")) or 319 | dst.endswith("ecx") and BITS == 64): 320 | break 321 | elif name.endswith(("snprintf", "fnprintf")): 322 | if op in ("mov", "lea") and (dst.endswith(("rdx", "[esp+8]", "R2")) or 323 | dst.endswith("edx") and BITS == 64): 324 | break 325 | elif name.endswith(("sprintf", "fprintf", "dprintf", "printf_chk")): 326 | if op in ("mov", "lea") and (dst.endswith(("rsi", "[esp+4]", "R1")) or 327 | dst.endswith("esi") and BITS == 64): 328 | break 329 | elif name.endswith("printf"): 330 | if op in ("mov", "lea") and (dst.endswith(("rdi", "[esp]", "R0")) or 331 | dst.endswith("edi") and BITS == 64): 332 | break 333 | 334 | # format arg found, check its type and value 335 | # get last oprend 336 | op_index = idc.generate_disasm_line(addr, 0).count(",") 337 | op_type = idc.get_operand_type(addr, op_index) 338 | opnd = idc.print_operand(addr, op_index) 339 | 340 | if op_type == idc.o_reg: 341 | # format is in register, try to track back and get the source 342 | _addr = addr 343 | while True: 344 | _addr = idc.prev_head(_addr) 345 | _op = idc.print_insn_mnem(_addr).lower() 346 | if _op in ("ret", "retn", "jmp", "b") or _addr < function_head: 347 | break 348 | elif _op in ("mov", "lea", "ldr") and idc.print_operand(_addr, 0) == opnd: 349 | op_type = idc.get_operand_type(_addr, 1) 350 | opnd = idc.print_operand(_addr, 1) 351 | addr = _addr 352 | break 353 | 354 | if op_type == idc.o_imm or op_type == idc.o_mem: 355 | # format is a memory address, check if it's in writable segment 356 | op_addr = idc.get_operand_value(addr, op_index) 357 | seg = idaapi.getseg(op_addr) 358 | if seg: 359 | if not seg.perm & idaapi.SEGPERM_WRITE: 360 | # format is in read-only segment 361 | return 362 | 363 | print("0x%X: Possible Vulnerability: %s, format = %s" % (addr, name, opnd)) 364 | return ["0x%X" % addr, name, opnd] 365 | 366 | class hexrays_action_handler_t(idaapi.action_handler_t): 367 | """ 368 | Action handler for hexrays actions 369 | """ 370 | def __init__(self, action): 371 | idaapi.action_handler_t.__init__(self) 372 | self.action = action 373 | self.ret_type = {} 374 | 375 | def activate(self, ctx): 376 | if self.action == ACTION_HX_REMOVERETTYPE: 377 | vdui = idaapi.get_widget_vdui(ctx.widget) 378 | self.remove_rettype(vdui) 379 | vdui.refresh_ctext() 380 | elif self.action == ACTION_HX_COPYEA: 381 | ea = idaapi.get_screen_ea() 382 | if ea != idaapi.BADADDR: 383 | copy_to_clip("0x%X" % ea) 384 | print("Address 0x%X (EA) has been copied to clipboard" % ea) 385 | elif self.action == ACTION_HX_COPYFO: 386 | ea = idaapi.get_screen_ea() 387 | if ea != idaapi.BADADDR: 388 | fo = idaapi.get_fileregion_offset(ea) 389 | if fo != idaapi.BADADDR: 390 | copy_to_clip("0x%X" % fo) 391 | print("Address 0x%X (FO) has been copied to clipboard" % fo) 392 | elif self.action == ACTION_HX_COPYNAME: 393 | highlight = idaapi.get_highlight(idaapi.get_current_viewer()) 394 | name = highlight[0] if highlight else None 395 | if name: 396 | copy_to_clip(name) 397 | print("'%s' has been copied to clipboard" % name) 398 | elif self.action == ACTION_HX_GOTOCLIPEA: 399 | loc, is_named, name = parse_location(clip_text(), False) 400 | if loc != idaapi.BADADDR: 401 | if is_named: 402 | print("Goto named location '%s' 0x%X" % (name, loc)) 403 | else: 404 | print("Goto location 0x%X (EA)" % loc) 405 | idc.jumpto(loc) 406 | elif self.action == ACTION_HX_GOTOCLIPFO: 407 | loc, is_named, name = parse_location(clip_text(), True) 408 | if loc != idaapi.BADADDR: 409 | if is_named: 410 | print("Goto named location '%s' 0x%X" % (name, loc)) 411 | else: 412 | print("Goto location 0x%X (FO)" % idaapi.get_fileregion_offset(loc)) 413 | idc.jumpto(loc) 414 | else: 415 | return 0 416 | 417 | return 1 418 | 419 | def update(self, ctx): 420 | vdui = idaapi.get_widget_vdui(ctx.widget) 421 | return idaapi.AST_ENABLE_FOR_WIDGET if vdui else idaapi.AST_DISABLE_FOR_WIDGET 422 | 423 | def remove_rettype(self, vu): 424 | if vu.item.citype == idaapi.VDI_FUNC: 425 | # current function 426 | ea = vu.cfunc.entry_ea 427 | old_func_type = idaapi.tinfo_t() 428 | if not vu.cfunc.get_func_type(old_func_type): 429 | return False 430 | elif vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr() and vu.item.e.type.is_funcptr(): 431 | # call xxx 432 | ea = vu.item.get_ea() 433 | old_func_type = idaapi.tinfo_t() 434 | 435 | func = idaapi.get_func(ea) 436 | if func: 437 | try: 438 | cfunc = idaapi.decompile(func) 439 | except idaapi.DecompilationFailure: 440 | return False 441 | 442 | if not cfunc.get_func_type(old_func_type): 443 | return False 444 | else: 445 | return False 446 | else: 447 | return False 448 | 449 | fi = idaapi.func_type_data_t() 450 | if ea != idaapi.BADADDR and old_func_type.get_func_details(fi): 451 | # Return type is already void 452 | if fi.rettype.is_decl_void(): 453 | # Restore ret type 454 | if ea not in self.ret_type: 455 | return True 456 | ret = self.ret_type[ea] 457 | else: 458 | # Save ret type and change it to void 459 | self.ret_type[ea] = fi.rettype 460 | ret = idaapi.BT_VOID 461 | 462 | # Create new function info with new rettype 463 | fi.rettype = idaapi.tinfo_t(ret) 464 | 465 | # Create new function type with function info 466 | new_func_type = idaapi.tinfo_t() 467 | new_func_type.create_func(fi) 468 | 469 | # Apply new function type 470 | if idaapi.apply_tinfo(ea, new_func_type, idaapi.TINFO_DEFINITE): 471 | return vu.refresh_view(True) 472 | 473 | return False 474 | 475 | class UI_Hook(idaapi.UI_Hooks): 476 | def __init__(self): 477 | idaapi.UI_Hooks.__init__(self) 478 | 479 | def finish_populating_widget_popup(self, form, popup): 480 | form_type = idaapi.get_widget_type(form) 481 | 482 | if idaapi.IDA_SDK_VERSION >= 900: 483 | try: 484 | dump_type = idaapi.BWN_HEXVIEW 485 | except: 486 | dump_type = idaapi.BWN_DUMP 487 | else: 488 | dump_type = idaapi.BWN_DUMP 489 | 490 | if form_type == idaapi.BWN_DISASM or form_type == dump_type: 491 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 492 | if idaapi.read_selection(view, t0, t1) or idc.get_item_size(idc.get_screen_ea()) > 1: 493 | idaapi.attach_action_to_popup(form, popup, ACTION_XORDATA, None) 494 | idaapi.attach_action_to_popup(form, popup, ACTION_FILLNOP, None) 495 | for action in ACTION_CONVERT: 496 | idaapi.attach_action_to_popup(form, popup, action, "Dump/") 497 | 498 | if form_type == idaapi.BWN_DISASM and (ARCH, BITS) in [(idaapi.PLFM_386, 32), 499 | (idaapi.PLFM_386, 64), 500 | (idaapi.PLFM_ARM, 32),]: 501 | idaapi.attach_action_to_popup(form, popup, ACTION_SCANVUL, None) 502 | 503 | 504 | class HexRays_Hook(object): 505 | def callback(self, event, *args): 506 | if event == idaapi.hxe_populating_popup: 507 | form, phandle, vu = args 508 | if vu.item.citype == idaapi.VDI_FUNC or (vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr() and vu.item.e.type.is_funcptr()): 509 | idaapi.attach_action_to_popup(form, phandle, ACTION_HX_REMOVERETTYPE, None) 510 | elif event == idaapi.hxe_double_click: 511 | vu, shift_state = args 512 | # auto jump to target if clicked item is xxx->func(); 513 | if vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr(): 514 | expr = idaapi.tag_remove(vu.item.e.print1(None)) 515 | if "->" in expr: 516 | # find target function 517 | name = expr.split("->")[-1] 518 | addr = idc.get_name_ea_simple(name) 519 | if addr == idaapi.BADADDR: 520 | # try class::function 521 | e = vu.item.e 522 | while e.x: 523 | e = e.x 524 | addr = idc.get_name_ea_simple("%s::%s" % (str(e.type).split()[0], name)) 525 | 526 | if addr != idaapi.BADADDR: 527 | idc.jumpto(addr) 528 | return 1 529 | return 0 530 | 531 | class LazyIDA_t(idaapi.plugin_t): 532 | flags = idaapi.PLUGIN_HIDE 533 | comment = "LazyIDA" 534 | help = "" 535 | wanted_name = "LazyIDA" 536 | wanted_hotkey = "" 537 | 538 | def init(self): 539 | self.hexrays_inited = False 540 | self.registered_actions = [] 541 | self.registered_hx_actions = [] 542 | 543 | global ARCH 544 | global BITS 545 | ARCH = idaapi.ph_get_id() 546 | 547 | if idaapi.IDA_SDK_VERSION >= 900: 548 | if idaapi.inf_is_64bit(): 549 | BITS = 64 550 | elif idaapi.inf_is_32bit_exactly(): 551 | BITS = 32 552 | elif idaapi.inf_is_16bit(): 553 | BITS = 16 554 | else: 555 | raise ValueError 556 | else: 557 | info = idaapi.get_inf_structure() 558 | if info.is_64bit(): 559 | BITS = 64 560 | elif info.is_32bit(): 561 | BITS = 32 562 | else: 563 | BITS = 16 564 | 565 | print("LazyIDA (v1.1.0.0) plugin has been loaded.") 566 | 567 | # Register menu actions 568 | menu_actions = ( 569 | idaapi.action_desc_t(ACTION_CONVERT[0], "Dump as string", menu_action_handler_t(ACTION_CONVERT[0]), None, None, 80), 570 | idaapi.action_desc_t(ACTION_CONVERT[1], "Dump as hex string", menu_action_handler_t(ACTION_CONVERT[1]), None, None, 8), 571 | idaapi.action_desc_t(ACTION_CONVERT[2], "Dump as C/C++ array (BYTE)", menu_action_handler_t(ACTION_CONVERT[2]), None, None, 38), 572 | idaapi.action_desc_t(ACTION_CONVERT[3], "Dump as C/C++ array (WORD)", menu_action_handler_t(ACTION_CONVERT[3]), None, None, 38), 573 | idaapi.action_desc_t(ACTION_CONVERT[4], "Dump as C/C++ array (DWORD)", menu_action_handler_t(ACTION_CONVERT[4]), None, None, 38), 574 | idaapi.action_desc_t(ACTION_CONVERT[5], "Dump as C/C++ array (QWORD)", menu_action_handler_t(ACTION_CONVERT[5]), None, None, 38), 575 | idaapi.action_desc_t(ACTION_CONVERT[6], "Dump as python list (BYTE)", menu_action_handler_t(ACTION_CONVERT[6]), None, None, 201), 576 | idaapi.action_desc_t(ACTION_CONVERT[7], "Dump as python list (WORD)", menu_action_handler_t(ACTION_CONVERT[7]), None, None, 201), 577 | idaapi.action_desc_t(ACTION_CONVERT[8], "Dump as python list (DWORD)", menu_action_handler_t(ACTION_CONVERT[8]), None, None, 201), 578 | idaapi.action_desc_t(ACTION_CONVERT[9], "Dump as python list (QWORD)", menu_action_handler_t(ACTION_CONVERT[9]), None, None, 201), 579 | idaapi.action_desc_t(ACTION_XORDATA, "Get xored data", menu_action_handler_t(ACTION_XORDATA), None, None, 9), 580 | idaapi.action_desc_t(ACTION_FILLNOP, "Fill with NOPs", menu_action_handler_t(ACTION_FILLNOP), None, None, 9), 581 | idaapi.action_desc_t(ACTION_SCANVUL, "Scan format string vulnerabilities", menu_action_handler_t(ACTION_SCANVUL), None, None, 160), 582 | ) 583 | for action in menu_actions: 584 | idaapi.register_action(action) 585 | self.registered_actions.append(action.name) 586 | 587 | # Register hotkey actions 588 | hotkey_actions = ( 589 | idaapi.action_desc_t(ACTION_COPYEA, "Copy EA", hotkey_action_handler_t(ACTION_COPYEA), "w", "Copy current EA", 0), 590 | idaapi.action_desc_t(ACTION_COPYFO, "Copy FO", hotkey_action_handler_t(ACTION_COPYFO), "Shift-W", "Copy current FO", 0), 591 | idaapi.action_desc_t(ACTION_GOTOCLIPEA, "Goto clipboard EA", hotkey_action_handler_t(ACTION_GOTOCLIPEA), "Shift-G"), 592 | idaapi.action_desc_t(ACTION_GOTOCLIPFO, "Goto clipboard FO", hotkey_action_handler_t(ACTION_GOTOCLIPFO), "Ctrl-Shift-G"), 593 | ) 594 | for action in hotkey_actions: 595 | idaapi.register_action(action) 596 | self.registered_actions.append(action.name) 597 | 598 | # Add ui hook 599 | self.ui_hook = UI_Hook() 600 | self.ui_hook.hook() 601 | 602 | # Add hexrays ui callback 603 | if idaapi.init_hexrays_plugin(): 604 | addon = idaapi.addon_info_t() 605 | addon.id = "tw.l4ys.lazyida" 606 | addon.name = "LazyIDA" 607 | addon.producer = "Lays" 608 | addon.url = "https://github.com/L4ys/LazyIDA" 609 | addon.version = "1.1.0.0" 610 | idaapi.register_addon(addon) 611 | 612 | hx_actions = ( 613 | idaapi.action_desc_t(ACTION_HX_REMOVERETTYPE, "Remove return type", hexrays_action_handler_t(ACTION_HX_REMOVERETTYPE), "v"), 614 | idaapi.action_desc_t(ACTION_HX_COPYEA, "Copy EA", hexrays_action_handler_t(ACTION_HX_COPYEA), "w", "Copy current EA", 0), 615 | idaapi.action_desc_t(ACTION_HX_COPYFO, "Copy FO", hexrays_action_handler_t(ACTION_HX_COPYFO), "Shift-W", "Copy current FO", 0), 616 | idaapi.action_desc_t(ACTION_HX_GOTOCLIPEA, "Goto clipboard EA", hexrays_action_handler_t(ACTION_HX_GOTOCLIPEA), "Shift-G"), 617 | idaapi.action_desc_t(ACTION_HX_GOTOCLIPFO, "Goto clipboard FO", hexrays_action_handler_t(ACTION_HX_GOTOCLIPFO), "Ctrl-Shift-G"), 618 | idaapi.action_desc_t(ACTION_HX_COPYNAME, "Copy name", hexrays_action_handler_t(ACTION_HX_COPYNAME), "c"), 619 | ) 620 | for action in hx_actions: 621 | idaapi.register_action(action) 622 | self.registered_hx_actions.append(action.name) 623 | 624 | self.hx_hook = HexRays_Hook() 625 | idaapi.install_hexrays_callback(self.hx_hook.callback) 626 | self.hexrays_inited = True 627 | 628 | return idaapi.PLUGIN_KEEP 629 | 630 | def run(self, arg): 631 | pass 632 | 633 | def term(self): 634 | if hasattr(self, "ui_hook"): 635 | self.ui_hook.unhook() 636 | 637 | # Unregister actions 638 | for action in self.registered_actions: 639 | idaapi.unregister_action(action) 640 | 641 | if self.hexrays_inited: 642 | # Unregister hexrays actions 643 | for action in self.registered_hx_actions: 644 | idaapi.unregister_action(action) 645 | if self.hx_hook: 646 | idaapi.remove_hexrays_callback(self.hx_hook.callback) 647 | idaapi.term_hexrays_plugin() 648 | 649 | def PLUGIN_ENTRY(): 650 | return LazyIDA_t() 651 | --------------------------------------------------------------------------------