‘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 IOUserClients, 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 a uint32_t - that is, four bytes. This gives a "magic" of 00 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> #includes. 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

Next
Next

Will macOS and iOS merge?