第一次学习idapython,偶然间发现了一本秘籍《IDA脚本开发之旅》,这是白龙的系列文章,主要是安卓平台,笔者只是根据他的知识点学习,拓展,可以会稍微提及别的平台。本文并不会贴出他的思路分析,只对于源码进行学习,有兴趣可以加他星球一起学习!
另外本文也学习了无名侠的IDAFrida脚本,有兴趣可以加他星球一起学习!
IDA 静态分析 + Frida Hook,可以让分析变得更加丝滑。
这里我们会学习两个脚本 一个是白龙的 一个是 无名侠的。
这是白龙写的一个Python脚本,用于生成Frida的Hook代码。主要包含了以下几个部分:
frida ida就不说了,主要说一下 其他的知识
android_dlopen_ext
是 Android 系统中的一个函数,用于在运行时动态加载共享库。与标准的 dlopen()
函数相比,android_dlopen_ext
提供了更多的参数选项和扩展功能,例如支持命名空间、符号版本等特性。该函数通常用于 Android 应用程序或系统进程中,用于加载共享库并获取其中定义的符号。在 Android 应用程序中,常常会使用 System.loadLibrary()
函数调用 android_dlopen_ext()
,从而加载与应用程序打包在一起的共享库文件。
在win上我们一般用 LoadLibrary
函数来加载 DLL(动态链接库)文件,并使用 GetProcAddress
函数来获取函数指针。在 iOS 平台上,可以使用 dlopen
函数来加载共享库,并使用 dlsym
函数来获取函数指针。
linker的调用流程:
了解了上面两个知识点,在Hook Native时,我们常常通过Spawn抢占两个较早的时机,1是 JNIOnLoad 前 2是 init_proc 以及 init_array 前
,就可以通过hook上述函数。
接下来就看源码学习吧、我会在代码中给出详细的注释
ShowFridaCode 这是一个十分简单但是很优秀的脚本 包含了ida界面hook frida语法 模板生成 安卓初始化流程 arm汇编的理解
这是无名侠写的一个Python脚本。这段脚本主要是一个基于 IDA Pro 和 Frida 的集成工具,用于辅助逆向工程师进行动态调试。具体来说,这个工具通过 IDA Pro 分析程序的代码,提取出函数的信息(如函数名、地址、参数数量等),然后使用 Frida 注入 JS 脚本,在运行时拦截这些函数,并在函数进入和返回时打印出函数的参数和返回值。这个工具的具体功能包括:
具体来说,这个脚本实现了以下几个类和函数:
我们直接看源码学习吧 同样我也会给出详细的注释
总的来说,这个脚本实现了一个较为完整的 IDA Pro 和 Frida 集成工具,并提供了一些 UI 功能和便捷的函数接口,使得逆向工程师能够更加高效地进行动态调试和分析。
通过上面两个例子,我们已经学习了frida模板的生成 以及如何和ida的ui交互 这样我们就可以自己写个插件 整合上面的代码 并且可以添加自己想要的功能
源码: https://github.com/AnxiangLemon/MyIdaFrida
from
string
import
Template
import
ida_lines
import
idaapi
import
idc
from
ida_idaapi
import
plugin_t
hook_function_template
=
inline_hook_template
=
logTemplate
=
'console.log("arg$index:"+args[$index]);\n'
dlopenAfter_template
=
init_template
=
dump_template
=
def
generate_printArgs(argNum):
if
argNum
=
=
0
:
return
"// no args"
else
:
temp
=
Template(logTemplate)
logText
=
""
for
i
in
range
(argNum):
logText
+
=
temp.substitute({
'index'
: i})
logText
+
=
" "
return
logText
def
generate_for_func(soName, functionName, address, argNum, hasReturn):
argsPrint
=
generate_printArgs(argNum)
retPrint
=
"// no return"
if
hasReturn:
retPrint
=
"console.log(retval);"
temp
=
Template(hook_function_template)
offset
=
getOffset(address)
result
=
temp.substitute(
{
'soName'
: soName,
"functionName"
: functionName,
"offset"
:
hex
(offset),
"args"
: argsPrint,
"result"
: retPrint})
print
(result)
def
getOffset(address):
if
idaapi.get_inf_structure().is_64bit():
return
address
else
:
return
address
+
idc.get_sreg(address,
"T"
)
def
generate_for_inline(soName, address):
offset
=
getOffset(address)
temp
=
Template(inline_hook_template)
result
=
temp.substitute({
'soName'
: soName,
"offset"
:
hex
(offset)})
if
idaapi.is_call_insn(address):
callAddr
=
idaapi.get_name_ea(
0
, idc.print_operand(address,
0
))
if
callAddr !
=
idaapi.BADADDR:
callAddress
=
idc.get_operand_value(address,
0
)
argnum, _
=
get_argnum_and_ret(callAddress)
argsPrint
=
generate_printArgs(argnum)
print
(result.replace(
"console.log(JSON.stringify(this.context));"
, argsPrint))
else
:
print
(result)
else
:
print
(result)
def
get_argnum_and_ret(address):
cfun
=
idaapi.decompile(address)
argnum
=
len
(cfun.arguments)
ret
=
True
dcl
=
ida_lines.tag_remove(cfun.print_dcl())
if
(dcl.startswith(
"void "
)
is
True
) & (dcl.startswith(
"void *"
)
is
False
):
ret
=
False
return
argnum, ret
def
generate_for_func_by_address(addr):
soName
=
idaapi.get_root_filename()
functionName
=
idaapi.get_func_name(addr)
argnum, ret
=
get_argnum_and_ret(addr)
generate_for_func(soName, functionName, addr, argnum, ret)
def
generate_for_inline_by_address(addr):
soName
=
idaapi.get_root_filename()
generate_for_inline(soName, addr)
def
generate_snippet(addr):
startAddress
=
idc.get_func_attr(addr, idc.FUNCATTR_START)
if
startAddress
=
=
addr:
generate_for_func_by_address(addr)
elif
startAddress
=
=
idc.BADADDR:
print
(
"不在函数内"
)
else
:
generate_for_inline_by_address(addr)
def
generateInitCode():
soName
=
idaapi.get_root_filename()
print
(Template(dlopenAfter_template).substitute({
'soName'
: soName}))
print
(Template(init_template).substitute({
'soName'
: soName}))
def
generate_dump_script(start, length):
soName
=
idaapi.get_root_filename()
print
(Template(dump_template).substitute(
{
'soName'
: soName,
"offset"
:
hex
(start),
"length"
:
hex
(length)}))
class
Hook(idaapi.View_Hooks):
def
view_dblclick(
self
, view, event):
widgetType
=
idaapi.get_widget_type(view)
if
widgetType
=
=
idaapi.BWN_DISASM:
global
initialized
if
not
initialized:
initialized
=
True
generateInitCode()
address
=
idaapi.get_screen_ea()
generate_snippet(address)
def
view_click(
self
, view, event):
widgetType
=
idaapi.get_widget_type(view)
if
widgetType
=
=
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
GenFrida_Plugin_t(plugin_t):
comment
=
"A Toy Plugin for Generating Frida Code"
help
=
"unknown"
flags
=
idaapi.PLUGIN_KEEP
wanted_name
=
"ShowFridaCode"
wanted_hotkey
=
""
def
init(
self
):
print
(
"ShowFridaCode init"
)
return
idaapi.PLUGIN_KEEP
def
run(
self
, arg):
print
(
"ShowFridaCode run"
)
global
myViewHook
myViewHook
=
Hook()
myViewHook.hook()
def
term(
self
):
pass
initialized
=
False
def
PLUGIN_ENTRY():
return
GenFrida_Plugin_t()
from
string
import
Template
import
ida_lines
import
idaapi
import
idc
from
ida_idaapi
import
plugin_t
hook_function_template
=
inline_hook_template
=
logTemplate
=
'console.log("arg$index:"+args[$index]);\n'
dlopenAfter_template
=
init_template
=
dump_template
=
def
generate_printArgs(argNum):
if
argNum
=
=
0
:
return
"// no args"
else
:
temp
=
Template(logTemplate)
logText
=
""
for
i
in
range
(argNum):
logText
+
=
temp.substitute({
'index'
: i})
logText
+
=
" "
return
logText
def
generate_for_func(soName, functionName, address, argNum, hasReturn):
argsPrint
=
generate_printArgs(argNum)
retPrint
=
"// no return"
if
hasReturn:
retPrint
=
"console.log(retval);"
temp
=
Template(hook_function_template)
offset
=
getOffset(address)
result
=
temp.substitute(
{
'soName'
: soName,
"functionName"
: functionName,
"offset"
:
hex
(offset),
"args"
: argsPrint,
"result"
: retPrint})
print
(result)
def
getOffset(address):
if
idaapi.get_inf_structure().is_64bit():
return
address
else
:
return
address
+
idc.get_sreg(address,
"T"
)
def
generate_for_inline(soName, address):
offset
=
getOffset(address)
temp
=
Template(inline_hook_template)
result
=
temp.substitute({
'soName'
: soName,
"offset"
:
hex
(offset)})
if
idaapi.is_call_insn(address):
callAddr
=
idaapi.get_name_ea(
0
, idc.print_operand(address,
0
))
if
callAddr !
=
idaapi.BADADDR:
callAddress
=
idc.get_operand_value(address,
0
)
argnum, _
=
get_argnum_and_ret(callAddress)
argsPrint
=
generate_printArgs(argnum)
print
(result.replace(
"console.log(JSON.stringify(this.context));"
, argsPrint))
else
:
print
(result)
else
:
print
(result)
def
get_argnum_and_ret(address):
cfun
=
idaapi.decompile(address)
argnum
=
len
(cfun.arguments)
ret
=
True
dcl
=
ida_lines.tag_remove(cfun.print_dcl())
if
(dcl.startswith(
"void "
)
is
True
) & (dcl.startswith(
"void *"
)
is
False
):
ret
=
False
return
argnum, ret
def
generate_for_func_by_address(addr):
soName
=
idaapi.get_root_filename()
functionName
=
idaapi.get_func_name(addr)
argnum, ret
=
get_argnum_and_ret(addr)
generate_for_func(soName, functionName, addr, argnum, ret)
def
generate_for_inline_by_address(addr):
soName
=
idaapi.get_root_filename()
generate_for_inline(soName, addr)
def
generate_snippet(addr):
startAddress
=
idc.get_func_attr(addr, idc.FUNCATTR_START)
if
startAddress
=
=
addr:
generate_for_func_by_address(addr)
elif
startAddress
=
=
idc.BADADDR:
print
(
"不在函数内"
)
else
:
generate_for_inline_by_address(addr)
def
generateInitCode():
soName
=
idaapi.get_root_filename()
print
(Template(dlopenAfter_template).substitute({
'soName'
: soName}))
print
(Template(init_template).substitute({
'soName'
: soName}))
def
generate_dump_script(start, length):
soName
=
idaapi.get_root_filename()
print
(Template(dump_template).substitute(
{
'soName'
: soName,
"offset"
:
hex
(start),
"length"
:
hex
(length)}))
class
Hook(idaapi.View_Hooks):
def
view_dblclick(
self
, view, event):
widgetType
=
idaapi.get_widget_type(view)
if
widgetType
=
=
idaapi.BWN_DISASM:
global
initialized
if
not
initialized:
initialized
=
True
generateInitCode()
address
=
idaapi.get_screen_ea()
generate_snippet(address)
def
view_click(
self
, view, event):
widgetType
=
idaapi.get_widget_type(view)
if
widgetType
=
=
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
GenFrida_Plugin_t(plugin_t):
comment
=
"A Toy Plugin for Generating Frida Code"
help
=
"unknown"
flags
=
idaapi.PLUGIN_KEEP
wanted_name
=
"ShowFridaCode"
wanted_hotkey
=
""
def
init(
self
):
print
(
"ShowFridaCode init"
)
return
idaapi.PLUGIN_KEEP
def
run(
self
, arg):
print
(
"ShowFridaCode run"
)
global
myViewHook
myViewHook
=
Hook()
myViewHook.hook()
def
term(
self
):
pass
initialized
=
False
def
PLUGIN_ENTRY():
return
GenFrida_Plugin_t()
import
ida_name
import
idaapi
class
ActionManager(
object
):
def
__init__(
self
):
self
.__actions
=
[]
def
register(
self
, action):
self
.__actions.append(action)
idaapi.register_action(
idaapi.action_desc_t(action.name, action.description, action, action.hotkey)
)
def
initialize(
self
):
pass
def
finalize(
self
):
for
action
in
self
.__actions:
idaapi.unregister_action(action.name)
action_manager
=
ActionManager()
class
Action(idaapi.action_handler_t):
description
=
None
hotkey
=
None
def
__init__(
self
):
super
(Action,
self
).__init__()
@property
def
name(
self
):
return
"FridaIDA:"
+
type
(
self
).__name__
def
activate(
self
, ctx):
raise
NotImplementedError
def
update(
self
, ctx):
raise
NotImplementedError
import
ida_funcs
import
idc
import
json
import
os
from
PyQt5
import
QtCore
from
PyQt5.Qt
import
QApplication
from
PyQt5.QtWidgets
import
QDialog, QHBoxLayout, QVBoxLayout, QTextEdit
default_template
=
class
Configuration:
def
__init__(
self
)
-
>
None
:
self
.frida_cmd
=
self
.template
=
default_template
if
os.path.exists(
"IDAFrida.json"
):
self
.load()
def
set_frida_cmd(
self
, s):
self
.frida_cmd
=
s
self
.store()
def
set_template(
self
, s):
self
.template
=
s
self
.store()
def
reset(
self
):
self
.__init__()
def
store(
self
):
try
:
data
=
{
"frida_cmd"
:
self
.frida_cmd,
"template"
:
self
.template}
open
(
"IDAFrida.json"
,
"w"
).write(json.dumps(data))
except
Exception as e:
print
(e)
def
load(
self
):
try
:
data
=
json.loads(
open
(
"IDAFrida.json"
,
"r"
).read())
self
.frida_cmd
=
data[
"frida_cmd"
]
self
.template
=
data[
"template"
]
except
Exception as e:
print
(e)
global_config
=
Configuration()
class
ConfigurationUI(QDialog):
def
__init__(
self
, conf: Configuration)
-
>
None
:
super
(ConfigurationUI,
self
).__init__()
self
.conf
=
conf
self
.edit_template
=
QTextEdit()
self
.edit_template.setPlainText(
self
.conf.template)
layout
=
QHBoxLayout()
layout.addWidget(
self
.edit_template)
self
.setLayout(layout)
def
closeEvent(
self
, a0)
-
>
None
:
self
.conf.set_template(
self
.edit_template.toPlainText())
self
.conf.store()
return
super
().closeEvent(a0)
class
ScriptGenerator:
def
__init__(
self
, configuration: Configuration)
-
>
None
:
self
.conf
=
configuration
self
.imagebase
=
idaapi.get_imagebase()
@staticmethod
def
get_idb_filename():
return
os.path.basename(idaapi.get_input_file_path())
@staticmethod
def
get_idb_path():
return
os.path.dirname(idaapi.get_input_file_path())
def
get_function_name(
self
,
ea):
function_name
=
idc.demangle_name(idc.get_func_name(ea), idc.get_inf_attr(idc.INF_SHORT_DN))
if
not
function_name:
function_name
=
idc.get_func_name(ea)
if
not
function_name:
function_name
=
idc.get_name(ea, ida_name.GN_VISIBLE)
if
not
function_name:
function_name
=
"UNKN_FNC_%s"
%
hex
(ea)
return
function_name
def
generate_stub(
self
, repdata:
dict
):
s
=
self
.conf.template
for
key, v
in
repdata.items():
s
=
s.replace(
"[%s]"
%
key, v)
return
s
def
generate_for_funcs(
self
, func_addr_list)
-
>
str
:
stubs
=
[]
for
func_addr
in
func_addr_list:
dec_func
=
idaapi.decompile(func_addr)
repdata
=
{
"filename"
:
self
.get_idb_filename(),
"funcname"
:
self
.get_function_name(func_addr),
"offset"
:
hex
(func_addr
-
self
.imagebase),
"nargs"
:
hex
(dec_func.
type
.get_nargs())
}
stubs.append(
self
.generate_stub(repdata))
return
"\n"
.join(stubs)
def
generate_for_funcs_to_file(
self
, func_addr_list, filename)
-
>
bool
:
data
=
self
.generate_for_funcs(func_addr_list)
try
:
open
(filename,
"w"
).write(data)
print
(
"The generated Frida script has been exported to the file: "
, filename)
except
Exception as e:
print
(e)
return
False
try
:
QApplication.clipboard().setText(data)
print
(
"The generated Frida script has been copied to the clipboard!"
)
except
Exception as e:
print
(e)
return
False
return
True
class
Frida:
def
__init__(
self
, conf: Configuration)
-
>
None
:
self
.conf
=
conf
class
IDAFridaMenuAction(Action):
TopDescription
=
"IDAFrida"
def
__init__(
self
):
super
(IDAFridaMenuAction,
self
).__init__()
def
activate(
self
, ctx)
-
>
None
:
raise
NotImplemented
def
update(
self
, ctx)
-
>
None
:
if
ctx.form_type
=
=
idaapi.BWN_FUNCS
or
ctx.form_type
=
=
idaapi.BWN_PSEUDOCODE
or
ctx.form_type
=
=
idaapi.BWN_DISASM:
idaapi.attach_action_to_popup(ctx.widget,
None
,
self
.name,
self
.TopDescription
+
"/"
)
return
idaapi.AST_ENABLE_FOR_WIDGET
return
idaapi.AST_DISABLE_FOR_WIDGET
class
GenerateFridaHookScript(IDAFridaMenuAction):
description
=
"Generate Frida Script"
def
__init__(
self
):
super
(GenerateFridaHookScript,
self
).__init__()
def
activate(
self
, ctx):
gen
=
ScriptGenerator(global_config)
idb_path
=
os.path.dirname(idaapi.get_input_file_path())
out_file
=
os.path.join(idb_path,
"IDAhook.js"
)
if
ctx.form_type
=
=
idaapi.BWN_FUNCS:
selected
=
[idaapi.getn_func(idx).start_ea
for
idx
in
ctx.chooser_selection]
else
:
selected
=
[idaapi.get_func(idaapi.get_screen_ea()).start_ea]
gen.generate_for_funcs_to_file(selected, out_file)
class
ViewFridaTemplate(IDAFridaMenuAction):
description
=
"View Frida Template"
def
__init__(
self
):
super
(ViewFridaTemplate,
self
).__init__()
def
activate(
self
, ctx):
ui
=
ConfigurationUI(global_config)
ui.show()
ui.exec_()
class
RunGeneratedScript(IDAFridaMenuAction):
description
=
"Run Generated Script"
def
__init__(
self
):
super
(RunGeneratedScript,
self
).__init__()
def
activate(
self
, ctx):
print
(
"template"
)
class
SetFridaRunCommand(IDAFridaMenuAction):
description
=
"Set Frida Command"
def
__init__(
self
):
super
(SetFridaRunCommand,
self
).__init__()
def
activate(
self
, ctx):
print
(
"template"
)
action_manager.register(GenerateFridaHookScript())
action_manager.register(ViewFridaTemplate())
import
ida_name
import
idaapi
class
ActionManager(
object
):
def
__init__(
self
):
self
.__actions
=
[]
def
register(
self
, action):
self
.__actions.append(action)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)