【技术分享】OS X内核大揭秘之基础篇

http://p3.qhimg.com/t011814e74b9736b5cf.jpg

译者:天鸽

预估稿费:200RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿

传送门

【技术分享】OS X内核大揭秘之利用篇

前言

虽然近年来的许多研究都集中在 Windows 操作系统上,但在安全性方面,iPhone 上的 iOS 和 MacBook 上的 macOS 操作系统也不容忽视。在本系列博客中,我们将探讨 OS X 内核中的 bug 分析和漏洞利用技术。


(1)寻找内核bug

我们可以使用模糊测试和代码审计的方法找到在内核级执行中的 bug,如 BSDMach IOKit

BSD:内核的 BSD 部分提供了大多数系统调用、网络和文件系统的功能。源自 FreeBSD 5。

Mach:来自 CMU 开发的 Mach 3.0 微内核。实现了内核映射和 IPC 等基本服务。用户空间应用程序可以通过 Mach Trap 访问 Mach 服务。

IOKit:IOKit 是一个用 C++ 编写的框架,它为 XNU 提供了驱动程序,而 Apple 提供了自己的运行时系统 libkern。


(2)初步利用

任意读/写可用于获取利用此漏洞必要的数据,或者在内核区域中创建任意数据。

由于每个 bug 都有自己独特的数据,所以应该读/写可以用于该漏洞利用的数据。


(3)内核权限获取和AAR/AAW

为了获得 kernel_task(pid=0) 权限所需的值,我们先遍历内核中的所有进程以获取 ipc object 和 kernel task,然后将数据转储到用户空间。

你可以使用它进行内核级的读/写。


(4)获得root权限

由于每个进程都被加载到内核内存中,所以我们可以捕获目标进程,并将下面的进程权限结构的 CR_RUID(Credential Real UID) 更改为 0。

[bsd/sys/ucred.h]
/*
 * In-kernel credential structure.
 *
 * Note that this structure should not be used outside the kernel, nor should
 * it or copies of it be exported outside.
 */
struct ucred {
    TAILQ_ENTRY(ucred)  cr_link; /* never modify this without KAUTH_CRED_HASH_LOCK */
    u_long  cr_ref;         /* reference count */
struct posix_cred {
    /*
     * The credential hash depends on everything from this point on
     * (see kauth_cred_get_hashkey)
     */
    uid_t   cr_uid;         /* effective user id */
    uid_t   cr_ruid;        /* real user id */
    uid_t   cr_svuid;       /* saved user id */
    short   cr_ngroups;     /* number of groups in advisory list */
    gid_t   cr_groups[NGROUPS]; /* advisory group list */
    gid_t   cr_rgid;        /* real group id */
    gid_t   cr_svgid;       /* saved group id */
    uid_t   cr_gmuid;       /* UID for group membership purposes */
    int cr_flags;       /* flags on credential */
} cr_posix;
    struct label    *cr_label;  /* MAC label */
    /*
     * NOTE: If anything else (besides the flags)
     * added after the label, you must change
     * kauth_cred_find().
     */
    struct au_session cr_audit;     /* user auditing data */
};

更改完成后,该进程将返回到 root 目录。

于是,如果你运行 system("/bin/bash");,将可以获得 root 权限的 shell。


背景知识

Kernel Zone

在 OS X 中,内核使用一个称为 Zone 的结构来分配堆。

Zone 使用 zalloc(zone) 和 kalloc(size) 进行分配,使用 zfree(zone,ptr) 和 kfree(ptr,size) 进行释放。

当调用 kalloc 时,zalloc 在内部被调用。kalloc zone 可以通过 sudo zprint kalloc 找到。

zone metadata:第一页中包含的 zone 信息有 size、page_count、alloc_element、free_element 等。

OOL(Out-Of-Line) Port

在 IPC 通信中加载的非内联数据包

它保存在内核中,直到它收到 OOL 的数据。

在做 OS X 内核利用时,可以通过 OOL 的 Leak 进行 fakeport 攻击。


OS X 内核漏洞缓解技术

LASLR

引导时内核内存地址随机化

Kext(Kernel Extension) 和内核共享相同的 kslide

旁路攻击:kslide地址计算(kslide=kernel_base – kernel text base)

DEP

防止内核中的 RWX 权限

旁路攻击:ROP

SMEP/SMAP

Intel CPU 提供的内核内存保护计算

SMEP(Supervisor Mode Execution Protection):无法在用户地址空间中执行内核代码

SMAP(Supervisor Mode Access Protection):不允许在用户地址空间中访问内存。仅在支持的 CPU 体系结构上可用。

旁路攻击:启用 ROP 绕过内核

vm_map_copy()更改

OS X 10.11 El Capitan 以前的内核中,可以使用溢出漏洞捕获 kdata 指针和 vm_map_copy 的 size,从而读取任意的数据,但这里 vm_map_copy 结构体发生了变化。(osfmk/vm/vm_map.h)

// 更改前

struct vm_map_copy{
    int type;
    #define VM_MAP_COPY_ENTRY_LIST      1
    #define VM_MAP_COPY_OBJECT          2
    #define VMMAP_COPY_KERNEL_BUFFER    3
    vm_object_offset_t offset;
    vm_map_size_t size;
    union {
        struct vm_map_header    hdr;
        vm_object_t             object;
        struct { // <<= Before Change
            void *kdata;
            vm_size_t kalloc_size;
        }c_k;
    }c_u;
};

// 更改后

struct vm_map_copy{
    int type;
    #define VM_MAP_COPY_ENTRY_LIST 1
    #define VM_MAP_COPY_OBJECT 2
    #define VM_MAP_COPY_KERNEL_BUFFER 3
    vm_object_offset_t offset;
    vm_map_size_t size;
    union{
        struct vm_map_header hdr;
        vm_object_t object;
        uint8_t kdata[0]; // <<= Changed
    }
}

然而,在 OS X 10.11 El capitan 的 ipc_kmsg_copyout_ool_descriptor() 中存在一种绕过竞争条件技术的情况。(目前该结构体已被修复)


其他

iOS 和 macOS 的结构和安全性补丁是非常相似的,所以通常可以把它们放在一起。

攻击是棘手的,除非有一个完美的溢出 bug。

许多 bug 很难通过远程攻击实现。(例如,使用safari漏洞获取内核权限的过程)

在早期版本的 OS X 中,通过将数据写入一个空指针,可以相对简单地实现 LPE(参见下面的源代码)。

void null_page(){
  sync();
  vm_address_t addr=0;
  vm_deallocate(mach_task_self(),0x0,0x1000);
  vm_allocate(mach_task_self(),&addr,0x1000,0);
  uint64_t * np=0;
  for(int i=1;i<0x100;i++)
  {
    np[i] = 0x4141414141414141;
  }
}

从 iOS 9.2,macOS 10.11 开始及以后的版本中,重新分配的内存地址很难被预测。(freelist randomization)

在 iOS 10,macOS 10.12 中,vm_map_size(vm_map_copy) 堆利用的漏洞也已经被修补。


内核调试

简介

一旦内核崩溃,操作系统将停止工作,因此你必须安装一个或多个虚拟机,并使用 kdp 来进行调试。如果你使用的是 OS X 中的“Parallels Desktop”,则可以使用恢复分区来安装 OSX 虚拟机。如果你已经有所需的 OSX 版本,则可以通过搜索安装镜像文件或虚拟机。

首先你需要下载内核调试工具包(Kernel Debug Kit),以便于在主机(host)中配置各种调试环境。内核调试工具包允许你使用 LLDB 或 GDB 加载和调试默认情况下不被包含的符号。如果你希望直接分析内核二进制文件并监视 1day 漏洞,则可以使用内核调试工具包在存在漏洞的 OSX 版本上进行分析。

主机设置

https://developer.apple.com/download/more/?=Kernel%20Debug%20Kit

t01b07fbe1cfe3e067c.png

它与 OSX 的版本是对应的,因此请根据你正在分析的 1-day 漏洞版本进行下载。安装并运行下载的 dmg 文件时,调试工具包存在于 /Library/Developer/KDKs/ 路径下。

t0101bd13670d16794d.png

在 OSX 上运行的内核二进制文件存在于路径 /System/Library/Kernels/kernel 下。你可以将二进制文件 attach 到调试器上,通过配置调试工具包,即使主机和客户机的 OSX 版本不同,你也可以进行调试。

现在,使用 LLDB 完成对主机的配置。

t0142ffd7f3cad4aa70.png

如果你将二进制文件加载到 LLDB 时看到了上面的报错信息。这很容易解决,如下所示。

(lldb) command script import "/Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/kernel.py"
songsangjun-ui-MacBook-Pro:~ s0ngsari$ echo "settings set target.load-script-from-symbol-file true" > ~/.lldbinit

请注意主机和客户机都必须禁用 System Integrity Protection(SIP)。

你已经完成了对主机环境的配置,现在再对客户机进行配置就可以调试内核了。

客户机设置

OSX 可以使用 nvram 设置引导参数(boot-args)。

$ nvram boot-args

boot-args 是作为 OS 在启动时设置的参数。但我们使用这些参数是为了记录调试所需的中断和日志信息。boot-args 有很多参数,这里我们先设置 debug。可以参考下面的图表。

t01ed684192df6b1d86.png

t01ed684192df6b1d86.png

调试内核时,给 debug 参数赋值 0x144。DB_NMI 标志允许调试器使用 NMI 中断 attach 到调试对象上。你可以设置如下所示的选项。

$ sudo nvram boot-args="debug=0x144 -v"
DB_LOG_PI_SCRN
DB_ARP
DB_NMI
Boot Verbose mode
$ sudo reboot

之后,boot-args 被设置并产生一个 NMI 中断,从而允许调试器调试内核。

有时候即使 NMI 已经在调试对象中给出,也不能进行调试,那么需要在调试器中添加 arp 表。

$ arp -s <DebuggeeIP> <DebuggeeMac>

这样就完成了主机和客户机的配置,可以继续下面的调试了。

attach 到调试器

作为本文的例子,我们将调试的 1day 漏洞是 CVE-2017-2370。

$ vi test.c
$ clang -o test test.c

在客户机中编译 PoC 并产生一个 NMI,NMI 可以通过命令 Command + Alt + Control + Shift + Esc 产生。一旦产生了 NMI,操作系统将被安全地停止运行。

在调试器中,你可以使用 kdp-remote 进行远程调试,首先打开 KDK 的内核二进制文件。

songsangjun-ui-MacBook-Pro:~ s0ngsari$ lldb /Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel
(lldb) target create "/Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel"
Loading kernel debugging from /Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/kernel.py
LLDB version lldb-350.0.21.9
settings set target.process.python-os-plugin-path "/Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/lldbmacros/core/operating_system.py"
settings set target.trap-handler-names hndl_allintrs hndl_alltraps trap_from_kernel hndl_double_fault hndl_machine_check _fleh_prefabt _ExceptionVectorsBase _ExceptionVectorsTable _fleh_undef _fleh_dataabt _fleh_irq _fleh_decirq _fleh_fiq_generic _fleh_dec
command script import "/Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel.dSYM/Contents/Resources/DWARF/../Python/lldbmacros/xnu.py"
xnu debug macros loaded successfully. Run showlldbtypesummaries to enable type summaries.
Current executable set to '/Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel' (x86_64).
(lldb)

如果没有报错,则使用 kdp-remote 命令链接到远程调试器。

(lldb) kdp-remote <debuggeeIP>
Version: Darwin Kernel Version 16.1.0: Wed Oct 19 20:31:56 PDT 2016; root:xnu-3789.21.4~4/RELEASE_X86_64; UUID=75CA1C4D-7BF4-321B-B544-D8F1B6D60EF8; stext=0xffffff8014200000
Kernel UUID: 75CA1C4D-7BF4-321B-B544-D8F1B6D60EF8
Load Address: 0xffffff8014200000
Kernel slid 0x14000000 in memory.
Loaded kernel file /Library/Developer/KDKs/KDK_10.12.1_16B2657.kdk/System/Library/Kernels/kernel
Loading 94 kext modules warning: Can't find binary/dSYM for com.apple.kec.corecrypto (809FEC94-017C-307A-B099-A01EFF5485FB)
.warning: Can't find binary/dSYM for com.apple.kec.pthread (36567317-B854-3157-ABF3-CEAD0A3770BB)
.warning: Can't find binary/dSYM for com.apple.kec.Libm (51D82C5F-0248-334D-ADC6-5861BBB83C97)
.warning: Can't find binary/dSYM for com.apple.iokit.IOACPIFamily (4F7FB6AD-2498-3F71-827C-ED7AA4BF2511)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.driver.AppleACPIPlatform (249D7BA8-3FD5-3207-A482-0605CB898037)
.warning: Can't find binary/dSYM for com.apple.driver.AppleFDEKeyStore (EA5D0966-E8EA-337A-98EB-195806E8F723)
.warning: Can't find binary/dSYM for com.apple.iokit.IOReportFamily (B14DC3D3-7250-3DA3-BF50-C666EBEDAF4C)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.driver.DiskImages (05A729EF-20B8-3254-8F13-42DF42E0544B)
.warning: Can't find binary/dSYM for com.apple.driver.AppleBusPowerController (DB526B45-1A45-3A81-A0C1-57F826CADEDF)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.driver.KernelRelayHost (3B58E6F0-DE92-3289-9D3B-3BF12208585F)
.warning: Can't find binary/dSYM for com.apple.driver.AppleCredentialManager (54677B39-44B3-3AAA-BBEC-D78D0B5CC1A7)
.warning: Can't find binary/dSYM for com.apple.driver.AppleMobileFileIntegrity (0EFA4D2C-2271-3C43-B777-17D05716144A)
.warning: Can't find binary/dSYM for com.apple.driver.AppleKeyStore (75515493-6D25-39F7-8F0B-B08B505CAB74)
.warning: Can't find binary/dSYM for com.apple.security.TMSafetyNet (1CB512A3-24BD-344A-BFB4-44A61F27AB03)
.warning: Can't find binary/dSYM for com.apple.kext.AppleMatch (3B280DAB-903F-33DC-8110-525A1154B11E)
.warning: Can't find binary/dSYM for com.apple.security.sandbox (32039FC4-CA9B-3B74-B326-A2BF5CFE45E1)
.warning: Can't find binary/dSYM for com.apple.security.quarantine (EC92F0F9-694E-3E22-8B2C-4A071D20C6BA)
.warning: Can't find binary/dSYM for com.apple.nke.applicationfirewall (2A0DC0EF-655C-3D4B-93FD-3AED72BEBBDC)
.warning: Can't find binary/dSYM for com.apple.driver.AppleAPIC (BC2E6D01-BCBB-3525-BF38-BF99C3F1EC46)
.warning: Can't find binary/dSYM for com.apple.driver.AppleSMBIOS (9BB02681-4B47-3592-AD62-71FB0BF56965)
.warning: Can't find binary/dSYM for com.apple.driver.AppleRTC (3FD1BCF4-8AFC-3CE6-A36E-26410544AD14)
.warning: Can't find binary/dSYM for com.apple.iokit.IOSMBusFamily (185F0EBF-0262-3370-BD47-8FE4C8AA726E)
.warning: Can't find binary/dSYM for com.apple.driver.AppleACPIEC (BC227AE1-3CD5-3938-9C8C-009F1A966FBE)
.warning: Can't find binary/dSYM for com.apple.driver.AppleHPET (2CFB49B8-4CC2-320B-9C6E-99646DFD8571)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.driver.AppleACPIButtons (4D5E51D6-8A6B-3B6A-A8F2-472D56C9D0C3)
.warning: Can't find binary/dSYM for com.apple.driver.AppleSmartBatteryManager (31670664-0EF0-39B5-A13F-15B8F9EC1283)
.warning: Can't find binary/dSYM for com.apple.driver.AppleEFIRuntime (6B7A5B9A-C313-3F7F-B6E2-60EE54593BC8)
.warning: Can't find binary/dSYM for com.apple.driver.AppleEFINVRAM (6F4404D6-8625-35CA-AEB6-6ECD7B64FA52)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBHostPacketFilter (9888F9CD-B7EE-3A9D-8530-6FA4C167B26C)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBEHCI (BF6EF9A2-F090-3094-B3FA-F34351D946CF)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBUHCI (4EF43593-FC11-31E6-8B27-E7A6B5703C15)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBUHCIPCI (4EC90565-DB48-3190-8608-1F6DA30B8691)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBXHCI (E5F9850E-A1A1-305F-854D-48B46C08B2EC)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBXHCIPCI (B4287428-23D9-3547-93B5-2FEB73A02EA6)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBEHCIPCI (4FCE62CA-0477-34A2-9564-86F807CDBD4D)
.warning: Can't find binary/dSYM for com.apple.iokit.IOATAFamily (BC25A382-3DA0-33C7-93C5-E8A823B50F98)
.warning: Can't find binary/dSYM for com.apple.driver.AppleIntelPIIXATA (BDC5E432-B04E-3ACF-A213-672128140381)
.warning: Can't find binary/dSYM for com.apple.iokit.IOAHCIFamily (5C275B66-A173-3D92-853A-44FC35D45FFC)
.warning: Can't find binary/dSYM for com.apple.driver.AppleAHCIPort (BE72151C-73BE-35B7-8C31-74F49E4C5E98)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.driver.AppleIntel8254XEthernet (34B30414-098D-3D22-AAB5-1A754D0647C6)
.warning: Can't find binary/dSYM for com.apple.iokit.IOAHCIBlockStorage (C449634B-8121-3BFB-972D-966847C4321F)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.iokit.IOAHCISerialATAPI (681FA1E2-E3DE-3FEB-ACA7-16FC2B9078A6)
.warning: Can't find binary/dSYM for com.apple.filesystems.hfs.encodings.kext (68A8D6C1-CDCA-371C-970B-325BF2E7ECAB)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.filesystems.hfs.kext (6C6C4A98-1534-3C52-B006-00FBC479233E)
.warning: Can't find binary/dSYM for com.apple.BootCache (C38789F4-9226-303C-99BE-3B8EAF8EC5C2)
.warning: Can't find binary/dSYM for com.apple.AppleFSCompression.AppleFSCompressionTypeZlib (9B32DDE9-151F-31A1-90E9-3CEB2C7BE27C)
.warning: Can't find binary/dSYM for com.apple.AppleFSCompression.AppleFSCompressionTypeDataless (C6F882D7-C35C-3963-A2FA-10033FF40107)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBHostCompositeDevice (30502C8D-F4B2-345F-B8F0-F8C54CAD7F46)
.warning: Can't find binary/dSYM for com.apple.driver.usb.networking (74394A72-1E87-363E-8CFD-182BD8C9362E)
.warning: Can't find binary/dSYM for com.apple.driver.usb.AppleUSBHub (F7BC6869-E4BA-3291-B7EA-BF28A0ABEF4A)
.warning: Can't find binary/dSYM for com.apple.driver.usb.IOUSBHostHIDDevice (0548123A-013B-3C74-86A8-33DF73E9CBBB)
.warning: Can't find binary/dSYM for com.apple.driver.AppleHIDKeyboard (664B787F-6DE5-3211-9081-E434055A550B)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.parallels.kext.video (5520E5F4-AC7C-9446-6088-5D8CAF25478D)
.warning: Can't find binary/dSYM for com.parallels.driver.AppleIntelAC97Controller (705C3A56-06CE-E995-5A75-618C5EF3D45D)
.warning: Can't find binary/dSYM for com.apple.vecLib.kext (C0ABF85C-CA30-3F02-9E1E-06F3BA5047A8)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.iokit.IOSlowAdaptiveClockingFamily (F026208D-CC0C-3599-B303-9196904A584E)
.warning: Can't find binary/dSYM for com.apple.driver.AppleIntelSlowAdaptiveClocking (6FE984DD-A1FE-309E-83CF-B346989A6F17)
.warning: Can't find binary/dSYM for com.apple.driver.IOPlatformPluginFamily (087648A2-8A44-3095-AEC7-44A872A46205)
.warning: Can't find binary/dSYM for com.apple.driver.IOPlatformPluginLegacy (9156271B-C61E-3B40-B5B6-102369F12A8B)
.warning: Can't find binary/dSYM for com.apple.driver.AppleSMC (969D80B2-E714-3145-95B0-F61627E0EE4D)
.warning: Can't find binary/dSYM for com.apple.driver.ACPI_SMC_PlatformPlugin (7224B682-B40F-3A4A-BCA0-82727D251ECB)
.warning: Can't find binary/dSYM for com.parallels.kext.tg (09C02F97-D104-80F1-2A96-6BEF8A2F6967)
.warning: Can't find binary/dSYM for com.apple.driver.AppleSMBusController (4DAA381E-3690-3E94-8025-DFB34F714094)
.warning: Can't find binary/dSYM for com.apple.driver.AppleMCCSControl (102DD5D9-2DD5-3BCB-B5C0-BE08E1049CD6)
.warning: Can't find binary/dSYM for com.apple.driver.AppleUpstreamUserClient (F39509A4-191C-35DA-B7D9-08F95E5AB8BC)
.warning: Can't find binary/dSYM for com.apple.driver.AppleHV (39AC9B9B-7B20-322F-82F0-044B3CC08D43)
.warning: Can't find binary/dSYM for com.apple.driver.AppleSSE (907BB577-46DF-3C86-9034-758B61AD054D)
.warning: Can't find binary/dSYM for com.apple.Dont_Steal_Mac_OS_X (B97F871A-44FD-3EA4-BC46-8FD682118C79)
.warning: Can't find binary/dSYM for com.apple.iokit.IOBluetoothFamily (794ACDDD-2B46-3BF0-94E9-4FD7C109A427)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
.warning: Can't find binary/dSYM for com.apple.iokit.IOBluetoothSerialManager (6F68B8CF-6543-328E-AF57-DD250412CF02)
.warning: Can't find binary/dSYM for com.apple.iokit.IOSurface (D3B2D208-487C-3166-9F7D-D6159AABC428)
.warning: Can't find binary/dSYM for com.apple.iokit.IOUserEthernet (5EE448BD-95EC-35AD-B7FC-A1237E4BB346)
.warning: Can't find binary/dSYM for com.apple.driver.pmtelemetry (F46D019B-17FF-3CD5-A093-0894B81C1404)
.warning: Can't find binary/dSYM for com.parallels.driver.AppleIntelAC97Audio (F8F3B21C-958B-BB10-E13C-42CA34BF6815)
.warning: Can't find binary/dSYM for com.apple.driver.AppleOSXWatchdog (757A8B72-2A1A-32BA-99EC-6D802DE6E91F)
.warning: Can't find binary/dSYM for com.apple.kext.triggers (4E564246-8804-3673-B440-606AD360A3BB)
.warning: Can't find binary/dSYM for com.apple.filesystems.autofs (AA36D92F-D92B-3102-BAE3-F86A0A298143)
.warning: Can't find binary/dSYM for com.apple.filesystems.smbfs (42EF3BC8-5041-3E94-BC74-9D5906694E3A)
.warning: Can't find binary/dSYM for com.apple.driver.usb.cdc (6CB80B6B-9071-38ED-9A4B-635ABF20A429)
.Target arch: x86_64
Instantiating threads completely from saved state in memory.
 done.
Target arch: x86_64
Instantiating threads completely from saved state in memory.
kernel was compiled with optimization - stepping may behave oddly; variables may not be available.
Process 1 stopped
* thread #2: tid = 0x16cc, 0xffffff801440bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513, name = '0xffffff801c791028', queue = '0x0', stop reason = signal SIGSTOP
    frame #0: 0xffffff801440bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513 [opt]
(lldb)

如果正确地捕获到了内核,就可以通过恢复调试对象进程并运行 PoC 来检查是否会产生崩溃。

(lldb) c
Process 1 resuming
(lldb) Unloading 1 kext modules . done.
Process 1 stopped
* thread #4: tid = 0x1af6, 0xffffff8014751a8c kernel`fp_lookup(p=0xffffff801ebec780, fd=1, resultfp=0xffffff806eb6bf20, locked=0) + 92 at kern_descrip.c:3879, name = '0xffffff801c09a9a8', queue = '0x0', stop reason = EXC_BAD_INSTRUCTION (code=13, subcode=0x0)
frame #0: 0xffffff8014751a8c kernel`fp_lookup(p=0xffffff801ebec780, fd=1, resultfp=0xffffff806eb6bf20, locked=0) + 92 at kern_descrip.c:3879 [opt]
(lldb) register read
General Purpose Registers:
       rax = 0x4141414141414141
       rbx = 0x0000000000000001
       rcx = 0x0000000000000001
       rdx = 0xffffff8020d0e800
       rdi = 0xffffff801ebec848
       rsi = 0x0000000000000001
       rbp = 0xffffff806eb6bef0
       rsp = 0xffffff806eb6bec0
        r8 = 0x0000000000000000
        r9 = 0x00007fffda6afa50
       r10 = 0x000000000000000a
       r11 = 0x0000000000000246
       r12 = 0xffffff801ebec780
       r13 = 0xffffff801ec3e4f8
       r14 = 0x0000000000000000
       r15 = 0xffffff806eb6bf20
       rip = 0xffffff8014751a8c  kernel`fp_lookup + 92 at kern_descrip.c:3879
    rflags = 0x0000000000010246
        cs = 0x0000000000000008
        fs = 0x0000000000000000
        gs = 0x0000000000000000
(lldb)

LLDB 和 GDB 的命令有所不同,最好将它们分开学习。或者使用一个名为 kgmacros 的脚本,总的来说使用 GDB 进行调试也不错。

记录堆日志

运行 Poc 并设置断点来跟踪堆是很困难的。但是,你可以通过上面说到的 boot-args 来跟踪堆。这可以跟踪 OSX 堆的所覆盖的 zone。

$ sudo nvram boot-args="debug=0x144 -v -zc zlog1=kalloc.128 zlog2=kalloc.256"

与上面的命令一样,使用 -zc zlog1=zone,然后重启。最重要的是知道 Poc 所使用的 zone。我们只跟踪所选择的 zone,而不是全部,所以如果你想跟踪起来比较轻松,就只传递一个 zone 到 boot-args 中。

使用 zlog 时

(lldb) bt
* thread #2: tid = 0x0cb5, 0xffffff801200bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513, name = '0xffffff8019752980', queue = '0x0', stop reason = signal SIGSTOP
  * frame #0: 0xffffff801200bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513 [opt]
    frame #1: 0xffffff801200bb4e kernel`Debugger(message=<unavailable>) + 910 at model_dep.c:1025 [opt]
    frame #2: 0xffffff8011ef368c kernel`panic(str=""a freed zone element has been modified in zone %s: expected %p but found %p, bits changed %p, at offset %d of %d in element %p, cookies %p %p"@/Library/Caches/com.apple.xbs/Sources/xnu/xnu-3789.21.4/osfmk/kern/zalloc.c:651") + 236 at debug.c:458 [opt]
    frame #3: 0xffffff8011f3f5c0 kernel`backup_ptr_mismatch_panic [inlined] zone_element_was_modified_panic(offset=0) + 800 at zalloc.c:642 [opt]
    frame #4: 0xffffff8011f3f559 kernel`backup_ptr_mismatch_panic(zone=<unavailable>, element=<unavailable>, primary=4702111234474983745, backup=<unavailable>) + 697 at zalloc.c:710 [opt]
    frame #5: 0xffffff8011f3e739 kernel`try_alloc_from_zone(zone=<unavailable>, check_poison=<unavailable>) + 521 at zalloc.c:832 [opt]
    frame #6: 0xffffff8011f3d174 kernel`zalloc_internal(zone=<unavailable>, canblock=1, nopagewait=0) + 484 at zalloc.c:2284 [opt]
    frame #7: 0xffffff8011f84580 kernel`vm_map_copyin_internal + 51 at vm_map.c:9428 [opt]
    frame #8: 0xffffff8011f8454d kernel`vm_map_copyin_internal(src_map=<unavailable>, src_addr=140351705630208, len=3240, flags=<unavailable>, copy_result=<unavailable>) + 253 at vm_map.c:10279 [opt]
    frame #9: 0xffffff8011ed7629 kernel`ipc_kmsg_copyin_ool_descriptor [inlined] vm_map_copyin_common(src_map=<unavailable>, src_destroy=<unavailable>, copy_result=0xffffff8071adbe40, use_maxprot=0) + 201 at vm_map.c:10187 [opt]
    frame #10: 0xffffff8011ed7616 kernel`ipc_kmsg_copyin_ool_descriptor(dsc=0xffffff8018874c98, user_dsc=<unavailable>, is_64bit=<unavailable>, paddr=<unavailable>, copy=0xffffff8071adbe40, space_needed=<unavailable>, map=<unavailable>, mr=<unavailable>) + 182 at ipc_kmsg.c:2701 [opt]
    frame #11: 0xffffff8011ed7c25 kernel`ipc_kmsg_copyin_body(kmsg=0xffffff8018874c00, space=0xffffff8018925b40, map=0xffffff801c0e9e08) + 613 at ipc_kmsg.c:3035 [opt]
    frame #12: 0xffffff8011ee992f kernel`mach_msg_overwrite_trap(args=<unavailable>) + 287 at mach_msg.c:548 [opt]
    frame #13: 0xffffff8011ff26ae kernel`mach_call_munger64(state=0xffffff8018dd12c0) + 430 at bsd_i386.c:562 [opt]
    frame #14: 0xffffff8011ea5f66 kernel`hndl_mach_scall64 + 22

未使用 zlog 时

(lldb) bt
* thread #2: tid = 0x13ab, 0xffffff8015c0bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513, name = '0xffffff80200f4288', queue = '0x0', stop reason = signal SIGSTOP
  * frame #0: 0xffffff8015c0bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513 [opt]
    frame #1: 0xffffff8015c0bb4e kernel`Debugger(message=<unavailable>) + 910 at model_dep.c:1025 [opt]
    frame #2: 0xffffff8015af368c kernel`panic(str=""Invalid queue element linkage for %p: next %p next->prev %p prev %p prev->next %p"@/Library/Caches/com.apple.xbs/Sources/xnu/xnu-3789.21.4/osfmk/kern/queue.h:245") + 236 at debug.c:458 [opt]
    frame #3: 0xffffff8015bec040 kernel`pmap_enter_options [inlined] __QUEUE_ELT_VALIDATE + 81 at queue.h:244 [opt]
    frame #4: 0xffffff8015bebfef kernel`pmap_enter_options [inlined] insque at queue.h:347 [opt]
    frame #5: 0xffffff8015bebfef kernel`pmap_enter_options [inlined] pv_hash_add + 32 at pmap_internal.h:544 [opt]
    frame #6: 0xffffff8015bebfcf kernel`pmap_enter_options(pmap=<unavailable>, vaddr=<unavailable>, pn=<unavailable>, prot=<unavailable>, fault_type=<unavailable>, flags=<unavailable>, wired=<unavailable>, options=<unavailable>, arg=<unavailable>) + 5103 at pmap_x86_common.c:926 [opt]
    frame #7: 0xffffff8015b6fb41 kernel`vm_fault_enter(m=0xffffff801c1c3c00, pmap=<unavailable>, vaddr=140736734584832, prot=<unavailable>, caller_prot=<unavailable>, wired=0, change_wiring=<unavailable>, no_cache=0, cs_bypass=<unavailable>, user_tag=1962753648, pmap_options=<unavailable>, need_retry=<unavailable>, type_of_fault=<unavailable>) + 4481 at vm_fault.c:3292 [opt]
    frame #8: 0xffffff8015b71405 kernel`vm_fault_internal(map=<unavailable>, vaddr=<unavailable>, caller_prot=<unavailable>, change_wiring=0, interruptible=2, caller_pmap=0x0000000000000000, caller_pmap_addr=0, physpage_p=<unavailable>) + 4421 at vm_fault.c:4086 [opt]
    frame #9: 0xffffff8015c069fc kernel`user_trap [inlined] vm_fault(map=<unavailable>, vaddr=<unavailable>, fault_type=<unavailable>, change_wiring=0, interruptible=2, caller_pmap=<unavailable>, caller_pmap_addr=0) + 652 at vm_fault.c:3397 [opt]
    frame #10: 0xffffff8015c069d8 kernel`user_trap(saved_state=0xffffff801ff11060) + 616 at trap.c:1120 [opt]
    frame #11: 0xffffff8015aa5655 kernel`hndl_alltraps + 229

当查看调用栈时,差异是很明显的。使用了 zlog 时,你可以看到调用栈被分配给了 zalloc,但未使用 zlog 时就看不到。所以如果你要分析 1day 漏洞的堆腐败时,了解 zone 的概念并使用 zlog 可以进行更好的调试。

更多内容

了解 bug 很重要,但调试也很重要。做调试时我会使用一些简单的命令。

(lldb) bt
* thread #2: tid = 0x1650, 0xffffff801200bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513, name = '0xffffff801a0eee18', queue = '0x0', stop reason = signal SIGSTOP
  * frame #0: 0xffffff801200bb4e kernel`Debugger [inlined] hw_atomic_sub(delt=1) at locks.c:1513 [opt]
    frame #1: 0xffffff801200bb4e kernel`Debugger(message=<unavailable>) + 910 at model_dep.c:1025 [opt]
    frame #2: 0xffffff8011ef368c kernel`panic(str=""a freed zone element has been modified in zone %s: expected %p but found %p, bits changed %p, at offset %d of %d in element %p, cookies %p %p"@/Library/Caches/com.apple.xbs/Sources/xnu/xnu-3789.21.4/osfmk/kern/zalloc.c:651") + 236 at debug.c:458 [opt]
    frame #3: 0xffffff8011f3f5c0 kernel`backup_ptr_mismatch_panic [inlined] zone_element_was_modified_panic(offset=0) + 800 at zalloc.c:642 [opt]
    frame #4: 0xffffff8011f3f559 kernel`backup_ptr_mismatch_panic(zone=<unavailable>, element=<unavailable>, primary=4702111234474983745, backup=<unavailable>) + 697 at zalloc.c:710 [opt]
    frame #5: 0xffffff8011f3e739 kernel`try_alloc_from_zone(zone=<unavailable>, check_poison=<unavailable>) + 521 at zalloc.c:832 [opt]
    frame #6: 0xffffff8011f3d174 kernel`zalloc_internal(zone=<unavailable>, canblock=1, nopagewait=0) + 484 at zalloc.c:2284 [opt]
    frame #7: 0xffffff8011ed5248 kernel`ipc_kmsg_alloc(msg_and_trailer_size=4352) + 248 at ipc_kmsg.c:929 [opt]
    frame #8: 0xffffff8011ef832d kernel`ipc_kobject_server(request=<unavailable>, option=<unavailable>) + 141 at ipc_kobject.c:299 [opt]
    frame #9: 0xffffff8011ed5f61 kernel`ipc_kmsg_send(kmsg=<unavailable>, option=<unavailable>, send_timeout=<unavailable>) + 225 at ipc_kmsg.c:1826 [opt]
    frame #10: 0xffffff8011ee9957 kernel`mach_msg_overwrite_trap(args=<unavailable>) + 327 at mach_msg.c:556 [opt]
    frame #11: 0xffffff8011ff26ae kernel`mach_call_munger64(state=0xffffff8019fed920) + 430 at bsd_i386.c:562 [opt]
    frame #12: 0xffffff8011ea5f66 kernel`hndl_mach_scall64 + 22

bt 命令是 BackTrace 的缩写,它用于将线程堆栈信息打印出来。你可以选择跳转到单个 frame 并查看其局部变量。

(lldb) frame select 5
frame #5: 0xffffff8011f3e739 kernel`try_alloc_from_zone(zone=<unavailable>, check_poison=<unavailable>) + 521 at zalloc.c:832 [opt]

如果你使用 frame select 命令选择了一个 frame,则会把当前 frame 的信息打印出来,并且 rip 也会变为该 frame 的地址。如下所示。

(lldb) register read
General Purpose Registers:
       rbx = 0xffffff801f977000
       rbp = 0xffffff8872cabc50
       rsp = 0xffffff8872cabc10
       r12 = 0x7e415085550ee3c7
       r13 = 0x4141414141414141
       r14 = 0xffffff8017cd70a0
       r15 = 0x4141414141414141
       rip = 0xffffff8011f3e739  kernel`try_alloc_from_zone + 521 at zalloc.c:832

你可以看到 rip 确实在我们选择的 frame 中,并且可以看到该函数局部变量的值。

(lldb) frame var
(zone_t) zone = <variable not available>
(boolean_t *) check_poison = <variable not available>
(zone_page_metadata *) page_meta = 0xffffff8017cd70a0
(vm_offset_t) element = 18446743524483756032
(vm_offset_t *) primary = 0xffffff801f977000
(vm_offset_t) next_element_primary = 4702111234474983745
(vm_offset_t) next_element = 9097641255853024199
(vm_offset_t) next_element_backup = 4702111234474983745
(vm_offset_t *) backup = <no location, value may have been optimized out>

OSX 中 Page 的第一部分被称为元数据(Meta data),它包含了有关该 zone 的大量信息,如堆的元数据。

对于 iOS 10,有如下元数据:

zindex:zone_array 中的索引

Page_count:分配的页面大小

free_count:页面中 free element 数量

freelist_offset:页面中第一个 free element 的地址

下面我们看一下名为 primary 的变量。

(lldb) memory read 0xffffff801f977000
0xffffff801f977000: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA
0xffffff801f977010: 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41  AAAAAAAAAAAAAAAA

如果 PoC 被正确地触发,那么正常情况下会显示一串 A。由于分配给 zone 的元素在 memset 中以 A 填充,因此结果如上所示。

以这种方式触发 PoC 后,如果存在你所需的 frame,则可以通过选择该 frame 并检查局部变量的方式进行调试。请注意 frame 相关的命令通常是很有用的。

我们可以查看在 Mac 上运行的所有任务的基址,内核任务也在其中。可以看到内核任务名为 kernel_task,你可以使用下面的命令将其输出。

(lldb) showalltasks 
task                 vm_map               ipc_space            #acts flags    pid       process             io_policy  wq_state  command             
0xffffff80185b3aa0   0xffffff8014d1e6e8   0xffffff80180ab800     134            0   0xffffff80126ba360                -1 -1 -1    kernel_task         
0xffffff80185b3000   0xffffff8018db0838   0xffffff80180ab840       6            1   0xffffff8018d81128                -1 -1 -1    launchd             
0xffffff801961c000   0xffffff8019603268   0xffffff80195c2740       4 D         34   0xffffff8018d80cb0                -1 -1 -1    UserEventAgent      
0xffffff801961caa0   0xffffff8019603838   0xffffff80195c2980       2 D         36   0xffffff8018d80838             TQ -1 -1 -1    uninstalld          
0xffffff8019634aa0   0xffffff8019603458   0xffffff80195c29c0       2 D         37   0xffffff8018d81e90                -1 -1 -1    kextd     &n
(完)