<a target="_blank" href="http://bbs.pediy.com/showthread.php?t=101217">http://bbs.pediy.com/showthread.php?t=101217</a>
by WinsOn@Cybersword
在經典的棧溢出模型中,通過覆寫函數的傳回位址來達到控制程式執行流程(EIP寄存器),通常将傳回位址覆寫為0x7FFA4512,這個位址是一條JMP ESP指令,在函數傳回時就會跳轉到這個位址去執行,也就是執行JMP ESP,而此時ESP剛好指向我們在棧上布置的Shellcode,于是就執行了Shellcode。
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l1"></a>
之是以棧上的資料能被執行,是因為早期作業系統沒有區分資料和代碼,EIP指向哪裡就去哪裡執行。
當引入DEP(Data Execution Prevention 資料執行保護)之後,堆、棧上的記憶體頁屬性預設不再具有可執行屬性,此時如果想直接在棧上執行資料,就會發生錯誤:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l2"></a>
常用的繞過DEP的技術室ROP(Return Oriented Programming,早期也叫Ret2Libc),ROP由一系列的 Gadget組成。所謂ROP Gadget,就是一系列以retn結尾的指令,所有的這些Gadget組合起來就能完成特定的任務,比如調用VirtualProtect給指定的記憶體塊添加可執行屬性。
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l4"></a>
為了達到穩定利用的目的,要求選取的Gadget的位址是固定的,無論是什麼時候,其指向的都是我們想要的指令。這裡引入了ASLR的概念。
ASLR全稱Address Space Layout Randomization,即位址空間格局随機化。ASLR使得加載程式時不再使用固定的加載基位址加載。該技術需要作業系統以及應用程式的雙重支援,ASLR才能發揮正常的作用。支援ASLR的程式在PE頭中會設定IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE辨別表明其支援ASLR。
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l5"></a>
通過Visual Studio對項目屬性的配置,可以使二進制檔案支援ASLR:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l6"></a>
ASLR主要影響一下幾個部分,分别為:
1. 子產品随機化
系統将PE檔案映射到記憶體時,對其加載基位址進行随機化處理,這個位址在系統啟動時确定,系統重新開機後會變化。如圖所示,用Ollydbg加載一個應用程式,檢視子產品清單:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l7"></a>
重新開機作業系統後再次檢視,發現基位址全改變了:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l8"></a>
2. 堆棧随機化
每次程式加載後,其記憶體空間中堆、棧的基址都會發生變化。于是記憶體中的變量所在的位址也會發生變化。
3. PEB/TEB随機化
從Windows XP SP2開始,PEB、TEB的位址不再固定不變。
不過,幾乎很少簡單有人用固定的位址去擷取PEB、TEB指針,而是通過fs寄存器進行定位。
TEB可以用FS : [18h]擷取
PEB可以從TEB偏移30h處擷取
常用的繞過ASLR的方法有:
1. 攻擊未啟用ASLR的子產品
雖然有映像随機化,但有可能程序中存在未啟用ASLR的子產品。 前面提到的ROP技術要求從一個固定的位址擷取Gadget,如果程序中存在未啟用ASLR的子產品,那麼就可以從那個子產品擷取Gadget了。 使用OD的OllyFindAddr插件可以快速找到程序空間中未啟用ASLR的子產品。
2. 堆噴射(HeapSpray)技術
雖然有堆棧随機化,不過HeapSpray技術将ShellCode布局到0x0C0C0C0C(或者其他指定的位址上,通常這個位址要比較大),并不會受堆棧随機化的影響。 其實,HeapSpray中使用ROP繞過DEP的時候,就使用了前面提到的“攻擊未啟用ASLR的子產品”。 隻是,HeapSpray把ShellCode布局在堆上。
3. 覆寫部分傳回位址
映像随機化中,雖然子產品的加載基位址發生變化,但是各子產品的入口點位址的低位字不變,隻有高位字進行了随機化處理。
對于位址0×12345678,其中5678部分是固定的,如果存在緩沖區溢出,可以通過memcpy對後兩個位元組進行覆寫,可以将其設定為0×12340000 ~ 0x1234FFFF中的任意一個值。
如果通過strcpy進行覆寫,因為strcpy會複制末尾的結束符0×00,那麼可以将0×12345678覆寫為0×12345600,或者0×12340001 ~ 0x123400FF。
部分傳回位址覆寫,可以使得覆寫後的位址相對于基位址的距離是固定的,可以從基位址附近找可以利用的跳轉指令。
這種方法的通用性不是很強,因為覆寫傳回位址時棧上的Cookie會被破壞。不過具體問題具體分析,為了繞過作業系統的安全保護機制需要考慮各種各樣的情況。
4. Java Applet Spray
Java Applet中動态申請的記憶體空間具有可執行屬性(PAGE_EXECUTE_READWRITE),類似HeapSpray技術,可以在固定的位址上配置設定滑闆指令(如NOP)和ShellCode,然後跳轉到那個位址上面去執行。 和正常的HeapSpray不同,Applet申請空間的上限為100MB,而正常的HeapSpray可以達到1GB。
5. JIT Spray
JIT (Just In Time Compilation) 即時編譯,也就是解釋器(比如Python解釋器)。
主要思想是将 ActionScript代碼中進行大量的XOR操作。然後編譯成位元組碼,并且多次更新到Flash VM中,這樣它會建立很多帶有惡意Xor操作的記憶體塊。例如,一個序列為:
var y=(0×11223344^0×44332211^0×4433221);
正常情況下被解釋器解釋為:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l7-2"></a>
如果非正常的跳轉到中間某一個位元組開始執行代碼,結果就是另一番景象了:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l8-2"></a>
關于JIT的詳細介紹,可以參考Pointer Inference and JIT Spraying以及Writing JIT-Spray shellcode for fun and profit,文章末尾會給對外連結接。
6. Tombkeeper在CanSecWest 2013上提出的基于SharedUserData的方法
從Windows NT 4到Windows 8,SharedUserData的位置一直固定在位址0x7ffe0000上。 從WRK源代碼中nti386.h以及ntamd64.h可以看出:
#define MM_SHARED_USER_DATA_VA 0x7FFE0000
在x86 Windows上,通過Windbg,可以看到:
0:001> dt _KUSER_SHARED_DATA SystemCall 0x7ffe0000
ntdll!_KUSER_SHARED_DATA
+0×300 SystemCall : 0x774364f0
0x7ffe0300總是指向KiFastSystemCall
0:001> uf poi(0x7ffe0300)
ntdll!KiFastSystemCall:
774364f0 8bd4 mov edx,esp
774364f2 0f34 sysenter
774364f4 c3 ret
反彙編NtUserLockWorkStation函數,發現其就是通過7ffe0300進入核心的:
0:001> uf USER32!NtUserLockWorkStation
USER32!NtUserLockWorkStation:
75f70fad b8e6110000 mov eax,11E6h
75f70fb2 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
75f70fb7 ff12 call dword ptr [edx]
75f70fb9 c3 ret
其中11E6是NtUserLockWorkStation的服務号(ShadowSSDT中0x01E6的服務),通過Xuetr可以看到:
<a target="_blank" href="http://cybersword.net/exploit/558.html/attachment/422l9"></a>
這樣,在觸發漏洞前合理布局寄存器内容,用函數在系統服務(SSDT / Shadow SSDT)中服務号填充EAX寄存器,然後讓EIP跳轉到對應的地方去執行,就可以調用指定的函數了。但是也存在很大的局限性:僅僅工作于x86 Windows上;幾乎無法調用有參數的函數。
64位Windows系統上0x7ffe0350總是指向函數ntdll!LdrHotPatchRoutine。
HotPatchBuffer結構體的定義如下:
struct HotPatchBuffer {
ULONG NotSoSure01; // & 0×20000000 != 0
ULONG NotSoSure02;
USHORT PatcherNameOffset; // 結構體相對偏移位址
USHORT PatcherNameLen;
USHORT PatcheeNameOffset;
USHORT PatcheeNameLen;
USHORT UnknownNameOffset;
USHORT UnknownNameLen
};
LdrHotPatchRoutine調用方式:
void LdrHotPatchRoutine (struct *HotPatchBuffer);
在觸發漏洞前合理布局寄存器内容,合理填充HotPatchBuffer 結構體的内容,然後調用LdrHotPatchRoutine。
如果是網頁挂馬,可以指定從遠端位址加載一個DLL檔案;
如果已經經過其他方法把DLL打包發送給受害者,執行本地加載DLL即可。
此方法通常需要HeapSpray協助布局記憶體資料;且需要檔案共享伺服器存放惡意DLL;隻工作于64位系統上的32位應用程式;不适用于Windows 8(已經被修補)。
參考資料與擴充閱讀:
《0Day安全*軟體漏洞分析技術》第二版,王清
<a target="_blank" href="http://www.semantiscope.com/research/BHDC2010/BHDC-2010-Paper.pdf">Pointer Inference and JIT Spraying</a>
<a target="_blank" href="http://cansecwest.com/slides/2013/DEP-ASLR%20bypass%20without%20ROP-JIT.pdf">DEP-ASLR bypass without ROP-JIT</a>