首页
社区
课程
招聘
[原创]libDexHelper.so 全面分析
发表于: 23小时前 911

[原创]libDexHelper.so 全面分析

23小时前
911

声明:

libDexHelper.so 是一个典型加固壳早期 native 组件。它不是普通 JNI 业务库,而是承担初始化门卫、隐藏代码装载、ELF 重定位、JNI_OnLoad 调度和析构生命周期管理等职责。

这篇文档按“先看图,再逐函数”的顺序组织:

建议阅读路线:

第 1 章只保留两张入口图:一张静态整体调用链,一张 init_proc 总流程。其余子流程图放回对应函数章节,避免开头连续堆图。

先只看 5 行主链路:

图上真正需要优先记住的是 7 个逻辑函数:

28 个 0x2a30..0x2bf0 短函数先按 导入跳板层 整体理解,完整地址表见第 11 章附录。

读图结论:

读图结论:

主流程可以压缩为:

第 1 章解决“函数在图中的位置”,本章解决“按什么顺序读函数”。后面的第 3-8 章会按这些分组展开,避免把 28 个短导入跳板和 7 个真实逻辑函数混在同一层级阅读。

当前 IDA 函数边界共 35 个函数:

按职责可分为 6 组:

读图结论:

这里的 start 是 IDA 自动命名,不是 ELF entry,也不是程序启动入口。动态段中的 .fini_array[0] 指向 0x2c00:

sub_2AA0__cxa_finalize 导入跳板,因此这个函数的实际职责是 fini bridge: 把当前 DSO handle 传给析构收口函数。

sub_4F84 是析构注册桥:

它把 loc_4F6C、调用者传入对象、unk_16000 组织成 __cxa_atexit(func, arg, dso_handle) 形式。由此可见:

读图结论:

建议先读 4.14.2 建立正常入口与检测态,再读 4.3 失败分支,最后用 4.4 回到主 loader 入口。

sub_4A78.init_array[0] 的实际入口承载函数。.init_array 重定位指向 sub_4A78 内部的 0x4d1c,而不是函数起始地址 0x4a78

它包含两个状态:

初始化态的关键步骤:

关键反汇编:

检测态的核心逻辑:

对应导入跳板:

这段检测并不是按进程名判断,也不是单纯搜索某个字符串;它关注的是 libc.so/libdl.so/linker 相关映射中是否存在小型 rwx 区域,大小阈值为 0x4000

检测返回值经以下指令进入分支:

失败分支:

语义:

检测结果为 0 时,函数继续复制 dword_16010..16028 一组状态,并在 0x4ee8 调用 sub_36E0:

因此 sub_4A78 的静态定位是:

读图结论:

先按阶段 A-G 读标题,确认 loader 的大方向;再进入每个阶段的代码范围、输入输出和下游函数。dynamic section 和 JNI_OnLoad 的细节图放在本章对应小节里。

sub_36E0libDexHelper.so 的主体函数,大小 0x1398。它的职责不是普通 JNI 调用,而是从当前 so 的隐藏材料中恢复后续 ELF/payload,并执行装载器工作。

主线速读:

本节后面的 A-G 阶段都按同一格式阅读: 先看代码范围,再看行为,最后看关联导入。这样可以把 sub_36E0 从一个大函数拆成 7 个连续装载动作。

代码范围:

行为:

目的:

代码范围:

行为:

关联导入:

代码范围:

行为:

sub_2C0C 的核心语义:

代码范围:

行为:

典型解码调用形态:

相关导入:

读图结论:

代码范围:

本阶段的作用是把动态段中的 tag 翻译成 loader 后续可直接使用的状态字段。读这一段时不要把所有 tag 当成同等重要;先抓四类输出即可:

静态特征:

随后 sub_2B10 -> dlopen 加载 DT_NEEDED 依赖,并保存 handle 数组供 relocation 使用。

sub_36E0 调用 sub_2DC0 两次:

两轮 relocation 对应两个 relocation 表或两类 relocation 记录。sub_2DC0 负责遍历条目、解析符号、写回修正地址。

读图结论:

relocation 完成后,loader 继续:

读图结论:

阅读重点是符号解析链路: 先尝试 dlsym,失败后看 dlerror 文本,再在特定错误条件下进入 ELF hash fallback。

sub_2DC0 大小 0x4b4,是手动 loader 完成符号修复的关键函数。

一句话结论: sub_2DC0 不是单纯写地址,它同时承担 找符号按 relocation type 写回 的职责。前者决定 symbol value,后者决定目标位置最终写入什么。

sub_36E0 的调用约定,参数可解释为:

可以按三层理解:

关联函数:

已识别写回规则:

读图结论:

sub_2CE8 是标准 ELF SysV hash 算法实现:

它的阅读价值不在算法本身,而在调用位置。本文中它有两个用途:

读图结论:

0x2a30..0x2bf0 是导入跳板区。典型短跳板形态:

sub_2A30 是 PLT0 resolver 入口:

这一节只需要建立一个判断: 0x2a50..0x2bf0 这些短函数大多不是业务逻辑,而是到 libc/libdl 的导入跳板。逐函数分析时应优先关注谁调用了它、传了什么参数,而不是在跳板内部寻找复杂逻辑。

样本 .dynsym 名称被抹空,但文件内保留了导入名字符串池,结合每个跳板的调用点和参数形态可以确定其语义。

libDexHelper.so 的静态结构可以归纳为:

按函数覆盖范围看,本文已覆盖 libDexHelper.so 当前 IDA 函数边界内的全部 35 个函数。

本节只汇总检测面,不重复 loader、relocation 和导入跳板细节。先给出完整链路:

libDexHelper.so 内真正构成检测逻辑的是 sub_4A78。它有两个入口状态:

检测对象是 /proc/self/maps。执行态通过 sub_2B40 -> fopen("/proc/self/maps", "r") 打开 maps,通过 sub_2A80 -> fgets(line, 0x400, fp) 逐行读取,再由 off_16040 -> sscanf 解析地址范围和权限字段。

这里的关键点是: 它不按进程名判断,也不依赖单一命名字符串;它关心的是内存映射形态。

检测匹配目标集中在三类路径: libc.solibdl.solinker。只有当前 maps 行路径匹配这些目标之一时,才继续解析权限字符。

权限格式来自字符串 %lx-%lx %c%c%c%*c %*s %*s %*d %*s,对应 start、end、r、w、x 等字段。

核心命中条件是小型 rwx 映射:

条件命中后 sub_4A78 返回 1;遍历结束仍未命中时关闭 maps 并返回 0。

检测结果在 0x4e08..0x4e0c 被消费:

其中 0x4e08 只做返回值规整,0x4e0c 才是是否进入失败链的条件跳转。返回值非零时进入 0x4f38;返回值为 0 时继续正常初始化,并在 0x4ee8 调用 sub_36E0

失败链从 0x4f38 开始。sub_2A70 -> getpid 取得当前进程 pid,随后设置 x1 = 9x8 = 0x81 并在 0x4f48 执行 svc #0。在 AArch64 Linux syscall 约定下,这就是 kill(getpid(), SIGKILL)

如果 syscall 返回,后续还有一条后备退出链。sub_2A60 用于处理 errno 存储位置,随后 mov w0, #3; bl sub_2BB0 进入 _Exit(3) 风格退出。这个结构说明检测命中后的终止路径不是单点,而是 kill_Exit 的双保险。

sub_2AD0 -> abort 是 relocation 处理器 sub_2DC0 的致命错误出口,不属于 maps 检测点。它在符号解析、relocation 类型或写回过程遇到不可恢复状态时终止流程。

sub_2AE0 -> __stack_chk_fail 是编译器栈保护失败出口,也不属于壳的主动 maps 检测点。它用于函数栈 canary 异常时的通用保护收口。

因此,本库可确认的主动检测面只有一条主线: sub_4A78 读取 /proc/self/maps,筛选 libc.so/libdl.so/linker,判断小型 rwx 映射,非零结果经 0x4e0c 转入 0x4f38 失败链。abort__stack_chk_fail 是错误/保护出口,应在分析中单独归类。

本附录只用于查地址,不作为首次阅读入口。正文先读 7 个真实逻辑函数,28 个导入跳板按支撑层整体理解。

附件里有一个AI友好型的md文档,把所有图片都改成了mermaid,方便喂给AI
原先是想写过Frida的,但是思路和我上一篇文章差不多,就改让AI静动态全面分析了,还是可以学到不少东西的
该样本来自于 com.sfacg 5.2.74
本文仅用于学习交流

地址 IDA 名称 先读作用
0x4a78 sub_4A78 init 门卫、maps 检测、失败终止、进入 loader
0x36e0 sub_36E0 手动 loader 主体
0x2dc0 sub_2DC0 relocation 处理器
0x2c0c sub_2C0C XOR 解码 helper
0x2ce8 sub_2CE8 ELF SysV hash helper
0x2c00 IDA 命名 start 实际是 fini bridge,不是 ELF entry
0x4f84 sub_4F84 __cxa_atexit 注册桥
分组 函数 说明
生命周期 IDA 命名 startsub_4F84sub_2AA0sub_2B20 fini 触发、析构注册、DSO handle 管理
门卫链 sub_4A78sub_2B10sub_2B30sub_2B40sub_2A80sub_2B60sub_2A70sub_2A60sub_2BB0 自定位、maps 扫描、失败终止
主 loader sub_36E0sub_2BD0sub_2B00sub_2AB0sub_2AC0sub_2B50sub_2BA0sub_2BE0sub_2A90sub_2BF0 读取、映射、解码、设权限、回写、关闭资源
解码 sub_2C0C 单字节 XOR 解码
relocation sub_2DC0sub_2B30sub_2A50sub_2AF0sub_2CE8sub_2B80sub_2AD0 符号解析、hash fallback、重定位写回、异常终止
辅助 sub_2A30sub_2B70sub_2B90sub_2BC0sub_2AE0 PLT0、dump 辅助、字符串长度、栈保护失败
函数 导入/API 用途
sub_2B40 fopen 打开 /proc/self/maps
sub_2A80 fgets 逐行读取 maps
off_16040 sscanf 解析映射起止地址和权限字符
sub_2B60 fclose 关闭 maps 文件句柄
函数 API 用途
sub_2BD0 pread 早期大块读取
sub_2B00 pread 后续片段读取
sub_2AB0 lseek 调整 fd 偏移
sub_2B50 memset 清理工作缓冲
调用位置 参数形态 说明
0x3a14 offset=0, size=0x1000 解码头部窗口
0x4550 offset=0x40, size=0xf6cac 解码大块段内容
0x4350 offset=0, size=0x9160 解码表区或映射片段
函数 API 用途
sub_2AC0 mmap 创建装载镜像
sub_2BA0 mprotect 调整内存权限
sub_2BE0 mprotect 按 segment flags 二次设权
sub_2A90 memcpy 回写或复制元数据
函数 API/语义 用途
sub_2B30 dlsym 从依赖库 handle 查符号
sub_2A50 dlerror 读取符号解析错误
sub_2AF0 strstr 判断错误是否为非全局符号
sub_2CE8 ELF SysV hash hash fallback
sub_2B80 strcmp 确认候选符号名
sub_2AD0 abort 致命错误出口
地址 IDA 名称 大小 归属图层 作用
0x2c00 IDA 命名 start 0x0c fini 生命周期 实际是 fini bridge,调用 __cxa_finalize(dso_handle)
0x2c0c sub_2C0C 0xdc 解码图 单字节 XOR 解码 helper
0x2ce8 sub_2CE8 0xd8 relocation/JNI 图 ELF SysV hash helper
0x2dc0 sub_2DC0 0x4b4 relocation 图 relocation 处理器
0x36e0 sub_36E0 0x1398 主 loader 图 手动 loader 主体
0x4a78 sub_4A78 0x50c init 门卫图 自定位、maps 检测、失败终止和进入 loader
0x4f84 sub_4F84 0x18 fini 生命周期 __cxa_atexit 注册桥
地址 IDA 名称 大小 归属图层 作用
0x2a30 sub_2A30 0x14 导入跳板区 PLT0 resolver 入口,所有导入跳板最终经它或相邻 GOT 槽跳转
0x2a50 sub_2A50 0x10 relocation 图 dlerror,在 dlsym 后读取错误信息
0x2a60 sub_2A60 0x10 sub_4A78 失败链 errno 存储位置,处理失败 syscall 返回
0x2a70 sub_2A70 0x10 sub_4A78 失败链 getpid,给 kill syscall 提供 pid
0x2a80 sub_2A80 0x10 maps 读取链 fgets,逐行读取 /proc/self/maps
0x2a90 sub_2A90 0x10 sub_36E0 收尾 memcpy,复制/回写运行时元数据
0x2aa0 sub_2AA0 0x10 fini 生命周期 __cxa_finalize,析构收口
0x2ab0 sub_2AB0 0x10 sub_36E0 loader lseek,调整 fd 偏移
0x2ac0 sub_2AC0 0x10 sub_36E0 loader mmap,映射 header window 和完整装载镜像
0x2ad0 sub_2AD0 0x10 relocation 图 abort,relocation 致命错误出口
0x2ae0 sub_2AE0 0x10 辅助/保护 __stack_chk_fail,栈保护失败出口
0x2af0 sub_2AF0 0x10 relocation 图 strstr,判断 dlerror 是否含非全局符号提示
0x2b00 sub_2B00 0x10 sub_36E0 loader pread,读取隐藏材料或 segment 数据
0x2b10 sub_2B10 0x10 init/loader 依赖链 dlopen,打开 libc 和 DT_NEEDED 依赖
0x2b20 sub_2B20 0x10 fini 生命周期 __cxa_atexit,注册析构回调
0x2b30 sub_2B30 0x10 init/relocation 图 dlsym,解析 sscanf 和依赖符号
0x2b40 sub_2B40 0x10 maps 读取链 fopen,打开 /proc/self/maps
0x2b50 sub_2B50 0x10 sub_36E0 loader memset,清理工作缓冲
0x2b60 sub_2B60 0x10 maps 读取链 fclose,关闭 maps 文件句柄
0x2b70 sub_2B70 0x10 dump 辅助 isprint,判断可打印字符
0x2b80 sub_2B80 0x10 relocation/JNI 图 strcmp,确认符号名
0x2b90 sub_2B90 0x10 dump 辅助 sprintf,格式化十六进制 dump 行
0x2ba0 sub_2BA0 0x10 sub_36E0 loader mprotect,调整装载镜像权限
0x2bb0 sub_2BB0 0x10 sub_4A78 失败链 _Exit,kill 后备退出
0x2bc0 sub_2BC0 0x10 dump 辅助 strlen,取格式化字符串长度
0x2bd0 sub_2BD0 0x10 sub_36E0 loader pread,早期读取 0x80000 级隐藏材料
0x2be0 sub_2BE0 0x10 sub_36E0 loader mprotect,按 program header flags 设置页面保护
0x2bf0 sub_2BF0 0x10 sub_36E0 收尾 close,关闭 fd/资源
.init_array[0] -> sub_4A78@0x4d1c
  -> maps 检测
  -> sub_36E0 主 loader
  -> sub_2DC0 relocation
  -> JNI_OnLoad / fini bridge
Android linker
  -> .init_array[0]
    -> sub_4A78@0x4d1c
      -> 找自身 ELF base
      -> dlopen("libc.so")
      -> dlsym("sscanf")
      -> 递归进入 sub_4A78@0x4a78
        -> fopen("/proc/self/maps")
        -> fgets maps 行
        -> 匹配 libc.so / libdl.so / linker
        -> sscanf 解析权限
        -> 命中小型 rwx 映射: kill/_Exit 分支
        -> 未命中: sub_36E0 主 loader
          -> 读取隐藏材料
          -> mmap 新镜像
          -> sub_2C0C XOR 解码
          -> 解析 ELF header / program header / dynamic section
          -> dlopen DT_NEEDED
          -> sub_2DC0 relocation
          -> sub_2CE8 + strcmp 查 JNI_OnLoad
          -> init/JNI_OnLoad/析构注册/资源关闭
0x2a30..0x2bf0  28 个导入跳板/PLT 封装
0x2c00          IDA 命名 start,实际是 fini bridge
0x2c0c          XOR 解码 helper
0x2ce8          ELF SysV hash helper
0x2dc0          relocation 处理器
0x36e0          主 loader
0x4a78          init 门卫 + maps 检测 + kill 分支
0x4f84          atexit 注册桥
0x2c00  adrl x0, unk_16000
0x2c08  b    sub_2AA0
0x4f84  mov  x1, x0
0x4f88  adrp x2, unk_16000
0x4f8c  adrp x0, loc_4F6C
0x4f98  b    sub_2B20
0x4dd8  adrp x3, n70
0x4ddc  adrp x0, "libc.so"
0x4de0  mov  w1, #2
0x4de8  str  x2, [x3,#n70]
0x4dec  bl   sub_2B10        ; dlopen
0x4df0  adrl x1, "sscanf"
0x4df8  bl   sub_2B30        ; dlsym
0x4e00  str  x0, [off_16040]
0x4e04  bl   sub_4A78        ; enter detector state
fp = fopen("/proc/self/maps", "r");
while (fgets(line, 1024, fp)) {
    if (line_path_contains("libc.so") ||
        line_path_contains("libdl.so") ||
        line_path_contains("linker")) {
        sscanf(line, "%lx-%lx %c%c%c...", &start, &end, &r, &w, &x);
        if (r == 'r' && w == 'w' && x == 'x' && end - start <= 0x4000) {
            return 1;
        }
    }
}
fclose(fp);
return 0;
0x4e04  bl    sub_4A78
0x4e08  uxtb  w0, w0
0x4e0c  cbnz  w0, loc_4F38
0x4f38  bl    sub_2A70
0x4f3c  sxtw  x0, w0
0x4f40  mov   x1, #9
0x4f44  mov   x8, #0x81
0x4f48  svc   #0
0x4f4c  cmn   x0, #1, lsl #12
0x4f50  mov   x19, x0
0x4f54  b.ls  loc_4F64
0x4f58  bl    sub_2A60
0x4f5c  neg   w19, w19
0x4f60  str   w19, [x0]
0x4f64  mov   w0, #3
0x4f68  bl    sub_2BB0
0x4f74  cbz   x0, loc_4F7C
0x4f78  blr   x0
0x4f7c  ret
0x4ee8  bl    sub_36E0
init array 入口
  -> 自定位
  -> 初始化 sscanf
  -> maps 小型 rwx 检测
  -> 失败: kill/_Exit
  -> 通过: sub_36E0 主 loader
定位自身映射
  -> 读取隐藏材料和 XOR key
  -> mmap 头部窗口并解码
  -> 遍历 program header,建立完整装载镜像
  -> 解析 dynamic section,收集表地址和依赖项
  -> 两轮 relocation
  -> 查找并调用 JNI_OnLoad,注册析构并收尾
0x3844..0x3910
0x3914..0x39a8
0x39a8..0x3b8c
char *sub_2C0C(base, offset, size) {
    p = base + offset;
    for (i = 0; i < size; i++) {
        p[i] ^= dword_16088;
    }
    return p;
}
0x3c20..0x43c8
0x4458..0x480c
0x44b0..0x4a1c
0x4838 -> sub_2DC0
0x4858 -> sub_2DC0
a1: dynsym/symbol table 基址或条目基址
a2: 字符串表/相关基址
a3: relocation 表
a4: relocation 数量
a5: dlopen handle 数组
a6: handle 数量
a7: 新映射基址/load bias
for each relocation:
  1. 解析 relocation offset/info/addend
  2. 从 info 拆出 symbol index 和 relocation type
  3. 若 relocation 需要符号:
       name = strtab + sym.st_name
       for handle in dependency_handles:
           addr = dlsym(handle, name)
           if addr found:
               use addr
           else:
               err = dlerror()
               if strstr(err, "symbol found but not global:"):
                   走 ELF hash bucket/chain fallback
  4. 按 relocation type 写目标地址
257 / 258 / 259: 目标位置 += addend + symbol_value
261 / 262:       目标位置 += addend - target_offset + symbol_value
1024 / 1025:     目标位置 = addend + symbol_value
1026:            目标位置 = addend + symbol_value
1027:            目标位置 = addend + load_bias
h = 0;
while (*s) {
    h = *s++ + 16 * h;
    g = h & 0xf0000000;
    h = (g ^ h) ^ (g >> 24);
}
return h;
ADRP X16, #got_slot@PAGE
LDR  X17, [X16,#got_slot@PAGEOFF]
ADD  X16, X16, #got_slot@PAGEOFF
BR   X17
0x2a30  stp  x16, x30, [sp,#-0x10]!
0x2a34  adrp x16, qword_15F10
0x2a38  ldr  x17, [x16,#qword_15F10]
0x2a3c  add  x16, x16,#qword_15F10
0x2a40  br   x17

传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 23小时前 被aut0run编辑 ,原因:
上传的附件:
收藏
免费 53
打赏
分享
最新回复 (15)
雪    币: 158
活跃值: (5466)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享。
21小时前
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
21小时前
0
雪    币: 207
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
学习一下
21小时前
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
666
21小时前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
太强了
20小时前
0
雪    币: 997
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
tql
20小时前
0
雪    币: 216
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
1
18小时前
0
雪    币: 7190
活跃值: (7335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
66
17小时前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
好,学习
14小时前
0
雪    币: 2507
活跃值: (3013)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
tal
13小时前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
10小时前
0
雪    币: 2410
活跃值: (2820)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
666
7小时前
0
雪    币: 3184
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
6666
7小时前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
学习
4小时前
0
雪    币: 0
活跃值: (575)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
66666
3小时前
0
游客
登录 | 注册 方可回帖
返回