在MacOS平台中使用LLDB进行系统内核调试分析的3种方法 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

在MacOS平台中使用LLDB进行系统内核调试分析的3种方法

lucywang 资讯 2020-05-20 08:46:53
602074
收藏

导语:在这篇文章中,我们展示了调试macOS内核的三种不同方式,同时确定不需要永久禁用SIP。

对于安全研究人员来说,他们经常需要研究各种内核,以充分了解研究目标。在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如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务