-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgen_frida.py
More file actions
365 lines (295 loc) · 11 KB
/
gen_frida.py
File metadata and controls
365 lines (295 loc) · 11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ida_hexrays import cfunc_t
from ida_kernwin import view_mouse_event_t
import idc
import idaapi
import ida_lines
import ida_ida
from ida_idaapi import plugin_t
hook_function_template = '''
function hook_{function_name}() {{
let base_addr = Module.findBaseAddress("{so_name}");
Interceptor.attach(base_addr.add(0x{offset:X}), {{
onEnter: function(args) {{
console.log(`onEnter {function_name}{args}`);
}}, onLeave: function(retval) {{
console.log(`onLeave {function_name}{result}`);
}}
}});
}}
'''
inline_hook_template = '''
function hook_0x{offset:X}() {{
let base_addr = Module.findBaseAddress("{so_name}");
Interceptor.attach(base_addr.add(0x{offset:X}), {{
onEnter(args) {{
console.log(`call 0x{offset:X} {args}`);
}}
}});
}}
'''
dlopen_after_template = '''
let android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext != null) {{
Interceptor.attach(android_dlopen_ext, {{
onEnter: function(args) {{
let so_name = args[0].readCString();
if(so_name.indexOf("{so_name}") !== -1) {{
this.hook = true;
}}
}}, onLeave: function(retval) {{
if(this.hook) {{
this.hook = false;
dlopen_todo();
}}
}}
}});
}}
function dlopen_todo() {{
//todo
}}
'''
init_template = '''
function hook_init() {{
let linker_name = "linker64";
let already_hook = false;
let call_constructor_addr = null;
if (Process.arch.endsWith("arm")) {{
linker_name = "linker";
}}
let symbols = Module.enumerateSymbolsSync(linker_name);
for (let i = 0; i < symbols.length; i++) {{
let symbol = symbols[i];
if (symbol.name.indexOf("call_constructor") !== -1) {{
call_constructor_addr = symbol.address;
}}
}}
if (call_constructor_addr != null) {{
console.log(`get construct address ${{call_constructor_addr}}`);
Interceptor.attach(call_constructor_addr, {{
onEnter: function (args) {{
if(already_hook === false) {{
const targetModule = Process.findModuleByName("{so_name}");
if (targetModule !== null) {{
already_hook = true;
init_todo();
}}
}}
}}
}});
}}
}}
function init_todo() {{
//todo
}}
'''
dump_template = '''
function dump_0x{offset:X}() {{
let base_addr = Module.findBaseAddress("{so_name}");
let dump_addr = base_addr.add(0x{offset:X});
let dump_mem = hexdump(dump_addr, {{length: {length:#x}}});
console.log(`dump {so_name} + {offset:#x}:\\n${{dump_mem}}`);
}}
'''
def generate_print_args(addr: int):
args_num = get_args_num(addr)
tmp = []
for index in range(args_num):
tmp.append(f'arg{index}:${{args[{index}]}}')
return ' '.join(tmp)
def generate_for_func(so_name: str, function_name: str, addr: int):
# 根据参数个数打印
args_print = generate_print_args(addr)
if args_print != '':
args_print = ' ' + args_print
# 根据是否有返回值判断是否打印retval
if has_return(addr):
ret_print = ' ' + '${retval}'
else:
ret_print = ''
result = hook_function_template.format_map({
'so_name': so_name,
'function_name': function_name,
'offset': get_offset(addr),
'args': args_print,
'result': ret_print
})
print(result)
def get_offset(addr: int) -> int:
if ida_ida.idainfo_is_64bit():
return addr
else:
# 可以通过T标志位判断某条指令是 ARM 还是 THUMB
# ARM 返回 0
# THUMB 返回 1
return addr + idc.get_sreg(addr, 'T')
def generate_for_inline(so_name: str, addr: int):
args_print = '${JSON.stringify(this.context)}'
if idaapi.is_call_insn(addr):
# 获取指定索引操作数
operand = idc.print_operand(addr, 0)
call_addr = idaapi.get_name_ea(0, operand)
if call_addr != idaapi.BADADDR:
# 解析 call 的函数其地址
# 获取指定索引操作数中的值
addr = idc.get_operand_value(addr, 0)
args_print = generate_print_args(addr)
print(inline_hook_template.format_map(
{'so_name': so_name, 'offset': get_offset(addr), 'args': args_print}))
def has_return(addr: int) -> bool:
cfun = idaapi.decompile(addr) # type: cfunc_t
has_return = True
dcl = ida_lines.tag_remove(cfun.print_dcl()) # type: str
if dcl.startswith('void ') and not dcl.startswith('void *'):
has_return = False
return has_return
def get_args_num(addr: int) -> int:
return len(idaapi.decompile(addr).arguments)
def generate_for_func_by_addr(addr: int):
so_name = idaapi.get_root_filename()
function_name = idaapi.get_func_name(addr).replace('.', 'dot_')
generate_for_func(so_name, function_name, addr)
def generate_for_inline_by_addr(addr: int):
so_name = idaapi.get_root_filename()
generate_for_inline(so_name, addr)
def generate_snippet():
'''
脚本生成入口
'''
# 获取光标所处地址
addr = idaapi.get_screen_ea() # type: int
# 通过获取函数属性取到函数首地址
start_addr = idc.get_func_attr(addr, idc.FUNCATTR_START)
if start_addr == addr:
generate_for_func_by_addr(addr)
elif start_addr == idc.BADADDR:
print('不在函数内')
else:
generate_for_inline_by_addr(addr)
def generate_init_code():
so_name = idaapi.get_root_filename()
print(dlopen_after_template.format_map({'so_name': so_name}))
print(init_template.format_map({'so_name': so_name}))
def generate_dump_script(start: int, length: int):
so_name = idaapi.get_root_filename()
print(dump_template.format_map(
{'so_name': so_name, 'offset': start, 'length': length}))
class MyViewHooks(idaapi.View_Hooks):
def view_dblclick(self, view, event: 'view_mouse_event_t'):
'''
在汇编界面双击地址
'''
widget_type = idaapi.get_widget_type(view)
if widget_type == idaapi.BWN_DISASM:
global initialized
if not initialized:
initialized = True
generate_init_code()
generate_snippet()
def view_click(self, view, event: 'view_mouse_event_t'):
'''
在汇编界面选中一个范围后松开鼠标
'''
widget_type = idaapi.get_widget_type(view)
if widget_type == idaapi.BWN_DISASM:
start = idc.read_selection_start()
end = idc.read_selection_end()
if (start != idaapi.BADADDR) and (end != idaapi.BADADDR):
length = end - start
generate_dump_script(start, length)
class GenFridaSnippetHandler(idaapi.action_handler_t):
'''
动作处理类,和右键菜单绑定
- https://hex-rays.com/blog/augmenting-ida-ui-with-your-own-actions/
'''
def __init__(self):
idaapi.action_handler_t.__init__(self)
def activate(self, ctx):
'''
点了右键菜单之后会调用这个函数
'''
generate_snippet()
return 1
def update(self, ctx):
'''
AST_ENABLE_ALWAYS 表示可以点击
'''
return idaapi.AST_ENABLE_ALWAYS
class GenFridaInitHandler(idaapi.action_handler_t):
def __init__(self):
idaapi.action_handler_t.__init__(self)
def activate(self, ctx):
generate_init_code()
return 1
def update(self, ctx):
return idaapi.AST_ENABLE_ALWAYS
class GenFridaDumpHandler(idaapi.action_handler_t):
def __init__(self):
idaapi.action_handler_t.__init__(self)
def activate(self, ctx):
start = idc.read_selection_start()
end = idc.read_selection_end()
if (start != idaapi.BADADDR) and (end != idaapi.BADADDR):
length = end - start
generate_dump_script(start, length)
return 1
def update(self, ctx):
return idaapi.AST_ENABLE_ALWAYS
class GenFridaPlugin(plugin_t):
# 关于插件的注释
# 当鼠标浮于菜单插件上方时,IDA左下角所示
comment = 'frida辅助插件'
# 帮助信息,我们选择不填
help = '右键选择生成hook代码后找地方粘贴即可'
# 插件的特性,是一直在内存里,还是运行一下就退出,等等
flags = idaapi.PLUGIN_KEEP
# 插件的名字
wanted_name = 'GenFridaCode'
# 快捷键,我们选择置空不弄
wanted_hotkey = ''
# 插件刚被加载到IDA内存中 这里适合做插件的初始化工作
def init(self):
print('GenFridaCode init')
# 初始化的时候将动作绑定到菜单
# 这个函数不支持 kwargs
# name, label, handler, shortcut=None, tooltip=None, icon=-1, flags=0
gen_frida_init_action_desc = idaapi.action_desc_t(
'my:gen_frida_init', '生成frida hook init代码', GenFridaInitHandler(), '', '生成frida hook init代码')
idaapi.register_action(gen_frida_init_action_desc)
gen_frida_snippet_action_desc = idaapi.action_desc_t(
'my:gen_frida_snippet', '生成frida hook代码', GenFridaSnippetHandler(), '', '生成frida hook代码')
idaapi.register_action(gen_frida_snippet_action_desc)
gen_frida_dump_action_desc = idaapi.action_desc_t(
'my:gen_frida_dump', '生成frida hook dump代码', GenFridaDumpHandler(), '', '生成frida hook dump代码')
idaapi.register_action(gen_frida_dump_action_desc)
global my_ui_hooks
my_ui_hooks = MyUIHooks()
my_ui_hooks.hook()
return idaapi.PLUGIN_KEEP
# 插件运行中 这里是主要逻辑
def run(self, arg: int):
print('GenFridaCode run')
global my_view_hooks
my_view_hooks = MyViewHooks()
my_view_hooks.hook()
# 插件卸载退出的时机 这里适合做资源释放
def term(self):
print('GenFridaCode term')
class MyUIHooks(idaapi.UI_Hooks):
def finish_populating_widget_popup(self, widget, popup_handle, ctx):
if idaapi.get_widget_type(widget) == idaapi.BWN_DISASM:
idaapi.attach_action_to_popup(
widget, popup_handle, 'my:gen_frida_init', 'GenFrida/')
idaapi.attach_action_to_popup(
widget, popup_handle, 'my:gen_frida_snippet', 'GenFrida/')
idaapi.attach_action_to_popup(
widget, popup_handle, 'my:gen_frida_dump', 'GenFrida/')
initialized = False
# 注册插件
def PLUGIN_ENTRY():
return GenFridaPlugin()
# 原作者 https://github.com/Pr0214 https://t.zsxq.com/05IEynQVV
# 修改者 https://github.com/SeeFlowerX https://t.zsxq.com/05E2VBuNj
# 地址 https://gist.github.com/SeeFlowerX/41b7f45913eba9d1dff6fd47c2502b13
# IDA插件,用于生成 frida hook 代码,放入plugins目录后,手动在插件菜单激活然后右键双击、选中释放;或者直接在汇编界面右键使用,选择GenFrida