从0到tfp0第一部分:基础知识
2020-11-17 11:00:29 Author: xz.aliyun.com(查看原文) 阅读量:225 收藏

本文原文来自From zero to tfp0 - Part 1: Prologue
2019年1月22日,Google Project zero安全研究员@_bazad发了以下推文

如果您对iOS内核引导自举安全研究(包括伪造PAC和调用任意内核函数的能力)感兴趣,请保留一台iOS 12.1.2A12研究设备

他发现的是MIG(Message Interface generator,消息接口生成器)生成的代码中的引用计数漏洞。他提供了触发该漏洞并引起内核panic的POC。之后又提供了向用户态提供内核任务端口(tfp0)从而可以读写内核中任何内容的完整的POC。
2019年1月29日,他又发布了以下推文,将其称为voucher_swap。

现在A12设备上能够在内核中执行代码,介绍一下voucher_swap
https://googleprojectzero.blogspot.com/2019/01/voucherswap-exploiting-mig-reference.html

越狱社区随后将该漏洞用于为iOS 12开发完整的越狱程序。本系列博客分为三个部分。
第1部分介绍iOS安全基础知识,这是理解下面两部分的基础。这一部分讨论了分析kernelcache,Mach消息,Mach端口,MIG,堆分配基础,CoreTrust,PAC等等,以及一些流行的漏洞利用技术,例如伪造内核任务端口,通过task_for_pid()读取内核等等。如果你已经知道这些技术可以直接跳至第2部分。在第1部分中我将提到其他两部分,这两部分将进一步重申为什么理解这些概念是必要的。
第2部分将讨论@_bazad的voucher_swap以及实现tfp0的完整步骤。
第3部分将讨论实现越狱的步骤,例如逃逸沙盒,CoreTrust,启用rootfs remount等等。
在开始之前,需要准备以下文件。

XNU内核

iOS kernelcache由核心内核及其内核扩展组成。内核代码本身是不开源的,但是它基于在macOS上使用的开源XNU内核的分支。可以从opensource.apple.com下载XNU内核。

从最近几年开始,Apple也一直在开源ARM架构特定的代码,这些代码可以在ifdef CONFIG_EMBEDDED语句下找到。当然也有一些没有开源。

通过源码审计就可能找到内核中的漏洞。但是,只有通过编译内核并查看BUILD目录才能找到某些漏洞(例如voucher_swap),该目录包含MIG生成的代码。内核扩展中存在的漏洞通常是通过逆向找到的,因为kext代码通常不是开源的。有些漏洞仅存在于macOS,而有些漏洞仅存在于iOS。

kernelcache

kernelcache是一个包括核心内核及其内核扩展的Mach-O二进制文件。在iOS 10之前它一直是加密的,令人意外的是之后Apple以性能为主要原因决定发布未加密的kernelcache。现在可以轻松从IPSW文件解压提取它。在此之前,获得kernelcache的方式通常是找到内核漏洞之后将它从内存中dump出来,或者获取加密密钥(从theiphonewiki或利用bootrom漏洞)之后解密。
要获得解压的kernelcache,只需将ipsw文件解压并查找kernelcache文件。

prateek:mv iPhone_4.7_P3_12.0_16A366_Restore.ipsw iPhone_4.7_P3_12.0_16A366_Restore.zip
prateek:unzip iPhone_4.7_P3_12.0_16A366_Restore.zip
Archive:  iPhone_4.7_P3_12.0_16A366_Restore.zip
  inflating: Restore.plist
   creating: Firmware/
   creating: Firmware/usr/
   creating: Firmware/usr/local/
  inflating: BuildManifest.plist
   creating: Firmware/AOP/
  inflating: Firmware/AOP/aopfw-t8010aop.im4p
  inflating: Firmware/D201_CallanFirmware.im4p
  ....
  inflating: kernelcache.release.iphone10
  inflating: Firmware/ICE16-3.00.01.Release.plist
  inflating: kernelcache.release.iphone9
  inflating: Firmware/ICE17-2.00.01.Release.plist
   creating: Firmware/Maggie/

可以使用jtool2列出所有内核扩展并将它们拆分为相应的kext文件。

IDA通过魔术值检测kernelcache,并提供了将kernelcache拆分为相应的kext文件的选项。现在可以分别逆向这些内核扩展,以便挖掘其中的漏洞。

在越狱的iOS设备上,解压后的kernelcache可以在/System/Library/Caches/com.apple.kernelcaches/kernelcache下找到。一些越狱使用这个文件来动态找到某些符号和偏移量的地址,而不是使用硬编码的偏移量。一个很好的例子就是@morpheus创建的Qilin工具包。

kernelcache符号

符号化二进制文件可能需要很多人力工作。直到iOS 11 kernelcache一直带有某些符号。从iOS 12开始,Apple决定删除kernelcache的所有符号,但是Apple误发布了符号完整的beta版本的kernelcache。这个IPSW后来从下载中删除了。下图显示了jtool2分别在删除了符号的iOS 12 kernelcache和符号完整的iOS 12 beta kernelcache上获得的符号数量。

符号完整的kernelcache随后被jtool2用来为删除了符号的kernelcache创建符号。jtool2其中一个最有用的功能是analyze命令,提供一个iOS 12 kernelcache,它会将符号缓存输出到文件。

如下所示,大约有12000个符号。

如果看到$$$,一种最简单的方法是使用IDA 7.2开始引入的Lumina功能获取符号。

编译内核

编译内核对于发现漏洞非常重要。实际上,我们在这里讨论的voucher_swap漏洞是不能通过审计xnu内核源码找到的。因为一些依赖关系,编译内核有点复杂,但是通过Google可以找到许多一步一步说明如何编译内核的文章,包括@_bazad为XNU 4570.1.46(MacOS High Sierra 10.13)编写的自动化脚本。在第2部分研究voucher_swap时我们将查看存在于MIG生成的文件中的含有漏洞的源代码。

Mach消息

XNU内核的独特功能之一是它广泛使用了Mach IPC,它是从Mach微内核派生而来的,并且是迄今为止最快的IPC机制之一。iOS上的许多常用IPC机制(例如XPC)仍在底层使用Mach消息。下面是有关Mach消息的一些要点。

  • Mach IPC基于单向通信
  • Mach IPC中的通信以Mach消息的形式在端口之间进行。根据消息头中设置的特定位,消息可以是简单的也可以是复杂的
  • 发送和接收消息必须有关联的端口权限。下面是不同类型的权限:
    MACH_PORT_RIGHT_SEND-允许发送消息
    MACH_PORT_RIGHT_RECEIVE-允许接收消息
    MACH_PORT_RIGHT_SEND_ONCE-允许发送一次消息
    MACH_PORT_RIGHT_PORT_SET-面向一组端口接收或发送消息
    MACH_PORT_RIGHT_DEAD_NAME-表示因为对应的端口被销毁或者已经发送了一次消息从而无效的SEND或者SEND_ONCE权限
  • Mach端口权限可以嵌入并通过Mach消息发送
  • 一个端口可以有多个发送权限,但只有一个接收权限。发送权限也可以克隆,而接收权限则不能
  • 发送的Mach消息在接收方收到之前将保留在内核的队列中。这种技术过去用于堆风水
  • iOS中最重要的二进制文件之一launchd充当引导服务器并允许进程相互通信。launchd可以帮助一个进程查找另一个进程,因为所有进程都通过launchd启动并在启动时注册。因此,launchd还可以实现节流(throttling)并在某些情况下允许或拒绝查找,从而充当安全控件。launchd非常重要,因此它是第一个启动的守护进程(PID 1),launchd中的任何crash都将立即引发内核panic
  • 消息由进程内的线程发送和接收,充当进程内的执行单元。但是,端口权限保留在任务级别,并可在任务的ipc_space中找到(稍后讨论)

让我们看一下内核中与Mach IPC相关的代码。来看一下xnu-4903.221.1/osfmk/mach/message.h。如前所述,消息本质上可以是简单或复杂的。在下图中,可以看到简单的Mach消息(mach_msg_base_t)的结构,其中包括头部(mach_msg_header_t)和主体(mach_msg_body_t)。但是简单的消息主体会被内核忽略。

Mach消息头部结构体具有以下成员。

  • msgh_bits:这是一个包含消息的各种属性的位图,例如消息是简单还是复杂,要执行的操作(例如移动或复制端口权限)。完整的逻辑可以在osfmk/mach/message.h中找到
  • msgh_size:头部和主体的大小
  • msgh_remote_port:目标端口的发送权限
  • msgh_local_port:接收端口的接收权限
  • msgh_voucher_port:voucher用于通过键值对传递消息中的数据
  • msgh_id:任意32位字段

在message.h中可以看到,将msgh_bits的复杂位设置为1来指定复杂消息。

除头部外还包含某些描述符,并且在主体中指定了描述符的数量(msgh_descriptor_count)。

mach_msg_type_descriptor_t字段指定它是什么类型的描述符,其他字段中含有相应的数据。存在以下类型的描述符:

  • MACH_MSG_PORT_DESCRIPTOR:在消息中发送一个端口
  • MACH_MSG_OOL_DESCRIPTOR:在消息中发送OOL数据
  • MACH_MSG_OOL_PORTS_DESCRIPTOR:在消息中发送OOL端口数组
  • MACH_MSG_OOL_VOLATILE_DESCRIPTOR:在消息中发送易失性数据

OOL(Out-of-line)端口描述符已广泛用于用用户控制的数据来喷射堆。每当使用MACH_MSG_OOL_PORTS_DESCRIPTOR时,它将在内核堆中分配(kalloc)具有所有端口指针的数组。该技术已用于voucher_swap EXP中,并将在本系列的第2部分中进行讨论。
端口在用户态中用mach_port_t或mach_port_name_t表示,但在内核中并不是。mach_port_name_t表示本地命名空间标识,但未关联任何端口权限,并且在任务命名空间之外基本上没有任何意义。但是,只要进程从内核接收到mach_port_t,它就会将关联的端口权限映射到接收者,mach_port_name_t则不会。mach_port_t通常总是至少拥有一项权限,可以是RECEIVE,SEND或SEND_ONCE。这就是我们在EXP中引用内核任务端口的原因。我们使用mach_port_t是因为它确实将端口权限与对象相关联。获得mach_port_t的句柄会自动在调用者的命名空间中创建关联的发送权限。
为了发送或接收消息,可以按照osfmk/mach/message.h中的定义使用mach_msg和mach_msg_overwrite API。让我们看一些代码示例以更好的理解它。以下代码段使用mach_port_allocate API创建Mach端口并获得该端口的接收权限。

可以使用mach_msg发送消息。

然后可以使用mach_msg接收消息。


如果你有一个端口的发送权限,你可以使用mach_port_insert_right将这个发送权限插入另一个任务,然后使用mach_msg发送消息。如前所述,mach_port_name_t在任务命名空间之外毫无意义,这就是为什么需要将任务(ipc_space_t)与mach_port_name_t一起指定的原因,以便内核可以将指定的名称(mach_port_name_t)放入该任务的命名空间。

MIG-Mach接口生成器

使用Mach API编写的许多代码都包含相同的样板代码,多次重复可能会导致复杂性,甚至导致安全漏洞,因此MIG非常方便。它基于MIG规范文件(defs)实现桩函数。客户端可以像调用任何其他C函数一样调用此桩函数,并且桩函数可以处理进出mach消息的数据的编组和解编组,从而控制发生在底层的所有Mach IPC实现。
MIG规范文件具有扩展名defs,并且在编译内核时,这些文件由mig处理并生成额外的文件,这些文件包含自动生成的MIG封装器。例如,让我们看一下osfmk/mach/task.defs中的task.defs文件。如下所示,每个defs文件都有一个子系统名称,后跟一个任意数字,该数字在文件的开头声明。task.defs文件的子系统名称为task,数字为3400。桩函数还可以检查传递给它的参数的有效性。

如果要生成MIG封装器,则可以在包含def文件的目录运行mig。

在编译期间,mig工具根据子系统名称创建三个文件。例如对于task子系统,将创建以下文件:

  • taskUser.c-该文件包含代理函数的实现,该代理函数负责将数据编组为消息并发送。它还负责解组返回的数据并将其发送回客户端
  • task.c-代理函数的原型
  • taskServer.c-桩函数的实现包含在此文件中

在生成的文件中定义了许多基本上是函数的routine。让我们看一下自动生成的MIG代码中的Mach API routine task_set_exception_port。

审计这些函数中的代码也非常重要。在下一篇文章中,我们将讨论在构建内核后获得的自动生成的MIG代码中找到的漏洞。

任务端口

Mach端口的另一个有用的功能是它们充当对象的抽象,而该抽象是由Mach消息提供的,这些消息主要通过MIG进行转换。例如,主机Mach端口提供许多API,以获取有关主机的信息。host_kernel_version()函数将打印内核版本。这与uname -r命令使用的API相同。查看osfmk/mach/mach_host.defs文件将显示主机端口API提供的所有routine。

类似,任务端口用作任务的抽象。可以在osfmk/mach/task.def或者BUILD文件夹中的osfmk/mach/task.defs找到这些API。

这些API十分强大,可以与目标进程进行完全交互。具有进程的任务端口的发送权限将完全控制该任务,包括在目标任务内存区域中读取,写入和分配内存。顺便说一句,我们提到的是进程(来自BSD)的任务(来自Mach)端口,这似乎有些奇怪,需要注意的是,它们内部是有联系的。每个BSD进程都有一个对应的Mach任务,反之亦然。可以在osfmk/kern/task.h下找到task结构体,其中的bsd_info指向bsd/sys/proc_internal.h中的proc结构体。同样,proc结构体中的task是指向该进程的task结构体的指针。

使用Mach系统调用task_for_pid(),可以将与目标PID相对应的任务端口的发送权限发送给调用方。从bsd/vm/vm_unix.c中的以下注释可以看出,仅允许特权进程或具有相同用户ID的进程调用。除此之外,调用此API还需要某些权限(entitlement):get-task-allow和task_for_pid-allow。

你将在这里注意到的另一件事是对pid=0的检查。这样做是为了防止调用task_for_pid(0)获得到内核任务端口的发送权限(tfp0)。以前一旦能够任意读写内核内存,越狱程序就会patch该检查并调用task_for_pid(0)。但是随着KPP和AMCC/KTRR的出现,不再能patch内核,因此使用了其它技术。tfp0这个叫法仍然存在,用于表示读写内核内存。
另一个非常常用的API是Mach系统调用pid_for_task(),用于查找与给定Mach任务相对应的进程的pid。它通常做的是查找task结构体,查找其中的bsd_info指向的内核中的proc结构体,并从proc结构体中读取p_pid。此技术已被广泛用于通过创建伪造的任务端口一次读取任意四个字节的内核内存(因为pid字段为32位),本文稍后将对此进行讨论。

内核任务端口

内核被分配了PID 0,相应的无进程任务被称为内核任务。拥有内核任务的发送权限可以完全控制内核内存,对内核内存进行读写,还可以通过分配内存来注入任意代码。这就是EXP所试图获得的。
如前所述,调用task_for_pid(0)的较早方法之一是patch对pid=0的检查。macOS上不安全内核(#if defined SECURE_KERNEL)也有一个processer_set_tasks() API会将内核任务端口作为第一个参数返回。
获得内核任务端口后,以下五个MACH API经常用于与内存进行交互。重要的是要注意,要成功执行此函数,调用者必须拥有目标任务的任务端口的发送权限。如果查看函数原型,则第一个参数是目标任务(vm_map_t target_task)。可以将内核任务端口(mach_port_t tfp0)作为第一个参数传递给它。

hsp4 Patch

Apple为防止越狱者获得内核任务而实施的另一项技术是对kernel_task的指针检查。即使获得了内核任务的句柄也无法使用Mach VM调用。检查从ipc_kmsg_trace_send函数开始,它会调用osfmk/kern/ipc_kobject.c中的convert_port_to_task_with_exec_token函数(第356行)。

然后,convert_port_to_task_with_exec_token函数调用task_conversion_eval函数。

这里就是检查的地方。caller是想要操作任务端口的任务,victim是被操作的任务。首先检查caller是否为内核,如果是则返回成功。然后检查caller是否与victim相同,因为任务应该能够对其自身执行操作。第三次检查就是关键的地方了,如果你想要操作kernel_task而自己不是kernel_task,则检查不会通过。不过这只是对kernel_task指针进行检查实现的。

因此尽管能够得到内核任务,但是你将无法在其上调用Mach API,因为这里会返回KERN_INVALID_SECURITY,从而之前的函数将返回TASK_NULL。顺便说一句,在嵌入式平台上,代码会检查代码签名中的TF_PLATFORM标志,它是platform-application entitlement,这意味着没有此entitlement的caller无法对拥有这项entitlement的victim执行操作(只有Apple的文件才有这个entitlement)。我们将在本系列的第3部分中对此进行讨论。
因此,较新的技术之一就是使用host_get_special_port()函数。来看看osfmk/mach/host_special_ports.h。

它包含一些特殊端口,你可能已经从注释中猜到了这些端口用于特殊目的。从注释中可以看出前七个端口是为内核本身保留的。但是到目前为止,仅使用了其中的三个。HOST_PORT提供了主机的抽象,HOST_PRIV用于特权操作,而HOST_IO_MASTER_PORT用于与设备进行交互。每个特殊端口都有特定的编号,这非常重要。我们可以注意到没有使用编号4。
另一件值得一提的事是为了获得发送到主机特殊端口的权限需要使用一个int参数调用host_get_special_port,该参数是分配给该特殊端口的编号。

查看该函数,我们可以看到它需要host_priv端口作为参数,因此除了满足所有沙盒检查之外,执行此调用还需要root权限。host_get_special_port函数从realhost.special[node]得到端口值并返回给调用者。
回到指针检查,如果我们可以重新映射内核任务,将其写入未使用的端口空间,即realhost.special[4],然后调用host_get_special_port(4),这应该就可以得到可用的内核任务。
Siguza编写的cl0ver中的以下代码就是这样做的。

该技术也称为hsp4 patch,广泛用于最近的一些越狱中。

伪造任务端口

最近一些越狱中使用的最常见技术之一是使用伪造的端口。这样做可以让内核查找用户控制的内存空间。然后使用某些API可以从内核中提取数据。
让我们看一下osfmk/ipc/ipc_port.h中定义的端口结构体。

第一个成员是ipc_object,可以在osfmk/ipc/ipc_object.h中找到其定义。

第一个成员是io_bits,可以在osfmk/ipc/ipc_object.h中找到这些位的详细信息。

需要设置IO_BITS_ACTIVE以确保该对象存活。IO_BITS_OTYPE指定对象类型。IO_BITS_KOTYPE指定端口类型,是一个任务端口或时钟端口等。创建伪造的端口时需要在io_bits中指定这些值。完整列表可以在osfmk/kern/ipc_kobject.h中找到。

设置端口的io_bits如下所示。

ipc_object的io_references也需要被设置为不为0的值确保该对象不释放。
回到端口结构体,另一个重要成员是struct ipc_space * receiver,它指向ipc_space结构体。任务的ipc_space结构体定义其IPC功能。每个IPC功能都由一个ipc_entry表示,并放在一个表中,ipc_space结构体中的is_table字段指向该表。is_table中的端口权限或功能为16位,其名称实际上是is_table的索引。重要的是注意在内核中,端口权限(mach_port_t)通过传递指向适当的端口数据结构体(ipc_port_t)的指针来表示。

ipc_space是非常重要的结构体,因此,大多数EXP程序都会寻找内核ipc_space以便获得适当的(但仍为假的)内核任务端口。方法是将ipc_space_kernel复制到新的内存,并使假的端口的receiver字段指向该内存。
根据io_bits字段中设置的kobject类型,kobject字段指向不同的数据结构。因此如果伪造任务端口则需要将kobject字段指向struct task,如果是时钟,则指向struct clock。
下面是async_wake EXP中相关的代码。

想了解更多详细信息,我强烈建议查看CanSecWest的演讲

pid_for_task()任意读取

如前所述,pid_for_task()将给出相应任务的PID。假设p_pid字段的偏移量为0x10,并且假设要读取的地址为addr,则可以创建一个伪造端口,然后将其关联到假的任务,使得该任务中的bsd_info字段为addr-0x10。
下面是voucher_swap EXP中相关的代码。

调用两次就可以读取64bit。

注意偏移量会随着iOS版本以及设备的不同而变化。这些偏移量既可以通过查看内核源代码来找到,也可以通过查看kernelcache文件来找到。
此技术非常强大,可一次读取4个字节的内核内存。还可以用该函数找到内核偏移。需要做的仅仅是每次向后读取四个字节的内核内存,直到获得魔术值0xfeedfacf为止。该地址是内核基地址,减去用IDA或Hopper打开kernelcache时的起始地址就得到了偏移量。下面是Yalu EXP中相关的代码。

获得了内核基地址就可以在内核内存中找到一些重要的结构体,例如extern struct proclist allproc;。可以在/bsd/sys/proc_internal.h中找到它。即使存在KASLR,它相对内核基地址的偏移总是固定的。正如我们从内核代码中看到的那样,该结构体包含进程的列表。也可以使用jtool2 –analyze命令找到符号地址,因为Apple错误地发布了含有符号的kernelcache。

然后,可以再次使用pid_for_task()来遍历这些结构体,通过检查pid = getpid()找到当前proc结构体(这样我们以后可以更改当前proc的cred以逃逸沙盒),并通过检查pid = 0来找到内核proc结构体(这样我们就可以获得内核proc的cred,找到 kernel task,ipc_space_kernel等)。

堆分配基础

下面是关于iOS中堆分配的非常简短的讨论。在iOS中,堆内存分为多个zone。相同大小的分配使用相同的zone,除非某些对象具有自己的特殊zone(ports,vouchers等)。这些zone随着分配对象增多而增长,并且从zone map中获得新页。在macOS上,可以使用zprint命令看到分配的zone。许多堆分配技术在iOS中仍然相同。另一件事是要注意,iOS也具有zone垃圾回收。

如上所述,某些对象具有自己的特殊zone。zone是固定大小的数据块的集合,可以对其进行快速分配和释放。例如,在下图中,我们可以看到很多IPC对象,包括ports,vouchers等都有自己的zone。因此如果释放一个voucher那么将无法使用另一种对象占用释放的内存,除非触发zone垃圾回收并将包含该地址的页移动到其它位置,然后再分配另一种对象。

在最近的几个iOS版本中对堆采取了很多加固措施。我强烈建议阅读这个Stefan Esser关于iOS内核堆的PPT,你也可以阅读内核源代码。可以从osfmk/kern/zalloc.c开始,其中有一些堆分配的注释。

最近用于heap spray的常用技术之一是通过发送带有选项MACH_MSG_OOL_PORTS_DESCRIPTOR的Mach消息,以端口指针数组填充内存。这将调用ipc/ipc_kmsg.c中的ipc_kmsg_copyin_ool_ports_descriptor,它会调用kalloc(ports_length)将端口指针填充到堆中。在voucher_swap EXP中可以看到这样做的好处:虽然分配端口会将它们放入ipc.port zone,但是如果是端口指针就不是这样,因此可以使用端口指针占用释放的对象。用端口其实也可以,因为进行足够的喷射之后能够迫使内核进行垃圾回收并从zone map中分配新页,其中可能包括释放的对象。本系列的第2部分将对此进行讨论。

mach_msg_descriptor_t *
    ipc_kmsg_copyin_ool_ports_descriptor(
        mach_msg_ool_ports_descriptor_t *dsc,
        mach_msg_descriptor_t *user_dsc,
        int is_64bit,
        vm_map_t map,
    .....................
        dsc->address = NULL;  /* for now */

        data = kalloc(ports_length);

        if (data == NULL) {
            *mr = MACH_SEND_NO_BUFFER;
            return NULL;
        }

指针验证检查和CoreTrust

ARM 8.3指令集添加了一个称为PAC(Pointer Authentication Check,指针验证检查)的新功能,目的是检查指针的完整性。它将加密签名附加到指针值里未使用的位中,然后在使用指针之前验证这些签名。由于攻击者没有用于为这些指针创建签名的密钥,因此无法创建有效的指针。
另一方面,CoreTrust是一个单独的内核扩展(com.apple.kext.CoreTrust),它不允许自签名二进制文件(jtool2 -sign)在设备上运行。以前,Apple Mobile File Integrity Kext(AMFI.kext)与用户态amfid守护程序一起检查代码签名。可以通过将代码签名哈希注入AMFI trust cache,hook amfid异常端口并允许代码继续执行等多种方式来绕过。CoreTrust施加了一些其他检查,这些检查仅允许Apple签名的二进制文件在设备上运行。

结论

在本文中,我们研究了iOS安全的一些基本基础知识,它们将作为接下来两篇文章的基础。下一篇文章将详细讨论voucher_swap,而第三部分将讨论越狱。

参考文献

1.Project Zero Issue tracker - https://bugs.chromium.org/p/project-zero/issues/detail?id=1731
2.iOS 10 - Kernel Heap Revisited - https://gsec.hitb.org/materials/sg2016/D2%20-%20Stefan%20Esser%20-%20iOS%2010%20Kernel%20Heap%20Revisited.pdf
3.Mac OS X Internals: A Systems Approach - https://www.amazon.com/Mac-OS-Internals-Approach-paperback/dp/0134426541
4.MacOS and iOS Internals, Volume III: Security & Insecurity: https://www.amazon.com/MacOS-iOS-Internals-III-Insecurity/dp/0991055535
5.CanSecWest 2017 - Port(al) to the iOS Core - https://www.slideshare.net/i0n1c/cansecwest-2017-portal-to-the-ios-core


文章来源: http://xz.aliyun.com/t/8509
如有侵权请联系:admin#unsafe.sh