简介
关于 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
文件或者通过问题的高发时间段排查是否与贵司的合入有关。