├── plugin ├── prefix │ ├── __init__.py │ └── resources │ │ ├── bulk.png │ │ ├── clear.png │ │ └── recursive.png └── ida_prefix.py ├── screenshots ├── bulk.gif ├── main.png ├── clearing.gif └── recursive.gif ├── LICENSE └── README.md /plugin/prefix/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /screenshots/bulk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/screenshots/bulk.gif -------------------------------------------------------------------------------- /screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/screenshots/main.png -------------------------------------------------------------------------------- /screenshots/clearing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/screenshots/clearing.gif -------------------------------------------------------------------------------- /screenshots/recursive.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/screenshots/recursive.gif -------------------------------------------------------------------------------- /plugin/prefix/resources/bulk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/plugin/prefix/resources/bulk.png -------------------------------------------------------------------------------- /plugin/prefix/resources/clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/plugin/prefix/resources/clear.png -------------------------------------------------------------------------------- /plugin/prefix/resources/recursive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gaasedelen/prefix/HEAD/plugin/prefix/resources/recursive.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Andrew Marumoto, Markus Gaasedelen 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 | # Prefix - Function Prefixing for IDA Pro 2 | 3 |

4 | Prefix Plugin 5 |

6 | 7 | ## Overview 8 | 9 | Prefix is a small function prefixing plugin for [IDA Pro](https://www.hex-rays.com/products/ida/). The plugin augments IDA's function renaming capabilities by adding a handful of convenient prefixing actions to relevant right click menus. 10 | 11 | ## Releases 12 | 13 | * v1.2 -- Supoort IDA 7.0 -> 7.4, Python 2/3, deprecates IDA 6.x. 14 | * v1.1 -- Added IDA 7 support. 15 | * v1.0 -- Initial release 16 | 17 | ## Installation 18 | 19 | Prefix is a cross-platform (Windows, macOS, Linux) Python 2/3 plugin. It takes zero third party dependencies, making the code both portable and easy to install. 20 | 21 | 1. From your disassembler's python console, run the following command to find its plugin directory: 22 | - **IDA Pro**: `os.path.join(idaapi.get_user_idadir(), "plugins")` 23 | 24 | 2. Copy the contents of this repository's `/plugin/` folder to the listed directory. 25 | 3. Restart your disassembler. 26 | 27 | This plugin is only supported for IDA 7.0 and newer. 28 | 29 | ## Usage 30 | 31 | The Prefix plugin loads automatically when an IDB is opened. The plugin will populate right click menus in the Functions, Disassembly, and HexRays views with additional actions when appropriate. 32 | 33 | ## Recursive Prefix 34 | 35 | A common technique to quickly triage and group related functions while reverse engineering is via a recursive prefix. 36 | 37 |

38 | Recursive Prefix 39 |

40 | 41 | Right clicking a function in the disassembly view now provides an option to 'Recursively prefix' a function and all of its callee's. 42 | 43 | ## Bulk Prefix 44 | 45 | Any number of functions can now be selected in the functions view and assigned a user specified prefix. 46 | 47 |

48 | Bulk Prefix 49 |

50 | 51 | ## Clear Prefix 52 | 53 | User prefixes can easily be cleared via the 'Clear prefix' option added to the Functions window right click menu. 54 | 55 |

56 | Clear Prefix 57 |

58 | 59 | ## Authors 60 | 61 | * Andrew Marumoto 62 | * Markus Gaasedelen ([@gaasedelen](https://twitter.com/gaasedelen)) 63 | -------------------------------------------------------------------------------- /plugin/ida_prefix.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import idc 4 | import idaapi 5 | import idautils 6 | 7 | from PyQt5 import QtGui, QtCore, QtWidgets 8 | 9 | #------------------------------------------------------------------------------ 10 | # IDA Plugin 11 | #------------------------------------------------------------------------------ 12 | 13 | VERSION = "v1.2" 14 | AUTHORS = ['Andrew Marumoto', 'Markus Gaasedelen'] 15 | 16 | def PLUGIN_ENTRY(): 17 | """ 18 | Required plugin entry point for IDAPython Plugins. 19 | """ 20 | return prefix_t() 21 | 22 | class prefix_t(idaapi.plugin_t): 23 | """ 24 | The IDA Plugin for Prefix. 25 | """ 26 | 27 | flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE 28 | help = "" 29 | comment = "A plugin for easy function prefixing" 30 | wanted_name = "prefix" 31 | wanted_hotkey = "" 32 | 33 | #-------------------------------------------------------------------------- 34 | # Plugin Overloads 35 | #-------------------------------------------------------------------------- 36 | 37 | def init(self): 38 | """ 39 | This is called by IDA when it is loading the plugin. 40 | """ 41 | 42 | # initialize the menu actions our plugin will inject 43 | self._init_action_bulk() 44 | self._init_action_clear() 45 | self._init_action_recursive() 46 | 47 | # initialize plugin hooks 48 | self._init_hooks() 49 | 50 | # done 51 | idaapi.msg("%s %s initialized...\n" % (self.wanted_name, VERSION)) 52 | return idaapi.PLUGIN_KEEP 53 | 54 | def run(self, arg): 55 | """ 56 | This is called by IDA when this file is loaded as a script. 57 | """ 58 | idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name) 59 | 60 | def term(self): 61 | """ 62 | This is called by IDA when it is unloading the plugin. 63 | """ 64 | 65 | # unhook our plugin hooks 66 | self._hooks.unhook() 67 | 68 | # unregister our actions & free their resources 69 | self._del_action_bulk() 70 | self._del_action_clear() 71 | self._del_action_recursive() 72 | 73 | # done 74 | idaapi.msg("%s terminated...\n" % self.wanted_name) 75 | 76 | #-------------------------------------------------------------------------- 77 | # Plugin Hooks 78 | #-------------------------------------------------------------------------- 79 | 80 | def _init_hooks(self): 81 | """ 82 | Install plugin hooks into IDA. 83 | """ 84 | self._hooks = Hooks() 85 | self._hooks.ready_to_run = self._init_hexrays_hooks 86 | self._hooks.hook() 87 | 88 | def _init_hexrays_hooks(self): 89 | """ 90 | Install Hex-Rrays hooks (when available). 91 | 92 | NOTE: This is called when the ui_ready_to_run event fires. 93 | """ 94 | if idaapi.init_hexrays_plugin(): 95 | idaapi.install_hexrays_callback(self._hooks.hxe_callback) 96 | 97 | #-------------------------------------------------------------------------- 98 | # IDA Actions 99 | #-------------------------------------------------------------------------- 100 | 101 | ACTION_BULK = "prefix:bulk" 102 | ACTION_CLEAR = "prefix:clear" 103 | ACTION_RECURSIVE = "prefix:recursive" 104 | 105 | def _init_action_bulk(self): 106 | """ 107 | Register the bulk prefix action with IDA. 108 | """ 109 | 110 | # load the icon for this action 111 | self._bulk_icon_id = idaapi.load_custom_icon(plugin_resource("bulk.png")) 112 | 113 | # describe the action 114 | action_desc = idaapi.action_desc_t( 115 | self.ACTION_BULK, # The action name. 116 | "Prefix selected functions", # The action text. 117 | IDACtxEntry(bulk_prefix), # The action handler. 118 | None, # Optional: action shortcut 119 | "Assign a user prefix to the selected functions", # Optional: tooltip 120 | self._bulk_icon_id # Optional: the action icon 121 | ) 122 | 123 | # register the action with IDA 124 | assert idaapi.register_action(action_desc), "Action registration failed" 125 | 126 | def _init_action_clear(self): 127 | """ 128 | Register the clear prefix action with IDA. 129 | """ 130 | 131 | # load the icon for this action 132 | self._clear_icon_id = idaapi.load_custom_icon(plugin_resource("clear.png")) 133 | 134 | # describe the action 135 | action_desc = idaapi.action_desc_t( 136 | self.ACTION_CLEAR, # The action name. 137 | "Clear prefixes", # The action text. 138 | IDACtxEntry(clear_prefix), # The action handler. 139 | None, # Optional: action shortcut 140 | "Clear user prefixes from the selected functions", # Optional: tooltip 141 | self._clear_icon_id # Optional: the action icon 142 | ) 143 | 144 | # register the action with IDA 145 | assert idaapi.register_action(action_desc), "Action registration failed" 146 | 147 | def _init_action_recursive(self): 148 | """ 149 | Register the recursive rename action with IDA. 150 | """ 151 | 152 | # load the icon for this action 153 | self._recursive_icon_id = idaapi.load_custom_icon(plugin_resource("recursive.png")) 154 | 155 | # describe the action 156 | action_desc = idaapi.action_desc_t( 157 | self.ACTION_RECURSIVE, # The action name. 158 | "Recursive function prefix", # The action text. 159 | IDACtxEntry(recursive_prefix_cursor), # The action handler. 160 | None, # Optional: action shortcut 161 | "Recursively prefix callees of this function", # Optional: tooltip 162 | self._recursive_icon_id # Optional: the action icon 163 | ) 164 | 165 | # register the action with IDA 166 | assert idaapi.register_action(action_desc), "Action registration failed" 167 | 168 | def _del_action_bulk(self): 169 | """ 170 | Delete the bulk prefix action from IDA. 171 | """ 172 | idaapi.unregister_action(self.ACTION_BULK) 173 | idaapi.free_custom_icon(self._bulk_icon_id) 174 | self._bulk_icon_id = idaapi.BADADDR 175 | 176 | def _del_action_clear(self): 177 | """ 178 | Delete the clear prefix action from IDA. 179 | """ 180 | idaapi.unregister_action(self.ACTION_CLEAR) 181 | idaapi.free_custom_icon(self._clear_icon_id) 182 | self._clear_icon_id = idaapi.BADADDR 183 | 184 | def _del_action_recursive(self): 185 | """ 186 | Delete the recursive rename action from IDA. 187 | """ 188 | idaapi.unregister_action(self.ACTION_RECURSIVE) 189 | idaapi.free_custom_icon(self._recursive_icon_id) 190 | self._recursive_icon_id = idaapi.BADADDR 191 | 192 | #------------------------------------------------------------------------------ 193 | # Plugin Hooks 194 | #------------------------------------------------------------------------------ 195 | 196 | class Hooks(idaapi.UI_Hooks): 197 | 198 | def ready_to_run(self): 199 | """ 200 | UI ready to run -- an IDA event fired when everything is spunup. 201 | 202 | NOTE: this is a placeholder func, it gets replaced on a live instance 203 | but we need it defined here for IDA 7.2+ to properly hook it. 204 | """ 205 | pass 206 | 207 | def finish_populating_widget_popup(self, widget, popup): 208 | """ 209 | A right click menu is about to be shown. (IDA 7) 210 | """ 211 | inject_prefix_actions(widget, popup, idaapi.get_widget_type(widget)) 212 | return 0 213 | 214 | def hxe_callback(self, event, *args): 215 | """ 216 | HexRays event callback. 217 | 218 | We lump this under the (UI) Hooks class for organizational reasons. 219 | """ 220 | 221 | # 222 | # if the event callback indicates that this is a popup menu event 223 | # (in the hexrays window), we may want to install our prefix menu 224 | # actions depending on what the cursor right clicked. 225 | # 226 | 227 | if event == idaapi.hxe_populating_popup: 228 | form, popup, vu = args 229 | 230 | # 231 | # if the user cursor isn't hovering over a function ref, there 232 | # is nothing for us to do 233 | # 234 | 235 | if get_cursor_func_ref() == idaapi.BADADDR: 236 | return 0 237 | 238 | # 239 | # the user cursor is hovering over a valid target for a recursive 240 | # function prefix. insert the prefix action entry into the menu 241 | # 242 | 243 | idaapi.attach_action_to_popup( 244 | form, 245 | popup, 246 | prefix_t.ACTION_RECURSIVE, 247 | "Rename global item", 248 | idaapi.SETMENU_APP 249 | ) 250 | 251 | # done 252 | return 0 253 | 254 | #------------------------------------------------------------------------------ 255 | # Prefix Wrappers 256 | #------------------------------------------------------------------------------ 257 | 258 | def recursive_prefix_cursor(): 259 | """ 260 | Recursive prefix under the user cursor. 261 | """ 262 | 263 | # get the function reference under the user cursor (if there is one) 264 | target = get_cursor_func_ref() 265 | if target == idaapi.BADADDR: 266 | return 267 | 268 | # execute the recursive prefix 269 | recursive_prefix(target) 270 | 271 | def inject_prefix_actions(form, popup, form_type): 272 | """ 273 | Inject prefix actions to popup menu(s) based on context. 274 | """ 275 | 276 | # 277 | # disassembly window 278 | # 279 | 280 | if form_type == idaapi.BWN_DISASMS: 281 | 282 | # 283 | # if the user cursor isn't hovering over a function ref, there 284 | # is nothing for us to do 285 | # 286 | 287 | if get_cursor_func_ref() == idaapi.BADADDR: 288 | return 289 | 290 | # 291 | # the user cursor is hovering over a valid target for a recursive 292 | # function prefix. insert the prefix action entry into the menu 293 | # 294 | 295 | idaapi.attach_action_to_popup( 296 | form, 297 | popup, 298 | prefix_t.ACTION_RECURSIVE, 299 | "Rename", 300 | idaapi.SETMENU_APP 301 | ) 302 | 303 | # 304 | # functions window 305 | # 306 | 307 | elif form_type == idaapi.BWN_FUNCS: 308 | 309 | # inject the 'Bulk' function prefix action 310 | idaapi.attach_action_to_popup( 311 | form, 312 | popup, 313 | prefix_t.ACTION_BULK, 314 | "Delete function(s)...", 315 | idaapi.SETMENU_INS 316 | ) 317 | 318 | # inject the 'Clear prefix' action 319 | idaapi.attach_action_to_popup( 320 | form, 321 | popup, 322 | prefix_t.ACTION_CLEAR, 323 | "Delete function(s)...", 324 | idaapi.SETMENU_INS 325 | ) 326 | 327 | # inject a menu separator 328 | idaapi.attach_action_to_popup( 329 | form, 330 | popup, 331 | None, 332 | "Delete function(s)...", 333 | idaapi.SETMENU_INS 334 | ) 335 | 336 | # done 337 | return 0 338 | 339 | #------------------------------------------------------------------------------ 340 | # Prefix API 341 | #------------------------------------------------------------------------------ 342 | 343 | PREFIX_DEFAULT = "MyPrefix" 344 | PREFIX_SEPARATOR = '%' 345 | 346 | def recursive_prefix(addr): 347 | """ 348 | Recursively prefix a function tree with a user defined string. 349 | """ 350 | func_addr = idaapi.get_name_ea(idaapi.BADADDR, idaapi.get_func_name(addr)) 351 | if func_addr == idaapi.BADADDR: 352 | idaapi.msg("Prefix: 0x%08X does not belong to a defined function\n" % addr) 353 | return 354 | 355 | # prompt the user for a prefix to apply to the selected functions 356 | tag = idaapi.ask_str(PREFIX_DEFAULT, 0, "Function Tag") 357 | 358 | # the user closed the window... ignore 359 | if tag == None: 360 | return 361 | 362 | # the user put a blank string and hit 'okay'... notify & ignore 363 | elif tag == '': 364 | idaapi.warning("[ERROR] Tag cannot be empty [ERROR]") 365 | return 366 | 367 | # recursively collect all the functions called by this function 368 | nodes_xref_down = graph_down(func_addr, path=set([])) 369 | 370 | # graph_down returns the int address needs to be converted 371 | tmp = [] 372 | tmp1 = '' 373 | for func_addr in nodes_xref_down: 374 | tmp1 = idaapi.get_func_name(func_addr) 375 | if tmp1: 376 | tmp.append(tmp1) 377 | nodes_xref_down = tmp 378 | 379 | # prefix the tree of functions 380 | for rename in nodes_xref_down: 381 | func_addr = idaapi.get_name_ea(idaapi.BADADDR, rename) 382 | if tag not in rename: 383 | idaapi.set_name(func_addr,'%s%s%s' % (str(tag), PREFIX_SEPARATOR, rename), idaapi.SN_NOWARN) 384 | 385 | # refresh the IDA views 386 | refresh_views() 387 | 388 | def bulk_prefix(): 389 | """ 390 | Prefix the Functions window selection with a user defined string. 391 | """ 392 | 393 | # prompt the user for a prefix to apply to the selected functions 394 | tag = idaapi.ask_str(PREFIX_DEFAULT, 0, "Function Tag") 395 | 396 | # the user closed the window... ignore 397 | if tag == None: 398 | return 399 | 400 | # the user put a blank string and hit 'okay'... notify & ignore 401 | elif tag == '': 402 | idaapi.warning("[ERROR] Tag cannot be empty [ERROR]") 403 | return 404 | 405 | # 406 | # loop through all the functions selected in the 'Functions window' and 407 | # apply the user defined prefix tag to each one. 408 | # 409 | 410 | for func_name in get_selected_funcs(): 411 | 412 | # ignore functions that already have the specified prefix applied 413 | if func_name.startswith(tag): 414 | continue 415 | 416 | # apply the user defined prefix to the function (rename it) 417 | new_name = '%s%s%s' % (str(tag), PREFIX_SEPARATOR, func_name) 418 | func_addr = idaapi.get_name_ea(idaapi.BADADDR, func_name) 419 | idaapi.set_name(func_addr, new_name, idaapi.SN_NOWARN) 420 | 421 | # refresh the IDA views 422 | refresh_views() 423 | 424 | def clear_prefix(): 425 | """ 426 | Clear user defined prefixes from the selected functions in the Functions window. 427 | """ 428 | 429 | # 430 | # loop through all the functions selected in the 'Functions window' and 431 | # clear any user defined prefixes applied to them. 432 | # 433 | 434 | for func_name in get_selected_funcs(): 435 | 436 | # 437 | # locate the last (rfind) prefix separator in the function name as 438 | # we will want to keep everything that comes after it 439 | # 440 | 441 | i = func_name.rfind(PREFIX_SEPARATOR) 442 | 443 | # if there is no prefix (separator), there is nothing to trim 444 | if i == -1: 445 | continue 446 | 447 | # trim the prefix off the original function name and discard it 448 | new_name = func_name[i+1:] 449 | func_addr = idaapi.get_name_ea(idaapi.BADADDR, func_name) 450 | idaapi.set_name(func_addr, new_name, idaapi.SN_NOWARN) 451 | 452 | # refresh the IDA views 453 | refresh_views() 454 | 455 | #------------------------------------------------------------------------------ 456 | # IDA Util 457 | #------------------------------------------------------------------------------ 458 | 459 | def refresh_views(): 460 | """ 461 | Refresh the IDA views. 462 | """ 463 | 464 | # refresh IDA views 465 | idaapi.refresh_idaview_anyway() 466 | 467 | # refresh hexrays 468 | current_widget = idaapi.get_current_widget() 469 | vu = idaapi.get_widget_vdui(current_widget) 470 | if vu: 471 | vu.refresh_ctext() 472 | 473 | def get_all_funcs(): 474 | """ 475 | Enumerate all function names defined in the IDB. 476 | """ 477 | return set(idaapi.get_func_name(ea) for ea in idautils.Functions()) 478 | 479 | def get_cursor_func_ref(): 480 | """ 481 | Get the function reference under the user cursor. 482 | 483 | Returns BADADDR or a valid function address. 484 | """ 485 | current_widget = idaapi.get_current_widget() 486 | form_type = idaapi.get_widget_type(current_widget) 487 | vu = idaapi.get_widget_vdui(current_widget) 488 | 489 | # 490 | # hexrays view is active 491 | # 492 | 493 | if vu: 494 | cursor_addr = vu.item.get_ea() 495 | 496 | # 497 | # disassembly view is active 498 | # 499 | 500 | elif form_type == idaapi.BWN_DISASM: 501 | cursor_addr = idaapi.get_screen_ea() 502 | opnum = idaapi.get_opnum() 503 | 504 | if opnum != -1: 505 | 506 | # 507 | # if the cursor is over an operand value that has a function ref, 508 | # use that as a valid rename target 509 | # 510 | 511 | op_addr = idc.get_operand_value(cursor_addr, opnum) 512 | op_func = idaapi.get_func(op_addr) 513 | 514 | if op_func and op_func.start_ea == op_addr: 515 | return op_addr 516 | 517 | # unsupported/unknown view is active 518 | else: 519 | return idaapi.BADADDR 520 | 521 | # 522 | # if the cursor is over a function definition or other reference, use that 523 | # as a valid rename target 524 | # 525 | 526 | cursor_func = idaapi.get_func(cursor_addr) 527 | if cursor_func and cursor_func.start_ea == cursor_addr: 528 | return cursor_addr 529 | 530 | # fail 531 | return idaapi.BADADDR 532 | 533 | def get_selected_funcs(): 534 | """ 535 | Return the list of function names selected in the Functions window. 536 | """ 537 | import sip 538 | twidget = idaapi.find_widget("Functions window") 539 | widget = sip.wrapinstance(int(twidget), QtWidgets.QWidget) 540 | 541 | # TODO: test this 542 | if not widget: 543 | idaapi.warning("Unable to find 'Functions window'") 544 | return 545 | 546 | # 547 | # locate the table widget within the Functions window that actually holds 548 | # all the visible function metadata 549 | # 550 | 551 | table = widget.findChild(QtWidgets.QTableView) 552 | 553 | # 554 | # scrape the selected function names from the Functions window table 555 | # 556 | 557 | selected_funcs = [str(s.data()) for s in table.selectionModel().selectedRows()] 558 | 559 | # 560 | # re-map the scraped names as they appear in the function table, to their true 561 | # names as they are saved in the IDB. See the match_funcs(...) function 562 | # comment for more details 563 | # 564 | 565 | return match_funcs(selected_funcs) 566 | 567 | def match_funcs(qt_funcs): 568 | """ 569 | Convert function names scraped from Qt to their *actual* representation. 570 | 571 | The function names we scrape from the Functions window Qt table actually 572 | use the underscore character ('_') as a substitute for a variety of 573 | different characters. 574 | 575 | For example, a function named foo%bar in the IDB will appears as foo_bar 576 | in the Functions window table. 577 | 578 | This function takes a list of names as they appear in the Functions window 579 | table such as the following: 580 | 581 | ['foo_bar'] 582 | 583 | And applies a best effort lookup to return a list of the 'true' function 584 | names as they are stored in the IDB. 585 | 586 | ['foo%bar'] 587 | 588 | TODO: rewrite this to be more efficient for larger idbs 589 | TODO: takes first matching function, may want to change it to make the requirements more strict 590 | """ 591 | res = set() 592 | ida_funcs = get_all_funcs() 593 | for f in qt_funcs: 594 | for f2 in ida_funcs: 595 | if len(f) == len(f2): 596 | i = 0 597 | while i < len(f) and (f[i] == f2[i] or f[i] == '_'): 598 | i += 1 599 | 600 | if i == len(f): 601 | res.add(f2) 602 | break 603 | 604 | return list(res) 605 | 606 | def graph_down(ea, path=set()): 607 | """ 608 | Recursively collect all function calls. 609 | 610 | Copied with minor modifications from 611 | http://hooked-on-mnemonics.blogspot.com/2012/07/renaming-subroutine-blocks-and.html 612 | """ 613 | path.add(ea) 614 | 615 | # 616 | # extract all the call instructions from the current function 617 | # 618 | 619 | call_instructions = [] 620 | instruction_info = idaapi.insn_t() 621 | for address in idautils.FuncItems(ea): 622 | 623 | # decode the instruction 624 | if not idaapi.decode_insn(instruction_info, address): 625 | continue 626 | 627 | # check if this instruction is a call 628 | if not idaapi.is_call_insn(instruction_info): 629 | continue 630 | 631 | # save this address as a call instruction 632 | call_instructions.append(address) 633 | 634 | # 635 | # iterate through all the instructions in the target function (ea) and 636 | # inspect all the call instructions 637 | # 638 | 639 | for x in call_instructions: 640 | 641 | # TODO 642 | for r in idautils.XrefsFrom(x, idaapi.XREF_FAR): 643 | #print(0x%08X" % h, "--calls-->", "0x%08X" % r.to) 644 | if not r.iscode: 645 | continue 646 | 647 | # get the function pointed at by this call 648 | func = idaapi.get_func(r.to) 649 | if not func: 650 | continue 651 | 652 | # ignore calls to imports / library calls / thunks 653 | if (func.flags & (idaapi.FUNC_THUNK | idaapi.FUNC_LIB)) != 0: 654 | continue 655 | 656 | # 657 | # if we have not traversed to the destination function that this 658 | # call references, recurse down to it to continue our traversal 659 | # 660 | 661 | if r.to not in path: 662 | graph_down(r.to, path) 663 | 664 | return path 665 | 666 | class IDACtxEntry(idaapi.action_handler_t): 667 | """ 668 | A basic Context Menu class to utilize IDA's action handlers. 669 | """ 670 | 671 | def __init__(self, action_function): 672 | idaapi.action_handler_t.__init__(self) 673 | self.action_function = action_function 674 | 675 | def activate(self, ctx): 676 | """ 677 | Execute the embedded action_function when this context menu is invoked. 678 | """ 679 | self.action_function() 680 | return 1 681 | 682 | def update(self, ctx): 683 | """ 684 | Ensure the context menu is always available in IDA. 685 | """ 686 | return idaapi.AST_ENABLE_ALWAYS 687 | 688 | #------------------------------------------------------------------------------ 689 | # Plugin Util 690 | #------------------------------------------------------------------------------ 691 | 692 | PLUGIN_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), prefix_t.wanted_name)) 693 | 694 | def plugin_resource(resource_name): 695 | """ 696 | Return the full path for a given plugin resource file. 697 | """ 698 | return os.path.join( 699 | PLUGIN_PATH, 700 | "resources", 701 | resource_name 702 | ) 703 | 704 | --------------------------------------------------------------------------------