07. XPC Object Decoding

The XPC object format itself is undocumented. Apple publishes an application-level API for XPC messaging, but reserves the right to change the XPC object format at any time. Despite its being the de-facto standard message-passing format on Apple systems, there is relatively little third-party documentation of this object format. Two notable third-party resources were instrumental in learning to parse the XPC object format. The first is Jonathan Levin’s *OS Internals: Vol 1 book, which is an excellent resource for understanding the service and messaging frameworks of Apple’s operating systems. The second was a slide deck from Ian Beer’s talk at the Jailbreak Security Summit.

XPC objects are 4-byte aligned. A magic number and version number compose the XPC header. As mentioned in Levin’s book, the XPC header changes periodically, with previous magic bytes values of 0x58504321 (“XPC!”) and 0x40585043 (“@XPC”). In our testing on 10.13.3 “High Sierra”, we saw magic bytes of 0x42133742 (no longer a string containing “XPC”), followed by a version number 0x5 , which interestingly matches the version number in Levin’s book for post-Darwin 16 despite the magic bytes update. These two fields (magic and version number) make up the entirety of the XPC object header. However, if the header is present, it is always followed by a dictionary object that contains any message contents to be transmitted, even if the dictionary is empty.

XPC objects are always prefixed with a 4-byte type field. What follows the type field is dependent on the type of the XPC object to be encoded. Many objects have similar formats, so rather than provide an exhaustive description of each XPC type, we will explain the formats of several categories of object types. xpc_types.py can be used as a more complete reference, although we have not deciphered the formats of every XPC type.

Types: XPC_NULL = 0x00001000 XPC_BOOL = 0x00002000 XPC_INT64 = 0x00003000 XPC_UINT64 = 0x00004000 XPC_DOUBLE = 0x00005000 XPC_POINTER = 0x00006000 XPC_DATE = 0x00007000 XPC_DATA = 0x00008000 XPC_STRING = 0x00009000 XPC_UUID = 0x0000a000 XPC_FD = 0x0000b000 XPC_SHMEM = 0x0000c000 XPC_MACH_SEND = 0x0000d000 XPC_ARRAY = 0x0000e000 XPC_DICTIONARY = 0x0000f000 XPC_ERROR = 0x00010000 XPC_CONNECTION = 0x00011000 XPC_ENDPOINT = 0x00012000 XPC_SERIALIZER = 0x00013000 XPC_PIPE = 0x00014000 XPC_MACH_RECV = 0x00015000 XPC_BUNDLE = 0x00016000 XPC_SERVICE = 0x00017000 XPC_SERVICE_INSTANCE = 0x00018000 XPC_ACTIVITY = 0x00019000 XPC_FILE_TRANSFER = 0x0001a000

Fixed-sized objects, such as uint64 , tend to have a simple fixed format:

Example: a uint64 with value 0x5 :

00 40 00 00 05 00 00 00 00 00 00 00 |___type__| |________value________|

The value has a known size, so there is no length field present.

Variable-length objects, such as strings, may also specify a length:

Strings are null-terminated and also padded to the 4-byte alignment.

Example: a string with value “duolabs!” :

00 90 00 00 09 00 00 00 64 75 6f 6c 61 62 73 21 00 00 00 00 |___type__| |__length_| |d__u__o__l__a__b__s__!_\0_padding|

Note that even though the printable characters alone would fit the 4-byte alignment, the required null terminator requires padding out to 12 bytes.

Compound objects, such as dictionaries, are more complex. A dictionary has the following format:

The length field specifies the size of the dictionary in bytes, excluding the type and length fields, but including the num_entries field.

Note that the string-like dictionary keys do not contain a 4-byte length field, as the string type does. They are still null-terminated and 4-byte aligned.

Example: a dictionary containing two uint64 s with values of 0x5 and 0x6 and keys of “five” and “six” :

00 f0 00 00 28 00 00 00 02 00 00 00 ... |___type__| |__length_| |num_entry| 66 69 76 65 00 00 00 00 00 40 00 00 05 00 00 00 00 00 00 00 ... |f__i__v__e_\0_padding| |___type__| |________value________| 73 69 78 00 00 40 00 00 06 00 00 00 00 00 00 00 |s__i_x_\0| |___type__| |________value________|

In addition to these three general categories of XPC objects, the file_transfer type is particularly noteworthy since it is used extensively in the sysdiagnose client, which we discuss below. The file_transfer object has the format:

The file_transfer type has an embedded dictionary with a fixed format. The only two fields of note are the embedded uint64 msg_id and embedded dictionary field “s” corresponding to the file_transfer_size . All other fields appear to be static. The file_transfer object is used to inform macOS that the T2 will subsequently start transferring a file of file_transfer_size bytes on a new stream identified by XPC wrapper parameter msg_id . Note that msg_id does not refer to the HTTP/2 stream ID that will be used.

Example: a file_transfer object specifying a subsequent file transfer of size 0x1b981 (113025 bytes) on a stream identified by message ID 0xe :