├── .gitignore ├── LICENSE ├── README.md ├── plugins ├── cvutils-cfs-exporter.py ├── cvutils-cfs-importer.py ├── cvutils-getoffset.py ├── cvutils-gotooffset.py ├── examples │ ├── plugin-cfs-export-demo.png │ ├── plugin-cfs-import-demo.png │ ├── plugin-getoffset-demo.png │ └── plugin-gotooffset-demo.png └── plugins-readme.md └── scripts ├── csdf_importer ├── apply_signatures.idc └── csdf-info.md ├── cvdf_importer ├── apply_vtables.idc └── cvdf-info.md ├── misc ├── resolve_unknown.py └── vtbl_print.idc └── theia-packer-dump-fixer └── fix_dump_x64.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore other crap we don't need in the repo 2 | *.csdf 3 | *.cvdf 4 | *.log 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cra0 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 | # ida-scripts 2 | Various IDA scripts I've created for Reverse Engineering. 3 | 4 | ## Plugins 5 | 6 | > [plugins/](plugins/plugins-readme.md) 7 | 8 | ## Scripts 9 | 10 | ### Cra0 Signature Definition File Importer 11 | *Imports CSDF files into IDA see [scripts/csdf_importer](scripts/csdf_importer/csdf-info.md) for further information.* 12 | 13 | * In IDA go `File -> Script File..` and select `apply_signatures.idc` 14 | * It will prompt you for the csdf file, locate it and click Open. 15 | 16 | ### Cra0 VTable Definition File Importer 17 | *Imports CVDF files into IDA see [scripts/cvdf_importer](scripts/cvdf_importer/cvdf-info.md) for further information.* 18 | 19 | * In IDA go `File -> Script File..` and select `apply_vtables.idc` 20 | * It will prompt you for the cvdf file, locate it and click Open. 21 | 22 | ### Misc 23 | Miscellaneous scripts that do various things. -------------------------------------------------------------------------------- /plugins/cvutils-cfs-exporter.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # IDA Plugin to export function signatures to my .cfs file format 3 | # Copy the 'cvutils-cfs-exporter.py' into the plugins directory of IDA 4 | #------------------------------------------------------------------------------ 5 | 6 | VERSION = '1.1.0' 7 | __AUTHOR__ = 'cra0' 8 | 9 | PLUGIN_NAME = "Export Function Signatures" 10 | PLUGIN_HOTKEY = "Ctrl+Shift+E" 11 | 12 | 13 | 14 | import os 15 | import sys 16 | import idc 17 | import idaapi 18 | import idautils 19 | import ida_funcs 20 | import ida_name 21 | import ida_ua 22 | 23 | import ida_ida 24 | UA_MAXOP=ida_ida.UA_MAXOP 25 | 26 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 27 | using_ida7api = (major > 6) 28 | using_pyqt5 = using_ida7api or (major == 6 and minor >= 9) 29 | 30 | idaver_74newer = (major == 7 and minor >= 4) 31 | idaver_8newer = (major >= 8) 32 | 33 | if idaver_74newer or idaver_8newer: 34 | newer_version_compatible = True 35 | else: 36 | newer_version_compatible = False 37 | 38 | if newer_version_compatible: 39 | #IDA 7.4+ 40 | #https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml 41 | import ida_ida 42 | import ida_kernwin 43 | 44 | if using_pyqt5: 45 | import PyQt5.QtGui as QtGui 46 | import PyQt5.QtCore as QtCore 47 | import PyQt5.QtWidgets as QtWidgets 48 | from PyQt5.Qt import QApplication 49 | 50 | else: 51 | import PySide.QtGui as QtGui 52 | import PySide.QtCore as QtCore 53 | QtWidgets = QtGui 54 | QtCore.pyqtSignal = QtCore.Signal 55 | QtCore.pyqtSlot = QtCore.Slot 56 | from PySide.QtGui import QApplication 57 | 58 | 59 | 60 | 61 | def PLUGIN_ENTRY(): 62 | """ 63 | Required plugin entry point for IDAPython Plugins. 64 | """ 65 | return cvutils_exportsigs() 66 | 67 | class cvutils_exportsigs(idaapi.plugin_t): 68 | 69 | flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE 70 | comment = "Export select function signatures." 71 | help = "Select functions right-click, click Export Signatures." 72 | wanted_name = PLUGIN_NAME 73 | wanted_hotkey = PLUGIN_HOTKEY 74 | 75 | #-------------------------------------------------------------------------- 76 | # Plugin Overloads 77 | #-------------------------------------------------------------------------- 78 | 79 | def init(self): 80 | """ 81 | This is called by IDA when it is loading the plugin. 82 | """ 83 | 84 | # initialize the menu actions our plugin will inject 85 | self._init_action_export_sigs() 86 | 87 | # initialize plugin hooks 88 | self._init_hooks() 89 | 90 | # done 91 | idaapi.msg("%s %s initialized...\n" % (self.wanted_name, VERSION)) 92 | return idaapi.PLUGIN_KEEP 93 | 94 | def run(self, arg): 95 | """ 96 | This is called by IDA when this file is loaded as a script. 97 | """ 98 | idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name) 99 | 100 | def term(self): 101 | """ 102 | This is called by IDA when it is unloading the plugin. 103 | """ 104 | 105 | # unhook our plugin hooks 106 | self._hooks.unhook() 107 | 108 | # unregister our actions & free their resources 109 | self._del_ACTION_EXPORT_SIGNATURES() 110 | 111 | 112 | # done 113 | idaapi.msg("%s terminated...\n" % self.wanted_name) 114 | 115 | #-------------------------------------------------------------------------- 116 | # Plugin Hooks 117 | #-------------------------------------------------------------------------- 118 | 119 | def _init_hooks(self): 120 | """ 121 | Install plugin hooks into IDA. 122 | """ 123 | self._hooks = Hooks() 124 | self._hooks.hook() 125 | 126 | #-------------------------------------------------------------------------- 127 | # IDA Actions 128 | #-------------------------------------------------------------------------- 129 | 130 | ACTION_EXPORT_SIGNATURES = "prefix:export_signatures" 131 | 132 | 133 | def _init_action_export_sigs(self): 134 | """ 135 | Register the export sigs action with IDA. 136 | """ 137 | # If the action is already registered, unregister it first. 138 | if idaapi.unregister_action(self.ACTION_EXPORT_SIGNATURES): 139 | idaapi.msg("Warning: action was already registered, unregistering it first\n") 140 | 141 | vaction_desc = "Export Signatures" 142 | if (sys.version_info > (3, 0)): 143 | # Describe the action using python3 copy 144 | action_desc = idaapi.action_desc_t( 145 | self.ACTION_EXPORT_SIGNATURES, # The action name. 146 | "Export Signatures", # The action text. 147 | IDACtxEntry(export_signatures_go), # The action handler. 148 | PLUGIN_HOTKEY, # Optional: action shortcut 149 | vaction_desc, # Optional: tooltip 150 | 35 # Icon 151 | ) 152 | else: 153 | # Describe the action using python2 copy 154 | action_desc = idaapi.action_desc_t( 155 | self.ACTION_EXPORT_SIGNATURES, # The action name. 156 | "Export Signatures", # The action text. 157 | IDACtxEntry(export_signatures_go), # The action handler. 158 | PLUGIN_HOTKEY, # Optional: action shortcut 159 | vaction_desc, # Optional: tooltip 160 | 35 # Icon 161 | ) 162 | 163 | # register the action with IDA 164 | assert idaapi.register_action(action_desc), "Action registration failed" 165 | 166 | 167 | def _del_ACTION_EXPORT_SIGNATURES(self): 168 | """ 169 | Delete the bulk prefix action from IDA. 170 | """ 171 | idaapi.unregister_action(self.ACTION_EXPORT_SIGNATURES) 172 | 173 | 174 | 175 | 176 | #------------------------------------------------------------------------------ 177 | # Plugin Hooks 178 | #------------------------------------------------------------------------------ 179 | 180 | class Hooks(idaapi.UI_Hooks): 181 | 182 | def __init__(self): 183 | # Call the __init__ method of the superclass 184 | super(Hooks, self).__init__() 185 | 186 | # Get the IDA version 187 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 188 | self.newer_version_compatible = (major == 7 and minor >= 4) or (major >= 8) 189 | 190 | # If the IDA version is less than 7.4, define finish_populating_tform_popup 191 | if not self.newer_version_compatible: 192 | self.finish_populating_tform_popup = self._finish_populating_tform_popup 193 | 194 | def finish_populating_widget_popup(self, widget, popup_handle, ctx=None): 195 | """ 196 | A right click menu is about to be shown. (IDA 7.x) 197 | """ 198 | inject_export_signatures_actions(widget, popup_handle, idaapi.get_widget_type(widget)) 199 | return 0 200 | 201 | 202 | def _finish_populating_tform_popup(self, form, popup): 203 | """ 204 | A right click menu is about to be shown. (IDA 6.x) 205 | """ 206 | inject_export_signatures_actions(form, popup, idaapi.get_tform_type(form)) 207 | return 0 208 | 209 | 210 | #------------------------------------------------------------------------------ 211 | # Prefix Wrappers 212 | #------------------------------------------------------------------------------ 213 | 214 | def inject_export_signatures_actions(widget, popup_handle, widget_type): 215 | if widget_type == idaapi.BWN_FUNCS: 216 | idaapi.attach_action_to_popup( 217 | widget, 218 | popup_handle, 219 | cvutils_exportsigs.ACTION_EXPORT_SIGNATURES, 220 | "Export Signatures", 221 | idaapi.SETMENU_APP 222 | ) 223 | return 0 224 | 225 | #------------------------------------------------------------------------------ 226 | # Get Screen linear address 227 | #------------------------------------------------------------------------------ 228 | def get_screen_linear_address(): 229 | if newer_version_compatible: 230 | return idc.get_screen_ea() 231 | else: 232 | return idc.ScreenEA() 233 | 234 | #------------------------------------------------------------------------------ 235 | # Export Functions 236 | #------------------------------------------------------------------------------ 237 | 238 | def get_demangled_function_name(function_name): 239 | demangled_name = ida_name.demangle_name(function_name, ida_name.DQT_FULL) 240 | if demangled_name is not None: 241 | function_name = demangled_name 242 | return function_name 243 | 244 | def get_clean_function_name(function_name): 245 | demangled_function_name = get_demangled_function_name(function_name) 246 | # Remove parameters from the function name if available 247 | if '(' in demangled_function_name: 248 | clean_function_name = demangled_function_name.split('(')[0] 249 | else: 250 | clean_function_name = demangled_function_name 251 | 252 | # Remove return type from the function name 253 | clean_function_name = clean_function_name.split()[-1] 254 | 255 | return clean_function_name 256 | 257 | def get_all_functions(): 258 | ''' 259 | Gets a dictionary of all function names (including library functions) and their corresponding addresses. 260 | ''' 261 | functions_dict = {} 262 | for seg_ea in idautils.Segments(): 263 | for func_ea in idautils.Functions(seg_ea): 264 | f = ida_funcs.get_func(func_ea) 265 | if not f: 266 | continue 267 | function_name = idc.get_name(func_ea) 268 | function_name_clean = get_clean_function_name(function_name) # Demangle if possible 269 | 270 | # Set in dictionary 271 | functions_dict[function_name_clean] = func_ea 272 | 273 | return functions_dict 274 | 275 | def get_addresses_of_selected_functions(): 276 | import sip 277 | twidget = ida_kernwin.find_widget("Functions window") 278 | widget = sip.wrapinstance(int(twidget), QtWidgets.QWidget) 279 | 280 | if not widget: 281 | idaapi.warning("Unable to find 'Functions window'") 282 | return [] 283 | 284 | table = widget.findChild(QtWidgets.QTableView) 285 | 286 | # Trim each function name before adding it to selected_function_names 287 | selected_function_names = [str(s.data()).split('(')[0].split()[-1] for s in table.selectionModel().selectedRows()] 288 | # Get all functions in Idb 289 | all_functions_dict = get_all_functions() 290 | # Map 1 <==> 1 291 | selected_functions_addresses = [all_functions_dict[function_name] for function_name in selected_function_names if function_name in all_functions_dict] 292 | return selected_functions_addresses 293 | 294 | def add_bytes_to_sig(sig, address, size): 295 | for i in range(size): 296 | sig.append("{:02X}".format(idaapi.get_byte(address + i))) 297 | 298 | def add_white_spaces_to_sig(sig, size): 299 | for i in range(size): 300 | sig.append("?") 301 | 302 | def get_current_opcode_size(instruction): 303 | for i in range(UA_MAXOP): 304 | if instruction.ops[i].type == ida_ua.o_void: 305 | return 0, i 306 | if instruction.ops[i].offb != 0: 307 | return instruction.ops[i].offb, i 308 | return 0, 0 309 | 310 | def match_operands(instruction, operand, size): 311 | # Check for data reference 312 | if idaapi.get_first_dref_from(instruction.ea) != idaapi.BADADDR: 313 | return False 314 | 315 | if idaapi.get_first_cref_from(instruction.ea) != idaapi.BADADDR: # Code reference 316 | return False 317 | 318 | return True 319 | 320 | def add_ins_to_sig(instruction, sig): 321 | size, count = get_current_opcode_size(instruction) 322 | if size == 0: 323 | add_bytes_to_sig(sig, instruction.ea, instruction.size) 324 | else: 325 | add_bytes_to_sig(sig, instruction.ea, size) 326 | 327 | if match_operands(instruction, 0, size): 328 | add_bytes_to_sig(sig, instruction.ea + size, instruction.size - size) 329 | else: 330 | add_white_spaces_to_sig(sig, instruction.size - size) 331 | 332 | def is_subOrAdd_instruction(insn): 333 | # Default bytes of those instructions 334 | opcode_sub = [0x48, 0x83, 0xEC] 335 | opcode_add = [0x48, 0x83, 0xC4] 336 | 337 | # Get the bytes of the instruction 338 | insn_bytes = ida_bytes.get_bytes(insn.ea, insn.size) 339 | 340 | # Convert the byte array to a list of integer byte values 341 | insn_byte_list = [b for b in insn_bytes] 342 | 343 | # Compare the first three bytes of the instruction with the opcode 344 | return insn_byte_list[:3] == opcode_sub or insn_byte_list[:3] == opcode_add 345 | 346 | 347 | class SigMaker: 348 | def __init__(self): 349 | pass 350 | 351 | def make_sig_default(self, start, end): 352 | signature = [] 353 | current_address = start 354 | 355 | if (end - start) < 5: 356 | print("Signature must be greater than 5 bytes") 357 | return "" 358 | 359 | while current_address <= end: 360 | instruction = ida_ua.insn_t() 361 | if ida_ua.decode_insn(instruction, current_address) == 0: 362 | break 363 | 364 | if instruction.size < 5: 365 | add_bytes_to_sig(signature, current_address, instruction.size) 366 | else: 367 | add_ins_to_sig(instruction, signature) 368 | 369 | current_address += instruction.size 370 | 371 | return " ".join(signature) 372 | 373 | def make_sig_smart(self, start, end): 374 | signature = [] 375 | current_address = start 376 | 377 | if (end - start) < 5: 378 | print("Signature must be greater than 5 bytes") 379 | return "" 380 | 381 | while current_address <= end: 382 | instruction = ida_ua.insn_t() 383 | if ida_ua.decode_insn(instruction, current_address) == 0: 384 | break 385 | 386 | #handle sub,add 387 | if is_subOrAdd_instruction(instruction): 388 | add_bytes_to_sig(signature, current_address, instruction.size - 1) 389 | add_white_spaces_to_sig(signature, 1) 390 | current_address += instruction.size 391 | continue 392 | 393 | if instruction.size < 5: 394 | add_bytes_to_sig(signature, current_address, instruction.size) 395 | else: 396 | add_ins_to_sig(instruction, signature) 397 | 398 | current_address += instruction.size 399 | 400 | return " ".join(signature) 401 | 402 | def GenerateSignature(ea): 403 | sig_maker = SigMaker() 404 | 405 | func = idaapi.get_func(ea) 406 | if func is None: 407 | print("No function at 0x%08x" % ea) 408 | return None 409 | 410 | start = func.start_ea 411 | end = func.end_ea 412 | 413 | signature = sig_maker.make_sig_default(start, end) 414 | 415 | return signature 416 | 417 | def export_signatures_go(): 418 | sig_maker = SigMaker() 419 | 420 | selected_funcs = get_addresses_of_selected_functions() 421 | if not selected_funcs: 422 | print("No suitable functions selected.") 423 | return 424 | 425 | # Prompt for the output file path 426 | filename = ida_kernwin.ask_file(1, "*.cfs", "Enter the name of the file:") 427 | if not filename: 428 | print("No file selected.") 429 | return 430 | 431 | idaapi.show_wait_box("Exporting signatures...") 432 | 433 | # Build sigs and export! 434 | count = 0 435 | with open(filename, "w") as file: 436 | for func_ea in selected_funcs: 437 | start = idc.get_func_attr(func_ea, idc.FUNCATTR_START) 438 | end = idc.get_func_attr(func_ea, idc.FUNCATTR_END) 439 | func_name = idc.get_func_name(start) 440 | 441 | # We'll create a signature for the entire function 442 | sig = sig_maker.make_sig_default(start, end) 443 | 444 | if sig is None: 445 | print(f"Failed to make a signature for function {func_name} at {start:x}") 446 | continue 447 | 448 | # Write the signature to the file, use the demangled name 449 | 450 | clean_function_name = get_clean_function_name(func_name) 451 | 452 | file.write(f"{count},\"{clean_function_name}\",\"{sig}\"\n") 453 | count += 1 454 | 455 | idaapi.hide_wait_box() 456 | 457 | print(f"Exported {count} function signatures to {filename}") 458 | idaapi.warning("Exported %i function signatures to %s \n" % (count, filename)) 459 | 460 | 461 | #------------------------------------------------------------------------------ 462 | # IDA ctxt 463 | #------------------------------------------------------------------------------ 464 | 465 | class IDACtxEntry(idaapi.action_handler_t): 466 | 467 | def __init__(self, action_function): 468 | idaapi.action_handler_t.__init__(self) 469 | self.action_function = action_function 470 | 471 | def activate(self, ctx): 472 | self.action_function() 473 | return 1 474 | 475 | def update(self, ctx): 476 | return idaapi.AST_ENABLE_ALWAYS 477 | 478 | #------------------------------------------------------------------------------ 479 | # Utilities 480 | #------------------------------------------------------------------------------ 481 | 482 | PLUGIN_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "plugin")) 483 | 484 | def plugin_resource(resource_name): 485 | """ 486 | Return the full path for a given plugin resource file. 487 | """ 488 | return os.path.join(PLUGIN_PATH, "resources", resource_name) 489 | 490 | 491 | def setClipboardText(data): 492 | cb = QApplication.clipboard() 493 | cb.clear(mode=cb.Clipboard ) 494 | cb.setText(data, mode=cb.Clipboard) -------------------------------------------------------------------------------- /plugins/cvutils-cfs-importer.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # IDA Plugin that imports CFS files 3 | # Copy the 'cvutils-cfs-importer.py' into the plugins directory of IDA 4 | #------------------------------------------------------------------------------ 5 | 6 | VERSION = '1.1.0' 7 | __AUTHOR__ = 'cra0' 8 | 9 | PLUGIN_NAME = "CFS Importer" 10 | PLUGIN_HOTKEY = "Ctrl+Shift+I" 11 | 12 | 13 | import os 14 | import idc 15 | import idaapi 16 | 17 | import ida_bytes 18 | import ida_funcs 19 | import ida_name 20 | 21 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 22 | using_ida7api = (major > 6) 23 | using_pyqt5 = using_ida7api or (major == 6 and minor >= 9) 24 | 25 | idaver_74newer = (major == 7 and minor >= 4) 26 | idaver_8newer = (major >= 8) 27 | 28 | if idaver_74newer or idaver_8newer: 29 | newer_version_compatible = True 30 | else: 31 | newer_version_compatible = False 32 | 33 | if newer_version_compatible: 34 | #IDA 7.4+ 35 | #https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml 36 | import ida_ida 37 | 38 | if using_pyqt5: 39 | import PyQt5.QtGui as QtGui 40 | import PyQt5.QtCore as QtCore 41 | import PyQt5.QtWidgets as QtWidgets 42 | from PyQt5.Qt import QApplication 43 | 44 | else: 45 | import PySide.QtGui as QtGui 46 | import PySide.QtCore as QtCore 47 | QtWidgets = QtGui 48 | QtCore.pyqtSignal = QtCore.Signal 49 | QtCore.pyqtSlot = QtCore.Slot 50 | from PySide.QtGui import QApplication 51 | 52 | 53 | class ImportFileMenuHandler(idaapi.action_handler_t): 54 | def __init__(self): 55 | idaapi.action_handler_t.__init__(self) 56 | 57 | def get_directory_path(self, file_path): 58 | """ 59 | Get the directory path from a given file path. 60 | """ 61 | return os.path.dirname(file_path) 62 | 63 | def get_file_name(self, file_path): 64 | """ 65 | Get the file name from a given file path. 66 | """ 67 | return os.path.basename(file_path) 68 | 69 | def find_all_matches(self, min_ea, max_ea, signature, max_matches=-1): 70 | matches = [] 71 | ea = idaapi.find_binary(min_ea, max_ea, signature, 16, idaapi.SEARCH_DOWN) 72 | count = 0 73 | 74 | while ea != idaapi.BADADDR: 75 | matches.append(ea) 76 | count += 1 77 | if max_matches != -1 and count >= max_matches: 78 | break 79 | ea = idaapi.find_binary(ea + 1, max_ea, signature, 16, idaapi.SEARCH_DOWN) 80 | 81 | return matches 82 | 83 | def process_signatures(self, sig_file_path): 84 | """ 85 | Process the signatures and resolve function names in IDA. 86 | """ 87 | counter = 0 88 | resolved_count = 0 89 | error_count = 0 90 | min_ea = idaapi.cvar.inf.min_ea 91 | max_ea = idaapi.cvar.inf.max_ea 92 | is_64bit = idc.__EA64__ 93 | 94 | print("Processing Signatures...") 95 | 96 | with open(sig_file_path, "r") as sig_file: 97 | for line in sig_file: 98 | line = line.strip() 99 | 100 | if not line or line.startswith("//"): 101 | continue 102 | 103 | # Index 104 | index, line = line.split(",", 1) 105 | index = int(index) 106 | 107 | # Function Name 108 | func_name, line = line.split(",", 1) 109 | func_name = func_name.strip() 110 | 111 | # Signature 112 | signature = line.strip()[1:-1] # Remove surrounding quotes 113 | 114 | # Find all matches 115 | ea = idaapi.BADADDR 116 | matches = self.find_all_matches(min_ea, max_ea, signature, 2) 117 | matches_count = len(matches) 118 | if matches_count > 1: 119 | print("Multiple signature matches[%i] found for [%s] ignoring sig." % (matches_count, func_name)) 120 | continue 121 | 122 | # Set EA if we have only 1 hit, change this if you wish. 123 | if matches_count == 1: 124 | ea = matches[0] 125 | 126 | print(f"({resolved_count}/{counter}) [{ea:X}] [{func_name}] ==> ", end="") 127 | 128 | if ea != idaapi.BADADDR: 129 | if ida_bytes.get_full_flags(ea) != idaapi.BADADDR: 130 | func_idb_name_str = ida_name.get_name(ea) 131 | 132 | if idc.get_func_flags(ea) == -1: 133 | ida_bytes.del_items(ea, ida_bytes.DELIT_SIMPLE, 1) 134 | idc.create_insn(ea) 135 | ida_funcs.add_func(ea) 136 | ida_name.set_name(ea, func_name, ida_name.SN_FORCE) 137 | idc.set_cmt(ea, "SIG-RESOLVED " + func_name, 1) 138 | resolved_count += 1 139 | print("[RESOLVED]") 140 | elif func_idb_name_str and len(func_idb_name_str) >= 3: 141 | if func_idb_name_str[:3] == "sub": 142 | ida_name.set_name(ea, func_name, ida_name.SN_FORCE) 143 | idc.set_cmt(ea, "SIG-RESOLVED " + func_name, 1) 144 | resolved_count += 1 145 | print("[RENAMED+RESOLVED]", func_idb_name_str, "TO", func_name) 146 | else: 147 | print("[IGNORED] Function @ 0x{:X} seems named.".format(ea)) 148 | else: 149 | print("[UNKNOWN ERROR]") 150 | else: 151 | error_count += 1 152 | print("[BAD!!!] Unable to resolve =>", func_name, "@ [0x{:X}]".format(ea)) 153 | else: 154 | print("[NOT FOUND] Signature not found in the binary") 155 | 156 | counter += 1 157 | 158 | print("------------------------------------------") 159 | print("Resolved ({}/{}) Functions!".format(resolved_count, counter)) 160 | if error_count > 0: 161 | print("Errors ({})".format(error_count)) 162 | return False 163 | 164 | return True 165 | 166 | def main(self): 167 | sig_file_path = idaapi.ask_file(0, "*.cfs", "Cra0 Signature Definition File") 168 | if sig_file_path: 169 | print("------------------------------------------") 170 | print("IDA Signature Resolver - cra0 (cra0.net)") 171 | print("Parsing:", sig_file_path) 172 | 173 | # Show the "Please wait" dialog before starting the heavy sigfind operation 174 | idaapi.show_wait_box("Processing... Please Wait (This may take a while chill)") 175 | 176 | if not self.process_signatures(sig_file_path): 177 | idaapi.warning("Some errors occurred while importing.") 178 | 179 | # Hide the "Please wait" dialog 180 | idaapi.hide_wait_box() 181 | 182 | 183 | # Invoke the main 184 | def activate(self, ctx): 185 | self.main() # call the main function when the action is activated 186 | return 1 187 | 188 | # This action is always available. 189 | def update(self, ctx): 190 | return idaapi.AST_ENABLE_ALWAYS 191 | 192 | 193 | class CFSImportPlugin(idaapi.plugin_t): 194 | flags = 0 195 | comment = "CFS Importer Plugin" 196 | help = "Import a CFS File into your idb." 197 | wanted_name = PLUGIN_NAME 198 | wanted_hotkey = "" 199 | 200 | ACTION_IMPORT_SIGNATURES = "cfs:import_action" 201 | ACTION_TOOLTIP_ICON = 198 202 | 203 | def init(self): 204 | # Create a new action 205 | action_desc = idaapi.action_desc_t( 206 | self.ACTION_IMPORT_SIGNATURES, # The action name. 207 | 'CFS File...', # The action text. 208 | ImportFileMenuHandler(), # The action handler. 209 | PLUGIN_HOTKEY, # Optional: the action shortcut. 210 | 'Import a CFS File into the current idb.', # Optional: the action tooltip. 211 | self.ACTION_TOOLTIP_ICON) # Icon. 212 | 213 | # Register the action 214 | idaapi.register_action(action_desc) 215 | 216 | # Attach the action to a menu item in the File menu. 217 | idaapi.attach_action_to_menu('File/Load file/', # The relative path of where to add the action. 218 | self.ACTION_IMPORT_SIGNATURES, # The action ID (declared above). 219 | idaapi.SETMENU_APP) # We want to append the action after. 220 | 221 | return idaapi.PLUGIN_KEEP 222 | 223 | def run(self, arg): 224 | print("CFSImportPlugin is running") 225 | 226 | def term(self): 227 | idaapi.unregister_action(self.ACTION_IMPORT_SIGNATURES) 228 | 229 | 230 | def PLUGIN_ENTRY(): 231 | return CFSImportPlugin() -------------------------------------------------------------------------------- /plugins/cvutils-getoffset.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # IDA Plugin to get an offset (rva) at the cursor position. 3 | # Copy the 'cvutils-getoffset.py' into the plugins directory of IDA 4 | #------------------------------------------------------------------------------ 5 | 6 | VERSION = '1.1.1' 7 | __AUTHOR__ = 'cra0' 8 | 9 | PLUGIN_NAME = "Get Address Offset" 10 | PLUGIN_HOTKEY = "Ctrl+Shift+C" 11 | 12 | import os 13 | import sys 14 | import idc 15 | import idaapi 16 | import idautils 17 | 18 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 19 | using_ida7api = (major > 6) 20 | using_pyqt5 = using_ida7api or (major == 6 and minor >= 9) 21 | 22 | idaver_74newer = (major == 7 and minor >= 4) 23 | idaver_8newer = (major >= 8) 24 | 25 | if idaver_74newer or idaver_8newer: 26 | newer_version_compatible = True 27 | else: 28 | newer_version_compatible = False 29 | 30 | IDA_9 = (major >= 9) 31 | 32 | if newer_version_compatible: 33 | # IDA 7.4+ 34 | # https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml 35 | import ida_ida 36 | import ida_kernwin 37 | 38 | if IDA_9: 39 | BITS = 32 if not ida_ida.inf_is_64bit() else 64 40 | BWN_DISASM = ida_kernwin.BWN_DISASM 41 | BWN_PSEUDOCODE = ida_kernwin.BWN_PSEUDOCODE 42 | SETMENU_APP = ida_kernwin.SETMENU_APP 43 | else: 44 | BITS = 32 if not idaapi.get_inf_structure().is_64bit() else 64 45 | BWN_DISASM = idaapi.BWN_DISASMS 46 | BWN_PSEUDOCODE = idaapi.BWN_PSEUDOCODE 47 | SETMENU_APP = idaapi.SETMENU_APP 48 | 49 | if using_pyqt5: 50 | import PyQt5.QtGui as QtGui 51 | import PyQt5.QtCore as QtCore 52 | import PyQt5.QtWidgets as QtWidgets 53 | from PyQt5.Qt import QApplication 54 | 55 | else: 56 | import PySide.QtGui as QtGui 57 | import PySide.QtCore as QtCore 58 | QtWidgets = QtGui 59 | QtCore.pyqtSignal = QtCore.Signal 60 | QtCore.pyqtSlot = QtCore.Slot 61 | from PySide.QtGui import QApplication 62 | 63 | 64 | def setClipboardText(data): 65 | cb = QApplication.clipboard() 66 | cb.clear(mode=cb.Clipboard) 67 | cb.setText(data, mode=cb.Clipboard) 68 | 69 | 70 | def PLUGIN_ENTRY(): 71 | """ 72 | Required plugin entry point for IDAPython Plugins. 73 | """ 74 | return cvutils_getoffset() 75 | 76 | 77 | class cvutils_getoffset(idaapi.plugin_t): 78 | 79 | flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE 80 | comment = "Get the Address offset at the cursor location." 81 | help = "At a certain location right-click 'Get Offset'" 82 | wanted_name = PLUGIN_NAME 83 | wanted_hotkey = PLUGIN_HOTKEY 84 | 85 | #-------------------------------------------------------------------------- 86 | # Plugin Overloads 87 | #-------------------------------------------------------------------------- 88 | 89 | def init(self): 90 | """ 91 | This is called by IDA when it is loading the plugin. 92 | """ 93 | 94 | # initialize the menu actions our plugin will inject 95 | self._init_action_get_offset() 96 | 97 | # initialize plugin hooks 98 | self._init_hooks() 99 | 100 | # done 101 | idaapi.msg("%s %s initialized...\n" % (self.wanted_name, VERSION)) 102 | return idaapi.PLUGIN_KEEP 103 | 104 | def run(self, arg): 105 | """ 106 | This is called by IDA when this file is loaded as a script. 107 | """ 108 | idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name) 109 | 110 | def term(self): 111 | """ 112 | This is called by IDA when it is unloading the plugin. 113 | """ 114 | 115 | # unhook our plugin hooks 116 | self._hooks.unhook() 117 | 118 | # unregister our actions & free their resources 119 | self._del_action_get_offset() 120 | 121 | # done 122 | idaapi.msg("%s terminated...\n" % self.wanted_name) 123 | 124 | #-------------------------------------------------------------------------- 125 | # Plugin Hooks 126 | #-------------------------------------------------------------------------- 127 | 128 | def _init_hooks(self): 129 | """ 130 | Install plugin hooks into IDA. 131 | """ 132 | self._hooks = Hooks() 133 | self._hooks.ready_to_run = self._init_hexrays_hooks 134 | self._hooks.hook() 135 | 136 | def _init_hexrays_hooks(self): 137 | """ 138 | Install Hex-Rays hooks (when available). 139 | NOTE: This is called when the ui_ready_to_run event fires. 140 | """ 141 | if idaapi.init_hexrays_plugin(): 142 | idaapi.install_hexrays_callback(self._hooks.hxe_callback) 143 | 144 | #-------------------------------------------------------------------------- 145 | # IDA Actions 146 | #-------------------------------------------------------------------------- 147 | 148 | ACTION_GET_OFFSET = "prefix:get_offset" 149 | 150 | def _init_action_get_offset(self): 151 | """ 152 | Register the get offset action with IDA. 153 | """ 154 | # If the action is already registered, unregister it first. 155 | if idaapi.unregister_action(self.ACTION_GET_OFFSET): 156 | idaapi.msg("Warning: action was already registered, unregistering it first\n") 157 | 158 | vaction_desc = "Get the offset from the image base of the current cursor address." 159 | action_desc = idaapi.action_desc_t( 160 | self.ACTION_GET_OFFSET, # The action name. 161 | "Get Offset", # The action text. 162 | IDACtxEntry(getcopy_offset), # The action handler. 163 | PLUGIN_HOTKEY, # Optional: action shortcut 164 | vaction_desc, # Optional: tooltip 165 | 31 # Copy icon 166 | ) 167 | 168 | # register the action with IDA 169 | assert idaapi.register_action(action_desc), "Action registration failed" 170 | 171 | def _del_action_get_offset(self): 172 | """ 173 | Delete the bulk prefix action from IDA. 174 | """ 175 | idaapi.unregister_action(self.ACTION_GET_OFFSET) 176 | 177 | 178 | #------------------------------------------------------------------------------ 179 | # Plugin Hooks 180 | #------------------------------------------------------------------------------ 181 | 182 | class Hooks(idaapi.UI_Hooks): 183 | 184 | def __init__(self): 185 | # Call the __init__ method of the superclass 186 | super(Hooks, self).__init__() 187 | 188 | # Get the IDA version 189 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 190 | self.newer_version_compatible = (major == 7 and minor >= 4) or (major >= 8) 191 | 192 | # If the IDA version is less than 7.4, define finish_populating_tform_popup 193 | if not self.newer_version_compatible: 194 | self.finish_populating_tform_popup = self._finish_populating_tform_popup 195 | 196 | def finish_populating_widget_popup(self, widget, popup_handle, ctx=None): 197 | """ 198 | A right click menu is about to be shown. (IDA 7.x) 199 | """ 200 | widget_type = idaapi.get_widget_type(widget) 201 | if widget_type is not None: 202 | inject_address_offset_copy_actions(widget, popup_handle, widget_type) 203 | return 0 204 | 205 | def _finish_populating_tform_popup(self, form, popup): 206 | """ 207 | A right click menu is about to be shown. (IDA 6.x) 208 | """ 209 | inject_address_offset_copy_actions(form, popup, idaapi.get_tform_type(form)) 210 | return 0 211 | 212 | def hxe_callback(self, event, *args): 213 | """ 214 | Hex-Rays event callback. 215 | """ 216 | if event == idaapi.hxe_populating_popup: 217 | form, popup, vu = args 218 | 219 | idaapi.attach_action_to_popup( 220 | form, 221 | popup, 222 | cvutils_getoffset.ACTION_GET_OFFSET, 223 | "Get Address Offset", 224 | SETMENU_APP, 225 | ) 226 | 227 | # done 228 | return 0 229 | 230 | 231 | #------------------------------------------------------------------------------ 232 | # Prefix Wrappers 233 | #------------------------------------------------------------------------------ 234 | 235 | def inject_address_offset_copy_actions(widget, popup_handle, widget_type): 236 | try: 237 | if widget_type in [BWN_DISASM, BWN_PSEUDOCODE]: 238 | idaapi.attach_action_to_popup( 239 | widget, 240 | popup_handle, 241 | cvutils_getoffset.ACTION_GET_OFFSET, 242 | "Get Address Offset", 243 | SETMENU_APP 244 | ) 245 | except AttributeError: 246 | # Handle older versions where the widget type constants might differ 247 | pass 248 | return 0 249 | 250 | 251 | #------------------------------------------------------------------------------ 252 | # Get Screen linear address 253 | #------------------------------------------------------------------------------ 254 | 255 | def get_screen_linear_address(): 256 | if newer_version_compatible: 257 | return idc.get_screen_ea() 258 | else: 259 | return idc.ScreenEA() 260 | 261 | 262 | #------------------------------------------------------------------------------ 263 | # Get Offset 264 | #------------------------------------------------------------------------------ 265 | 266 | def getcopy_offset(): 267 | """ 268 | Gets the offset of the current cursor's address 269 | """ 270 | vImagebase = idaapi.get_imagebase() 271 | vCurrentPos = get_screen_linear_address() 272 | if vCurrentPos != idaapi.BADADDR: 273 | vOffset = vCurrentPos - vImagebase 274 | print("Address [0x%x] =-> Offset [%x] Copied to Clipboard!" % (vCurrentPos, vOffset)) 275 | setClipboardText("%x" % vOffset) 276 | return 277 | 278 | 279 | #------------------------------------------------------------------------------ 280 | # IDA ctxt 281 | #------------------------------------------------------------------------------ 282 | 283 | class IDACtxEntry(idaapi.action_handler_t): 284 | 285 | def __init__(self, action_function): 286 | idaapi.action_handler_t.__init__(self) 287 | self.action_function = action_function 288 | 289 | def activate(self, ctx): 290 | self.action_function() 291 | return 1 292 | 293 | def update(self, ctx): 294 | return idaapi.AST_ENABLE_ALWAYS 295 | 296 | 297 | #------------------------------------------------------------------------------ 298 | # Utilities 299 | #------------------------------------------------------------------------------ 300 | 301 | PLUGIN_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), "plugin")) 302 | 303 | def plugin_resource(resource_name): 304 | """ 305 | Return the full path for a given plugin resource file. 306 | """ 307 | return os.path.join(PLUGIN_PATH, "resources", resource_name) 308 | -------------------------------------------------------------------------------- /plugins/cvutils-gotooffset.py: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------------------------ 2 | # IDA Plugin to jump to an offset from the Imagebase. 3 | # Copy the 'cvutils-getoffset.py' into the plugins directory of IDA 4 | #------------------------------------------------------------------------------ 5 | 6 | VERSION = '1.1.0' 7 | __AUTHOR__ = 'cra0' 8 | 9 | PLUGIN_NAME = "Go To Offset" 10 | PLUGIN_HOTKEY = "Shift+G" 11 | 12 | 13 | 14 | import os 15 | import sys 16 | import idc 17 | import idaapi 18 | import idautils 19 | import string 20 | 21 | 22 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 23 | using_ida7api = (major > 6) 24 | using_pyqt5 = using_ida7api or (major == 6 and minor >= 9) 25 | 26 | idaver_74newer = (major == 7 and minor >= 4) 27 | idaver_8newer = (major >= 8) 28 | 29 | if idaver_74newer or idaver_8newer: 30 | newer_version_compatible = True 31 | else: 32 | newer_version_compatible = False 33 | 34 | if newer_version_compatible: 35 | #IDA 7.4+ 36 | #https://hex-rays.com/products/ida/support/ida74_idapython_no_bc695_porting_guide.shtml 37 | import ida_ida 38 | import ida_kernwin 39 | 40 | if using_pyqt5: 41 | import PyQt5.QtGui as QtGui 42 | import PyQt5.QtCore as QtCore 43 | import PyQt5.QtWidgets as QtWidgets 44 | from PyQt5.Qt import QApplication 45 | 46 | else: 47 | import PySide.QtGui as QtGui 48 | import PySide.QtCore as QtCore 49 | QtWidgets = QtGui 50 | QtCore.pyqtSignal = QtCore.Signal 51 | QtCore.pyqtSlot = QtCore.Slot 52 | from PySide.QtGui import QApplication 53 | 54 | 55 | 56 | def PLUGIN_ENTRY(): 57 | """ 58 | Required plugin entry point for IDAPython Plugins. 59 | """ 60 | return cvutils_gotooffset() 61 | 62 | class cvutils_gotooffset(idaapi.plugin_t): 63 | 64 | flags = idaapi.PLUGIN_PROC | idaapi.PLUGIN_HIDE 65 | comment = "Go to an offset." 66 | help = "Use the shortcut key to open the goto dialog box." 67 | wanted_name = PLUGIN_NAME 68 | wanted_hotkey = PLUGIN_HOTKEY 69 | 70 | #-------------------------------------------------------------------------- 71 | # Plugin Overloads 72 | #-------------------------------------------------------------------------- 73 | 74 | def init(self): 75 | """ 76 | This is called by IDA when it is loading the plugin. 77 | """ 78 | 79 | # initialize the menu actions our plugin will inject 80 | self._init_action_goto_offset() 81 | 82 | # done 83 | idaapi.msg("%s %s initialized...\n" % (self.wanted_name, VERSION)) 84 | return idaapi.PLUGIN_KEEP 85 | 86 | def run(self, arg): 87 | """ 88 | This is called by IDA when this file is loaded as a script. 89 | """ 90 | idaapi.msg("%s cannot be run as a script.\n" % self.wanted_name) 91 | 92 | def term(self): 93 | """ 94 | This is called by IDA when it is unloading the plugin. 95 | """ 96 | 97 | 98 | # unregister our actions & free their resources 99 | self._del_action_goto_offset() 100 | 101 | 102 | # done 103 | idaapi.msg("%s terminated...\n" % self.wanted_name) 104 | 105 | 106 | #-------------------------------------------------------------------------- 107 | # IDA Actions 108 | #-------------------------------------------------------------------------- 109 | 110 | ACTION_GET_OFFSET = "prefix:goto_offset" 111 | 112 | 113 | def _init_action_goto_offset(self): 114 | """ 115 | Register the copy bytes action with IDA. 116 | """ 117 | vaction_desc = "Go to offset." 118 | if (sys.version_info > (3, 0)): 119 | # Describe the action using python3 copy 120 | action_desc = idaapi.action_desc_t( 121 | self.ACTION_GET_OFFSET, # The action name. 122 | "Go to offset", # The action text. 123 | IDACtxEntry(goto_offset), # The action handler. 124 | PLUGIN_HOTKEY, # Optional: action shortcut 125 | vaction_desc, # Optional: tooltip 126 | 31 # Copy icon 127 | ) 128 | else: 129 | # Describe the action using python2 copy 130 | action_desc = idaapi.action_desc_t( 131 | self.ACTION_GET_OFFSET, # The action name. 132 | "Go to offset", # The action text. 133 | IDACtxEntry(goto_offset), # The action handler. 134 | PLUGIN_HOTKEY, # Optional: action shortcut 135 | vaction_desc, # Optional: tooltip 136 | 31 # Copy icon 137 | ) 138 | 139 | 140 | # register the action with IDA 141 | assert idaapi.register_action(action_desc), "Action registration failed" 142 | 143 | 144 | def _del_action_goto_offset(self): 145 | """ 146 | Delete the bulk prefix action from IDA. 147 | """ 148 | idaapi.unregister_action(self.ACTION_GET_OFFSET) 149 | 150 | #------------------------------------------------------------------------------ 151 | # Display a warning message box 152 | #------------------------------------------------------------------------------ 153 | def display_warning(message): 154 | if newer_version_compatible: 155 | return idaapi.warning(message) 156 | else: 157 | return idc.Warning(message) 158 | 159 | #------------------------------------------------------------------------------ 160 | # Jump to a certain address 161 | #------------------------------------------------------------------------------ 162 | def jump_to_address(jump_address): 163 | if newer_version_compatible: 164 | ida_kernwin.jumpto(jump_address) 165 | else: 166 | idc.Jump(jump_address) 167 | 168 | 169 | #------------------------------------------------------------------------------ 170 | # Image Min EA 171 | #------------------------------------------------------------------------------ 172 | def get_minEA(): 173 | if newer_version_compatible: 174 | return ida_ida.inf_get_min_ea() 175 | else: 176 | return idc.MinEA() 177 | 178 | #------------------------------------------------------------------------------ 179 | # IsBadAddress 180 | #------------------------------------------------------------------------------ 181 | 182 | def is_hex(s): 183 | hex_digits = set(string.hexdigits) 184 | # if s is long, then it is faster to check against a set 185 | return all(c in hex_digits for c in s) 186 | 187 | def is_hex(s): 188 | try: 189 | int(s, 16) 190 | return True 191 | except ValueError: 192 | return False 193 | 194 | def isvalid_address(ea): 195 | """Check if the given address is valid 196 | 197 | Arguments: 198 | ea: The linear address to check. 199 | """ 200 | if (ea == idaapi.BADADDR): 201 | print("[%x] BADADDR" % ea) 202 | return 0 203 | 204 | pe_min_ea = get_minEA() 205 | 206 | if (ea < pe_min_ea): 207 | print("[%x] is lower than MinEA [%x]" % (ea, pe_min_ea)) 208 | return 0 209 | 210 | if not idaapi.getseg(ea): 211 | print("[%x] getseg failed" % ea) 212 | return 0 213 | 214 | return 1 215 | 216 | 217 | #------------------------------------------------------------------------------ 218 | # Go to offset 219 | #------------------------------------------------------------------------------ 220 | 221 | def goto_offset(): 222 | hint_min_offset = get_minEA() - idaapi.get_imagebase() 223 | 224 | #Use string for now, force them to use hex as the offset 225 | offset_value=None 226 | if newer_version_compatible: 227 | offset_value = ida_kernwin.ask_str("0x%x" % hint_min_offset, 0, "To Offset[HEX]:") 228 | else: 229 | offset_value = idc.AskStr("0x%x" % hint_min_offset, "To Offset[HEX]:") 230 | 231 | if not offset_value: 232 | print("No value was provided. Ignoring") 233 | return 234 | 235 | if not is_hex(offset_value): 236 | print("Bad input; It doesn't contain valid hex") 237 | display_warning("Bad Input!") 238 | return 239 | 240 | offset_hex = int(offset_value, 16) 241 | if offset_hex == 0: 242 | display_warning("Input is Invalid!") 243 | return 244 | 245 | image_base = idaapi.get_imagebase() 246 | jump_address = image_base + offset_hex 247 | 248 | if (isvalid_address(jump_address)): 249 | print ("Offset [%x] =-> Address [0x%x]" % (offset_hex, jump_address)) 250 | jump_to_address(jump_address) 251 | else: 252 | display_warning("Bad Offset!") 253 | 254 | return 255 | 256 | #------------------------------------------------------------------------------ 257 | # IDA ctxt 258 | #------------------------------------------------------------------------------ 259 | 260 | class IDACtxEntry(idaapi.action_handler_t): 261 | 262 | def __init__(self, action_function): 263 | idaapi.action_handler_t.__init__(self) 264 | self.action_function = action_function 265 | 266 | def activate(self, ctx): 267 | self.action_function() 268 | return 1 269 | 270 | def update(self, ctx): 271 | return idaapi.AST_ENABLE_ALWAYS -------------------------------------------------------------------------------- /plugins/examples/plugin-cfs-export-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cra0/ida-scripts/ee4ce429b3c059d0441c63e2476a9c0c7d726ccc/plugins/examples/plugin-cfs-export-demo.png -------------------------------------------------------------------------------- /plugins/examples/plugin-cfs-import-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cra0/ida-scripts/ee4ce429b3c059d0441c63e2476a9c0c7d726ccc/plugins/examples/plugin-cfs-import-demo.png -------------------------------------------------------------------------------- /plugins/examples/plugin-getoffset-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cra0/ida-scripts/ee4ce429b3c059d0441c63e2476a9c0c7d726ccc/plugins/examples/plugin-getoffset-demo.png -------------------------------------------------------------------------------- /plugins/examples/plugin-gotooffset-demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cra0/ida-scripts/ee4ce429b3c059d0441c63e2476a9c0c7d726ccc/plugins/examples/plugin-gotooffset-demo.png -------------------------------------------------------------------------------- /plugins/plugins-readme.md: -------------------------------------------------------------------------------- 1 | 2 | # IDA Plugins 3 | 4 | This directory contains scriptable plugins for IDA. Drop them into your `IDA\plugins` directory to activate. 5 | 6 | --------------- 7 | 8 | 9 | ## [Get Address Offset Plugin](cvutils-getoffset.py) 10 | 11 | This plugin allows you to do a very simple operation. Get the offset from the image base of the current cursor. 12 | Essentially it's copying to your clipboard the RVA. This is useful when you wish to jump to a *in-memory* live version of whatever you have dumped that is loaded inside IDA. 13 | 14 | ### **Usage Instructions:** 15 | 16 | Right click anywhere in an IDA View and select **Get Offset**. 17 | Alternatively you can use the shortcut command `Ctrl+Shift+C` 18 | The offset will be copied to your clipboard. 19 | 20 | ![screenshot-getoffset-plugin](examples/plugin-getoffset-demo.png) 21 | 22 | 23 | 24 | ## [Go To Offset Plugin](cvutils-gotooffset.py) 25 | 26 | This plugin allows you to jump to an address location given an offset. 27 | 28 | ### **Usage Instructions:** 29 | 30 | Press the shortcut command `Shift+G` to open the input dialog. 31 | Input the offset you wish to jump to and press OK. 32 | 33 | ![screenshot-gotooffset-plugin](examples/plugin-gotooffset-demo.png) 34 | 35 | 36 | ## [CFS Importer](cvutils-cfs-importer.py) 37 | 38 | This plugin allows you to import Function Signatures that have been previously exported using the export plugin. 39 | It esentially just does a binary search of each signature present in the .cfs file. If it finds a match it will rename the function at that location. 40 | 41 | ### **Usage Instructions:** 42 | 43 | File->Load file->CFS File.. 44 | Select the .cfs file in the explorer window. 45 | 46 | ![screenshot-cfs-importer-plugin](examples/plugin-cfs-import-demo.png) 47 | 48 | ## [CFS Exporter](cvutils-cfs-exporter.py) 49 | 50 | This plugin allows you to export Function Signatures in IDA. 51 | Select a function or functions in IDA's functions window. Right click -> Export Signatures and choice a file path and name to export. 52 | 53 | ![screenshot-cfs-exporter-plugin](examples/plugin-cfs-export-demo.png) 54 | -------------------------------------------------------------------------------- /scripts/csdf_importer/apply_signatures.idc: -------------------------------------------------------------------------------- 1 | // apply_signatures.idc : CSDF Signature Definition Importer 2 | // cra0 (cra0.net) 3 | // https://github.com/cra0/ida-scripts/tree/master/scripts/csdf_importer 4 | 5 | #include 6 | 7 | static getDirectoryPath(filePath) 8 | { 9 | auto str = filePath; 10 | auto i = strstr(str, "\\"); 11 | auto j = i + 1; 12 | while (i != -1) 13 | { 14 | str = str[i+1:]; 15 | j = j + (i+1); 16 | i = strstr(str, "\\"); 17 | } 18 | auto path = substr(filePath, 0, j - 3); 19 | return path; 20 | } 21 | 22 | static getFileName(filePath) 23 | { 24 | auto str = filePath; 25 | auto i = strstr(str, "\\"); 26 | while (i != -1) 27 | { 28 | str = str[i+1:]; 29 | i = strstr(str, "\\"); 30 | } 31 | return str; 32 | } 33 | 34 | static processSignatures(sigFileName, logFileName) 35 | { 36 | auto fhSigFile, fhLog; 37 | auto logFilePath; 38 | auto line; 39 | auto funcCount = 0; 40 | auto counter = 0; 41 | auto resolved_count = 0; 42 | auto errorCount = 0; 43 | auto peBaseAddress = SegStart(MinEA()); 44 | auto strFmtStr; 45 | 46 | logFilePath = getDirectoryPath(sigFileName) + logFileName; 47 | fhLog = fopen(logFilePath,"w"); 48 | fprintf(fhLog,"----- PROCESS LOG ----- \n"); 49 | 50 | fhSigFile = fopen(sigFileName,"r"); 51 | while((line = readstr(fhSigFile)) != -1) 52 | { 53 | if (strlen(line) <= 1) 54 | continue; 55 | 56 | if (line == '\n') 57 | continue; 58 | 59 | if (substr(line, 0, 2)=="//") 60 | continue; 61 | 62 | if (funcCount == 0 && substr(line, 0, 2)=="-c") 63 | { 64 | auto fcr = substr(line, 2, -1); 65 | funcCount = atol(fcr); 66 | fprintf(fhLog,"Function Count: %u\n", funcCount); 67 | fprintf(fhLog,"\n"); 68 | Message("Function Count: %u \n", funcCount); 69 | continue; 70 | } 71 | 72 | //Address Offset 73 | auto stknDiv1 = strstr(line, ","); 74 | auto addressOf = substr(line, 0, stknDiv1); 75 | auto fAddressOffset = xtol(addressOf); 76 | 77 | //Mangled Name 78 | auto strTok2 = substr(line, stknDiv1 + 2, -1); 79 | auto stknDiv2 = strstr(strTok2, ","); 80 | auto funcMangledName = substr(strTok2, 0, stknDiv2 - 1); 81 | 82 | //Display Name 83 | auto strTok3 = substr(strTok2, stknDiv2 + 2, -1); 84 | auto stknDiv3 = strstr(strTok3, "\""); 85 | auto funcName = substr(strTok3, 0, stknDiv3); 86 | 87 | //Function Address 88 | auto funcAddress = peBaseAddress + fAddressOffset; 89 | 90 | strFmtStr = "(%u/%u) [%x] [%s] ==>"; 91 | fprintf(fhLog, strFmtStr, resolved_count, funcCount, funcAddress, funcName); 92 | Message(strFmtStr, resolved_count, funcCount, funcAddress, funcName); 93 | 94 | if (Qword(funcAddress) != BADADDR) 95 | { 96 | auto funcNameStr = GetFunctionName(funcAddress); 97 | auto funcNameStrRaw = Demangle(funcNameStr, GetLongPrm(INF_SHORT_DN)); 98 | auto funcPrintName = funcNameStrRaw != 0 ? funcNameStrRaw : funcNameStr; 99 | 100 | if (GetFunctionFlags(funcAddress) == -1) 101 | { 102 | //Message("(%x is not a function! Making..)", funcAddress); 103 | MakeUnkn(funcAddress, 1); 104 | MakeCode(funcAddress); 105 | MakeFunction(funcAddress, BADADDR); 106 | MakeNameEx(funcAddress, funcMangledName, SN_NOWARN); 107 | MakeComm(funcAddress, "AUTO-GENERATED " + funcName); 108 | resolved_count++; 109 | strFmtStr = "[RESOLVED] %s\n"; 110 | fprintf(fhLog, strFmtStr, funcMangledName); 111 | Message(strFmtStr, funcMangledName); 112 | } 113 | else if (funcNameStr != 0 && strlen(funcNameStr) >= 3) 114 | { 115 | if (funcNameStr[0:3] == "sub") 116 | { 117 | MakeNameEx(funcAddress, funcMangledName, SN_NOWARN); 118 | MakeComm(funcAddress, "AUTO-GENERATED " + funcName); 119 | resolved_count++; 120 | strFmtStr = "[RENAMED+RESOLVED] [%s] TO [%s]\n"; 121 | fprintf(fhLog, strFmtStr, funcNameStr, funcMangledName); 122 | Message(strFmtStr, funcNameStr, funcMangledName); 123 | } 124 | else 125 | { 126 | strFmtStr = "[IGNORED] Function @ 0x%x seems named.\n"; 127 | fprintf(fhLog, strFmtStr, funcAddress); 128 | Message(strFmtStr, funcAddress); 129 | } 130 | } 131 | else 132 | { 133 | strFmtStr = "[UNKNOWN ERROR]\n"; 134 | fprintf(fhLog, strFmtStr); 135 | Message(strFmtStr); 136 | } 137 | 138 | } 139 | else 140 | { 141 | errorCount++; 142 | strFmtStr = "[BAD!!!] Unable to resolve => %s @ [0x%x] \n"; 143 | fprintf(fhLog, strFmtStr, funcName, funcAddress); 144 | Message(strFmtStr, funcName, funcAddress); 145 | } 146 | 147 | counter++; 148 | } 149 | fprintf(fhLog,"\n"); 150 | fprintf(fhLog,"Resolved: (%u/%u) Functions!\n", resolved_count, funcCount); 151 | fprintf(fhLog,"----- PROCESS LOG ----- \n"); 152 | fclose(fhSigFile); 153 | fclose(fhLog); 154 | 155 | Message("------------------------------------------ \n"); 156 | Warning("Resolved (%u/%u) Functions!", resolved_count, funcCount); 157 | if (errorCount > 0 ) 158 | { 159 | Warning(" Errors (%u) ", errorCount); 160 | } 161 | 162 | } 163 | 164 | 165 | static main() 166 | { 167 | Message("------------------------------------------ \n"); 168 | Message("IDA Signature Resolver - cra0 (cra0.net) \n"); 169 | 170 | auto inputFilePath = AskFile(0,"*.csdf","Cra0 Signature Definition File"); 171 | if (inputFilePath != 0) 172 | { 173 | Message("Parsing: %s \n", inputFilePath); 174 | SetStatus(IDA_STATUS_WORK); 175 | processSignatures(inputFilePath, "csdf_import.log"); 176 | SetStatus(IDA_STATUS_READY); 177 | } 178 | 179 | 180 | } -------------------------------------------------------------------------------- /scripts/csdf_importer/csdf-info.md: -------------------------------------------------------------------------------- 1 | 2 | # CSDF (Cra0 Signature Definition File) 3 | 4 | Definition file for IDA to resolve functions in a IDB based on a in-memory dump. 5 | 6 | ## Comments 7 | 8 | Comments are defined using two forward slashes: 9 | 10 | ``` 11 | // This is a comment 12 | ``` 13 | 14 | --------------- 15 | 16 | ## Definition Count 17 | 18 | The definition file must contain a count for the list of definitions defined in the file. This is denoted by a **hyphen** and **c** character followed by a space ending with the **count** as an integer. 19 | 20 | > -c {count} 21 | 22 | In the example below the count is 10. 23 | ``` 24 | -c 10 25 | ``` 26 | 27 | --------------- 28 | 29 | ## Definition Declaration 30 | 31 | A function declaration starts with a ***hexadecimal*** offset from the `ImageBase` followed by the **mangled function name** then lastly ending with the **function display name**. 32 | All strings are represented with quotation marks around them. **"string"**. 33 | 34 | > {hex_offset_address},"mangled_name","display_name" 35 | 36 | Below is an example of the [UObject::execExecuteUbergraph](https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/UObject/ExecuteUbergraph/) function represented as a declaration. It is offset by `0x1000` 37 | from the ImageBase of the PE Image. 38 | ``` 39 | 0x00001000,"?execExecuteUbergraph@CoreUObject_Object@@SAXPEAVUObject@@AEAUFFrame@@QEAX@Z","Function CoreUObject.Object.ExecuteUbergraph" 40 | ``` 41 | 42 | --------------- 43 | 44 | -------------------------------------------------------------------------------- /scripts/cvdf_importer/apply_vtables.idc: -------------------------------------------------------------------------------- 1 | // apply_signatures.idc : CVDF VTable Definition Importer 2 | // cra0 (cra0.net) 3 | // https://github.com/cra0/ida-scripts/tree/master/scripts/cvdf_importer 4 | 5 | #include 6 | 7 | static getDirectoryPath(filePath) 8 | { 9 | auto str = filePath; 10 | auto i = strstr(str, "\\"); 11 | auto j = i + 1; 12 | while (i != -1) 13 | { 14 | str = str[i+1:]; 15 | j = j + (i+1); 16 | i = strstr(str, "\\"); 17 | } 18 | auto path = substr(filePath, 0, j - 3); 19 | return path; 20 | } 21 | 22 | static getFileName(filePath) 23 | { 24 | auto str = filePath; 25 | auto i = strstr(str, "\\"); 26 | while (i != -1) 27 | { 28 | str = str[i+1:]; 29 | i = strstr(str, "\\"); 30 | } 31 | return str; 32 | } 33 | 34 | static processVTables(defFileName, logFileName) 35 | { 36 | auto fhDefFile, fhLog; 37 | auto logFilePath; 38 | auto line; 39 | auto vtblCount = 0; 40 | auto counter = 0; 41 | auto resolved_count = 0; 42 | auto errorCount = 0; 43 | auto peBaseAddress = SegStart(MinEA()); 44 | auto strFmtStr; 45 | 46 | logFilePath = getDirectoryPath(defFileName) + logFileName; 47 | fhLog = fopen(logFilePath,"w"); 48 | fprintf(fhLog,"----- PROCESS LOG ----- \n"); 49 | 50 | 51 | fhDefFile = fopen(defFileName,"r"); 52 | while((line = readstr(fhDefFile)) != -1) 53 | { 54 | if (strlen(line) <= 1) 55 | continue; 56 | 57 | if (line == '\n')//skip new line 58 | continue; 59 | 60 | if (substr(line, 0, 2)=="//")//skip comments 61 | continue; 62 | 63 | if (vtblCount == 0 && substr(line, 0, 2)=="-c")//skip comments 64 | { 65 | auto fcr = substr(line, 2, -1); 66 | vtblCount = atol(fcr); 67 | fprintf(fhLog,"VTable Count: %u\n", vtblCount); 68 | fprintf(fhLog,"\n"); 69 | 70 | Message("VTable Count: %u \n", vtblCount); 71 | continue; 72 | } 73 | 74 | //Address Offset 75 | auto stknDiv1 = strstr(line, ","); 76 | auto addressOf = substr(line, 0, stknDiv1); 77 | auto fAddressOffset = xtol(addressOf); 78 | 79 | //Name 80 | auto strTok2 = substr(line, stknDiv1 + 2, -1); 81 | auto stknDiv2 = strstr(strTok2, ","); 82 | auto vtblVarName = substr(strTok2, 0, stknDiv2 - 1); 83 | 84 | //Display Name 85 | auto strTok3 = substr(strTok2, stknDiv2 + 2, -1); 86 | auto stknDiv3 = strstr(strTok3, "\""); 87 | auto displayName = substr(strTok3, 0, stknDiv3); 88 | 89 | //Address 90 | auto vtblAddress = peBaseAddress + fAddressOffset; 91 | 92 | strFmtStr = "(%u/%u) [%x] [%s] ==>"; 93 | fprintf(fhLog, strFmtStr, resolved_count, vtblCount, vtblAddress, vtblVarName); 94 | Message(strFmtStr, resolved_count, vtblCount, vtblAddress, vtblVarName); 95 | 96 | if (Qword(vtblAddress) != BADADDR) 97 | { 98 | auto vtblNameStr = get_name(vtblAddress, GN_VISIBLE); 99 | auto vtblNameStrLen = strlen(vtblNameStr); 100 | 101 | if (vtblNameStrLen == 0) 102 | { 103 | MakeQword(vtblAddress); 104 | MakeName(vtblAddress, vtblVarName); 105 | MakeComm(vtblAddress, "AUTO-GENERATED " + displayName); 106 | resolved_count++; 107 | 108 | strFmtStr = "[CREATED] %x [%s]\n"; 109 | fprintf(fhLog, strFmtStr, vtblAddress, displayName); 110 | Message(strFmtStr, vtblAddress, displayName); 111 | } 112 | else 113 | { 114 | if (vtblNameStrLen >= 3 && (vtblNameStr[0:3] == "unk" || vtblNameStr[0:3] == "off")) 115 | { 116 | strFmtStr = "[RENAMED] VTable @ 0x%x seems to exist. Renamed from [%s] to [%s]\n"; 117 | fprintf(fhLog, strFmtStr, vtblAddress, vtblNameStr, vtblVarName); 118 | Message(strFmtStr, vtblAddress, vtblNameStr, vtblVarName); 119 | MakeQword(vtblAddress); 120 | MakeName(vtblAddress, vtblVarName); 121 | MakeComm(vtblAddress, "AUTO-GENERATED " + displayName); 122 | resolved_count++; 123 | } 124 | else 125 | { 126 | strFmtStr = "[IGNORED] Vtbl var already exists @ 0x%x as [%s]\n"; 127 | fprintf(fhLog, strFmtStr, vtblAddress, vtblNameStr); 128 | Message(strFmtStr, vtblAddress, vtblNameStr); 129 | } 130 | } 131 | 132 | } 133 | else 134 | { 135 | errorCount++; 136 | strFmtStr = "[BAD!!!] Unable to resolve => %s @ [0x%x] \n"; 137 | fprintf(fhLog, strFmtStr, vtblVarName, vtblAddress); 138 | Message(strFmtStr, vtblVarName, vtblAddress); 139 | } 140 | 141 | counter++; 142 | } 143 | fprintf(fhLog,"\n"); 144 | fprintf(fhLog,"Resolved: (%u/%u) VTables!\n", resolved_count, vtblCount); 145 | fprintf(fhLog,"----- PROCESS LOG ----- \n"); 146 | fclose(fhDefFile); 147 | fclose(fhLog); 148 | 149 | Message("------------------------------------------ \n"); 150 | Warning("Resolved (%u/%u) VTables!", resolved_count, vtblCount); 151 | if (errorCount > 0 ) 152 | { 153 | Warning(" Errors (%u) ", errorCount); 154 | } 155 | 156 | } 157 | 158 | 159 | static main() 160 | { 161 | Message("------------------------------------------ \n"); 162 | Message("IDA VTable Resolver - cra0 (cra0.net) \n"); 163 | 164 | auto inputFilePath; 165 | 166 | inputFilePath = AskFile(0,"*.cvdf","Cra0 VTable Definition File"); 167 | if (inputFilePath != 0) 168 | { 169 | Message("Parsing: %s \n", inputFilePath); 170 | SetStatus(IDA_STATUS_WORK); 171 | Message("Path: %s \n", inputFilePath); 172 | processVTables(inputFilePath, "cvdf_import.log"); 173 | SetStatus(IDA_STATUS_READY); 174 | } 175 | 176 | 177 | } -------------------------------------------------------------------------------- /scripts/cvdf_importer/cvdf-info.md: -------------------------------------------------------------------------------- 1 | 2 | # CVDF (Cra0 Vtable Definition File) 3 | 4 | Definition file for IDA to resolve object vtables in a IDB based on a in-memory dump. 5 | 6 | ## Comments 7 | 8 | Comments are defined using two forward slashes: 9 | 10 | ``` 11 | // This is a comment 12 | ``` 13 | 14 | --------------- 15 | 16 | ## Definition Count 17 | 18 | The definition file must contain a count for the list of definitions defined in the file. This is denoted by a **hyphen** and **c** character followed by a space ending with the **count** as an integer. 19 | 20 | > -c {count} 21 | 22 | In the example below the count is 10. 23 | ``` 24 | -c 10 25 | ``` 26 | 27 | --------------- 28 | 29 | ## Definition Declaration 30 | 31 | A vtable declaration starts with a ***hexadecimal*** offset from the `ImageBase` followed by the **vtable variable name** then lastly ending with the **full vtable name** as comment. 32 | All strings are represented with quotation marks around them. **"string"**. 33 | 34 | > {hex_offset_address},"variable_name","display_name" 35 | 36 | --------------- 37 | 38 | -------------------------------------------------------------------------------- /scripts/misc/resolve_unknown.py: -------------------------------------------------------------------------------- 1 | # resolve_unknown.py : An script that defines executable segments in your IDA project if they're unknown or set as data 2 | # This is useful for when dealing with packer dumps 3 | # cra0 (cra0.net) 4 | # https://github.com/cra0/ida-scripts/tree/master/scripts/misc/resolve_unknown.py 5 | 6 | import idc 7 | import idaapi 8 | import ida_bytes 9 | import ida_funcs 10 | import ida_name 11 | 12 | 13 | def get_length(ea): 14 | """ 15 | Get the length of the instruction at the given effective address. 16 | """ 17 | insn = idaapi.insn_t() 18 | if (idaapi.decode_insn(insn, ea) > 0): 19 | return insn.size 20 | else: 21 | return 0 22 | 23 | def is_executable_segment(ea): 24 | seg = idaapi.getseg(ea) 25 | if seg: 26 | return bool(seg.perm & idaapi.SEGPERM_EXEC) 27 | return False 28 | 29 | def get_segment_end(ea): 30 | seg = idaapi.getseg(ea) 31 | if seg: 32 | return seg.end_ea 33 | else: 34 | return -1 35 | 36 | 37 | def define_unk_code(): 38 | """ 39 | Identify unk regions and turn them into code 40 | """ 41 | min_ea = idaapi.cvar.inf.min_ea 42 | max_ea = idaapi.cvar.inf.max_ea 43 | 44 | # Define some progress breakpoints 45 | total_range = max_ea - min_ea 46 | num_intervals = 10 # Progress threshold 47 | progress_breakpoints = [min_ea + (total_range * i // num_intervals) for i in range(1, num_intervals)] 48 | 49 | ea = min_ea 50 | while ea < max_ea: 51 | 52 | # Show progress at breakpoints 53 | if ea in progress_breakpoints: 54 | print(f"Progress: Reached 0x{ea:x}") 55 | idc.jumpto(ea) 56 | 57 | # Check if EA is within a defined segment 58 | if idaapi.getseg(ea) is None: 59 | print(f"Found bad EA @ 0x{ea:x}") 60 | break 61 | 62 | # Check if segment is executable 63 | if not is_executable_segment(ea): 64 | segment_end = get_segment_end(ea) 65 | if segment_end != -1: 66 | ea = segment_end 67 | continue 68 | else: 69 | print(f"Unable to get segment end @ 0x{ea:x}") 70 | ea += 1 71 | continue 72 | 73 | # Try to create an instruction 74 | created = idc.create_insn(ea) 75 | if created: 76 | while ida_bytes.is_code(ida_bytes.get_flags(ea)) and ea < max_ea: 77 | if ea in progress_breakpoints: 78 | print(f"Progress: Reached 0x{ea:x}") 79 | idc.jumpto(ea) 80 | #print(f"EA 0x{ea:x}") 81 | ea += get_length(ea) 82 | else: 83 | ea += 1 # Move to the next byte 84 | 85 | return 86 | 87 | 88 | def main(): 89 | """ 90 | Main function. 91 | """ 92 | print("Starting to define executable as code...") 93 | print("Note this may take a while.\nPay attention to the Navigation Band at the top.") 94 | define_unk_code() 95 | print(f"Finished!") 96 | 97 | if __name__ == "__main__": 98 | main() 99 | -------------------------------------------------------------------------------- /scripts/misc/vtbl_print.idc: -------------------------------------------------------------------------------- 1 | // vtbl_print.idc : An IDA IDC script that prints out a Virtual Method Table (VMT) 2 | // at the current address your IDA cursor is at 3 | // cra0 (cra0.net) 4 | // https://github.com/cra0/ida-scripts/tree/master/scripts/misc/vtbl_print.idc 5 | 6 | 7 | #include 8 | 9 | static main() 10 | { 11 | //vars 12 | auto vtblFuncAddress; 13 | auto szFuncName, szFullName; 14 | auto vtblCounter = 0; //zero-based index 15 | 16 | SetStatus(IDA_STATUS_WORK); 17 | 18 | auto currentPtr = ScreenEA(); 19 | if (currentPtr == BADADDR) 20 | { 21 | Message("---| No valid vtable selected! Aborted Operation |---"); 22 | SetStatus(IDA_STATUS_READY); 23 | return; 24 | } 25 | 26 | Message("\n"); 27 | Message("VTBL : 0x%x \n", currentPtr); 28 | Message("------------------------------------------ \n"); 29 | 30 | // Loop through the vtable block 31 | while (currentPtr != BADADDR) 32 | { 33 | vtblFuncAddress = Qword(currentPtr); 34 | szFuncName = Name(vtblFuncAddress); 35 | if (strlen(szFuncName) == 0) 36 | { 37 | break; 38 | } 39 | 40 | szFullName = Demangle(szFuncName, GetLongPrm(INF_LONG_DN)); 41 | if (szFullName == "") 42 | szFullName = szFuncName; 43 | 44 | Message("[%x] -> [%x] [%d] %s \n", currentPtr, vtblFuncAddress, vtblCounter, szFullName); 45 | currentPtr = currentPtr + 8; 46 | vtblCounter++; 47 | }; 48 | 49 | Message("------------------------------------------ \n"); 50 | SetStatus(IDA_STATUS_READY); 51 | return; 52 | } 53 | -------------------------------------------------------------------------------- /scripts/theia-packer-dump-fixer/fix_dump_x64.py: -------------------------------------------------------------------------------- 1 | # fix_dump_x64.py : A very crude attempt at fixing a packed dump 2 | # cra0 (cra0.net) 3 | # https://github.com/cra0/ida-scripts/tree/master/scripts/theia-packer-dump-fixer/fix_dump_x64.py 4 | 5 | import idc 6 | import idaapi 7 | import ida_bytes 8 | import ida_funcs 9 | import ida_name 10 | 11 | 12 | def get_length(ea): 13 | insn = idaapi.insn_t() 14 | if (idaapi.decode_insn(insn, ea) > 0): 15 | return insn.size 16 | else: 17 | return 0 18 | 19 | def is_executable_segment(ea): 20 | seg = idaapi.getseg(ea) 21 | if seg: 22 | return bool(seg.perm & idaapi.SEGPERM_EXEC) 23 | return False 24 | 25 | def get_segment_end(ea): 26 | seg = idaapi.getseg(ea) 27 | if seg: 28 | return seg.end_ea 29 | else: 30 | return -1 31 | 32 | def identify_functions(): 33 | """ 34 | Identify functions based on the custom padding. 35 | """ 36 | min_ea = idaapi.cvar.inf.min_ea 37 | max_ea = idaapi.cvar.inf.max_ea 38 | 39 | ea = min_ea 40 | function_count = 0 41 | 42 | while ea < max_ea: 43 | 44 | # Check Segment is executable 45 | if not is_executable_segment(ea): 46 | segment_end = get_segment_end(ea) 47 | if segment_end != -1: 48 | ea = segment_end 49 | continue 50 | else: 51 | #print(f"Unable to get segment end @ 0x{ea:x}") 52 | ea += 1 53 | continue 54 | 55 | #print(f"We begin @ 0x{ea:x}") 56 | #break 57 | 58 | # Check if the instruction is already defined as code and can be disassembled 59 | insn = idaapi.insn_t() 60 | if (idaapi.decode_insn(insn, ea) > 0): 61 | if insn.itype not in [idaapi.NN_jmp, idaapi.NN_retn, idaapi.NN_retf]: 62 | #move on 63 | ea += insn.size 64 | continue 65 | else: 66 | #found jmp or ret, move the IP to the end 67 | ea += insn.size 68 | else: 69 | #print(f"Failed to decode instruction at 0x{ea:x}") 70 | # If unable to decode, use idaapi.find_binary to find the next occurrence of 'F1' 71 | ea = idaapi.find_binary(ea, max_ea, "F1", 16, idaapi.SEARCH_DOWN) 72 | if ea == idaapi.BADADDR: 73 | print("No more occurrences of 'F1' found, exiting.") 74 | ea+= 1 75 | continue 76 | 77 | 78 | if not is_executable_segment(ea): 79 | segment_end = get_segment_end(ea) 80 | if segment_end != -1: 81 | ea = segment_end 82 | continue 83 | else: 84 | ea += 1 85 | continue 86 | 87 | 88 | #print(f"Found @ 0x{ea:x}") 89 | 90 | # Check if the next byte is 'F1', if not, continue searching 91 | if ida_bytes.get_byte(ea) != 0xF1: 92 | ea += 1 93 | continue 94 | 95 | # Find the next byte that is not 'F1' (icebp) 96 | while ida_bytes.get_byte(ea) == 0xF1: 97 | ea += 1 98 | 99 | # Ensure the address is 8-byte aligned before making a function 100 | if ea % 8 != 0: 101 | # Not aligned, so skip to the next aligned address 102 | ea += 8 - (ea % 8) 103 | continue 104 | 105 | 106 | #print(f"Function start: 0x{ea:x}") 107 | if ea < max_ea: 108 | 109 | # Check if this function has already been resolved 110 | existing_comment = idc.get_cmt(ea, 1) 111 | if existing_comment == "FUNCTION_RESOLVED": 112 | #print("[IGNORED] Function @ 0x{:X} seems processed!".format(ea)) 113 | ea += 1 114 | continue 115 | 116 | # Check if this is an existing function 117 | func_name_str = ida_name.get_name(ea) 118 | if func_name_str and len(func_name_str) >= 3: 119 | if func_name_str[:3] == "sub": 120 | #print("[IGNORED] Function @ 0x{:X} seems named.".format(ea)) 121 | ea += 1 122 | continue 123 | 124 | # Convert to code and define as function 125 | ida_bytes.del_items(ea, ida_bytes.DELIT_SIMPLE, 1) 126 | idc.create_insn(ea) 127 | new_func = ida_funcs.add_func(ea) 128 | function_count += 1 # Increment the counter 129 | 130 | # Rename the function 131 | func_name = "prc_" + hex(ea)[2:].upper() 132 | ida_name.set_name(ea, func_name, ida_name.SN_FORCE) 133 | 134 | # Add a comment to indicate that this function was resolved 135 | idc.set_cmt(ea, "FUNCTION_RESOLVED", 1) 136 | 137 | print(f"Defined function at 0x{ea:x} with name {func_name}") 138 | 139 | # Jump to the end of this function to continue the search 140 | func_obj = ida_funcs.get_func(ea) 141 | if func_obj: 142 | ea = func_obj.end_ea 143 | else: 144 | print(f"Failed to get function object for 0x{ea:x}, continuing from next address.") 145 | ea += 1 146 | 147 | return function_count # Return the count of identified functions 148 | 149 | 150 | def main(): 151 | """ 152 | Main function. 153 | """ 154 | idaapi.process_ui_action("msglist:Clear") 155 | print("Starting to identify functions...") 156 | count = identify_functions() 157 | print(f"Identified and fixed {count} functions.") 158 | 159 | if __name__ == "__main__": 160 | main() 161 | --------------------------------------------------------------------------------