對模擬int3的探索

最近由於某個程序需要,探索了下int3指令,目的是不通過idt中斷表,直接訪問KiTrap03函數.寫此文的目的是
1,詢問幾個問題,
2,分享下探索過程 



以下是整個探索過程:
首先 開啓雙機調試(VM WIN7 + WINDBG),ctrl+break中斷後,棧回溯如圖:
名稱:  1.png查看次數: 4文件大小:  6.7 KB
 
可以看到斷點的由來是RtlpBreakWithStatusInstruction裏的int3,
從這個名稱開頭中的p可以看出來它是一個內部函數(原因參見<windows內核原理與實現>40頁),所以懷疑棧回溯顯示的符號不完全準確,驗證一下


反彙編KdCheckForDebugBreak函數如圖:
點擊圖片以查看大圖圖片名稱:	2.png查看次數:	13文件大小:	14.7 KB文件 ID :	86470
 
可以看到KdCheckForDebugBreak中並沒有對RtlpBreakWithStatusInstruction進行訪問,唯一有點像斷點的函數的就是DbgBreakPointWithStatus


反彙編DbgBreakPointWithStatus如圖:
名稱:  3.png查看次數: 1文件大小:  4.0 KB
 
這裏可以看到RtlpBreakWithStatusInstruction其實就是DbgBreakPointWithStatus函數中的一個標籤.



接下來就是寫代碼實現功能,首先是HOOK DbgBreakPointWithStatus函數,部分代碼如下:
代碼:
//得到內核中的KiTrap03函數地址 gNewKiTrap03Addr=GetOriginalProcAddr(L"KiTrap03");      //定位內核中DbgBreakPointWithStatus函數地址 ulNewFunAddr=GetOriginalProcAddr(L"DbgBreakPointWithStatus");  //讓DbgBreakPointWithStatus函數轉向到MY函數中 ReplaceProcAddr((ULONG)MyDbgBreakPointWithStatus, ulNewFunAddr); 


第一次實驗的MyDbgBreakPointWithStatus代碼如下:
代碼:
//MY DbgBreakPointWithStatus函數 void __declspec(naked) MyDbgBreakPointWithStatus()   /*++      Routine Description:   此函數用於替換函數DbgBreakPointWithStatus, 模擬其中的int3指令,讓其不經過中斷表    --*/ {     //反彙編DbgBreakPointWithStatus如下:   //83ecf0cc 8b442404        mov     eax,dword ptr [esp+4]   //83ecf0d0 cc              int     3   //83ecf0d1 c20400          ret     4   //把int3 直接替換爲跳轉到 jmp KiTrap03   __asm   {     mov     eax,dword ptr [esp+4]          //跳向KiTrap03函數     jmp gNewKiTrap03Addr      ret 4   } } 
由於先前沒有過多瞭解int3指令,只知道它會根據中斷表訪問相應例程,所以就想直接把int3 替換爲jmp KiTrap03的指令,這樣一來多方便一條指令搞定,編譯放到虛擬機運行,然後在windbg中ctrl+break 斷下後分析結果如下:

點擊圖片以查看大圖圖片名稱:	4.png查看次數:	7文件大小:	20.3 KB文件 ID :	86472
 
可以看到
1.  斷在了一個未知的地方,
2.  觀察esp是無效的,
3.  觀察棧回溯代碼也是無效,
4.  對比正常情況下和此時的命令行提示符也發現不一樣,現在是16.0: kd> 正常情況是 1: kd>,說明不是同一個進程
5.  繼續F5運行下去就出現系統錯誤,最終虛擬機自動重啓瞭如圖:

 點擊圖片以查看大圖圖片名稱:	5.png查看次數:	2文件大小:	5.6 KB文件 ID :	86473

通過以上結果,懷疑是不是到了其他進程環境中呢?再想想對int3的替換比較草率,沒想其原理,所以先看看其原理.



查閱<軟件調試>278頁:
 名稱:  6.png查看次數: 2文件大小:  82.2 KB

Int3屬於中斷門,遵循以上原理,由於進入前後代碼都是在ring0,所以CPU不需要堆棧切換,int3做的就是EFLAGS CS EIP 壓棧,流程自然就到KiTrap03例程中了,修改實驗代碼.



第二次實驗的MyDbgBreakPointWithStatus代碼如下:
代碼:
//MY DbgBreakPointWithStatus函數 void __declspec(naked) MyDbgBreakPointWithStatus() {     __asm   {     mov     eax,dword ptr [esp+4]      //志寄存器入棧     pushf      //CS入棧     push cs  //EIP入棧     push offset _ret1        //跳向KiTrap03函數     jmp gNewKiTrap03Addr  _ret1:     ret 4   } } 
編譯放入虛擬機運行結果如下:

點擊圖片以查看大圖圖片名稱:	7.png查看次數:	4文件大小:	17.4 KB文件 ID :	86475
 
這次結果可以發現,
1.  雖然還是沒有正確,但是棧回溯可以查看了,而且也可以看到的確進入MY函數
2.  提示符變成了VM.1 這和VM虛擬機相符合,但是和正常狀態下的提示符還是不一樣
3.  我們設定的EIP應該是ret 4指令,應該是9a97a392但是現在EIP減了一,實際是9a97a391
4.  繼續F5依然是錯誤,虛擬機自動重啓
通過上面的第3點可以印證一個原理:


名稱:  8.png查看次數: 1文件大小:  27.7 KB
 
----<軟件調試>76頁


名稱:  9.png查看次數: 1文件大小:  36.3 KB
----<軟件調試>78頁

由此可見壓入EIP這個代碼需要修改,通過其他結果,發現還是和環境有關 但是明顯的比第一次實驗要好一點了,既然和環境有關,這裏唯一沒做的就是CPU堆棧相關的切換代碼,當然抱着試一試的心態,加上了相關代碼 修改後的代碼下




第三次實驗的MyDbgBreakPointWithStatus代碼如下:
代碼:
//MY DbgBreakPointWithStatus函數 void __declspec(naked) MyDbgBreakPointWithStatus() {     __asm   {       mov     eax,dword ptr [esp+4]      //段選擇子 堆棧指針入棧     push ss     push esp        //志寄存器入棧     pushf      //CS、EIP入棧     push cs     push offset _ret1      //跳向KiTrap03函數     jmp gNewKiTrap03Addr      //彌補windows對INT3指令的特殊減一     nop _ret1:     ret 4 } } 
編譯後虛擬機中運行,windbg中ctrl+break斷下後結果如圖:

點擊圖片以查看大圖圖片名稱:	10.png查看次數:	2文件大小:	21.9 KB文件 ID :	86478
 
得到以下結果:
1.  EIP按照我的的想法到達了指定位置,這一點應該是修改正確了
2.  環境基本正確了,能正確顯示斷點地址,而且windbg命令提示符也正常了,這兩點都表明環境已經基本正確了
3.  查看棧回溯發現最後一個訪問點有問題,0x10807e不是一個有效地址,
4.  查看棧,發現esp不是在一個正確地址上的,因爲esp地址應該是4的倍數纔對,但現在最後一位是E 也就是十進制的14
5.  繼續F5同樣出現錯誤,VM自動重啓

通過上面幾點可以發現ESP有明顯問題,棧不平衡,這點需要修改,繼續以下實驗代碼




第四次實驗在MyDbgBreakPointWithStatus 函數頭添加斷點DbgBreakPoint(),目的是爲了查看調用 自定義的INT3 前後棧是不是一樣,同樣編譯放入VM中運行 windbg中ctrl+break斷下顯示在DbgBreakPoint處,記錄現在ESP,繼續F5,再一次斷下來,這一次是執行了 自定義的int3 段下來的,對比兩次的esp,如圖

點擊圖片以查看大圖圖片名稱:	11.png查看次數:	4文件大小:	30.0 KB文件 ID :	86479
 
發現結果:
1.發現兩次ESP相差了6字節,第一次是正確的,第二次是錯誤的
2.運行到第二次時,查看正確地址的ESP和第一次是完全一樣的,這點說明棧內容還是對了的
3.反彙編返回地址發現的確也是上一層的調用函數

此時果斷的想出直接調整下ESP看看是否正常,修改實驗代碼如下:





第五次實驗的MyDbgBreakPointWithStatus代碼如下:
代碼:
//MY DbgBreakPointWithStatus函數 void __declspec(naked) MyDbgBreakPointWithStatus() {     __asm   {     mov     eax,dword ptr [esp+4]      //段選擇子 堆棧指針入棧     push ss     push esp        //志寄存器入棧     pushf      //CS、EIP入棧     push cs     push offset _ret1      //跳向KiTrap03函數     jmp gNewKiTrap03Addr      //彌補windows對INT3指令的特殊減一     nop _ret1:     //調整堆棧     add esp,6          ret 4 } } 
編譯後VM運行,結果如圖:
名稱:  12.png查看次數: 2文件大小:  24.5 KB
 
可以發現已經正確了,棧的位置及值都和沒有執行int3以前是一樣的,棧回溯也能正確顯示,反覆測試也可以正確跑起來,至此目的就達到了,沒有經過idt表,但也實現了其功能,



問題列表:功能雖然實現但有些問題還是沒搞懂,

第一點:都是ring0爲什麼需要加上CPU切換棧的代碼呢,?

第二點:當我把push ss刪除以後 把棧平衡該爲add esp,2 程序依然是正確的,這是爲什麼呢?具體代碼如下:
代碼:
  mov     eax,dword ptr [esp+4]   push esp     pushf   push cs   push offset _ret1   jmp gNewKiTrap03Addr     nop _ret1:   add esp,2       ret 4 
第三點:如果保留push ss 刪除push esp則會出現錯誤 難道是<軟件調試>上以XP爲例的和現在的WIN7有差異嗎?

第四點:按照原理來寫的代碼,應該不會出現棧不平衡的情況呀,雖然自己可以調整ESP,但是這明顯是一種治標不治本的方法,出現這樣的情況是爲什麼呢?

以上就是我對int3的一點探索,最後的問題還請各位朋友講解以下,謝謝先
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章