Tracing Back to the Source | SPTM Round 3

February 12, 2025

First a Quick Recap:

If you haven't read our previous blog post and the one that started our exploration into the SPTM images, now's a good time to do so. Let's summarize what we've ascertained thus far:

  • XNU is being refactored into a micro-kernel inspired architecture, aiming to reduce its code base, and move security sensitive operations out of it.

  • The memory space isolation is performed with the help of a Secure Page Table Monitor - SPTM.

  • The code signing, entitlement verification, Developer Mode, Restricted Execution Mode, and other security sensitive operations are handled by the Trusted eXectuion Monitor - TXM

Apple's XNU 10063 sources (from iOS 17.4) and the newly released 11215.1.10 (of iOS 18.0) are more revealing than their predecessors. Whether intentionally or by mistake, previously redacted sources are now (almost) fully visible, and reveal many more details about TXM, SPTM, and the new Exclaves infrastructure introduced in 17.5 on the iPad M4.

When we wrote our first blog post, XNU included bsd/kern/code_signing/txm.c at a measly 1,605 bytes - all #include directives and no source. Later sources reveal that the body of the file - another 40K(!) was enclosed in an #if CONFIG_SPTM directive, which Apple appeared to have purposely redacted. The XNU cross reference shows the number of mentions of SPTM went up from around 0 in 10002 to over a thousand. Definitely much to dig into.

We'll detail our findings on ExclaveCore in a separate post (soon!), but wanted to share our understanding of the new source findings, especially as they complement our previous two posts, which remain (at present) the only public discussion of either SPTM or TXM.

SPTM, revisited

A key header, <sptm/sptm_xnu.h> is missing. This contains the SPTM library functions, and the file that we did find - <sptm/sptm.h> is just for wrappers over it.

The SPTM entry point

We can easily find the entry point using disarm(j)'s gadget finder - the use of GENTER ensures there's no chance of a false positive here:

$ disarm  -g PACIBSP,STP,MOVr,BL,MOVr,LDP,GENTER  kernel.18.0-iPhone17,1
fffffff00a924c98(0x28d4c98): PACIBSP ; 
fffffff00a924c9c(0x28d4c9c): STP	X29, X30, [X31, #-16]! ; SP -= 16; *[SP] = [X29, X30]
fffffff00a924ca0(0x28d4ca0): MOVr	FP, SP          ; FP = SP
fffffff00a924ca4(0x28d4ca4): BL	0xfffffff00805539c  	; auditing_or_other_hook
fffffff00a924ca8(0x28d4ca8): MOVr	SP, FP          ; SP = FP
fffffff00a924cac(0x28d4cac): LDP	X29, X30, [X31], #16 ; [X29, X0] = *[X31]; X31 += 2
fffffff00a924cb0(0x28d4cb0): GENTER	#0 ; 

With that located, another application of disarm(j) with grep(1) reveals all the call outs:

$  disarm kernel.18.0-iPhone17,1| grep -B5  fffffff00a924c98 
Opened companion file kernel.18.0-iPhone17,1.ARM64.2C09F61F-8E85-33FA-AFF2-01FEC3E3144E
_func_0xfffffff008814eb4:
fffffff008814eb4  f2e00010	MOVK	X16, #0, LSL #48 	; X16 := 0x0
fffffff008814eb8  f2c00010	MOVK	X16, #0, LSL #32 	; X16 := 0x0
fffffff008814ebc  f2a00010	MOVK	X16, #0, LSL #16 	; X16 := 0x0
fffffff008814ec0  f2800010	MOVK	X16, #0	; X16 := 0x0
fffffff008814ec4  14843f75	B	0xfffffff00a924c98	; 
		_func_0xfffffff00a924c98(0);
_func_0xfffffff008814ec8:
fffffff008814ec8  f2e00010	MOVK	X16, #0, LSL #48 	; X16 := 0x0
fffffff008814ecc  f2c00010	MOVK	X16, #0, LSL #32 	; X16 := 0x0
fffffff008814ed0  f2a00010	MOVK	X16, #0, LSL #16 	; X16 := 0x0
fffffff008814ed4  f2800030	MOVK	X16, #1	; X16 := 0x1
fffffff008814ed8  14843f70	B	0xfffffff00a924c98	
		_func_0xfffffff00a924c98(ARG0,ARG1,ARG2,ARG3,ARG4);
...
...
_func_0xfffffff00881542c:
fffffff00881542c  f2e00010	MOVK	X16, #0, LSL #48 	; X16 := 0x0
fffffff008815430  f2c00130	MOVK	X16, #9, LSL #32 	; X16 := 0x900000000
fffffff008815434  f2a00010	MOVK	X16, #0, LSL #16 	; X16 := 0x900000000
fffffff008815438  f2800090	MOVK	X16, #4	; X16 := 0x900000004
fffffff00881543c  14843e17	B	0xfffffff00a924c98	
		_func_0xfffffff00a924c98(ARG0,ARG1,ARG2,ARG3,ARG4); 

The callouts are (in this order) 0-21,31, 0x600000000-0x600000007 , 0x300000000-0x300000010 0x500000000-0x500000002, 0x700000000-0x70000000c, and 0x900000001-0x900000004.

Looking through the __LINKINFO.__symbolsets section reveals a couple of additional clues:

$ disarm -e __LINKINFO.__symbolsets kernel.18.0-iPhone17,1 
  Extracted __LINKINFO.__symbolsets (0x3e28000-0x3e6d532) to kernel.18.0-iPhone17,1.__LINKINFO.__symbolsets
$  jlutil kernel.18.0-iPhone17,1.__LINKINFO.__symbolsets | grep sptm 
         SymbolName: _sptm_cputrace_get_carveout_addr
         SymbolName: _sptm_cputrace_get_carveout_size
         SymbolName: _sptm_cputrace_set_base
         SymbolName: _sptm_cputrace_start
         SymbolName: _sptm_cputrace_stop 

and these are presumably from some other subsystem, likely subsystem 9.

0xfffffff0088147b0is_sptm_get_paddr_type.

The startup code - from start_cold - can now be easily found in the assembly, since it's in a .s file to begin with:

_start_cold:
fffffff00a920038  f0feb989      ADRP    X9, #-10445     ; X9 = 0xfffffff008053000-
fffffff00a92003c  91000129      ADD     X9, X9, #0      ; (0xfffffff008053000) 'ExceptionVectorsBase'
fffffff00a920040  d518c009      MSR     VBAR_EL1, X9    ; 
fffffff00a920044  d5033fdf      ISB     ; 
        /* Set up exception stack */ 
fffffff00a920048  d50041bf      MSR     SPSel, #1       ; 
fffffff00a92004c  90001c8a      ADRP    X10, #912       ; X10 = 0xfffffff00acb0000-
fffffff00a920050  9100014a      ADD     X10, X10, #0    ; (0xfffffff00acb0000) -- X10 = X10 + 0x0 = 0xfffffff00acb0000 -- !
fffffff00a920054  9100015f      MOVr    SP, X10        ; SP = X10 (excepstack_top)
  	/* Set up IRQ stack */ 
fffffff00a920058  d50040bf      MSR     SPSel, #0       ; 
fffffff00a92005c  90001c4a      ADRP    X10, #904       ; X10 = 0xfffffff00aca8000-
fffffff00a920060  9100014a      ADD     X10, X10, #0    ; (0xfffffff00aca8000) -- X10 = X10 + 0x0 = 0xfffffff00aca8000 -- !
fffffff00a920064  9100015f      MOVr    SP, X10        ; SP = X10 (intstack_top)
 	 /* Save off boot arguments */ 
fffffff00a920068  aa0103fa      MOVr    X26, X1         ; X26 = X1 (0x0)
fffffff00a92006c  aa0203fb      MOVr    X27, X2         ; X27 = X2 (0x0)
        arm_slide_rebase_and_sign_image(ARG0,ARG1,ARG2);
fffffff00a920070  94000fe8      BL      0xfffffff00a924010      ; 
 
        /**
         * Call into the SPTM for the first time. This function traps to GL2 to
         * signal the SPTM that the fixups phase has been completed.
         */ 
        SPTM_LOAD_DISPATCH_ID SPTM_DOMAIN, SPTM_DISPATCH_TABLE_XNU_BOOTSTRAP, SPTM_FUNCTIONID_FIXUPS_COMPLETE
        SPTM_DOMAIN_ENTER       x16

fffffff00a920074  f2e00010      MOVK    X16, #0, LSL #48        ; X16 := 0x0
fffffff00a920078  f2c00010      MOVK    X16, #0, LSL #32        ; X16 := 0x0
fffffff00a92007c  f2a00010      MOVK    X16, #0, LSL #16        ; X16 := 0x0
fffffff00a920080  f28001f0      MOVK    X16, #15        ; X16 := 0xf
fffffff00a920084  00201420      GENTER  #0      ; 
 
        /**
         * At this point, the SPTM has retyped the RX region to SPTM_XNU_CODE.
         */

	/* Jump to handler */ 
fffffff00a920088  aa1a03e0      MOVr    X0, X26 ; X0 = X26 (0x0)
fffffff00a92008c  aa1b03e1      MOVr    X1, X27 ; X1 = X27 (0x0)
fffffff00a920090  17634c41      B       0xfffffff0081f3194      ; _arm_init
                _arm_init(ARG1,ARG2);
_start_warm:
fffffff00a920094  aa0303f4      MOVr    X20, X3 ; X20 = X3 (0x0)
fffffff00a920098  d53800af      MRS     X15, MPIDR_EL1  ; >not yet..<
fffffff00a92009c  92403de0      AND     X0, X15, #0xffff        ; 
fffffff00a9200a0  90001621      ADRP    X1, #708        ; X1 = 0xfffffff00abe4000-
fffffff00a9200a4  912cc021      ADD     X1, X1, #2864   ; (0xfffffff00abe4000) -- X1 = X1 + 0xb30 = 0xfffffff00abe4b30 -- !
..

About Those SPTM callouts..

The sources give a lot more information about the XNU ←→ interaction, in disclosing some interesting sptm_… function names, but the implementations remain redacted. Nonetheless, it's easy to piece together now a more comprehensive list of callouts (building on what we provided in the previous blogpost:

  • _func_0xfffffff007ff3210 - a.k.a. machine_lockdown calls _func_0xfffffff0085fb1d4 which calls sptm_enter with code 0 - that's _sptm_lockdown_xnu.

  • The above also calls arm_vm_prot_finalize (0xfffffff007fe9238), which then calls sptm_slide_region(…) multiple times. We therefore deduce 0xfffffff0085fb300 - which calls sptm_enter with code #20 - is sptm_slide_region.

  • Right after all these calls are three calls to _func_0xfffffff007ff55bc, which is _ml_static_mfree. The first of these calls gives us SPTMArgs->phys_slide_[papt/size], which - even though we don't have the args - fall in two adjacent fields at 0xfffffff007a0cd60 + 32

  • Similary we can figure out sptm_retype is code #1 (called through 0xfffffff0085fb1e8). This associates one of several predefined types with physical pages, so as to restrict access to the page on type mismatch.

SPTM args

SPTM uses an argument structure which is passed along with the boot_args structure from early initialization.

Xn👀p> slide 0xfffffff007a0cd60 
0xfffffff007a0cd60 -> 0xfffffff039450d60
Xn👀p> dump  0xfffffff039450d60,8 
Dumping 8 bytes from 0xfffffff039450d60 
                               KERNEL	                     
0xfffffff039450d60    0xfffffff0393bdcc0 
Xn👀p> sym _sptm_args 0xfffffff0393bdcc0 
Xn👀p> dump _sptm_args,256 
                               KERNEL	           device tree
0xfffffff0393bdcc0    0xfffffff03e930000        0xfffffff018a04000   
                              Unknown	                     
0xfffffff0393bdcd0    0xfffffff131f44000     00 40 d8 09 08 00 00 00    .@.1.....@......
                              Unknown	                     
0xfffffff0393bdce0    0xfffffff12a2b8000     00 c0 39 03 00 00 00 00    ..+*......9.....
                              Unknown	                     
0xfffffff0393bdcf0    0xfffffff018abcb58     08 00 00 00 00 00 00 00    X...............
                               KERNEL	            KERNEL
0xfffffff0393bdd00    0xfffffff03eb3c000        0xfffffff03ec0c000   
                              Unknown	            KERNEL
0xfffffff0393bdd10    0xfffffff038a48000        0xfffffff03c158000   
                              Unknown	                     
0xfffffff0393bdd20    0xfffffff018ac9000     00 08 00 00 72 61 6e 64    ............rand
0xfffffff0393bdd30 73 65 65 64 00 00 00 00   00 00 00 00 00 00 00 00    seed............
0xfffffff0393bdd40 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff0393bdd50 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff0393bdd60 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff0393bdd70 00 00 00 00 00 00 00 00   48 00 00 00 00 00 00 00    ........H.......
                                        	           Unknown
0xfffffff0393bdd80 00 00 00 00 00 00 00 00      0xfffffff018b0c020   
                                        	           Unknown
libsptm_state starts here
0xfffffff0393bdd90 07 00 00 00 00 00 00 00      0xfffffff018b14878   
                              Unknown	          gPhysBase               
0xfffffff0393bdda0    0xfffffff018b14880     00 00 39 02 08 00 00 00    .H........9.....
                                        	           Unknown
0xfffffff0393bddb0 00 80 34 f5 08 00 00 00      0xfffffff018a04000  

We can find const_sptm_args.libsptm_state (and libsptm_init) from arm_init:

...
fffffff007fe6e44  3d808120      STRi    Q0, [X9, #512]  ; *0xfffffff007979200 = 0x0
fffffff007fe6e48  52800000      MOVZ    W0, #0  ; X0 = 0x0
fffffff007fe6e4c  aa1303e1      MOVr    X1, X19 ; X1 = X19 (0xfffffff007979840)  ; _args
        _PE_init_platform(0,_args);
fffffff007fe6e50  9417b327      BL      0xfffffff0085d3aec      ; 
fffffff007fe6e54  91034280      ADD     X0, X20, #208   ; X0 = 0xfffffff007979d90 &const_sptm_args.libsptm_state
	libsptm_init(0xfffffff007979d90);
fffffff007fe6e58  94184de3      BL      0xfffffff0085fa5e4      ;  libsptm_init
        _wfe_timeout_configure(0);
fffffff007fe6e5c  94003b13      BL      0xfffffff007ff5aa8      ; 
fffffff007fe6e60  f0ffcda8      ADRP    X8, #-1609      ; X8 = 0xfffffff00799d000-
fffffff007fe6e64  b941b908      LDRi    W8, [X8, #440]  ;       ; X8 = *(0xfffffff00799d1b8) (8) = ??? 
fffffff007fe6e68  7101011f      CMPi    W8, #64 ; 
fffffff007fe6e6c  540032e2      B.CS    0xfffffff007fe74c8      ; 
fffffff007fe6e70  d538e109      MRS     X9, CNTKCTL_EL1 ; 
fffffff007fe6e74  aa081128      ORR     X8, X9, X8, LSL #4      ; X0 = X9 | 0x4
fffffff007fe6e78  b2400d08      _ORR    X8, X8, #0xf    ; X0 = X8 | 0xf
fffffff007fe6e7c  d518e108      MSR     CNTKCTL_EL1, X8 ; 
fffffff007fe6e80  d0ffd134      ADRP    X20, #-1498     ; X20 = 0xfffffff007a0c000-
fffffff007fe6e84  f9470e88      LDRi    X8, [X20, #3608]        ;       ; X8 = *(0xfffffff007a0ce18) (20) = ??? 
fffffff007fe6e88  9101b100      ADD     X0, X8, #108    ; (0xfffffff00799d00f) -- X0 = X8 + 0x6c = 0xfffffff00799d06c -- !
fffffff007fe6e8c  d0ff8361      ADRP    X1, #-3986      ; X1 = 0xfffffff007054000-
fffffff007fe6e90  9108f821      ADD     X1, X1, #574    ; (0xfffffff007054000) -- X1 = X1 + 0x23e ='mdsb'
fffffff007fe6e94  f0ffcf73      ADRP    X19, #-1553     ; X19 = 0xfffffff0079d5000-
fffffff007fe6e98  91146273      ADD     X19, X19, #1304 ; (0xfffffff0079d5000) -- X19 = X19 + 0x518 = 0xfffffff0079d5518 -- !
fffffff007fe6e9c  aa1303e2      MOVr    X2, X19 ; X2 = X19 (0xfffffff0079d5518)
fffffff007fe6ea0  52800083      MOVZ    W3, #4  ; X3 = 0x4
fffffff007fe6ea4  52800004      MOVZ    W4, #0  ; X4 = 0x0
        _PE_parse_boot_argn_internal(0xfffffff00799d06c,"mdsb",0xfffffff0079d5518,0x4,0);

And, now that we have the state:

Xn👀p> slide 0xfffffff007979d90 
0xfffffff007979d90 -> 0xfffffff0393bdd90
Xn👀p> dump 0xfffffff0393bdd90,256 
Dumping 256 bytes from 0xfffffff0393bdd90 
                                        	   n_papt_ranges
0xfffffff0393bdd90 07 00 00 00 00 00 00 00      0xfffffff018b14878   
                          papt_ranges	          sptm_phys_begin
0xfffffff0393bdda0    0xfffffff018b14880     00 00 39 02 08 00 00 00    .H........9.....
                       sptm_phys_end     	__papt_of_phys_begin
0xfffffff0393bddb0 00 80 34 f5 08 00 00 00      0xfffffff018a04000   
                       __papt_of_phys_end	  root_table_paddr             
0xfffffff0393bddc0    0xfffffff12e020000     00 40 a9 08 08 00 00 00    .........@......
                               KERNEL	           Unknown
0xfffffff0393bddd0    0xfffffff03e564000        0xfffffff018a5d9a0   
0xfffffff0393bdde0 00 00 00 00 00 00 00 00   47 00 00 00 00 00 00 00    ........G.......
                              Unknown	                     
0xfffffff0393bddf0    0xfffffff018a80000     00 00 00 00 00 00 00 00    ................
                                        	           Unknown
0xfffffff0393bde00 00 00 00 00 00 00 00 00      0xfffffff018b0c01c   
                                        	           Unknown
0xfffffff0393bde10 00 00 00 00 00 00 00 00      0xfffffff018abcad0   
                                        	           Unknown
0xfffffff0393bde20 08 00 00 00 00 00 00 00      0xfffffff018abcb10   
0xfffffff0393bde30 08 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff0393bde40 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................

Xn👀p>  dump 0xfffffff018b14878,8 
Dumping 8 bytes from 0xfffffff018b14878 
0xfffffff018b14878 0f 00 00 00 00 00 00 00   -- libsptm_n_papt_ranges
Dumping 384 bytes from 0xfffffff018b14880 
                   papt_table[0].paddr_start          KERNEL
0xfffffff018b14880 00 00 09 09 08 00 00 00      0xfffffff03e000000   
                                            papt_table[1].paddr_start
0xfffffff018b14890 ae b0 03 00 00 00 00 00   00 00 39 02 08 00 00 00    ..........9.....
                              Unknown	                     
0xfffffff018b148a0    0xfffffff12a2b8000     e7 0c 00 00 00 00 00 00    ..+*............
                                        	            KERNEL
0xfffffff018b148b0 00 c0 78 05 08 00 00 00      0xfffffff0398ac000   
0xfffffff018b148c0 05 09 00 00 00 00 00 00   00 00 ba 07 08 00 00 00    ................
                              Unknown	                     
0xfffffff018b148d0    0xfffffff038a48000     99 03 00 00 00 00 00 00    ...8............
                                        	           Unknown
0xfffffff018b148e0 00 00 b5 08 08 00 00 00      0xfffffff12e150000   
0xfffffff018b148f0 7d 00 00 00 00 00 00 00   00 40 a0 08 08 00 00 00    }........@......
                              Unknown	                     
0xfffffff018b14900    0xfffffff018a04000     51 00 00 00 00 00 00 00    .@......Q.......
                                        	            KERNEL
0xfffffff018b14910 00 80 eb 08 08 00 00 00      0xfffffff03c028000   
0xfffffff018b14920 4c 00 00 00 00 00 00 00   00 40 d8 08 08 00 00 00    L........@......
                               KERNEL	                     
0xfffffff018b14930    0xfffffff03bef4000     2c 00 00 00 00 00 00 00    .@.;....,.......
                                        	           Unknown
0xfffffff018b14940 00 80 fe 08 08 00 00 00      0xfffffff12e0a8000   
0xfffffff018b14950 2a 00 00 00 00 00 00 00   00 40 e3 08 08 00 00 00    *........@......
                              Unknown	                     
0xfffffff018b14960    0xfffffff12e024000     21 00 00 00 00 00 00 00    .@......!.......
                                        	           Unknown
0xfffffff018b14970 00 c0 72 05 08 00 00 00      0xfffffff028a48000   
0xfffffff018b14980 17 00 00 00 00 00 00 00   00 40 d4 08 08 00 00 00    .........@......
                               KERNEL	                     
0xfffffff018b14990    0xfffffff03beb4000     0f 00 00 00 00 00 00 00    .@.;............
                                        	           Unknown
0xfffffff018b149a0 00 80 b4 08 08 00 00 00      0xfffffff028aa4000   
0xfffffff018b149b0 02 00 00 00 00 00 00 00   00 00 d8 08 08 00 00 00    ................
                              Unknown	                     
0xfffffff018b149c0    0xfffffff12e020000     01 00 00 00 00 00 00 00    ................
                                        	            KERNEL
0xfffffff018b149d0 00 80 78 05 08 00 00 00      0xfffffff03c200000   
0xfffffff018b149e0 01 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff018b149f0 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................

TXM

We've already established that TXM, like SPTM, can be entered from kernel. The newly visible <bsd/sys/trusted_execution_monitor.h> shows us:

typedef struct _txm_call {
        /* Input arguments */
        TXMKernelSelector_t selector;          // 0x0   (SP+32)
        TXMReturnCode_t failure_code_silent;   // 0x4
        bool failure_fatal;		       // 0x8
        bool failure_silent;		       // 0x9
        bool skip_logs;			       // 0xa
	/* implcit padding of one byte to align the uint32_ts */
        uint32_t num_input_args;	       // 0x0c  
        uint32_t num_output_args; 	       // 0x10  (SP +48)

        /* Output arguments */
        TXMReturn_t txm_ret;
        uint64_t num_return_words;
        uint64_t return_words[kTXMStackReturnWords];
} txm_call_t;
 
/**
 * The main function to use for calling into the TrustedExecutionMonitor. This
 * function handles all the bits required, including allocation/deallocation of
 * the thread stack pages, the CPU instructions required to reach TXM, and also
 * going through the TXM buffer and capturing any logs left by the monitor.
 */
kern_return_t
txm_kernel_call(
        txm_call_t *parameters, ...);

The fields of the txm_call_t can be further explained as follows:

  • selector: is a number indicating the call type. This is very similar to the selector notion in IOKit. Since multiple calls are multiplexed over a single entry point.

  • failure_fatal: Indicating a failure should result in a call to panic().

  • failure_silent: Indicating a failure doesn't merit an os_log operation.

  • num_[input/output]_args: Similar to IOConnectCalls, how many arguments the call either expects or returns

  • txm_ret: The standard return value from a call.

  • return_words: Indicating the number and addresses of extra out parameters from this call.

TXM also uses special thread stacks. Every thread_t can now be associated with a TXM thread stack (thread_[dis]associate_txm_thread_stack() in osfmk/kern/thread.c). This is done by [acquire/release]_thread_stack(), which are inlined and called during txm_kernel_call().

TXM_kernel_CALL

It's easy to match txm_kernel_call with two following disarm(j) rules:

0|received fatal error for a selector from TXM|_txm_kernel_call|_panic|bsd/kern/code_signing/txm.c
0|received excessive return words from TXM|_txm_kernel_call_internal|_panic|bsd/kern/code_signing/txm.c

Which in our kernel yielded 0xfffffff0083577b0 . Symbolication ends up leading to numerous functions in the disassembly of the general form:

_func_0xfffffff00835646c:
fffffff00835646c  d503237f      PACIBSP ; 
fffffff008356470  d10203ff      SUBi    SP, SP, #128    ; SP = SP - 0x80
fffffff008356474  a9077bfd      STP     X29, X30, [X31, #112]   ; *[SP +112] = [X29, X30]
fffffff008356478  9101c3fd      MOVr    X29, X31        ; FP = XZR (0x0)..
fffffff00835647c  6f00e400      MOVI.2D V0, #0000000000000000   ; 
fffffff008356480  f90033ff      STRi    XZR, [SP, #96] ; *0x60 = 0x0
fffffff008356484  ad0203e0      STP     Q0, Q0, [SP, #64]      ; *[SP +32] = [X0, X0]
fffffff008356488  ad0103e0      STP     Q0, Q0, [SP, #32]      ; *[SP +16] = [X0, X0]
fffffff00835648c  3d8007e0      STRi    Q0, [SP, #16]  ; *0x10 = 0x0
....
.... initialization of the structure fields which are being used:
....
fffffff008356490  528003a8      MOVZ    W8, #29 ; X8 = 0x1d
fffffff008356494  b90013e8      STRi    X8, [SP, #16]  ; *0x10 = 0x1d
fffffff008356498  52800028      MOVZ    W8, #1  ; X8 = 0x1
fffffff00835649c  39005be8      STRBi   W8, [SP, #88]  ; 
fffffff0083564a0  b9001fe8      STRi    X8, [SP, #28]  ; *0x1c = 0x1
fffffff0083564a4  f90003e8      STRi    X8, [SP]       ; *0x0 = 0x1
fffffff0083564a8  910043e0      MOVr    X0, SP ; X0 = XZR (0x0)..
... 
... The txm_kernel_call - as a vararg, all arguments are on the stack
...
        _txm_kernel_call(SP);
fffffff0083564ac  940004c1      BL      0xfffffff0083577b0      ; 
...
fffffff0083564b0  a9477bfd      LDP     X29, X30, [X31, #112]   ; [X29, X0] = *[X31]
fffffff0083564b4  910203ff      MOVr    X31, X31        ; SP = XZR (0x0)..
fffffff0083564b8  d65f0fff      RETAB   ; 

With only the two highlighted rows - MOVZ into X8, followed by stores - being different in between them. These align nicely with the txm_kernel_call wrappers in the sources, which use kTXMKernelSelector* constants as "selectors" for whichever TXM function is to be invoked.

(Unless we're missing something) the kTXMKernelSelector* constants are also in some redacted .h file. Thanks to this pattern, however, it's simple to reconstruct these - Starting with kTXMKernelSelectorGetSecureChannelAddr = 7 (from txm_secure_channel_shared_page()). Likewise, kTXMKernelSelectorImage4Dispatch is 42. Doing this for all constants we found in the sources, we end up with a largely reconstructed enumeration:

enum { 
kTXMKernelSelectorGetLogInfo = 2,
kTXMKernelSelectorEnterLockdownMode = 5,
kTXMKernelSelectorEnableRestrictedMode = 6,
kTXMKernelSelectorGetSecureChannelAddr = 7,
kTXMSelectorUpdateDeviceState = 8,
kTXMSelectorCompleteSecurityBootMode = 9,
kTXMKernelSelectorRegisterProvisioningProfile = 15,
kTXMKernelSelectorTrustProvisioningProfile = 16,
kTXMKernelSelectorUnregisterProvisioningProfile = 17,
kTXMKernelSelectorAssociateProvisioningProfile = 18,
kTXMKernelSelectorDisassociateProvisioningProfile = 19,
kTXMKernelSelectorRegisterCodeSignature = 20,
kTXMKernelSelectorUnregisterCodeSignature = 21,
kTXMKernelSelectorValidateCodeSignature = 22,
kTXMKernelSelectorReconstituteCodeSignature = 23,
kTXMKernelSelectorSetLocalSigningPublicKey = 24,
kTXMKernelSelectorGetLocalSigningPublicKey = 25,
kTXMKernelSelectorAuthorizeLocalSigningCDHash = 26,
kTXMKernelSelectorAuthorizeCompilationServiceCDHash = 27,
kTXMKernelSelectorMatchCompilationServiceCDHash = 28,

kTXMKernelSelectorDeveloperModeToggle = 29,
kTXMKernelSelectorAcquireSigningIdentifier = 30,
kTXMKernelSelectorAssociateKernelEntitlements = 31,
kTXMKernelSelectorAccelerateEntitlements= 32,
kTXMKernelSelectorRegisterAddressSpace =34,
kTXMKernelSelectorUnregisterAddressSpace = 35,
kTXMKernelSelectorAssociateCodeSignature = 36,
kTXMKernelSelectorAllowJITRegion = 37,
kTXMKernelSelectorAllowInvalidCode = 39,
kTXMKernelSelectorResolveKernelEntitlementsAddressSpace = 41,
kTXMKernelSelectorImage4Dispatch = 42,
kTXMKernelSelectorImage4SetNonce = 46,
kTXMKernelSelectorImage4RollNonce = 47,
kTXMKernelSelectorImage4GetNonce = 48,
};

From the TXM side, the entry point is 0xfffffff017023eac, which is registered under subsystem 2 early in TXM's initialization:

fffffff01701ba64  d503237f      PACIBSP ; 
fffffff01701ba68  d100c3ff      SUBi    SP, SP, #48     ; SP = SP - 0x30
fffffff01701ba6c  a9014ff4      STP     X20, X19, [X31, #16]    ; *[SP +16] = [X20, X19]
fffffff01701ba70  a9027bfd      STP     X29, X30, [X31, #32]    ; *[SP +32] = [X29, X30]
fffffff01701ba74  910083fd      MOVr    X29, X31  	        ; FP = SP (0x0)..
fffffff01701ba78  10fa5293      ADR     X19, 0xfffffff0170104c8 
fffffff01701ba7c  d503201f      NOP     ; 
fffffff01701ba80  52800028      MOVZ    W8, #1  ; X8 = 0x1
fffffff01701ba84  39000268      STRBi           ; 
        func_0xfffffff0170212f0(0);
fffffff01701ba88  9400161a      BL      0xfffffff0170212f0      ; 
fffffff01701ba8c  10042110      ADR     X16, 0xfffffff017023eac ; 
fffffff01701ba90  d503201f      NOP     ; 
fffffff01701ba94  dac123f0      PACIZA  X16     ; 
fffffff01701ba98  aa1003e1      MOVr    X1, X16 ; X1 = X16 (0xfffffff017023eac)
fffffff01701ba9c  52800000      MOVZ    W0, #0  ; X0 = 0x0
fffffff01701baa0  52800042      MOVZ    W2, #2  ; X2 = 0x2
        registerSubsystem(0,0xfffffff017023eac,0x2);
fffffff01701baa4  9400a8b0      BL      0xfffffff017045d64      ; 
fffffff01701baa8  100414d0      ADR     X16, 0xfffffff017023d40 ; 
fffffff01701baac  d503201f      NOP     ; 
fffffff01701bab0  dac123f0      PACIZA  X16     ; 
fffffff01701bab4  aa1003e1      MOVr    X1, X16 ; X1 = X16 (0xfffffff017023d40)
fffffff01701bab8  52800020      MOVZ    W0, #1  ; X0 = 0x1
fffffff01701babc  52800022      MOVZ    W2, #1  ; X2 = 0x1
        _registerSubSystem(0x1,0xfffffff017023d40,0x1);
fffffff01701bac0  9400a8a9      BL      0xfffffff017045d64      ; 
        func_0xfffffff01702153c(0);  // getsBuildVariant
fffffff01701bac4  9400169e      BL      0xfffffff01702153c      ; 
fffffff01701bac8  f90003e0      STRi    X0, [X31]       ; *0x0 = 0x0
fffffff01701bacc  10f56f00      ADR     X0, 0xfffffff0170068ac  ; X0 = 0xfffffff0170068ac-
fffffff01701bad0  d503201f      NOP     ; 
        func_0xfffffff01701d77c("build variant: %s");

This transfers control to 0xfffffff017022798 which switches on the kTXMKernelSelector... (at fffffff01702285c) with a branch table @0xfffffff017023204. The table has 48 entries, exactly up to kTXMKernelSelectorImage4GetNonce (which is also 48). From this, we can work out and symbolicate the TXM handler names.

fffffff017022798  d503237f      PACIBSP ; 
fffffff01702279c  d10303ff      SUBi    SP, SP, #192    ; SP = SP - 0xc0
fffffff0170227a0  a90767fa      STP     X26, X25, [X31, #112]   ; *[SP +112] = [X26, X25]
..
fffffff017022808  b140113f      CMN     X9, #16384 (@TODO).     ; 
fffffff01702280c  54000083      B.CC    ok			;  0xfffffff01702281c 
..
        some_panic(0x42,...);
fffffff017022818  97fffbe2      BL      0xfffffff0170217a0      ; 
ok:
	
fffffff01702281c  aa0003f3      MOVr    X19, X0 ; X19 = X0 (0x0)
fffffff017022820  f9001be8      STRi    X8, [X31, #48]  ; *0x30 = 0x0
fffffff017022824  52880008      MOVZ    W8, #16384      ; X8 = 0x4000
fffffff017022828  4e080d00      DUP.2D  V0, X8 
fffffff01702282c  3c8383e0      STUR    Q0, [X31, #56]  ; 
fffffff017022830  9100c3e0      MOVr    X0, X31 ; X0 = XZR (0x0)..
fffffff017022834  528004e1      MOVZ    W1, #39 ; X1 = 0x27
        page_type_maybe(0,0x27);
fffffff017022838  9400045f      BL      0xfffffff0170239b4      ; 
fffffff01702283c  f9000e7f      STRi    XZR, [X19, #24] ; *0x18 = 0x0
fffffff017022840  51000730      SUBi    W16, W25, #1    ; X16 = X25 - 0x1
fffffff017022844  7100be1f      CMPi    W16, #47        ; 
fffffff017022848  540011a8      B.HI    0xfffffff017022a7c      ; 
fffffff01702284c  f100be1f      CMPi    X16, #47        ; 
fffffff017022850  9a9f9210      CSEL    X16, X16, X31, LS       ; X16 = ( ) ? 0x0 : 0x0
fffffff017022854  10004d91      ADR     X17, 0xfffffff017023204 ; X17 = 0xfffffff017023204-
fffffff017022858  d503201f      NOP     ; 
fffffff01702285c  b8b07a30      LDRSW(r)        X16, [X17, X16, LSL #2] ;  branch table @0xfffffff017023204
fffffff017022860  10000011      ADR     X17, 0xfffffff017022860 ; X17 = 0xfffffff017022860-
fffffff017022864  8b100230      ADDsr   X16, X17, X16   ; R16 = R17 + R16 = 0xfffffff017022870
fffffff017022868  d61f0200      BR      X16     ; (no symbol)
code_:
fffffff01702286c  d503249f      BTI     j       ; 
...
fffffff017023204  00000224      DCD     0x224   ; 
fffffff017023208  00000254      DCD     0x254   ; 
fffffff01702320c  0000029c      DCD     0x29c   ; 
fffffff017023210  0000001c      DCD     0x1c    ; 
fffffff017023214  000002d4      DCD     0x2d4   ; 
...
fffffff0170232a8  000007cc      DCD     0x7cc   ; 
fffffff0170232ac  0000000c      DCD     0xc     ; 
fffffff0170232b0  0000000c      DCD     0xc     ; 
fffffff0170232b4  0000000c      DCD     0xc     ; 
fffffff0170232b8  0000000c      DCD     0xc     ; 
fffffff0170232bc  0000000c      DCD     0xc     ; 
fffffff0170232c0  0000000c      DCD     0xc     ;  

Exiting TXM

TXM returns to whomever called it (presumably, XNU) loading a code into X16. There are several possible codes here:

_sptm_retype_from_txm:
fffffff017045d50  f2e00010      MOVKKKK X16, #0, 0x100000003
fffffff017045d60  140058be      B       do_svc	; 0xfffffff01705c058    
_registerSubSystem:
fffffff017045d64  f2e00010      MOVKKKK X16, #0, 0x100000002
fffffff017045d74  140058b9      B       do_svc	; 0xfffffff01705c058
svc_0_100000004_unknown:
fffffff017045d78  f2e00010      MOVKKKK X16, 0x100000004
fffffff017045d88  140058b4      B       do_svc	; 0xfffffff01705c058    
svc_0_fe00000000:   # triggers a panic
fffffff017045d8c  f2e00010      MOVKKKK X16, #0, 0xfe00000000
fffffff017045d9c  140058af      B       do_svc	; 0xfffffff01705c058     
svc_0_fd00000000:   # returns to caller
fffffff017045da0  f2e00010      MOVKKKK X16, 0xfd00000000 
fffffff017045db0  140058aa      B       do_svc	; 0xfffffff01705c058     
svc_ff_fe00000000:
fffffff017045db4  f2e00010      MOVKKKK X16, 0xff00000000
fffffff017045dc4  140058a5      B       do_svc	; 0xfffffff01705c058  
which all go to a common funnel, to issue an SVC. Since we are in GL0 at this point, this would go to GL1, which is where SPTM awaits:
$ disarm -a 0xfffffff01705c058 txm.iphoneos.release.im4p
fffffff01705c058  d503237f      PACIBSP ; 
fffffff01705c05c  d4000001      SVC     #0      ; 
fffffff01705c060  d65f0fff      RETAB   ; 
Back in SPTM this gets to the SVC #0 handler, which uses UBFX X9, X16, #32, #8 (unsigned bit field extract, to take out bits 32-39) and then operate on the code, e.g.:
fffffff027061928  d3609e09      UBFX    X9, X16, #32, #8        ; 
fffffff02706192c  d2801faa      MOVZ    X10, #253               ; X10 = 0xfd
fffffff027061930  eb0a013f      CMPsr   X9, X10 ; 
fffffff027061934  54000041      B.NE    0xfffffff02706193c      ; 
fffffff027061938  140000f6      B       0xfffffff027061d10      ; (no symbol)
From what we see:
  • 0x10000000. codes are system calls. We've discussed those in a previous post
  • 0xfd returns to the caller.
  • 0xfe triggers an SPTM panic path (evident by so many [TXM] Panic strings in calls to 0xfffffff017045d8c).
  • 0xff is unknown (at least, to us)

Example: TXM Logging

TXM logs its output to memory pages it shares with the kernel, with the TXM logging function at fffffff01701d77c. The get_logging_info() routine initializes this, by caching the address of the txm_log_page, the txm_log_head, and txm_log_sync:

static void
get_logging_info(void)
{
        txm_call_t txm_call = {
                .selector = kTXMKernelSelectorGetLogInfo,
                .failure_fatal = true,
                .num_output_args = 3
        };
        txm_kernel_call(&txm_call);

        txm_log_page = (const char*)txm_call.return_words[0];
        txm_log_head = (const uint32_t*)txm_call.return_words[1];
        txm_log_sync = (const uint32_t*)txm_call.return_words[2];
} 

We can find get_logging_info() inlined in its caller code_signing_init() (0xfffffff00835823c). From the disassembly we find that the kTXMKernelSelectorGetLogInfo is 2, and the address of txm_log_page is 0xfffffff0079bc478. From there, it's a simple matter of xn00ping around, getting to that page (whose address is in the variable), and seeing the 128-byte log records output by TXM:

Xn👀p> dump 0xfffffff050cc4000,4096
Dumping 4096 bytes from 0xfffffff050cc4000 

0xfffffff050cc4000 54 58 4d 20 5b 4c 6f 67   5d 3a 20 73 65 74 75 70    TXM [Log]: setup
0xfffffff050cc4010 20 6c 6f 67 67 69 6e 67   3a 20 33 32 37 36 38 20     logging: 32768 
0xfffffff050cc4020 62 79 74 65 73 20 28 32   35 36 20 7c 20 31 32 38    bytes (256 | 128
0xfffffff050cc4030 29 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    )...............
0xfffffff050cc4040 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff050cc4050 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff050cc4060 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff050cc4070 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
0xfffffff050cc4080 54 58 4d 20 5b 4c 6f 67   5d 3a 20 73 79 73 74 65    TXM [Log]: syste
0xfffffff050cc4090 6d 20 73 75 70 70 6f 72   74 73 20 44 49 54 20 66    m supports DIT f
0xfffffff050cc40a0 65 61 74 75 72 65 00 00   00 00 00 00 00 00 00 00    eature..........
0xfffffff050cc40b0 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00    ................
..
From the TXM side, we see 0xfffffff01701d77c is the log function:
$ disarm txm.iphoneos.release.im4p| grep fffffff01701d77c | grep -v ^f
txm.iphoneos.release.im4p: This is an IM4P... with a BVX2 payload... Uncompressed 409760 bytes
	func_0xfffffff01701d77c("profile does not have a standard length UUID",ARG1,ARG2,ARG3,ARG4);
	func_0xfffffff01701d77c("page enforcement failed (%u | %u): (%p | %u) --> %u | 0x%016llX");
	...
	func_0xfffffff01701d77c("setup logging: %u bytes (%u | %u)");
	func_0xfffffff01701d77c("loaded external trust cache modules: %u");
	func_0xfffffff01701d77c("failed to load external trust cache module: %u");
	func_0xfffffff01701d77c("system supports DIT feature");
...
	func_0xfffffff01701d77c("%s");
	func_0xfffffff01701d77c("%s: %s");

The function is a small wrapper of a bigger function to actually perform the logging:

fffffff01701d77c  d503237f      PACIBSP ; 
fffffff01701d780  d10083ff      SUBi    SP, SP, #32     ; SP = SP - 0x20
fffffff01701d784  a9017bfd      STP     X29, X30, [X31, #16]    ; *[SP +16] = [X29, X30]
fffffff01701d788  910043fd      MOVr    X29, SP        ; FP = SP (0x0)..
fffffff01701d78c  910043a8      ADD     X8, X29, #16    ; (0x100) -- X8 = FP + 0x10 = 0x10 -- !
fffffff01701d790  f90007e8      STRi    X8, [SP, #8]    ; *0x58 = 0x10
fffffff01701d794  910043a1      ADD     X1, X29, #16    ; (0x0) -- X1 = FP + 0x10 = 0x10 -- !
        func_0xfffffff01701d7a8(0,0x10);
fffffff01701d798  94000004      BL      0xfffffff01701d7a8      ; 
fffffff01701d79c  a9417bfd      LDP     X29, X30, [SP, #16]    ; [X29, X0] = *[X31]
fffffff01701d7a0  910083ff      ADD     SP, X31, #32    ; (0x50) -- SP = SP + 0x20 = 0x20 -- !
fffffff01701d7a4  d65f0fff      RETAB   ; 
Since get_logging_info() used selector 2, we look at the branch table (@fffffff017023204)

fffffff017023204  00000224      DCD     0x224   ;  case 0
fffffff017023208  00000254      DCD     0x254   ;  case 1
fffffff01702320c  0000029c      DCD     0x29c   ;  case 2
fffffff017023210  0000001c      DCD     0x1c    ;  case 3
..
..

Adding 0x29c to 0xfffffff017022860 gives us 0xfffffff017022afc, where we find:

fffffff017022afc  d503249f      BTI     j       ; 
        func_0xfffffff017024084(ARG0,ARG1,ARG2,ARG3,ARG4);
fffffff017022b00  94000561      BL      0xfffffff017024084      ; 
fffffff017022b04  52800088      MOVZ    W8, #4  ; X8 = 0x4
fffffff017022b08  10f6ce09      ADR     X9, 0xfffffff0170104c8  ; X9 = 0xfffffff0170104c8-
fffffff017022b0c  d503201f      NOP     ; 
fffffff017022b10  9102c12a      ADD     X10, X9, #176   ; (0x0) -- X10 = X9 + 0xb0 = 0xfffffff017010578 -- !
fffffff017022b14  a901a808      STP     X8, X10, [X0, #24]      ; *[? +24] = [X8, X10]
fffffff017022b18  b940d928      LDRi    W8, [X9, #216]  ;       ; X8 = *(0xfffffff0170105a0) (9) = ??? 
fffffff017022b1c  f9001408      STRi    X8, [X0, #40]   ; *0x28 = 0x4
fffffff017022b20  f9409928      LDRi    X8, [X9, #304]  ;       ; X8 = *(0xfffffff0170105f8) (9) = ??? 
fffffff017022b24  f9001808      STRi    X8, [X0, #48]   ; *0x30 = 0x4
fffffff017022b28  f9409d28      LDRi    X8, [X9, #312]  ;       ; X8 = *(0xfffffff017010600) (9) = ??? 
fffffff017022b2c  f9001c08      STRi    X8, [X0, #56]   ; *0x38 = 0x4
fffffff017022b30  14000003      B       0xfffffff017022b3c      ; (no symbol)
fffffff017022b3c  d503249f      BTI     j       ; 
fffffff017022b40  f9000bff      STRi    XZR, [SP, #16]  ; *[SP + 16] = 0x0
fffffff017022b44  1400012f      B       common_return   ; 0xfffffff017023000      

From the BTI j we know (thanks to ARMv8.5 Branch Target Indicators) that our hex math was correct (since the instruction indicates this is a safe address to jump (BR) to. We then see the setting of the output_words [0] through [2] - (i.e. [X0, #40] htrough [X0, #56]. The txm_log_page value written is from X9 + 216 - 0xfffffff0170105a0

And, to sum up the example with the return to caller:

common_return:
fffffff017023000  f9400be0      LDRi    X0, [SP, #16]  ; X0 = SP + 16
fffffff017023004  f9000660      STRi    X0, [X19, #8]   ; *[X19 + 8] = X0 = SP + 16
        do_return(0x8080000000c);
fffffff017023008  97fffd3b      BL      do_return	; 0xfffffff0170224f4      
...
do_return:
fffffff0170224f4  d503237f      PACIBSP ; 
fffffff0170224f8  a9be4ff4      STP     X20, X19, [X31, #-32]!  ; SP -= 32; *[SP] = [X20, X19]
fffffff0170224fc  a9017bfd      STP     X29, X30, [X31, #16]    ; *[SP +16] = [X29, X30]
fffffff017022500  910043fd      MOVr    X29, X31        ; FP = XZR (0x0)..
fffffff017022504  aa0003f3      MOVr    X19, X0 ; X19 = X0 (0x0)
	some_context();
fffffff017022508  940006df      BL      some_context	; 0xfffffff017024084      ; 
fffffff01702250c  39400008      LDRB    W8, X0]
fffffff017022510  34000088      CBZ     W8, 0xfffffff017022520  ; 
fffffff017022514  aa0003f4      MOVr    X20, X0 ; X20 = X0 (0x0)
	svc_0x38
fffffff017022518  940006d6      BL      svc_0x38  ; 0xfffffff017024070     
;; return to caller here 
fffffff01702251c  3901629f      STRBi   WZR, [X20, #88]
fffffff017022520  aa1303e0      MOVr    X0, X19 ; X0 = X19 (0x0)
	svc_0_fd00000000 (X0)
fffffff017022524  94008e1f      BL      0xfffffff017045da0      ;     

PMAP/TXM interaction

XNU's pmap layer has been modified to accommodate for TXM Address Spaces.

struct pmap {
        /* Pointer to the root translation table. */
        tt_entry_t *tte;

        /* Physical page of the root translation table. */
        pmap_paddr_t ttep;

...
        bool reserved6;

#define PMAP_TYPE_USER 0 /* ordinary pmap */
#define PMAP_TYPE_KERNEL 1 /* kernel pmap */
#define PMAP_TYPE_COMMPAGE 2 /* commpage pmap */
#define PMAP_TYPE_NESTED 3 /* pmap nested within another pmap */
        uint8_t type;

        /*
         * TrustedExecutionMonitor manages its own address space data structure and
         * the PMAP is used as the owning structure for keeping this structure.
         */
        uint32_t reserved7[4];
        void *reserved8;
        uint8_t reserved9;
};

Interestingly enough, pmap.h is full of pmap_txm_* functions, bridging betwen XNU and the TXM, and those refer to fields that are not declared in the pmap.h file. Specifically, txm_addr_space and txm_trust_level. It's likely that the dereferecing of these fields happens through macros, which are unavailable in the open sources. From the assembly, however, we can safely reckon reserved7 is a type of lock, reserved8 is the txm_addr_space field, and the reserved9 is txm_trust_level.

Other Tidbits:

  • TXM and SPTM register two pseudo-kexts: g_sptm_kmod_info and g_txm_kmod_info (from /libkern/c++/OSKext.cpp). Unlike the other pseudokexts, however, these do record the (slid) addresses and mapped/wired sizes of both SPTM and TXM, along with their Mach-O UUID. In the output of kextstat, this looks like this:

sh-3.2# kextstat  | head
Index Refs Address            Size       Wired      Name (Version) UUID 
    1    0 0xfffffff027004000 0xec000    0xec000    com.apple.sptm (24.0.0) 2BB4E63C-24CC-3A4D-86F8-92A13349656E
    2    0 0xfffffff017004000 0x68000    0x68000    com.apple.txm (24.0.0) 0F3F33B1-DA4D-3D23-A051-C08BA710ADA7
    3  177 0                  0          0          com.apple.kpi.bsd (24.0.0) 359DB623-5C72-3BF3-B5D2-785FB581FFF7
	...

We Are Hiring iOS and Android Researchers / Developers - more details here

Next
Next

‘tis the Season