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_tables 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:

#SubsystemDescriptor# of calls/services
0XNU0xfffffff00705b60022
1TXM0xfffffff00705b7005
2?0xfffffff00705b8003
3t8110dart0xfffffff0070589a817
4??0xfffffff007058aa8..
5SART0xfffffff0070588a03
6NVMe0xfffffff0070587885
7UAT0xfffffff00705800013

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:

ConstantRetype classIn handlerOut handler
0x8 or 0x11 or 0x12cpu_root_table_func_0xfffffff0070875ec_func_0xfffffff007087374
0xbxnu_default0xfffffff00708735c0xfffffff007087174
0xe or 0xf or 0x10xnu_defaultN/A0xfffffff0070870e8
0x13, 0x14cpu_page_table....
0x15, 0x16cpu_page_table....N/A
0x18xnu_rozoneN/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:

  1. 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 jump 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_PREFIXed 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 CA

  • 0xfffffff01700d3d0 is the Extra Content Root CA

  • 0xfffffff01700d958 is the Secure Boot Root CA

  • 0xfffffff01700dec0 is the Basic Attestation User Root CA

  • 0xfffffff01700e0e8 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)identifierFull nameDatatype
0xfffffff017012728nsphpreboot splat manifest hashdigest
0xfffffff017012810DGSTpayload digestdigest
0xfffffff0170128f0 / 0xfffffff017012960ECIDunique chip identifieru64
0xfffffff017012880AMNMallow mix-n-matchbool
0xfffffff017012a40vnummaximum restore version cstring [version]

And the following functions get the data from the IMG4:

  • func_0xfffffff017045254 - gets a u64

  • func_0xfffffff0170431fc - gets a bool

  • func_0xfffffff017043834 - gets a cstring

Other Notes:

  • func_0xfffffff00706c77c activates SPRR, by messing with S3_6_C15_C1_* registers, which we know are the SPRR_* registers.

  • func_0xfffffff00706c854 activates GXF, by GXF_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() and PE_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) and PMAP_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.

We Are Hiring Android Researchers and Developers - more details here

Andy Bartlomain

I design and develop custom, professional Squarespace websites for businesses, restaurants, churches, and entrepreneurs. I code beyond Squarespace's limitations to create unique layouts that best fit my clients' content and needs. The custom Squarespace websites I develop are optimized for any browser window size and is coded to keep Squarespace's built-in ease-of-use intact. My custom Squarespace designs will not make ongoing updates and maintenance complicated!

https://www.connectionmadedesign.com
Previous
Previous

Will macOS and iOS merge?

Next
Next

iOS 17: New Version, New Acronyms