首页 > 代码库 > OD: Heap Exploit : DWORD Shooting & Opcode Injecting

OD: Heap Exploit : DWORD Shooting & Opcode Injecting

堆块分配时的任意地址写入攻击原理

堆管理系统的三类操作:分配、释放、合并,归根到底都是对堆块链表的修改。如果能伪造链表结点的指针,那么在链表装卸的过程中就有可能获得读写内存的机会。堆溢出利用的精髓就是用精心构造的数据去溢出下一个堆块的块首,改写块首中的前向指针 Flink 和后向指针 Blink,然后在分配、释放、合并操作发生时获得一次读写内存的机会。

这种利用内存读写机会在任意位置写入任意数据的做法在原书中称为“DWORD SHOT”,在其它文献中叫做“Arbitrary DWORD Reset”。攻击之后,可以劫持进程、运行 Shellcode。

将一个节点从堆块链表中卸下(分配堆块时)的过程可以用如下代码表示:

int remove (ListNode * node)
{
    node -> blink -> flink = node -> flink;   // 堆溢出攻击利用点
    node -> flink -> blink = node -> blink;
    return 0;
}

当堆溢出发生时,攻击者可以覆盖下一个堆块的块首(是不是意味着堆块必须在物理内存中连续?),攻击者伪造被覆盖的堆块的前向指针 Flink 和后向指针 Blink。当被覆盖的堆块被管理系统从堆块链表中卸下时,会执行 node -> blink -> flink = node -> flink 过程,这会导致伪造的 Flink 指针值被写入伪造的 Blink 所指的地址,从而发生任意地址写入攻击。

 

实际上,除了堆块的分配外,堆块释放(装入链表)、堆块合并时都涉及链表操作,所以都能引发 DWORD SHOOT 攻击。

快表也可以被用来伪造 DWORD SHOOT。

 

堆溢出攻击的代码植入原理

堆溢出的精髓是获得一个 DWORD SHOOT 机会,所以堆溢出利用的精髓就是 DWORD SHOOT 的利用。与栈溢出时地毯式攻击不同,堆溢出攻击更加精准,往往要直接狙击目标。精准是 DWORD SHOOT 的优点,但火力不足也会限制堆溢出的利用。

对于 Windows XP SP1 之前的 Windows 系统,DWORD SHOOT 的攻击目标可以分为以下几类:

1. 内存变量:修改能够影响程序执行的重要标志变量。对于之前溢出修改邻接变量的试验,DWORD SHOOT 比栈溢出攻击强大得多,因为栈溢出时要求溢出数据连续。

2. 代码逻辑:修改代码段重要函数的关键逻辑,如分支判断逻辑,或者将身份验证函数的调用指令改成 nop 来爆破。

3. 函数返回地址:DWORD SHOOT 也能像栈溢出一样修改函数返回地址来劫持进程。但由于栈帧移位的原因,堆溢出在这种攻击中局限较多。

4. 攻击异常处理机制:当程序异常时,系统会转入异常处理,因此异常处理所使用的重要数据结构往往会成为 DWORD SHOOT 的重要目标。主要包括:

S.E.H        Structured Exception Handler,结构化异常处理
F.V.E.H      First Vectored Exception Handler
P.E.B        Process Environment Block
U.E.F        Unhandled Exception Filter
T.E.B        Thread Environment Block 中存入的第一个 S.E.H 指针

5. 函数指针:系统调用动态链接库中的函数、C++ 中的虚函数等用到了函数指针,另外如果软件的开发方式中使用了函数指针,那么修改这些指针能够劫持进程。

6. P.E.B 中的线程同步函数的入口地址:每个进程的 P.E.B 中都存放着一对同步函数指针,指向 RtlEnterCriticalSection() 和 RtlLeaveCriticalSection(),并且在进程退出时会被 ExitProcess() 调用。如果 DWORD SHOOT 能修改这对指针中的其中一个,那么退出时 ExitProcess() 将会调用 Shellcode。由于 P.E.B 的位置始终不会变化,这对指针在 P.E.B 中的偏移也始终不变,这使得利用堆溢出开发适用于不同 OS 和补丁版本的 exploit 成为可能。静止的鞭子比活动的靶子好打得多,所以这种方法成为了 Windows 下堆溢出攻击最经典的方法之一。

溢出 P.E.B 中的 RtlEnterCriticalSection() 函数指针

Windows 使用了锁机制、信号量(Semaphore)、临界区(Critical Section)等措施来同步进程中的多个线程。当进程退出时,ExitProcess() 函数需要做很多善后工作,其中必然乃至临界区函数 RtlEnterCriticalSection() 和 RtlLeaveCriticalSection() 来同步线程防止产生“脏数据”。

ExitProcess() 调用临界区函数的方法比较独特,是通过进程环境块 P.E.B 中偏移 0x20 处存放的函数指针来间接完成的:0x7FFDF020 处存放着指向 RtlEnterCriticalSection() 的指针,0x7FFDF024 处存放着指向 RtlLeaveCriticalSection() 的指针。但从 Windows 2003 Server 开始,MS 已经修改了这个实现。

现在将 0x7FFDF024 处的 RtlEnterCriticalSection() 函数指针作为目标,利用 DWORD SHOOT 进行攻击:

 1 #include<windows.h>
 2 char shellcode[1024];
 3 int main()
 4 {
 5     HLOCAL    h1=0,h2=0;
 6     HANDLE    hp;
 7     int           i=0;
 8     while(i<1024)shellcode[i++]=\x90;    //init shellcode
 9     hp=HeapCreate(0,0x1000,0x10000);
10     __asm int 3
11     h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
12     memcpy(h1,shellcode,0x200);            //overflow,0x200=512
13     h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
14     return 0;
15 }
View Code