Android9.0中在activity的onCreate之前hook dlopen函数,如果需要返回值(即修改了LR寄存器),那么会触发:E/libEGL: EGL_ANDROID_blob_cache advertised, but unable to get eglSetBlobCacheFuncsANDROID。不会crash,但是界面不会绘制出来。
是因为dlopen(libEGL_adreno.so, 2)、dlopen(libGLESv2_adreno.so, 2)返回都是null。理论上其他任何hook框架也都存在这个问题,测试了几个也确实都存在。
dlopen函数实现在libdl.so,
.text:0000000000000EF8 WEAK dlopen
.text:0000000000000EF8 dlopen ; DATA XREF: LOAD:0000000000000508↑o
.text:0000000000000EF8
.text:0000000000000EF8 var_s0 = 0
.text:0000000000000EF8
.text:0000000000000EF8 ; __unwind {
.text:0000000000000EF8 STP X29, X30, [SP,#-0x10+var_s0]!
.text:0000000000000EFC MOV X29, SP
.text:0000000000000F00 MOV X2, X30 ; a3
.text:0000000000000F04 BL .__loader_dlopen
.text:0000000000000F08 LDP X29, X30, [SP+var_s0],#0x10
.text:0000000000000F0C RET
.text:0000000000000F0C ; } // starts at EF8
.text:0000000000000F0C ; End of function dlopen
这里把LR寄存器当作第三个参数传递给.__loader_dlopen,而我们hook时为了能拿到返回值是修改了LR寄存器的,这是不可避免的。如果不修改LR寄存器,除非我们知道函数结尾,在函数结尾修改LR寄存器或者跳到shellcode/hook函数,但是通常是不现实的,函数开头容易确认,函数结尾很难自动化确认。
// Proxy calls to bionic loader
__attribute__((__weak__))
void* dlopen(const char* filename, int flag) {
const void* caller_addr = __builtin_return_address(0);
return __loader_dlopen(filename, flag, caller_addr);
}
__builtin_return_address函数应该是gcc内部函数,0获取的是被调用函数返回后执行的指令地址,至于1之后的数字是否能获取到函数调用栈待测试。已测试:
1、0获取的就是进入函LR寄存器的值。
2、1获取的是r7(thumb)/r12(arm),即ip寄存器的值,所以能不能获取到正确的值取决于上层是否使用了ip寄存器暂存sp,且ip寄存器之后(栈上)就是在栈上存储的LR寄存器的值。所以不满足这些条件的函数是获取不到的甚至是错误的。
爷爷函数:
.text:0000C250 ; __unwind {
.text:0000C250 PUSH {R4,R5,R7,LR}
.text:0000C252 ADD R7, SP, #8
...
.text:0000C292 BLX j__Z12test_replacev ;