在MacOS平台中使用LLDB进行系统内核调试分析的3种方法
2020-05-20 09:46:53 Author: www.4hou.com(查看原文) 阅读量:301 收藏

对于安全研究人员来说,他们经常需要研究各种内核,以充分了解研究目标。在Windows平台上这样做并不是什么难事,因为有无数关于内核调试设置的现成文章。但是,对于macOS,情况就略有不同。

有很多文章已经描述了如何在两台设备之间设置内核调试,但是所有这些都建议应该禁用SIP(系统完整性保护)进行内核调试。如果我们要调查macOS安全机制的内部运作,则会产生问题,因为关闭SIP也会关闭操作系统的大多数基本安全功能。

这篇文章将会介绍几个设置,这些设置使你可以在调试时启用SIP。

设置如下:

主机:带有补充更新的macOS Catalina 10.15.4;

来宾:具有补充更新的macOS Catalina 10.15.4;

VMware Fusion 11.5.3;

调试器:LLDB;

第一种方法

我们将从调试内核的原始发行版开始,该发行版默认包含在macOS中。到目前为止,这是我们将看到的最简单的方法。

第一步是从Apple的开发人员下载中下载内核调试工具包(KDK)。但是,在执行此操作之前,我们需要确定感兴趣的构建版本。这可以通过在来宾VM上使用以下命令来完成:

% sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.4
BuildVersion:   19E287

检索内核构建版本信息

知道BuildVersion编号后,就可以下载相应的KDK并将其安装在主机上。它将安装在/ Library / Developer / KDKs /下。

% ls -l /Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/
total 212112
-rwxr-xr-x  1 root  wheel  16030560 Mar  5 07:38 kernel
drwxr-xr-x  3 root  wheel    96 Mar  5 07:38 kernel.dSYM
-rwxr-xr-x  1 root  wheel  23795528 Mar  5 07:27 kernel.debug
drwxr-xr-x  3 root  wheel    96 Mar  5 07:27 kernel.debug.dSYM
-rwxr-xr-x  1 root  wheel  19329072 Mar  5 07:39 kernel.development
drwxr-xr-x  3 root  wheel    96 Mar  5 07:39 kernel.development.dSYM
-rwxr-xr-x  1 root  wheel  49436536 Mar  5 07:30 kernel.kasan

KDK的位置

下一步是在来宾VM上启用远程调试。幸运的是,VMware Fusion具有一个称为gdb stub的功能,该功能可以设置GDB服务器,并允许调试器使用GDB远程协议调试任何VM(包括Windows)。由于LLDB支持GDB协议,因此我们可以使用这种方法。

要为我们的来宾VM启用GDB存根,就需要在虚拟机vmx配置文件中添加以下行:

debugStub.listen.guest64 = "TRUE"

启用VMware gdb存根

接下来,我们启动虚拟机,在主机上启动lldb,并发出以下命令来指定我们的内核并启用符号文件的加载:

(lldb) target create /Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel
(lldb) settings set target.load-script-from-symbol-file true
(lldb) gdb-remote 8864

使用适当的符号启动LLBD

第一个命令将告诉lldb在哪里可以找到内核符号,此命令不是严格必需的,因为lldb会搜索/ Library / Developer / KDKs路径以及Spotlight索引的任何其他路径,但即使搜索失败,它仍然是一个很好的实践。

第二个命令将告诉lldb加载在符号(dSYM)目录中找到的所有脚本,这非常有用,因为这些脚本通常扩展lldb的功能。对于内核,我们可以使用大约400个新命令。

最后一个命令告诉lldb远程服务器正在侦听哪个端口,如果我们未指定IP地址或主机名(如此处所示),它将连接到本地主机。在默认情况下,VMware监听本地主机上的端口8864,所以这是我们连接到的端口。

如果一切顺利,我们应该进入虚拟机并开始调试。我们还可以随时使用调试器中的“CTRL+C”快捷键再次中断。

(lldb) gdb-remote 8864
Kernel UUID: AB0AA7EE-3D03-3C21-91AD-5719D79D7AF6
Load Address: 0xffffff8002600000
Kernel slid 0x2400000 in memory.
(...)
Process 1 stopped
* thread #3, name = '0xffffff8009854a40', queue = 'cpu-0', stop reason = signal SIGTRAP
frame #0: 0xffffff800284dede kernel`machine_idle at pmCPU.c:181:3 [opt]
Target 0: (kernel) stopped.
(lldb) image list
[  0] AB0AA7EE-3D03-3C21-91AD-5719D79D7AF6 0xffffff8002600000 /Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel
/Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/kernel
(...)

使用符号调试LLDB macOS内核

需要注意的一件事是,如果我们的VM和主机运行相同的内核,我们也可以使用基本内核二进制文件(/ System / Library / Kernels / kernel)作为目标。但是,我们将无权访问这些符号。

(lldb) target create /System/Library/Kernels/kernel
Current executable set to '/System/Library/Kernels/kernel' (x86_64).
(lldb) gdb-remote 8864
Process 1 stopped
* thread #1, stop reason = signal SIGTRAP
frame #0: 0xffffff8003b980f6
->  0xffffff8003b980f6: rep stosb byte ptr es:[rdi], al
0xffffff8003b980f8: ret
0xffffff8003b980f9: add byte ptr [rax], al
0xffffff8003b980fb: add byte ptr [rax], al
Target 0: (kernel) stopped.
(lldb) image list
[  0] AB0AA7EE-3D03-3C21-91AD-5719D79D7AF6 0xffffff8000200000 /System/Library/Kernels/kernel
(lldb) memory read 0xffffff8000200000
0xffffff8000200000: cf fa ed fe 07 00 00 01 03 00 00 00 02 00 00 00  ????............
0xffffff8000200010: 12 00 00 00 d0 0f 00 00 01 00 20 00 00 00 00 00  ....?..... .....
(lldb) continue

没有符号的LLDB macOS内核调试

最后,需要注意的是,VMware默认使用硬件断点,这将我们限制为四个。但是,可以通过将vmx配置文件中的hideBreakpoints设置设置为FALSE来克服此限制,如下所示:

debugStub.hideBreakpoints = "FALSE"

禁用VMWare使用硬件断点

第二种方法

在上一节中,我们讨论了如何使用内核的发行版设置macOS内核调试。不过,必须要指出的是,苹果还发布了调试或开发内核。根据苹果的说法,它们是使用“其他断言和错误检查”进行编译的,它们可以在初次启动后停止并等待调试器连接。

对于这个设置,这些内核并不是严格需要的,因为我们仍然会使用VMware以前的功能。更具体地说,macOS内核将不负责实际的调试。但是,如果在特殊情况下确实需要这些内核,这就是我们可以进行设置的方式。

在来宾VM上执行其他任何操作之前,我们首先需要暂时禁用SIP。这样做的原因是因为/ System /路径受SIP写入保护。为此,我们可以启动到恢复模式(在打开虚拟机后启动CMD + R),运行csrutil disable命令,然后重新启动。

研究人员发现,MIME库中MFMutableData的实现缺少对系统调用ftruncate()的漏洞检查,该漏洞导致越界写入。我们还找到了一种无需等待系统调用ftruncate失败即可触发OOB-Write的方法。此外,我们发现了可以远程触发的堆溢出。众所周知,这两种漏洞都是可以远程触发的。OOB写入漏洞和堆溢出漏洞都是由于相同的漏洞而引发的,即未正确处理系统调用的返回值。远程漏洞可以在处理下载的电子邮件时触发,在这种情况下,电子邮件将无法完全下载到设备上。

受影响的库:/System/Library/PrivateFrameworks/MIME.framework/MIME;易受攻击的函数:-[MFMutableData appendBytes:length:]。

除了手机邮件应用暂时放缓外,用户观察不到任何其他异常行为。在iOS 12上尝试利用漏洞(成功/失败)之后,用户只会注意到邮件应用程序突然崩溃。在iOS13上,除了暂时的速度下降之外,这不会引起注意。如果随后进行另一次攻击并删除电子邮件,则失败的攻击在iOS 13上不会明显。

在失败的攻击中,攻击者发送的电子邮件将显示消息:“此消息无内容。”用户经历的部分崩溃(多次崩溃中的一部分)如下;崩溃的指令是stnp x8,x9,[x3],这意味着x8和x9的值已被写入x3并由于访问存储在x3中的无效地址0x000000013aa1c000而崩溃。

为了找出导致进程崩溃的原因,我们需要看一下MFMutableData的实现。

下面的调用树是从崩溃日志中提取的,只有选定的一些设备才会发生崩溃。通过分析MIME库,-[MFMutableData appendBytes:length:]的伪代码如下:在崩溃发生之前执行以下调用堆栈:如果数据大小达到阈值,则使用文件存储实际数据,当数据更改时,应相应更改映射文件的内容和大小,系统调用ftruncate()被inside -[MFMutableData _flushToDisk:capacity:]调用以调整映射文件的大小。ftruncate的帮助文档是这样说明的:如上所示,如果调用失败,则返回-1,并且全局变量errno指定漏洞。这意味着在某些情况下,此系统调用将无法截断文件并返回漏洞代码。但是,在ftruncate系统调用失败时,_flushToDisk无论如何都会继续,这意味着映射的文件大小不会扩展,执行最终会到达appendBytes()函数中的memmove(),从而导致mmap文件出现超出边界(OOB)的写入。自macOS Catalina发行以来,/路径作为只读安装,作为除SIP之外的附加保护。因此,我们需要使其可写,以便将调试内核复制到正确的位置。

重新启动后,我们将根目录mount 为可写:

sudo mount -uw /

将根目录mount 为可写,并复制我们选择的内核(在本例中为调试):

sudo cp /Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel.debug /System/Library/Kernels/

将调试内核复制到适当的位置

然后,我们需要使内核缓存无效,这是必需的,因为macOS不会直接运行内核二进制文件,而是作为预链接的内核运行,它是由内核和内核扩展构建的。通常,预链接是在我们安装新的内核扩展或内核时发生的,但在本例中并非如此。在这里,我们只需复制内核的开发或调试版本。

sudo kextcache -invalidate /
sudo kextcache -invalidate /Volumes/Macintosh\ HD

内核缓存无效

最后,我们需要设置NVRAM引导参数以引导进入调试内核而不是常规内核,并引导进入恢复模式以使用csrutil enable命令重新打开SIP。

sudo nvram boot-args="kcsuffix=debug"

设置启动内核

现在,我们可以在主机上运行与以前相同的命令,这次指定调试内核。

(lldb) target create /Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel.debug
(lldb) settings set target.load-script-from-symbol-file true
(lldb) gdb-remote 8864

启动调试内核调试的命令

现在,我们可以在调试版而不是内核的发行版上执行macOS内核调试。

Process 1 stopped
* thread #2, name = '0xffffff80158f0e28', queue = 'cpu-0', stop reason = signal SIGTRAP
frame #0: 0xffffff80052ee796 kernel.debug`machine_idle at pmCPU.c:181:3
Target 0: (kernel.debug) stopped.
(lldb) image list
[  0] 16545FA7-C11F-3D9E-88FA-8DDB13E1A439 0xffffff8005000000 /Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel.debug
/Library/Developer/KDKs/KDK_10.15.4_19E287.kdk/System/Library/Kernels/kernel.debug.dSYM/Contents/Resources/DWARF/kernel.debug
[  1] A6D59354-C9A1-3C61-87A7-C04DD74421B1 0xffffff7f8609f000 //System/Library/Extensions/corecrypto.kext/Contents/MacOS/corecrypto
(...)

其他方法

为了完整起见,我们将简要讨论如何使用macOS内核而不是VMware gdb存根设置内核调试,这是其他地方最常用的方法。我们还将展示我们可以启用SIP,尽管人们普遍错误地认为SIP必须关闭。

这些步骤与前面的步骤本质上是相同的,只是有一点不同。在本例中,应按如下所示设置调试器框中的NVRAM变量:

nvram boot-args="kcsuffix=debug debug=0x44"

为内核调试设置NVRAM变量

从本质上讲,这些设置表明调试对象可以执行网络级调试并中断时中断。但是,中断是很复杂的,因为我们需要按CMD + OPTION + CONTROL + SHIFT + ESCAPE来进入调试器。此外,这必须在我们正在调试的目标VM上完成。当其他人使用这种方法成功使用此方法导致了中断,,我们就没有那么幸运了。相反,我们在VMware Key Mappings使用CMD + B为这个组合创建了一个键盘快捷键。

“Windows”和“Alt”按钮作为目标键映射,但它们在macOS中分别转换为“Command”和“Option”,VMware没有提供指定密钥macOS版本的选项。

最后,在主机上,我们将使用带有调试对象IP地址的kdp-remote命令,而不是gdb-remote。请注意,在发出此命令之前,我们需要在debuggee上进入调试器。

(lldb) kdp-remote 192.168.242.129

使用kdp-remote

总结

在这篇文章中,我们展示了调试macOS内核的三种不同方式,同时确定不需要永久禁用SIP。由于易于设置,我们首选的方法是我们描述的第一种方法,但如果情况需要,可以使用其他方法。

本文翻译自:https://www.offensive-security.com/offsec/kernel-debugging-macos-with-sip/?utm_content=129155041&utm_medium=social&utm_source=twitter&hss_channel=tw-134994790如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/jLZz
如有侵权请联系:admin#unsafe.sh