看到這一章我又抓狂了:作者提及從程序空間類型為Map的映射檔案中尋找如下指令位址:
call/jmp dword ptr[esp+0x8]
call/jmp dword ptr[esp+0x14]
call/jmp dword ptr[esp+0x1c]
call/jmp dword ptr[esp+0x2c]
call/jmp dword ptr[esp+0x44]
call/jmp dword ptr[esp+0x50]
call/jmp dword ptr[ebp+0xc]
call/jmp dword ptr[ebp+0x24]
call/jmp dword ptr[ebp+0x30]
call/jmp dword ptr[ebp-0x4]
call/jmp dword ptr[ebp-0xc]
call/jmp dword ptr[ebp-0x18]
至于為什麼要尋找這些指令,作者的态度是輕拂衣袖不留任何解釋,空留我一臉迷茫。迷茫很久我決定還調試一遍觀察程序棧布局并把調試的結果記錄于此。示例代碼就用作者書本上的那個,這裡就不重複貼代碼了。
進入__try/__except塊之後,程式目前的異常處理鍊如下:
0:000> !exchain
0012fe80<-----這個數值很重要,所有的jmp [ebp+N]最終是要跳到這個位址上: offset!ILT+115(__except_handler4)+0 (00411078)
0012ffa8: offset!ILT+115(__except_handler4)+0 (00411078)
0012ffe0: kernel32!_except_handler3+0 (7c839ac0)
CRT scope 0, filter: kernel32!BaseProcessStart+29 (7c843882)
func: kernel32!BaseProcessStart+3a (7c843898)
Invalid exception stack at ffffffff
0:000> dd fs:[0] L1 ;程序異常處理連結清單頭
003b:00000000 0012fe80 ;目前第一個異常塊的位址為:0x12fe80
0:000> dd 0012fe80 L2 ;第一個異常塊的内容
0012fe80 0012ffa8 00411078
在這個異常鍊的保護下,當異常發生并且OS接管異常後,經過一路過關斬将最終會進入函數ntdll!ExecuteHandler4(如果程式沒有溢出)
0:000> x *!__except_handler4 ;查找異常處理函數ExecuteHandler4所在子產品
00411840 offset!_except_handler4 = <no type information>
102d3280 MSVCR90D!_except_handler4 = <no type information>
0:000> bp offset!_except_handler4 ;為了友善觀察異常發生時,OS調用異常處理函數ExecuteHandler4的棧回溯,需要在這個函數上下斷點
0:000> g ;觸發除0異常
(a78.e6c): Integer divide-by-zero - code c0000094 (first chance)
First chance exceptions are reported before any exception handling.
我們來觀察一下異常處理的棧回溯資訊:
0:000> kb
ChildEBP RetAddr Args to Child
0012f9b4 7c9232a8 0012faa0 0012fe80 0012fab4 offset!_except_handler4
0012f9d8 7c92327a 0012faa0 0012fe80 0012fab4 ntdll!ExecuteHandler2+0x26 ;由ntdll!ExecuteHandler2調用異常處理函數offset!_except_handler4
0012fa88 7c92e46a 00000000 0012fab4 0012faa0 ntdll!ExecuteHandler+0x24
0012fa88 00411493 00000000 0012fab4 0012faa0 ntdll!KiUserExceptionDispatcher+0xe
0012fe90 00411588 0041573c 00edf6ee 00edf7b4 offset!test+0x73 [c:\documents and settings\administrator\桌面\studio\offset\offset\offset.cpp @ 22]
0012ff68 00411b88 00000001 00394c70 00393320 offset!main+0x28 [c:\documents and settings\administrator\桌面\studio\offset\offset\offset.cpp @ 30]
0012ffb8 004119cf 0012fff0 7c817067 00edf6ee offset!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 586]
0012ffc0 7c817067 00edf6ee 00edf7b4 7ffdf000 offset!mainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]
根據棧回溯資訊,我們可以确定offset!_except_handler4是由ntdll!ExecuteHandler2調用的,而異常處理函數的接口形式是固定的,形如:
EXCEPTION_DISPOSITION ExcuteHandler(PEXCEPTION_RECORD,PEXCEPTION_REGISTRATION,struct _CONTEXT*,void*);
既然ntdll!ExecuteHandler2負責調用offset!_except_handler4,那麼它也必須負責為offset!_except_handler4傳遞參數。代碼片段摘取了部分ntdll!ExecuteHandler2實作,盡管是彙編代碼,但還是請讀者耐心的往下看下去:
0:000> uf ntdll!ExecuteHandler2
ntdll!ExecuteHandler2:
7c923282 55 push ebp
7c923283 8bec mov ebp,esp
7c923285 ff750c push dword ptr [ebp+0Ch]
7c923288 52 push edx
7c923289 64ff3500000000 push dword ptr fs:[0]
7c923290 64892500000000 mov dword ptr fs:[0],esp
7c923297 ff7514 push dword ptr [ebp+14h]
7c92329a ff7510 push dword ptr [ebp+10h]
7c92329d ff750c push dword ptr [ebp+0Ch] <-------------------
7c9232a0 ff7508 push dword ptr [ebp+8]
7c9232a3 8b4d18 mov ecx,dword ptr [ebp+18h]
7c9232a6 ffd1 call ecx
箭頭指向處出現了作者提到的第一個跳闆指令的跳轉目标:[ebp+0x0c]。當然,看了這段代碼,你并不能确定最後一行call ecx就是調用offset!_except_handler4。可以通過檢視ecx的符号來确定call ecx的目标。另外,由于我在offset!_except_handler4入口加了斷點,程式停留在函數入口并沒有對ecx做操作:
0:000> r esp,ebp ;call ecx後進入到offset!_except_handler4中時,ebp/esp的值
esp=0012f9b8 ebp=0012f9d8
0:000> ln ecx
(00411078) offset!ILT+115(__except_handler4) | (0041107d) offset!ILT+120(__lock)
Exact matches:
除了獲得這個資訊,
還可以看到剛進入offset!_except_handler4時,esp和ebp在數值上差0x20.這個內插補點很重要,為什麼這麼說?如果你仔細觀察作者提到的指令位址就會發現
這樣的規律:[ebp+N-0x20]=[esp+M],換句話說,兩個指令指向的位址是同一個,隻是用了不同的寄存器做索引,于是,我們有了這樣的對應關系:
call/jmp dword ptr[esp+0x8] = call/jmp dword ptr[ebp-0x18]
call/jmp dword ptr[esp+0x14] = call/jmp dword ptr[ebp-0xc]
call/jmp dword ptr[esp+0x1c] = call/jmp dword ptr[ebp-0x4]
call/jmp dword ptr[esp+0x2c] = call/jmp dword ptr[ebp+0xc]
call/jmp dword ptr[esp+0x44] = call/jmp dword ptr[ebp+0x24]
call/jmp dword ptr[esp+0x50] = call/jmp dword ptr[ebp+0x30]
有了這個關系,我們分析的時候可以把注意力放在一類指令上,比如,後面的章節我将集中分析[ebp+N]系列的指令。
目前為止,溢出後能利用SEH并繞過SafeSeh保護的原因是因為異常處理結構EXCEPTION_REGISTRATION_RECORD存放在retAddr前面(位址比retAddr更低)的棧位址。當函數的緩沖區被溢出後,shellcode分布在從變量位址到retAddr附近的棧空間上(中間也包含了異常處理節點)。要讓異常處理程式在茫茫4G程序空間定位并執行這段空間(shellcode一般才幾百位元組)幾乎是海底撈針。不過這并不代表沒有辦法定位,辦法就是利用異常處理過程中傳遞的
EXCEPTION_REGISTRATION_RECORD變量。如前所述,EXCEPTION_REGISTRATION_RECORD結構中包含了觸發異常時,目前棧上的異常處理結構:
0:000> dt EXCEPTION_REGISTRATION_RECORD
offset!EXCEPTION_REGISTRATION_RECORD
+0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION
由于人為制造溢出的緣故,書中提到Next所在的位置被篡改為短跳轉語句,Handler被篡改為call [ebp+N]指令所在位址。這樣安排的目的是讓函數ntdll!ExecuteHandler2執行指令call ecx(ecx中應該為offset!_except_handler4)時,錯誤的從ecx中取出jmp [ebp+N]的位址,先跳到程序空間映射檔案中執行jmp [ebp+N],然後跳轉到棧中變量EXCEPTION_REGISTRATION_RECORD!Next繼續取指令執行,當然,這回取到的是一個短跳轉語句。
大概的流程讀者可能知道了,但是我知道仍然有許多未解的疑惑----那就是[ebp+N]代表啥?哎,這是最難的部分,慢慢來吧。先看[ebp+N]這種表達方式是不是很眼熟?對,這些語句就是用作取局部變量/函數參數。先看看第一個[ebp-0x0c]:
0:000> kb ;檢視堆棧,程式目前進入函數offset!_except_handler4
ChildEBP RetAddr Args to Child
0012f9b4 7c9232a8 0012faa0 0012fe80 0012fab4 offset!_except_handler4
0012f9d8 7c92327a 0012faa0 0012fe80 0012fab4 ntdll!ExecuteHandler2+0x26
0:000> r eip ;檢視目前執行到函數中哪條指令以及函數反彙編
eip=00411840
0:000> uf .
offset!_except_handler4:
00411840 8bff mov edi,edi
00411842 55 push ebp
;上面的結果告訴我們,雖然進入函數offset!_except_handler4,但ebp沒有變動仍然保持它在ntdll!ExecuteHandler2中的值,
;這和發生溢出後異常處理調用被溢出的異常處理函數而進入到單指令函數call [ebp+N]時的情景是一緻的
0:000> r ebp
ebp=0012f9d8
0:000> ?? @ebp-0xc ;[ebp-0x0c]指向的位址
unsigned int 0x12f9cc
0:000> dd 0x12f9cc L2 ;這個位址包含的内容是一個異常處理結構,這也可以從下面!exchain的輸出得到印證
0012f9cc 0012fe80 <-----文章開頭提過這個數值,這是第一次出現 7c9232bc
0:000> ln 7c9232bc
(7c923282) ntdll!ExecuteHandler2+0x3a | (7c92330a) ntdll!RtlpUnlinkHandler
0:000> !exchain
0012f9cc: ntdll!ExecuteHandler2+3a (7c9232bc)
0012fe80: offset!ILT+115(__except_handler4)+0 (00411078)
0012ffa8: offset!ILT+115(__except_handler4)+0 (00411078)
0012ffe0: kernel32!_except_handler3+0 (7c839ac0)
CRT scope 0, filter: kernel32!BaseProcessStart+29 (7c843882)
func: kernel32!BaseProcessStart+3a (7c843898)
Invalid exception stack at ffffffff
[ebp-0x0c]這個異常處理結構是進入ntdll!ExecuteHandler2後形成的内嵌異常處理器 (參見<軟體調試>P723) 反彙編代碼如下:
ntdll!ExecuteHandler2:
7c923282 55 push ebp ;[ebp+0]
7c923283 8bec mov ebp,esp
7c923285 ff750c push dword ptr [ebp+0Ch] ;将函數ntdll!ExecuteHandler2參數2壓入棧中,壓入後變成局部變量,位置在[ebp-4]
7c923288 52 push edx ;第二個局部變量[ebp-8]
7c923289 64ff3500000000 push dword ptr fs:[0] ;fs:[0]原本儲存了異常發生時的異常節點EXCEPTION_REGISTRATION_RECORD!Next.第三個變量[ebp-c]
7c923290 64892500000000 mov dword ptr fs:[0],esp
上面第4條push指令,将fs:[0]的值重新壓入堆棧,這個值原本指向程式第一個異常處理節點(前面多次提到該節點存放在棧上,已被溢出覆寫)。經過這次push操作,這個節點存放于位址[ebp-c]。jmp [ebp-c]就是跳到棧上異常處理節點EXCEPTION_REGISTRATION_RECORD!prev位址處。
剩下的指令位址将在下一篇分析