-
-
[原创]基于 LLVM Pass 的 iOS代码混淆方案--C/OC 字符串自动化混淆实现
-
发表于: 2小时前 59
-
项目地址:Cocoons
引言
在逆向工程中,strings 命令往往是破译 App 逻辑的第一把钥匙。硬编码的 API 密钥、后端地址、甚至业务逻辑提示,都赤裸裸地暴露在二进制文件的 __TEXT,__cstring 段中。
为了对抗静态分析,本文将分享一种基于 LLVM Pass 与 Mach-O 链接器魔术符号 实现的全局字符串混淆方案。
一、核心原理详解
1. 数据结构的“账本”设计
为了让解密函数知道去哪里解密,我们需要在编译期构建一个“元数据账本”。每一个加密的字符串都会对应一个结构体:
1 2 3 4 5 | struct CocoonsMetaTy { void *str_addr; // 字符串在内存中的实际地址 uint32_t length; // 包含 \0 的原始长度 uint8_t key; // 异或密钥}; |
这些结构体被统一放置在一个特殊的自定义段 __DATA,__cocoons_obs 中。
2. 链接器的“幻影符号”魔法
如何定位这个“账本”的起止点?在 macOS/iOS 的链接器 ld64 中,存在两个魔术符号:
sectionstart__DATA$__cocoons_obs
sectionend__DATA$__cocoons_obs
链接器在最终链接阶段会自动将这两个符号指向自定义段的首尾。通过在 LLVM IR 中声明这两个 External 符号,我们的解密函数就能精准地遍历所有元数据。
二、 LLVM Pass 实现细节
1. 穿透 Objective-C 字符串
OC 字符串(CFString)在 IR 中是一个结构体而非字节数组。Pass 需要识别 NSConstantString 类型,并“顺藤摸瓜”找到其内部指向 __cstring 的指针进行加密。
2. 内存安全与 \0 保护
在异或运算中,一个关键细节是:不能加密字符串末尾的 \0。
如果 \0 ^ key 变成了非零值,字符串将失去终止符,导致程序读取越界。我们在 IR 构建中通过 DecLen = StrLen - 1 并配合 CreateICmpSGT(有符号大于比较)来确保安全。
代码段
1 2 3 4 | ; 典型的解密循环 IR 片段%dec.len = sub i32 %full.len, 1%has.payload = icmp sgt i32 %dec.len, 0br i1 %has.payload, label %inner.xor, label %loop.next |
3. 多模块协同:LinkOnceODR
在大型项目中,多个 .o 文件会被混淆。为了防止解密逻辑重复执行(导致明文再次被加密回乱码),我们使用了 LinkOnceODR 结合全局锁(Guard Variable)。链接器会自动去重,确保全局只有一份解密器副本。
三、 隐蔽性强化
为了进一步提升对抗强度,我们采取了以下措施:
隐藏可见性:将解密函数设为 Hidden Visibility,确保它不会出现在动态符号表中。
段名伪装:通过 Pass 动态生成随机段名,不留痕迹。
DSO Local:优化调用开销,并防止符号被劫持。
四、 总结
通过这种方式实现的字符串混淆,不仅在性能和体积上达到了工业级平衡,更利用了 Mach-O 链接器的底层特性,使得代码更加优雅且难以被静态分析工具还原。
技术栈小结:
LLVM 17+ New Pass Manager
Mach-O Section Boundary Symbols
LinkOnceODR Linkage Policy
XOR Symmetric Encryption
结语
安全是一场持久的攻防战。字符串混淆只是第一步,未来我们还可以探索 控制流平坦化 (CFP) 和 指令替换 (Substitution) 等更高级的混淆技术。