分析对象: sub_1B924 及其完整调用链(so文件见附件)
分析目标: 还原代码逻辑、提取核心对抗算法、复现 Shellcode、制定防御策略
分析深度: 指令级/内核级
技术标签: Anti-Frida, Watchdog, Shellcode Injection, State Machine, ELF Parsing, Ptrace
说明: 仅作为安全技术交流,如有侵权联系删除
经过对提供的 C 伪代码进行逐行审计和静态还原,确认该模块是一个针对 Frida 框架的高级主动防御引擎。
这是防御逻辑的起点。代码通过极其繁琐的步骤来隐藏其真实意图。
攻击者没有将字符串存储在 .rodata 段,而是通过硬编码的十六进制数在栈上动态还原。

代码定位:
解密算法:
还原结果:
在启动核心逻辑前,代码执行了严格的环境检查。

此函数负责定位攻击目标,并启动看门狗线程。它是连接初始化与监控的桥梁。
Android 7.0+ 引入了 Linker Namespace,禁止 APP 直接 dlopen 系统私有库(如 libart.so)。




这是一个死循环函数 (__noreturn),负责全方位的环境扫描。


初始化:解密出一系列黑名单字符串(如 "Xposed", "Frida", "Magisk")。
循环体 (while(1)):
这是整个防御体系中最坚固、最隐蔽的堡垒。它通过状态机混淆控制流,通过动态 Shellcode 执行处决。
函数内部维护一个状态变量 v4,初始状态为 293539132。
当检测到 Hook 时,代码会解密并执行一段 Shellcode。我们完全静态还原了这段代码。
步骤 A: 提取密钥


代码逻辑:v10 = *((_DWORD *)&qword_30794 + v8 + -3 * (v9 / 3) + 1);
步骤 B: 数据解密

源数据位于 xmmword_30760。代码首先将首字节置为 0x08。
步骤 C: 最终修正 (The Final Trick)
代码执行:*(_DWORD *)v16 += 3008; (对前4字节进行整数加法)。
步骤 D: 最终载荷 (The Payload)
将结果解释为 ARM64 汇编:
如果配置不允许 Hook(状态码非 249),程序会进入备用防御模式。


针对“看门狗+自爆”架构,最优雅的破解方式是让看门狗失效。
v27 = 0xA700000099LL; // 密钥低位 0x99, 高位 0xA7
v28 = 236; // 密钥 0xEC
// ...
*(_QWORD *)v15 = 0xF69F89FA8ECEF5LL; // 密文数据
v27 = 0xA700000099LL; // 密钥低位 0x99, 高位 0xA7
v28 = 236; // 密钥 0xEC
// ...
*(_QWORD *)v15 = 0xF69F89FA8ECEF5LL; // 密文数据
result = dlopen(v20, 2); // 加载 libc.so/libart.so
if ( result ) {
v25 = dlsym(v22, v21); // 获取 pthread_create 地址
v26 = ...; // v26 保存 pthread_create 指针
}
result = dlopen(v20, 2); // 加载 libc.so/libart.so
if ( result ) {
v25 = dlsym(v22, v21); // 获取 pthread_create 地址
v26 = ...; // v26 保存 pthread_create 指针
}
// a1: pthread_create (由上层传入)
// sub_1C544: 线程执行体 (看门狗)
// v20: PrettyMethod 的内存地址 (作为参数传递)
return a1(&thread_id, 0, sub_1C544, v20);
// a1: pthread_create (由上层传入)
// sub_1C544: 线程执行体 (看门狗)
// v20: PrettyMethod 的内存地址 (作为参数传递)
return a1(&thread_id, 0, sub_1C544, v20);
| Index |
原始字节 |
Key |
运算 (XOR) |
结果 (Hex) |
| 0 |
0x08 |
- |
Set to 0x08 |
08 |
| 1 |
0xA7 |
0xA7 |
A7 ^ A7 |
00 |
| 2 |
0x29 |
0xA9 |
29 ^ A9 |
80 |
| 3 |
0x4B |
0x99 |
4B ^ 99 |
D2 |
| 4 |
0xA6 |
0xA7 |
A6 ^ A7 |
01 |
| 5 |
0xA9 |
0xA9 |
A9 ^ A9 |
00 |
| 6 |
0x99 |
0x99 |
99 ^ 99 |
00 |
| 7 |
0x73 |
0xA7 |
73 ^ A7 |
D4 |
| 8 |
0x69 |
0xA9 |
69 ^ A9 |
C0 |
| 9 |
0x9A |
0x99 |
9A ^ 99 |
03 |
| 10 |
0xF8 |
0xA7 |
F8 ^ A7 |
5F |
| 11 |
0x7F |
0xA9 |
7F ^ A9 |
D6 |
| Hex |
汇编指令 |
含义 |
C8 0B 80 D2 |
MOV X8, #94 |
系统调用号 94 (__NR_exit_group) |
01 00 00 D4 |
SVC #0 |
Trap to Kernel (执行系统调用,强制退出了) |
C0 03 5F D6 |
RET |
之前强制退出了,所以此处为不可达代码 |
1 2 3 4 | v27 = 0xA700000099LL; // 密钥低位 0x99, 高位 0xA7
v28 = 236; // 密钥 0xEC
// ...
*(_QWORD *)v15 = 0xF69F89FA8ECEF5LL; // 密文数据
|
1 2 3 4 5 | result = dlopen(v20, 2); // 加载 libc.so/libart.so
if ( result ) {
v25 = dlsym(v22, v21); // 获取 pthread_create 地址
v26 = ...; // v26 保存 pthread_create 指针
}
|
1 2 3 4 | // a1: pthread_create (由上层传入)
// sub_1C544: 线程执行体 (看门狗)
// v20: PrettyMethod 的内存地址 (作为参数传递)
return a1(&thread_id, 0, sub_1C544, v20);
|
| Index |
原始字节 |
Key |
运算 (XOR) |
结果 (Hex) |
| 0 |
0x08 |
- |
Set to 0x08 |
08 |
| 1 |
0xA7 |
0xA7 |
A7 ^ A7 |
00 |
| 2 |
0x29 |
0xA9 |
29 ^ A9 |
80 |
| 3 |
0x4B |
0x99 |
4B ^ 99 |
D2 |
| 4 |
0xA6 |
0xA7 |
A6 ^ A7 |
01 |
| 5 |
0xA9 |
0xA9 |
A9 ^ A9 |
00 |
| 6 |
0x99 |
0x99 |
99 ^ 99 |
00 |
| 7 |
0x73 |
0xA7 |
73 ^ A7 |
D4 |
| 8 |
0x69 |
0xA9 |
69 ^ A9 |
C0 |
| 9 |
0x9A |
0x99 |
9A ^ 99 |
03 |
| 10 |
0xF8 |
0xA7 |
F8 ^ A7 |
5F |
| 11 |
0x7F |
0xA9 |
7F ^ A9 |
D6 |
| Hex |
汇编指令 |
含义 |
C8 0B 80 D2 |
MOV X8, #94 |
系统调用号 94 (__NR_exit_group) |
01 00 00 D4 |
SVC #0 |
Trap to Kernel (执行系统调用,强制退出了) |
C0 03 5F D6 |
RET |
之前强制退出了,所以此处为不可达代码 |
- 核心机制:利用 Frida 为了修复 ART Bug 而必须 Hook
art::ArtMethod::PrettyMethod 的特性,部署了一个内存完整性监控陷阱。
- 执行架构:
- 主线程 (
sub_1B924):负责环境清洗、反模拟器检测,并根据 Android 版本适配加载 ART 库。
- 监控线程 (
sub_1C544):通过 pthread_create 启动,在后台死循环运行,负责扫描文件系统、内存映射和核心函数完整性。
- 处决引擎 (
sub_26334):一个混淆的状态机函数。一旦检测到异常(Hook),动态解密 Shellcode 并执行 exit_group(0) 强制抹杀进程。
- 隐蔽性:全程无显式字符串(全部栈上解密),无直接系统调用(通过 Shellcode 完成),无常规 Crash 日志。
代码定位:
1 2 3 4 | v27 = 0xA700000099LL; // 密钥低位 0x99, 高位 0xA7
v28 = 236; // 密钥 0xEC
// ...
*(_QWORD *)v15 = 0xF69F89FA8ECEF5LL; // 密文数据
|
解密算法:
- 密钥序列 (Key): 循环使用
[0x99, 0xA7, 0xEC]。
- 操作:
Plaintext[i] = Ciphertext[i] ^ Key[i % 3]。
还原结果:
v20 (库名): "libart.so" (或 libc.so,视上下文,此处用于获取线程函数)。
v21 (函数名): "pthread_create"。
- 分析意义:这解释了后续
v26 函数指针的真实身份——它是线程创建函数,而非 Hook 函数。
- 密钥序列 (Key): 循环使用
[0x99, 0xA7, 0xEC]。
- 操作:
Plaintext[i] = Ciphertext[i] ^ Key[i % 3]。
- 分析意义:这解释了后续
v26 函数指针的真实身份——它是线程创建函数,而非 Hook 函数。
- 目的:防止在 ELF 的
Import Table (导入表) 中留下 pthread_create 的痕迹,对抗静态分析工具的交叉引用分析。
- 配置检查 (
sub_CAA8):检查全局变量 dword_48810 是否为 248 (0xF8)。这通常是 SDK 版本适配或功能开关。
- 硬件黑名单 (
sub_12D9C):
- 逻辑:读取
ro.product.model,检查是否包含字符串 "Firefly-RK3399"。
- 对抗意图:Firefly 开发板是安全研究员常用的低成本 ARM 逆向平台。代码检测到此环境直接跳过核心注入,导致分析人员在特定设备上无法复现恶意行为(“装死”)。
- 逻辑:读取
ro.product.model,检查是否包含字符串 "Firefly-RK3399"。
- 对抗意图:Firefly 开发板是安全研究员常用的低成本 ARM 逆向平台。代码检测到此环境直接跳过核心注入,导致分析人员在特定设备上无法复现恶意行为(“装死”)。
- 代码逻辑:
- 检查 SDK 版本 (
*off_47FB8)。
- SDK >= 24: 调用
sub_18D54("libart.so")。
sub_18D54 原理:这是一个手动 ELF 加载器。它读取 /proc/self/maps 找到 libart.so 的基址,然后手动解析 ELF Header、Program Header 和 Dynamic Segment,从而在不通过系统 Linker 的情况下查找符号。
- 检查 SDK 版本 (
*off_47FB8)。
- SDK >= 24: 调用
sub_18D54("libart.so")。
sub_18D54 原理:这是一个手动 ELF 加载器。它读取 /proc/self/maps 找到 libart.so 的基址,然后手动解析 ELF Header、Program Header 和 Dynamic Segment,从而在不通过系统 Linker 的情况下查找符号。
- 符号解密:函数内部解密出字符串
_ZN3art9ArtMethod12PrettyMethodEb。
- 符号含义:这是 ART 运行时函数
art::ArtMethod::PrettyMethod(bool) 的 C++ Mangled Name。
- 为什么选它?:
- Frida 的
frida-java-bridge 源码中包含 fixupArtQuickDeliverExceptionBug。(关于frida检测的一个新思路)
- 为了修复 Native 线程抛出异常时栈帧缺失导致的 Crash,Frida 必须 Hook 这个函数。
- 结论:攻击者利用了 Frida 的“刚需”,将其设为陷阱。
- Frida 的
frida-java-bridge 源码中包含 fixupArtQuickDeliverExceptionBug。(关于frida检测的一个新思路)
- 为了修复 Native 线程抛出异常时栈帧缺失导致的 Crash,Frida 必须 Hook 这个函数。
- 结论:攻击者利用了 Frida 的“刚需”,将其设为陷阱。
- 检测阶段:
- 读取
PrettyMethod 函数头部的 4 字节机器码。
- 比对特征值:
1476395088 (Hex: 0x58000050)。
- 特征含义:
0x58000050 对应 ARM64 指令 LDR X16, #8。这是 Inline Hook Trampoline 的标准起手式(将跳转地址加载到 X16,然后 BR X16)。(具体原因参考文章:关于frida检测的一个新思路)
- 分支判定:
- 如果等于
0x58000050(发现 Hook) -> 状态跳转至 887579370 (处决状态)。
- 如果不等(未发现 Hook) -> 状态跳转至安全退出。
- 读取
PrettyMethod 函数头部的 4 字节机器码。
- 比对特征值:
1476395088 (Hex: 0x58000050)。
- 特征含义:
0x58000050 对应 ARM64 指令 LDR X16, #8。这是 Inline Hook Trampoline 的标准起手式(将跳转地址加载到 X16,然后 BR X16)。(具体原因参考文章:关于frida检测的一个新思路)
- 分支判定:
- 如果等于
0x58000050(发现 Hook) -> 状态跳转至 887579370 (处决状态)。
- 如果不等(未发现 Hook) -> 状态跳转至安全退出。
- 如果等于
0x58000050(发现 Hook) -> 状态跳转至 887579370 (处决状态)。
- 如果不等(未发现 Hook) -> 状态跳转至安全退出。
- 基址:
0x30794
- 内存值:
99 00..., A7 00..., A9 00...
- 密钥序列:
0xA7, 0xA9, 0x99 (循环使用)
- 前4字节 (Little Endian):
0xD2800008
- 加数:
3008 (0xBC0)
- 运算:
0xD2800008 + 0x00000BC0 = 0xD2800BC8
- 修正后前4字节:
C8 0B 80 D2
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-12-4 09:12
被教教我吧~编辑
,原因: 上传附件