├── MyIDAFrida.py ├── README.md └── pic └── 1678938482469.png /MyIDAFrida.py: -------------------------------------------------------------------------------- 1 | from string import Template 2 | import ida_lines 3 | import idaapi 4 | import idc 5 | from ida_idaapi import plugin_t 6 | import datetime 7 | import ida_name 8 | import json 9 | import os 10 | 11 | from PyQt5 import QtCore 12 | from PyQt5.Qt import QApplication 13 | from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QTextEdit 14 | 15 | 16 | def clear_screen(): 17 | window = idaapi.find_widget("Output window") 18 | idaapi.activate_widget(window, True) 19 | idaapi.process_ui_action("msglist:Clear") 20 | print("-" * 10, datetime.datetime.now(), "-" * 10) 21 | 22 | 23 | def generate_file_byjsdata(data, filename) -> bool: 24 | try: 25 | open(filename, "w").write(data) 26 | print("生成的Frida脚本已导出到文件: ", filename) 27 | except Exception as e: 28 | print(e) 29 | return False 30 | return generate_clipboard_byjsdata(data) 31 | 32 | 33 | def generate_clipboard_byjsdata(data) -> bool: 34 | clear_screen() 35 | print(data) 36 | try: 37 | QApplication.clipboard().setText(data) 38 | print("生成的Frida脚本已复制到剪贴板!") 39 | except Exception as e: 40 | print(e) 41 | return False 42 | return True 43 | 44 | 45 | hook_function_template = """ 46 | function hook_$functionName(){ 47 | var base_addr = Module.findBaseAddress("$soName"); 48 | 49 | Interceptor.attach(base_addr.add($offset), { 50 | onEnter(args) { 51 | console.log("call $functionName"); 52 | $args 53 | }, 54 | onLeave(retval) { 55 | $result 56 | console.log("leave $functionName"); 57 | } 58 | }); 59 | } 60 | """ 61 | 62 | inline_hook_template = """ 63 | function hook_$offset(){ 64 | var base_addr = Module.findBaseAddress("$soName"); 65 | 66 | Interceptor.attach(base_addr.add($offset), { 67 | onEnter(args) { 68 | console.log("call $offset"); 69 | console.log(JSON.stringify(this.context)); 70 | }, 71 | }); 72 | } 73 | """ 74 | 75 | logTemplate = 'console.log("arg$index:"+args[$index]);\n' 76 | 77 | dlopenAfter_template = """ 78 | var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext"); 79 | if(android_dlopen_ext != null){ 80 | Interceptor.attach(android_dlopen_ext,{ 81 | onEnter: function(args){ 82 | var soName = args[0].readCString(); 83 | if(soName.indexOf("$soName") !== -1){ 84 | this.hook = true; 85 | } 86 | }, 87 | onLeave: function(retval){ 88 | if(this.hook) { 89 | this.hook = false; 90 | dlopentodo(); 91 | } 92 | } 93 | }); 94 | } 95 | 96 | function dlopentodo(){ 97 | //todo 98 | } 99 | """ 100 | 101 | init_template = """ 102 | function hookInit(){ 103 | var linkername; 104 | var alreadyHook = false; 105 | var call_constructor_addr = null; 106 | var arch = Process.arch; 107 | if (arch.endsWith("arm")) { 108 | linkername = "linker"; 109 | } else { 110 | linkername = "linker64"; 111 | } 112 | 113 | var symbols = Module.enumerateSymbolsSync(linkername); 114 | for (var i = 0; i < symbols.length; i++) { 115 | var symbol = symbols[i]; 116 | if (symbol.name.indexOf("call_constructor") !== -1) { 117 | call_constructor_addr = symbol.address; 118 | } 119 | } 120 | 121 | if (call_constructor_addr.compare(NULL) > 0) { 122 | console.log("get construct address"); 123 | Interceptor.attach(call_constructor_addr, { 124 | onEnter: function (args) { 125 | if(alreadyHook === false){ 126 | const targetModule = Process.findModuleByName("$soName"); 127 | if (targetModule !== null) { 128 | alreadyHook = true; 129 | inittodo(); 130 | } 131 | } 132 | } 133 | }); 134 | } 135 | } 136 | 137 | function inittodo(){ 138 | //todo 139 | } 140 | """ 141 | 142 | dump_template = """ 143 | function dump_$offset() { 144 | var base_addr = Module.findBaseAddress("$soName"); 145 | var dump_addr = base_addr.add($offset); 146 | console.log(hexdump(dump_addr, {length: $length})); 147 | } 148 | """ 149 | 150 | 151 | def generate_printArgs(argNum): 152 | if argNum == 0: 153 | return "// no args" 154 | else: 155 | temp = Template(logTemplate) 156 | logText = "" 157 | for i in range(argNum): 158 | logText += temp.substitute({"index": i}) 159 | logText += " " 160 | return logText 161 | 162 | 163 | def generate_for_func(soName, functionName, address, argNum, hasReturn): 164 | argsPrint = generate_printArgs(argNum) 165 | 166 | retPrint = "// no return" 167 | if hasReturn: 168 | retPrint = "console.log(retval);" 169 | 170 | temp = Template(hook_function_template) 171 | offset = getOffset(address) 172 | result = temp.substitute( 173 | { 174 | "soName": soName, 175 | "functionName": functionName, 176 | "offset": hex(offset), 177 | "args": argsPrint, 178 | "result": retPrint, 179 | } 180 | ) 181 | 182 | generate_file_byjsdata(result, "MyIDAFrida_hook.js") 183 | 184 | 185 | def getOffset(address): 186 | if idaapi.get_inf_structure().is_64bit(): 187 | return address 188 | else: 189 | return address + idc.get_sreg(address, "T") 190 | 191 | 192 | def generate_for_inline(soName, address): 193 | offset = getOffset(address) 194 | temp = Template(inline_hook_template) 195 | result = temp.substitute({"soName": soName, "offset": hex(offset)}) 196 | if idaapi.is_call_insn(address): 197 | callAddr = idaapi.get_name_ea(0, idc.print_operand(address, 0)) 198 | if callAddr != idaapi.BADADDR: 199 | callAddress = idc.get_operand_value(address, 0) 200 | argnum, _ = get_argnum_and_ret(callAddress) 201 | argsPrint = generate_printArgs(argnum) 202 | result = result.replace( 203 | "console.log(JSON.stringify(this.context));", argsPrint 204 | ) 205 | 206 | generate_file_byjsdata(result, "MyIDAFrida_inline.js") 207 | 208 | 209 | def get_argnum_and_ret(address): 210 | cfun = idaapi.decompile(address) 211 | 212 | argnum = len(cfun.arguments) 213 | ret = True 214 | 215 | dcl = ida_lines.tag_remove(cfun.print_dcl()) 216 | 217 | if (dcl.startswith("void ") is True) & (dcl.startswith("void *") is False): 218 | ret = False 219 | return argnum, ret 220 | 221 | 222 | def generate_for_func_by_address(addr): 223 | soName = idaapi.get_root_filename() 224 | functionName = idaapi.get_func_name(addr) 225 | argnum, ret = get_argnum_and_ret(addr) 226 | generate_for_func(soName, functionName, addr, argnum, ret) 227 | 228 | 229 | def generate_for_inline_by_address(addr): 230 | soName = idaapi.get_root_filename() 231 | generate_for_inline(soName, addr) 232 | 233 | 234 | def generate_snippet(addr): 235 | startAddress = idc.get_func_attr(addr, idc.FUNCATTR_START) 236 | if startAddress == addr: 237 | generate_for_func_by_address(addr) 238 | elif startAddress == idc.BADADDR: 239 | print("当前传入的地址不在函数内..idc.BADADDR", startAddress) 240 | else: 241 | generate_for_inline_by_address(addr) 242 | 243 | 244 | def generateInitCode(): 245 | soName = idaapi.get_root_filename() 246 | dlopenjs = Template(dlopenAfter_template).substitute({"soName": soName}) 247 | 248 | initjs = Template(init_template).substitute({"soName": soName}) 249 | 250 | generate_clipboard_byjsdata(dlopenjs + "\n\n" + initjs) 251 | 252 | 253 | def generate_dump_script(start, length): 254 | soName = idaapi.get_root_filename() 255 | dumpjs = Template(dump_template).substitute( 256 | {"soName": soName, "offset": hex(start), "length": hex(length)} 257 | ) 258 | 259 | generate_clipboard_byjsdata(dumpjs) 260 | 261 | 262 | class Hook(idaapi.View_Hooks): 263 | def view_dblclick(self, view, event): 264 | widgetType = idaapi.get_widget_type(view) 265 | if widgetType == idaapi.BWN_DISASM: 266 | address = idaapi.get_screen_ea() 267 | generate_snippet(address) 268 | 269 | 270 | class ActionManager(object): 271 | def __init__(self): 272 | self.__actions = [] 273 | 274 | def register(self, action): 275 | self.__actions.append(action) 276 | idaapi.register_action( 277 | idaapi.action_desc_t(action.name, action.description, action, action.hotkey) 278 | ) 279 | 280 | def initialize(self): 281 | pass 282 | 283 | def finalize(self): 284 | for action in self.__actions: 285 | idaapi.unregister_action(action.name) 286 | 287 | 288 | action_manager = ActionManager() 289 | 290 | 291 | class Action(idaapi.action_handler_t): 292 | """ 293 | Convenience wrapper with name property allowing to be registered in IDA using ActionManager 294 | """ 295 | 296 | description = None 297 | hotkey = None 298 | 299 | def __init__(self): 300 | super(Action, self).__init__() 301 | 302 | @property 303 | def name(self): 304 | return "FridaIDA:" + type(self).__name__ 305 | 306 | def activate(self, ctx): 307 | raise NotImplementedError 308 | 309 | def update(self, ctx): 310 | raise NotImplementedError 311 | 312 | 313 | default_template = """ 314 | //这是默认的模板 315 | (function () { 316 | // @ts-ignore 317 | function print_arg(addr) { 318 | try { 319 | var module = Process.findRangeByAddress(addr); 320 | if (module != null) return "\\n"+hexdump(addr) + "\\n"; 321 | return ptr(addr) + "\\n"; 322 | } catch (e) { 323 | return addr + "\\n"; 324 | } 325 | } 326 | // @ts-ignore 327 | function hook_native_addr(funcPtr, paramsNum) { 328 | var module = Process.findModuleByAddress(funcPtr); 329 | try { 330 | Interceptor.attach(funcPtr, { 331 | onEnter: function (args) { 332 | this.logs = ""; 333 | this.params = []; 334 | // @ts-ignore 335 | this.logs=this.logs.concat("So: " + module.name + " Method: [funcname] offset: " + ptr(funcPtr).sub(module.base) + "\\n"); 336 | for (let i = 0; i < paramsNum; i++) { 337 | this.params.push(args[i]); 338 | this.logs=this.logs.concat("this.args" + i + " onEnter: " + print_arg(args[i])); 339 | } 340 | }, onLeave: function (retval) { 341 | for (let i = 0; i < paramsNum; i++) { 342 | this.logs=this.logs.concat("this.args" + i + " onLeave: " + print_arg(this.params[i])); 343 | } 344 | this.logs=this.logs.concat("retval onLeave: " + print_arg(retval) + "\\n"); 345 | console.log(this.logs); 346 | } 347 | }); 348 | } catch (e) { 349 | console.log(e); 350 | } 351 | } 352 | // @ts-ignore 353 | hook_native_addr(Module.findBaseAddress("[filename]").add([offset]), [nargs]); 354 | })(); 355 | """ 356 | 357 | 358 | class Configuration: 359 | def __init__(self) -> None: 360 | self.frida_cmd = ( 361 | """frida -U --attach-name="com.example.app" -l gen.js --no-pause""" 362 | ) 363 | self.template = default_template 364 | if os.path.exists("IDAFrida.json"): 365 | self.load() 366 | 367 | def set_frida_cmd(self, s): 368 | self.frida_cmd = s 369 | self.store() 370 | 371 | def set_template(self, s): 372 | self.template = s 373 | self.store() 374 | 375 | def reset(self): 376 | self.__init__() 377 | 378 | def store(self): 379 | try: 380 | data = {"frida_cmd": self.frida_cmd, "template": self.template} 381 | open("IDAFrida.json", "w").write(json.dumps(data)) 382 | except Exception as e: 383 | print(e) 384 | 385 | def load(self): 386 | try: 387 | data = json.loads(open("IDAFrida.json", "r").read()) 388 | self.frida_cmd = data["frida_cmd"] 389 | self.template = data["template"] 390 | except Exception as e: 391 | print(e) 392 | 393 | 394 | global_config = Configuration() 395 | 396 | 397 | class ConfigurationUI(QDialog): 398 | def __init__(self, conf: Configuration) -> None: 399 | super(ConfigurationUI, self).__init__() 400 | self.conf = conf 401 | self.edit_template = QTextEdit() 402 | self.edit_template.setPlainText(self.conf.template) 403 | layout = QHBoxLayout() 404 | layout.addWidget(self.edit_template) 405 | self.setLayout(layout) 406 | 407 | def closeEvent(self, a0) -> None: 408 | self.conf.set_template(self.edit_template.toPlainText()) 409 | self.conf.store() 410 | return super().closeEvent(a0) 411 | 412 | 413 | class ScriptGenerator: 414 | def __init__(self, configuration: Configuration) -> None: 415 | self.conf = configuration 416 | self.imagebase = idaapi.get_imagebase() 417 | 418 | @staticmethod 419 | def get_idb_filename(): 420 | return os.path.basename(idaapi.get_input_file_path()) 421 | 422 | @staticmethod 423 | def get_idb_path(): 424 | return os.path.dirname(idaapi.get_input_file_path()) 425 | 426 | def get_function_name(self, ea): 427 | """ 428 | Get the real function name 429 | """ 430 | 431 | function_name = idc.demangle_name( 432 | idc.get_func_name(ea), idc.get_inf_attr(idc.INF_SHORT_DN) 433 | ) 434 | 435 | if not function_name: 436 | function_name = idc.get_func_name(ea) 437 | 438 | if not function_name: 439 | function_name = idc.get_name(ea, ida_name.GN_VISIBLE) 440 | 441 | if not function_name: 442 | function_name = "UNKN_FNC_%s" % hex(ea) 443 | 444 | return function_name 445 | 446 | def generate_stub(self, repdata: dict): 447 | s = self.conf.template 448 | for key, v in repdata.items(): 449 | s = s.replace("[%s]" % key, v) 450 | return s 451 | 452 | def generate_for_funcs(self, func_addr_list) -> str: 453 | stubs = [] 454 | for func_addr in func_addr_list: 455 | dec_func = idaapi.decompile(func_addr) 456 | repdata = { 457 | "filename": self.get_idb_filename(), 458 | "funcname": self.get_function_name(func_addr), 459 | "offset": hex(func_addr - self.imagebase), 460 | "nargs": hex(dec_func.type.get_nargs()), 461 | } 462 | stubs.append(self.generate_stub(repdata)) 463 | return "\n".join(stubs) 464 | 465 | def generate_for_funcs_to_file(self, func_addr_list, filename) -> bool: 466 | data = self.generate_for_funcs(func_addr_list) 467 | return generate_file_byjsdata(data, filename) 468 | 469 | 470 | class Frida: 471 | def __init__(self, conf: Configuration) -> None: 472 | self.conf = conf 473 | 474 | 475 | class IDAFridaMenuAction(Action): 476 | TopDescription = "MyIDAFrida" 477 | 478 | def __init__(self): 479 | super(IDAFridaMenuAction, self).__init__() 480 | 481 | def activate(self, ctx) -> None: 482 | raise NotImplemented 483 | 484 | def update(self, ctx) -> None: 485 | if ( 486 | ctx.form_type == idaapi.BWN_FUNCS 487 | or ctx.form_type == idaapi.BWN_PSEUDOCODE 488 | or ctx.form_type == idaapi.BWN_DISASM 489 | ): 490 | idaapi.attach_action_to_popup( 491 | ctx.widget, None, self.name, self.TopDescription + "/" 492 | ) 493 | return idaapi.AST_ENABLE_FOR_WIDGET 494 | return idaapi.AST_DISABLE_FOR_WIDGET 495 | 496 | 497 | class GenerateFridaHookScript(IDAFridaMenuAction): 498 | description = "生成Frida hook" 499 | 500 | def __init__(self): 501 | super(GenerateFridaHookScript, self).__init__() 502 | 503 | def activate(self, ctx): 504 | gen = ScriptGenerator(global_config) 505 | idb_path = os.path.dirname(idaapi.get_input_file_path()) 506 | out_file = os.path.join(idb_path, "MyIDAhook.js") 507 | if ctx.form_type == idaapi.BWN_FUNCS: 508 | selected = [idaapi.getn_func(idx).start_ea for idx in ctx.chooser_selection] 509 | 510 | else: 511 | eaaddr = idaapi.get_screen_ea() 512 | startAddress = idc.get_func_attr(eaaddr, idc.FUNCATTR_START) 513 | if startAddress == idc.BADADDR: 514 | print("当前传入的地址不在函数内..idc.BADADDR", startAddress) 515 | return 516 | selected = [idaapi.get_func(eaaddr).start_ea] 517 | gen.generate_for_funcs_to_file(selected, out_file) 518 | 519 | 520 | class ViewFridaTemplate(IDAFridaMenuAction): 521 | description = "查看Frida Template" 522 | 523 | def __init__(self): 524 | super(ViewFridaTemplate, self).__init__() 525 | 526 | def activate(self, ctx): 527 | ui = ConfigurationUI(global_config) 528 | ui.show() 529 | ui.exec_() 530 | 531 | 532 | class GenerateFridaInitScript(IDAFridaMenuAction): 533 | description = "生成Frida Init" 534 | 535 | def __init__(self): 536 | super(GenerateFridaInitScript, self).__init__() 537 | 538 | def activate(self, ctx): 539 | generateInitCode() 540 | 541 | 542 | class GenerateFridaDumpScript(IDAFridaMenuAction): 543 | description = "生成Frida Dump" 544 | 545 | def __init__(self): 546 | super(GenerateFridaDumpScript, self).__init__() 547 | 548 | def activate(self, ctx): 549 | if ctx.form_type == idaapi.BWN_DISASM: 550 | start = idc.read_selection_start() 551 | end = idc.read_selection_end() 552 | if (start != idaapi.BADADDR) and (end != idaapi.BADADDR): 553 | length = end - start 554 | generate_dump_script(start, length) 555 | 556 | 557 | myViewHook = Hook() 558 | myViewHook.hook() 559 | 560 | action_manager.register(GenerateFridaHookScript()) 561 | action_manager.register(GenerateFridaDumpScript()) 562 | action_manager.register(GenerateFridaInitScript()) 563 | action_manager.register(ViewFridaTemplate()) 564 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyIdaFrida 2 | 3 | 通过Ida插件生成frida脚本 4 | 5 | ![示例图](./pic/1678938482469.png) 6 | 7 | ## 用法 8 | 9 | 1. 双击反汇编窗口 自动生成对应的inlinehook frida脚本 10 | 2. 右键菜单 选择 MyIDAFrida 可以生成对应的脚本 11 | 3. 函数窗口列表多选 右键菜单可以批量生成Frida Hook 12 | 13 | ## 学习来源 14 | 15 | 1. ShowFridaCode -by lilac 16 | 2. https://github.com/P4nda0s/IDAFrida -by P4nda0s 17 | -------------------------------------------------------------------------------- /pic/1678938482469.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnxiangLemon/MyIdaFrida/14b9a3857b83af17f4a979d5ba95d134a3a0bb2b/pic/1678938482469.png --------------------------------------------------------------------------------