1,詢問幾個問題,
2,分享下探索過程
以下是整個探索過程:
首先 開啓雙機調試(VM WIN7 + WINDBG),ctrl+break中斷後,棧回溯如圖:
可以看到斷點的由來是RtlpBreakWithStatusInstruction裏的int3,
從這個名稱開頭中的p可以看出來它是一個內部函數(原因參見<windows內核原理與實現>40頁),所以懷疑棧回溯顯示的符號不完全準確,驗證一下
反彙編KdCheckForDebugBreak函數如圖:
可以看到KdCheckForDebugBreak中並沒有對RtlpBreakWithStatusInstruction進行訪問,唯一有點像斷點的函數的就是DbgBreakPointWithStatus
反彙編DbgBreakPointWithStatus如圖:
這裏可以看到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 } }
可以看到
1. 斷在了一個未知的地方,
2. 觀察esp是無效的,
3. 觀察棧回溯代碼也是無效,
4. 對比正常情況下和此時的命令行提示符也發現不一樣,現在是16.0: kd> 正常情況是 1: kd>,說明不是同一個進程
5. 繼續F5運行下去就出現系統錯誤,最終虛擬機自動重啓瞭如圖:
通過以上結果,懷疑是不是到了其他進程環境中呢?再想想對int3的替換比較草率,沒想其原理,所以先看看其原理.
查閱<軟件調試>278頁:
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 } }
這次結果可以發現,
1. 雖然還是沒有正確,但是棧回溯可以查看了,而且也可以看到的確進入MY函數
2. 提示符變成了VM.1 這和VM虛擬機相符合,但是和正常狀態下的提示符還是不一樣
3. 我們設定的EIP應該是ret 4指令,應該是9a97a392但是現在EIP減了一,實際是9a97a391
4. 繼續F5依然是錯誤,虛擬機自動重啓
通過上面的第3點可以印證一個原理:
----<軟件調試>76頁
----<軟件調試>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 } }
得到以下結果:
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,如圖
發現結果:
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 } }
可以發現已經正確了,棧的位置及值都和沒有執行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
第四點:按照原理來寫的代碼,應該不會出現棧不平衡的情況呀,雖然自己可以調整ESP,但是這明顯是一種治標不治本的方法,出現這樣的情況是爲什麼呢?
以上就是我對int3的一點探索,最後的問題還請各位朋友講解以下,謝謝先