关于 autiasp 指令 Native Crash 介绍

763 阅读12分钟

简介

关于 autiasp 指令相关资料可以参考芦半山大佬的《Android Security | PAC和BTI机制杂谈》,这里就不在解答具体作用,本文仅解答与此指令相关的程序段错误问题,案例素材来源软件调试与分析群的群友提供(2024-08-13),当日简单了解下情况感觉挺有意思的案例,并且比较少见有人解答该类问题,正好适合写一份简单的介绍扫盲。

问题

群友发来的 tombstone 文件(已脱敏),发生以下 SIGILL 指令异常的错误,像这种问题,我们需要对 PC 寄存器的值进行解析,看下是否为一个正确有效的指令。

Screenshot from 2024-08-13 21-05-47.png

可以从 memory near pc 看到相关的机器码确实来自 libart.so 的代码段地址。机器码转义的办法很多,可以参考《如何理解Native Crash问题》,也可以用这个网站 godbolt.org 的转义功能。

Screenshot from 2024-08-13 21-07-55.png

翻译成汇编代码后,可以看到在 autiasp 指令上发生指令异常。

Screenshot from 2024-08-13 21-07-36.png

指令异常解答

autiasp 指令会对 x30 寄存器的值做校验,当前的 LR = b4000071166da9c0,校验失败,因此主动抛出 SIGILL 指令异常错误。相信对 b4 这个值了解的同学,一眼就知道这应该是某个 malloc 申请指针地址,因此当前 LR 寄存器必然不是某个代码段的地址,校验失败是必然的。

Screenshot from 2024-08-13 21-28-25.png

污染 LR 寄存器实验

在真实的 Arm64 支持 PAC 机器上模拟该错误,将 core-parser 推到手机上后。

# ./data/core-parser -p `pidof system_server`
...

Screenshot from 2024-08-13 21-47-29.png

Screenshot from 2024-08-13 21-48-10.png

可以看到函数 android::Looper::pollInner(int) 使用了 paciaspLR 寄存器进行了加密处理,然后将结果压栈到栈顶处。

Screenshot from 2024-08-13 21-48-58.png

我们可以看到进程的该 FP = 0x7fc7e9ef40 处内存。

Screenshot from 2024-08-13 21-49-43.png

LR = 00381bf962076924 就是 paciasp0x7962076924 加密后的结果。 于是我们仅去掉 PAC 部分对原进程栈内存进行修改。

Screenshot from 2024-08-13 21-50-11.png

很快系统就发生了 Native Crash 重启,并且也是 autiasp 指令异常错误。

Screenshot from 2024-08-13 21-45-38.png Screenshot from 2024-08-13 21-46-31.png

原问题剖析

有看我之前发的文章里,也有类似的堆栈打印不全的问题,此类情况都需自行对栈内存进行分析取证。我们可以从 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 校验失败。

Screenshot from 2024-08-14 09-30-18.png

观察栈内存分布,不难发现满足 FP 堆栈特征的有如下结果:

FPCaller FPCaller LRNoPAC LRLocation
0000006e904d43a00000006e904d4420004370eff28a95240000006ff28a9524/apex/com.android.art/lib64/libart.so
0000006e904d44200000006e904d44800072386fd505a8f00000006fd505a8f0/system/lib64/libaudioeffect_jni.so
0000006e904d44800000006e904d4af0002139efd505bf7c0000006fd505bf7c/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 文件后在 gdbcore-parser 上进行分析取证。

原理参考《 基于安卓墓碑文件制作 FakeCore 原理

tombstone 文件的 FP,SP,PC,#1 内容进行第一次修改制作一个 core 文件。

目标内容作用
FP0000006e904d43a0回到#0退栈前
SP0000006e904d43a0回到#0退栈前
PC0000006ff28580ac回到#0退栈前
#1/system/lib64/libaudioeffect_jni.soFakecore link_map 依赖

Screenshot from 2024-08-14 10-33-25.png

栈恢复后,不难发现一点,art::Thread 地址并非正确地址,说明 #0 的栈可能存在异常,实际上是因为函数已运行结束,此时x0寄存器存放的是函数的返回值art::mirror::Object *地址,对恢复后堆栈其它变量,以及栈大小进行取证。

栈大小取证

由于 core-parser 目前的栈回溯是纯 FP 强制回溯,#1 为 LR 帧在当前堆栈里可忽略不计。

Screenshot from 2024-08-14 10-57-26.png

在 core-parser 的 #0 处解析栈大小,从汇编代码可知 FP = SP,SIZE = 0x10

Screenshot from 2024-08-14 10-58-11.png

在 core-parser 的 #2 处解析栈大小,从汇编代码可知 FP = SP + 0x60, SIZE = 0xC0

Screenshot from 2024-08-14 11-01-17.png

在 core-parser 的 #3 处解析栈大小,从汇编代码可知 FP = SP, SIZE = 0x60

Screenshot from 2024-08-14 11-03-16.png

在 core-parser 的 #4 处解析栈大小,从汇编代码可知 FP = SP, SIZE = 0x50

Screenshot from 2024-08-14 11-05-01.png

于是我们可以对栈内存进行划分边界如下图:

7.png

从栈内存分布上看,原 #0 SP = 0000006e904d43a0 并不合理,栈地址合法值仍是 0000006e904d43b0,但是 0000006e904d43b0 压栈的内容并不正确。

数据取证

前面的栈大小取证基本可说明 art::JNI<false>::GetArrayLength 的栈是可靠的。于是我们对 GDB #4~#7 数据进行取证。

Screenshot from 2024-08-14 10-41-23.png

Screenshot from 2024-08-14 10-42-18.png

由于 tombstone 文件并未记录有 0xb40000712648aca0 相关内存,因此无法确定它内容的真实性。从函数调用角度看是可靠的。

Screenshot from 2024-08-14 10-52-24.png

Screenshot from 2024-08-14 10-45-18.png

callbackInfo 完全符合数据结构 visualizer_callback_cookie 的内存结构。

Screenshot from 2024-08-14 10-46-47.png

数据分析

栈回溯 以及 取证过程 中得知一个信息是,退栈前合法地址 0000006e904d43b0,并且 art::JNI<false>::GetArrayLength 的栈内容应该是可靠的,于是我们从此处人工推导后续过程。

Screenshot from 2024-08-14 11-47-03.png

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)

Screenshot from 2024-08-14 11-49-08.png

可以看到此时 #2 跳转的下一个函数应该是 art::Thread::DecodeGlobalJObject 并且它的栈大小正好是 0x20x19 = 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

Screenshot from 2024-08-14 11-56-21.png

程序运行到 0x6ff2a95f78 处,正好函数退栈后直接跳入 art::JavaVMExt::DecodeGlobal,那么问题来了!!

0x6ff2858060: a9bf7bfd | stp x29, x30, [sp, #-0x10]!

程序运行到 0x6ff2858060 处,会将 art::Thread::DecodeGlobalJObject 压栈过的内容再次入栈,此时的 0000006e904d43a00000006e904d43b0 的内容除了 PAC 值不同外,其它应该完全一致。

Screenshot from 2024-08-14 14-02-57.png

Screenshot from 2024-08-14 14-03-08.png

这里我们核实下 x0 = 1baf9dc8 是否为一个大小为 128 的数组对象,此处也说明了 DecodeGlobal 正确的返回了全局引用对象。

Screenshot from 2024-08-14 14-10-46.png

art::Thread::DecodeGlobalJObject 退栈后的 x30 寄存器至少是满足 PAC 校验的,否则本次报错的函数就不是 art::JavaVMExt::DecodeGlobal 而是它了。然而却在函数 6ff2858060 处,压栈的 x29 x30 非正确值。

Screenshot from 2024-08-14 14-40-53.png

现在的栈内存就像是指令 stp x29, x30, [sp, #-0x10]! 等效于 sub sp, sp, #0x10 一样,但比较可惜只有 tombstone 文件,PC 附近的内存保存有效,无法得知内存中的具体指令情况。于是我在真机上模拟类似场景。

Screenshot from 2024-08-14 14-42-49.png

将指令修改成 sub sp, sp, #0x10 就能出现类似的结果,但具体原因应该不是这样发生的。

Screenshot from 2024-08-14 14-45-24.png

当然也可能是存在某个函数将原本正确压栈的内容破坏了,我们结合最后的寄存器信息继续分析函数art::JavaVMExt::DecodeGlobal 的运行逻辑。注意到 0x6ff28580840x6ff28580a8 存在一段分支逻辑代码。

  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 内存一致。

目标内容作用
FP0000006e904d43b0回到#0退栈前
SP0000006e904d43b0回到#0退栈前
PC0000006ff2858068回到#0退栈前
#1/system/lib64/libaudioeffect_jni.soFakecore link_map 依赖

Screenshot from 2024-08-14 16-01-23.png

Screenshot from 2024-08-14 16-04-25.png

gUseReadBarrierwith_read_barrier 一般来说都是 true 假设这里会进第一个逻辑分支。

(gdb) p 'art::kUseBakerReadBarrier'
$64 = true

因此该函数将会在 173 行处返回结果。至于 art::Thread 的地址,我们可以通过特征值寻找,当前线程 Tid = 26882 = 00006902,因此我们可以在 tombstone 中搜索 00006902

Screenshot from 2024-08-14 16-32-12.png 因此 art::Thread 地址应该是 00000071a66f2830,存在的内存内容与堆栈前面的数据是吻合的。

Screenshot from 2024-08-14 16-33-39.png Screenshot from 2024-08-14 16-34-47.png Screenshot from 2024-08-14 17-01-14.png

后记

tombstone 文件信息有限,难以完整的剖析内容,问题仍存在许多的疑点,需群友继续努力争取抓到对应的 core 文件或者通过问题的高发时间段排查是否与贵司的合入有关。