枚举各种

枚举导入表

通过枚举导入表,可以得到出现在导入表中的函数地址

var imports = Module.enumerateImports("libencryptlib.so");
for(var i = 0; i < imports.length; i++){
console.log(JSON.stringify(imports[i]));
}

枚举导出表

通过枚举导出表,可以得到出现在导出表中的函数地址

var exports = Module.enumerateImports("libencryptlib.so");
for(var i = 0; i < exports.length; i++){
console.log(JSON.stringify(exports[i]));
}

枚举符号表

通过枚举符号表,可以得到出现在符号表中的函数地址

var symbols = Module.enumerateSymbols("libencryptlib.so");
for(var i = 0; i < symbols.length; i++){
console.log(JSON.stringify(symbols[i]));
}

枚举模块

通过枚举模块,再枚举模块里面的导出表,可以快速找到某个导入函数出自哪个so

var modules = Process.enumerateModules();
console.log(JSON.stringify(modules[0].enumerateExports()[0]));

为什么是导出表呢?

因为每个模块中的导入函数,必然是来自另一个模块的,所以会出现在另一个模块的导出表中

exports和symbols的区别

exports解析的是SHT_DYNSYM

symbols解析的是SHT_SYMTAB

SHT_SYMTAB比SHT_DYNSYM更全,SYMTAB是SYMTAB的子集,不是so运行所必须的,一般会被去掉

Hook导出函数

这里以旧版的口袋48为例,新版的加了 frida 检测,以我目前的水平还无法hook。。。。

findExportByName

在so导出表里的函数,可以通过frida提供的api来获取函数地址

var funcAddr = Module.findExportByName("libencryptlib.so", "_ZN7MD5_CTX11MakePassMD5EPhjS0_");
console.log(funcAddr);

函数名以汇编中出现的为准

Interceptor.attach

得到函数地址后,可以使用Interceptor.attach来对函数进行hook

Interceptor.attach(funcAddr,{
onEnter: function (args) {// args存放了传递给函数的参数
console.log("funcAddr onEnter args[0]: ",hexdump(args[0]));
console.log("funcAddr onEnter args[1]: \n",hexdump(args[1]));
console.log("funcAddr onEnter args[2]: ",args[2].toInt32());
console.log("funcAddr onEnter args[3]: ",hexdump(args[3]));
this.args3 = args[3]; // 将args[3]附加到this.args3就可以得到函数执行后的值,我也不知道为什么
}, onLeave: function (retval){// retval之前函数的返回值
console.log("funcAddr onLeave arg3: ", hexdump(this.args3));
}
});

模块基址的几种获取方式

首先,为什么要学这个?

因为在导入表、导出表、符号表里找不到的函数,地址需要自己计算

Process.findModuleByName()

var module = Process.findModuleByName("libencryptlib.so");
console.log(module.base);

Process.getModuleByName()

var module2 = Process.getModuleByName("libencryptlib.so");
console.log(module2.base);

Module.findBaseAddress()

var soAddr = Module.findBaseAddress("libencryptlib.so");
console.log("soAddr: ", soAddr);

Process.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);
}
}

Process.findModuleByAddress()

var module = Process.findModuleByAddress(Module.findBaseAddress("libencryptlib.so"));
console.log("module: " + module.name + " " + module.base);

Process.getModuleByAddress()

var module = Process.getModuleByAddress(Module.findBaseAddress("libencryptlib.so"));
console.log("module: " + module.name + " " + module.base);

函数地址的计算

thumb指令

so基址 + 函数在so中的偏移+1

arm指令

so基址 + 函数在so中的偏移

判断方式

在安卓中,32位的so中的函数,基本都是thumb指令;64的so中的函数,基本都是arm指令

也可以通过显示汇编指令对应的opcode bytes,来判断:

options -> general -> Number of opcode bytes (non-graph) 4

arm指令位4个字节,如果函数中有些指令是两个字节,那么函数地址计算就需要+1

代码示范

var soAddr = Module.findBaseAddress("libencryptlib.so");
console.log(soAddr.add(0x1FA38));

// 可以使用ptr(new NativePointer())将一个数字转换成指针

SoHook模板

function print_arg(addr){
var module = Process.findRangeByAddress(addr); //传入函数的地址便能计算出所在module的基址
if(module != null) return hexdump(addr) + "\n";
return ptr(addr) + "\n";
}
function hook_native_addr(funcPtr, paramsNum){
var module = Process.findModuleByAddress(funcPtr);
Interceptor.attach(funcPtr, {
onEnter: function(args){
this.logs = [];
this.params = [];
this.logs.push("call " + module.name + "!" + ptr(funcPtr).sub(module.base) + "\n");
for(let i = 0; i < paramsNum; i++){
this.params.push(args[i]);
this.logs.push("this.args" + i + " onEnter: " + print_arg(args[i]));
}
}, onLeave: function(retval){
for(let i = 0; i < paramsNum; i++){
this.logs.push("this.args" + i + " onLeave: " + print_arg(this.params[i]));
}
this.logs.push("retval onLeave: " + print_arg(retval) + "\n");
console.log(this.logs);
}
});
}

var soAddr = Module.findBaseAddress("libencryptlib.so");
var funcAddr = soAddr.add(0x1FA38);
hook_native_addr(funcAddr, 4);