点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新
原书为 iOS Crash Dump Analysis Book,已得作者授权,欢迎 star
Pointer Authentication(指针验证机制)
在本章中,我们将研究指针验证机制以及相关的崩溃。
使用 Apple A12 芯片或更高版本的设备将称为 Pointer Authentication 的安全功能作为 ARMv8.3-A 体系结构的一部分。例如, iPhone XS,iPhone XS Max, 和 iPhone XR 使用 A12芯片。其基本思想是在 64 位指针中存在未使用的位,因为它已经可以寻址相当广泛的地址,因此只有 40 位被分配给这样的目的。 @iospac 因此,剩余的位可用于存储在将预期的指针地址与上下文值和密钥组合后计算出的哈希值。 然后,如果由于错误或恶意操作而要更改指针地址,则该指针将被认为是无效的,并且如果将其用于更改程序的控制流,则最终将导致 SIGSEGV。
实际上,在许多情况下都使用了指针验证机制,例如确保 C++ 虚拟调度表未被篡改。 但是,我们只看一个错误操作跳转地址的简单情况。因此,我们只看一个错误操作跳转地址的简单情况。
配置指针验证机制
在使用 A12 或更高版本的设备,Apple 在设备内核中启用了指针验证机制。对于用户空间代码,指针身份验证是一项可选功能。可以在项目的构建设置中来启用它,如图为 icdab_ptr
示例 Enable Pointer Authentication(启用指针验证机制)。我们将架构 arm64e
添加到架构设置中。
如果我们正在编写对安全性敏感的软件,那么值得尽早的采用此功能。
操纵指针
让我们考虑下面的程序,该程序以人工方式操纵指针,以揭示指针验证机制背后的一些思想。
#import "ViewController.h"
#include "ptrauth.h"
@interface ViewController ()
@end
@implementation ViewController
typedef void(*ptrFn)(void);
static void interestingJumpToFunc(void) {
NSLog(@"Simple interestingJumpToFunc\n");
}
// this function's address is where we will be jumping to
static void nextInterestingJumpToFunc(void) {
NSLog(@"Simple nextInterestingJumpToFunc\n");
}
- (void)viewDidLoad {
[super viewDidLoad];
ptrFn result = [self generatePtrToFn];
NSLog(@"ptrFn result is %p\n", result);
result(); // will crash; deferences a pointer with a bad PAC
}
- (ptrFn)generatePtrToFn {
uintptr_t a1 = (uintptr_t)interestingJumpToFunc;
uintptr_t a2 = (uintptr_t)nextInterestingJumpToFunc;
NSLog(@"ptr addresses as uintptr_t are 0x%lx 0x%lx\n",
a1, a2);
ptrdiff_t delta = a2 - a1;
ptrdiff_t clean_delta =
ptrauth_strip(&nextInterestingJumpToFunc,
ptrauth_key_asia) -
ptrauth_strip(&interestingJumpToFunc,
ptrauth_key_asia);
NSLog(@"delta is 0x%lx clean_delta is 0x%tx\n", delta,
clean_delta);
ptrFn func = interestingJumpToFunc;
func += clean_delta; // correct offset but neglects PAC
component
return func;
}
如果运行上述程序,则会崩溃。 该程序的日志输出为:
ptr addresses as uintptr_t are 0x2946180102c55dd8
0x22b810102c55df8
delta is 0xd8e5690000000020 clean_delta is 0x20
ptrFn result is 0x2946180102c55df8
我们看到,当获得指向函数interestingJumpToFunc
的指针,然后将其存储在足以容纳指针地址的整数中时,我们将获得一个较大的值0x2946180102c55dd8
。 这是因为地址的前24位是指针验证码(PAC)。 在这种情况下,PAC 为 0x294618
,有效指针为0x0102c55dd8
。
在物理上相邻的下一个指针 nextInterestingJumpToFunc
是 0x22b810102c55df8
; 显然,它具有相似的有效地址0x0102c55df8
,但具有完全不同的 PAC 0x22b81
。
当我们计算指针之间的增量时,由于指针值的 PAC 部分,我们显然会得到一个无意义的地址。 为了正确计算指针的有效地址之间的增量,我们需要使用 ptrauth_strip
实用程序函数。 这是作为内置宏汇编指令实现的。
经过宏预处理后,代码为:
ptrdiff_t clean_delta =
__builtin_ptrauth_strip(&nextInterestingJumpToFunc,
ptrauth_key_asia) -
__builtin_ptrauth_strip(&interestingJumpToFunc,
ptrauth_key_asia);
生成的装配说明的格式为:
xpaci x9
当使用 __builtin_ptrauth_strip
剥离功能时。 这将从寄存器(在本例中为x9
)中删除PAC。
使用带状函数的好处是我们能够正确确定感兴趣的两个函数之间的距离。它是 0x20
。 我们的函数 generatePtrToFn
实际上只是将delta加到interestingJumpToFunc
的地址上来计算 nextInterestingJumpToFunc
的地址,但是这样做是错误的。 它将PAC在其计算出的地址中留给interestingJumpToFunc
。
请注意,这类指针操作均不会导致崩溃。 检查指针的时间是用于更改链接寄存器\index{register!link} 的时间。 也就,当我们根据指针值来更改程序的控制流时。
在函数 viewDidLoad()
我们存在以下代码
result(); // will crash; deferences a pointer with a bad PAC
使用的指针是我们的错误指针0x2946180102c55df8
。 汇编指令检测到错误的指针
blraaz x8
这是具有指向注册的链接的分支,具有使用指令密钥 A 的指针身份验证。
请注意,当我们剥离指针时,我们使用了ptrauth_key_asia
来删除指令密钥A。但是,我们无法访问执行相反操作所需的特殊值,而是使用带有适当的 salt 的指令密钥A来对指针进行签名值以获取上下文中正确的带符号指针。
现在,我们已经检查了错误的代码,让我们看看崩溃时会发生什么。
icdab_ptr
PAC 崩溃
在具有 A13 仿生芯片的 iPhone 11上,运行icdab_ptr
程序时会得到以下崩溃信息。
Incident Identifier: DA9FE0C7-6127-4660-A214-5DF5D432DBD9
CrashReporter Key: d3e622273dd1296e8599964c99f70e07d25c8ddc
Hardware Model: iPhone12,1
Process: icdab_ptr [2125]
Path:
/private/var/containers/Bundle/Application/32E9356D-AF19-4F30-BB
87-E4C056468063/icdab_ptr.app/icdab_ptr
Identifier: perivalebluebell.com.icdab-ptr
Version: 1 (1.0)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: perivalebluebell.com.icdab-ptr [1288]
Date/Time: 2020-10-14 23:09:20.9645 +0100
Launch Time: 2020-10-14 23:09:20.6958 +0100
OS Version: iPhone OS 14.2 (18B5061e)
Release Type: Beta
Baseband Version: 2.02.00
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x2000000100ae9df8 ->
0x0000000100ae9df8 (possible pointer authentication failure)
VM Region Info: 0x100ae9df8 is in 0x100ae4000-0x100aec000; bytes
after start: 24056 bytes before end: 8711
REGION TYPE START - END [ VSIZE]
PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
---> __TEXT 100ae4000-100aec000 [ 32K]
r-x/r-x SM=COW ...app/icdab_ptr
__DATA_CONST 100aec000-100af0000 [ 16K]
r--/rw- SM=COW ...app/icdab_ptr
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [2125]
Triggered by Thread: 0
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 icdab_ptr 0x0000000100ae9df8
nextInterestingJumpToFunc + 24056 (ViewController.m:26)
1 icdab_ptr 0x0000000100ae9cf0
-[ViewController viewDidLoad] + 23792 (ViewController.m:35)
2 UIKitCore 0x00000001aca4cda0
-[UIViewController
_sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 108
3 UIKitCore 0x00000001aca515fc
-[UIViewController loadViewIfRequired] + 956
4 UIKitCore 0x00000001aca519c0
-[UIViewController view] + 32
.
.
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x0000000103809718 x1: 0x0000000103809718 x2:
0x0000000000000000 x3: 0x00000000000036c8
x4: 0x00000000000062dc x5: 0x000000016f319b20 x6:
0x0000000000000031 x7: 0x0000000000000700
x8: 0x045d340100ae9df8 x9: 0x786bdebd0a110081 x10:
0x0000000103809718 x11: 0x00000000000007fd
x12: 0x0000000000000001 x13: 0x00000000d14208c1 x14:
0x00000000d1621000 x15: 0x0000000000000042
x16: 0xe16d9e01bf21b57c x17: 0x00000002057e0758 x18:
0x0000000000000000 x19: 0x0000000102109620
x20: 0x0000000000000000 x21: 0x0000000209717000 x22:
0x00000001f615ffcb x23: 0x0000000000000001
x24: 0x0000000000000001 x25: 0x00000001ffac7000 x26:
0x0000000282c599a0 x27: 0x00000002057a44a8
x28: 0x00000001f5cfa024 fp: 0x000000016f319dd0 lr:
0x0000000100ae9cf0
sp: 0x000000016f319da0 pc: 0x0000000100ae9df8 cpsr:
0x60000000
esr: 0x82000004 (Instruction Abort) Translation fault
Binary Images:
0x100ae4000 - 0x100aebfff icdab_ptr arm64e
<83e44566e30039258fd14db647344501>
/var/containers/Bundle/Application/32E9356D-AF19-4F30-BB87-E4C05
6468063/icdab_ptr.app/icdab_ptr
首先,我们注意到的点是崩溃发生在 ViewController.m:26
0 icdab_ptr 0x0000000100ae9df8
nextInterestingJumpToFunc + 24056 (ViewController.m:26)
在我们的源代码里有:
24 // this function's address is where we will be jumping to
25 static void nextInterestingJumpToFunc(void) {
26 NSLog(@"Simple nextInterestingJumpToFunc\n");
27 }
这个程序的目的是通过指针运算来计算nextInterestingJumpToFunc
函数的地址,然后跳转到它。它成功地做到了,然后崩溃了。从上一节我们知道这是因为我们故意使用了从 interestingJumpToFunc
函数借用的函数地址 PAC。
崩溃报告系统会对指针进行拆解,以推导出所给的错误指针的有效指针地址。 我们有:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x2000000100ae9df8 ->
0x0000000100ae9df8 (possible pointer authentication failure)
VM Region Info: 0x100ae9df8 is in 0x100ae4000-0x100aec000; bytes
after start: 24056 bytes before end: 8711
REGION TYPE START - END [ VSIZE]
PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
---> __TEXT 100ae4000-100aec000 [ 32K]
r-x/r-x SM=COW ...app/icdab_ptr
__DATA_CONST 100aec000-100af0000 [ 16K]
r--/rw- SM=COW ...app/icdab_ptr
我们的指针 0x2000000100ae9df8
指向程序的文本区域 0x0000000100ae9df8
,但是指针的高 24 位不正确,因此显示了消息 (可能的指针身份验证失败)
,并导致了SIGSEGV
。 注意,PAC是一个特殊值 0x200000
,大概是代表 无效PAC
的值。
从上一节中,我们知道可以通过下面的代码检查程序中 PAC:
blraaz x8
我们的 x8
寄存器是 0x045d340100ae9df8
,所以估计出错误的 PAC 是 0x045d34
。
指针验证机制调试技巧
在本节中,我们将指出在架构构 armv8e
目标上运行调试器时的一些区别。我们还将展示如何将崩溃报告与调试会话匹配。
当我们打印出指针时,我们得到了去掉 PAC 值的指针。例如,对于指针0x36f93010201ddf8
,我们的 result
变量,我们会得到:
(lldb) po result
(actual=0x000000010201ddf8 icdab_ptr`nextInterestingJumpToFunc at
ViewController.m:25)
该值来自产生以下输出的执行
ptr addresses as uintptr_t are 0x36f93010201ddd8
0xc7777b010201ddf8
delta is 0xc407e80000000020 clean_delta is 0x20
ptrFn result is 0x36f93010201ddf8
通过调试器进行连接时,看不到崩溃分析报告。 但是,如果我们分离调试器:
(lldb) detach
系统将继续运行,并出发崩溃,然后生成报告。
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x200000010201ddf8 ->
0x000000010201ddf8 (possible pointer authentication failure)
VM Region Info: 0x10201ddf8 is in 0x10201c000-0x102020000; bytes
after start: 7672 bytes before end: 8711
REGION TYPE START - END [ VSIZE]
PRT/MAX SHRMOD REGION DETAIL
__TEXT 102018000-10201c000 [ 16K]
r-x/r-x SM=COW ...app/icdab_ptr
---> __TEXT 10201c000-102020000 [ 16K]
r-x/rwx SM=COW ...app/icdab_ptr
__DATA_CONST 102020000-102024000 [ 16K]
r--/rw- SM=COW ...app/icdab_ptr
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [2477]
Triggered by Thread: 0
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 icdab_ptr 0x000000010201ddf8
nextInterestingJumpToFunc + 24056 (ViewController.m:25)
1 icdab_ptr 0x000000010201dcf0
-[ViewController viewDidLoad] + 23792 (ViewController.m:34)
2 UIKitCore 0x00000001aca4cda0
-[UIViewController
_sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 108
3 UIKitCore 0x00000001aca515fc
-[UIViewController loadViewIfRequired] + 956
4 UIKitCore 0x00000001aca519c0
-[UIViewController view] + 32
.
.
Thread 0 crashed with ARM Thread State (64-bit):
x0: 0x000000010c80a738 x1: 0x000000010c80a738 x2:
0x000000000000000d x3: 0x0000000000000000
x4: 0x000000016dde59b8 x5: 0x0000000000000040 x6:
0x0000000000000033 x7: 0x0000000000000800
x8: 0x036f93010201ddf8 x9: 0xb179df14ab2900d1 x10:
0x000000010c80a738 x11: 0x00000000000007fd
x12: 0x0000000000000001 x13: 0x00000000b3e33897 x14:
0x00000000b4034000 x15: 0x0000000000000068
x16: 0x582bcd01bf21b57c x17: 0x00000002057e0758 x18:
0x0000000000000000 x19: 0x000000010be0d760
x20: 0x0000000000000000 x21: 0x0000000209717000 x22:
0x00000001f615ffcb x23: 0x0000000000000001
x24: 0x0000000000000001 x25: 0x00000001ffac7000 x26:
0x0000000280436140 x27: 0x00000002057a44a8
x28: 0x00000001f5cfa024 fp: 0x000000016dde5b60 lr:
0x000000010201dcf0
sp: 0x000000016dde5b30 pc: 0x000000010201ddf8 cpsr:
0x60000000
esr: 0x82000004 (Instruction Abort) Translation fault
这种方法很方便,因为这样我们就可以将故障转储报告与调试器中的分析直接关联起来。 请注意,x8
寄存器恰好是我们先前探讨的 result
值。
感谢你阅读本文! 🚀