├── .gitignore ├── result.png ├── .gitattributes ├── README.md ├── LICENSE └── stalker_trace_so.py /.gitignore: -------------------------------------------------------------------------------- 1 | work_temp/ 2 | .idea/ -------------------------------------------------------------------------------- /result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oacia/stalker_trace_so/HEAD/result.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stalker_trace_so 2 | 3 | 一个IDA插件,利用frida-stalker在加载so时打印出所有函数调用,解决frida-trace无法在so加载时trace的问题 4 | 5 | # 使用 6 | 7 | 1. 将`stalker_trace_so.py`复制到`[IDA安装目录]\plugins` 8 | 9 | 2. 在IDA中选择`Edit->Plugins->stalker_trace_so`,将会在so所在的目录下自动生成`trace_xxx.js`,你可以在任何想要开始追踪函数执行流的地方调用其中的`trace_so`函数 10 | 11 | 3. frida运行脚本 12 | ```shell 13 | frida -U -l trace_xxx.js -f [package name] 14 | ``` 15 | 16 | 示例输出: 17 | 18 | ![image-20231117181707861](./result.png) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 oacia 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 | -------------------------------------------------------------------------------- /stalker_trace_so.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import os 3 | from idaapi import plugin_t 4 | from idaapi import PLUGIN_PROC 5 | from idaapi import PLUGIN_OK 6 | from idaapi import get_imagebase 7 | import idaapi 8 | import ida_nalt 9 | import idautils 10 | import idc 11 | import random 12 | from functools import reduce 13 | 14 | template_js = ''' 15 | var func_addr = [[func_addr]]; 16 | var func_name = [[func_name]]; 17 | var so_name = "[so_name]"; 18 | 19 | /* 20 | @param print_stack: Whether printing stack info, default is false. 21 | */ 22 | var print_stack = false; 23 | 24 | /* 25 | @param print_stack_mode 26 | - FUZZY: print as much stack info as possible 27 | - ACCURATE: print stack info as accurately as possible 28 | - MANUAL: if printing the stack info in an error and causes exit, use this option to manually print the address 29 | */ 30 | var print_stack_mode = "FUZZY"; 31 | 32 | function addr_in_so(addr){ 33 | var process_Obj_Module_Arr = Process.enumerateModules(); 34 | for(var i = 0; i < process_Obj_Module_Arr.length; i++) { 35 | if(addr>process_Obj_Module_Arr[i].base && addr= 0) { 49 | this.is_can_hook = true; 50 | } 51 | } 52 | }, 53 | onLeave: function (retval) { 54 | if (this.is_can_hook) { 55 | // note: you can do any thing before or after stalker trace so. 56 | 57 | trace_so(); 58 | } 59 | } 60 | } 61 | ); 62 | } 63 | 64 | function trace_so(){ 65 | var times = 1; 66 | var module = Process.getModuleByName(so_name); 67 | var pid = Process.getCurrentThreadId(); 68 | console.log("start Stalker!"); 69 | Stalker.exclude({ 70 | "base": Process.getModuleByName("libc.so").base, 71 | "size": Process.getModuleByName("libc.so").size 72 | }) 73 | Stalker.follow(pid,{ 74 | events:{ 75 | call:false, 76 | ret:false, 77 | exec:false, 78 | block:false, 79 | compile:false 80 | }, 81 | onReceive:function(events){ 82 | }, 83 | transform: function (iterator) { 84 | var instruction = iterator.next(); 85 | do{ 86 | if (func_addr.indexOf(instruction.address - module.base) !== -1) { 87 | console.log("call" + times + ":" + func_name[func_addr.indexOf(instruction.address - module.base)]) 88 | times = times + 1 89 | if (print_stack) { 90 | if (print_stack_mode === "FUZZY") { 91 | iterator.putCallout((context) => { 92 | console.log("backtrace:\\n"+Thread.backtrace(context, Backtracer.FUZZY).map(DebugSymbol.fromAddress).join('\\n')); 93 | console.log('---------------------') 94 | }); 95 | } 96 | else if (print_stack_mode === "ACCURATE") { 97 | iterator.putCallout((context) => { 98 | console.log("backtrace:\\n"+Thread.backtrace(context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\\n')); 99 | console.log('---------------------') 100 | }) 101 | } 102 | 103 | else if (print_stack_mode === "MANUAL") { 104 | iterator.putCallout((context) => { 105 | console.log("backtrace:") 106 | Thread.backtrace(context, Backtracer.FUZZY).map(addr_in_so); 107 | console.log('---------------------') 108 | }) 109 | } 110 | } 111 | } 112 | iterator.keep(); 113 | } while ((instruction = iterator.next()) !== null); 114 | }, 115 | 116 | onCallSummary:function(summary){ 117 | 118 | } 119 | }); 120 | console.log("Stalker end!"); 121 | } 122 | 123 | setImmediate(hook_dlopen,0); 124 | ''' 125 | 126 | 127 | class UI_Hook(idaapi.UI_Hooks): 128 | def __init__(self): 129 | idaapi.UI_Hooks.__init__(self) 130 | 131 | def finish_populating_widget_popup(self, form, popup): 132 | form_type = idaapi.get_widget_type(form) 133 | if form_type == idaapi.BWN_FUNCS or form_type == idaapi.BWN_PSEUDOCODE or form_type == idaapi.BWN_DISASM: 134 | idaapi.attach_action_to_popup(form, popup, "stalkerTraceSo:genJsScript", None) 135 | 136 | 137 | class GenerateFridaHookScript(idaapi.action_handler_t): 138 | def __init__(self): 139 | idaapi.action_handler_t.__init__(self) 140 | 141 | def activate(self, ctx): 142 | 143 | if ctx.widget_type == idaapi.BWN_FUNCS: 144 | selected = [idaapi.getn_func(idx).start_ea for idx in ctx.chooser_selection] 145 | else: 146 | selected = idautils.Functions() 147 | 148 | generate_js_script(selected) 149 | 150 | def update(self, ctx): 151 | return idaapi.AST_ENABLE_ALWAYS 152 | 153 | 154 | def generate_hook_code(template_js, func_addr, func_name, so_name): 155 | replacements = { 156 | "[func_addr]": ', '.join(func_addr), 157 | "[func_name]": ', '.join(func_name), 158 | "[so_name]": "%s" % so_name 159 | } 160 | return reduce(lambda acc, item: acc.replace(item[0], item[1]), replacements.items(), template_js) 161 | 162 | 163 | def generate_js_script(func_list): 164 | func_addr = [] 165 | func_name = [] 166 | for func_ea in func_list: 167 | # thumb mode 168 | if idc.get_sreg(func_ea, "T"): 169 | func_addr.append(hex(func_ea + 1)) 170 | else: 171 | func_addr.append(hex(func_ea)) 172 | func_name.append('"{}"'.format(idc.get_func_name(func_ea))) 173 | 174 | so_path, so_name = os.path.split(ida_nalt.get_input_file_path()) 175 | hook_code = generate_hook_code(template_js, func_addr, func_name, so_name) 176 | r = [random.choice("abcdefghijklmnopqrstuvwxyz") for _ in range(5)] 177 | script_name = "trace_" + so_name.split(".")[0] + '_' + ''.join(r) + ".js" 178 | save_path = os.path.join(so_path, script_name) 179 | with open(save_path, "w", encoding="utf-8") as f: 180 | f.write(hook_code) 181 | 182 | print("usage:") 183 | print(f'frida -U -l "{save_path}" -f [package name]') 184 | 185 | 186 | class stalker_trace_so(plugin_t): 187 | flags = PLUGIN_PROC 188 | comment = "stalker trace so" 189 | help = "" 190 | wanted_name = "stalker trace so" 191 | wanted_hotkey = "" 192 | 193 | def init(self): 194 | print("stalker_trace_so plugin has been loaded.") 195 | idaapi.register_action( 196 | idaapi.action_desc_t("stalkerTraceSo:genJsScript", "stalker trace so", GenerateFridaHookScript(), None, 197 | None, 201)) 198 | # Add ui hook 199 | self.ui_hook = UI_Hook() 200 | self.ui_hook.hook() 201 | 202 | return idaapi.PLUGIN_KEEP 203 | 204 | def run(self, arg): 205 | generate_js_script(idautils.Functions()) 206 | 207 | def term(self): 208 | pass 209 | 210 | 211 | def PLUGIN_ENTRY(): 212 | return stalker_trace_so() 213 | --------------------------------------------------------------------------------