Titan is a PPL & SPTM bypass for iOS 16.1 - 17.4 beta 3, supporting A14 - A17. It is based on the Rocket exploit that was deployed in the Coruna in-the-wild exploit kit. Titan first gains code execution on the AGX coprocessor in the form of a ROP chain and then uses this to stage the microPPL bypass. After the microPPL has been bypassed, it maps an AP kernel page table and creates a self-referencing page table entry, which provides a full defeat of PPL & SPTM.
Titan requires two secondary primitives:
kalloc- the ability to make permanent kernel memory allocations (they must never be deallocated)kmap- the ability to map arbitrary physical pages into userspace (within the bounds of PPL/SPTM)
Aside from these, Titan uses an IPC port stealing primitive implemented here. This was taken from Coruna's kernel exploitation implant and later patched in iOS 26 by adding data PAC to ipc_port->ip_nsrequest.
Initial code execution is obtained by overwriting the thread state pointer inside the AGX's power thread's structure. When this thread is subsequently awoken (which happens almost instantaneously), it will load and execute a completely controlled thread state.
The main ROP invocation gadget used is one that will load an entire register state from the stack, including both X30 and ELR_EL1, and then perform an ERET. It's used, as denoted by the ERET, during exception handling.
ADD X1, SP, #0x110
LDP X2, X3, [X1,#-0x10]
LDR X4, [X1],#8
MSR CPACR_EL1, X4
LDP X5, X6, [X1]
MSR FPSR, X5
MSR FPCR, X6
LDP Q0, Q1, [X1,#0x10]
...
LDP Q30, Q31, [X1,#0x1F0]
ADD X1, X1, #0x210
LDP X5, X6, [X1]
LDR W7, [X1,#0x10]
MSR ELR_EL1, X2
MSR SPSR_EL1, X3
MSR FAR_EL1, X5
MSR PAR_EL1, X6
MSR ESR_EL1, X7
LDP X6, X7, [SP,#0x30]
LDP X8, X9, [SP,#0x40]
LDP X10, X11, [SP,#0x50]
LDP X12, X13, [SP,#0x60]
LDP X14, X15, [SP,#0x70]
LDP X16, X17, [SP,#0x80]
LDR X18, [SP,#0x90]
LDP X29, X30, [SP,#0xE8]
LDP X0, X1, [SP]
LDP X2, X3, [SP,#0x10]
LDP X4, X5, [SP,#0x20]
ADD SP, SP, #0x350
ERET
This provides us with an easy way to execute a series of controlled register states as ROP frames. We place our desired jump address into ELR_EL1, so that the AGX will begin executing there on ERET. Then, we set X30 to the address of the main ROP gadget so that it will return from our arbitrary call, increment the stack pointer and load the next ROP frame.
This primitive was patched in iOS 18.1 beta 4 via the introduction of instruction PAC to the AGX. Now, it will verify the thread state's signature using PACGA to ensure that important registers (PC, LR, CPSR, X16 and X17) have not been modified, just like XNU.
The microPPL bypass is trivial to understand, and an extremely obvious oversight by Apple. When the AGX enters hibernation (i.e. on core reset), it will cache its register state in memory so that it can restore it on wake. Included in this register state are the values in TTBR0_EL1 and TTBR1_EL1, which will be loaded from memory and restored into their respective registers when the AGX resumes execution after hibernation.
We use our initial AGX code execution to modify the hibernation context so that it will load a different TTBR1_EL1 value that we supply (by setting up fake page tables on the AP). Then, we also modify the cached stack pointer so that it will begin executing a second ROP chain after hibernation (as it hits the ROP gadget we use above). The new page tables will map an XNU page table into the AGX's address space, and then the second ROP chain will write a controlled value to the new mapping. This will be a page table entry that maps the XNU page table to itself.
This bypass was patched in iOS 17.4 beta 4. Now, they don't even restore TTBR0_EL1 at all, and they restore TTBR1_EL1 to a hardcoded value.
At this point, on the AP, we can now write to a kernel page table with our existing kernel read/write primitives, allowing us to map and access any page of physical memory, including those protected by PPL & SPTM. From here it is relatively straightforward to implement into an existing jailbreak flow and setup stable PPLRW/SPTM-RW primitives in your process too.
- littlelailo - for his writeup on the Coruna chain which provided a fantastic base knowledge for my reimplementation efforts, as well as answering countless questions I had as I battled with the AGX during development!
- staturnz & Clarity - assisting me in reverse engineering and exploitation throughout the development of Titan.
- Google Threat Intelligence Group & iVerify - for their disclosure, analysis and publications about the chain.
- wh1te4ever & khanhduytran0 - for their assistance in obtaining an exploit sample for me to analyse.
- The exploit kit developers - for some great bugs, and even better exploits. I won't provide attribution here, but it's not hard to find if you go looking online. 😉
This bypass will not work out-of-the-box. It is designed to be used with Dopamine's libjailbreak, and you can add its headers and .tbd file into external/ to compile. Using Dopamine's kernel read/write primitives provided in a jailbroken environment will cause the bypass to fail, because they only work on DRAM addresses that are within the range of gPhysBase and gPhysBase + gPhysSize, which is not the case for several AGX-related pages.
Titan is available to use in Dopamine 3.0 and newer on supported devices.