├── .gitignore ├── LICENSE ├── LazyIDA.py └── README.md /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /LazyIDA.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from __future__ import print_function 3 | import binascii 4 | from struct import unpack 5 | import idaapi 6 | import idautils 7 | import idc 8 | import ida_dbg 9 | import base64 10 | import ida_ida 11 | 12 | from PyQt5 import QtCore 13 | from PyQt5.Qt import QApplication 14 | from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QLabel, QRadioButton, QTextEdit, QPushButton, QLineEdit, \ 15 | QMessageBox, QFileDialog, QComboBox 16 | 17 | ACTION_CONVERT = ["lazyida:convert%d" % i for i in range(10)] 18 | ACTION_SCANVUL = "lazyida:scanvul" 19 | ACTION_COPYEA = "lazyida:copyea" 20 | ACTION_GOTOCLIP = "lazyida:gotoclip" 21 | ACTION_XORDATA = "lazyida:xordata" 22 | ACTION_FILLNOP = "lazyida:fillnop" 23 | ACTION_PASTE = "lazyida:paste" 24 | ACTION_DUMPER = "lazyida:dumper" 25 | ACTION_JMP = "lazyida:jmper" 26 | ACTION_COPY_RVA = "lazyida:copy_rva" 27 | 28 | ACTION_HX_REMOVERETTYPE = "lazyida:hx_removerettype" 29 | ACTION_HX_COPYEA = "lazyida:hx_copyea" 30 | ACTION_HX_COPYNAME = "lazyida:hx_copyname" 31 | ACTION_HX_GOTOCLIP = "lazyida:hx_gotoclip" 32 | 33 | u16 = lambda x: unpack(" 0: 105 | self.combobox_new_base.setCurrentIndex(len(history_jmp_base) - 1) 106 | else: 107 | self.combobox_new_base.addItem(toHex(self.cur_image_base)) 108 | self.combobox_new_base.setCurrentIndex(0) 109 | 110 | self.combobox_new_base.setEditable(True) 111 | self.btn_jmp = QPushButton("Jump") 112 | 113 | layout_main.addLayout(layout_cur_base) 114 | layout_main.addLayout(layout_new_base) 115 | layout_main.addLayout(layout_target) 116 | layout_main.addWidget(self.btn_jmp) 117 | self.btn_jmp.clicked.connect(self.jmp_clicked) 118 | 119 | self.setLayout(layout_main) 120 | self.show() 121 | self.exec_() 122 | 123 | def keyPressEvent(self, event): 124 | key_code = event.key() 125 | if key_code == QtCore.Qt.Key_Escape: 126 | self.close() 127 | elif key_code == QtCore.Qt.Key_Enter: 128 | self.jmp_clicked() 129 | 130 | def jmp_clicked(self): 131 | target_base_hex = hex_cleaner(self.combobox_new_base.currentText()) 132 | target = int(hex_cleaner(self.edit_target_addr.text()), 16) 133 | target_base = int(target_base_hex, 16) 134 | offset = target - target_base 135 | real_offset = offset + self.cur_image_base 136 | if target_base_hex not in history_jmp_base: 137 | history_jmp_base.append(target_base_hex) 138 | print("original base: %x new base: %x offset:%x" % (self.cur_image_base, target_base, offset)) 139 | idc.jumpto(real_offset) 140 | self.close() 141 | 142 | 143 | class dumper_windows(QDialog): 144 | def __init__(self): 145 | super(dumper_windows, self).__init__() 146 | self.addr = idc.get_screen_ea() 147 | self.setWindowTitle("Lazy dumper.") 148 | layout_main = QVBoxLayout() 149 | layout_base = QHBoxLayout() 150 | layout_base.addWidget(QLabel("Base(HEX):")) 151 | self.edit_base = QLineEdit() 152 | layout_base.addWidget(self.edit_base) 153 | layout_size = QHBoxLayout() 154 | layout_size.addWidget(QLabel("Size(HEX):")) 155 | self.edit_size = QLineEdit() 156 | layout_size.addWidget(self.edit_size) 157 | 158 | self.btn_cancel = QPushButton("Cancel") 159 | self.btn_dump = QPushButton("Dump") 160 | 161 | layout_main.addLayout(layout_base) 162 | layout_main.addLayout(layout_size) 163 | layout_main.addWidget(self.btn_dump) 164 | self.edit_base.setText(hex(self.addr)) 165 | 166 | self.setLayout(layout_main) 167 | 168 | self.btn_dump.clicked.connect(self.click_dump) 169 | 170 | self.show() 171 | self.exec_() 172 | 173 | def click_cancel(self): 174 | self.close() 175 | 176 | def click_dump(self): 177 | addr = self.edit_base.text() 178 | size = self.edit_size.text() 179 | try: 180 | addr = int(hex_cleaner(addr), 16) 181 | size = int(hex_cleaner(size), 16) 182 | except ValueError as e: 183 | QMessageBox.warning(self, " Error ", "Wrong numbers! please check!") 184 | return 185 | 186 | print("dump from %x size:%x" % (addr, size)) 187 | data = dump_bytes(addr, size) 188 | fileName, filetype = QFileDialog.getSaveFileName(self, 189 | "File Saving", 190 | "", 191 | "All Files (*)") 192 | if fileName != u'': 193 | fp = open(fileName, 'wb') 194 | fp.write(data) 195 | fp.close() 196 | print("saved to : " + fileName) 197 | self.close() 198 | 199 | class paste_data_window(QDialog): 200 | def __init__(self, target_addr): 201 | super(paste_data_window, self).__init__() 202 | self.addr = target_addr 203 | self.setWindowTitle('Paste data') 204 | layout_main = QVBoxLayout() 205 | layout_option = QHBoxLayout() 206 | layout_option.addWidget(QLabel("Input Type: ")) 207 | self.option_types = [QRadioButton("HEX"), QRadioButton("BASE64"), QRadioButton("ASCII")] 208 | self.option_types[0].setChecked(True) 209 | for qcheck in self.option_types: 210 | layout_option.addWidget(qcheck) 211 | self.edit = QTextEdit() 212 | self.btn_apply = QPushButton("Apply") 213 | layout_main.addWidget(QLabel("Target Addr: %s " % hex(target_addr)[2:].upper())) 214 | layout_main.addLayout(layout_option) 215 | layout_main.addWidget(self.edit) 216 | layout_main.addWidget(self.btn_apply) 217 | self.btn_apply.clicked.connect(self.event_apply_onclicked) 218 | self.setLayout(layout_main) 219 | self.show() 220 | self.exec_() 221 | 222 | def event_apply_onclicked(self): 223 | text = self.edit.toPlainText() 224 | if self.option_types[0].isChecked(): 225 | text = text.strip() 226 | stopWords = [",", "0x", "{", "}", "H", "h", "[", "]", " ", "\n", ";"] 227 | for ch in stopWords: 228 | text = text.replace(ch, "") 229 | print("HEX:" + text) 230 | hex_bytes = bytearray(binascii.a2b_hex(text)) 231 | for i in range(len(hex_bytes)): 232 | idaapi.patch_byte(self.addr + i, hex_bytes[i]) 233 | self.close() 234 | elif self.option_types[1].isChecked(): 235 | text = text.strip() 236 | hex_bytes = bytearray(base64.b64decode(text)) 237 | for i in range(len(hex_bytes)): 238 | idaapi.patch_byte(self.addr + i, hex_bytes[i]) 239 | self.close() 240 | elif self.option_types[2].isChecked(): 241 | hex_bytes = bytearray(text.encode('utf-8')) 242 | for i in range(len(hex_bytes)): 243 | idaapi.patch_byte(self.addr + i, hex_bytes[i]) 244 | self.close() 245 | 246 | 247 | def copy_to_clip(data): 248 | QApplication.clipboard().setText(data) 249 | 250 | 251 | def clip_text(): 252 | return QApplication.clipboard().text() 253 | 254 | 255 | def parse_location(loc): 256 | try: 257 | loc = int(loc, 16) 258 | except ValueError: 259 | try: 260 | loc = idc.get_name_ea_simple(loc.encode().strip()) 261 | except: 262 | return idaapi.BADADDR 263 | return loc 264 | 265 | 266 | class VulnChoose(idaapi.Choose): 267 | """ 268 | Chooser class to display result of format string vuln scan 269 | """ 270 | 271 | def __init__(self, title, items, icon, embedded=False): 272 | idaapi.Choose.__init__(self, title, [["Address", 20], ["Function", 30], ["Format", 30]], embedded=embedded) 273 | self.items = items 274 | self.icon = 45 275 | 276 | def GetItems(self): 277 | return self.items 278 | 279 | def SetItems(self, items): 280 | self.items = [] if items is None else items 281 | 282 | def OnClose(self): 283 | pass 284 | 285 | def OnGetLine(self, n): 286 | return self.items[n] 287 | 288 | def OnGetSize(self): 289 | return len(self.items) 290 | 291 | def OnSelectLine(self, n): 292 | idc.jumpto(int(self.items[n][0], 16)) 293 | 294 | 295 | class hotkey_action_handler_t(idaapi.action_handler_t): 296 | """ 297 | Action handler for hotkey actions 298 | """ 299 | 300 | def __init__(self, action): 301 | idaapi.action_handler_t.__init__(self) 302 | self.action = action 303 | 304 | def activate(self, ctx): 305 | if self.action == ACTION_COPYEA: 306 | ea = idc.get_screen_ea() 307 | if ea != idaapi.BADADDR: 308 | copy_to_clip("0x%X" % ea) 309 | print("Address 0x%X has been copied to clipboard" % ea) 310 | elif self.action == ACTION_GOTOCLIP: 311 | # loc = parse_location(clip_text()) 312 | # if loc != idaapi.BADADDR: 313 | # print("Goto location 0x%x" % loc) 314 | # idc.jumpto(loc) 315 | jmper_windows(hex_cleaner(clip_text())) 316 | return 1 317 | 318 | def update(self, ctx): 319 | if ctx.widget_type in (idaapi.BWN_DISASM, idaapi.BWN_HEXVIEW): 320 | return idaapi.AST_ENABLE_FOR_WIDGET 321 | else: 322 | return idaapi.AST_DISABLE_FOR_WIDGET 323 | 324 | 325 | class menu_action_handler_t(idaapi.action_handler_t): 326 | """ 327 | Action handler for menu actions 328 | """ 329 | 330 | def __init__(self, action): 331 | idaapi.action_handler_t.__init__(self) 332 | self.action = action 333 | 334 | def activate(self, ctx): 335 | if self.action in ACTION_CONVERT: 336 | # convert 337 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 338 | if idaapi.read_selection(view, t0, t1): 339 | start, end = t0.place(view).toea(), t1.place(view).toea() 340 | size = end - start 341 | elif idc.get_item_size(idc.get_screen_ea()) > 1: 342 | start = idc.get_screen_ea() 343 | size = idc.get_item_size(start) 344 | end = start + size 345 | else: 346 | return False 347 | 348 | data = idc.get_bytes(start, size) 349 | if isinstance(data, str): # python2 compatibility 350 | data = bytearray(data) 351 | name = idc.get_name(start, idc.GN_VISIBLE) 352 | if not name: 353 | name = "data" 354 | if data: 355 | print("\n[+] Dump 0x%X - 0x%X (%u bytes) :" % (start, end, size)) 356 | if self.action == ACTION_CONVERT[0]: 357 | # escaped string 358 | print('"%s"' % "".join("\\x%02X" % b for b in data)) 359 | elif self.action == ACTION_CONVERT[1]: 360 | # hex string 361 | print("".join("%02X" % b for b in data)) 362 | elif self.action == ACTION_CONVERT[2]: 363 | # C array 364 | output = "unsigned char %s[%d] = {" % (name, size) 365 | for i in range(size): 366 | if i % 16 == 0: 367 | output += "\n " 368 | output += "0x%02X, " % data[i] 369 | output = output[:-2] + "\n};" 370 | print(output) 371 | elif self.action == ACTION_CONVERT[3]: 372 | # C array word 373 | data += b"\x00" 374 | array_size = (size + 1) // 2 375 | output = "unsigned short %s[%d] = {" % (name, array_size) 376 | for i in range(0, size, 2): 377 | if i % 16 == 0: 378 | output += "\n " 379 | output += "0x%04X, " % u16(data[i:i + 2]) 380 | output = output[:-2] + "\n};" 381 | print(output) 382 | elif self.action == ACTION_CONVERT[4]: 383 | # C array dword 384 | data += b"\x00" * 3 385 | array_size = (size + 3) // 4 386 | output = "unsigned int %s[%d] = {" % (name, array_size) 387 | for i in range(0, size, 4): 388 | if i % 32 == 0: 389 | output += "\n " 390 | output += "0x%08X, " % u32(data[i:i + 4]) 391 | output = output[:-2] + "\n};" 392 | print(output) 393 | elif self.action == ACTION_CONVERT[5]: 394 | # C array qword 395 | data += b"\x00" * 7 396 | array_size = (size + 7) // 8 397 | output = "unsigned long %s[%d] = {" % (name, array_size) 398 | for i in range(0, size, 8): 399 | if i % 32 == 0: 400 | output += "\n " 401 | output += "%#018X, " % u64(data[i:i + 8]) 402 | output = output[:-2] + "\n};" 403 | print(output.replace("0X", "0x")) 404 | elif self.action == ACTION_CONVERT[6]: 405 | # python list 406 | print("[%s]" % ", ".join("0x%02X" % b for b in data)) 407 | elif self.action == ACTION_CONVERT[7]: 408 | # python list word 409 | data += b"\x00" 410 | print("[%s]" % ", ".join("0x%04X" % u16(data[i:i + 2]) for i in range(0, size, 2))) 411 | elif self.action == ACTION_CONVERT[8]: 412 | # python list dword 413 | data += b"\x00" * 3 414 | print("[%s]" % ", ".join("0x%08X" % u32(data[i:i + 4]) for i in range(0, size, 4))) 415 | elif self.action == ACTION_CONVERT[9]: 416 | # python list qword 417 | data += b"\x00" * 7 418 | print("[%s]" % ", ".join("%#018X" % u64(data[i:i + 8]) for i in range(0, size, 8)).replace("0X", 419 | "0x")) 420 | elif self.action == ACTION_XORDATA: 421 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 422 | if idaapi.read_selection(view, t0, t1): 423 | start, end = t0.place(view).toea(), t1.place(view).toea() 424 | else: 425 | if idc.get_item_size(idc.get_screen_ea()) > 1: 426 | start = idc.get_screen_ea() 427 | end = start + idc.get_item_size(start) 428 | else: 429 | return False 430 | 431 | data = idc.get_bytes(start, end - start) 432 | if isinstance(data, str): # python2 compatibility 433 | data = bytearray(data) 434 | x = idaapi.ask_long(0, "Xor with...") 435 | if x: 436 | x &= 0xFF 437 | print("\n[+] Xor 0x%X - 0x%X (%u bytes) with 0x%02X:" % (start, end, end - start, x)) 438 | print(repr("".join(chr(b ^ x) for b in data))) 439 | elif self.action == ACTION_FILLNOP: 440 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 441 | if idaapi.read_selection(view, t0, t1): 442 | start, end = t0.place(view).toea(), t1.place(view).toea() 443 | idaapi.patch_bytes(start, b"\x90" * (end - start)) 444 | print("\n[+] Fill 0x%X - 0x%X (%u bytes) with NOPs" % (start, end, end - start)) 445 | elif self.action == ACTION_SCANVUL: 446 | print("\n[+] Finding Format String Vulnerability...") 447 | found = [] 448 | for addr in idautils.Functions(): 449 | name = idc.get_func_name(addr) 450 | if "printf" in name and "v" not in name and idc.get_segm_name(addr) in (".text", ".plt", ".idata"): 451 | xrefs = idautils.CodeRefsTo(addr, False) 452 | for xref in xrefs: 453 | vul = self.check_fmt_function(name, xref) 454 | if vul: 455 | found.append(vul) 456 | if found: 457 | print("[!] Done! %d possible vulnerabilities found." % len(found)) 458 | ch = VulnChoose("Vulnerability", found, None, False) 459 | ch.Show() 460 | else: 461 | print("[-] No format string vulnerabilities found.") 462 | elif self.action == ACTION_PASTE: 463 | print("paste data.") 464 | paste_data_window(idc.get_screen_ea()) 465 | elif self.action == ACTION_DUMPER: 466 | print("dump data.") 467 | dumper_windows() 468 | elif self.action == ACTION_JMP: 469 | print("jmper") 470 | jmper_windows() 471 | elif self.action == ACTION_COPY_RVA: 472 | ea = idaapi.get_screen_ea() 473 | if not ida_dbg.is_debugger_on(): 474 | rva = ea - idaapi.get_imagebase() 475 | else: 476 | for mod in idautils.Modules(): 477 | if mod.base <= ea < mod.base + mod.size: 478 | rva = ea - mod.base 479 | break 480 | print("[+] RVA of 0x%X is 0x%X" % (ea, rva)) 481 | copy_to_clip("0x%X" % rva) 482 | else: 483 | return 0 484 | 485 | return 1 486 | 487 | def update(self, ctx): 488 | return idaapi.AST_ENABLE_ALWAYS 489 | 490 | @staticmethod 491 | def check_fmt_function(name, addr): 492 | """ 493 | Check if the format string argument is not valid 494 | """ 495 | function_head = idc.get_func_attr(addr, idc.FUNCATTR_START) 496 | 497 | while True: 498 | addr = idc.prev_head(addr) 499 | op = idc.print_insn_mnem(addr).lower() 500 | dst = idc.print_operand(addr, 0) 501 | 502 | if op in ("ret", "retn", "jmp", "b") or addr < function_head: 503 | return 504 | 505 | c = idc.get_cmt(addr, 0) 506 | if c and c.lower() == "format": 507 | break 508 | elif name.endswith(("snprintf_chk",)): 509 | if op in ("mov", "lea") and dst.endswith(("r8", "r8d", "[esp+10h]")): 510 | break 511 | elif name.endswith(("sprintf_chk",)): 512 | if op in ("mov", "lea") and (dst.endswith(("rcx", "[esp+0Ch]", "R3")) or 513 | dst.endswith("ecx") and BITS == 64): 514 | break 515 | elif name.endswith(("snprintf", "fnprintf")): 516 | if op in ("mov", "lea") and (dst.endswith(("rdx", "[esp+8]", "R2")) or 517 | dst.endswith("edx") and BITS == 64): 518 | break 519 | elif name.endswith(("sprintf", "fprintf", "dprintf", "printf_chk")): 520 | if op in ("mov", "lea") and (dst.endswith(("rsi", "[esp+4]", "R1")) or 521 | dst.endswith("esi") and BITS == 64): 522 | break 523 | elif name.endswith("printf"): 524 | if op in ("mov", "lea") and (dst.endswith(("rdi", "[esp]", "R0")) or 525 | dst.endswith("edi") and BITS == 64): 526 | break 527 | 528 | # format arg found, check its type and value 529 | # get last oprend 530 | op_index = idc.generate_disasm_line(addr, 0).count(",") 531 | op_type = idc.get_operand_type(addr, op_index) 532 | opnd = idc.print_operand(addr, op_index) 533 | 534 | if op_type == idc.o_reg: 535 | # format is in register, try to track back and get the source 536 | _addr = addr 537 | while True: 538 | _addr = idc.prev_head(_addr) 539 | _op = idc.print_insn_mnem(_addr).lower() 540 | if _op in ("ret", "retn", "jmp", "b") or _addr < function_head: 541 | break 542 | elif _op in ("mov", "lea", "ldr") and idc.print_operand(_addr, 0) == opnd: 543 | op_type = idc.get_operand_type(_addr, 1) 544 | opnd = idc.print_operand(_addr, 1) 545 | addr = _addr 546 | break 547 | 548 | if op_type == idc.o_imm or op_type == idc.o_mem: 549 | # format is a memory address, check if it's in writable segment 550 | op_addr = idc.get_operand_value(addr, op_index) 551 | seg = idaapi.getseg(op_addr) 552 | if seg: 553 | if not seg.perm & idaapi.SEGPERM_WRITE: 554 | # format is in read-only segment 555 | return 556 | 557 | print("0x%X: Possible Vulnerability: %s, format = %s" % (addr, name, opnd)) 558 | return ["0x%X" % addr, name, opnd] 559 | 560 | 561 | class hexrays_action_handler_t(idaapi.action_handler_t): 562 | """ 563 | Action handler for hexrays actions 564 | """ 565 | 566 | def __init__(self, action): 567 | idaapi.action_handler_t.__init__(self) 568 | self.action = action 569 | self.ret_type = {} 570 | 571 | def activate(self, ctx): 572 | if self.action == ACTION_HX_REMOVERETTYPE: 573 | vdui = idaapi.get_widget_vdui(ctx.widget) 574 | self.remove_rettype(vdui) 575 | vdui.refresh_ctext() 576 | elif self.action == ACTION_HX_COPYEA: 577 | ea = idaapi.get_screen_ea() 578 | if ea != idaapi.BADADDR: 579 | copy_to_clip("0x%X" % ea) 580 | print("Address 0x%X has been copied to clipboard" % ea) 581 | elif self.action == ACTION_HX_COPYNAME: 582 | name = idaapi.get_highlight(idaapi.get_current_viewer())[0] 583 | if name: 584 | copy_to_clip(name) 585 | print("%s has been copied to clipboard" % name) 586 | elif self.action == ACTION_HX_GOTOCLIP: 587 | loc = parse_location(clip_text()) 588 | print("Goto location 0x%x" % loc) 589 | idc.jumpto(loc) 590 | else: 591 | return 0 592 | 593 | return 1 594 | 595 | def update(self, ctx): 596 | vdui = idaapi.get_widget_vdui(ctx.widget) 597 | return idaapi.AST_ENABLE_FOR_WIDGET if vdui else idaapi.AST_DISABLE_FOR_WIDGET 598 | 599 | def remove_rettype(self, vu): 600 | if vu.item.citype == idaapi.VDI_FUNC: 601 | # current function 602 | ea = vu.cfunc.entry_ea 603 | old_func_type = idaapi.tinfo_t() 604 | if not vu.cfunc.get_func_type(old_func_type): 605 | return False 606 | elif vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr() and vu.item.e.type.is_funcptr(): 607 | # call xxx 608 | ea = vu.item.get_ea() 609 | old_func_type = idaapi.tinfo_t() 610 | 611 | func = idaapi.get_func(ea) 612 | if func: 613 | try: 614 | cfunc = idaapi.decompile(func) 615 | except idaapi.DecompilationFailure: 616 | return False 617 | 618 | if not cfunc.get_func_type(old_func_type): 619 | return False 620 | else: 621 | return False 622 | else: 623 | return False 624 | 625 | fi = idaapi.func_type_data_t() 626 | if ea != idaapi.BADADDR and old_func_type.get_func_details(fi): 627 | # Return type is already void 628 | if fi.rettype.is_decl_void(): 629 | # Restore ret type 630 | if ea not in self.ret_type: 631 | return True 632 | ret = self.ret_type[ea] 633 | else: 634 | # Save ret type and change it to void 635 | self.ret_type[ea] = fi.rettype 636 | ret = idaapi.BT_VOID 637 | 638 | # Create new function info with new rettype 639 | fi.rettype = idaapi.tinfo_t(ret) 640 | 641 | # Create new function type with function info 642 | new_func_type = idaapi.tinfo_t() 643 | new_func_type.create_func(fi) 644 | 645 | # Apply new function type 646 | if idaapi.apply_tinfo(ea, new_func_type, idaapi.TINFO_DEFINITE): 647 | return vu.refresh_view(True) 648 | 649 | return False 650 | 651 | 652 | class UI_Hook(idaapi.UI_Hooks): 653 | def __init__(self): 654 | idaapi.UI_Hooks.__init__(self) 655 | 656 | def finish_populating_widget_popup(self, form, popup): 657 | form_type = idaapi.get_widget_type(form) 658 | if form_type == idaapi.BWN_DISASM or form_type == idaapi.BWN_DUMP: 659 | idaapi.attach_action_to_popup(form, popup, ACTION_PASTE, None) 660 | idaapi.attach_action_to_popup(form, popup, ACTION_DUMPER, None) 661 | idaapi.attach_action_to_popup(form, popup, ACTION_JMP, None) 662 | idaapi.attach_action_to_popup(form, popup, ACTION_COPY_RVA, None) 663 | t0, t1, view = idaapi.twinpos_t(), idaapi.twinpos_t(), idaapi.get_current_viewer() 664 | if idaapi.read_selection(view, t0, t1) or idc.get_item_size(idc.get_screen_ea()) > 1: 665 | idaapi.attach_action_to_popup(form, popup, ACTION_XORDATA, None) 666 | idaapi.attach_action_to_popup(form, popup, ACTION_FILLNOP, None) 667 | for action in ACTION_CONVERT: 668 | idaapi.attach_action_to_popup(form, popup, action, "Convert/") 669 | 670 | if form_type == idaapi.BWN_DISASM and (ARCH, BITS) in [(idaapi.PLFM_386, 32), 671 | (idaapi.PLFM_386, 64), 672 | (idaapi.PLFM_ARM, 32), ]: 673 | idaapi.attach_action_to_popup(form, popup, ACTION_SCANVUL, None) 674 | 675 | 676 | class HexRays_Hook(object): 677 | def callback(self, event, *args): 678 | if event == idaapi.hxe_populating_popup: 679 | form, phandle, vu = args 680 | if vu.item.citype == idaapi.VDI_FUNC or ( 681 | vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr() and vu.item.e.type.is_funcptr()): 682 | idaapi.attach_action_to_popup(form, phandle, ACTION_HX_REMOVERETTYPE, None) 683 | elif event == idaapi.hxe_double_click: 684 | vu, shift_state = args 685 | # auto jump to target if clicked item is xxx->func(); 686 | if vu.item.citype == idaapi.VDI_EXPR and vu.item.e.is_expr(): 687 | expr = idaapi.tag_remove(vu.item.e.print1(None)) 688 | if "->" in expr: 689 | # find target function 690 | name = expr.split("->")[-1] 691 | addr = idc.get_name_ea_simple(name) 692 | if addr == idaapi.BADADDR: 693 | # try class::function 694 | e = vu.item.e 695 | while e.x: 696 | e = e.x 697 | addr = idc.get_name_ea_simple("%s::%s" % (str(e.type).split()[0], name)) 698 | 699 | if addr != idaapi.BADADDR: 700 | idc.jumpto(addr) 701 | return 1 702 | return 0 703 | 704 | 705 | class LazyIDA_t(idaapi.plugin_t): 706 | flags = idaapi.PLUGIN_HIDE 707 | comment = "LazyIDA" 708 | help = "" 709 | wanted_name = "LazyIDA" 710 | wanted_hotkey = "" 711 | 712 | def init(self): 713 | self.hexrays_inited = False 714 | self.registered_actions = [] 715 | self.registered_hx_actions = [] 716 | 717 | global ARCH 718 | global BITS 719 | 720 | ARCH = idaapi.ph_get_id() 721 | if idaapi.inf_is_64bit(): 722 | BITS = 64 723 | elif ida_ida.inf_is_16bit(): 724 | BITS = 16 725 | else: 726 | BITS = 32 727 | 728 | print("LazyIDA (v1.0.0.3) plugin has been loaded.") 729 | 730 | # Register menu actions 731 | menu_actions = ( 732 | idaapi.action_desc_t(ACTION_CONVERT[0], "Convert to string", menu_action_handler_t(ACTION_CONVERT[0]), None, 733 | None, 80), 734 | idaapi.action_desc_t(ACTION_CONVERT[1], "Convert to hex string", menu_action_handler_t(ACTION_CONVERT[1]), 735 | None, None, 8), 736 | idaapi.action_desc_t(ACTION_CONVERT[2], "Convert to C/C++ array (BYTE)", 737 | menu_action_handler_t(ACTION_CONVERT[2]), None, None, 38), 738 | idaapi.action_desc_t(ACTION_CONVERT[3], "Convert to C/C++ array (WORD)", 739 | menu_action_handler_t(ACTION_CONVERT[3]), None, None, 38), 740 | idaapi.action_desc_t(ACTION_CONVERT[4], "Convert to C/C++ array (DWORD)", 741 | menu_action_handler_t(ACTION_CONVERT[4]), None, None, 38), 742 | idaapi.action_desc_t(ACTION_CONVERT[5], "Convert to C/C++ array (QWORD)", 743 | menu_action_handler_t(ACTION_CONVERT[5]), None, None, 38), 744 | idaapi.action_desc_t(ACTION_CONVERT[6], "Convert to python list (BYTE)", 745 | menu_action_handler_t(ACTION_CONVERT[6]), None, None, 201), 746 | idaapi.action_desc_t(ACTION_CONVERT[7], "Convert to python list (WORD)", 747 | menu_action_handler_t(ACTION_CONVERT[7]), None, None, 201), 748 | idaapi.action_desc_t(ACTION_CONVERT[8], "Convert to python list (DWORD)", 749 | menu_action_handler_t(ACTION_CONVERT[8]), None, None, 201), 750 | idaapi.action_desc_t(ACTION_CONVERT[9], "Convert to python list (QWORD)", 751 | menu_action_handler_t(ACTION_CONVERT[9]), None, None, 201), 752 | idaapi.action_desc_t(ACTION_XORDATA, "Get xored data", menu_action_handler_t(ACTION_XORDATA), None, None, 753 | 9), 754 | idaapi.action_desc_t(ACTION_FILLNOP, "Fill with NOPs", menu_action_handler_t(ACTION_FILLNOP), None, None, 755 | 9), 756 | idaapi.action_desc_t(ACTION_PASTE, "Paste Data", menu_action_handler_t(ACTION_PASTE), None, None, 9), 757 | idaapi.action_desc_t(ACTION_DUMPER, "Lazy Dumper", menu_action_handler_t(ACTION_DUMPER), None, None, 9), 758 | idaapi.action_desc_t(ACTION_JMP, "Lazy Jumper [Shift + G]", menu_action_handler_t(ACTION_JMP), None, None, 759 | 9), 760 | idaapi.action_desc_t(ACTION_SCANVUL, "Scan format string vulnerabilities", 761 | menu_action_handler_t(ACTION_SCANVUL), None, None, 160), 762 | idaapi.action_desc_t(ACTION_COPY_RVA, "Copy RVA", menu_action_handler_t(ACTION_COPY_RVA), None, None, 9), 763 | ) 764 | for action in menu_actions: 765 | idaapi.register_action(action) 766 | self.registered_actions.append(action.name) 767 | 768 | # Register hotkey actions 769 | hotkey_actions = ( 770 | idaapi.action_desc_t(ACTION_COPYEA, "Copy EA", hotkey_action_handler_t(ACTION_COPYEA), "w", 771 | "Copy current EA", 0), 772 | idaapi.action_desc_t(ACTION_GOTOCLIP, "Goto clip EA", hotkey_action_handler_t(ACTION_GOTOCLIP), "Shift-G", 773 | "Goto clipboard EA", 0), 774 | ) 775 | for action in hotkey_actions: 776 | idaapi.register_action(action) 777 | self.registered_actions.append(action.name) 778 | 779 | # Add ui hook 780 | self.ui_hook = UI_Hook() 781 | self.ui_hook.hook() 782 | 783 | # Add hexrays ui callback 784 | if idaapi.init_hexrays_plugin(): 785 | addon = idaapi.addon_info_t() 786 | addon.id = "tw.l4ys.lazyida" 787 | addon.name = "LazyIDA" 788 | addon.producer = "Lays" 789 | addon.url = "https://github.com/L4ys/LazyIDA" 790 | addon.version = "1.0.0.3" 791 | idaapi.register_addon(addon) 792 | 793 | hx_actions = ( 794 | idaapi.action_desc_t(ACTION_HX_REMOVERETTYPE, "Remove return type", 795 | hexrays_action_handler_t(ACTION_HX_REMOVERETTYPE), "v"), 796 | idaapi.action_desc_t(ACTION_HX_COPYEA, "Copy ea", hexrays_action_handler_t(ACTION_HX_COPYEA), "w"), 797 | idaapi.action_desc_t(ACTION_HX_COPYNAME, "Copy name", hexrays_action_handler_t(ACTION_HX_COPYNAME), 798 | "c"), 799 | idaapi.action_desc_t(ACTION_HX_GOTOCLIP, "Goto clipboard ea", 800 | hexrays_action_handler_t(ACTION_HX_GOTOCLIP), "Shift-G"), 801 | ) 802 | for action in hx_actions: 803 | idaapi.register_action(action) 804 | self.registered_hx_actions.append(action.name) 805 | 806 | self.hx_hook = HexRays_Hook() 807 | idaapi.install_hexrays_callback(self.hx_hook.callback) 808 | self.hexrays_inited = True 809 | 810 | return idaapi.PLUGIN_KEEP 811 | 812 | def run(self, arg): 813 | pass 814 | 815 | def term(self): 816 | if hasattr(self, "ui_hook"): 817 | self.ui_hook.unhook() 818 | 819 | # Unregister actions 820 | for action in self.registered_actions: 821 | idaapi.unregister_action(action) 822 | 823 | if self.hexrays_inited: 824 | # Unregister hexrays actions 825 | for action in self.registered_hx_actions: 826 | idaapi.unregister_action(action) 827 | if self.hx_hook: 828 | idaapi.remove_hexrays_callback(self.hx_hook.callback) 829 | idaapi.term_hexrays_plugin() 830 | 831 | 832 | def PLUGIN_ENTRY(): 833 | return LazyIDA_t() 834 | -------------------------------------------------------------------------------- /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 | - 原版很久没更新【添加以下功能】 9 | - 自动重定位跳转 (调试的时候不用重新计算偏移或者rebase) 10 | - DUMP 指定内存到文件(脱壳不用脚本了) 11 | - 粘贴 ASCII/HEX/BASE64 到指定内存 (批量修改内存) 12 | - 复制 RVA 13 | 14 | - Jump to other based-address without rebase the idb. 15 | ![](https://a123-1304302739.cos.ap-chengdu.myqcloud.com/%7BCC12AA66-0AAC-585B-09FD-FD50E90FE957%7D.jpg) 16 | 17 | When you debug a program using ohter debuggers, such as ollydbg, and you want to jump to some runtime address in ida, but the imagebase had changed sometimes, so the only way is to rebase idb and type 'G' to jump to the target address. For some large program's idb, it will takes terrible time to rebase the idb. 18 | 19 | offset = target_addr - target_base + currrent_base 20 | 21 | LazyIDA can help you jump to other based-address without rebase. 22 | shortcuts: 23 | Shift + G, LazyIDA will copy the address from clipboard, and fill it in 'Target Addr'. 24 | 25 | 26 | - Remove function return type in Hex-Rays: 27 | 28 | ![2016-06-12 11 05 29](https://cloud.githubusercontent.com/assets/5360374/15991889/2dad5d62-30f2-11e6-8d4b-e4efb0b73c77.png) 29 | 30 | - Convert data into different formats: 31 | 32 | ![2016-06-12 11 01 57](https://cloud.githubusercontent.com/assets/5360374/15991854/b813070a-30f1-11e6-931e-08ae85355cca.png) 33 | ![2016-06-12 11 03 18](https://cloud.githubusercontent.com/assets/5360374/15991863/e5271146-30f1-11e6-89ac-bafd46eb1e45.png) 34 | - Scan for format string vulnerabilities: 35 | 36 | ![2016-06-15 8 19 03](https://cloud.githubusercontent.com/assets/5360374/16064234/da39aa8c-32d1-11e6-89b8-1709cef270f5.png) 37 | - Jump to vtable functions by double clicking 38 | - Lazy shortcuts: 39 | - Disasm Window: 40 | - `w`: Copy address of current line into clipboard 41 | - Hex-rays Window: 42 | - `w`: Copy address of current item into clipboard 43 | - `c`: Copy name of current item into clipboard 44 | - `v`: Remove return type of current item 45 | 46 | - paste data to arbitary address, supports paste from HEX, BASE64, or ASCII 47 | 48 | ![](https://x1hy9.oss-cn-beijing.aliyuncs.com/img/%7B604FF5B0-723B-943A-B34A-DA2E2D7B6D91%7D.jpg) 49 | - lazy dumper, A tool for dump memory to a file, you can specify it size in ui. 50 | 51 | ![](https://x1hy9.oss-cn-beijing.aliyuncs.com/img/%7B9ED5EC0D-3338-0CA6-EB59-7414CFB9C4E8%7D.jpg) 52 | --------------------------------------------------------------------------------