最近在看《0day安全:軟件漏洞分析》那本書,初步接觸一些windows下的溢出利用,和linux上還是有較大不同的。
本篇對應書上第6、11章節(jié)關于利用SEH異常處理來繞過GS的內容。
GS相當于Windows下的canary,這時通過溢出覆蓋返回地址,來控制程序執(zhí)行流程的方法就不再可行。在linux下繞過canary的方法也有一些,在windows下總的思路是一致的:
一、泄露canary的值(參考?%s打印棧上數據時的canary泄露)
二、canary檢查的機制是生成隨機canary后放到返回地址之前,并將其備份(例如linux下存到gs:0x14),在函數返回時,再把兩處的canary通過異或來判斷是否發(fā)生改動,如果能夠同時修改這兩個地方的canary值,就可以通過canary的檢測。(難以利用)
三、利用其它溢出方式(虛函數,堆)。
以上三種比較通用,除此之外,linux下還有一種利用___stack_chk_fail()函數實現一次任意地址讀的姿勢(Stack Smashing Protector任意地址讀)
而windows下還可以利用覆蓋SEH異常處理指針,只要觸發(fā)異常先于canary檢查,就可以先去執(zhí)行SEH異常處理函數從而實現利用。
SEH chain結構:

由pointer to next SEH構成單向鏈表,而handle指向異常處理函數。
具體的異常處理機制就不做過多說明。
Windows XP sp2之后引入了SafeSEH,接下來的內容主要針對SafeSEH,且暫時不考慮DEP。
SafeSEH針對指向異常處理函數的指針做了若干檢查,使得我們直接覆蓋其為shellcode地址變得不可行。先看看能夠通過檢查的情況:
1.異常處理函數位于加載模塊內存范圍之外,DEP關閉
2.異常處理函數位于加載模塊內存范圍之內,相應模塊未啟用SafeSEH,同時相關模塊不是純IL指令。
3.異常處理函數位于加載模塊內存范圍之內,相應模塊啟用SafeSEH,異常處理函數地址包含在安全S.E.H表中。
0x00 利用未啟用SafeSEH模塊繞過SafeSEH
實驗環(huán)境:
? ? ?Windows XP SP3 ? ?(DEP disabled)
? ? ?Visual Studio 2008
? ? ?VC++ 6.0
首先用vc6.0構建一個未啟用SafeSEH的dll:
#include "stdafx.h"
#include "stdio.h"
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
? ? return TRUE;
}
void jump()
{
? ? puts("hereisit"); ? ? ? ? //主要用于定位ROP
? ? __asm{
? ? ? ? ? ? pop eax
? ? ? ? ? ? pop eax
? ? ? ? ? ? retn ? ? ? ? ? ? ? ? ? ? //一個pop pop ret 的rop,用法后面會提到
? ? ? ? ? ? ? ? ?}
}
為了防止ROP的地址包含‘'\x00'被截斷,需要手動設置dll的加載基址:
在 工程-->設置-->連接 的工程選項中添加 ?/base:"0x11120000"/

接著用vs2008構建一個啟用SafeSEH的exe,其中調用之前的dll。(vs2008默認開啟GS、SafeSEH)
char shellcode[]="”;
DWORD MyException(void)
{
? ? ? ? ?printf("This is an exception");
? ? ? ? ?getchar();
? ? ? ? ?return 1;
}
void test(char * input)
{
? ? ? ? ? char str[200];
? ? ? ? ? strcpy(str,input); ? ? ? //溢出點
? ? ? ? ? int zero=0;
? ? ? ? ? __try{
? ? ? ? ? ? ? ? ? zero=1/zero; ? ? ? ? ? ? //構造一個除零來觸發(fā)異常處理
? ? ? ? ? ?}
? ? ? ? ? __except(MyException()) { }
}
int _tmain(int argc,_TCHAR* argv[])
{
? ? ? ? ? ?HINSTANCE hinst = LoadLibrary(_T("SEH_NOSafeSEH_JUMP.dll"));
? ? ? ? ? ?char str[200];
? ? ? ? ? ?test(shellcode);
? ? ? ? ? ?return 0;
}
先用Immunity Debugger的mona插件計算需要多少個字節(jié)才能覆蓋到SEH的內容:

把生成的字符串放到shellcode數組中,od或者immunity debugger調試,此時SEH的pointer to next SEH被覆蓋為41326841,由于是小端格式,倒過來用mona計算偏移:

我們只需在shellcode中填充’\x90'*216,接著就將覆蓋SEH結構體。
棧中內容大致是這樣的

調用異常處理函數的過程:
把0x12FE94處的四字節(jié)放入eax,然后call eax,這時棧的情況:

回看之前我們的rop:POP POP RETN,兩次彈棧后將執(zhí)行0x12FE90處的指令。
書中給出的shellcode布局是:

執(zhí)行了我們偽造的異常處理函數,實際上是pop pop retn后,eip指向0x12fe90,\x90是nop指令,pop pop retn的地址相當于一個操作eax的指令無關緊要,那么可以一直滑向shellcode執(zhí)行。
但是實際調試時,這樣布局0x12ef98~0x12fe9c的位置會被破壞,不再是NOP指令無法滑向shellcode。
由于沒有開啟DEP,我在0x12FE90的位置放置了\xeb\x0b\x90\x90,對應匯編指令jmp 0x10,直接跳到0x12fea0即shellcode的第一條指令執(zhí)行。

0x01 利用加載模塊之外的地址繞過SafeSEH
當程序加載到內存中后,在它所占的整個內存空間中,除了我們平時常見的PE文件模塊(EXE和DLL)之外,還有其他一些映射文件,例如,類型為MAP的映射文件,SafeSEH是無視它們的,所以我們可以在這些文件中尋找跳轉指令。
這種情況下可用的跳板地址,除了pop pop retn序列外,還有:
call / jmp dword ptr [esp+0x8/0x14/0x1c/0x2c/0x44/0x50]
call / jmp dword ptr [ebp+0xc/0x24/0x30]
call / jmp dword ptr [ebp-0x4/0xc/0x18]
關于為什么這些跳板可以,有興趣的童鞋可以自己調試跟一下。
書作者開發(fā)了一個插件叫做OllyFindAddr,與ollydbg自己的指令搜索不同,該插件不只在加載模塊中搜索指令,而是在整個程序的內存空間搜索。

exploit!

后記
某次課堂練習復現一個簡單的SafeSEH利用,系統(tǒng)是windows xp sp3 pro,od給的exp里不知道為什么要往seh handler填一個jmp ebx的地址,重新找了pop pop ret填入發(fā)現無法正常調用異常處理函數,最后發(fā)現根本找不到未開啟safeSEH的模塊。
然而發(fā)現exp中的jmp ebx是可以執(zhí)行的,其位置位于地址空間的最后,一塊未識別的模塊。在其中搜索pop pop ret找不到,最后終于找到形如:
pop?
mov ebx,
pop
mov ebx,
ret
的指令序列,完成利用。