Skip to content
 编辑

Darwin/XNU

Darwin/XNU

事实证明,在不使用 macOS 附带的任何专有内核扩展的情况下,从 XNU 源代码构建可用于模糊测试的虚拟机镜像异常困难。因此,本指南基于标准的 macOS 安装。不幸的是,苹果的 macOS 最终用户许可协议(EULA)使得此方法不适合在 Google Cloud Platform 上对 XNU 进行模糊测试。

准备 macOS 安装磁盘镜像

如今,苹果主要通过 Mac App Store 分发 macOS 更新。但这只能获取最新版本。幸运的是,Munki 社区维护了一个脚本,允许我们从命令行下载指定版本的 macOS 构建。

我们需要选择与目标内核版本相近的 macOS 构建。您可以在此页面查看苹果最新的源代码发布。在撰写本文时,20G71 版本的 11.5 可用,并且与 11.5 的 xnu 源代码发布兼容。Munki 下载脚本仅能识别 macOS 版本和构建号,而无法识别 XNU 版本。有时您可能下载了匹配的 macOS 版本,但构建的内核仍无法启动。其中一个常见原因是内核与引导加载程序版本不匹配。获取正确的构建可能需要反复尝试。有时正确的构建可能已不可用。撰写本文时,可用的 11.5 构建 20G71 可与 11.5 的 xnu 源代码配合使用。

在下面的说明中,我将假定您已在主机 macOS 上安装了 VMware Fusion 以创建虚拟机磁盘映像。选择 Fusion 的原因是它允许我们直接拖放下载的 macOS 安装器应用程序。如果您想使用 Qemu 等其他工具,请参考苹果官方创建可引导安装介质的方法。在使用苹果方法生成可引导 ISO 时遇到问题的情况下,我通常会使用 Fusion 创建安装介质。

此外,以下说明要求您在虚拟机中禁用系统完整性保护(System Integrity Protection)和认证根(Authenticated Root)。我们需要禁用这些功能以运行稍后构建的自定义内核。这些功能的简要说明:

快速操作指南:创建虚拟机镜像

检查各项配置是否正确: 显示干净 macOS 11.5 构建 20G71 安装的截图,包含 xnu-7195.141.2~5/RELEASE_X86_64 内核、已禁用系统完整性保护和认证根

准备适用于模糊测试的优化内核

您可能会疑惑为何不使用苹果内核开发套件(KDK)中的预编译内核。因为这些内核未启用 KSANCOV 功能标志。KSANCOV 是苹果提供的 API,允许用户空间请求内核追踪指定线程访问的内核代码,并将这些信息暴露给用户空间。Syzkaller 需要这些信息才能有效进行模糊测试。

幸运的是,afrojer@ 定期在其博客更新从源代码构建 XNU 并在 macOS 上安装的指南。撰写本文时,他的指南落后三个次要版本。最新指南适用于 macOS 11.2。本文将介绍一些必要的额外修改。

构建和测试适用于模糊测试的 XNU:

mkdir -p BUILD/mnt
sudo mount -o nobrowse -t apfs /dev/<your_disk> $PWD/BUILD/mnt

make SDKROOT=macosx TARGET_CONFIGS="KASAN X86_64 NONE" KSANCOV=1

kmutil create -a x86_64 -Z -n boot sys \
-B BUILD/BootKernelExtensions.kc.kasan \
-S BUILD/SystemKernelExtensions.kc.kasan \
-k BUILD/obj/kernel.kasan \
--elide-identifier com.apple.driver.AppleIntelTGLGraphicsFramebuffer

sudo ditto BUILD/BootKernelExtensions.kc.kasan "$PWD/BUILD/mnt/System/Library/KernelCollections/"
sudo ditto BUILD/SystemKernelExtensions.kc.kasan "$PWD/BUILD/mnt/System/Library/KernelCollections/"
sudo ditto BUILD/obj/kernel.kasan "$PWD/BUILD/mnt/System/Library/Kernels/"

sudo bless --folder $PWD/BUILD/mnt/System/Library/CoreServices --bootefi --create-snapshot
sudo nvram boot-args="-v kcsuffix=kasan wlan.skywalk.enable=0"

重启后运行 uname -a 应显示新内核:Darwin users-Mac.local 20.6.0 Darwin Kernel Version 20.6.0: Mon Aug 9 16:12:43 PDT 2021; user:xnu-7195.141.2/BUILD/obj/KASAN_X86_64 x86_64

为高效进行模糊测试,需要在宿主机准备内核二进制文件、符号表和源代码。执行以下复制操作:

mkdir -p ~/115/src/Users/user/kernel/ ~/115/obj
rsync -r mac:/Users/user/kernel/xnu-7195.141.2 ~/115/src/Users/user/kernel/
mv ~/115/src/Users/user/kernel/xnu-7195.141.2/BUILD/obj/KASAN_X86_64/kernel.kasan ~/115/obj/
mv ~/115/src/Users/user/kernel/xnu-7195.141.2/BUILD/obj/KASAN_X86_64/kernel.kasan.dSYM/ ~/115/obj/

为 Qemu 准备虚拟机

尽管Mac电脑是带有EFI的AMD64架构机器(至少本文涉及的机型是),但它们并不是完全兼容IBM PC的。到目前为止,VMware Fusion为我们完成了虚拟化macOS所需的所有复杂操作,但qemu-system-x86_64却没有。

为使 macOS 启动,我们首先使用 OVMF(基于 tianocore 的 Qemu UEFI)启动 Qemu。接着引导 OpenCore,后者将执行某些技巧使得链式加载苹果原生 AMD64 EFI 引导加载程序成为可能。它还会进行二进制内核补丁,以便在需要时加载 macOS 附带的 RELEASE 内核。

OpenCore 具有较高可配置性,但我们不关心真实硬件。本文使用此预构建版本,已配置为在 Qemu 中工作。我们可以直接用该仓库镜像中的 EFI 分区覆盖虚拟机的 EFI 分区。

首先确定要覆盖的分区。在 macOS 虚拟机中(当前仍通过 Fusion 启动)执行以下命令,可见 EFI 分区位于 /dev/disk0s1

user@users-Mac ~ % diskutil list
/dev/disk0 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *69.8 GB    disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:                 Apple_APFS Container disk1         69.6 GB    disk0s2

/dev/disk1 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +69.6 GB    disk1
                                 Physical Store disk0s2
   1:                APFS Volume macos - Data            43.2 GB    disk1s1
   2:                APFS Volume Preboot                 385.6 MB   disk1s2
   3:                APFS Volume Recovery                623.2 MB   disk1s3
   4:                APFS Volume VM                      1.1 MB     disk1s4
   5:                APFS Volume macos                   16.0 GB    disk1s5
   6:              APFS Snapshot com.apple.bless.4099... 16.0 GB    disk1s5s1

下载 OpenCore-v13.iso.gz 并通过 gzip -d OpenCore-v13.iso.gz 解压。查看分区映射以确定镜像块大小及 EFI 分区偏移量和大小:

user@users-Mac ~ % hdiutil pmap ./OpenCore-v13.iso

MEDIA: ""; Size 150 MB [307200 x 512]; Max Transfer Blocks 2048
SCHEME: 1 GPT, "GPT Partition Scheme" [16]
SECTION: 1 Type:'MAP'; Size 150 MB [307200 x 512]; Offset 34 Blocks (307133 + 67) x 512
ID Type                 Offset       Size         Name                      (1)
-- -------------------- ------------ ------------ -------------------- --------
 1 EFI                            40       307120 disk image

组合这些值执行 dd 命令:sudo dd if=./OpenCore-v13.iso of=/dev/disk0s1 bs=512 iseek=40 count=307120

挂载 EFI 磁盘:sudo mount -t msdos /dev/disk0s1 ~/mnt/。需微调 OpenCore 配置文件:禁用引导设备选择器(以便全自动启动虚拟机),并在此处设置引导参数(在 OpenCore 中需通过 config.plist 设置,而非 VMware Fusion 中的 nvram 等工具)。

编辑 ~/mnt/EFI/OC/config.plist

index 8537ca8..a46de97 100755
--- a/Users/user/mnt/EFI/OC/config.plist
+++ b/Users/user/mnt/EFI/OC/config.plist
@@ -799,7 +799,7 @@
 			<key>PollAppleHotKeys</key>
 			<true/>
 			<key>ShowPicker</key>
-			<true/>
+			<false/>
 			<key>TakeoffDelay</key>
 			<integer>0</integer>
 			<key>Timeout</key>
@@ -944,7 +944,7 @@
 				<key>SystemAudioVolume</key>
 				<data>Rg==</data>
 				<key>boot-args</key>
-				<string>keepsyms=1</string>
+				<string>-v kcsuffix=kasan wlan.skywalk.enable=0 keepsyms=1 debug=0x100008 kasan.checks=4294967295</string>
 				<key>csr-active-config</key>
 				<data>Jg8=</data>
 				<key>prev-lang:kbd</key>

此时仍可通过 Fusion 启动虚拟机,但 OpenCore 将被忽略,这属正常现象。

准备 isa-applesmc

macOS 启动时会通过读取系统管理控制器(SMC)的值验证是否运行在正版 Mac 上。我们需要获取该值并在后续 Qemu 启动时传递。获取方法:

输出结果将作为 <YOUR_APPLE_SMC_HERE> 的替代值用于后续步骤。

通过 Qemu 启动 macOS

启动 Qemu(替换 <YOUR_APPLE_SMC_HERE> 和磁盘路径中的用户名):

qemu-system-x86_64 \
  -device isa-applesmc,osk="<YOUR_APPLE_SMC_HERE>" \
  -accel hvf -machine q35 -smp "2",cores="2",sockets="1" -m "4096" \
  -cpu Penryn,vendor=GenuineIntel,+invtsc,vmware-cpuid-freq=on,"+pcid,+ssse3,+sse4.2,+popcnt,+avx,+aes,+xsave,+xsaveopt,check" \
  -drive if=pflash,format=raw,readonly=on,file="/usr/local/share/OVMF/OVMF_CODE.fd" \
  -drive if=pflash,format=raw,readonly=on,file="/usr/local/share/OVMF/OVMF_VARS.fd" \
  -device ich9-intel-hda -device hda-duplex -device ich9-ahci,id=sata \
  -device ide-hd,bus=sata.4,drive=MacHDD \
  -drive id=MacHDD,if=none,file="/Users/user/115/macos_11_5.qcow",format=qcow2 \
  -netdev user,id=net0,hostfwd=tcp::1042-:22, -device e1000-82545em,netdev=net0,id=net0 \
  -device usb-ehci,id=ehci -usb -device usb-kbd -device usb-tablet \
  -monitor stdio -vga vmware

您应该既能看到 macOS 界面,也能通过 ssh user@localhost -p 1042 连接。确认已启动至 KASAN 内核:

user@users-Mac ~ % uname -a
Darwin users-Mac.local 20.6.0 Darwin Kernel Version 20.6.0: Mon Aug  9 16:12:43 PDT 2021; user:xnu-7195.141.2/BUILD/obj/KASAN_X86_64 x86_64

现在关闭虚拟机。我们很快会让 syzkaller 重新启动它。

构建 Syzkaller

export GOPATH=/Users/user/go
export PATH=$GOPATH/bin:$PATH
git clone https://github.com/google/syzkaller
cd syzkaller
make HOSTOS=darwin HOSTARCH=amd64 TARGETOS=darwin TARGETARCH=amd64 SOURCEDIR=/Users/user/115/src/Users/user/kernel/xnu-7195.141.2

使用 Syzkaller 进行模糊测试

{
    "target": "darwin/amd64",
    "http": "127.0.0.1:56741",
    "sshkey": "/Users/user/.ssh/id_macos115",
    "workdir": "/Users/user/sk_darwin/",
    "kernel_obj": "/Users/user/115/obj/",
    "kernel_src": "/Users/user/115/src/",
    "syzkaller": "/Users/user/go/src/github.com/google/syzkaller",
    "procs": 2,
    "type": "qemu",
    "cover": true,
    "image": "/Users/user/115/macos_11_5.qcow",
    "vm": {
        "count": 2,
        "cpu": 2,
        "mem": 4096,
        "efi_code_device": "/usr/local/share/OVMF/OVMF_CODE.fd",
        "efi_vars_device": "/usr/local/share/OVMF/OVMF_VARS.fd",
        "apple_smc_osk": "<YOUR_APPLE_SMC_HERE>"
    }
}

通过 ~/115/bin/syz-manager -config=/root/115/syzkaller.cfg 启动 syzkaller,并在浏览器中打开 http://localhost:56741。