天天看點

簡單示範Exploit SEH原理(未開啟SafeSEH子產品)

    前面寫了不少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)​​

繼續閱讀