首页
社区
课程
招聘
[原创]Android9.0 hook dlopen问题/如何hook dlopen相关函数
发表于: 2020-1-2 13:00 14782

[原创]Android9.0 hook dlopen问题/如何hook dlopen相关函数

2020-1-2 13:00
14782

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 ; test_replace(void)

--------------------------------------

父函数:
.text:0000B648                 PUSH            {R7,LR}
.text:0000B64A                 MOV             R7, SP
...
.text:0000B6A6                 BLX             j__Z12dump_replacePvS_PFvP10my_pt_regsP11STR_HK_INFOES5_PKc ;

--------------------------------------

子函数:
.text:0000D268                 PUSH            {R4-R7,LR}
.text:0000D26A                 ADD             R7, SP, #0xC
...
.text:0000D2DA                 MOV             R1, R7
.text:0000D2DC                 LDR             R1, [R1];    父函数传过来的R7的值
.text:0000D2DE                 LDR             R3, [R1,#4];r3寄存器为__builtin_return_address(1),即取父函数放在R7之后的LR寄存器的值

.__loader_dlopen就是一个跳板

.plt:0000000000000DD8 ; __int64 __fastcall __loader_dlopen(__int64 a1, __int64 a2, __int64 a3)
.plt:0000000000000DD8 .__loader_dlopen                        ; CODE XREF: dlopen+C↓p
.plt:0000000000000DD8                 ADRP            X16, #off_1FF70@PAGE
.plt:0000000000000DDC                 LDR             X17, [X16,#off_1FF70@PAGEOFF]
.plt:0000000000000DE0                 ADD             X16, X16, #off_1FF70@PAGEOFF
.plt:0000000000000DE4                 BR              X17     ; __loader_dlopen
.plt:0000000000000DE4 ; End of function .__loader_dlopen

导入loader_dlopen函数,实现在linker/64,所以从libdl.so的got表中可以获取loader_dlopen函数的绝对地址。

static void* dlopen_ext(const char* filename,
                        int flags,
                        const android_dlextinfo* extinfo,
                        const void* caller_addr) {
  ScopedPthreadMutexLocker locker(&g_dl_mutex);
  g_linker_logger.ResetState();
  void* result = do_dlopen(filename, flags, extinfo, caller_addr);
  if (result == nullptr) {
    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
    return nullptr;
  }
  return result;
}

void* __loader_android_dlopen_ext(const char* filename,
                           int flags,
                           const android_dlextinfo* extinfo,
                           const void* caller_addr) {
  return dlopen_ext(filename, flags, extinfo, caller_addr);
}

void* __loader_dlopen(const char* filename, int flags, const void* caller_addr) {
  return dlopen_ext(filename, flags, nullptr, caller_addr);
}

dlopen_ext函数被loader_dlopen函数内联了,所以loader_dlopen函数足够被inline hook了。

__int64 __fastcall _loader_dlopen(__int64 a1, unsigned int a2, __int64 a3)
{
  _BYTE *v3; // x21
  __int64 v4; // x19
  unsigned int v5; // w20
  __int64 v6; // x19
  __int64 v7; // x0

  v3 = (_BYTE *)a1;
  v4 = a3;
  v5 = a2;
  _dl_pthread_mutex_lock(&_dl__ZL10g_dl_mutex);
  _dl__ZN12LinkerLogger10ResetStateEv(&_dl_g_linker_logger);
  v6 = _dl__Z9do_dlopenPKciPK17android_dlextinfoPKv(v3, v5, 0LL, v4);
  if ( !v6 )
  {
    v7 = _dl__Z23linker_get_error_bufferv(0LL);
    _dl__ZL23__bionic_format_dlerrorPKcS0_("dlopen failed", v7);
  }
  _dl_pthread_mutex_unlock(&_dl__ZL10g_dl_mutex);
  return v6;
}

首先为了验证是不是因为LR寄存器导致的问题,使用dump函数"(dump((void*)dlopen, onPreCallBack, NULL, "dlopen");)",只在函数之前打印下参数寄存器,之后调用原函数,发现可以正常运行了。

 

使用导入表/导出表hook dlopen也出现该问题。

 

hook __loader_dlopen代替dlopen,发现dump、replace都可以正常运行,至此可以确定确实是修改了LR寄存器的问题。

 

loader_dlopen函数导出在linker,通过dlsym(RTLD_DEFAULT, "loader_dlopen")是获取不到的,linker不是动态库,这里采用


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 1
支持
分享
最新回复 (9)
雪    币: 205
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
2020-1-2 13:15
0
雪    币: 2334
活跃值: (1436)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
VA hook dlopen解决方案
void hook_dlopen() {
    void *symbol = NULL;

    if (find_symbol("__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V24, (void**)&real_dlopen_V24);
    } else if (find_symbol("__dl__Z9do_dlopenPKciPK17android_dlextinfoPv", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V24, (void**)&real_dlopen_V24);
    } else if (find_symbol("__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V24, (void**)&real_dlopen_V24);
    } else if (find_symbol("__dl__Z20__android_dlopen_extPKciPK17android_dlextinfoPKv", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V24, (void**)&real_dlopen_V24);
    } else if (find_symbol("__dl___loader_android_dlopen_ext", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V24, (void**)&real_dlopen_V24);
    } else if (find_symbol("__dl__Z9do_dlopenPKciPK17android_dlextinfo", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V19, (void**)&real_dlopen_V19);
    } else if (find_symbol("__dl__Z8__dlopenPKciPKv", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V19, (void**)&real_dlopen_V19);
    } else if (find_symbol("__dl___loader_dlopen", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen_V19, (void**)&real_dlopen_V19);
    } else if (find_symbol("__dl_dlopen", "linker", (unsigned long *)&symbol) == 0) {
        ZzHookReplace(symbol, (void*)&fake_dlopen, (void**)&real_dlopen);
    } else {
        ZzHookReplace((void*)dlopen, (void*)&fake_dlopen, (void**)&real_dlopen);
    }
}

frida解决hook dlopen方案好像是 <7.0直接hook dlopen ,>7.0则用capstone 解析 dlopen汇编获取 __loader_dlopen 第三个参数是libc.so的基址
最后于 2020-1-2 14:08 被FraMeQ编辑 ,原因:
2020-1-2 14:02
0
雪    币: 205
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
请问楼主,可知道有什么办法在android直接调用基于glibc编译的arm动态库?以前找了一些加载库的代码(crazy_linker),不行。
2020-1-3 09:28
0
雪    币: 6737
活跃值: (796)
能力值: ( LV13,RANK:393 )
在线值:
发帖
回帖
粉丝
5
lion张 请问楼主,可知道有什么办法在android直接调用基于glibc编译的arm动态库?以前找了一些加载库的代码(crazy_linker),不行。
没做过,无法回答,难道不应该自己定位下错误是什么去进行修复吗,比如先查找这个动态库依赖哪些glibc的so,比如libgcc_s.so、libc.so.6之类的,自己编译个arm可用的glibc、linker等。
2020-1-6 10:46
0
雪    币: 180
活跃值: (1493)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
兄弟,你这个代码框架是从哪里抠来的,刚好需要用到
2020-1-9 10:23
0
雪    币: 1109
活跃值: (3621)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
Android Linux 内核功能裁剪过,bionic 库都缺胳膊少腿,直接用 glibc 大概率开不起来
2020-1-9 11:49
0
雪    币: 120
活跃值: (1733)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
8
b xxx

PreCall 和 PostCall 很鸡肋 所以我删了
2020-1-12 04:24
0
雪    币: 2334
活跃值: (1436)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
9
jmpews b xxx PreCall 和 PostCall 很鸡肋 所以我删了
留着吧,我一般用来打印LR
2020-1-13 09:51
0
雪    币: 245
活跃值: (955)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
分析的很好
2020-1-31 00:22
0
游客
登录 | 注册 方可回帖
返回