内核崩溃报告解读

准备

  • 首先我们确认自己的内核版本
    uname -a
    Darwin xjns-iMac-Pro.local 19.6.0 Darwin Kernel Version 19.6.0: Mon Aug 31 22:12:52 PDT 2020; root:xnu-6153.141.2~1/RELEASE_X86_64 x86_64
  • 确认自己的电脑已经开启了kASLR功能

错误报告(Kernel panic report)

一般存放在/Library/Logs/DiagnosticReports/ 目录下,报告以.panic结尾。比如Kernel_2020-10-15-185538_Patricks-MacBook-Pro.panic

随便网上找了一份错误报告:


*** Panic Report ***
panic(cpu 6 caller 0xffffff8008b6f2e9): Kernel trap at 0xffffff7f8c7ba8b1, type 14=page fault, registers:
CR0: 0x000000008001003b, CR2: 0xffffff80639b8000, CR3: 0x0000000022202000, CR4: 0x00000000003627e0
RAX: 0x0000000000000564, RBX: 0x0000000000000564, RCX: 0x0000000000000020, RDX: 0x000000000000002a
RSP: 0xffffff92354ebc80, RBP: 0xffffff92354ebce0, RSI: 0x00000000000fbeab, RDI: 0xffffff92487b9154
R8:  0x0000000000000000, R9:  0x0000000000000010, R10: 0x0000000000000010, R11: 0x0000000000000000
R12: 0xffffff80639b6a70, R13: 0xffffff92354ebdc0, R14: 0xffffff92354ebdd4, R15: 0x0000000000000000
RFL: 0x0000000000010297, RIP: 0xffffff7f8c7ba8b1, CS:  0x0000000000000008, SS:  0x0000000000000010
Fault CR2: 0xffffff80639b8000, Error code: 0x0000000000000000, Fault CPU: 0x6, PL: 0, VF: 1

Backtrace (CPU 6), Frame : Return Address
0xffffff92354eb730 : 0xffffff8008a505f6 
0xffffff92354eb780 : 0xffffff8008b7d604 
0xffffff92354eb7c0 : 0xffffff8008b6f0f9 
0xffffff92354eb840 : 0xffffff8008a02120 
0xffffff92354eb860 : 0xffffff8008a5002c 
0xffffff92354eb990 : 0xffffff8008a4fdac 
0xffffff92354eb9f0 : 0xffffff8008b6f2e9 
0xffffff92354ebb70 : 0xffffff8008a02120 
0xffffff92354ebb90 : 0xffffff7f8c7ba8b1 
0xffffff92354ebce0 : 0xffffff7f8c7ba40f 
0xffffff92354ebd60 : 0xffffff7f8c7b85e8 
0xffffff92354ebda0 : 0xffffff7f8c7b9db2 
0xffffff92354ebe00 : 0xffffff7f8b2b3873 
0xffffff92354ebe50 : 0xffffff7f8b2bd473 
0xffffff92354ebe90 : 0xffffff7f8b2bcc7d 
0xffffff92354ebed0 : 0xffffff8009091395 
0xffffff92354ebf30 : 0xffffff800908fba2 
0xffffff92354ebf70 : 0xffffff800908f1dc 
0xffffff92354ebfa0 : 0xffffff8008a014f7 
      Kernel Extensions in backtrace:
         com.apple.iokit.IOAcceleratorFamily2(376.6)[5F8F39B4-41AB-3263-9867-D0FAF9BBD2AE]@0xffffff7f8b2b0000->0xffffff7f8b345fff
            dependency: com.apple.driver.AppleMobileFileIntegrity(1.0.5)[58669FC2-CC90-3594-AD69-DB89B923FD20]@0xffffff7f899ff000
            dependency: com.apple.iokit.IOSurface(209.2.2)[AE58720D-7079-388F-AD95-FD2366F98F8D]@0xffffff7f8b294000
            dependency: com.apple.iokit.IOPCIFamily(2.9)[C08F7FC1-78A4-3A1B-BFE2-C07080CF2048]@0xffffff7f89294000
            dependency: com.apple.iokit.IOGraphicsFamily(517.22)[2AEA02BF-2A38-3674-A187-E5F610FD65B7]@0xffffff7f89a39000
         com.apple.kext.AMDRadeonX4150(1.6)[DF336AB9-8300-3ED2-AAD3-7D2C8F4B8DEB]@0xffffff7f8c7b4000->0xffffff7f8cf20fff
            dependency: com.apple.iokit.IOSurface(209.2.2)[AE58720D-7079-388F-AD95-FD2366F98F8D]@0xffffff7f8b294000
            dependency: com.apple.iokit.IOPCIFamily(2.9)[C08F7FC1-78A4-3A1B-BFE2-C07080CF2048]@0xffffff7f89294000
            dependency: com.apple.iokit.IOGraphicsFamily(517.22)[2AEA02BF-2A38-3674-A187-E5F610FD65B7]@0xffffff7f89a39000
            dependency: com.apple.iokit.IOAcceleratorFamily2(376.6)[5F8F39B4-41AB-3263-9867-D0FAF9BBD2AE]@0xffffff7f8b2b0000

BSD process name corresponding to current thread: kernel_task

Mac OS version:
17C88

Kernel version:
Darwin Kernel Version 17.3.0: Thu Sep 9 18:09:22 PST 2020; root:xnu-4570.31.3~1/RELEASE_X86_64
Kernel slide:     0x0000000008600000
Kernel text base: 0xffffff8008800000
__HIB  text base: 0xffffff8008700000
System model name: MacBookPro14,3

EOF

错误报告有很多无用内容,我们直接看错误发生在哪里:

 panic(cpu 6 caller 0xffffff8008b6f2e9): Kernel trap at 0xffffff7f8c7ba8b1, type 14=page fault

这段告诉我们系统奔溃是因为type 14=page fault。这种错误往往指出这段内容无法写入或读取一段内存页。

接着,我们需要找到发生错误命令的注册的地址。在这之前,我们先记录一下它的RIP,也就是命令在哪一段死了,从报告中搜索RIP,我们搜到的是0xffffff7f8c7ba8b1

据此,根据内存注入的地址,我们得到引起错误的地址0xffffff80639b8000

Fault CR2: 0xffffff80639b8000, Error code: 0x0000000000000000, Fault CPU: 0x6 ...

这份错误报告同样回溯了这样的错误是通过何种功能方法注入导致的错误指令。

  Backtrace (CPU 6), Frame : Return Address
  0xffffff92354eb730 : 0xffffff8008a505f6 
  0xffffff92354eb780 : 0xffffff8008b7d604 
  0xffffff92354eb7c0 : 0xffffff8008b6f0f9 
  0xffffff92354eb840 : 0xffffff8008a02120 
  0xffffff92354eb860 : 0xffffff8008a5002c 
  0xffffff92354eb990 : 0xffffff8008a4fdac 
  0xffffff92354eb9f0 : 0xffffff8008b6f2e9 
  0xffffff92354ebb70 : 0xffffff8008a02120 
  0xffffff92354ebb90 : 0xffffff7f8c7ba8b1 
  0xffffff92354ebce0 : 0xffffff7f8c7ba40f 
  0xffffff92354ebd60 : 0xffffff7f8c7b85e8 
  0xffffff92354ebda0 : 0xffffff7f8c7b9db2 
  0xffffff92354ebe00 : 0xffffff7f8b2b3873 
  0xffffff92354ebe50 : 0xffffff7f8b2bd473 
  0xffffff92354ebe90 : 0xffffff7f8b2bcc7d 
  0xffffff92354ebed0 : 0xffffff8009091395 
  0xffffff92354ebf30 : 0xffffff800908fba2 
  0xffffff92354ebf70 : 0xffffff800908f1dc 
  0xffffff92354ebfa0 : 0xffffff8008a014f7

我们需要找到这些内存地址归属于哪一个kexts才能真正去寻找这个错误的来源:

kext: com.apple.iokit.IOAcceleratorFamily2
loaded at: 0xffffff7f8b2b0000

kext: com.apple.kext.AMDRadeonX4150
loaded at: 0xffffff7f8c7b4000

这份报告中可能缺少一些对我们非常重要的信息,但是Kernel slide0x0000000008600000告诉了我们内核镜像如何通过kASLR转换到了内存中。

总的来说,通过此份报告,我们得到了:

  • 内核在注入0xffffff80639b8000发生了错误。
  • 错误被禁止(RIP)的地址是0xffffff7f8c7ba8b1
  • com.apple.iokit.IOAcceleratorFamily2com.apple.kext.AMDRadeonX4150这两个kexts是引起错误的主要原因。
  • 内核通过kASLR转换进内存的地址是0x0000000008600000
  • 内存地址的最后一段是0xffffff8008a014f7

分析错误报告

得到了主要的几个数据后,我们把/System/Library/Kernels/kernel拖入Hopper disassembler软件里, 因为kASLR把内核注入到了内存中,我们需要Hopper Disassembler来重新定位镜像。按Modify然后点击Change File Base Address。输入之前得到的slide地址+0x100000,也就是0x0000000008600000+0x100000=0xffffff8008700000

Screen Shot 2020-11-01 at 10.12.23 PM.png
等待内核镜像被重新定位后,我们点击G输入内存的最后一段地址0xffffff8008a014f7

Screen Shot 2020-11-01 at 10.18.45 PM.png

拆开镜像后我们看到这条指令是直接被加载在一条call指令之前。
callCont.png

一般来说,我们的CPU收到了call指令后,它会马上保存这个地址,这样允许我们知道如何去return这个call以及什么时候这个call完成。当内核正在准备错误报告时,它通过回溯这个被保存的地址来找到错误关键。因此,当我们遇到一个回溯的地址比如0xffffff8008a014f7时,它会立即保存并执行回溯。 所以根据这张图,在0xffffff8008a014f5地址上的call rcx是产生错误的位置。

我们根据这个地址,按照内存的排列顺序往上回溯,我们可以列出如下信息:

  • kernel.call_continuation()

    • 0xffffff8008a014f5 call rcx
  • kernel.IOWorkLoop::threadMain()

    • 0xffffff800908f1d6 call qword [rax+0x1a8]
  • kernel.IOWorkLoop::runEventSources()

    • 0xffffff800908fb9c call qword [rax+0x120]
  • kernel.IOInterruptEventSource::checkForWork()

    • 0xffffff8009091392 call r11
  • com.apple.iokit.IOAcceleratorFamily2.IOAccelEventMachine2::hardwareErrorEvent()

    • 0xffffff7f8b2bcc78 call IOAccelEventMachine2::restart_channel()
  • com.apple.iokit.IOAcceleratorFamily2.IOAccelEventMachine2::restart_channel()

    • 0xffffff7f8b2bd46d call qword [rax+0x160]
  • com.apple.iokit.IOAcceleratorFamily2.IOAccelFIFOChannel2::restart()

    • 0xffffff7f8b2b386d call qword [rax+0x208]
  • com.apple.kext.AMDRadeonX4150.AMDRadeonX4150_AMDAccelChannel::getHardwareDiagnosisReport()

    • 0xffffff7f8c7b9dac call qword [rax+0xb00]
  • com.apple.kext.AMDRadeonX4150.AMDRadeonX4150_AMDGraphicsAccelerator::writeDiagnosisReport()

    • 0xffffff7f8c7b85e2 call qword [rax+0x258]
  • com.apple.kext.AMDRadeonX4150.AMDRadeonX4150_AMDAccelChannel::writeDiagnosisReport()

    • 0xffffff7f8c7ba40a call AMDRadeonX4150_AMDAccelChannel::writePendingCommandInfo
  • com.apple.kext.AMDRadeonX4150.AMDRadeonX4150_AMDAccelChannel::writePendingCommandInfoDiagnosisReport()

    • 0xffffff7f8c7ba8b1 mov r8d, dword [r12+rax*4]
  • kernel.hndl_alltraps()
    • 0xffffff8008a0211b call _kernel_trap

我们列出表格后,就可以更轻松看到这个错误是如何发生的。准确地说,这个错误是com.apple.iokit.IOAcceleratorFamily2 kext 在处理硬件时产生的。
com.apple.iokit.IOAcceleratorFamily2产生了restart_channel,转而把信息递交到了com.apple.kext.AMDRadeonX4150。一般来说,这个kext是AMD 560显卡的驱动。

除了这个restart_channel之外,硬件的分析信息也同时生成了。com.apple.kext.AMDRadeonX4150执行了 AMDRadeonX4150_AMDAccelChannel::writeDiagnosisReport,这种方法我们叫做writePendingCommandInfoDiagnosisReport

我们看到第11个位置,它不是一个call指令,而是一个move指令:

0xffffff7f8c7ba8b1 mov r8d, dword [r12+rax*4]

另外,我们在载入com.apple.kext.AMDRadeonX4150 kext,同样发现了这个地址0xffffff7f8c7ba8b1,与错误报告中的RIP地址一致。另外,下一段地址执行了一个call back去处理一个trap(call_kernel_trap),也就是这个报告里面的page fault。这意味着这个move指令是导致错误的真正元凶。

fault.png

现在我们找到了导致错误的真正地址和指令,我们可以更详细地看这个指令。我们看到它在R12这个base上面增加了一个RAX*4。通过计算我们得到增加到的是R8d。因为我们原始的错误报告中记录了这个错误指令的注册值,我们可以重新计算这个地址:

  registers:
  CR0: 0x000000008001003b, CR2: 0xffffff80639b8000, ...
  RAX: 0x0000000000000564, RBX: 0x0000000000000564, ...
  RSP: 0xffffff92354ebc80, RBP: 0xffffff92354ebce0, ...
  R8:  0x0000000000000000, R9:  0x0000000000000010, ...
  R12: 0xffffff80639b6a70, R13: 0xffffff92354ebdc0, ...
  RFL: 0x0000000000010297, RIP: 0xffffff7f8c7ba8b1, ...
mov r8d, dword [r12+rax*4]

R12: 0xffffff80639b6a70
RAX: 0x0000000000000564

R12 + RAX*4 = 0xffffff80639b6a70 + (0x564 * 4) = 0xffffff80639b8000

计算出来的0xffffff80639b8000地址,便是我们原始报告中的:

Fault CR2: 0xffffff80639b8000, Error code: 0x0000000000000000, Fault CPU: 0x6 ...

从这里看出来,0xffffff80639b8000注册在了没有寻址过的页面中,因此,在com.apple.kext.AMDRadeonX4150中执行mov命令是不可行的,最终导致系统奔溃。
但是,我们不能100%确定为什么这个指令会在不被寻址的页面中执行。我们因此会有如下的疑问:

  • R12这个地址是被其他东西占用了或者本身就是无效的吗?

  • 这个增加的RAX值本身就是无效的还是太大了所以超出了范围?

  • 还是这本身就是个硬件问题?

显卡驱动是非常复杂的,并且你可能需要通过很多逆向工程才能获得一些基础的认识。但是我们可以假设应该有方法去绕过这个错误,比如说确认一下这个RAX值是否在R12范围内?

根据这种情况,我们何不考虑把R12这个值移动到可读的范围内呢?
我们尝试用一个r8来替换,发现snprintf功能引导出来了。

  ffffff7f8c7ba8af   mov        eax, eax
  ffffff7f8c7ba8b1   mov        r8d, dword [r12+rax*4] ;faulting instruction
  ffffff7f8c7ba8b5   xor        eax, eax
  ffffff7f8c7ba8b7   lea        rdx, qword [aC08x]     ; "%c%08x"
  ffffff7f8c7ba8be   call       0xffffff7f198091e8     ;  snprintf

关于这个会话,macOS确认了ystem V AMD64 ABI。这意味着R8是第五条arg去引入snprintf

int snprintf(char *str, size_t size, const char *format, ...);

这样的话,snprintf被加入到了%c%08x,第五个指令就会被寻址到%08x。这样,R8就能被注册进寻址过的页面中去了。
因为这个代码被加入到了writeDiagnosisReport,我们可以猜测带有R8snprintf被写到了user mode之外,因此用这样的方法可以暂时解决。


References

Objective-See., (2018). An Unpatched Kernel Bug., avaliable at https://objective-see.com/index.html. last accessed at 1st Nov.2020.

下一篇