random About
Introduced with iOS9, the kernel integrity protection aka watchtower aka KPP, posed a new
problem to arm64 jailbreaks. Effectively, it was observed that after patching the kernel code
- customary in older jailbreaks - the device would silently panic after a while. It soon
became obvious that some thing was checking the kernel code. That thing was outside the
kernel, and it became evident pretty quickly that there was a kind of hypervisor at work.
To this day, I’m still surprised nobody did a write-up on the KPP – at least none that I know
of. So, I will try to explain how the hypervision works. Specific implementation details will
maybe come at a later time, provided I have the time for it. Now, without further ado, here
we go…
EL3
_start(monitor_boot_args *mba)
struct monitor_boot_args {
uint64_t version;
uint64_t virtBase;
uint64_t physBase;
uint64_t memSize;
struct kernel_boot_args *kernArgs;
uint64_t kernEntry;
uint64_t kernPhysBase;
uint64_t kernPhysSlide;
uint64_t kernVirtSlide;
};
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html 1/7
2017-4-14 Tick (FPU) Tock (IRQ)
The KPP overwrites its own Mach-O header at 0x4100000000 with a trampoline which
calls _start(NULL) and installs two exception handlers, sync_handler and
irq_handler . Recall the AArch64 exception table:
Finally, if enabled – which always is – it sets (among others) the following registers:
EL1
Kernel starts executing in EL1:
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html 2/7
2017-4-14 Tick (FPU) Tock (IRQ)
EL3
sync_handler:
When something goes wrong, FAIL(code) sets a global variable violated and signals
the kernel by:
Code Meaning
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html 3/7
2017-4-14 Tick (FPU) Tock (IRQ)
1 violation in frame
2 bad syscall
3 not locked
4 already locked
5 software request
6 invalid TTE/PTE
7 violation in mapping
8 violation in system register
Execution is then transferred down as SError back into kernel’s ExceptionTable:
Otherwise, if everything is fine and dandy, execution resumes in the kernel, right after
monitor_call() .
This was the setup phase, required in order to let the kernel set-up the write-once memory
locations. Next, onto the heartbeat phase…
EL1
In the running kernel, fleh_synchronous() fleh_irq() fleh_fiq() and
fleh_serror() all eventually tail to
EL3
This time, it means business.
sync_handler:
(continued)
else if (ESR_EL3 == 0x62340400) { // ESR_EL3.EC==0x18 && ESR_EL3.IL==1 && ESR_EL
if (violated) {
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html 4/7
2017-4-14 Tick (FPU) Tock (IRQ)
FAIL(1);
}
if (!locked) {
FAIL(3);
}
if (!(++number_of_hits & watchtower_throttle)) {
if (!(++flip_flop & 1)) {
if (hash_is_ready) {
blake2b_final(&hash, digest);
if (memcmp(cur->digest, digest, 32)) {
FAIL(1);
}
cur = get_next_region();
if (!cur) {
cur = get_first_region();
}
cur_data_ptr = cur->base;
cur_data_left = cur->size;
blake2b_init(&hash);
hash_is_ready = 0;
} else {
chunk = min(cur_data_left, 128);
blake2b_update(&hash, cur_data_ptr, chunk);
cur_data_ptr += chunk;
cur_data_left -= chunk;
if (!cur_data_left) {
hash_is_ready = 1;
}
}
} else {
/* walk and check TTE/PTE
* verify map list
* check system registers SCTLR_EL1, TCR_EL1, TTBR1_EL1, VBAR_EL1
*/
...
}
}
ELR_EL3 += 4; // skip insn
CPTR_EL3 = 0; // CPTR_EL3.TCPAC=0, accesses to CPACR_EL1 will not tr
CPACR_EL1 = 0x300000; // CPACR_EL1.FPEN=3, does not cause any FPU instructio
SCR_EL3 = 0x6B3; // SCR_EL3.IRQ=1, When executing at any Exception leve
// SCR_EL3.SMD=1, SMC instructions are UNDEFINED at EL
// SCR_EL3.SIF=1, Secure state instruction fetches fro
return OK;
}
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html 5/7
2017-4-14 Tick (FPU) Tock (IRQ)
As you can see, it skips a few beats once in a while ( watchtower_throttle ==4) to go
easy on CPU usage and/or battery. It also slowly but surely crawl over all protected areas in
a sequential manner.
If all checks pass, the hypervisor disables FPU trapping (allowing the FPU to finally
execute), enables IRQ to EL3 (to make sure it is hit again), resumes kernel right after
CPACR_EL1 hit and waits.
EL1
Kernel/userland runs happily. When the next IRQ fires, it is taken to hypervisor’s EL3 IRQ
handler.
EL3
irq_handler:
That is: reset IRQs back to EL1, re-enable FPU trapping, re-enable trap for CPACR_EL1
accesses.
***
In summary: KPP makes sure the FPU always trap and the trap cannot be disabled. When
FPU hits (tick) the kernel tries to disable the trapping but is immediately taken to KPP. KPP
then runs its checks, frees the FPU, but routes the IRQs to itself. As soon as any IRQ fires
(tock) it makes the FPU trap again and de-routes the IRQs.
This is the engine that keeps the hypervisor beating. If you patch away the trigger, that is,
CPACR_EL1 access, the FPU can’t execute. However, there is one catch. We can “steal”
away the CPACR_EL1 access to a separate trampoline:
1. undo patches
2. hit CPACR_EL1, hypervisor runs and restores execution right after our CPACR_EL1
3. redo patches
4. profit
random
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html 7/7