Frida so层中的hook
前言
so 中会接触到的东西:系统库函数、加密算法、jni 调用、系统调用、自定义算法
如何 hook
so hook 只需要得到一个地址,有函数地址就能 hook 与主动调用,与 java 层的 hook 一致。
得到函数地址的方式
- 通过 frida 提供的 api 来得到,该函数必须有符号的才可以
- 通过计算得到地址:so 基址+函数在 so 中的偏移[+1]
演示代码如下
const moduleName = 'libnative-lib.so'
let baseAddr = Module.findBaseAddress(moduleName)
let sub_99C0 = baseAddr.add(0x99c0 + 1)
Interceptor.attach(funcPtr, {
onEnter: function (args) {
// ...
},
onLeave: function (retval) {
// ...
},
})
API
枚举导入表
const improts = Module.enumerateImports('libencryptlib.so')
for (const iterator of improts) {
console.log(JSON.stringify(iterator))
// {"type":"function","name":"__cxa_atexit","module":"/apex/com.android.runtime/lib64/bionic/libc.so","address":"0x778957bd34"}
}
枚举导出表
const exports = Module.enumerateExports('libencryptlib.so')
for (const iterator of exports) {
console.log(JSON.stringify(iterator))
// {"type":"letiable","name":"_ZTSx","address":"0x74d594b1c0"}
}
枚举符号表
const symbols = Module.enumerateSymbols('libencryptlib.so')
for (const iterator of symbols) {
console.log(JSON.stringify(iterator))
// {"isGlobal":true,"type":"function","name":"pthread_getspecific","address":"0x0","size":0
}
枚举进程中已加载的模块
const modules = Process.enumerateModules()
console.log(JSON.stringify(modules[0].enumerateExports()[0]))
findExportByName
注: 函数名以汇编中出现的为准
const funcAddr = Module.findExportByName('libencryptlib.so', '_ZN7MD5_CTX11MakePassMD5EPhjS0_')
// 返回的是函数地址 第二个参数根据汇编中为准
console.log(funcAddr)
// 通过Interceptor.attach来对函数进行hook
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log('args[1]: ', hexdump(args[1])) // 打印参数的地址 通过hexdump打印16进制
console.log(this.context.x1) // 打印寄存器内容
console.log('args[2]: ', args[2].toInt32()) // 默认显示16进制,这里转为10进制
this.args3 = args[3] // 将args[3]值保存到this上
},
onLeave: function (retval) {
console.log('args[3]: ', hexdump(this.args3))
},
})
模块基址获取方式
如果在导入表、导出表、符号表里找不到的函数,那么函数地址需要自己计算
计算公式:so 基址+函数在 so 中的偏移[+1]
安卓位数 | 指令 | 计算方式 |
---|---|---|
32 位 | thumb | so 基址 + 函数在 so 中的偏移 + 1 |
64 位 | arm | so 基址 + 函数在 so 中的偏移 |
也可通过显示汇编指令对应的 opcode bytes,来判断
IDA -> Options -> General -> Number of opcode bytes (non-graph) 改为 4
arm 指令为 4 个字节,如果函数中有些指令是两个字节,那么函数地址计算需要 + 1
不清楚的话,+1 和不+1 都试一遍即可
所以获取基址就显得尤为重要
Process.findModuleByName
通过模块名找到模块
const module = Process.findModuleByName('libencryptlib.so')
console.log(JSON.stringify(module))
// {"name":"libencryptlib.so","base":"0x74d5934000","size":303104,"path":"/data/app/~~Nzn4SQ_RZn1-PYH7TbX7Ig==/com.pocket.snh48.activity-Muxx7c_dtplxjFPY2SGF0A==/lib/arm64/libencryptlib.so"}
// base为基址
Process.getModuleByName
同 findModuleByName
Module.findBaseAddress()(常用)
直接获得模块基址
const baseAddr = Module.findBaseAddress('libencryptlib.so')
console.log(baseAddr)
// 0x74d5934000
Process.findModuleByAddress(address)
通过基址来找到模块
Process.getModuleByAddress(address)
同 findModuleByAddress
测试 hook 任意函数
const baseAddr = Module.findBaseAddress('libencryptlib.so')
// const so = 0x77ab999000;
// console.log(ptr(so).add(0x1FA38)); // ptr 是 new NativePointer()的简写
const funcAddr = baseAddr.add(0x1fa38) // 0x1FA38 是IDA中函数定义的地址
Interceptor.attach(funcAddr, {})
打印参数
function print_arg(addr) {
const module = Process.findRangeByAddress(addr)
// 判断传入的参数是否为地址
if (module !== null) return hexdump(addr) + '\n'
return ptr(addr) + '\n'
}
参数 的方法
// args[0] 是一个内存地址
hexdump(args[0]) // 打印参数的所在内存区域的字节数据
args[0].readCString() // 读取参数所对应的C字符串 (前提: 参数是一个可见字符串)
args[0].readPointer() // 用读地址方式去读取参数所对应的值 (如果参数是一个指针的话可能就需要使用)