‘tis the Season
July 8, 2024
Jonathan Levin, Co-Founder and CTO
Another year, another set of *OS updates. Apple has released the initial beta versions of iOS/iPadOS/tvOS 18, macOS 10.20 (="15"), watchOS 11, and VisionOS 2. Security researchers and reverse engineers, including our team at Dataflow Forensics, look through these betas for any indication of undocumented features, patches and more.
The sources for XNU (now in version 11215) will eventually be released, along with some of the other Darwin components, on Apple's GitHub pages (formerly, https://opensource.apple.com). This might take a while, however, and even then some components (including architecture specific portions of XNU) remain in closed source. This makes reversing especially important during this "dark period", but also later during other phases of the *OS SDLC.
Back when I was still actively into Darwin book writing and research, I used to maintain what has become the "unofficial Darwin ChangeLog". Unlike the official one, mine detailed the low-level, often intentionally undocumented updates to Darwin, with an emphasis on kernel-level changes. Today's post is an attempt at nostalgia, with a twist for Darwin 24.
As with the other posts by my DFFender colleagues, the aim is not to just show findings or results, but to demonstrate the tools and techniques to do so. The tool used here is disarm(j)
, the unofficial "jtool3
", which once again refactors and reimplements the functions of its predecessors, and further extends them with new capabilities and - for the first time - support for other binary formats, like ELF and PE.
Analyzing the Kernelcaches
Apple has long used kernelcaches in *OS variants, and with the move to Apple Silicon they are now used in macOS as well. Using kernelcaches has many performance and security advantages. It fuses the kexts and the kernel proper together, and makes analysis a bit harder.
Starting with Darwin 20, Apple created the MH_FILESET
Mach-O format (Type #12) for kernelcaches. A fileset is a "set of related Mach-Os", which are loaded together into the same address space. The Mach-Os which are members of the set are indicated by LC_FILESET_ENTRY
load commands, which detail the name and the offset (but curiously, not the size) of each Mach-O.
Using disarm -L
will display all the load commands in a Mach-O. We can use that to compare the iOS 17.5.1 and the 18.0m kernelcaches, like so:
morpheus@eM1nent (~) % disarm -L ~/Downloads/kernelcache.release.iphone16,2_17.5.1_21F90 | grep FILE | cut -c 76- > /tmp/out1 /Users/morpheus/Downloads/kernelcache.release.iphone16,2_17.5.1_21F90: This is an IM4P... with a BVX2 payload... Uncompressed 59359232 bytes morpheus@eM1nent (~) % disarm -L ~/Downloads/kernelcache.release.iphone16,2_18.0_22A5282m | grep FILE | cut -c 76- > /tmp/out2 /Users/morpheus/Downloads/kernelcache.release.iphone16,2_18.0_22A5282m: This is an IM4P... with a BVX2 payload... Uncompressed 61964288 bytes morpheus@eM1nent (~) % diff /tmp/out1 /tmp/out2 60a61 > com.apple.kec.AppleEncryptedArchive 87c88 < com.apple.security.AppleImage4 --- > com.apple.security.AppleImage4 138d138 < com.apple.driver.AppleSamsungSPI 196a197 > com.apple.iokit.IOGameControllerFamily
We see two new kexts - com.apple.kec.AppleEncryptedArchive
and com.apple.iokit.IOGameControllerFamily
. The former is used for the new DMG format introduced in iOS 18 - .aea:
morpheus@eM1nent (~) % imjtool -v ~/Downloads/iPhone16,2_18.0_22A5282m_Restore.ipsw| grep aea$
6912212992 090-27454-036.dmg.aea
1610612736 090-29713-049.dmg.aea
Why Apple would go back to encrypting *OS disk images eludes this author - especially after the OTA update saga demonstrated how the GID encryption used by Apple up to iOS 9.x was futile.
AEA is a simple container format, which starts with a magic, version, size, and provides URLs for image key recovery and management, as well as JSON metadata.
___magic___ __version?_ _payloadSz_ 00000000 41 45 41 31 01 00 00 00 78 08 00 00 30 00 00 00 |AEA1....x...0...| 00000010 63 6f 6d 2e 61 70 70 6c 65 2e 77 6b 6d 73 2e 75 |com.apple.wkms.u| 00000020 72 6c 00 68 74 74 70 73 3a 2f 2f 77 6b 6d 73 2e |rl.https://wkms.| 00000030 73 64 2e 61 70 70 6c 65 2e 63 6f 6d 5d 03 00 00 |sd.apple.com]...| 00000040 63 6f 6d 2e 61 70 70 6c 65 2e 77 6b 6d 73 2e 61 |com.apple.wkms.a| .. 000007c0 22 77 72 61 70 70 65 64 2d 6b 65 79 22 3a 20 22 |"wrapped-key": "| 000007d0 77 52 74 4a 56 50 33 2b 72 31 6c 45 5a 6c 45 46 |wRtJVP3+r1lEZlEF| 000007e0 66 30 31 65 63 64 36 38 65 65 6e 32 74 49 36 66 |f01ecd68een2tI6f| 000007f0 37 32 74 62 30 4a 68 34 79 62 64 34 69 77 30 51 |72tb0Jh4ybd4iw0Q| 00000800 38 54 6b 32 59 2f 4e 6f 70 76 47 7a 6b 35 76 67 |8Tk2Y/NopvGzk5vg| 00000810 22 7d 72 00 00 00 63 6f 6d 2e 61 70 70 6c 65 2e |"}r...com.apple.| 00000820 77 6b 6d 73 2e 66 63 73 2d 6b 65 79 2d 75 72 6c |wkms.fcs-key-url| 00000830 00 68 74 74 70 73 3a 2f 2f 77 6b 6d 73 2d 70 75 |.https://wkms-pu| 00000840 62 6c 69 63 2e 61 70 70 6c 65 2e 63 6f 6d 2f 66 |blic.apple.com/f| 00000850 63 73 2d 6b 65 79 73 2f 43 37 36 4f 45 6f 69 58 |cs-keys/C76OEoiX| 00000860 35 4c 66 63 30 6e 52 51 74 6e 31 63 4c 6b 4f 45 |5Lfc0nRQtn1cLkOE| 00000870 77 44 74 43 38 48 47 49 4d 5f 4d 5f 31 72 4a 67 |wDtC8HGIM_M_1rJg| 00000880 51 39 67 3d (end of header at 0x884 = 0xc + 0x878) ... (encrypted contents follow) b1 e0 fc 52 6b e5 c8 60 fa ed 54 de |Q9g=...Rk..`..T.| 00000890 28 92 ec 21 0c 3c 9d 89 d2 55 51 37 8b bc 66 c9 |(..!.<...UQ7..f.|
A deeper analysis of filesystem changes (new daemons, etc) is left for a future post.
KEXTRACTION
The fileset structure makes it easy to extract the kexts with a single command line:
disarm -e filesets /Users/morpheus/Downloads/kernelcache.release.iphone16,2_18.0_22A5282m
/Users/morpheus/Downloads/kernelcache.release.iphone16,2_18.0_22A5282m: This is an IM4P... with a BVX2 payload... Uncompressed 61964288 bytes
Got PRELINK_INFO
255 LC_FILESET_ENTRY commands
All files are in /tmp/extracted. Kernel is in /tmp/extracted/kernel.rebuilt
Getting kernel from 0x8000
Kernel is in /tmp/extracted/kernel.rebuilt
ls /tmp/extracted/*kext
/tmp/extracted/AGXFirmwareKextG16PRTBuddy.kext /tmp/extracted/AppleSART.kext
/tmp/extracted/AGXFirmwareKextRTBuddy64.kext /tmp/extracted/AppleSEPCredentialManager.kext
...
/tmp/extracted/AppleS8000DWI.kext /tmp/extracted/pthread.kext
/tmp/extracted/AppleSARService.kext
All kexts will be properly fixed up and ready for further analysis, though it should be noted that their stubs (referencing other kexts or XNU proper) are not (at this time) resolved. As an example of analysis, we consider a perennial favorite - the Sandbox.kext.
Sandbox.kext
The Sandbox is undoubtedly one of the two most important kexts in Darwin. Along with its partner in crime (AMFI) it is responsible for enforcing the system security restrictions.
LC 0: LC_SEGMENT_64 Mem: 0xfffffff0077c43d0-0xfffffff0079593e3 __TEXT Mem: 0xfffffff0077c4a00-0xfffffff007950479 __TEXT.__const Mem: 0xfffffff007950479-0xfffffff0079573ba __TEXT.__cstring (C-String Literals) Mem: 0xfffffff0079573ba-0xfffffff0079593e3 __TEXT.__os_log LC 1: LC_SEGMENT_64 Mem: 0xfffffff00a3f34d0-0xfffffff00a423afc __TEXT_EXEC Mem: 0xfffffff00a3f34d0-0xfffffff00a423afc __TEXT_EXEC.__text (Normal) Mem: 0xfffffff00a423afc-0xfffffff00a423afc __TEXT_EXEC.__auth_stubs (Symbol Stubs) LC 2: LC_SEGMENT_64 Mem: 0xfffffff00aa52648-0xfffffff00aa66ce8 __DATA Mem: 0xfffffff00aa52648-0xfffffff00aa52828 __DATA.__data Mem: 0xfffffff00aa52828-0xfffffff00aa66ce8 __DATA.__bss (Zero Fill) LC 3: LC_SEGMENT_64 Mem: 0xfffffff007ea1950-0xfffffff007ea6830 __DATA_CONST Mem: 0xfffffff007ea1950-0xfffffff007ea2178 __DATA_CONST.__auth_got (Non-Lazy Symbol Ptrs) Mem: 0xfffffff007ea2178-0xfffffff007ea2240 __DATA_CONST.__got (Non-Lazy Symbol Ptrs) Mem: 0xfffffff007ea2240-0xfffffff007ea5860 __DATA_CONST.__const Mem: 0xfffffff007ea5860-0xfffffff007ea5db0 __DATA_CONST.__kalloc_var Mem: 0xfffffff007ea5db0-0xfffffff007ea6830 __DATA_CONST.__kalloc_type LC 4: LC_SEGMENT_64 Mem: 0xfffffff00aab8000-0xfffffff00ab16079 __LINKEDIT LC 5: LC_SYMTAB Symtab: 0 entries @0x22c5a5(2278821), Strtab is 1 bytes @0x22c5a5(2278821) LC 6: LC_DYSYMTAB No local symbols No external symbols No undefined symbols No TOC No modtab 547 Indirect symbols at offset 0x22c5a6 No External relocations LC 7: LC_UUID UUID: 5B143F5D-B83A-3428-BB25-E9D13C651780 LC 8: LC_SOURCE_VERSION Source Version: 2401.0.31.0.1 LC 9: LC_FUNCTION_STARTS
As Darwin enthusiasts know (q.v. M-III/8), the kernel extension uses two important structures. The first is the mac_policy_conf
. Unlike AMFI's policy (which is dynamically constructed in code), the Sandbox policy is preinitialized in __DATA_CONST
:
fffffff007da2340: 0xfffffff00787ef69 "Sandbox" fffffff007da2348: 0xfffffff00787d3eb "Seatbelt sandbox policy" fffffff007da2350: 0xfffffff007da2398 __policy_label fffffff007da2358: 01 00 00 00 00 00 00 00 |........| fffffff007da2360: 0xfffffff007da23a0 __policy_ops fffffff007da2368: 00 00 00 00 00 00 00 00 |........| fffffff007da2370: 0xfffffff007da2334 __policy_label_slot fffffff007da2378: 00 00 00 00 00 00 00 00 |........| fffffff007da2380: 00 00 00 00 00 00 00 00 |........| fffffff007da2388: 00 00 00 00 00 00 00 00 |........| fffffff007da2390: 00 00 00 00 00 00 00 00 |........| __policy_label: fffffff007da2398: 0xfffffff00787d403 "sb" __policy_ops: fffffff007da23a0: 00 00 00 00 00 00 00 00 |........| fffffff007da23a8: 00 00 00 00 00 00 00 00 |........| fffffff007da23b0: 00 00 00 00 00 00 00 00 |........| fffffff007da23b8: 00 00 00 00 00 00 00 00 |........| fffffff007da23c0: 00 00 00 00 00 00 00 00 |........| fffffff007da23c8: 00 00 00 00 00 00 00 00 |........| fffffff007da23d0: 0xfffffff00a1d5754 _hook_cred_check_label_update_execve fffffff007da23d8: 0xfffffff00a1c97d4 _hook_cred_check_label_update ...
And so we note two new MAC policy hooks - hook_proc_check_set_[task/thread]_exception_port
.
The second is the array of Sandbox "operation names", which provides hard-coded strings for all the operation names used by the SandBox Profile Language (SBPL). This can be easily found due to the reference to the first operation, "default", also in __DATA_CONST.__const
.
disarm -r __DATA_CONST.__const /tmp/extracted/Sandbox.kext | grep -A197 \"default\"
fffffff007ea3110: 0xfffffff0079517aa "default"
fffffff007ea3118: 0xfffffff0079517b2 "appleevent-send"
.....
fffffff007ea3730: 0xfffffff007952573 "mach-message-send"
fffffff007ea3738: 0xfffffff007952585 "xpc-message-send"
Comparing the list with the one from iOS 17 reveals two new operations: mach-task-exception-port-set
and sandbox-check
. The former is understandable, as it finally fixes a long vulnerability in exception handling (and, thus, process control). The latter is likely due to the user mode sandbox_check*
APIs (from libsandbox.dylib) being used as a backhanded way to enumerate processes on the system.
XNU
XNU's LC_SOURCE_VERSION
indicates 11215.0.31.522.1
, hinting at many internal builds and rebuilds over the last year. The structure of the kernelcache and kernel proper remains the same as last year, with either PPL (pre-A14) or SPTM (thereafter) segments.
A main interest for vulnerability researchers is to map out any change in the the kernel attack surface. For the kernel proper, this means any new system calls, Mach traps or in-kernel MIG routines. For the various kernel extensions, this means any IOUserClient
changes, particulary any new methods, or modifications in existing ones. In this context, it should be noted that Apple went to great lengths to finally filter system calls, Mach traps, (some) Mach/XPC messages and even IOUserClient
s, starting with Darwin 21 and 23. Thus, the change in attack surface does not necessarily imply reachability from the browser or the application context.
SYSTEM CALLS (SYSENT
AND NSYSENT
)
XNU's system call table (_sysent
) and system call number (nsysent
) have long been removed from the public symbols, but they are trivial to find in __DATA_CONST.
. The observation is due to the very distinct structure of the table. There are several distinct markers to use here:
The system call entry of
exit(2)
calls for no return value, and one argument of auint32_t
- that is, four bytes. This gives a "magic" of00 00 00 00 01 00 04 00
- in the sense that it appears exactly once in the segment.The table entries are in a form of
function/munger/(arg values)
, 24 bytes each.The table is full of repeating entries - particularly,
enosys
(for invalid system calls), or the mungers. These are at pre-determined offsets from one another.
We can refer to the above logic particularly the first rule, as rule sc. As you will shortly see, disarm(j)
can automatically find the _sysent
, and warns on any syscall changes, including suspected new syscalls, as part of its automated analysis of XNU kernels. This will spit out a clear warning to stderr
:
morpheus@eM1nent (~/Documents/OSXBook/2nd/src/disarm) % disarm -i /tmp/extracted/kernel.rebuilt
Finding functions
xnu.matchers: Loaded 236/74/54/25 arg matchers in 546 lines
This is a XNU kernel - running further analysis
Symbolicating XNU syscalls
Unrecognized Darwin version 24 - going with latest one (23)
⚠️ Possible new system call #556@0xfffffff00844b628
⚠️ Possible new system call #557@0xfffffff00844b740
_sysent: fffffff007a29378: 0xfffffff008442df0 einval fffffff007a29380: 00 00 00 00 00 00 00 00 |........| fffffff007a29388: 01 00 00 00 00 00 00 00 |........| fffffff007a29390: 0xfffffff0083f50bc exit fffffff007a29398: 0xfffffff0080b6418 _func_0xfffffff0080b6418 fffffff007a293a0: 00 00 00 00 01 00 04 00 |........| fffffff007a293a8: 0xfffffff0083fc370 fork fffffff007a293b0: 00 00 00 00 00 00 00 00 |........| fffffff007a293b8: 01 00 00 00 00 00 00 00 |........| fffffff007a293c0: 0xfffffff008443780 read .. fffffff007a2c770: 0xfffffff0080b6460 _func_0xfffffff0080b6460 fffffff007a2c778: 01 00 00 00 04 00 10 00 |........| fffffff007a2c780: 0xfffffff0080f18a0 ungraftdmg fffffff007a2c788: 0xfffffff0080b6578 _func_0xfffffff0080b6578 fffffff007a2c790: 01 00 00 00 02 00 0C 00 |........| fffffff007a2c798: 0xfffffff00844b628 #556 (_coalition_policy_set) fffffff007a2c7a0: 0xfffffff0080b6a90 _func_0xfffffff0080b6a90 fffffff007a2c7a8: 01 00 00 00 03 00 10 00 |........| fffffff007a2c7b0: 0xfffffff00844b740 #557 (_coalition_policy_get) fffffff007a2c7b8: 0xfffffff0080b6a78 _func_0xfffffff0080b6a78 fffffff007a2c7c0: 01 00 00 00 02 00 0C 00 |........| __syscall_machdep.wake_abs_time: fffffff007a2c7c8: 0xfffffff007a2c818 fffffff007a2c7d0: 00 00 00 00 00 00 00 00 |........| fffffff007a2c7d8: FF FF FF FF 04 00 60 80 |......`.| fffffff007a2c7e0: 0xfffffff00a8cbe68 fffffff007a2c7e8: 00 00 00 00 00 00 00 00 |........| fffffff007a2c7f0: 0xfffffff007097e20 "wake_abstime"
MACH TRAPS (MACH_TRAP_TABLE
)
XNU is a unique kernel in that, in addition to the BSD-style system calls, it exports another "personality", of Mach traps. Mach traps are a vestige of the original Mach micro kernel, and deal mostly with memory management and ports. Unlike system calls, the traps are all in a fixed length mach_trap_table
, hard coded to 128 entries, though a fair number of them unused, and set to kern_invalid
(the equivalent of einval
in the Mach trap world).
The mach_trap_table
can be found similarly, or by first finding kern_invalid
. This is easy thanks to its informative message:
_kern_invalid: fffffff007f9743c d503237f PACIBSP ; fffffff007f97440 a9bf7bfd STP X29, X30, [X31, #-16]! ; SP -= 16; *[SP] = [X29, X30] fffffff007f97444 910003fd ADD X29, X31, #0 ; (0x0) -- FP = SP + 0x0 = 0x0 -- ! fffffff007f97448 b0014748 ADRP X8, #10473 ; X8 = 0xfffffff00a880000- fffffff007f9744c b94cf108 LDRi X8, [X8, #3312] ; ; X8 = *(0xfffffff00a880cf0) (8) = ??? fffffff007f97450 34000128 CBZ W8, 0xfffffff007f97474 ; fffffff007f97454 aa1e03e4 MOV X4, X30 ; X4 = LR (0x0) fffffff007f97458 dac143e4 XPACI X4 ; fffffff007f9745c d0ff8582 ADRP X2, #-3918 ; X2 = 0xfffffff007049000- fffffff007f97460 91340c42 ADD X2, X2, #3331 ; (0xfffffff007049000) -- X2 = X2 + 0xd03 ='kern_invalid mach trap' fffffff007f97464 52800000 MOVZ W0, #0 ; X0 = 0x0 fffffff007f97468 d2800001 MOVZ X1, #0 ; X1 = 0x0 fffffff007f9746c d2800003 MOVZ X3, #0 ; X3 = 0x0 _Debugger(0,0,"kern_invalid mach trap", 0); fffffff007f97470 97fee4dc BL 0xfffffff007f507e0 ; fffffff007f97474 52800080 MOVZ W0, #4 ; X0 = 0x4 fffffff007f97478 a8c17bfd LDP X29, X30, [X31], #16 ; [X29, X0] = *[X31]; X31 += 2 fffffff007f9747c d65f0fff RETAB ;
If using a "magic" here, it is 04 05 00 00 00 00 00 00
, which occurs in the expansion of the MACH_TRAP
macro for _kernelrpc_mach_vm_allocate_trap
. This is the macro which creates the 24-byte mach_trap_table
entries. This appears for trap 10 and 11. A match on trap 10 is thus 240 bytes after the beginning of the mach_trap_table
. Let's call this rule mt, as we will revisit it later.
There are no new Mach traps in Darwin 24 so far, and the latest - _exclaves_ctl_trap
is still unimplemented (outside of the latest M4 iPad).
IN-KERNEL MIG ROUTINES
A third dimension of the kernel attack surface lies in its in-kernel MIG routines. These are created from the .defs files in XNU's /osfmk/mach/*.defs using the mig(1)
utility. mig(1)
creates the client and server code, as well as an .h file, which is provided to user mode through the SDK's <mach/*.h> #include
s. The well known Mach primitives - Host, Task, Thread, Exception, and others - are all implemented as MIG messages to ports, whose RECEIVE
rights (and, in practice, obligations) are held by the kernel.
MIG tables are easy to identify, because of their very distinct structure. They, too, reside in the __DATA_CONST.__const
. Without getting into the boring details, disarm(j)
(like its precursors) can readily identify them. This allows a quick and effective comparison between the past and present kernelcaches, like so:
disarm -g BTI,RET sptm.t8122.release.im4p morpheus@eM1nent (~/) % export JMATCHERS=xnu.matchers morpheus@eM1nent (~/) % disarm -r __DATA_CONST.__const kc/kernel.17.4.1-iPhone14,6 | grep autodetected_MIG Analyzing with specific analyzer Finding functions xnu.matchers: Loaded 236/74/54/25 arg matchers in 546 lines This is a XNU kernel - running further analysis Symbolicating XNU syscalls This is Darwin 23, expecting 556 syscalls Opened companion file ../xn00p2/kernelcaches/kernel.17.4.1-iPhone14,6.ARM64.3F715130-4277-3E54-BE34-D1019A869CE2 _autodetected_MIG_1000_Subsystem: (3 routines, 1000-1003, msg size 52) _autodetected_MIG_2401_Subsystem: (3 routines, 2401-2404, msg size 5236) _autodetected_MIG_716200_Subsystem: (4 routines, 716200-716204, msg size 68) _autodetected_MIG_400_Subsystem: (26 routines, 400-426, msg size 4148) _autodetected_MIG_2405_Subsystem: (5 routines, 2405-2410, msg size 5236) _autodetected_MIG_200_Subsystem: (35 routines, 200-235, msg size 1072) _autodetected_MIG_3200_Subsystem: (43 routines, 3200-3243, msg size 576) _autodetected_MIG_4800_Subsystem: (26 routines, 4800-4826, msg size 4140) _autodetected_MIG_5400_Subsystem: (5 routines, 5400-5405, msg size 5168) _autodetected_MIG_4900_Subsystem: (3 routines, 4900-4903, msg size 56) _autodetected_MIG_8000_Subsystem: (2 routines, 8000-8002, msg size 44) _autodetected_MIG_3000_Subsystem: (6 routines, 3000-3006, msg size 144) _autodetected_MIG_4000_Subsystem: (11 routines, 4000-4011, msg size 84) _autodetected_MIG_3400_Subsystem: (65 routines, 3400-3465, msg size 5232) _autodetected_MIG_3600_Subsystem: (31 routines, 3600-3631, msg size 5232) _autodetected_MIG_3800_Subsystem: (32 routines, 3800-3832, msg size 2092) _autodetected_MIG_2800_Subsystem: (91 routines, 2800-2891, msg size 4296) # # Comparing to the 18β1 # morpheus@eM1nent (~/) % disarm -r __DATA_CONST.__const kc/kernel.18b1-iPhone14,6 | grep autodetected_MIG ... _autodetected_MIG_1000_Subsystem: (3 routines, 1000-1003, msg size 52) _autodetected_MIG_2401_Subsystem: (3 routines, 2401-2404, msg size 5236) _autodetected_MIG_716200_Subsystem: (4 routines, 716200-716204, msg size 68) _autodetected_MIG_400_Subsystem: (26 routines, 400-426, msg size 4148) _autodetected_MIG_2405_Subsystem: (6 routines, 2405-2411, msg size 5236) # new message _autodetected_MIG_200_Subsystem: (35 routines, 200-235, msg size 1072) _autodetected_MIG_3200_Subsystem: (43 routines, 3200-3243, msg size 576) _autodetected_MIG_4800_Subsystem: (26 routines, 4800-4826, msg size 4140) _autodetected_MIG_5400_Subsystem: (5 routines, 5400-5405, msg size 5168) _autodetected_MIG_4900_Subsystem: (3 routines, 4900-4903, msg size 56) _autodetected_MIG_8000_Subsystem: (2 routines, 8000-8002, msg size 44) _autodetected_MIG_3000_Subsystem: (6 routines, 3000-3006, msg size 144) _autodetected_MIG_4000_Subsystem: (11 routines, 4000-4011, msg size 84) _autodetected_MIG_3400_Subsystem: (66 routines, 3400-3466, msg size 5232) # new message _autodetected_MIG_3600_Subsystem: (32 routines, 3600-3632, msg size 5232) # new message _autodetected_MIG_3800_Subsystem: (32 routines, 3800-3832, msg size 2092) _autodetected_MIG_2800_Subsystem: (91 routines, 2800-2891, msg size 4296)
The above shows two new MIG messages have been added: #2411 (in the osfmk/mach/mach_exc.defs), #3466 (in the osfmk/mach/task.defs), and #3632 (osfmk/mach/thread_act.defs).
Conducting Your Own Deeper Analysis
Veteran iOS reversers might remember my old joker(j)
tool, which I wrote in order to symbolicate *OS kernels. The tool, like its name, started out as a joke, but gained surprising popularity when Apple stripped its kernelcaches bare. Since then, the logic was incorporated into jtool2(j) --analyze
, where it lived on as a statically linked module.
When I decided to refactor jtool2(j)
into disarm(j)
, I realized that all the symbolication logic, when hard compiled, would quickly grow outdated. I thus decided to take its core - pattern matching - and put into a separate text file. As with companion file, I emphasize a simple and easily maintainable design, as a simple text file.
The beauty of using a separate file for matching logic is not only that it decouples it from the disassembly engine, but also that, in this way, matchers can be used on any file - Not just kernel caches! Any Mach-O, or even ELFs can be analyzed with matchers. It's a kind of feature one would want in their favorite disassembler/reversing engine - which could probably be made using some Python plugin (or built-in to a future version). With disarm(j)
, it's available here and now.
Analysis can be triggered by specifying JA=1
. Default analysis is very basic, however, so oftentimes you'll want to supply a custom matcher file, using JMATCHERS=/path/to/file
. Because analysis can be CPU intensive, it will automatically create a companion file - a simple text file of 0xaddress:_symbol
records, which will then be loaded automatically by disarm(j)
in future runs.
ARGUMENT MATCHERS
Although it's a command-line tool, disarm(j)
contains a rather sophisticated proprietary disassembler. Among other capabilities, it tracks register values, which enables it to figure out arguments to functions. There are also numerous plug-in options in this tool (i.e. the user can supply dylibs with callbacks on specific function calls, or argument values). The most common use of this feature, however, is creating rules - when argument #x
has value y
. The syntax couldn't be easier:
# # Disarm pattern matcher file. # # This was originally used by me for jtool2's joker module (my kernel symbolicator). It proved # far more scalable and extensible than hard coding the rules into jtool2, opening up the way # for this to be used for any binary, not just the kernelcaches. # # jtool2 is no longer supported by me, but disarm (effectively, jtool3, I guess), meets all of # its predecessor's abilities, and adds many more - and so this is now a core part of it. # # Syntax is intentionally kept simple: # # arg#|pattern|function_this_is_called_in|calling_function|reserved|Ignored (comment) # # where: # # - arg# = 0,1,2, or 3 only # # - pattern = any partial but exact (from beginning) match. And, no, it can't have a '|' in it.. # # - function_this_is_called_in = the containing function, NOT the calling function # calling_function (optional) = function whose argument this is # # - The calling function = is optional, because many times you will have identified it already # since it gets called alot (e.g. OSKextLog, strcmp, panic etc). # # # is a comment, obviously # # Usage is even simpler: # # JMATCHERS=/path/to/this/file disarm file # # and then use generated companion file. Using the companion file will speed up processing, since analysis # can be at times slow. # # Using JMD=1 will provide matcher statistics, so you can see which matchers have grown stale # # Find the official manual for disarm - and a new book on Darwin and Linux/Android debugging - # # Coming soon at https://NewDebuggingBook.com/ # # LICENSE: # # FREE TO USE (AISE) **BUT** PLEASE GIVE CREDIT IF YOU FIND THIS USEFUL. # # IDA's "Lumina" and other "community databases" or some productz have long been fed by jtool2 - and that's # perfectly fine, but please give credit where due - and spread the word of disarm, the newosxbook.com books # and tools and get other people to use/read them, and hopefully find them useful. # Oh - And please share your patterns, if they work for you, leaving this LICENSE comment intact. # # # Start from here # ---------------- # ## Matches for argument 0: # 0|policy's OPs field is NULL|_mac_policy_register|_panic ## Matches for argument 1: 1|vm_mem_bootstrap|_kernel_bootstrap|_strncpy||osfmk/kern/startup.c
REGION MATCHERS
Argument matchers are useful for many functions, but another common pattern is looking through values pointed to in the various data segments of a binary. Since disarm(j)
automagically handles chained fixups, pointer rebasing, etc, it is capable of determining pointer targets with 100% certainty. This opens up an ability to create really powerful rules.
For example, remember the system call entry logic, above, what we called rule sc? Easily to express in the following rule:
__DATA_CONST|val=0x0004000100000000|_sysent|_sysent|-40
How about the Mach trap table, the rule mt from above? Just as easy:
__DATACONST|val=0x0000000504|mach_trap_table|_mach_trap_table|-240
As one can discern from the above examples, the format of region matchers is also kept simple:
# # Region rules: These apply to data. Format is: # _SECTION/_SEGMENT:"string" or val=0x....|symbol|type|+/-offset # __DATA_CONST|val=0x0004000100000000|_sysent|_sysent|-40 __DATA_CONST|val=0x0000000000000504|_mach_trap_table|_mach_trap_table|-240 __DATA_CONST|"nbuf"|_sysctl__kern_nbuf|sysctl|-40 __DATA_CONST|"wake_abstime"|_sysctl__kern_wake_abstime|sysctl|-40 __DATA_CONST|"uat"|_uat_descriptor||0 __DATA_CONST|"t8110dart"|_t8110_descriptor||0
Matching on strings is highly effective, especially given the large number of sysctl
structures in the kernel's __DATA_CONST.__const
.
Another option is to match by value. Taking, as an example, a fragment of Apple's gAppleSystemVariableGuid
- we know its value (after all, it's globally unique), and that it resides in __TEXT.__const
. Thus, the following example can immediately identify it, along with two other friends:
## Another rule type: val=... - in case you find pointers to GUID, certs, etc __TEXT.__const|val=0xbb4b2aab1061437c|__ZL15gAppleNVRAMGuid||0 __TEXT.__const|val=0x9243F877d2dda040|__ZL24gAppleSystemVariableGuid||0 __TEXT.__const|val=0x504c6665b58ac236|___ZL14gAppleWifiGuid||0
TAKEAWAYS
Using disarm(j)
's analysis and custom matchers, it's possible to create many symbolication rules, which can be used across multiple versions of the same binary - analysis which is future proof for important binaries like XNU. Thanks to disarm(j)
's handling of many file formats, it can also be used on any Aarch64 binary - user mode Mach-Os, ELFs, or even PE32+.
One more thing..
What about this Apple Intelligence, that's all the buzz (and that popped AAPL stock by 11% last week?) Well, that's a user mode subsystem. Surprisingly, the daemon appears to have made its debut in iOS 17.4-ish. Xn00ping around we see it's the intelligenceplatformd
, with files in ~mobile/Library/IntelligencePlatform/graph.db
Xn00p> pid 726 fds proc@0xffffffede8da2f00: 726 intelligenceplatformd proc_ro@0xffffffeb35fc7780 task@0xffffffede8da3640 Files: 0xffffffed00c0b600 0: fileproc@0xffffffec19e47f80 /dev/null 1: fileproc@0xffffffec19e47fc0 /dev/null 2: fileproc@0xffffffec19e44040 /dev/null 3: fileproc@0xffffffec19e44460 socket! 5: fileproc@0xffffffec19e44220 /User/Library/IntelligencePlatform/graph.db 6: fileproc@0xffffffec19e44240 /User/Library/IntelligencePlatform/graph.db-wal 7: fileproc@0xffffffec19e44260 /User/Library/IntelligencePlatform/graph.db-shm 8: fileproc@0xffffffec19e44280 /User/Library/IntelligencePlatform/graph.db 9: fileproc@0xffffffec19e442c0 /User/Library/IntelligencePlatform/graph.db-wal 10: fileproc@0xffffffec19e44300 /User/Library/IntelligencePlatform/ontology.db 11: fileproc@0xffffffec19e443a0 /User/Library/IntelligencePlatform/ontology.db-wal 12: fileproc@0xffffffec19e443c0 /User/Library/IntelligencePlatform/ontology.db-shm 13: fileproc@0xffffffec19e443e0 /User/Library/IntelligencePlatform/ontology.db 14: fileproc@0xffffffec19e44400 /User/Library/IntelligencePlatform/ontology.db-wal 15: fileproc@0xffffffec19e44480 /User/Library/IntelligencePlatform/state.db 16: fileproc@0xffffffec19e444a0 /User/Library/IntelligencePlatform/state.db-wal 17: fileproc@0xffffffec19e444c0 /User/Library/IntelligencePlatform/state.db-shm 18: fileproc@0xffffffec19e444e0 /User/Library/IntelligencePlatform/state.db 19: fileproc@0xffffffec19e44500 /User/Library/IntelligencePlatform/state.db-wal 20: fileproc@0xffffffec19e44520 /User/Library/IntelligencePlatform/keyvalue.db 21: fileproc@0xffffffec19e44540 /User/Library/IntelligencePlatform/keyvalue.db-wal 22: fileproc@0xffffffec19e44560 /User/Library/IntelligencePlatform/keyvalue.db-shm 23: fileproc@0xffffffec19e44580 /User/Library/IntelligencePlatform/keyvalue.db 24: fileproc@0xffffffec19e445a0 /User/Library/IntelligencePlatform/keyvalue.db-wal 25: fileproc@0xffffffec19e445c0 /User/Library/IntelligencePlatform/views.db 26: fileproc@0xffffffec19e445e0 /User/Library/IntelligencePlatform/views.db-wal 27: fileproc@0xffffffec19e44600 /User/Library/IntelligencePlatform/views.db-shm 28: fileproc@0xffffffec19e44620 /User/Library/IntelligencePlatform/views.db 29: fileproc@0xffffffec19e44640 /User/Library/IntelligencePlatform/views.db-wal 30: fileproc@0xffffffec19e44660 /User/Library/IntelligencePlatform/eventLog.db 31: fileproc@0xffffffec19e44680 /User/Library/IntelligencePlatform/eventLog.db-wal 32: fileproc@0xffffffec19e446a0 /User/Library/IntelligencePlatform/eventLog.db-shm 33: fileproc@0xffffffec19e446c0 /User/Library/IntelligencePlatform/eventLog.db 34: fileproc@0xffffffec19e446e0 /User/Library/IntelligencePlatform/eventLog.db-wal 35: fileproc@0xffffffec19e44700 /User/Library/IntelligencePlatform/feedbackLog.db 36: fileproc@0xffffffec19e44720 /User/Library/IntelligencePlatform/feedbackLog.db-wal 37: fileproc@0xffffffec19e44740 /User/Library/IntelligencePlatform/feedbackLog.db-shm 38: fileproc@0xffffffec19e44760 /User/Library/IntelligencePlatform/feedbackLog.db 39: fileproc@0xffffffec19e44780 /User/Library/IntelligencePlatform/feedbackLog.db-wal 40: fileproc@0xffffffec19e447a0 /User/Library/IntelligencePlatform/globalKnowledge.db 41: fileproc@0xffffffec19e447c0 /User/Library/IntelligencePlatform/globalKnowledge.db-wal 42: fileproc@0xffffffec19e447e0 /User/Library/IntelligencePlatform/globalKnowledge.db-shm 43: fileproc@0xffffffec19e448a0 /User/Library/IntelligencePlatform/globalKnowledge.db 44: fileproc@0xffffffec19e44900 /User/Library/IntelligencePlatform/globalKnowledge.db-wal _vm_map@0xffffffeae64cdeb0: 0x100bbc000-0xfc0000000 (78 pageable entries, page shift 0x4000) pmap@0xffffffeb36146440
And, unsurprisingly, such an important database of intimate knowledge about the user is well protected:
-sh-3.2# ps -ef | grep Intelligence 501 319 1 0 6:24PM ?? 0:00.00 /System/Library/PrivateFrameworks/IntelligencePlatformCompute.framework/XPCServices/IntelligencePlatformComputeService.xpc/IntelligencePlatformComputeService 501 726 1 0 7:19PM ?? 0:00.00 /System/Library/PrivateFrameworks/IntelligencePlatformCore.framework/intelligenceplatformd 0 1599 1491 0 9:31PM ttys000 0:00.00 grep Intelligence sh-3.2# ls -l /var/mobile/Library/IntelligencePlatform ls: /var/mobile/Library/IntelligencePlatform: Operation not permitted
Lots of interesting tidbits here, but that's something we might leave for a future blog post.. ;-)
Cover Image Credit: Image by pvproductions
We are hiring for multiple positions - more details here