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:
Labeling this as _sptm_syscall
, we can then disassemble the rest of TXM (that is, from __TEXT_EXEC
, wherein we see the following):
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:
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:
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:
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
):
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):
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:
SPTM retyping
Looking through symbols, we see quite a few references to ...page_type_retype_[in/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:
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:
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:
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:
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.
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:
Going back from it:
Then disassembling ...2827f16c:
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:
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:
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:
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:
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:
- At address 0xfffffff01700c588 we find
which is the initial context of SHA-256.
- There are also quite a few encryption/hashing algorithm OID encodings embedded. For example,
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:
Looking at the 3rd argument, we see it is a region in __DATA_CONST
:
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.