Created on 5.11.18

Important note: this is the very first bug I’ve ever exploited and, therefore, my first write-up of such kind. If you notice a mistake of any kind, polite criticism is always appreciated

This bug was discovered and exploited bymany years ago. It’s settled in HFS+ driver and was fixed in iOS 6. It is triggered by settingto a high value (e.g.). This makes iBoot try to read a huge chunk of data from HFS+ partition to heap (near the end of it):

The general idea of our exploit is based on this fact — make iBoot overwrite itself with data from a partition, which we fully control. Target IRQ handler address to get arbitrary code execution

iBoot memory is mirrored after everybytes (1 GB) on devices with 1 GB RAM, every(512 MB) on devices with 512 MB RAM and every(256 MB) on devices with 256 MB RAM, don’t know about the others. That means, for example, if iBoot is based at(like in case of), we can also read/write to it at

The exploitation

Note: this guide comes with the : this guide comes with the free exploitation starter kit , which will help you much on this way. It contains parts of HFS+ for initial bug triggering and a source code for payloads

We will use n18ap on iOS 5.1.1 in most of examples in this article

Create a third partition, we don’t want to corrupt main root filesystem

Delete from NVRAM anything that you feel isn’t needed (we’ll talk about the reasons behind that later)

Restore iOS 5.1.1 root filesystem for n18ap to the third partition

Replace the value at offset 0x52C in restored HFS by a large number ( 0x10000000 should do fine), and a value at 0x528 by a low number (e.g. 0x10 ). Note that all values in HFS+ header are big-endian

in restored HFS by a large number ( should do fine), and a value at by a low number (e.g. ). Note that all values in HFS+ header are big-endian Take a target iBoot, patch its boot-command to be upgrade

to be Add boot-commanb (this is not a typo) variable with upgrade value to NVRAM

(this is not a typo) variable with value to NVRAM Boot the iBoot Let’s start with simply triggering the bug. This is pretty easy:

Shortly after the iBoot tries to mount the filesystem and load iBEC from it, you’ll get a panic: panic: arm_exception_abort: ARM data abort abort in supervisor mode at 0x4ff172b8 due to translation error:

far 0x50000000 fsr 0x00000805

r0 0x50000000 0x600000d3 0x00000001 0x4ff69ee0

r4 0x50001180 0x50000040 0x00000003 0x4ff3716c

r8 0x4ff172b8 0x00000cfc 0x00000001 0x4ffff180 0x00000000

sp 0x4ff3715c lr 0x4ff1cd97 spsr 0x000000d3

Yes, UART-cable is highly recommended for exploiting this bug. It’s still possible to succeed without it, but much harder. I’ll give some advice in the end of the guide



The only logical explanation would be something being overwritten between our heap buffer and 0x60000000 [this is about s5l8930x] is causing it to crash once it gets to 0x60000000. Dumping a lot of memory any analyzing it shows there's really only 2 distinct structure between our heap buffer (it's the last buffer allocated in heap so we're not corrupting any other heap buffers). These 2 structures are the stack (but we already know there was no stack corruption based on our earlier analysis), and a special structure in ARM called TLB (Translation Look-aside Buffer). The TLB is a specialized cache that holds a table of physical addresses which are mapped to virtual addresses in the system memory. In iBEC this structure starts at address 0x5FFF8000. This would make since because reason for the crash from the report states "Domain Error". A little research in the ARM technical reference shows that this happens when an illegal virtual to physical page mapping is attempted to be looked up. The TLB is essentially one 32bit address containing representing each physical to virtual mapping on the system Extract from @p0sixninja’s write-up about the panic:

How can we avoid corrupting the TLB? Just rewrite it with a copy from the HFS partition. You're probably wondering now — how can we put TLB at random place in HFS? Won't it ruin its' structure? And even if it won’t, how do we know where to put it? These questions share an answer - a wrapper for HFSReadBlock()

The wrapper We have to know from where, to where and how many bytes iBoot reads from a partition with a filesystem. For that purpose we’ll create a tiny wrapper for HFSReadBlock()

HFSReadBlock() is a very simple function, which just extracts the address of blockdev_read_hook() from the block device descriptor and passes its' own args to it: is a very simple function, which just extracts the address offrom the block device descriptor and passes its' own args to it:

HFSInitPartition() flow: The function is very easy to locate, it's used to read the HFS+ header in the very beginning of theflow:

Our aim is to make a function with the same functionality plus printing args passed to it. Luckily for you, I already wrote one and it’s available in the starter kit

Pass your offsets - printf is obviously offset of printf()+1 (because we’re in Thumb) and BASEADDR is where the payload is going to be located

HFSReadBlock(), so the payload will be executed every time: Good place to land it is around build banner’s strings. Put a branch (not with link! This is very important) to our wrapper payload right at the top of, so the payload will be executed every time:

Apply patches and boot the iBoot. Now on serial you’ll see this: HFSInitPartition: 0x4ff57e00

HFSReadBlock: offset 0x400 size 0x200 buffer 0x4ff30efc

returned: 0x200

HFSReadBlock: offset 0x100a000 size 0x100 buffer 0x4ff310fc

returned: 0x100

HFSReadBlock: offset 0x80a000 size 0x100 buffer 0x4ff311fc

returned: 0x100

HFSReadBlock: offset 0x1214000 size 0x2000 buffer 0x4ffaf180

returned: 0x2000

HFSReadBlock: offset 0x28e000 size 0xffd92000 buffer 0x4ffaf180

panic: arm_exception_abort: ARM data abort abort in supervisor mode at 0x4ff172b8 due to translation error:

far 0x50000000 fsr 0x00000805

r0 0x50000000 0x600000d3 0x00000001 0x4ff69ee0

r4 0x50001180 0x50000040 0x00000003 0x4ff3716c

r8 0x4ff172b8 0x00000cfc 0x00000001 0x4ffff180 0x00000000

sp 0x4ff3715c lr 0x4ff1cd97 spsr 0x000000d3 For now we’re only interested in the last call: For now we’re only interested in the last call: HFSReadBlock: offset 0x28e000 size 0xffd92000 buffer 0x4ffaf180

Now you know the precise location of a buffer iBoot tries to read the filesystem to

The TLB Dump the stock TLB. Its location is always iBoot base address plus 0xF8000, so it’s 0x4FFF8000 in my case. 0x4000 (16 KB) of size. Once you've dumped it, calculate where it should be within the filesystem:

0x4FFF8000 - 0x4FFAF180 = 0x48E80 between heap address and TLB

0x48E80 + 0x28E000 = 0x2D6E80 exact position in our HFS+ partition



An address you’ll get most likely won’t be divided by block size without a remainder. And all operations with disk nodes on pre-PPN devices must be blocksize-aligned. Calculate on which block it has to be and with what padding on both sides:

0x2D6E80 / 0x2000 = 0x16B (360) block

0x2D6E80 - 0x16B * 0x2000 = 0xE80 padding on top



Wrap it in a block with calculated paddings consisted of zeroes and write to the filesystem

dd if=/block_363 of=/dev/rdisk0s1s3 bs=8192 seek=363

Boot the iBoot. Panic should no longer happen: HFSInitPartition: 0x4ff57e00

HFSReadBlock: offset 0x400 size 0x200 buffer 0x4ff30efc

returned: 0x200

HFSReadBlock: offset 0x100a000 size 0x100 buffer 0x4ff310fc

returned: 0x100

HFSReadBlock: offset 0x80a000 size 0x100 buffer 0x4ff311fc

returned: 0x100

HFSReadBlock: offset 0x1214000 size 0x2000 buffer 0x4ffaf180

returned: 0x2000

HFSReadBlock: offset 0x28e000 size 0xffd92000 buffer 0x4ffaf180

But now it just hangs

The hang Now, nothing interrupts it from reading. At some point you might get weird distortions on your device’s display. This means it reached framebuffer and overwrote it with data from the filesystem

Obviously, we don’t want to wait until it reads enough data to hit mirrored iBoot. There must be a way to speed up this process

The accelerator When create new partition, LwVM (or something else?) marks all its blocks as empty. (Partition isn’t a filesystem!) The real reason for this may differ, but the fact is that iBoot skips all zero marked blocks and reads only those that matter

Dump all the blocks iBoot reads from the FS, delete the third partition, save, recreate it, write all the blocks back at their original positions plus TLB

Boot the iBoot, see how it quickly returns from reading 500+ MB and then starts an infinite loop

The TLB hack Let’s take a look at TLB now. Imagine it’s an array of 32-bit integers. At both TLB[0x0] and TLB[iBoot baseaddr/0x100000] you can see the same record. Put it to TLB[mirrored iBoot baseaddr/0x100000]

By this we’re kinda directly mapping iBoot physical address (0x4FF00000) to the mirrored iBoot virtual address (0x5FF00000). The real explanation is probably different, but the fact is that it increases success rate of the exploit

The overwritten iBoot Calculate where you have to put iBoot image (part of it) in the exploit partition to overwrite it on mirrored address

In my case iBoot base is at 0x4FF00000, so I need to put part of iBoot image in a place in the partition that blockdev_read_hook() will overwrite memory at 0x5FF00000 by it:

0x5FF00000 - 0x4FFAF180 = 0xFF50E80 between heap address and mirrored iBoot

0xFF50E80 + 0x28E000 = 0x101DEE80 exact position in our HFS+

0x101DEE80 / 0x2000 = 0x80EF (33007) block

0x101DEE80 - 0x80EF * 0x2000 = 0xE80 padding on top



In the near future our aim will be overwriting IRQ handler vector, for now we’ll touch reset vector instead. Replace 0E0000EA with FFFFFFFF. If overwriting will be done fine, a task_yield() panic will be immediately called. That way we’ll know how reliably the mirrored iBoot is overwritten

Important: the more bytes of iBoot you overwrite, the more reliable the exploit. It's highly recommended to write about 0x16000 bytes of iBoot to get 100% success rate

Boot the patched iBoot 10 or so times. Every attempt you should get such a panic: panic: task_yield: reset vector overwritten while executing task 'main' (0xffffffff)

The payload If it panics reliably, we can now make it to jump to our payload. The idea is very typical for ARM exploitation - overwrite address of the IRQ vector. In iBoot image for overwriting overwrite value at 0x38 to a pointer to our payload plus 0x1, since our payload is in Thumb (mostly)

The payload calls the real IRQ handler to the handle previous interrupt. Then it restores original address of the IRQ handler, then it prints cool strings and finally jumps to a new fresh iBoot of your choice with whatever patches you would like

In the payload replace offsets and compile with GNU ARM Toolchain. JUMPADDR is an address you'll put a new iBoot to jump to. Where to put it in the partition is your choice, I usually use the very first block iBoot reads in the last reading

The new iBoot Just take a decrypted unpacked iBoot, apply all necessary patches. Choose a position to put the new iBoot very wisely not to overwrite some other block you write