前言
移动端Android安全的发展,催生了各种Android加固的诞生,基于ELF文件的特性,很多的加固厂商在进行Android逆向的对抗的时,都会在Android的so文件中进行动态的对抗,对抗的点一般在so文件的.init段和JNI_OnLoad处。因此,我们在逆向分析各种厂商的加固so时,需要在so文件的.init段和JNI_OnLoad处下断点进行分析,过掉这些加固的so对抗。
今天就来使用如何在这些段上下断点,和hook init段的函数
实验
先来看看so库加载时的执行顺序
//动态注册时都会执行到这个方法中
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;//定义JNI Env
jint result = -1;
/*JavaVM::GetEnv 原型为 jint (*GetEnv)(JavaVM*, void**, jint);
*/
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return result;
}
__android_log_print(ANDROID_LOG_ERROR, "SharkChilli", "JNI_OnLoad");
result = JNI_VERSION_1_6;
return result;
}
//编译生成后在.init段
extern "C" void _init(void) {
__android_log_print(ANDROID_LOG_ERROR, "SharkChilli", "_init");
}
//编译生成后在.init_array段
__attribute__((constructor)) void myinitarray(void) {
__android_log_print(ANDROID_LOG_ERROR, "SharkChilli",
"myinitarray __attribute__((constructor))");
}
__attribute__((constructor)) void myinitarray2(void) {
__android_log_print(ANDROID_LOG_ERROR, "SharkChilli",
"myinitarray2 __attribute__((constructor))");
}
class Test {
public:
Test() {
__android_log_print(ANDROID_LOG_ERROR, "SharkChilli",
"Test Test()");
}
};
Test objTest;
运行后日志如下
2020-04-23 10:53:59.776 24392-24392/com.shark.initapp E/SharkChilli: _init
2020-04-23 10:53:59.776 24392-24392/com.shark.initapp E/SharkChilli: myinitarray __attribute__((constructor))
2020-04-23 10:53:59.776 24392-24392/com.shark.initapp E/SharkChilli: myinitarray2 __attribute__((constructor))
2020-04-23 10:53:59.776 24392-24392/com.shark.initapp E/SharkChilli: Test Test()
2020-04-23 10:53:59.777 24392-24392/com.shark.initapp E/SharkChilli: JNI_OnLoad
由此可以知道上面的执行顺序依次是init>init_array>c++全局对象初始化>JNI_OnLoad
frida Hook
读者需要阅读上一章android loadLibrary源码分析,从上一章可以知道在Android4中我们需要Hook CallFunction函数,在Android8中我们需要Hook call_function函数来得到.init .init_array等函数等相关信息
我们以Android8为例来演示
我们先将上面的代码以32位模式运行在手机上,这里为了方便在32位上。
adb install -t --abi armeabi-v7a .\app-debug.apk
由于该函数没有导出我们无法直接获得。
现在有一个问题我们如何得到call_function的地址? 这里有两种方式(其实殊途同归)。
先将linker导出
adb pull /system/bin/linker
- 使用IDA打开linker,在IDA中查找符号call_function
image.png
通过demangler可以知道我们需要Hook的函数为_dl__ZL13call_functionPKcPFviPPcS2_ES0,它的偏移地址为0x00013554,在frida中我们得到linker的基址加上这个偏移就是它在内存中的地址了。
2.使用frida 查询导出符号中call_function,代码如下
var linker = Process.getModuleByName("linker");
var call_function_addr = null;
var exports = linker.enumerateSymbols();
for (var m = 0; m < exports.length; m++) {
// console.log(exports[m].name);
if (exports[m].name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") >= 0) {
call_function_addr = exports[m].address;
//得到call_function偏移地址 因为我们运行在32位上所以需要-1
console.log("call_function offset:", ptr(call_function_addr).sub(linker.base).sub(0x1));
}
}
Spawning `com.shark.initapp`...
call_function offset: 0x13554
Spawned `com.shark.initapp`. Resuming main thread!
可以看到这个和IDA中分析到的是一样的。
最后我们使用这个地址去Hook并打印相关参数即可
function hook_call_function() {
var linker = Process.getModuleByName("linker");
var call_function_addr = null;
var exports = linker.enumerateSymbols();
for (var m = 0; m < exports.length; m++) {
// console.log(exports[m].name);
if (exports[m].name.indexOf("__dl__ZL13call_functionPKcPFviPPcS2_ES0_") >= 0) {
call_function_addr = exports[m].address;
//得到call_function偏移地址 因为我们运行在32位上所以需要-1
console.log("call_function offset:", ptr(call_function_addr).sub(linker.base).sub(0x1));
// console.log("find call_function_addr:", exports[m].name, call_function_addr);
hook_func(call_function_addr);
}
}
}
function hook_func(call_address) {
//不能在这得到libnative-lib.so的基地址,因为这个时候还没加载进来
Interceptor.attach(call_address, {
onEnter: function (args) {
/**
* 参数一:函数名称
* 参数二:执行函数地址
* 参数三:函数所在so库
*/
//过滤一下 我们只关注libnative-lib.so中的函数
if (args[2].readCString().indexOf("libnative-lib.so") >= 0) {
var mod_address = Module.findBaseAddress("libnative-lib.so");
console.log("[function name:", args[0].readCString());
//计算出函数在libnative-lib.so中的偏移地址 因为我们运行在32位上所以需要-1
console.log("function offset:", ptr(args[1]).sub(mod_address).sub(0x1),"]");
}
}, onLeave: function (ret) {
// console.log("ret:", ret);
}
});
}
setImmediate(hook_call_function);
打印结果如下
[function name: DT_INIT
function offset: 0x12cc0 ]
[function name: function
function offset: 0x12cdc ]
[function name: function
function offset: 0x12cf8 ]
[function name: function
function offset: 0x12d28 ]
我们使用IDA打开libnative-lib.so查询相关函数
其中还有一个就是c++全局对象初始化
由此我们也可以知道c++全局对象初始化的构造函数也是在.init_array段中
ok到这就拿到了我们想要的函数地址了,至于后面如何使用就看读者自己了