一、SO层相关
1. hook_so层
- hook_so层只需要得到它的函数地址,有函数地址就能hook与主动调用,而得到函数地址的方式有两种;
1.1 方式一
- 通过frida提供的api来得到,该函数必须有符号的才可以;
- 有符号是指此函数是否出现在导出、导入、符号表里;
1.2 方式二
- 通过计算得到地址:so基址+函数在so中的偏移[+1](32位+1)
2. 各种枚举
2.1 枚举导入表
- 通过枚举导入表,可以得到出现在导入表中的函数地址;(enumerateImports)
var imports = Module.enumerateImports("libifeng_secure.so");
// console.log(JSON.stringify(imports[0]))
for (var i = 0; i < imports.length; i++) {
// if (imports[i].name == "atoi") {
// console.log(JSON.stringify(imports[i]));
console.log('导入函数名--->>>',imports[i].name);
console.log('导入函数地址--->>>',imports[i].address);
console.log('-----------------------------------------------------------------')
// break;
// }
}
- 以某so为例,得到的结果如下:
2.2 枚举导出表
- 通过枚举导出表,可以得到出现在导出表中的函数地址,与导入表同理,api不同;(enumerateExports)
var imports = Module.enumerateExports("libifeng_secure.so");
// console.log(JSON.stringify(imports[0]))
for (var i = 0; i < imports.length; i++) {
// if (imports[i].name == "atoi") {
// console.log(JSON.stringify(imports[i]));
console.log('导出函数名--->>>',imports[i].name);
console.log('导出函数地址--->>>',imports[i].address);
console.log('-----------------------------------------------------------------')
// break;
// }
}
- 以某so为例结果如下:
2.3 枚举符号表
- 通过枚举符号表,可以得到出现在符号表中的函数地址;
var symbols = Module.enumerateSymbols("libifeng_secure.so");
// console.log(JSON.stringify(symbols[0]))
for (var i = 0; i < symbols.length; i++) {
console.log('符号表函数名--->>>',symbols[i].name);
console.log('符号表函数地址--->>>',symbols[i].address);
console.log('-----------------------------------------------------------------')
}
2.4 枚举模块
- 通过枚举模块,再枚举模块里面的导出表,可以快速找到某个导入函数出自哪个so;
// 枚举进程中已加载的模块
var modules = Process.enumerateModules();
var module = modules[0].enumerateExports()
for(let i = 0; i < module.length; i++){
console.log('枚举进程函数名--->>>',module[i].name);
console.log('枚举进程函数地址--->>>',module[i].address);
console.log('-----------------------------------------------------------------')
}
// console.log(JSON.stringify(modules[0].enumerateExports()));
- 通过枚举模块得到的是一个数组,取第一个后就可以调用上述三个方法;
3. hook导出函数
- 在so导出表里的函数,可以通过frida提供的api来获取函数地址,Module.findExportByName(“xxxx.so”, “add”),函数名以汇编中出现的为准;
// 导出函数的hook
var funcAddr = Module.findExportByName("libencryptlib.so", "_ZN7MD5_CTX11MakePassMD5EPhjS0_");
console.log('函数地址--->>>',funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log("funcAddr onEnter args[1]: ", hexdump(args[1]));
console.log("funcAddr onEnter args[2]: ", args[2].toInt32());
this.args3 = args[3];
}, onLeave: function (retval) {
console.log("funcAddr onLeave args[3]: ", hexdump(this.args3));
}
});
- 在这里以口袋48为例:
- 在这里我们的前置条件是,传了三个参数,这里初始打印第三个参数时,发现结果全是0,这里大概就是一个缓冲区,这里是因为C语言里,非常喜欢将参数当作返回值使用,那么这里我们就需要在离开的时候再读取内存;
this.args3 = args[3];
console.log("funcAddr onLeave args[3]: ", hexdump(this.args3));
- 在这里我们知道它是MD5,我们对比一下结果;
- 可以发现与hook到的结果是一致的;
4. 获取模块基址
- 在此的前提是,我们需要的函数不在三个表里,我们就没办法直接使用frida提供的api来获取函数地址,在此需要计算它的函数地址;
- 计算公式如下:
so基址+函数在so中的偏移[+1] 32位则 +1
- 因此,我们需要先得到so基址,也就是模块基址;
4.1 findModuleByName
- 使用findModuleByName时,指明so文件即可,得到的是一个module对象,可以进行转换,也可以直接.base取地址;
var module1 = Process.findModuleByName("libencryptlib.so");
console.log("module1对象--->>>",JSON.stringify(module1));
console.log("module1基址--->>>", module1.base);
- 直接.base取的就是基址;
4.2 getModuleByName
- 与findModuleByName类似,得到的也是一个对象;
var module2 = Process.getModuleByName("libencryptlib.so");
console.log("module2对象--->>>",JSON.stringify(module2));
console.log("module2基址--->>>", module2.base);
- 结果如下:
4.3 findBaseAddress(推荐)
- 与前两个有所不同,这里得到的直接就是函数地址,也就是基址;
var soAddr = Module.findBaseAddress("libencryptlib.so");
console.log("soAddr基址--->>>", soAddr);
- 这里就无需再去操作,返回值就是地址;
4.4 enumerateModules
- 通过枚举所有模块,再判断是否与我们需要的模块一样,如果一致则输出地址等;
var modules = Process.enumerateModules();
for(let i = 0; i < modules.length; i++){
if(modules[i].name == "libencryptlib.so"){
console.log(modules[i].name + " " + modules[i].base);
}
}
-
不过此方式用的较少;
-
也可以通过地址找模块,这也就可以调用一些方法;
var module = Process.findModuleByAddress(Module.findBaseAddress("libencryptlib.so"));
console.log("module " + module.name + " " + module.base);
5. 函数地址计算
5.1 偏移
- 上述描述可知,函数地址计算如下:
so基址+函数在so中的偏移[+1] [ 32位则 +1]
- 基址我们已经能够获取到了,那就剩下偏移;
- 以口袋48app为例,这里我们需要找某函数的偏移;
- 在其定义位置按下tab,则可转到汇编,界面如下:
- 实际上这就是它的偏移,它是相对so基址的偏移;得到这个偏移就可以计算出函数地址了;
- 在这里是否+1呢,在目前来说,大部分是64位的so,此时则不需要加,若为32位则需要加;
5.2 地址计算
- 依据公式,首先得到基址,再加上偏移;
var soAddr = Module.findBaseAddress("libencryptlib.so");
console.log(ptr(soAddr).add(0xxx));
- soAddr得到是基址,其实也就是指针,在这里通过add (sub方法为减) 加上偏移,ptr实际上就是指针,也可以不加ptr;
- 而add里面是一个数值,不是字符串,这里是十六进制,则需要加上0x;
- 何时加ptr呢,若你的soAddr为具体的数值,则需要加,如:
var soAddr = Module.findBaseAddress("libencryptlib.so");
var so = 0x72777a6000;
console.log("soAddr基址--->>>" + soAddr);
console.log(ptr(so).add(0x1FA38)); // new NativePointer() == ptr()
- 在一个具体的数值add时,由于不是指针,则无法调用add方法,加上ptr即可,也是同样可以得到地址的;
6. hook任意函数
- 根据上述条件,我们已经可以hook任意的函数了,得到一个地址即可;
var soAddr = Module.findBaseAddress("libencryptlib.so");
// var so = 0x72777a6000;
console.log("soAddr基址--->>>" + soAddr);
// console.log(ptr(so).add(0x1FA38)); // new NativePointer()
var funcAddr = soAddr.add(0x1FA38);
console.log("funcAddr函数地址--->>>" + funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log("funcAddr onEnter args[1]: ", hexdump(args[1]));
console.log("funcAddr onEnter args[2]: ", args[2].toInt32());
this.args3 = args[3];
}, onLeave: function (retval) {
console.log("funcAddr onLeave args[3]: ", hexdump(this.args3));
}
});
- 其余就与hook导出函数是类似的;