前面寫了不少SEH相關文章,這裡來個複雜點的棧溢出SEH。文章不重複解釋SEH運作原理,但對主要步驟加以調試和注釋,另外本文參考考了雪上 Exploit 編寫系列教程第三篇_基于SEH的Exploit 一文。一般來說Exploit重在溢出,不過本文旨在是示範Exploit SEH的原理,是以省略溢出過程直接在棧上修改。
程式源碼(vc++6.0 Debug版本)如下:
#include<windows.h>
int ExceptionHandler(void);
void Fake_Handler();
void FakeShellcode();
int main(int argc,char *argv[])
{
__try
{
__asm -->這段__asm代碼用于修改棧上的_EXCEPTION_REGISTRATION結構
{
mov edx,ebp;
sub edx,0x10;
mov DWORD ptr [edx],0x909006EB;
lea eax,Fake_Handler;
mov DWORD ptr [edx+4],eax;
mov BYTE ptr [edx+8],0xE9;
lea ebx,[edx+8];
lea ecx,FakeShellcode;
sub ecx,5;
mov eax,ecx;
sub eax,ebx;
mov DWORD ptr [edx+9],eax;
}
__asm -->這段代碼用于産生異常
{
xor eax,eax;
mov [eax],eax;
}
}
__except(ExceptionHandler())
{}
return 0;
}
__declspec(naked) void Fake_Handler()
{
__asm --> 當異常發生,控制流将進入ExceptHandle,pop/pop/ret指令流使控制流從ExcepHandle跳轉到目前棧中_EXCEPTION_REGISTRATION_RECORD結構執行
{
pop edi;
pop esi;
ret;
}
}
__declspec(naked) void FakeShellcode()
{
MessageBox(NULL,"","",MB_OK);
}
int ExceptionHandler(void)
{
return 0;
}
前面的博文"vc++6對windows SEH擴充分析"提到vc++對SEH機制做了擴充,将_EXCEPTION_REGISTRATION_RECORD結構
struct _EXCEPTION_REGISTRATION_RECORD
{
struct _EXCEPTION_REGISTRATION *prev;
void (*handler)(PEXCEPTION_RECORD,
PEXCEPTION_REGISTRATION,
PCONTEXT,
PEXCEPTION_RECORD);
}
擴充為
struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
void (*handler)(PEXCEPTION_RECORD,
PEXCEPTION_REGISTRATION,
PCONTEXT,
PEXCEPTION_RECORD);
struct scopetable_entry *scopetable;
int trylevel;
int _ebp;
PEXCEPTION_POINTERS xpointers;
};
代碼L13,L14借由_EXCEPTION_REGISTRATION!_ebp的位址減去一個偏移,定位到_EXCEPTION_REGISTRATION!prev的位址,這同時是目前函數棧中的
EXCEPTION_REGISTRATION異常處理結構的起始位址。後面的代碼全是在這個結構上倒騰~
L16,L17将EXCEPTION_REGISTRATION!handler從__except_handler3修改為Fake_Handler。
下面的代碼片段是僅修改函數異常處理結構_EXCEPTION_REGISTRATION!prev後的棧記憶體狀态。棧記憶體0012FF40處的0x4012A0為__except_handler3:
0012FF38 EB 06 90 90 A0 12 40 00 ..悙..@.
0012FF40 20 F0 41 00 00 00 00 00 餉.....
0012FF48 88 FF 12 00 69 14 40 00
mapfile内容:
0001:00000291 __NLG_Dispatch 00401291 f LIBCD:exsup.obj
0001:000002a0 __except_handler3 004012a0 f LIBCD:exsup3.obj
0001:0000035d __seh_longjmp_unwind@4 0040135d f LIBCD:exsup3.obj
可能有人會疑惑,為什麼要把_EXCEPTION_REGISTRATION!prev改為"EB 06 90 90"?這是因為後面異常進入Fake_Handler後,執行pop/pop/ret指令流,跳轉到_EXCEPTION_REGISTRATION!prev所在的位址。再往後,eip将在_EXCEPTION_REGISTRATION!prev所在的位址取指令運作。如果不修改,這塊區域儲存了下一個_EXCEPTION_REGISTRATION結構的位址值,鬼曉得這個位址值将是怎樣的代碼流。是以要改為一段有用的代碼;理論上可以是任意可執行的代碼,但是EXCEPTION_REGISTRATION!handler儲存的是Fake_Handler的位址,如果_EXCEPTION_REGISTRATION!prev處的代碼被改為順序執行的指令,勢必會執行到EXCEPTION_REGISTRATION!handler所在的位址。是以需要用跳轉指令跳過EXCEPTION_REGISTRATION!handler。
模拟handler覆寫後EXCEPTION_REGISTRATION!handler内容:
0012FF38 EB 06 90 90 0F 10 40 00 ..悙..@.
0012FF40 20 F0 41 00 00 00 00 00 餉.....
0012FF48 88 FF 12 00 69 14 40 00
0x40100F是ILT增量連接配接表中跳轉到Fake_Handler函數的位址
當異常發生,系統首先到fs:[0]隊列中找最近節點的異常處理函數,如果處理不了,則通過該節點的prev域找次近節點處理。現在就假設出現一個異常,那得到執行的将是被覆寫了的Fake_Handler函數。我們跟着系統的節奏進入Fake_Handler函數。
Fake_Handler是個裸函數,進入該函數後,編譯器并沒有自作主張的生成函數幀,是以在執行L41前堆棧的布局如下:
[esp]:Fake_Handler
[esp+4]:_EXCEPTION_RECORD
[esp+8]:EXCEPTION_REGISTER
[esp+0x0C]:_CONTEXT *pContex
[esp+0x10]:pDispatcherContext
當然,你們可以質疑我是不是在大力亂神,我可以證明之:
進入該函數時堆棧布局:
0012FAFC B9 72 F2 76 E4 FB 12 00 箁騰潲..
0012FB04 38 FF 12 00 00 FC 12 00 8.......
0012FB0C B8 FB 12 00
記憶體0012FAFC處的0x76F272B9看着像是ntdll中的位址,
看下0x12FBE4處的内容,理應是_EXCEPTION_RECORD結構,裡面至少包含了出錯資訊和出錯指令位址:
0012FBE4 05 00 00 C0 00 00 00 00 ........
0012FBEC 00 00 00 00 99 10 40 00 ......@.
其内容是0xC0000005,違例訪存和0x401099:mov [eax],eax所在指令位址:
30: xor eax,eax;
00401097 xor eax,eax
31: mov [eax],eax;
00401099 mov dword ptr [eax],eax
32: }
33: }
到這,可以确定堆棧上參數如上面假設那樣。好,經過L41,L42的
兩個pop操作目前esp指向了EXCEPTION_REGIST結構----
目前被展開的異常處理節點,這個節點的内容已經被溢出覆寫。當執行L43 ret指令時會發生什麼?想象一下pop eip,将esp的内容傳遞給eip,之後eip去那取指令運作。
看看執行ret時,esp指向:
ESI = 0012FBE4 EDI = 76F272B9
EIP = 00401102 ESP = 0012FB04
...
0012FB04 38 FF 12 00 00 FC 12 00 8.......
0x12FB04處的内容是0x12FF38,跟過去看看:
0012FF38 EB 06 90 90 0F 10 40 00 ..悙..@.
0012FF40 E9 C0 10 2D 00 00 00 00 槔.-....
0012FF48 88 FF 12 00 69 14 40 00 ....i.@.
如果一時記不起這塊記憶體的内容,麻煩向上滾動一下滑鼠滾輪,前面提到這是程式開始時,由編譯器在函數堆棧上安置的
EXCEPTION_REGIST異常處理節點!!
很明顯了,eip将去堆棧上取指令運作,而且取出的指令來自EXCEPTION_REGIST!prev所在(已經被溢出覆寫)。
回到main函數L15處這裡的指令mov DWORD
main函數中在後面的内容已經沒有解釋的必要了,本篇完~
相關連結:
1.Exploit 編寫系列教程第三篇_基于SEH的Exploit(+3b)