SkillAgentSearch skills...

Ktrw

An iOS kernel debugger based on a KTRR bypass for A11 iPhones; works with LLDB and IDA Pro.

Install / Use

/learn @googleprojectzero/Ktrw
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

KTRW

KTRW is an iOS kernel debugger for devices with an A11 SoC, such as the iPhone 8. It demonstrates how to use debug registers present on these devices to bypass KTRR, remap the kernel as writable, and load a kernel extension that implements a GDB stub, allowing full-featured kernel debugging with LLDB or IDA Pro over a standard Lightning to USB cable.

With the release of the checkra1n jailbreak and pongoOS pre-boot environment, it is possible to load KTRW as a kernel extension directly into the iOS kernelcache, so the original KTRR bypass is no longer used.

Bypassing KTRR

KTRR was introduced with the A10 as a means of locking down critical kernel data (including all executable code) to prevent it from being modified, even by an attacker with a kernel memory read/write capability. However, on A11 SoCs, the ARMv8 External Debug registers and a proprietary register called DBGWRAP were left enabled. This makes it possible to subvert execution of the reset vector on these devices, skipping the MMU's KTRR initialization and setting a custom page table base that remaps the kernel as writable. Once KTRR has been disabled, it becomes possible to execute dynamically loaded kernel code, i.e., load kernel extensions.

Note that even though the kernel is remapped as writable, the physical pages spanned by the AMCC RoRgn remain protected by the memory controller, and thus writes to these physical pages will be discarded. Bypassing KTRR on the MMU does not defeat KTRR on the AMCC, and thus the only way to remap the kernel as writable is to copy the kernel data in the AMCC RoRgn onto new, writable physical pages. But since the AMCC is still protecting the original physical pages, and since the reset vector executes from a physical address inside the AMCC RoRgn, the reset vector cannot be persistently modified to disable KTRR automatically on reset without a more powerful capability (such as a bootchain vulnerability). Thus, the KTRR bypass will disappear once the core resets normally (that is, without being hijacked using the debug registers from another core). This means that the KTRR bypass is not persistent: it will be lost once the device sleeps.

Using KTRW

KTRW consists of four components: the pongo_kext_loader utility, the kextload.pongo-module pongoOS module, the ktrw_gdb_stub.ikext kernel extension, and the ktrw_usb_proxy USB-to-TCP proxy utility. These can be built individually by ruunning make in each subdirectory or collectively by running make in the top-level directory.

To use KTRW, we'll run three utilities: checkra1n, pongo_kext_loader, and ktrw_usb_proxy.

Checkra1n uses the checkm8 SecureROM exploit to establish a pre-boot environment called pongoOS capable of loading arbitrary code modules and patching the kernel. Running the following command causes checkra1n to listen for attached iOS devices in DFU mode and boot pongoOS:

$ /Applications/checkra1n.app/Contents/MacOS/checkra1n -c -p

By itself pongoOS does not provide the ability to insert XNU kernel extensions into the kernelcache on the device; in order to do this we use pongo_kext_loader and kextload.pongo-module. The kextload pongoOS module adds two new commands to the pongoOS shell: kernelcache-symbols, which allows uploading a symbol table to resolve kernel symbols, and kextload, which inserts an uploaded kernel extension into the kernelcache. The pongo_kext_loader utility glues everything together: it listens for attached pongoOS devices, loads kextload.pongo-module, inserts the ktrw_gdb_stub.ikext kernel extension into the iOS kernelcache, and boots XNU.

Run the following command to have pongo_kext_loader load ktrw_gdb_stub.ikext on the iOS device at boot:

$ pongo_kext_loader/pongo_kext_loader \
	pongo_kextload/kextload.pongo-module \
	ktrw_gdb_stub/kernel_symbols \
	ktrw_gdb_stub/ktrw_gdb_stub.ikext

The final utility that needs to run is ktrw_usb_proxy. ktrw_usb_proxy is needed to communicate with the kernel extension over USB and relay the data over TCP so that LLDB can connect to it. It will print the data being exchanged over the connection to stdout. Run ktrw_usb_proxy with the port number LLDB will connect to:

$ ktrw_usb_proxy/ktrw_usb_proxy 39399

Finally, connect an iOS device using a USB cable and enter DFU mode. First checkra1n will boot pongoOS, then pongo_kext_loader will insert the KTRW GDB stub kernel extension, and finally XNU will boot. The GDB stub will start running about 30 seconds after the kernel starts booting to give the system time to initialize. (This delay can be configured during build using the ACTIVATION_DELAY make variable.)

Once the GDB stub runs, it will claim one CPU core for itself and halt the remaining cores. It will also hijack the Synopsys USB 2.0 OTG controller from the kernel so that it can communicate with the host. As a result, the host will not see the iPhone as an iOS device and the phone (once it has been resumed) will not be able to send data over USB as normal.

At this point, you are ready to debug the device.

A few common issues:

  • If you're having trouble entering DFU mode using a USB C cable, try using a USB A cable instead.
  • If the device immediately panics when the GDB stub starts to run, the issue may be that the USB hardware is not yet powered. This is likely to be the case if the device has a passcode and the "USB Accessories" setting is disabled to prevent USB accessories from connecting when the device is locked. Either disabling the passcode, allowing USB accessories, or unlocking the device before the GDB stub starts should solve the issue. Try changing ACTIVATION_DELAY if you need more time to unlock the device.
  • Similarly, do not unplug the device while KTRW is running, as this powers down the USB hardware.
  • If LLDB does not automatically detect that the target is an iOS kernelcache, it's possible that the system halted while a CPU core was in userspace. Try continuing and re-interrupting the target until all CPU cores are running in kernel mode, then reattach to the target.
  • Sometimes the screen becomes unresponsive right after connecting LLDB. I have been unable to identify/fix the root cause of this issue, but it seems to help if you connect with LLDB as soon as the GDB stub first halts the device, then immediately continue to minimize the amount of time the system is stopped during this initial halt.
  • KTRW is incompatible with on-device debugging, e.g. using Xcode or debugserver to debug a process on the device.
  • The pongoOS kernel extension loading module currently has several limitations: it will only work on new-style kernelcaches and only one kernel extension can be inserted into the kernelcache.

The method described here has been tested as working on iOS 13.3. Prior versions of KTRW used a KTRR bypass to load kernel extensions rather than relying on checkra1n; ktrw_kext_loader implements this technique, and it should be used instead when debugging iOS 12.

Debugging with LLDB

Use LLDB to connect to ktrw_usb_proxy and communicate with ktrw_gdb_stub.ikext. Here I have connected to an iPhone 8 running iOS 12.1.2:

$ lldb kernelcache.iPhone10,1.16C101
(lldb) target create "kernelcache.iPhone10,1.16C101"
Current executable set to 'kernelcache.iPhone10,1.16C101' (arm64).
(lldb) settings set plugin.dynamic-loader.darwin-kernel.load-kexts false
(lldb) gdb-remote 39399
Kernel UUID: 94463A80-7B38-3176-8872-0B8E344C7138
Load Address: 0xfffffff027e04000
Kernel slid 0x20e00000 in memory.
Loaded kernel file kernelcache.iPhone10,1.16C101
Process 2 stopped
Target 0: (kernelcache.iPhone10,1.16C101) stopped.
(lldb)

You can use thread list to list the code running on each physical CPU core. (Note that one core is reserved for the debugger itself, so it will not show up in the list.)

(lldb) th l
Process 2 stopped
* thread #1: tid = 0x0002, 0xfffffff027ffda18 kernelcache.iPhone10,1.16C101`___lldb_unnamed_symbol1734$$kernelcache.iPhone10,1.16C101 + 272
  thread #2: tid = 0x0003, 0xfffffff027ffda18 kernelcache.iPhone10,1.16C101`___lldb_unnamed_symbol1734$$kernelcache.iPhone10,1.16C101 + 272
  thread #3: tid = 0x0004, 0xfffffff027ffda18 kernelcache.iPhone10,1.16C101`___lldb_unnamed_symbol1734$$kernelcache.iPhone10,1.16C101 + 272
  thread #4: tid = 0x0005, 0xfffffff027ffda18 kernelcache.iPhone10,1.16C101`___lldb_unnamed_symbol1734$$kernelcache.iPhone10,1.16C101 + 272
  thread #5: tid = 0x0006, 0xfffffff027ffda18 kernelcache.iPhone10,1.16C101`___lldb_unnamed_symbol1734$$kernelcache.iPhone10,1.16C101 + 272
(lldb)

Because KTRR has been disabled, it is possible to patch kernel memory:

(lldb) x/12wx 0xfffffff027e04000
0xfffffff027e04000: 0xfeedfacf 0x0100000c 0x00000000 0x00000002
0xfffffff027e04010: 0x00000016 0x00001068 0x00200001 0x00000000
0xfffffff027e04020: 0x00000019 0x00000188 0x45545f5f 0x00005458
(lldb) mem wr -s 4 0xfffffff027e04000 0x11223344 0x55667788
(lldb) x/12wx 0xfffffff027e04000
0xfffffff027e04000: 0x11223344 0x55667788 0x00000000 0x00000002
0xfffffff027e04010: 0x00000016 0x00001068 0x00200001 0x00000000
0xfffffff027e04020: 0x00000019 0x00000188 0x45545f5f 0x00005458

Resume executing the kernel with continue. You can interrupt it at any time with ^C:

(lldb) c
Process 2 resuming
(lldb) ^C
Process 2 stopped
Target 0: (kernelcache.iPhone10,1.16C101) stopped.
(lldb)

You can set breakpoints as usual. KTRW currently only supports hardware breakpoints, but LLD

View on GitHub
GitHub Stars690
CategoryDevelopment
Updated1d ago
Forks131

Languages

C

Security Score

95/100

Audited on Mar 29, 2026

No findings