├── README.md └── dumpulator.py /README.md: -------------------------------------------------------------------------------- 1 | # Dumpulator-IDA 2 | Currently proof-of-concept 3 | 4 | This project is a small POC plugin for launching dumpulator emulation within IDA, passing it addresses from your IDA view using the context menu. 5 | 6 | Find the amazing dumpulator project by @mrexodia at [link](https://github.com/mrexodia/dumpulator) 7 | 8 | ## Configure 9 | You can now go to Edit -> Plugins -> Dumpulate and this will prompt you to select your dump file 10 | 11 | ## Currently only allows you to: 12 | For the example file found [here](https://github.com/mrexodia/dumpulator/releases/download/v0.0.1/StringEncryptionFun_x64.dmp) you can 13 | 14 | - Set your call address and choose a number of arguments to pass 15 | - Right click an address, choose select argument, and then select which arg you'd like to pass it as 16 | - Right click and select allocate temporary space for any arg you'd like to monitor the output of 17 | - Finally right click and choose emulate function 18 | 19 | This will emulate the chosen call address, pass your assigned arguments, and output the passed addresses as strings if possible 20 | 21 | ## Limitations 22 | If you right click the variable within the function call line, you'll get the wrong address, so you have to be selecting it directly by clicking through to it. 23 | 24 | Additionally, I haven't yet coded this to support stack variables. I will need to confirm how that is coded with dumpulator, before I try and impliment. 25 | 26 | *The biggest limitation is that that's insanely buggy* 27 | 28 | ## Future Work 29 | I feel that this could be more dynamic, and there are a lot more options and scenarios to account for than the basic string decryption POC. 30 | 31 | I will look into more dynamic ways of building your function argument structure, as well as ways of controlling what you monitor and how it is outputted, potentially with enums or comments as an option -------------------------------------------------------------------------------- /dumpulator.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import idaapi 3 | import idautils 4 | import idc 5 | import ida_kernwin 6 | from ida_kernwin import Choose 7 | import ida_enum 8 | import ida_bytes 9 | import ida_netnode 10 | 11 | from dumpulator import Dumpulator 12 | 13 | __AUTHOR__ = 'Micycle' 14 | 15 | PLUGIN_NAME = "dumpulate" 16 | PLUGIN_HOTKEY = 'Alt+Shift+d' 17 | VERSION = '0.0.0' 18 | 19 | 20 | major, minor = map(int, idaapi.get_kernel_version().split(".")) 21 | using_ida7api = (major > 6) 22 | 23 | 24 | #-------------------------------------------------------------------------- 25 | # Global settings 26 | #-------------------------------------------------------------------------- 27 | def global_settings(): 28 | global DUMP_FILE 29 | DUMP_FILE = ida_kernwin.ask_file(1, "*", "Enter the name of dump file:") 30 | print("You have selected " + DUMP_FILE) 31 | global dp 32 | dp = Dumpulator(DUMP_FILE) 33 | return 34 | 35 | 36 | 37 | 38 | #-------------------------------------------------------------------------- 39 | # Number of Args Form 40 | #-------------------------------------------------------------------------- 41 | class arg_num_select_t(ida_kernwin.Form): 42 | """Simple form to select how many args to initialise""" 43 | def __init__(self): 44 | self.__n = 0 45 | F = ida_kernwin.Form 46 | F.__init__(self, 47 | r"""BUTTON YES* Select 48 | BUTTON CANCEL Cancel 49 | Creating Function Arg Array 50 | 51 | {FormChangeCb} 52 | {cStr1} 53 | <##Number of args :{iArgCount}> 54 | 55 | 56 | 57 | """, { 'FormChangeCb': F.FormChangeCb(self.OnFormChange), 58 | 'cStr1': F.StringLabel("How many arguments will you be passing?", tp=F.FT_HTML_LABEL), 59 | 'iArgCount': F.StringInput(), 60 | }) 61 | 62 | 63 | def OnFormChange(self, fid): 64 | return 1 65 | 66 | @staticmethod 67 | def show(): 68 | f = arg_num_select_t() 69 | f, args = f.Compile() 70 | # Show form 71 | ok = f.Execute() 72 | if ok == 1: 73 | arg_count = f.iArgCount.value 74 | f.Free() 75 | print("Initiated " + arg_count + " args in array") 76 | return arg_count 77 | else: 78 | f.Free() 79 | return None 80 | #-------------------------------------------------------------------------- 81 | # Arg chooser Form 82 | #-------------------------------------------------------------------------- 83 | class arg_select_t(ida_kernwin.Form): 84 | """Simple form to select argument""" 85 | def __init__(self): 86 | self.__n = 0 87 | F = ida_kernwin.Form 88 | F.__init__(self, 89 | r"""BUTTON YES* Select 90 | BUTTON CANCEL Cancel 91 | Choose which argument to set 92 | 93 | {FormChangeCb} 94 | {cStr1} 95 | <##Argument :{iArgNum}> 96 | 97 | 98 | 99 | """, { 'FormChangeCb': F.FormChangeCb(self.OnFormChange), 100 | 'cStr1': F.StringLabel("Which argument would you like to set?", tp=F.FT_HTML_LABEL), 101 | 'iArgNum': F.StringInput(), 102 | }) 103 | 104 | 105 | def OnFormChange(self, fid): 106 | return 1 107 | 108 | @staticmethod 109 | def show(): 110 | f = arg_select_t() 111 | f, args = f.Compile() 112 | # Show form 113 | ok = f.Execute() 114 | if ok == 1: 115 | arg_choice = f.iArgNum.value 116 | f.Free() 117 | print("Setting argument " + arg_choice) 118 | return arg_choice 119 | else: 120 | f.Free() 121 | return None 122 | #-------------------------------------------------------------------------- 123 | # Dumpulator functions 124 | #-------------------------------------------------------------------------- 125 | def emulate_func(): 126 | """ 127 | Emulate your function 128 | """ 129 | 130 | idaapi.msg("Emulating function:\n") 131 | 132 | 133 | dp.call(CALL_ADDRESS, func_args) 134 | 135 | for arg in func_args: 136 | try: 137 | print("Address " + str(arg) + ": " + dp.read_str(arg)) 138 | except: 139 | print("Address " + str(arg) + " could not be stringified") 140 | 141 | return True 142 | 143 | 144 | def set_temp_addr_arg(): 145 | """ 146 | Set argument to a temp address 147 | """ 148 | argument_selection = (int(arg_select_t.show()) - 1) 149 | if ((argument_selection)>len(func_args)): 150 | print("Argument " + str(argument_selection) + " doesn't exist...") 151 | else: 152 | func_args[argument_selection] = dp.allocate(256) 153 | print("Allocated temporary space for argument " + str(argument_selection+1) + ". Address set to " + str(hex(func_args[argument_selection]))) 154 | 155 | return True 156 | 157 | def set_arg(): 158 | """ 159 | Set argument to selected address 160 | """ 161 | global first_arg 162 | #value = parse_highlighted_value("ERROR: Not a valid value selection\n") 163 | #value = copy_bytes_py3() 164 | value = idc.get_screen_ea() 165 | 166 | argument_selection = (int(arg_select_t.show()) - 1) 167 | if ((argument_selection)>len(func_args)): 168 | print("Argument " + str(argument_selection) + " doesn't exist...") 169 | else: 170 | print("Setting argument " + str(argument_selection+1) + " to " + str(hex(value))) 171 | func_args[argument_selection] = value 172 | 173 | return True 174 | 175 | def run_single_arg(): 176 | """ 177 | Run with dumpulator single argument 178 | """ 179 | global first_arg 180 | #value = parse_highlighted_value("ERROR: Not a valid value selection\n") 181 | #value = copy_bytes_py3() 182 | value = idc.get_screen_ea() 183 | if value is None: 184 | return False 185 | first_arg = value 186 | idaapi.msg("running with %s as single arg\n" % value) 187 | print(hex(value)) 188 | 189 | 190 | 191 | temp_addr = dp.allocate(256) 192 | 193 | dp.call(CALL_ADDRESS, [temp_addr, first_arg]) 194 | 195 | 196 | decrypted = dp.read_str(temp_addr) 197 | 198 | print(f"decrypted: '{decrypted}'") 199 | 200 | return True 201 | 202 | def set_call_addr(): 203 | """ 204 | Set call address 205 | """ 206 | value = idc.get_screen_ea() 207 | 208 | if value is None: 209 | return False 210 | global CALL_ADDRESS 211 | CALL_ADDRESS = value 212 | 213 | idaapi.msg("call address is set: %s\n" % value) 214 | print(hex(value)) 215 | 216 | global func_args 217 | func_arg_num = arg_num_select_t.show() 218 | func_args = [0] * int(func_arg_num) 219 | 220 | return True 221 | #-------------------------------------------------------------------------- 222 | # Plugin 223 | #-------------------------------------------------------------------------- 224 | class dumpulate_Plugin_t(idaapi.plugin_t): 225 | """ 226 | IDA Plugin for Dumpulate 227 | """ 228 | comment = "Dumpulate" 229 | help = "" 230 | wanted_name = PLUGIN_NAME 231 | # We only want a hotkey for the actual hash lookup 232 | wanted_hotkey = '' 233 | flags = idaapi.PLUGIN_KEEP 234 | 235 | #-------------------------------------------------------------------------- 236 | # Plugin Overloads 237 | #-------------------------------------------------------------------------- 238 | def init(self): 239 | """ 240 | This is called by IDA when it is loading the plugin. 241 | """ 242 | global p_initialized 243 | 244 | # Check if already initialized 245 | if p_initialized is False: 246 | p_initialized = True 247 | ## Print a nice header 248 | print("You've taken your dump... It's time to play with it") 249 | # initialize the menu actions our plugin will inject 250 | self._init_action_run_single_arg() 251 | self._init_action_set_call_addr() 252 | self._init_action_set_temp_addr_arg() 253 | self._init_action_set_arg() 254 | self._init_action_emulate_func() 255 | # initialize plugin hooks 256 | self._init_hooks() 257 | return idaapi.PLUGIN_KEEP 258 | 259 | 260 | def run(self, arg): 261 | """ 262 | This is called by IDA when the plugin is run from the plugins menu 263 | """ 264 | global_settings() 265 | 266 | 267 | 268 | def term(self): 269 | """ 270 | This is called by IDA when it is unloading the plugin. 271 | """ 272 | 273 | # unhook our plugin hooks 274 | self._hooks.unhook() 275 | # unregister our actions & free their resources 276 | self._del_action_run_single_arg() 277 | self._del_action_set_call_addr() 278 | self._del_action_set_temp_addr_arg() 279 | self._del_action_set_arg() 280 | self._del_action_emulate_func 281 | # done 282 | idaapi.msg("%s terminated...\n" % self.wanted_name) 283 | 284 | 285 | #-------------------------------------------------------------------------- 286 | # IDA Actions 287 | #-------------------------------------------------------------------------- 288 | ACTION_RUN_SINGLE_ARG = "dumpulate:runsinglearg" 289 | ACTION_SET_CALL_ADDR = "dumpulate:setcalladdr" 290 | ACTION_SET_TEMP_ADDR_ARG = "dumpulate:settempaddrarg" 291 | ACTION_SET_ARG = "dumpulate:setarg" 292 | ACTION_EMULATE_FUNC = "dumpulate:emulatefunc" 293 | 294 | def _init_action_run_single_arg(self): 295 | """ 296 | Register the run single arg action with IDA. 297 | """ 298 | action_desc = idaapi.action_desc_t( 299 | self.ACTION_RUN_SINGLE_ARG, # The action name. 300 | "Dumpulate run with single arg", # The action text. 301 | IDACtxEntry(run_single_arg), # The action handler. 302 | None, # Optional: action shortcut 303 | "Run with single arg" # Optional: tooltip 304 | 305 | ) 306 | # register the action with IDA 307 | assert idaapi.register_action(action_desc), "Action registration failed" 308 | 309 | def _init_action_set_call_addr(self): 310 | """ 311 | Register the set call address action with IDA. 312 | """ 313 | action_desc = idaapi.action_desc_t( 314 | self.ACTION_SET_CALL_ADDR, # The action name. 315 | "Dumpulate Set Call Address", # The action text. 316 | IDACtxEntry(set_call_addr), # The action handler. 317 | None, # Optional: action shortcut 318 | "Set call address" # Optional: tooltip 319 | 320 | ) 321 | # register the action with IDA 322 | assert idaapi.register_action(action_desc), "Action registration failed" 323 | 324 | def _init_action_set_temp_addr_arg(self): 325 | """ 326 | Register the set temp addr arg action with IDA. 327 | """ 328 | action_desc = idaapi.action_desc_t( 329 | self.ACTION_SET_TEMP_ADDR_ARG, # The action name. 330 | "Dumpulate Allocate Temp For Argument", # The action text. 331 | IDACtxEntry(set_temp_addr_arg), # The action handler. 332 | None, # Optional: action shortcut 333 | "Allocate temporary address space for arg" # Optional: tooltip 334 | 335 | ) 336 | # register the action with IDA 337 | assert idaapi.register_action(action_desc), "Action registration failed" 338 | 339 | def _init_action_set_arg(self): 340 | """ 341 | Register the set arg action with IDA. 342 | """ 343 | action_desc = idaapi.action_desc_t( 344 | self.ACTION_SET_ARG, # The action name. 345 | "Dumpulate Set Argument", # The action text. 346 | IDACtxEntry(set_arg), # The action handler. 347 | None, # Optional: action shortcut 348 | "Set arg to selected address" # Optional: tooltip 349 | 350 | ) 351 | # register the action with IDA 352 | assert idaapi.register_action(action_desc), "Action registration failed" 353 | 354 | def _init_action_emulate_func(self): 355 | """ 356 | Register the set arg action with IDA. 357 | """ 358 | action_desc = idaapi.action_desc_t( 359 | self.ACTION_EMULATE_FUNC, # The action name. 360 | "Dumpulate Emulate Function", # The action text. 361 | IDACtxEntry(emulate_func), # The action handler. 362 | None, # Optional: action shortcut 363 | "Emulate your function" # Optional: tooltip 364 | 365 | ) 366 | # register the action with IDA 367 | assert idaapi.register_action(action_desc), "Action registration failed" 368 | 369 | def _del_action_set_call_addr(self): 370 | idaapi.unregister_action(self.ACTION_SET_CALL_ADDR) 371 | 372 | def _del_action_run_single_arg(self): 373 | idaapi.unregister_action(self.ACTION_RUN_SINGLE_ARG) 374 | 375 | def _del_action_set_temp_addr_arg(self): 376 | idaapi.unregister_action(self.ACTION_SET_TEMP_ADDR_ARG) 377 | 378 | def _del_action_set_arg(self): 379 | idaapi.unregister_action(self.ACTION_SET_ARG) 380 | 381 | def _del_action_emulate_func(self): 382 | idaapi.unregister_action(self.ACTION_EMULATE_FUNC) 383 | 384 | #-------------------------------------------------------------------------- 385 | # Initialize Hooks 386 | #-------------------------------------------------------------------------- 387 | 388 | def _init_hooks(self): 389 | """ 390 | Install plugin hooks into IDA. 391 | """ 392 | self._hooks = Hooks() 393 | self._hooks.ready_to_run = self._init_hexrays_hooks 394 | self._hooks.hook() 395 | 396 | 397 | def _init_hexrays_hooks(self): 398 | """ 399 | Install Hex-Rays hooks (when available). 400 | NOTE: This is called when the ui_ready_to_run event fires. 401 | """ 402 | if idaapi.init_hexrays_plugin(): 403 | idaapi.install_hexrays_callback(self._hooks.hxe_callback) 404 | 405 | 406 | #------------------------------------------------------------------------------ 407 | # Plugin Hooks 408 | #------------------------------------------------------------------------------ 409 | class Hooks(idaapi.UI_Hooks): 410 | 411 | def finish_populating_widget_popup(self, widget, popup): 412 | """ 413 | A right click menu is about to be shown. (IDA 7) 414 | """ 415 | inject_actions(widget, popup, idaapi.get_widget_type(widget)) 416 | return 0 417 | 418 | def hxe_callback(self, event, *args): 419 | """ 420 | HexRays event callback. 421 | We lump this under the (UI) Hooks class for organizational reasons. 422 | """ 423 | 424 | # 425 | # if the event callback indicates that this is a popup menu event 426 | # (in the hexrays window), we may want to install our menu 427 | # actions depending on what the cursor right clicked. 428 | # 429 | 430 | if event == idaapi.hxe_populating_popup: 431 | form, popup, vu = args 432 | 433 | 434 | idaapi.attach_action_to_popup( 435 | form, 436 | popup, 437 | dumpulate_Plugin_t.ACTION_RUN_SINGLE_ARG, 438 | "Dumpulate - run single arg", 439 | idaapi.SETMENU_APP, 440 | ) 441 | 442 | idaapi.attach_action_to_popup( 443 | form, 444 | popup, 445 | dumpulate_Plugin_t.ACTION_SET_CALL_ADDR, 446 | "Dumpulate - set call address", 447 | idaapi.SETMENU_APP, 448 | ) 449 | 450 | idaapi.attach_action_to_popup( 451 | form, 452 | popup, 453 | dumpulate_Plugin_t.ACTION_SET_TEMP_ADDR_ARG, 454 | "Dumpulate - set temp addr arg", 455 | idaapi.SETMENU_APP, 456 | ) 457 | 458 | idaapi.attach_action_to_popup( 459 | form, 460 | popup, 461 | dumpulate_Plugin_t.ACTION_SET_ARG, 462 | "Dumpulate - set argument", 463 | idaapi.SETMENU_APP, 464 | ) 465 | 466 | idaapi.attach_action_to_popup( 467 | form, 468 | popup, 469 | dumpulate_Plugin_t.ACTION_EMULATE_FUNC, 470 | "Dumpulate - emulate function", 471 | idaapi.SETMENU_APP, 472 | ) 473 | 474 | # done 475 | return 0 476 | 477 | #------------------------------------------------------------------------------ 478 | # Prefix Wrappers 479 | #------------------------------------------------------------------------------ 480 | def inject_actions(form, popup, form_type): 481 | """ 482 | Inject actions to popup menu(s) based on context. 483 | """ 484 | 485 | # 486 | # disassembly window 487 | # 488 | 489 | if (form_type == idaapi.BWN_DISASMS) or (form_type == idaapi.BWN_PSEUDOCODE): 490 | # insert the action entry into the menu 491 | # 492 | 493 | idaapi.attach_action_to_popup( 494 | form, 495 | popup, 496 | dumpulate_Plugin_t.ACTION_RUN_SINGLE_ARG, 497 | "Dumpulate run with single arg", 498 | idaapi.SETMENU_APP 499 | ) 500 | 501 | idaapi.attach_action_to_popup( 502 | form, 503 | popup, 504 | dumpulate_Plugin_t.ACTION_SET_CALL_ADDR, 505 | "Dumpulate set call address", 506 | idaapi.SETMENU_APP 507 | ) 508 | 509 | idaapi.attach_action_to_popup( 510 | form, 511 | popup, 512 | dumpulate_Plugin_t.ACTION_SET_TEMP_ADDR_ARG, 513 | "Dumpulate set temp addr arg", 514 | idaapi.SETMENU_APP 515 | ) 516 | 517 | idaapi.attach_action_to_popup( 518 | form, 519 | popup, 520 | dumpulate_Plugin_t.ACTION_SET_ARG, 521 | "Dumpulate set argument", 522 | idaapi.SETMENU_APP 523 | ) 524 | 525 | idaapi.attach_action_to_popup( 526 | form, 527 | popup, 528 | dumpulate_Plugin_t.ACTION_EMULATE_FUNC, 529 | "Dumpulate Emulate Function", 530 | idaapi.SETMENU_APP 531 | ) 532 | 533 | # done 534 | return 0 535 | 536 | #------------------------------------------------------------------------------ 537 | # IDA ctxt 538 | #------------------------------------------------------------------------------ 539 | 540 | class IDACtxEntry(idaapi.action_handler_t): 541 | """ 542 | A basic Context Menu class to utilize IDA's action handlers. 543 | """ 544 | 545 | def __init__(self, action_function): 546 | idaapi.action_handler_t.__init__(self) 547 | self.action_function = action_function 548 | 549 | def activate(self, ctx): 550 | """ 551 | Execute the embedded action_function when this context menu is invoked. 552 | """ 553 | self.action_function() 554 | return 1 555 | 556 | def update(self, ctx): 557 | """ 558 | Ensure the context menu is always available in IDA. 559 | """ 560 | return idaapi.AST_ENABLE_ALWAYS 561 | 562 | 563 | #-------------------------------------------------------------------------- 564 | # Plugin Registration 565 | #-------------------------------------------------------------------------- 566 | 567 | # Global flag to ensure plugin is only initialized once 568 | p_initialized = False 569 | 570 | # Register IDA plugin 571 | def PLUGIN_ENTRY(): 572 | return dumpulate_Plugin_t() 573 | --------------------------------------------------------------------------------