前置知识
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的调用流程:
在do_dlopen中通过find_library进行加载so
在加载完so后通过call_constructors完成init_array的加载
find_library最后调用load_libray完成so的转载
最后通过load_library的elf_reader.load完成so的装载
了解了上面两个知识点,在Hook Native时,我们常常通过Spawn抢占两个较早的时机,1是 JNIOnLoad 前 2是 init_proc 以及 init_array 前 ,就可以通过hook上述函数。
接下来就看源码学习吧、我会在代码中给出详细的注释
# Template 类提供了一种简单而安全的字符串替换机制,可以避免常见的安全漏洞,例如 SQL 注入攻击。在使用模板字符串时,可以将需要被替换的变量名以 $ 作为前缀,并在后面用花括号 {} 将变量名括起来。然后使用 substitute() 方法将变量值传递给模板,该方法会将变量名替换为对应的值。
from string import Template
# 文本行相关的函数和类。
import ida_lines
# IDA Pro 插件开发相关的函数和类。
import idaapi
import idc
# 该类型是插件开发的基本接口
from ida_idaapi import plugin_t
# hook函数的模板
"""
这段代码通过调用 Module.findBaseAddress("$soName") 找到指定so库的基地址,然后使用 Interceptor.attach() 函数 hook 在基地址加上指定偏移量的函数。
通过这些占位符,可以实现动态替换相应的值
$soName 表示要 hook 的so库的名字;
$functionName 表示要 hook 的函数名;
$offset 表示要 hook 的函数在so库中的偏移量;
$args 表示在 onEnter() 函数中输出的参数值;
$result 表示在 onLeave() 函数中输出的返回值。
"""
hook_function_template = """
function hook_$functionName(){
var base_addr = Module.findBaseAddress("$soName");
Interceptor.attach(base_addr.add($offset), {
onEnter(args) {
console.log("call $functionName");
$args
},
onLeave(retval) {
$result
console.log("leave $functionName");
}
});
}
"""
# 这段模板和上面那段差不多 主要用于某行指令去call函数的时候 在onEnter回调中 可以查看函数参数、寄存器值等调用上下文的详细信息。
inline_hook_template = """
function hook_$offset(){
var base_addr = Module.findBaseAddress("$soName");
Interceptor.attach(base_addr.add($offset), {
onEnter(args) {
console.log("call $offset");
console.log(JSON.stringify(this.context));
},
});
}
"""
# 参数打印
logTemplate = 'console.log("arg$index:"+args[$index]);\n'
# 在指定共享库加载时执行自定义逻辑的功能,可以用于动态监视和修改目标进程中的行为。
dlopenAfter_template = """
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");
if(android_dlopen_ext != null){
Interceptor.attach(android_dlopen_ext,{
onEnter: function(args){
var soName = args[0].readCString();
if(soName.indexOf("$soName") !== -1){
this.hook = true;
}
},
onLeave: function(retval){
if(this.hook) {
this.hook = false;
dlopentodo();
}
}
});
}
function dlopentodo(){
//todo
}
"""
# 在指定共享库初始化时执行自定义逻辑的功能 例如 init_proc 以及 init_array
init_template = """
function hookInit(){
var linkername;
var alreadyHook = false;
var call_constructor_addr = null;
var arch = Process.arch;
if (arch.endsWith("arm")) {
linkername = "linker";
} else {
linkername = "linker64";
}
var symbols = Module.enumerateSymbolsSync(linkername);
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("call_constructor") !== -1) {
call_constructor_addr = symbol.address;
}
}
if (call_constructor_addr.compare(NULL) > 0) {
console.log("get construct address");
Interceptor.attach(call_constructor_addr, {
onEnter: function (args) {
if(alreadyHook === false){
const targetModule = Process.findModuleByName("$soName");
if (targetModule !== null) {
alreadyHook = true;
inittodo();
}
}
}
});
}
}
function inittodo(){
//todo
}
"""
# 以十六进制形式打印指定so文件中指定偏移量处内存数据
dump_template = """
function dump_$offset() {
var base_addr = Module.findBaseAddress("$soName");
var dump_addr = base_addr.add($offset);
console.log(hexdump(dump_addr, {length: $length}));
}
"""
# 生成参数打印脚本
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
# 生成hook函数模板 通过 soName, functionName, address, argNum, hasReturn
def generate_for_func(soName, functionName, address, argNum, hasReturn):
# 根据参数个数打印
argsPrint = generate_printArgs(argNum)
# 根据是否有返回值判断是否打印retval
retPrint = "// no return"
if hasReturn:
retPrint = "console.log(retval);"
# 使用Python提供的Template字符串模板方法
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")
# 生成inline hook函数模板 通过 soName, address
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)