简介
关于 autiasp 指令相关资料可以参考芦半山大佬的《Android Security | PAC和BTI机制杂谈》,这里就不在解答具体作用,本文仅解答与此指令相关的程序段错误问题,案例素材来源软件调试与分析群的群友提供(2024-08-13),当日简单了解下情况感觉挺有意思的案例,并且比较少见有人解答该类问题,正好适合写一份简单的介绍扫盲。
问题
群友发来的 tombstone 文件(已脱敏),发生以下 SIGILL 指令异常的错误,像这种问题,我们需要对 PC 寄存器的值进行解析,看下是否为一个正确有效的指令。
可以从 memory near pc 看到相关的机器码确实来自 libart.so 的代码段地址。机器码转义的办法很多,可以参考《如何理解Native Crash问题》,也可以用这个网站 godbolt.org 的转义功能。
翻译成汇编代码后,可以看到在 autiasp 指令上发生指令异常。
指令异常解答
autiasp 指令会对 x30 寄存器的值做校验,当前的 LR = b4000071166da9c0,校验失败,因此主动抛出 SIGILL 指令异常错误。相信对 b4 这个值了解的同学,一眼就知道这应该是某个 malloc 申请指针地址,因此当前 LR 寄存器必然不是某个代码段的地址,校验失败是必然的。
污染 LR 寄存器实验
在真实的 Arm64 支持 PAC 机器上模拟该错误,将 core-parser 推到手机上后。
# ./data/core-parser -p `pidof system_server`
...
可以看到函数 android::Looper::pollInner(int) 使用了 paciasp 对 LR 寄存器进行了加密处理,然后将结果压栈到栈顶处。
我们可以看到进程的该 FP = 0x7fc7e9ef40 处内存。
LR = 00381bf962076924 就是 paciasp 对 0x7962076924 加密后的结果。
于是我们仅去掉 PAC 部分对原进程栈内存进行修改。
很快系统就发生了 Native Crash 重启,并且也是 autiasp 指令异常错误。
原问题剖析
有看我之前发的文章里,也有类似的堆栈打印不全的问题,此类情况都需自行对栈内存进行分析取证。我们可以从 memory near sp 内容着手分析,SP = 0000006e904d43c0 正好处于线程栈上,因此也符合栈地址合法性,于是我们可以就 tombstone 文件内容继续栈恢复尝试。
0000006e'903dd000-0000006e'904d7fff rw- 0 fb000 [anon:stack_and_tls:26882]
栈回溯
可以看到 PC = 0000006ff28580b0 地址位于 libart.so 代码段上,而在 android arm64 中系统默认开启 -fno-omit-frame-pointer,或者从翻译后的机器码中得知开启防止 FP 被优化,因此在栈内存中必然存在带有 FP 堆栈特征的内容。
0x6ff28580ac: a8c17bfd | ldp x29, x30, [sp], #0x10
0x6ff28580b0: d50323bf | autiasp
从此处汇编代码可知,当函数在退栈前 FP = SP = 0000006e904d43b0,正确情况下 FP + 0x8 处存放的 Caller LR 应该是某个 PAC LR 的值,但目前存放的是 b4000071166da9c0 因此才会发生 PAC 校验失败。
观察栈内存分布,不难发现满足 FP 堆栈特征的有如下结果:
| FP | Caller FP | Caller LR | NoPAC LR | Location |
|---|---|---|---|---|
| 0000006e904d43a0 | 0000006e904d4420 | 004370eff28a9524 | 0000006ff28a9524 | /apex/com.android.art/lib64/libart.so |
| 0000006e904d4420 | 0000006e904d4480 | 0072386fd505a8f0 | 0000006fd505a8f0 | /system/lib64/libaudioeffect_jni.so |
| 0000006e904d4480 | 0000006e904d4af0 | 002139efd505bf7c | 0000006fd505bf7c | /system/lib64/libaudioeffect_jni.so |
小插曲 NoPAC_LR = Caller_LR & 0x7F'FFFF'FFFF (大部分手机 Arm64 的 vabits = 39)
可以看到满足的 FP 堆栈特征的栈地址是 0000006e904d43a0 而不是 0000006e904d43b0,会是栈寄存器异常或是被其它地方修改了?当然这里不能直接下结论,仍需对栈大小,栈存放数据以及寄存器进行取证。由于群友仅提供了 tombstone 文件,实际上对于纯 tombstone 内容分析能做到的事情已经结束无法往下,取证需获得 libart.so 以及 libaudioeffect_jni.so 原文件。
取证过程
于是向群友索求这个两个原文件,万万没想到群友竟然提供带了符号的库文件过来,实际上对于高手而言(虽然我不是)是否带符号并不是太过于重要,当然有符号可以节省许多分析时间。为了节省分析取证时间使用 tomb2core 将转换成 core 文件后在 gdb、core-parser 上进行分析取证。
原理参考《 基于安卓墓碑文件制作 FakeCore 原理》
对 tombstone 文件的 FP,SP,PC,#1 内容进行第一次修改制作一个 core 文件。
| 目标 | 内容 | 作用 |
|---|---|---|
| FP | 0000006e904d43a0 | 回到#0退栈前 |
| SP | 0000006e904d43a0 | 回到#0退栈前 |
| PC | 0000006ff28580ac | 回到#0退栈前 |
| #1 | /system/lib64/libaudioeffect_jni.so | Fakecore link_map 依赖 |
栈恢复后,不难发现一点,art::Thread 地址并非正确地址,说明 #0 的栈可能存在异常,实际上是因为函数已运行结束,此时x0寄存器存放的是函数的返回值art::mirror::Object *地址,对恢复后堆栈其它变量,以及栈大小进行取证。
栈大小取证
由于 core-parser 目前的栈回溯是纯 FP 强制回溯,#1 为 LR 帧在当前堆栈里可忽略不计。
在 core-parser 的 #0 处解析栈大小,从汇编代码可知 FP = SP,SIZE = 0x10。
在 core-parser 的 #2 处解析栈大小,从汇编代码可知 FP = SP + 0x60, SIZE = 0xC0。
在 core-parser 的 #3 处解析栈大小,从汇编代码可知 FP = SP, SIZE = 0x60。
在 core-parser 的 #4 处解析栈大小,从汇编代码可知 FP = SP, SIZE = 0x50。
于是我们可以对栈内存进行划分边界如下图:
从栈内存分布上看,原 #0 SP = 0000006e904d43a0 并不合理,栈地址合法值仍是 0000006e904d43b0,但是 0000006e904d43b0 压栈的内容并不正确。
数据取证
前面的栈大小取证基本可说明 art::JNI<false>::GetArrayLength 的栈是可靠的。于是我们对 GDB #4~#7 数据进行取证。
由于 tombstone 文件并未记录有 0xb40000712648aca0 相关内存,因此无法确定它内容的真实性。从函数调用角度看是可靠的。
callbackInfo 完全符合数据结构 visualizer_callback_cookie 的内存结构。
数据分析
从 栈回溯 以及 取证过程 中得知一个信息是,退栈前合法地址 0000006e904d43b0,并且 art::JNI<false>::GetArrayLength 的栈内容应该是可靠的,于是我们从此处人工推导后续过程。
core-parser> map
NUM LINKMAP REGION FLAGS NAME
1 0x2002000 [2000000, 2001000) rw- fake [*]
2 0x2002028 [6ff2400000, 6ff2565000) r-- /apex/com.android.art/lib64/libart.so [*](MMAP)
3 0x2002050 [6fd504c000, 6fd5054000) r-- /system/lib64/libaudioeffect_jni.so [*](MMAP)
可以看到此时 #2 跳转的下一个函数应该是 art::Thread::DecodeGlobalJObject 并且它的栈大小正好是 0x20, x19 = 000000000002c20e 压栈到 SP+0x10 正好也是 fft_data,完全符合。那为什么会出现 PC 是函数 art::JavaVMExt::DecodeGlobal,注意到地址 0x6ff2a95f78。
0x6ff2a95f6c: f9400bf3 | ldr x19, [sp, #0x10]
0x6ff2a95f70: a8c27bfd | ldp x29, x30, [sp], #0x20
0x6ff2a95f74: d50323bf | autiasp
0x6ff2a95f78: 17f70839 | b 0x6ff285805c
程序运行到 0x6ff2a95f78 处,正好函数退栈后直接跳入 art::JavaVMExt::DecodeGlobal,那么问题来了!!
0x6ff2858060: a9bf7bfd | stp x29, x30, [sp, #-0x10]!
程序运行到 0x6ff2858060 处,会将 art::Thread::DecodeGlobalJObject 压栈过的内容再次入栈,此时的 0000006e904d43a0 与 0000006e904d43b0 的内容除了 PAC 值不同外,其它应该完全一致。
这里我们核实下 x0 = 1baf9dc8 是否为一个大小为 128 的数组对象,此处也说明了 DecodeGlobal 正确的返回了全局引用对象。
从 art::Thread::DecodeGlobalJObject 退栈后的 x30 寄存器至少是满足 PAC 校验的,否则本次报错的函数就不是 art::JavaVMExt::DecodeGlobal 而是它了。然而却在函数 6ff2858060 处,压栈的 x29 x30 非正确值。
现在的栈内存就像是指令 stp x29, x30, [sp, #-0x10]! 等效于 sub sp, sp, #0x10 一样,但比较可惜只有 tombstone 文件,PC 附近的内存保存有效,无法得知内存中的具体指令情况。于是我在真机上模拟类似场景。
将指令修改成 sub sp, sp, #0x10 就能出现类似的结果,但具体原因应该不是这样发生的。
当然也可能是存在某个函数将原本正确压栈的内容破坏了,我们结合最后的寄存器信息继续分析函数art::JavaVMExt::DecodeGlobal 的运行逻辑。注意到 0x6ff2858084 ~ 0x6ff28580a8 存在一段分支逻辑代码。
0x6ff2858084: 7100011f | cmp w8, #0
0x6ff2858088: 7a401924 | ccmp w9, #0, #4, ne
0x6ff285808c: b9400540 | ldr w0, [x10, #4]
0x6ff2858090: 540000e0 | b.eq 0x6ff28580ac
0x6ff2858094: d53bd048 | mrs x8, TPIDR_EL0
0x6ff2858098: f9401d08 | ldr x8, [x8, #0x38]
0x6ff285809c: b4000088 | cbz x8, 0x6ff28580ac
0x6ff28580a0: b9402508 | ldr w8, [x8, #0x24]
0x6ff28580a4: 34000048 | cbz w8, 0x6ff28580ac
0x6ff28580a8: 97f7de48 | bl 0x6ff264f9c8 <<<<----
假设程序进入函数 0x6ff264f9c8, 大概率会导致 x8 x9 寄存器被污染无法准确的判断是否进入了该函数。该段汇编代码实际对应的是函数 art::ReadBarrier::BarrierForRoot,为了方便大家阅读。对 tombstone 文件的 FP,SP,PC,#1 内容进行第二次修改制作另一个 core 文件。
这里让 0000006e904d43b0 与 0000006e904d43a0 内存一致。
| 目标 | 内容 | 作用 |
|---|---|---|
| FP | 0000006e904d43b0 | 回到#0退栈前 |
| SP | 0000006e904d43b0 | 回到#0退栈前 |
| PC | 0000006ff2858068 | 回到#0退栈前 |
| #1 | /system/lib64/libaudioeffect_jni.so | Fakecore link_map 依赖 |
gUseReadBarrier 与 with_read_barrier 一般来说都是 true 假设这里会进第一个逻辑分支。
(gdb) p 'art::kUseBakerReadBarrier'
$64 = true
因此该函数将会在 173 行处返回结果。至于 art::Thread 的地址,我们可以通过特征值寻找,当前线程 Tid = 26882 = 00006902,因此我们可以在 tombstone 中搜索 00006902
因此
art::Thread 地址应该是 00000071a66f2830,存在的内存内容与堆栈前面的数据是吻合的。
后记
tombstone 文件信息有限,难以完整的剖析内容,问题仍存在许多的疑点,需群友继续努力争取抓到对应的 core 文件或者通过问题的高发时间段排查是否与贵司的合入有关。