Skip to content

Commit

Permalink
Rewrite TZ driver, TZ0 unlock patch for all devices
Browse files Browse the repository at this point in the history
  • Loading branch information
Siguza committed Sep 8, 2023
1 parent 2add44a commit a2e9cbb
Show file tree
Hide file tree
Showing 8 changed files with 812 additions and 119 deletions.
193 changes: 192 additions & 1 deletion src/boot/patches.S
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,6 @@ In r2:
We search for the pattern, then seek backwards for a "cbz w0, ..." with positive offset. If we find none
within a short range, we skip this match, otherwise we replace that instruction with an unconditional branch.
*/

sym fuse_jump
Expand Down Expand Up @@ -514,3 +513,195 @@ sym fuse_jump
bfi w8, w12, 0, 26
str w8, [x10]
ret


// void antitrust(volatile void *boot_image)

/*
Keep TrustZone unlocked.
There are a total of 4 variants of this patch, and this is just one of them.
In the matrix below:
- s2 = A10+ on iOS 14.0+. Stage2-only.
- XX = A7, any version, and A8-A9 on 10.x and lower. Pongo-only.
- YY = A9X, any version. Pongo-only.
- ZZ = The rest, this is us. The only patch that is shared between stage2 and Pongo.
For A8-A9, ZZ could be used on iOS 8 and 10 as well, but not on iOS 9.
But XX works on those too, so we don't care.
+--------+----+----+-----+----+-----+-----+------+----+-----+
| | A7 | A8 | A8X | A9 | A9X | A10 | A10X | T2 | A11 |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 7 | XX | | | | | | | | |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 8 | XX | XX | XX | | | | | | |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 9 | XX | XX | XX | XX | YY | | | | |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 10 | XX | XX | XX | XX | YY | ZZ | ZZ | | |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 11 | XX | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 12 | XX | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 13 | | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 14 | | ZZ | ZZ | ZZ | YY | s2 | s2 | s2 | s2 |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 15 | | ZZ | ZZ | ZZ | YY | s2 | s2 | s2 | s2 |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 16 | | ZZ | | ZZ | YY | s2 | s2 | s2 | s2 |
+--------+----+----+-----+----+-----+-----+------+----+-----+
| iOS 17 | | ZZ | | | | s2 | s2 | s2 | |
+--------+----+----+-----+----+-----+-----+------+----+-----+
We start by looking for these two instructions:
+---------------------+
| lsr xN, xN, 0xc |
| stur wN, [xM, -0xc] |
+---------------------+
Where N < 16 and M >= 16.
In r2:
/x 00fc4cd300421fb8:10feffff10feffff
After that, we search for a "str wT, [xM]" (same base register, offset 0) within 20 instructions.
We expect one of the two following sequences there:
+----------------+
| str wT, [xM] |
| ldr wS, [xM] |
| tbz wS, 0, ... |
+----------------+
| str wT, [xM] |
| bl ... |
| ldr wS, [xM] |
| tbz wS, 0, ... |
+----------------+
Where S < 16.
Right after that, devices with TZ1 have another block with the same base register but offset 4:
+-----------------+
| str wT, [xM, 4] |
| ldr wS, [xM, 4] |
| tbz wS, 0, ... |
+-----------------+
| str wT, [xM, 4] |
| bl ... |
| ldr wS, [xM, 4] |
| tbz wS, 0, ... |
+-----------------+
Again with S < 16.
We NOP out the str, ldr and tbz in both blocks.
*/

sym antitrust
mov x2, x0 // instr
mov w7, 0xfffffe10
mov w8, 0xd34c0000 // lsr xN, xN, 0xc
movk w8, 0xfc00
mov w9, 0xb81f0000 // stur wN, [xM, -0xc]
movk w9, 0x4200
// Search for pattern
1:
ldr w3, [x2], 0x4
and w4, w3, w7
cmp w4, w8
b.ne 1b

bfi w9, w3, 0, 5
ldr w3, [x2]
and w4, w3, 0xfffffe1f
cmp w4, w9
b.ne 1b

// Search for "str wT, [xM]"
and w7, w3, 0x3e0
movk w7, 0xb900, lsl 16 // str wT, [xM]
add x3, x2, 0x50
2:
ldr w5, [x2, 0x4]!
and w4, w5, 0xffffffe0
cmp w4, w7
b.eq 3f
cmp x2, x3
b.lo 2b
b . // XXX: fail

3:
// Patch 1st write
mov w10, 0xd5030000 // nop
movk w10, 0x201f
str w10, [x2]

// Check for bl
ldr w3, [x2, 0x4]!
ubfx w4, w3, 26, 6
cmp w4, 0x25 // bl
b.ne 4f
ldr w3, [x2, 0x4]!

4:
// 1s load
orr w7, w7, 0x00400000 // str to ldr
and w4, w3, 0xfffffff0
cmp w4, w7
b.ne . // XXX: fail
// Patch 1st load
str w10, [x2]

// 1s tbz
mov w8, 0x36000000 // tbz wS, 0, ...
bfi w8, w3, 0, 5
ldr w3, [x2, 0x4]!
and w3, w3, 0xfffc001f
cmp w3, w8
b.ne . // XXX: fail
// Patch 1st tbz
str w10, [x2]

// Check for 2nd set of str, ldr, tbz
orr w5, w5, 0x400 // [xM] to [xM, 4]
ldr w3, [x2, 0x4]!
cmp w3, w5
b.ne Ligma
// Patch 2nd write
str w10, [x2]

// Check for bl
ldr w3, [x2, 0x4]!
ubfx w4, w3, 26, 6
cmp w4, 0x25 // bl
b.ne 5f
ldr w3, [x2, 0x4]!

5:
// 2nd load
orr w7, w7, 0x400 // [xM] to [xM, 4]
and w4, w3, 0xfffffff0
cmp w4, w7
b.ne . // XXX: fail
// Patch
str w10, [x2]

// 2nd tbz
bfi w8, w3, 0, 5
ldr w3, [x2, 0x4]!
and w3, w3, 0xfffc001f
cmp w3, w8
b.ne . // XXX: fail
// Patch
str w10, [x2]

Ligma:
ret
138 changes: 138 additions & 0 deletions src/boot/stage3.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ void iorvbar_yeet(volatile void *boot_image) __asm__("iorvbar_yeet");
void aes_keygen(volatile void *boot_image) __asm__("aes_keygen");
void recfg_yoink(volatile void *boot_image) __asm__("recfg_yoink");
void fuse_jump(volatile void *boot_image) __asm__("fuse_jump");
void antitrust(volatile void *boot_image) __asm__("antitrust");

extern uint8_t need_to_release_L3_SRAM;

Expand Down Expand Up @@ -139,6 +140,139 @@ void patch_bootloader(void* boot_image)
}
}

// TrustZone patches.
// We have two of them here, see patches.S for a device/version matrix.
bool tz_done = false;
for(volatile uint32_t *p = boot_image, *end = (volatile uint32_t*)((uintptr_t)boot_image + SAFE_TEXT_SIZE); p < end; ++p)
{
uint32_t op1 = p[0],
op2 = p[1],
op3 = p[2];

// A7 (any version) and A8-A9 (iOS 10 and lower).
// We look for the following sequence:
// str wN, [xM]
// ldr wT, [xM]
// tbz wT, 0, ...
// str wN, [xM, 4]
// ldr wS, [xM, 4]
// tbz wS, 0, ...
// On iOS 7 specifically, there is an immediate generated in the middle,
// and thus the second load and store have no offset:
// str wN, [xM]
// ldr wT, [xM]
// tbz wT, 0, ...
// movz xM, 0x200000000
// movk xM, 0x914
// str wN, [xM]
// ldr wS, [xM]
// tbz wS, 0, ...
// We require that T and S are <16, and that the two tbz have positive offset.
// /x 000000b9000040b900000036000400b9000440b900000036:00fcffff10fcffff0000fcff00fcffff10fcffff0000fcff
// /x 000000b9000040b9000000364000c0d2802281f2000000b9000040b900000036:00fcffff10fcffff0000fcffe0ffffffe0ffffff00fcffff10fcffff0000fcff
if((op1 & 0xfffffc00) == 0xb9000000 && (op2 & 0xfffffff0) == ((op1 & 0x000003e0) | 0xb9400000) && (op3 & 0xfffc001f) == ((op2 & 0x0000001f) | 0x36000000))
{
volatile uint32_t *one = p,
*two = p + 3;
uint32_t op4 = two[0],
op5 = two[1];
uint32_t imm = 1; // 1 << 2
if(op4 == (((op1 & 0x000003e0) >> 5) | 0xd2c00040) && op5 == (((op1 & 0x000003e0) >> 5) | 0xf2812280))
{
two += 2;
op4 = two[0];
op5 = two[1];
imm = 0;
}
if(op4 == ((op1 & 0x000003ff) | (imm << 10) | 0xb9000000) && (op5 & 0xfffffff0) == ((op1 & 0x000003e0) | (imm << 10) | 0xb9400000) && (two[2] & 0xfffc001f) == ((op5 & 0x0000001f) | 0x36000000))
{
// Nop them all out.
one[0] = 0xd503201f;
one[1] = 0xd503201f;
one[2] = 0xd503201f;
two[0] = 0xd503201f;
two[1] = 0xd503201f;
two[2] = 0xd503201f;
tz_done = true;
break;
}
}
// A9X patch, any version.
// The reason A9X is separate is because it has two sets of TZ registers
// rather than just one, which makes for *very* different codegen!
// The signature sequence we look for is:
// add xS, xD, 0x10
// orr xN, xM, xS
// str wT, [xN]
// After that, there is a load from xN and a tbz based on bit 0 of the value.
// Then we have another store, another load, but this time a tbnz because it's a loop.
// In addition, there can be various other instructions scattered between these.
// Only the block above seems to reliably get emitted contiguously.
// /x c84200911a0308aa570300b9:00fcffff00fce0ff00fcffff
else if((op1 & 0xfffffc00) == 0x91004000 && (op2 & 0xfffffc00) == (((op1 & 0x1f) << 16) | 0xaa000000) && (op3 & 0xffffffe0) == (((op2 & 0x1f) << 5) | 0xb9000000))
{
// Within a few instructions, there has to be a load from the same base reg as op3
uint32_t ins = (op3 & 0x3e0) | 0xb9400000;
uint32_t op = 0;
volatile uint32_t *ldr = NULL;
for(size_t i = 0; i < 4; ++i)
{
op = p[3 + i];
if((op & 0xffffffe0) == ins)
{
ldr = p + 3 + i;
break;
}
}
if(!ldr)
{
goto fail;
}
// NOP the write and turn the load into an immediate move
p[2] = 0xd503201f;
*ldr = (op & 0x1f) | 0x52800020;

// There is another store after this, with the same value register as op3, but possibly a different base register.
ins = op3 & 0xfffffc1f;
volatile uint32_t *str = NULL;
for(size_t i = 1; i <= 8; ++i)
{
op = ldr[i];
if((op & 0xfffffc1f) == ins)
{
str = ldr + i;
break;
}
}
if(!str)
{
goto fail;
}
// And another load like above
ins = (op & 0x3e0) | 0xb9400000;
ldr = NULL;
for(size_t i = 1; i <= 4; ++i)
{
op = str[i];
if((op & 0xffffffe0) == ins)
{
ldr = str + i;
break;
}
}
if(!ldr)
{
goto fail;
}
// Same patch
*str = 0xd503201f;
*ldr = (op & 0x1f) | 0x52800020;

tz_done = true;
break;
}
}

iorvbar_yeet(boot_image);
aes_keygen(boot_image);
// Ultra dirty hack: 16K support = Reconfig Engine
Expand All @@ -147,6 +281,10 @@ void patch_bootloader(void* boot_image)
recfg_yoink(boot_image);
}
fuse_jump(boot_image);
if(!tz_done)
{
antitrust(boot_image);
}
return;

fail:;
Expand Down
Loading

0 comments on commit a2e9cbb

Please sign in to comment.