77、iOS逆向(六):iOS 14+新特性、指针认证(PAC)、代码签名
好,咱们继续聊iOS逆向。到了iOS 14之后,苹果在安全上又加了不少硬菜。说实话,我第一次打开iOS 14的设备做逆向时,心里就咯噔一下——这玩意儿跟以前不太一样了。
今天咱们重点聊三个东西:iOS 14+的新特性、指针认证(PAC)、还有代码签名。这三者其实是环环相扣的,你搞懂了一个,另外两个也就顺了。
一、iOS 14+ 安全新特性概览
iOS 14 引入了一些让我印象深刻的改动。我个人习惯把新特性分成两类:一类是“看得见的”,比如App Clips、小组件;另一类是“看不见的”,也就是底层安全加固。咱们逆向工程师,当然更关心后者。
| 特性 | 影响 | 逆向难度 |
|---|---|---|
| 指针认证(PAC) | 函数指针、返回地址被签名 | ★★★★★ |
| 代码签名强化 | 运行时签名验证更严格 | ★★★★☆ |
| 内核完整性保护 | 内核代码段只读 | ★★★★★ |
| App沙箱增强 | 文件系统访问更受限 | ★★★☆☆ |
嗯,这里要特别提一下PAC。我在项目中遇到过好几次,明明hook点找对了,但一运行就崩。后来才发现,是PAC在作怪。
二、指针认证(PAC)—— 苹果的“防篡改锁”
PAC,全称Pointer Authentication Code。说白了,就是给指针加了个“签名”。
你想想看,传统的漏洞利用,很多都是通过篡改函数指针或者返回地址来劫持控制流的。PAC的做法是:在指针的高位(通常是高16位或高24位)嵌入一个认证码。这个认证码是用一个密钥和上下文算出来的。如果你改了指针,认证码就对不上了,CPU直接触发异常。
2.1 PAC 的工作原理
PAC 的核心是 ARMv8.3 引入的几条指令:
PACIA/PACIB:对指令地址进行签名PACDA/PACDB:对数据地址进行签名AUTIA/AUTIB:验证指令地址签名XPACLRI:清除PAC码
举个例子,一个函数返回时,原本是:
ret ; 直接返回,返回地址在LR寄存器
有了PAC之后,变成了:
autibsp ; 验证LR中的PAC码
ret ; 验证通过才返回
如果LR被篡改了,PAC验证失败,CPU会跳转到异常处理程序。嗯,说白了就是直接崩掉,不给攻击者任何机会。
核心要点:PAC不是加密,而是认证。它不隐藏指针的值,只是确保指针没有被篡改。
2.2 逆向中如何应对PAC
说实话,PAC对逆向的影响很大。以前我们hook函数,直接改GOT表或者Inline Hook就行。现在呢?函数指针被签名了,你改了指针,PAC验证就失败。
我曾经在一个项目中,想hook一个系统库的函数。用Frida试了好几次,一调用就崩。后来发现,那个函数指针被PAC保护了。
那怎么办?有几种思路:
- 绕过PAC:在PAC验证之前或之后修改指针。比如在函数入口处直接修改寄存器。
- 伪造PAC:如果你知道密钥(通常只有内核知道),可以自己计算PAC码。但这基本不可能。
- 禁用PAC:在越狱设备上,可以通过内核补丁禁用PAC。但iOS 14+的越狱越来越难了。
小技巧:在调试时,可以用 lldb 的 pacia 命令手动计算PAC码。但记住,这只是调试用,实际运行时密钥不同。
三、代码签名 —— 苹果的“身份证”
代码签名,说白了就是给每个可执行文件、dylib、framework发一张“身份证”。iOS系统在加载代码时,会检查这张身份证是否有效。
iOS 14+ 对代码签名做了强化:
- 签名验证更频繁:不仅在加载时验证,运行时也会定期检查。
- 签名范围更广:连JIT编译的代码也要签名。
- 签名强度更高:使用SHA256哈希,不再支持SHA1。
3.1 代码签名的结构
一个Mach-O文件的签名信息存储在 LC_CODE_SIGNATURE 加载命令中。结构大致如下:
SuperBlob {
magic: "fairsign" 或 "fairplay"
count: 2
blobs: [
CodeDirectory { // 代码目录
version: 0x20400
hashType: SHA256
hashSize: 32
hashes: [...] // 每个页面的哈希
},
Entitlements { // 权限
data: "..."
}
]
}
嗯,这里要注意,CodeDirectory 里包含了每个代码页面的哈希值。系统在加载时,会逐页验证这些哈希。如果你改了代码,哈希就对不上了。
3.2 逆向中的代码签名绕过
在越狱设备上,我们通常用 ldid 或 jtool 来重新签名。但iOS 14+ 之后,有些签名验证是在内核里做的,光重新签名可能不够。
我记得有一次,我修改了一个系统dylib,重新签名后还是加载失败。后来发现,iOS 14 引入了 amfi(Apple Mobile File Integrity)的新策略,连已经签名的dylib也要验证完整性。
解决办法?要么打内核补丁禁用amfi,要么用 CoreTrust 漏洞(如果有的话)。
警告:不要在生产设备上尝试禁用代码签名。这会导致设备变砖,而且无法恢复。
四、PAC与代码签名的关系
这两个东西其实是配合使用的。代码签名保证了代码的静态完整性,PAC保证了运行时的控制流完整性。
我画了一张图,帮你理清它们的关系:
从图上可以看得很清楚:代码签名是“静态锁”,PAC是“动态锁”。你只解开一个,另一个还是会把你锁住。
五、实战:在iOS 14+上调试PAC保护的代码
好了,理论说完了,咱们来点实际的。假设你有一个iOS 14+的设备,想调试一个系统库函数,但被PAC保护了。怎么办?
我个人习惯用 lldb 配合 debugserver 来做。步骤如下:
- 找到目标函数地址:用
image lookup -n或nm命令。 - 检查是否有PAC保护:看反汇编中是否有
autibsp、pacibsp等指令。 - 在PAC验证前打断点:在
pacibsp之前打断点,此时指针还未被签名。 - 修改指针:在断点处修改寄存器值,然后跳过PAC验证指令。
举个例子:
(lldb) disassemble -n _some_function
...
0x1a2b3c00: pacibsp ; 对LR签名
0x1a2b3c04: stp x29, x30, [sp, #-0x10]!
0x1a2b3c08: ...
...
0x1a2b3c50: autibsp ; 验证LR签名
0x1a2b3c54: ret ; 返回
(lldb) breakpoint set -a 0x1a2b3c00
(lldb) breakpoint set -a 0x1a2b3c50
; 在第一个断点处,修改x30(LR)
(lldb) register write x30 0xdeadbeef
; 然后跳过pacibsp指令
(lldb) jump -a 0x1a2b3c04
注意:跳过PAC验证指令可能会导致后续的 autibsp 验证失败。所以最好在 autibsp 之前也做处理,或者直接跳过整个验证流程。
嗯,说实话,这种方法只适用于调试场景。在实际的越狱工具中,通常会用更底层的方法,比如内核补丁来禁用PAC。
六、总结
iOS 14+ 的安全机制,说白了就是“层层加码”。代码签名管静态,PAC管动态,两者配合,让逆向的难度上了一个台阶。
但别怕。理解原理之后,总有办法绕过去。我见过太多人一看到PAC就放弃了,其实只要你静下心来分析,找到PAC验证的时机和位置,还是可以hook的。
最后送大家一句话:逆向的本质不是对抗,而是理解。你理解了苹果的设计思路,自然就知道怎么应对了。
公众号:蓝海资料掘金营,微信deep3321