iOS 17: New Version, New Acronyms | Round 2
November 27, 2023
Our goal at DFF is to reveal any threats on mobile devices, and that requires us to keep up to date with every single version of Android and iOS, including the beta and "Developer Preview" phases. Often, these are the under-the-hood, undocumented changes which have the real impact on operating system security.
iOS 17 indeed introduced such changes. Two notable ones were SPTM and TXM, two binaries included in the beta IPSW. Our previous article took a first look at them, but left much unanswered, and promised "more soon". We've been keeping busy, but haven't forgotten our readers' desire for detail. As with its predecessor, this article provides a reproducible step-by-step flow with disarm which the interested reader is encouraged to follow with the disassembler of choice. And, as before, the impatient reader can just skip to the end.
iOS has moved a bit since our last post. We were hoping to wait for the mid-year release (to see if CL4 is put in), but Apple is moving slowly. So this analysis is on the SPTM and TXM binaries from 17.1.1 (21B91) - specifically, the iPhone 15 Pro Max. For SPTM, that's LC_SOURCE_VERSION
184.42.1.0.0. For TXM it's 80.40.3.0.0. This picks up where we left off, and corrects an inaccuracy (in the previous article's "SUBSYSTEMS" sections the numbers didn't align up, since the numbers registered appear to be for IOMMU, not actual dispatchers - more on this later here).
Our story, so far:
Let's remember where we left off:
PPL as we knew it is no longer.
TXM runs in GL0, and handles code signing and entitlements, much as PPL used to.
SPTM runs in GL1/2.
SPTM provides three "system calls":
SVC #0: TBD.
SVC #37: enable all interrupts.
SVC #38: disable all interrupts.
And pose some questions to answer here:
What is the mysterious SVC #0 for?
What is the general choreography between XNU, TXM, and SPTM?
How does SPTM live up to its name, that is securely manage page tables?
What are the actual responsibilities assumed by TXM?
SPTM, revisited
Let's begin by re-examining the SPTM binary (this time, from the t8122, iPhone 15 Pro).
About that SVC #0…
Remember SVC #0
? We saw the check for it in SPTM, but didn't really look where it was called. Well, looking at TXM's __TEXT_BOOT_EXEC.__bootcode
we find:
fffffff017054084 d503237f PACIBSP ; fffffff017054088 d4000001 SVC #0 ; fffffff01705408c d65f0fff RETAB ;
Labeling this as _sptm_syscall
, we can then disassemble the rest of TXM (that is, from __TEXT_EXEC
, wherein we see the following):
_sptm_syscall_0x100000003: fffffff01704a5e8 f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff01704a5ec f2c00030 MOVK X16, #1, LSL #32 ; X16 := 0x100000000 fffffff01704a5f0 f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0x100000000 fffffff01704a5f4 f2800070 MOVK X16, #3 ; X16 := 0x100000003 // retype? fffffff01704a5f8 140026a3 B 0xfffffff017054084 ; _sptm_syscall _sptm_syscall_0x100000002: fffffff01704a5fc f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff01704a600 f2c00030 MOVK X16, #1, LSL #32 ; X16 := 0x100000000 fffffff01704a604 f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0x100000000 fffffff01704a608 f2800050 MOVK X16, #2 ; X16 := 0x100000002 // register_dispatch_table? fffffff01704a60c 1400269e B 0xfffffff017054084 ; _sptm_syscall _sptm_syscall_0x100000004: fffffff01704a610 f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff01704a614 f2c00030 MOVK X16, #1, LSL #32 ; X16 := 0x100000000 fffffff01704a618 f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0x100000000 fffffff01704a61c f2800090 MOVK X16, #4 ; X16 := 0x100000004 fffffff01704a620 14002699 B 0xfffffff017054084 ; _sptm_syscall _sptm_syscall_0xfe00000000: fffffff01704a624 f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff01704a628 f2c01fd0 MOVK X16, #254, LSL #32 ; X16 := 0xfe00000000 fffffff01704a62c f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0xfe00000000 fffffff01704a630 f2800010 MOVK X16, #0 ; X16 := 0xfe00000000 fffffff01704a634 14002694 B 0xfffffff017054084 ; _sptm_syscall _sptm_syscall_0xfd00000000: fffffff01704a638 f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff01704a63c f2c01fb0 MOVK X16, #253, LSL #32 ; X16 := 0xfd00000000 fffffff01704a640 f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0xfd00000000 fffffff01704a644 f2800010 MOVK X16, #0 ; X16 := 0xfd00000000 fffffff01704a648 1400268f B 0xfffffff017054084 ; _sptm_syscall _sptm_syscall_0xff00000000: fffffff01704a64c f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff01704a650 f2c01ff0 MOVK X16, #255, LSL #32 ; X16 := 0xff00000000 fffffff01704a654 f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0xff00000000 fffffff01704a658 f2800010 MOVK X16, #0 ; X16 := 0xff00000000 fffffff01704a65c 1400268a B 0xfffffff017054084 ; _sptm_syscall
The first three calls have X16
as a value of 0x100000002, 3 and 4 (1 is missing)
. We can expect this pattern to be found in other clients of SPTM.
Indeed, we find the following pattern in XNU. Using disarm
's gadget finding (and focusing on X16
) uncovers these sequences:
DFFenders@xxxx (~) % disarm -g MOVK,MOVK,MOVK,MOVK,B /tmp/extracted/kernel.rebuilt ## '0' subsystem - Total 0-0x15 = 22 calls fffffff02850e818(0x72e818): MOVK X16, #0, LSL #48 ; X16 := 0x0 fffffff02850e81c(0x72e81c): MOVK X16, #0, LSL #32 ; X16 := 0x0 fffffff02850e820(0x72e820): MOVK X16, #0, LSL #16 ; X16 := 0x0 fffffff02850e824(0x72e824): MOVK X16, #0 ; X16 := 0x0 .. fffffff02850e9b4(0x72e9b4): MOVK X16, #21 ; X16 := 0x15 fffffff02850e9b8(0x72e9b8): B 0xfffffff02a330cc4 ; _func_0xfffffff02a330cc4 ... ## '6' subsystem - Total 0-0x4 = 5 calls fffffff02850e9c8(0x72e9c8): MOVK X16, #0 ; X16 := 0x600000000 ... fffffff02850ea18(0x72ea18): MOVK X16, #4 ; X16 := 0x600000004 fffffff02850ea1c(0x72ea1c): B 0xfffffff02a330cc4 ; _func_0xfffffff02a330cc4 .. ## '3' subsystem - Total 0-0x10 = 17 calls fffffff02850ea2c(0x72ea2c): MOVK X16, #0 ; X16 := 0x300000000 .. fffffff02850eb6c(0x72eb6c): MOVK X16, #16 ; X16 := 0x300000010 fffffff02850eb70(0x72eb70): B 0xfffffff02a330cc4 ; _func_0xfffffff02a330cc4 .. ## '5' subsystem (a bit out of order, 3 calls) fffffff02850eb80(0x72eb80): MOVK X16, #1 ; X16 := 0x500000001 fffffff02850eb94(0x72eb94): MOVK X16, #2 ; X16 := 0x500000002 fffffff02850eb98(0x72eb98): B 0xfffffff02a330cc4 ; _func_0xfffffff02a330cc4 ## '7' subsystem - Total 0-0x12 = 13 calls fffffff02850ebbc(0x72ebbc): MOVK X16, #0 ; X16 := 0x700000000 fffffff02850ebc0(0x72ebc0): B 0xfffffff02a330cc4 ; _func_0xfffffff02a330cc4 ... fffffff02850ecac(0x72ecac): MOVK X16, #12 ; X16 := 0x70000000c fffffff02850ecb0(0x72ecb0): B 0xfffffff02a330cc4 ; _func_0xfffffff02a330cc4
It looks like XNU is calling a host of functions, with "families" (which we'll refer to as "subsystem IDs") of 0, 6, 3, 5 and 7. But XNU can't issue an SVC (it's already at a level higher than EL0!). So looking at the common branch point, 0xfffffff02a614e20:
DFFenders@xxxx (~) % disarm -r func_0xfffffff02a330cc4 /tmp/extracted/kernel.rebuilt _func_0xfffffff02a330cc4 fffffff02a614e20 d503237f PACIBSP ; fffffff02a614e24 a9bf7bfd STP X29, X30, [SP, #-16]! ; SP -= 16; *[SP] = [X29, X30] fffffff02a614e28 910003fd ADD X29, SP, #0 ; FP = SP func_0xfffffff027e8cb44(); fffffff02a614e2c 9761df46 BL 0xfffffff027e8cb44 ; fffffff02a614e30 910003bf ADD X31, X29, #0 ; SP = FP fffffff02a614e34 a8c17bfd LDP X29, X30, [SP], #16 ; [X29, LR] = *[SP]; X31 += 16 fffffff02a614e38 00201420 GENTER #0 ; fffffff02a614e3c a9bf7bfd STP X29, X30, [SP, #-16]! ; SP -= 16; *[SP] = [X29, X30] fffffff02a614e40 910003fd ADD X29, SP, #0 ; FP = SP + 0x0 = 0x0! _func_0xfffffff027e8cb84(0); fffffff02a614e44 9761df50 BL 0xfffffff027e8cb84 ; fffffff02a614e48 910003bf ADD X31, X29, #0 ; SP = FP + 0x0 = 0x0! fffffff02a614e4c a8c17bfd LDP X29, X30, [SP], #16 ; [X29, LR] = *[SP]; SP += 16 fffffff02a614e50 d65f0fff RETAB ;
So XNU makes a "system call" to SPTM by means of GENTER #0
, as expected. The two functions there (0xfffffff027e8cb44
and 0xfffffff027e8cb84
) appear to do before/after processing, with the latter (0xfffffff027e8cb84
) calling into XNU's AST - Asynchronous Software Traps, which are often called post interrupt handling and certain kernel events.
Figuring out the subsystems:
So far, then, we see that there are thus several subsystems - 0, 3, 5 and 7, along with TXM's 1, and 253/254/255 (which seem to be individual codes). Recall (from the previous article) that we had already identified several subsystems in SPTM:
DFFenders@xxxx (~/Downloads/extracted/Firmware) % disarm sptm.t8120.release.im4p | grep 0xfffffff0070895b4 _func_0xfffffff0070895b4(0x1,0xfffffff00705b3b8); // SART _func_0xfffffff0070895b4(0x2,0xfffffff00705b458); // NVMe _func_0xfffffff0070895b4(0x3,0xfffffff00705b4d0); // uat _func_0xfffffff0070895b4(0x5,0xfffffff00705b260); // t8110dart
These registrations, however, appear to be IOMMU related, and weren't the actual numbers (as could also be observed from the previous article). There is a current_iommu()
routine (0xfffffff0070891c8
, which is sometimes inlined), which uses the TLS (TPIDR_GL2
at offset 0x8), to get the active IOMMU index, from the above list. Looking into three of these IOMMUs (which are contiguous in __DATA_CONST
):
fffffff00705b3b8: 0xfffffff00700876e "SART" fffffff00705b3c0: 0xfffffff007077da4 _func_0xfffffff007077da4 sart_bootstrap (INITIALIZER) fffffff00705b3c8: 00 00 00 00 00 00 00 00 |........| fffffff00705b3d0: 00 00 00 00 00 00 00 00 |........| fffffff00705b3d8: 05 00 00 00 00 00 00 00 |........| <-- actual subsystem number fffffff00705b3e0: 0xfffffff0070588a0 _sart_dispatch_table fffffff00705b3e8: 00 00 00 00 00 00 00 00 |........| fffffff00705b3f0: 01 00 00 00 00 00 00 00 |........| fffffff00705b3f8: 38 01 00 00 00 00 00 00 |8.......| fffffff00705b400: 0xfffffff007008773 "VIOLATION_SART_INVALID_PT" fffffff00705b408: 0xfffffff00700878d "VIOLATION_SART_INVALID_PADDR" fffffff00705b410: 0xfffffff0070087aa "VIOLATION_SART_INVALID_N_OPS" fffffff00705b418: 0xfffffff0070087c7 "VIOLATION_SART_INVALID_SIZE" fffffff00705b420: 0xfffffff0070087e3 "VIOLATION_SART_INVALID_PERM" fffffff00705b428: 0xfffffff0070087ff "VIOLATION_SART_ILLEGAL_STATE" fffffff00705b430: 0xfffffff00700881c "VIOLATION_SART_NO_SPACE" fffffff00705b438: 0xfffffff007008834 "VIOLATION_SART_ILLEGAL_MAP" fffffff00705b440: 0xfffffff00700884f "VIOLATION_SART_ILLEGAL_UNMAP" fffffff00705b448: 0xfffffff00700886c "VIOLATION_SART_CPU_RACE" fffffff00705b450: 0xfffffff007008884 "VIOLATION_SART_INVALID_CONFIG" --- fffffff00705b458: 0xfffffff007007cf5 "NVMe" fffffff00705b460: 0xfffffff007076160 _func_0xfffffff007076160 fffffff00705b468: 00 00 00 00 00 00 00 00 |........| fffffff00705b470: 00 00 00 00 00 00 00 00 |........| fffffff00705b478: 06 00 00 00 00 00 00 00 |........| <-- actual subsystem number fffffff00705b480: 0xfffffff007058788 _nvme_dispatch_table fffffff00705b488: 00 00 00 00 00 00 00 00 |........| fffffff00705b490: 01 00 00 00 00 00 00 00 |........| fffffff00705b498: 78 07 00 00 00 00 00 00 |x.......| fffffff00705b4a0: 0xfffffff007007cfa "VIOLATION_NVME_INVALID_QID" fffffff00705b4a8: 0xfffffff007007d15 "VIOLATION_NVME_INVALID_CID" fffffff00705b4b0: 0xfffffff007007d30 "VIOLATION_NVME_INVALID_PAGE_COUNT" fffffff00705b4b8: 0xfffffff007007d52 "VIOLATION_NVME_INVALID_NVME_PAGE" fffffff00705b4c0: 0xfffffff007007d73 "VIOLATION_NVME_ILLEGAL_CALL" fffffff00705b4c8: 0xfffffff007007d8f "VIOLATION_NVME_ILLEGAL_CID_STATE_TRANSITION" --- fffffff00705b4d0: 0xfffffff007006297 "uat" fffffff00705b4d8: 0xfffffff007071bb4 _func_0xfffffff007071bb4 UAT_bootstrap fffffff00705b4e0: 0xfffffff007071b1c _func_0xfffffff007071b1c uat_retype_in fffffff00705b4e8: 0xfffffff007071a90 _func_0xfffffff007071a90 uat_retype_out fffffff00705b4f0: 07 00 00 00 00 00 00 00 |........| <-- actual subsystem number fffffff00705b4f8: 0xfffffff007058000 _uat_dispatch_table ....
You might wonder how we deduced those …_dispatch_table
s above - To get those we have to look into sptm_dispatch
(q.v. first article, where table was @fffffff00705b5d8):
DFFenders@xxxx (~/Firmware/Extracted) % disarm -a 0xfffffff00708a194 sptm.t8122.release.im4p" _sptm_dispatch: fffffff00708a194 d503237f PACIBSP ; fffffff00708a198 d10183ff SUBi X31, X31, #96 ; SP = SP - 0x60 fffffff00708a19c a9057bfd STP X29, X30, [X31, #80] ; *[SP +80] = [X29, X30] fffffff00708a1a0 910143fd ADD X29, X31, #80 ; FP = SP + 0x50 = 0x50 -- ! fffffff00708a1a4 d53efb29 MRS X9, TPIDR_GL2 ; not yet.. fffffff00708a1a8 f27b081f TST X0, #0xe0 ; fffffff00708a1ac 540007c1 B.NE 0xfffffff00708a2a4 ; fffffff00708a1b0 d3609c08 LSRi X8, X0, #0 ; fffffff00708a1b4 71001d1f CMPi W8, #7 ; cmp to # of subsystems fffffff00708a1b8 54000bc8 B.HI 0xfffffff00708a330 ; tooHighForASubsystem fffffff00708a1bc aa0803f0 MOV X16, X8 ; X16 = X8 (0xfffffff00700e670) fffffff00708a1c0 f1001e1f CMPi X16, #7 ; fffffff00708a1c4 9a9f9210 CSEL X16, X16, X31, LS ; X16 = ( ) ? 0xfffffff00700e670 : 0x50 fffffff00708a1c8 10000df1 ADR X17, 0xfffffff00708a384 ; X17 = 0xfffffff00708a384 fffffff00708a1cc d503201f NOP ; fffffff00708a1d0 b8b07a30 LDRSW(r) X16, [X17, X16, LSL #2] ; branch table @0xfffffff00708a384 fffffff00708a1d4 10000011 ADR X17, 0xfffffff00708a1d4 ; X17 = 0xfffffff00708a1d4 fffffff00708a1d8 8b100230 ADDsr X16, X17, X16 ; R16 = R17 + R16 = 0xfffffff00708a1e4 fffffff00708a1dc d61f0200 BR X16 ; (no symbol) _case_0: // (XNU) fffffff00708a1e0 d503249f BTI j ; fffffff00708a1e4 10e8a0e9 ADR X9, 0xfffffff00705b600 ; _xnu_dispatch fffffff00708a1ec 14000026 B 0xfffffff00708a284 ; (no symbol) case 2: (unknown) fffffff00708a1f0 d503249f BTI j ; fffffff00708a1f4 10e8b069 ADR X9, 0xfffffff00705b800 ; X9 = _unknown_dispatch fffffff00708a1fc 14000022 B 0xfffffff00708a284 ; (no symbol) case 3: (t8110dart) fffffff00708a200 d503249f BTI j ; fffffff00708a204 528000ca MOVZ W10, #6 ; X10 = 0x6 fffffff00708a208 f900052a STRi X10, [X9, #8] ; *0x8 = 0x6 fffffff00708a20c 10e73ce9 ADR X9, 0xfffffff0070589a8 ; _t8011dart_dispatch fffffff00708a214 1400001c B 0xfffffff00708a284 ; (no symbol) case 4: (???) fffffff00708a218 d503249f BTI j ; fffffff00708a21c 528000ca MOVZ W10, #6 ; X10 = 0x6 fffffff00708a220 f900052a STRi X10, [X9, #8] ; *0x8 = 0x6 fffffff00708a224 10e74429 ADR X9, 0xfffffff007058aa8 ; __unknown_subsystem_4_dispatch fffffff00708a22c 14000016 B 0xfffffff00708a284 ; (no symbol) case 1: (TXM) fffffff00708a230 d503249f BTI j fffffff00708a234 10e8a669 ADR X9, 0xfffffff00705b700 ; _TXM_dispatch fffffff00708a23c 14000012 B 0xfffffff00708a284 ; (no symbol) case 5: (sart) fffffff00708a240 d503249f BTI j ; fffffff00708a244 5280004a MOVZ W10, #2 ; X10 = 0x2 fffffff00708a248 f900052a STRi X10, [X9, #8] ; *0x8 = 0x2 fffffff00708a24c 10e732a9 ADR X9, 0xfffffff0070588a0 ; _SART_dispatch fffffff00708a254 1400000c B 0xfffffff00708a284 ; (no symbol) case 6: (nvme) fffffff00708a258 d503249f BTI j ; fffffff00708a25c 5280006a MOVZ W10, #3 ; X10 = 0x3 fffffff00708a260 f900052a STRi X10, [X9, #8] ; *0x8 = 0x3 fffffff00708a264 10e72929 ADR X9, 0xfffffff007058788 ; _NVMe_dispatch fffffff00708a268 d503201f NOP ; fffffff00708a26c 14000006 B 0xfffffff00708a284 ; (no symbol) case 7: (uat) fffffff00708a270 d503249f BTI j ; fffffff00708a274 5280008a MOVZ W10, #4 ; X10 = 0x4 fffffff00708a278 f900052a STRi X10, [X9, #8] ; *0x8 = 0x4 fffffff00708a27c 10e6ec29 ADR X9, 0xfffffff007058000 ; _uat_dispatch fffffff00708a280 d503201f NOP ; fffffff00708a284 8b200d29 ADDer X9, X9, W0, UXTB #3 ; X9 = X9 + 0x3 = 0xfffffff007058003 -- ! fffffff00708a288 f9400129 LDRi X9, [X9] ; ; X9 = *(0xfffffff007058003) (9) = ??? fffffff00708a28c b4000289 CBZ X9, 0xfffffff00708a2dc ; fffffff00708a290 aa0903e0 MOV X0, X9 ; X0 = X9 (0xfffffff007058003) fffffff00708a294 a9457bfd LDP X29, X30, [X31, #80] ; [X29, X0] = *[X31] fffffff00708a298 910183ff ADD X31, X31, #96 ; SP = SP + 0x60 = 0x60 -- ! .... branch_table_from_0xfffffff00708a1dc: fffffff00708a384 0000000c DCD 0xc ; fffffff00708a388 0000005c DCD 0x5c ; fffffff00708a38c 0000001c DCD 0x1c ; fffffff00708a390 0000002c DCD 0x2c ; fffffff00708a394 00000044 DCD 0x44 ; fffffff00708a398 0000006c DCD 0x6c ; fffffff00708a39c 00000084 DCD 0x84 ; fffffff00708a3a0 0000009c DCD 0x9c ;
There are also references to an internal state machine in the dispatching logic, which verifies the validity of transitions from one domain to another, which we leave out of this post’s scope.
We now have a nearly complete picture of the subsystem dispatchers:
# | Subsystem | Descriptor | # of calls/services |
---|---|---|---|
0 | XNU | 0xfffffff00705b600 | 22 |
1 | TXM | 0xfffffff00705b700 | 5 |
2 | ? | 0xfffffff00705b800 | 3 |
3 | t8110dart | 0xfffffff0070589a8 | 17 |
4 | ?? | 0xfffffff007058aa8 | .. |
5 | SART | 0xfffffff0070588a0 | 3 |
6 | NVMe | 0xfffffff007058788 | 5 |
7 | UAT | 0xfffffff007058000 | 13 |
And note that the numbers for 0, 3, 5, 6 and 7 - those numbers we saw earlier are used by XNU - indeed line up, if counting from 0)!
Example: The SPTM XNU Subsystem
We can piece together the XNU subsystem of SPTM by looking through its dispatch table (we we uncovered as "case 0", above), and then matching some function call names (left in debug messages by our friends at Apple, but probably not for long), as well as correlating them from the places where they are called out in XNU. This would result in the following:
_xnu_dispatch:
fffffff00705b600: 0xfffffff007072ba0 called_from_machine_lockdown
fffffff00705b608: 0xfffffff00708a3d8 sptm_retype
fffffff00705b610: 0xfffffff00708a910 _func_0xfffffff00708a910
fffffff00705b618: 0xfffffff00708b3c4 sptm_map_table
fffffff00705b620: 0xfffffff00708bb20 sptm_unmap_table
fffffff00705b628: 0xfffffff00708ce44 _func_0xfffffff00708ce44
fffffff00705b630: 0xfffffff00708d43c _func_0xfffffff00708d43c
fffffff00705b638: 0xfffffff00708ca48 _func_0xfffffff00708ca48
fffffff00705b640: 0xfffffff00708d4dc _func_0xfffffff00708d4dc
fffffff00705b648: 0xfffffff00708d91c _func_0xfffffff00708d91c
fffffff00705b650: 0xfffffff00708dc60 sptm_nest_region
fffffff00705b658: 0xfffffff00708e3a8 _func_0xfffffff00708e3a8
fffffff00705b660: 0xfffffff00708e7e4 _func_0xfffffff00708e7e4
fffffff00705b668: 0xfffffff00708e8c0 _func_0xfffffff00708e8c0
fffffff00705b670: 0xfffffff0070730b0 sptm_register_cpu
fffffff00705b678: 0xfffffff007075118 _func_0xfffffff007075118
fffffff00705b680: 0xfffffff00708ed80 sptm_sign_user_pointer
fffffff00705b688: 0xfffffff00708ee40 sptm_auth_user_pointer
fffffff00705b690: 0xfffffff00708a10c __xnu_exc_return_handler_registration_maybe
fffffff00705b698: 0xfffffff007073818 _func_0xfffffff007073818
fffffff00705b6a0: 0xfffffff007073534 spth_slide_region
fffffff00705b6a8: 0xfffffff00708d514 _func_0xfffffff00708d514
SPTM retyping
Looking through symbols, we see quite a few references to ...page_type_retype_[in/out]
:
xnu_default_retype_out xnu_exec_retype_out cpu_page_table_retype_out cpu_root_table_retype_in xnu_rozone_retype_out t8110dart_retype_in uat_retype_in xnu_default_retype_out
What's interesting is that these functions have no references from elsewhere inside the code. But… they must be called from somewhere, right? So - symbolicating accordingly, and dumping __DATA_CONST
, we see what appears to be a descriptor table:
fffffff00705b920: 00 00 00 00 00 01 01 00 |........| fffffff00705b928: FF FF FF FF FF FF FF FF |........| fffffff00705b930: 00 00 00 00 00 00 00 00 |........| fffffff00705b938: 00 00 00 00 00 00 00 00 |........| fffffff00705b940: 0xfffffff0070878bc _sptm_retype_null_in fffffff00705b948: 0xfffffff0070878b4 _sptm_retype_null_out ..(multiple repetitions) ..
The structure leads us to deduce the following:
Retyping a page changes its designation, and essentially which subsystem can use it.
There are up to thirty-two types of pages, each associated with an index in a global (read: in SPTM's
__DATA_CONST
) descriptor table. The Table is at 0xfffffff00705b920
, a global which is referenced several types. The table can be found from its "magic" (in reality, constants values) of 0x01010000000000 followed by 0xFFFFFFFFFFFFFFFF.From the positions in the table, coupled with the names, we can reconstruct (some of) the constants to be:
Constant | Retype class | In handler | Out handler |
---|---|---|---|
0x8 or 0x11 or 0x12 | cpu_root_table | _func_0xfffffff0070875ec | _func_0xfffffff007087374 |
0xb | xnu_default | 0xfffffff00708735c | 0xfffffff007087174 |
0xe or 0xf or 0x10 | xnu_default | N/A | 0xfffffff0070870e8 |
0x13, 0x14 | cpu_page_table | .... | |
0x15, 0x16 | cpu_page_table | .... | N/A |
0x18 | xnu_rozone | N/A | .... |
The retype operation has an "in" callback and an "out" callback. Neither or either or both can be implemented. Two null functions (
BTI c
/RET
) are used if a given callback is left unimplemented. You can find those easily looking for the gadgets:
DFFenders@xxxx (~) % disarm -g BTI,RET sptm.t8122.release.im4p sptm.t8122.release.im4p: This is an IM4P... with a BVX2 payload... Uncompressed 721064 bytes fffffff0070878b4(0x278b4): BTI c ; fffffff0070878b8(0x278b8): RET ; --- fffffff0070878bc(0x278bc): BTI c ; fffffff0070878c0(0x278c0): RET ;
Though from the position in the descriptor table it seems like they're reversed (first one being the "out" callback, and second being the "in", not that it matters much).
TXM
XNU → XTM transitions
As described in the previous article, XNU is a client of TXM for all of its code signing/entitlement needs. This means that XNU needs a way to call out to TXM - which is technically a separate address space than itself (presumably, this is the Apple idea of an "exclave".
To get from XNU to TXM, therefore, will require involving SPTM. The sequence is as follows:
XNU makes an SPTM call - via
GENTER
. This was explained in the previous article (0xfffffff028448e70
) and is in 0xfffffff02850e01c
in the 17.1.1 kernel:
fffffff02850e01c d503237f PACIBSP ; fffffff02850e020 2a0003f0 MOV W16, W0 ; X16 = X0 (0x0) fffffff02850e024 f2e00050 MOVK X16, #2, LSL #48 ; X16 := 0x2000000000000 fffffff02850e028 f2c00010 MOVK X16, #0, LSL #32 ; X16 := 0x2000000000000 fffffff02850e02c aa0103ea MOV X10, X1 ; X10 = X1 (0x0) fffffff02850e030 a9400540 LDP X0, X1, [X10] ; [X0, X0] = *[X10] fffffff02850e034 a9410d42 LDP X2, X3, [X10, #16] ; [X2, X0] = *[X10] fffffff02850e038 a9421544 LDP X4, X5, [X10, #32] ; [X4, X0] = *[X10] fffffff02850e03c a9431d46 LDP X6, X7, [X10, #48] ; [X6, X0] = *[X10] fffffff02850e040 00201420 GENTER #0 ; fffffff02850e044 d65f0fff RETAB ; -- fffffff02850e048 00201421 GENTER #1 ; fffffff02850e04c 00201422 GENTER #2 ; fffffff02850e050 14000000 HALT #0 ; -- fffffff02850e054 00201423 GENTER #3 ;
2. Control is transferred to SPTM (via GENTER handler), which realizes (via X16 0x20000........) this is a call from XNU proper.
3. SPTM somehow transfers control to TXM. But how?
The TXM Side of things
Early in its initialization, TXM only signs two pointers:
fffffff01701b210 d503237f PACIBSP ; fffffff01701b214 d100c3ff SUBi X31, X31, #48 ; SP = SP - 0x30 fffffff01701b218 a9014ff4 STP X20, X19, [X31, #16] ; *[SP +16] = [X20, X19] fffffff01701b21c a9027bfd STP X29, X30, [X31, #32] ; *[SP +32] = [X29, X30] fffffff01701b220 910083fd ADD X29, X31, #32 ; FP = SP + 0x20 = 0x20 -- ! fffffff01701b224 10fa8ff3 ADR X19, 0xfffffff017010420 ; X19 = 0xfffffff017010420 fffffff01701b228 d503201f NOP ; fffffff01701b22c 52800028 MOVZ W8, #1 ; X8 = 0x1 fffffff01701b230 39000268 STRBi W8, [X19] ; __Do_Setup(....); fffffff01701b234 940016b4 BL 0xfffffff017020d04 ; fffffff01701b238 10042d30 ADR X16, 0xfffffff0170237dc ; X16 = 0xfffffff0170237dc fffffff01701b23c d503201f NOP ; fffffff01701b240 dac123f0 PACIZA X16 ; fffffff01701b244 aa1003e1 MOV X1, X16 ; X1 = X16 (0xfffffff0170237dc) fffffff01701b248 52800000 MOVZ W0, #0 ; X0 = 0x0 fffffff01701b24c 52800042 MOVZ W2, #2 ; X2 = 0x2 func_0xfffffff01704b234(0,0xfffffff0170237dc (PAC),0x2); fffffff01701b250 9400bff9 BL 0xfffffff01704b234 ; fffffff01701b254 100420f0 ADR X16, 0xfffffff017023670 ; X16 = 0xfffffff017023670 fffffff01701b258 d503201f NOP ; fffffff01701b25c dac123f0 PACIZA X16 ; fffffff01701b260 aa1003e1 MOV X1, X16 ; X1 = X16 (0xfffffff017023670) fffffff01701b264 52800020 MOVZ W0, #1 ; X0 = 0x1 fffffff01701b268 52800022 MOVZ W2, #1 ; X2 = 0x1 func_0xfffffff01704b234(0x1,0xfffffff017023670 (PAC),0x1); fffffff01701b26c 9400bff2 BL 0xfffffff01704b234 ; _gets_build_variant_string(); fffffff01701b270 94001745 BL 0xfffffff017020f84 ; fffffff01701b274 f90003e0 STRi X0, [X31] ; *0x0 = 0x0 fffffff01701b278 50f5faa0 ADR X0, 0xfffffff0170071ce ; X0 = 0xfffffff0170071ce fffffff01701b27c d503201f NOP ; _Log("build variant: %s");
Both pointers are provided as arguments to 0xfffffff01704b234. Where have we seen that before?
fffffff01704b234 f2e00010 MOVK X16, #0, LSL #48 ; X16 := 0x0
fffffff01704b238 f2c00030 MOVK X16, #1, LSL #32 ; X16 := 0x100000000
fffffff01704b23c f2a00010 MOVK X16, #0, LSL #16 ; X16 := 0x100000000
fffffff01704b240 f2800050 MOVK X16, #2 ; X16 := 0x100000002
That is, in a call to SPTM! We can therefore deduce these two pointers are callbacks into TXM, and selector 0x100000002 indicates sptm_register_callback(...)
or similar The first of them - 0x2 - is of particular interest. After finding a TXM Thread and ensuring stack alignment, it calls SVC #37 goes to 0xfffffff017021e40
. Wherein we shortly find ourselves in a giant switch table. The switch table is automatically reconstructed by disarm
, and can be double checked thanks to ARMv8.5 Branch Target Indicators, marking the branch addresses as safe to BTI j
ump to.
fffffff017021f18 b8b07a30 LDRSW(r) X16, [X17, X16, LSL #2] ; table @0x...0170222ec fffffff017021f1c 10000011 ADR X17, 0xfffffff017021f1c ; X17 = 0xfffffff017021f1c fffffff017021f20 8b100230 ADDsr X16, X17, X16 ; R16 = R17 + R16 = 0xfffffff017021f2c fffffff017021f24 d61f0200 BR X16 ; (no symbol) .. _case 0: fffffff017021f28 d503249f BTI j ; func_0xfffffff0170239b0(ARG0,ARG1,ARG2,ARG3,ARG4); fffffff017021f2c 940006a1 BL 0xfffffff0170239b0 .. _case_1: // code signing related fffffff017021f60 d503249f BTI j ; func_0xfffffff017022390(ARG0,ARG1,ARG2,ARG3,ARG4); fffffff017021f64 9400010b BL 0xfffffff017022390 ; fffffff017021f68 1400001c B 0xfffffff017021fd8 .. fffffff017021fa4 d503249f BTI j ; func_0xfffffff0170239b0(ARG0,ARG1,ARG2,ARG3,ARG4); fffffff017021fa8 94000682 BL 0xfffffff0170239b0 ; fffffff017021fac aa0003f4 MOV X20, X0 ; X20 = X0 (0x0) fffffff017021fb0 52800028 MOVZ W8, #1 ; X8 = 0x1 fffffff017021fb4 f9000c08 STRi X8, [X0, #24] ; *0x18 = 0x1 _gets_build_variant_string(); fffffff017021fb8 97fffbf3 BL 0xfffffff017020f84 ; ... fffffff0170222ec 0000000c DCD 0xc ; ..017021f28 fffffff0170222f0 00000044 DCD 0x44 ; ..17021f60 fffffff0170222f4 00000050 DCD 0x50 ; fffffff0170222f8 00000088 DCD 0x88 ; fffffff0170222fc 000000b0 DCD 0xb0 ; fffffff017022300 000000c8 DCD 0xc8 ; fffffff017022304 000000d4 DCD 0xd4 ; ...... fffffff017022374 00000300 DCD 0x300 ; 0xfffffff01702221c, calls 0xfffffff017022c94 fffffff017022378 00000310 DCD 0x310 ; 0xfffffff01702222c, calls 0xfffffff017022cec fffffff01702237c 0000031c DCD 0x31c ; 0xfffffff017022238, calls 0xfffffff017023520 fffffff017022380 0000032c DCD 0x32c ; 0xfffffff017022248, calls 0xfffffff01702353c fffffff017022384 000003b4 DCD 0x3b4 ; fffffff017022388 0000033c DCD 0x33c ; 0xfffffff017022258, calls 0xfffffff0170235b4 fffffff01702238c 0000034c DCD 0x34c ; 0xfffffff017022268, calls 0xfffffff017022d38
At least one of the functions invoked, we can immediately tell - since it is the only argument to a format string of "build variant", we know it reports the TXM build variant (from previous listing). How do we find the rest? Let's head back to XNU.
Back to XNU
Apple released the XNU sources for iOS 17 (XNU-10002) surprisingly early. The 8792 sources already showed the PPL-client side (in bsd/kern/kern_codesigning.c
, with #if PMAP_CS_PPL_MONITOR
), but the 10002 sources remove that #if
, instead providing many CSM_PREFIX
ed call-outs, and a brand new /bsd/sys/code_signing_internal.h. From the header we learn there are two possible prefixes - ppl_
(for PMAP_CS
) and xnu_
(for older, non PPL devices). But what of the newer, GXF devices? Apple redacts this, because obviously if the #if
for that is gone, nobody will consider disassembling the kernel, will they?
If they did, however, they'd quickly find the calls to TXM. We would start with one of the kern_codesigning.c
panics:
fffffff02827d880 52804c88 MOVZ W8, #612 ; X8 = 0x264 fffffff02827d884 d0ff7029 ADRP X9, #-4602 ; X9 = 0xfffffff027083000 fffffff02827d888 911bd529 ADD X9, X9, #1781 ; X9 = 0xfffffff0270836f5 fffffff02827d88c a90123e9 STP X9, X8, [X31, #16] ; *[SP +16] = [X9, X8] fffffff02827d890 a90053e0 STP X0, X20, [X31] ; *(X31) = [X0, X20] fffffff02827d894 d0ff7020 ADRP X0, #-4602 ; X0 = 0xfffffff027083000 fffffff02827d898 911e2800 ADD X0, X0, #1930 ; X0 = 0xfffffff02708378a _panic("unable to unregister profile from monitor: %d | %p\n @%s:%d", …, 'kern_codesigning.c');
Going back from it:
fffffff02827d7f0 aa1503f4 MOV X20, X21 ; X20 = X21 (0x0)
fffffff02827d7f4 f94012b5 LDRi X21, [X21, #32] ; X21 = *(X21 + 0x20) = ?
fffffff02827d7f8 39406288 LDRB W8, [X20, #96] ;
fffffff02827d7fc 35ffff68 CBNZ W8, 0xfffffff02847d7e8 ;
fffffff02827d800 f9400a80 LDRi X0, [X20, #16] ; X0 = *(X20 + 0x10) = ?
_func_0xfffffff02827f16c(?,ARG1,ARG2,ARG3,ARG4);
fffffff02827d804 9400065a BL 0xfffffff02827f16c ;
fffffff02827d808 34000080 CBZ W0, 0xfffffff02827d818 ;
fffffff02827d80c 7100141f CMPi W0, #5 ;
fffffff02827d810 54fffee0 B.EQ 0xfffffff02827d7ec ;
fffffff02827d814 1400001b B 0xfffffff02827d880
Then disassembling ...2827f16c:
DFFenders@xxxx (~) % disarm -r _func_0xfffffff02827f16c /tmp/extracted/kernel.rebuilt fffffff02827f16c d503237f PACIBSP ; fffffff02827f170 d10283ff SUBi X31, X31, #160 ; SP = SP - 0xa0 fffffff02827f174 a90757f6 STP X22, X21, [X31, #112] ; *[SP +112] = [X22, X21] fffffff02827f178 a9084ff4 STP X20, X19, [X31, #128] ; *[SP +128] = [X20, X19] fffffff02827f17c a9097bfd STP X29, X30, [X31, #144] ; *[SP +144] = [X29, X30] fffffff02827f180 910243fd ADD X29, X31, #144 ; FP = SP + 0x90 = 0x90 -- ! fffffff02827f184 6f00e400 MOVI.2D V0, #0000000000000000 ; fffffff02827f188 ad0083e0 STP Q0, Q0, [X31, #16] ; *[SP +8] = [X0, X0] fffffff02827f18c f90033ff STRi X31, [X31, #96] ; *0x60 = 0x0 fffffff02827f190 ad0203e0 STP Q0, Q0, [X31, #64] ; *[SP +32] = [X0, X0] fffffff02827f194 3d800fe0 STRi Q0, [X31, #48] ; *0x30 = 0x0 fffffff02827f198 52800168 MOVZ W8, #11 ; X8 = 0xb fffffff02827f19c b90013e8 STRi X8, [X31, #16] ; *0x10 = 0xb fffffff02827f1a0 f0ff6c68 ADRP X8, #-4721 ; X8 = 0xfffffff02700e000 fffffff02827f1a4 fd443d00 LDRi D0, [X8, #2168] ; ~X0 = *(0xfffffff02700e878) fffffff02827f1a8 fc01c3e0 STUR X0, [X31, #28] ; fffffff02827f1ac f90003e0 STRi X0, [X31] ; *0x0 = 0x0 fffffff02827f1b0 910043e0 ADD X0, X31, #16 __txm_from_kernel(SP+0x10, ... ); fffffff02827f1b4 97fffc2b BL 0xfffffff02827e260
The "selector" is stored in the arguments to the TXM kernel call (specifically, loaded into W8 (as in "#11", above), and shoved on the buffer argument to the TXM gate (as shown with selector #11, in 0xfffffff02827f198 above). We leave it as a (lengthy) exercise to the reader to find all the selector calls. But, what of the names?
Looking at the header, we may not know the prefix, but the calls will still have the same:
DFFenders@xxxx (xnu-rel-xnu-10002.1.13) % grep CSM_P ./bsd/sys/code_signing_internal.h #define __CSM_PREFIX(prefix, name) prefix##_##name #define _CSM_PREFIX(prefix, name) __CSM_PREFIX(prefix, name) #define CSM_PREFIX(name) _CSM_PREFIX(CODE_SIGNING_MONITOR_PREFIX, name) void CSM_PREFIX(toggle_developer_mode)( void CSM_PREFIX(set_compilation_service_cdhash)( bool CSM_PREFIX(match_compilation_service_cdhash)( void CSM_PREFIX(set_local_signing_public_key)( uint8_t* CSM_PREFIX(get_local_signing_public_key)(void); void* CSM_PREFIX(image4_storage_data)( void CSM_PREFIX(image4_set_nonce)( void CSM_PREFIX(image4_roll_nonce)( errno_t CSM_PREFIX(image4_copy_nonce)( errno_t CSM_PREFIX(image4_execute_object)( errno_t CSM_PREFIX(image4_copy_object)( const void* CSM_PREFIX(image4_get_monitor_exports)(void); errno_t CSM_PREFIX(image4_set_release_type)( errno_t CSM_PREFIX(image4_set_bnch_shadow)( bool CSM_PREFIX(code_signing_enabled)(void); vm_size_t CSM_PREFIX(managed_code_signature_size)(void); void CSM_PREFIX(unrestrict_local_signing_cdhash)( kern_return_t CSM_PREFIX(register_provisioning_profileCSM_PREFIX(unregister_provisioning_profile)( kern_return_t CSM_PREFIX(associate_provisioning_profile)( kern_return_t CSM_PREFIX(disassociate_provisioning_profile)( kern_return_t CSM_PREFIX(register_code_signature)( kern_return_t CSM_PREFIX(unregister_code_signature)( kern_return_t CSM_PREFIX(verify_code_signature)( kern_return_t CSM_PREFIX(reconstitute_code_signature)( kern_return_t CSM_PREFIX(associate_code_signature)( kern_return_t CSM_PREFIX(allow_jit_region)( kern_return_t CSM_PREFIX(associate_jit_region)( kern_return_t CSM_PREFIX(associate_debug_region)( kern_return_t CSM_PREFIX(address_space_debugged)( kern_return_t CSM_PREFIX(allow_invalid_code)( kern_return_t CSM_PREFIX(get_trust_level_kdp)( kern_return_t CSM_PREFIX(address_space_exempt)( kern_return_t CSM_PREFIX(fork_prepare)( kern_return_t CSM_PREFIX(acquire_signing_identifier)( kern_return_t CSM_PREFIX(associate_kernel_entitlements)( kern_return_t CSM_PREFIX(resolve_kernel_entitlements)( kern_return_t CSM_PREFIX(accelerate_entitlements)(
As another example (from the TXM side), consider one of the above functions - image4_set_release_type. This happens to be reported in one of the TXM panic messages:
_func_0xfffffff0170447ec: fffffff0170447ec d503237f PACIBSP ; fffffff0170447f0 a9bf7bfd STP X29, X30, [X31, #-16]! ; SP -= 16; *[SP] = [X29, X30] fffffff0170447f4 910003fd ADD X29, X31, #0 ; FP = SP + 0x0 = 0x0 -- ! fffffff0170447f8 50e1f3e0 ADR X0, 0xfffffff017008676 ; X0 = 0xfffffff017008676 fffffff0170447fc d503201f NOP ; fffffff017044800 70e1f4e1 ADR X1, 0xfffffff01700869f ; X1 = 0xfffffff01700869f fffffff017044804 d503201f NOP ; fffffff017044808 70e1f6e2 ADR X2, 0xfffffff0170086e7 ; X2 = 0xfffffff0170086e7 fffffff01704480c d503201f NOP ; fffffff017044810 52809e03 MOVZ W3, #1264 ; X3 = 0x4f0 _func_0xfffffff01704fa5c("release_type_p == cf4->cf4_osreleasetype", "/Library/Caches/com.apple.xbs/Sources/AppleImage4_txm/src/runtime/txm.c","img4_txm_set_release_type",0x4f0,ARG4); fffffff017044814 94002c92 BL 0xfffffff01704fa5c ;
This is a "cold" path from the actual function, so going back we find its caller (and therefore, image4_set_release_type is _func_0xfffffff0170445ac. This is called from only one place - func_0xfffffff017023520, which is called from - you guessed it - that big switch table:
fffffff017022238 d503249f BTI j ;
fffffff01702223c aa1403e0 MOV X0, X20 ; X0 = X20 (0x0)
_func_0xfffffff017023520(0,ARG1,ARG2,ARG3,ARG4);
fffffff017022240 940004b8 BL 0xfffffff017023520 ;
fffffff017022244 14000027 B 0xfffffff0170222e0 ; (no symbol)
A little hex math shows that 0xfffffff017022238 - 0xfffffff017021f1c is 0x31c - which in the switch table was at entry #37 (counting from 1). Locating this in the kernel is trivial:
DFFenders@xxxx (~) % disarm /tmp/extracted/kernel.rebuilt | grep -B 10 0xfffffff02827e260 | grep \#37 fffffff02827e1dc 528004a8 MOVZ W8, #37 ; X8 = 0x25 # # Now locate which function called us, assuming the function start is within 10 # instructions before the address we've found # DFFenders@xxxx (~) % disarm /tmp/extracted/kernel.rebuilt | grep -B 10 fffffff02827e1dc | grep ^_ _func_0xfffffff02827e1b8:
Another example can be seen by looking at entitlements validated in TXM-land. research.com.apple.license-to-operate
and get-task-allow
are both validated by 0xfffffff01701b080
, so it makes sense they would be in TXM's implementation of allow_invalid_code
. Likewise, com.apple.private.pmap.load-trust-cache
is called from 0xfffffff01701d65c
, which is itself called from 0xffffff0170224b0
- which is at 0xfffff017021ff0
in the dispatching branch table. Some hex math again shows us this is at 0xfffff017021ff0 - 0xfffffff017021f1c = 0xd4
, or function #8.
Digging in __TEXT.__const
- TXM certificates and OIDs
For your daily reminder it's not about __DATA_CONST
alone, TXM's __TEXT.__const
has a few certificates in it, which are easily identifiable from the DER header of "30 82..." and the textual strings therein:
0xfffffff01700ced8
is the Apple Root CA0xfffffff01700d3d0
is the Extra Content Root CA0xfffffff01700d958
is the Secure Boot Root CA0xfffffff01700dec0
is the Basic Attestation User Root CA0xfffffff01700e0e8
is the DDI Root CA (Developer Disk Image)0xfffffff01700e668
is an X86 Root CA (odd?)
And func_0xfffffff01703c85c
handles certificate chain walking.
- There are a few other DER constructs embedded in __TEXT.__const
, which can be extracted using some brute forcing with your favorite (or least hated) DER parsing tool of choice. For example, we see at relative offset 0x14 an array of entitlements to process:
0:d=0 hl=4 l= 295 cons: appl [ 16 ] 4:d=1 hl=2 l= 1 prim: INTEGER :01 7:d=1 hl=4 l= 288 cons: cont [ 16 ] 11:d=2 hl=2 l= 56 cons: SEQUENCE 13:d=3 hl=2 l= 22 prim: UTF8STRING :application-identifier 37:d=3 hl=2 l= 30 prim: UTF8STRING :LOCALSPKEY.swift-playgrounds-* 69:d=2 hl=2 l= 63 cons: SEQUENCE 71:d=3 hl=2 l= 41 prim: UTF8STRING :com.apple.developer.app-management-domain 114:d=3 hl=2 l= 18 prim: UTF8STRING :swift-playgrounds* 134:d=2 hl=2 l= 46 cons: SEQUENCE 136:d=3 hl=2 l= 41 prim: UTF8STRING :com.apple.developer.swift-playgrounds-app 179:d=3 hl=2 l= 1 prim: BOOLEAN :255 182:d=2 hl=2 l= 64 cons: SEQUENCE 184:d=3 hl=2 l= 59 prim: UTF8STRING :com.apple.developer.swift-playgrounds-app.development-build 245:d=3 hl=2 l= 1 prim: UTF8STRING :* 248:d=2 hl=2 l= 49 cons: SEQUENCE 250:d=3 hl=2 l= 35 prim: UTF8STRING :com.apple.developer.team-identifier 287:d=3 hl=2 l= 10 prim: UTF8STRING :LOCALSPKEY 299:d=0 hl=2 l= 49 cons: SEQUENCE
- At address 0xfffffff01700c588 we find
fffffff01700c588: 67 E6 09 6A 85 AE 67 BB |g..j..g.| fffffff01700c590: 72 F3 6E 3C 3A F5 4F A5 |r.n<:.O.| fffffff01700c598: 7F 52 0E 51 8C 68 05 9B |.R.Q.h..| fffffff01700c5a0: AB D9 83 1F 19 CD E0 5B |.......[|
which is the initial context of SHA-256.
- There are also quite a few encryption/hashing algorithm OID encodings embedded. For example,
fffffff01700cb88: 2A 86 48 86 F7 0D 01 01 |*.H.....| fffffff01700cb90: 01 2A 86 48 86 F7 0D 01 |.*.H....| fffffff01700cb98: 01 05 2A 86 48 86 F7 0D |..*.H...| ...
encodes the OID of the RSA algorithm (OID 1.2.840.113549.1.1.1 ) followed (at ..cb91) by SHA1-RSA (1.2.840.113549.1.1.5), and others. For more on OID encodings (specifically, why "2A 86 48 86 F7 0D" encodes "1.2.840.113549"), this reference is handy.
TXM IMG4 support
Looking through TXM's __TEXT again, we come across this interesting bit of code:
func_0xfffffff0170431fc(0,0,0xfffffff0170129d0,SP+0x17);
fffffff0170430a4 94000056 BL 0xfffffff0170431fc ;
fffffff0170430a8 7100081f CMPi W0, #2 ;
fffffff0170430ac 540000a0 B.EQ 0xfffffff0170430c0 ;
fffffff0170430b0 35000600 CBNZ W0, 0xfffffff017043170
..
fffffff017043170 f90003e0 STRi X0, [X31] ; *0x50 = 0x0
fffffff017043174 30e2b860 ADR X0, 0xfffffff017008881 ; X0 = 0xfffffff017008881
fffffff017043178 d503201f NOP ;
func_0xfffffff017020fc0("panic: failed to retrieve research enabled bit: %d");
Looking at the 3rd argument, we see it is a region in __DATA_CONST
:
fffffff0170129d0: 0A 00 00 00 00 00 00 00 |........|
fffffff0170129d8: 00 04 00 00 00 00 00 00 |........|
fffffff0170129e0: 35 00 00 00 00 00 00 00 |5.......|
fffffff0170129e8: 00 00 00 00 68 63 73 72 |....hcsr|
fffffff0170129f0: 0xfffffff01700853c "rsch"
fffffff0170129f8: 0xfffffff017008541 "research mode"
fffffff017012a00: 0xfffffff017008517 "bool"
This structure encodes the well known IMG4 manifest identifiers, and can be seen in other places as well. Specifically:
Address (in __DATA_CONST ) | identifier | Full name | Datatype |
---|---|---|---|
0xfffffff017012728 | nsph | preboot splat manifest hash | digest |
0xfffffff017012810 | DGST | payload digest | digest |
0xfffffff0170128f0 / 0xfffffff017012960 | ECID | unique chip identifier | u64 |
0xfffffff017012880 | AMNM | allow mix-n-match | bool |
0xfffffff017012a40 | vnum | maximum restore version | cstring [version] |
And the following functions get the data from the IMG4:
func_0xfffffff017045254
- gets a u64func_0xfffffff0170431fc
- gets a boolfunc_0xfffffff017043834
- gets a cstring
Other Notes:
func_0xfffffff00706c77c
activates SPRR, by messing withS3_6_C15_C1_*
registers, which we know are theSPRR_*
registers.func_0xfffffff00706c854
activates GXF, byGXF_CONFIG_EL1
,GXF_[PAB]ENTRY_EL1
TL;DR
Putting it all together, we can (carefully) draw the following:
XNU and/or any other privileged components (EL1 or higher) enter SPTM through
GENTER
TXM (EL0) enters SPTM through
SVC #0
SPTM provides several dispatch tables, with 8 "subsystems" (#0-7) corresponding to the calling components. We listed those back here
Of the 8, two remain unexplained/unknown at this time
SPTM manages page tables (for all components) by assigning "types" to pages. This marks the pages as belonging to one or another of the domains (XNU, SART, DART, XNU RO, etc.). Pages may also be "retyped" (=repurposed) according to built-in rules.
SPTM also handles XNU lockdown (in 0xffffff007072ba0, called from XNU at 0xfffffff0285bfcfc, in between
machine_lockdown()
andPE_lockdown_iokit()
), and protects its exception handlers.TXM (subsystem 1) is redirected to from SPTM using a callback it registers.
TXM has its own
CSM_PREFIX
, meaning it is the third (but redacted) provider of CS functionality, alongside XNU proper (for non-PPL devices) andPMAP_CS
.Though redacted, it is relatively straightforward to find the TXM implementation of the calls, by first mapping the selectors from XNU and the bsd/sys/codesigning_internal.h, then looking at the TXM side of things and its entry.
TXM also provides Img4 services (as can be verified from the
CSM_PREFIX
. Quite possibly it has a full libDER implementation.TXM has some half dozen Apple root certificates in it, implying it verifies not only iOS binaries' code signatures, but also those of the DDI and firmware images.
And, yes, we did promise symbol files - but - moving to different binaries in 17.1.1 set back our plans to provide them. Sooner or later, we’ll make good on our word, so stay tuned.