Native层的hook
如何hook
知道函数地址就可以hook和主动调用
如何获取函数地址
- 利用函数名,通过
Module.findExportByName
函数获取函数地址。
- 通过
Module.findBaseAddress
函数获取所在库函数地址,然后加上函数偏移量即为函数地址。
hook示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const libName = 'libnative-lib.so' const funcName = 'Java_com_example_demoso1_MainActivity_stringFromJNI' const funcAddr = Module.findExportByName(libName, funcName) console.log(funcAddr)
const offset = 0x10018 const baseAddr = Module.findBaseAddress(libName) const funcAddr = baseAddr.add(offset) console.log(funcAddr)
Interceptor.attach(funcAddr, { onEnter: function (args) { }, onLeave: function (retval) { }, })
|
Frida Native层的API
枚举导出表
1 2 3 4 5
| const imports = Module.enumerateImports('libart.so'); for (const iterator of imports) { console.log(JSON.stringify(iterator));
}
|
枚举导入表
1 2 3 4 5
| const exports = Module.enumerateExports('libart.so'); for (const iterator of exports) { console.log(JSON.stringify(iterator)); }
|
枚举符号表
1 2 3 4 5
| const exports = Module.enumerateSymbols('libart.so'); for (const iterator of exports) { console.log(JSON.stringify(iterator)); }
|
枚举进程中已加载的模块
1 2 3 4 5
| const modules = Process.enumerateModules(); for (const iterator of exports) { console.log(JSON.stringify(iterator)); }
|
通过函数名找导出函数
findExportByName
1 2
| const funcAddr = Module.findExportByName('libart.so', '_ZN9unix_file6FdFile5WriteEPKcll'); console.log(funcAddr);
|
getExportByName
同findExportByName
模块(库)基址获取方式
Module.findBaseAddress
1 2 3
| const baseAddr = Module.findBaseAddress('libencryptlib.so') console.log(baseAddr)
|
Module.getBaseAddress
同 Module.findBaseAddress
Process.findModuleByName
通过模块名找。
1 2 3
| const module = Process.findModuleByName('libpiex.so') console.log(JSON.stringify(module))
|
Process.getModuleByName
同Process.findModuleByName
Process.findModuleByAddress()
1 2
| const module = Process.findModuleByAddress(addr) console.log(JSON.stringify(module))
|
Process.getModuleByAddress()
同Process.findModuleByAddress()
调用native函数
new NativeFunction(address, returnType, argTypes[, options])
: 创建一个新的 NativeFunction
用于调用位于指定地址的函数,其中 returnType
指定返回类型,argTypes
数组指定参数类型。
支持的参数类型:
void、pointer、int、uint、long、ulong、char、uchar、size_t、ssize_t、float、double、int8、uint8、int16、uint16、int32、uint32、int64、uint64、bool
示例:
1 2 3 4 5 6
| function main(){ const addAddr = Module.findExportByName('libleidian.so', 'Java_com_example_leidian_MainActivity_add'); const addFunction = new NativeFunction(addAddr, 'int64', ['int64', 'int64', 'int', 'int']); const result = addFunction(0, 0, 1, 2); console.log("result: ", result); }
|
hook任何函数
总之,hook可以hook任何函数,请不要局限于你的想象力。
注意事项
如果需要手动计算函数地址,请注意安卓32bit、64bit的计算区别:
安卓位数 |
指令 |
计算方式 |
32 位 |
thumb |
so 基址 + 函数在 so 中的偏移 + 1 |
64 位 |
arm |
so 基址 + 函数在 so 中的偏移 |
也可在IDA中通过显示汇编指令对应的硬编码来判断。因为 arm 指令为 4 个字节,如果函数中有些指令是两个字节,那么函数地址计算需要 + 1。(加不加1都可以试一下)
Interceptor.replace函数
Interceptor.replace(target, replacement[, data])
:将target
处的函数替换为replacement
的实现(使用NativeCallback
实现替换)。如果您想要完全或部分替换现有函数的实现,通常会使用此方法。
示例:
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
| function replaceHook(addr) { const addFunction = new NativeFunction(addr, 'int64', ['int64', 'int64', 'int', 'int']); Interceptor.replace(addr, new NativeCallback( function(arg1, arg2, arg3, arg4){ console.log("come into add function"); console.log("arg1: ", arg1); console.log("arg2: ", arg2); console.log("arg3: ", arg3); console.log("arg4: ", arg4); var result = addFunction(arg1, arg2, arg3, arg4); return result }, 'int64', ['int64', 'int64', 'int', 'int']) ) var result = addFunction(0, 0, 1, 2); console.log("result: ", result);
}
function hookAdd(){ var addAddr = Module.findExportByName('libleidian.so', 'Java_com_example_leidian_MainActivity_add'); replaceHook(addAddr); }
setImmediate(hookAdd)
|
请注意!!!需先启动应用,然后再attach,而非spawn,否者找不到函数地址。
Interceptor.attach函数
Interceptor.attach(target, callbacks[, data])
:拦截target
处的函数调用,callbacks
包括onEnter
、onLeave
的回调函数实现。
示例:
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
| function replaceHook(addr) {
Interceptor.attach(addr, { onEnter: function(args){ console.log("come into add function"); console.log("arg1: ", args[0]); console.log("arg2: ", args[1]); console.log("arg3: ", args[2]); console.log("arg4: ", args[3]); console.log('Context : ' + JSON.stringify(this.context)); }, onLeave: function(retval){ console.log("return value: ", retval); retval.replace(0x9) } } ) const addFunction = new NativeFunction(addr, 'int64', ['int64', 'int64', 'int', 'int']); var result = addFunction(0, 0, 1, 2); console.log("result: ", result);
}
|
同样需要先等待应用程序启动,再attach。
hook修改函数的参数和返回值
修改数值
1 2 3 4 5 6 7 8 9
| Interceptor.attach(helloAddr, { onEnter: function (args) { args[2] = ptr(1000) }, onLeave: function (retval) { retval.replace(1337) retval.replace(ptr("0x1234")) }, })
|
修改字符串????问题很严重
利用so层原有字符串进行地址替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Interceptor.attach(funcAddr, { onEnter: function (args) { const strAddr = baseAddr.add(0x142D7) args[1] = strAddr const str = strAddr.readCString() console.log("target string: ", str); args[2] = ptr(str.length) console.log("target string length: ", args[2]); }, onLeave: function (retval) { }, })
|
创建新字符串并进行地址替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const newStr = "some strings"; const newStrAddr = Memory.allocUtf8String(newStr);
Interceptor.attach(funcAddr, { onEnter: function (args) { args[1] = newStrAddr console.log("target string: ", args[1].readCString()) args[2] = ptr(newStr.length) console.log("target string length: ", args[2]); }, onLeave: function (retval) {}, })
|
原地址上修改字符
1 2 3 4 5 6 7 8 9 10 11 12
| Interceptor.attach(funcAddr, { onEnter: function (args) { let newStr = 'some strings' args[1].writeByteArray(hexToBytes(stringToHex(newStr) + '00')) console.log("target string: ", args[1].readCString()) args[2] = ptr(newStr.length) console.log("target string length: ", args[2]); }, onLeave: function (retval) {}, })
|
内存读写
https://frida.re/docs/javascript-api/#memory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const baseAddr = Module.findBaseAddress('libleidian.so') console.log(baseAddr.add(0x1234).readCString())
console.log(hexdump(baseAddr.add(0x1234)))
console.log(baseAddr.add(0x1234).readByteArray(16)) console.log(Memory.readByteArray(baseAddr.add(0x2c00), 16))
baseAddr.add(0x1234).writeByteArray(stringToBytes('xiaojianbang'))
Memory.alloc() Memory.allocUtf8String()
Memory.protect(ptr(libso.base), libso.size, 'rwx')
|
修改native层代码
需注意架构区别!!!arm
架构使用Arm64Writer
修改,x64
、x86
架构使用X86Writer
修改。
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
| function hexToBytes(hex) { let bytes = []; for (let i = 0; i < hex.length; i += 2) { bytes.push(parseInt(hex.substr(i, 2), 16)); } return bytes; }
function alterCode(){ const addFuncAddr = Module.findExportByName("libleidian.so", "Java_com_example_leidian_MainActivity_add") const addInsAddr = addFuncAddr.add(0x15) console.log("origin instruction => ", Instruction.parse(addInsAddr));
Memory.patchCode(addInsAddr, 1, function (code) { let Writer = new X86Writer(code, {pc: addInsAddr}); Writer.putBytes(hexToBytes("2b")); Writer.flush(); }); console.log("current instruction => ", Instruction.parse(addInsAddr)); }
|
函数堆栈打印
1 2 3 4 5 6
| console.log( Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress) .join('\n') )
|
寻找native函数所在的动态库
native函数根据注册时机可分为静态注册函数、动态注册函数,它们对应的hook点不同。
- 动态注册函数:动态注册函数hook RegisterNatives函数。
- 静态注册函数:可以hook dlsym函数(在动态加载的场景下)。
动态注册函数
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
| function hook_RegisterNatives() { let RegisterNatives_addr = null let symbols = Process.findModuleByName('libart.so').enumerateSymbols() for (let i = 0; i < symbols.length; i++) { let symbol = symbols[i].name if (symbol.indexOf('CheckJNI') == -1 && symbol.indexOf('JNI') >= 0) { if (symbol.indexOf('RegisterNatives') >= 0) { RegisterNatives_addr = symbols[i].address console.log('RegisterNatives_addr: ', RegisterNatives_addr) } } } Interceptor.attach(RegisterNatives_addr, { onEnter: function (args) { let env = args[0] let jclass = args[1] let class_name = Java.vm.tryGetEnv().getClassName(jclass) let methods_ptr = ptr(args[2]) let method_count = args[3].toInt32() console.log('RegisterNatives method counts: ', method_count)
for (let i = 0; i < method_count; i++) { let name = methods_ptr.add(i * Process.pointerSize * 3) .readPointer() .readCString() let sig = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize) .readPointer() .readCString() let fnPtr_ptr = methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2) .readPointer() let find_module = Process.findModuleByAddress(fnPtr_ptr) console.log( 'RegisterNatives java_class: ', class_name, 'name: ', name, 'sig: ', sig, 'fnPtr: ', fnPtr_ptr, 'module_name: ', find_module.name, 'module_base: ', find_module.base, 'offset: ', ptr(fnPtr_ptr).sub(find_module.base), ) } }, onLeave: function (retval) {}, }) }
|
静态注册函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function hook_dlsym(dlsymFuncAddr){ Interceptor.attach(dlsymFuncAddr, { onEnter: function (args) { this.libAddr = ptr(args[0]) this.funcName = ptr(args[1]) }, onLeave: function (retval) { let module = Process.findModuleByAddress(retval) console.log("function name: ", this.funcName.readCString(), "lib name: ", module.name, "function's memory address: "retval, "function offset: ", retval.sub(module.base) ) }, }) }
let dlsymFuncAddr = Module.findExportByName('libdl.so', 'dlsym') hook_dlsym(dlsymFuncAddr)
|
hook运行时动态加载的库与函数
dlopen
函数用于运行时动态加载一个共享库,并返回一个句柄。这个句柄可以用于后续的符号解析。
dlsym
函数用于获取运行时动态加载的库中符号的地址。该符号可以是函数、变量等。
hook dlopen函数
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
| function hook_dlopen(dlopenFuncAdrr, libName, targetFuncOffset, callback) { Interceptor.attach(addr, { onEnter: function (args) { let libPath = args[0].readCString() if (libPath.indexOf(libName) != -1) { this.hook = true } }, onLeave: function (retval) { if (this.hook){ callback(retval.add(targetFuncOffset)) } } }) }
function hook_targetFunc(targetFuncAddr) { Interceptor.attach(targetFuncAddr, { onEnter: function (args) { }, onLeave: function (retval) { }, }) }
let dlopenFuncAdrr = Module.findExportByName('libdl.so', 'dlopen') hook_dlopen(dlopenFuncAdrr, 'libxiaojianbang.so', hook_targetFunc)
|
hook dlsym函数
获取目标函数所在的so库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function hook_dlsym(dlsymFuncAddr){ Interceptor.attach(dlsymFuncAddr, { onEnter: function (args) { this.libAddr = ptr(args[0]) this.funcName = ptr(args[1]) }, onLeave: function (retval) { let module = Process.findModuleByAddress(retval) console.log("function name: ", this.funcName.readCString(), "lib name: ", module.name, "function's memory address: "retval, "function offset: ", retval.sub(module.base) ) }, }) }
let dlsymFuncAddr = Module.findExportByName('libdl.so', 'dlsym') hook_dlsym(dlsymFuncAddr)
|
inlineHook(针对寄存器的值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function inlineHook() { var nativePointer = Module.findBaseAddress('libleidian.so') var hookAddr = nativePointer.add(0x2080C) Interceptor.attach(hookAddr, { onEnter: function (args) { console.log('onEnter: ', JSON.stringify(this.context)) }, onLeave: function (retval) {}, })
var funcAddr = Module.findExportByName('libleidian.so', 'Java_com_example_leidian_MainActivity_stringFromJNI'); var func = new NativeFunction(funcAddr, "int64", ["pointer"]) func(Java.vm.tryGetEnv())
}
|
相关工具
参考:
https://frida.re/docs/javascript-api/(官方文档)
https://kuizuo.cn/docs/frida-so-hookhttps://blog.csdn.net/weixin_35016347/article/details/104002411)