准备
- 首先我们确认自己的内核版本
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 slide
值0x0000000008600000
告诉了我们内核镜像如何通过kASLR
转换到了内存中。
总的来说,通过此份报告,我们得到了:
- 内核在注入
0xffffff80639b8000
发生了错误。 - 错误被禁止(RIP)的地址是
0xffffff7f8c7ba8b1
。 com.apple.iokit.IOAcceleratorFamily2
和com.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
。
等待内核镜像被重新定位后,我们点击G
输入内存的最后一段地址0xffffff8008a014f7
拆开镜像后我们看到这条指令是直接被加载在一条call
指令之前。
一般来说,我们的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]
- 0xffffff7f8c7ba8b1
- 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
指令是导致错误的真正元凶。
现在我们找到了导致错误的真正地址和指令,我们可以更详细地看这个指令。我们看到它在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
,我们可以猜测带有R8
的snprintf
被写到了user mode之外,因此用这样的方法可以暂时解决。
References
Objective-See., (2018). An Unpatched Kernel Bug., avaliable at https://objective-see.com/index.html. last accessed at 1st Nov.2020.