Skip to content

Commit c6e26b2

Browse files
committed
Rewrite TZ driver, TZ0 unlock patch for all devices
1 parent 55b7145 commit c6e26b2

File tree

8 files changed

+812
-119
lines changed

8 files changed

+812
-119
lines changed

src/boot/patches.S

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,6 @@ In r2:
461461

462462
We search for the pattern, then seek backwards for a "cbz w0, ..." with positive offset. If we find none
463463
within a short range, we skip this match, otherwise we replace that instruction with an unconditional branch.
464-
465464
*/
466465

467466
sym fuse_jump
@@ -514,3 +513,195 @@ sym fuse_jump
514513
bfi w8, w12, 0, 26
515514
str w8, [x10]
516515
ret
516+
517+
518+
// void antitrust(volatile void *boot_image)
519+
520+
/*
521+
Keep TrustZone unlocked.
522+
523+
There are a total of 4 variants of this patch, and this is just one of them.
524+
In the matrix below:
525+
526+
- s2 = A10+ on iOS 14.0+. Stage2-only.
527+
- XX = A7, any version, and A8-A9 on 10.x and lower. Pongo-only.
528+
- YY = A9X, any version. Pongo-only.
529+
- ZZ = The rest, this is us. The only patch that is shared between stage2 and Pongo.
530+
531+
For A8-A9, ZZ could be used on iOS 8 and 10 as well, but not on iOS 9.
532+
But XX works on those too, so we don't care.
533+
534+
+--------+----+----+-----+----+-----+-----+------+----+-----+
535+
| | A7 | A8 | A8X | A9 | A9X | A10 | A10X | T2 | A11 |
536+
+--------+----+----+-----+----+-----+-----+------+----+-----+
537+
| iOS 7 | XX | | | | | | | | |
538+
+--------+----+----+-----+----+-----+-----+------+----+-----+
539+
| iOS 8 | XX | XX | XX | | | | | | |
540+
+--------+----+----+-----+----+-----+-----+------+----+-----+
541+
| iOS 9 | XX | XX | XX | XX | YY | | | | |
542+
+--------+----+----+-----+----+-----+-----+------+----+-----+
543+
| iOS 10 | XX | XX | XX | XX | YY | ZZ | ZZ | | |
544+
+--------+----+----+-----+----+-----+-----+------+----+-----+
545+
| iOS 11 | XX | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ |
546+
+--------+----+----+-----+----+-----+-----+------+----+-----+
547+
| iOS 12 | XX | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ |
548+
+--------+----+----+-----+----+-----+-----+------+----+-----+
549+
| iOS 13 | | ZZ | ZZ | ZZ | YY | ZZ | ZZ | ZZ | ZZ |
550+
+--------+----+----+-----+----+-----+-----+------+----+-----+
551+
| iOS 14 | | ZZ | ZZ | ZZ | YY | s2 | s2 | s2 | s2 |
552+
+--------+----+----+-----+----+-----+-----+------+----+-----+
553+
| iOS 15 | | ZZ | ZZ | ZZ | YY | s2 | s2 | s2 | s2 |
554+
+--------+----+----+-----+----+-----+-----+------+----+-----+
555+
| iOS 16 | | ZZ | | ZZ | YY | s2 | s2 | s2 | s2 |
556+
+--------+----+----+-----+----+-----+-----+------+----+-----+
557+
| iOS 17 | | ZZ | | | | s2 | s2 | s2 | |
558+
+--------+----+----+-----+----+-----+-----+------+----+-----+
559+
560+
We start by looking for these two instructions:
561+
562+
+---------------------+
563+
| lsr xN, xN, 0xc |
564+
| stur wN, [xM, -0xc] |
565+
+---------------------+
566+
567+
Where N < 16 and M >= 16.
568+
569+
In r2:
570+
571+
/x 00fc4cd300421fb8:10feffff10feffff
572+
573+
After that, we search for a "str wT, [xM]" (same base register, offset 0) within 20 instructions.
574+
We expect one of the two following sequences there:
575+
576+
+----------------+
577+
| str wT, [xM] |
578+
| ldr wS, [xM] |
579+
| tbz wS, 0, ... |
580+
+----------------+
581+
| str wT, [xM] |
582+
| bl ... |
583+
| ldr wS, [xM] |
584+
| tbz wS, 0, ... |
585+
+----------------+
586+
587+
Where S < 16.
588+
589+
Right after that, devices with TZ1 have another block with the same base register but offset 4:
590+
591+
+-----------------+
592+
| str wT, [xM, 4] |
593+
| ldr wS, [xM, 4] |
594+
| tbz wS, 0, ... |
595+
+-----------------+
596+
| str wT, [xM, 4] |
597+
| bl ... |
598+
| ldr wS, [xM, 4] |
599+
| tbz wS, 0, ... |
600+
+-----------------+
601+
602+
Again with S < 16.
603+
604+
We NOP out the str, ldr and tbz in both blocks.
605+
606+
*/
607+
608+
sym antitrust
609+
mov x2, x0 // instr
610+
mov w7, 0xfffffe10
611+
mov w8, 0xd34c0000 // lsr xN, xN, 0xc
612+
movk w8, 0xfc00
613+
mov w9, 0xb81f0000 // stur wN, [xM, -0xc]
614+
movk w9, 0x4200
615+
// Search for pattern
616+
1:
617+
ldr w3, [x2], 0x4
618+
and w4, w3, w7
619+
cmp w4, w8
620+
b.ne 1b
621+
622+
bfi w9, w3, 0, 5
623+
ldr w3, [x2]
624+
and w4, w3, 0xfffffe1f
625+
cmp w4, w9
626+
b.ne 1b
627+
628+
// Search for "str wT, [xM]"
629+
and w7, w3, 0x3e0
630+
movk w7, 0xb900, lsl 16 // str wT, [xM]
631+
add x3, x2, 0x50
632+
2:
633+
ldr w5, [x2, 0x4]!
634+
and w4, w5, 0xffffffe0
635+
cmp w4, w7
636+
b.eq 3f
637+
cmp x2, x3
638+
b.lo 2b
639+
b . // XXX: fail
640+
641+
3:
642+
// Patch 1st write
643+
mov w10, 0xd5030000 // nop
644+
movk w10, 0x201f
645+
str w10, [x2]
646+
647+
// Check for bl
648+
ldr w3, [x2, 0x4]!
649+
ubfx w4, w3, 26, 6
650+
cmp w4, 0x25 // bl
651+
b.ne 4f
652+
ldr w3, [x2, 0x4]!
653+
654+
4:
655+
// 1s load
656+
orr w7, w7, 0x00400000 // str to ldr
657+
and w4, w3, 0xfffffff0
658+
cmp w4, w7
659+
b.ne . // XXX: fail
660+
// Patch 1st load
661+
str w10, [x2]
662+
663+
// 1s tbz
664+
mov w8, 0x36000000 // tbz wS, 0, ...
665+
bfi w8, w3, 0, 5
666+
ldr w3, [x2, 0x4]!
667+
and w3, w3, 0xfffc001f
668+
cmp w3, w8
669+
b.ne . // XXX: fail
670+
// Patch 1st tbz
671+
str w10, [x2]
672+
673+
// Check for 2nd set of str, ldr, tbz
674+
orr w5, w5, 0x400 // [xM] to [xM, 4]
675+
ldr w3, [x2, 0x4]!
676+
cmp w3, w5
677+
b.ne Ligma
678+
// Patch 2nd write
679+
str w10, [x2]
680+
681+
// Check for bl
682+
ldr w3, [x2, 0x4]!
683+
ubfx w4, w3, 26, 6
684+
cmp w4, 0x25 // bl
685+
b.ne 5f
686+
ldr w3, [x2, 0x4]!
687+
688+
5:
689+
// 2nd load
690+
orr w7, w7, 0x400 // [xM] to [xM, 4]
691+
and w4, w3, 0xfffffff0
692+
cmp w4, w7
693+
b.ne . // XXX: fail
694+
// Patch
695+
str w10, [x2]
696+
697+
// 2nd tbz
698+
bfi w8, w3, 0, 5
699+
ldr w3, [x2, 0x4]!
700+
and w3, w3, 0xfffc001f
701+
cmp w3, w8
702+
b.ne . // XXX: fail
703+
// Patch
704+
str w10, [x2]
705+
706+
Ligma:
707+
ret

src/boot/stage3.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ void iorvbar_yeet(volatile void *boot_image) __asm__("iorvbar_yeet");
3939
void aes_keygen(volatile void *boot_image) __asm__("aes_keygen");
4040
void recfg_yoink(volatile void *boot_image) __asm__("recfg_yoink");
4141
void fuse_jump(volatile void *boot_image) __asm__("fuse_jump");
42+
void antitrust(volatile void *boot_image) __asm__("antitrust");
4243

4344
extern uint8_t need_to_release_L3_SRAM;
4445

@@ -139,6 +140,139 @@ void patch_bootloader(void* boot_image)
139140
}
140141
}
141142

143+
// TrustZone patches.
144+
// We have two of them here, see patches.S for a device/version matrix.
145+
bool tz_done = false;
146+
for(volatile uint32_t *p = boot_image, *end = (volatile uint32_t*)((uintptr_t)boot_image + SAFE_TEXT_SIZE); p < end; ++p)
147+
{
148+
uint32_t op1 = p[0],
149+
op2 = p[1],
150+
op3 = p[2];
151+
152+
// A7 (any version) and A8-A9 (iOS 10 and lower).
153+
// We look for the following sequence:
154+
// str wN, [xM]
155+
// ldr wT, [xM]
156+
// tbz wT, 0, ...
157+
// str wN, [xM, 4]
158+
// ldr wS, [xM, 4]
159+
// tbz wS, 0, ...
160+
// On iOS 7 specifically, there is an immediate generated in the middle,
161+
// and thus the second load and store have no offset:
162+
// str wN, [xM]
163+
// ldr wT, [xM]
164+
// tbz wT, 0, ...
165+
// movz xM, 0x200000000
166+
// movk xM, 0x914
167+
// str wN, [xM]
168+
// ldr wS, [xM]
169+
// tbz wS, 0, ...
170+
// We require that T and S are <16, and that the two tbz have positive offset.
171+
// /x 000000b9000040b900000036000400b9000440b900000036:00fcffff10fcffff0000fcff00fcffff10fcffff0000fcff
172+
// /x 000000b9000040b9000000364000c0d2802281f2000000b9000040b900000036:00fcffff10fcffff0000fcffe0ffffffe0ffffff00fcffff10fcffff0000fcff
173+
if((op1 & 0xfffffc00) == 0xb9000000 && (op2 & 0xfffffff0) == ((op1 & 0x000003e0) | 0xb9400000) && (op3 & 0xfffc001f) == ((op2 & 0x0000001f) | 0x36000000))
174+
{
175+
volatile uint32_t *one = p,
176+
*two = p + 3;
177+
uint32_t op4 = two[0],
178+
op5 = two[1];
179+
uint32_t imm = 1; // 1 << 2
180+
if(op4 == (((op1 & 0x000003e0) >> 5) | 0xd2c00040) && op5 == (((op1 & 0x000003e0) >> 5) | 0xf2812280))
181+
{
182+
two += 2;
183+
op4 = two[0];
184+
op5 = two[1];
185+
imm = 0;
186+
}
187+
if(op4 == ((op1 & 0x000003ff) | (imm << 10) | 0xb9000000) && (op5 & 0xfffffff0) == ((op1 & 0x000003e0) | (imm << 10) | 0xb9400000) && (two[2] & 0xfffc001f) == ((op5 & 0x0000001f) | 0x36000000))
188+
{
189+
// Nop them all out.
190+
one[0] = 0xd503201f;
191+
one[1] = 0xd503201f;
192+
one[2] = 0xd503201f;
193+
two[0] = 0xd503201f;
194+
two[1] = 0xd503201f;
195+
two[2] = 0xd503201f;
196+
tz_done = true;
197+
break;
198+
}
199+
}
200+
// A9X patch, any version.
201+
// The reason A9X is separate is because it has two sets of TZ registers
202+
// rather than just one, which makes for *very* different codegen!
203+
// The signature sequence we look for is:
204+
// add xS, xD, 0x10
205+
// orr xN, xM, xS
206+
// str wT, [xN]
207+
// After that, there is a load from xN and a tbz based on bit 0 of the value.
208+
// Then we have another store, another load, but this time a tbnz because it's a loop.
209+
// In addition, there can be various other instructions scattered between these.
210+
// Only the block above seems to reliably get emitted contiguously.
211+
// /x c84200911a0308aa570300b9:00fcffff00fce0ff00fcffff
212+
else if((op1 & 0xfffffc00) == 0x91004000 && (op2 & 0xfffffc00) == (((op1 & 0x1f) << 16) | 0xaa000000) && (op3 & 0xffffffe0) == (((op2 & 0x1f) << 5) | 0xb9000000))
213+
{
214+
// Within a few instructions, there has to be a load from the same base reg as op3
215+
uint32_t ins = (op3 & 0x3e0) | 0xb9400000;
216+
uint32_t op = 0;
217+
volatile uint32_t *ldr = NULL;
218+
for(size_t i = 0; i < 4; ++i)
219+
{
220+
op = p[3 + i];
221+
if((op & 0xffffffe0) == ins)
222+
{
223+
ldr = p + 3 + i;
224+
break;
225+
}
226+
}
227+
if(!ldr)
228+
{
229+
goto fail;
230+
}
231+
// NOP the write and turn the load into an immediate move
232+
p[2] = 0xd503201f;
233+
*ldr = (op & 0x1f) | 0x52800020;
234+
235+
// There is another store after this, with the same value register as op3, but possibly a different base register.
236+
ins = op3 & 0xfffffc1f;
237+
volatile uint32_t *str = NULL;
238+
for(size_t i = 1; i <= 8; ++i)
239+
{
240+
op = ldr[i];
241+
if((op & 0xfffffc1f) == ins)
242+
{
243+
str = ldr + i;
244+
break;
245+
}
246+
}
247+
if(!str)
248+
{
249+
goto fail;
250+
}
251+
// And another load like above
252+
ins = (op & 0x3e0) | 0xb9400000;
253+
ldr = NULL;
254+
for(size_t i = 1; i <= 4; ++i)
255+
{
256+
op = str[i];
257+
if((op & 0xffffffe0) == ins)
258+
{
259+
ldr = str + i;
260+
break;
261+
}
262+
}
263+
if(!ldr)
264+
{
265+
goto fail;
266+
}
267+
// Same patch
268+
*str = 0xd503201f;
269+
*ldr = (op & 0x1f) | 0x52800020;
270+
271+
tz_done = true;
272+
break;
273+
}
274+
}
275+
142276
iorvbar_yeet(boot_image);
143277
aes_keygen(boot_image);
144278
// Ultra dirty hack: 16K support = Reconfig Engine
@@ -147,6 +281,10 @@ void patch_bootloader(void* boot_image)
147281
recfg_yoink(boot_image);
148282
}
149283
fuse_jump(boot_image);
284+
if(!tz_done)
285+
{
286+
antitrust(boot_image);
287+
}
150288
return;
151289

152290
fail:;

0 commit comments

Comments
 (0)