# 3 自己動手寫一個 windows 調試器
現在我們已經講解完了基礎知識,是時候實現一個真正的的 調試器的時候了。當微軟開發 windows 的時候,他們增加了一 大堆的令人驚喜的調試函數以幫助開發者們保證產品的質量。我 們將大量的使用這些函數創建你自己的純 python 調試器。有一點很重要,我們本質上是在深入的學習 PyDbg(Pedram Amini’s )的使用,這是目前能找到的最簡潔的 Windows 平臺下的 Python 調試器 。拜 Pedram 所賜,我盡可能用 PyDbg 完成了我的代碼(包括函數名,變 量,等等),同時你也可以更容易的用 PyDbg 實現你的調試器。
為了對一個進程進行調試,你首先必須用一些方法把調試器和進程連接起來。所以,我 們的調試器要不然就是裝載一個可執行程序然后運行它,要不然就是動態的附加到一個運行 的進程。Windows 的調試接口(Windows debugging API)提供了一個非常簡單的方法完成 這兩點。
運行一個程序和附加到一個程序有細微的差別。打開一個程序的優點在于他能在程序運 行任何代碼之前完全的控制程序。這在分析病毒或者惡意代碼的時候非常有用。附加到一個 進程,僅僅是強行的進入一個已經運行了的進程內部,它允許你跳過啟動部分的代碼,分析 你感興趣的代碼。你正在分析的地方也就是程序目前正在執行的地方。
第一種方法,其實就是從調試器本身調用這個程序(調試器就是父進程,對被調試進程 的控制權限更大)。在 Windows 上創建一個進程用 CreateProcessA()函數。將特定的標志傳 進這個函數,使得目標進程能夠被調試。一個 CreateProcessA()調用看起來像這樣:
```
BOOL WINAPI CreateProcessA(
LPCSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
```
初看這個調用相當恐怖,不過,在逆向工程中我們必須把大的部分分解成小的部分以便理解。這里我們只關心在調試器中創建一個進程需要注意的參數。這些參數是 lpApplicationName,lpCommandLine,dwCreationFlags,lpStartupInfo, 和 lpProcessInformation。 剩余的參數可以設置成空值(NULL)。關于這個函數的詳細解釋可以查看 MSDN(微軟之葵 花寶典)。最前面的兩個參數用于設置,需要執行的程序的路徑和我們希望傳遞給程序的參 數。dwCreationFlags (創建標記)參數接受一個特定值,表示我們希望程序以被調試的狀 態 啟 動 。 最 后 兩 個 參 數 分 別 分 別 指 向 2 個 結 構 (STARTUPINFO and PROCESS_INFORMATION) ,不僅包含了進程如何啟動,以及啟動后的許多重要信息 。( lpStartupInfo : STARTUPINFO 結 構 , 用 于 在 創 建 子 進 程 時 設 置 各 種 屬 性 , lpProcessInformation:PROCESS_INFORMATION 結構,用來在進程創建后接收相關信息 , 該結構由系統填寫。)
創建兩個 Python 文件 my_debugger.py 和 my_debugger_defines.py。我們將創建一個父類 debugger() 接 著 逐 漸 的 增 加 各 種 調 試 函 數 。 另 外 , 把 所 有 的 結 構 , 聯 合 , 常 量 放 到 my_debugger_defines.py 方便以后維護。
```
# my_debugger_defines.py
from ctypes import *
# Let's map the Microsoft types to ctypes for clarity
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte) LPTSTR = POINTER(c_char)
HANDLE = c_void_p
# Constants
DEBUG_PROCESS = 0x00000001
CREATE_NEW_CONSOLE = 0x00000010
# Structures for CreateProcessA() function
class STARTUPINFO(Structure):
_fields_ = [
("cb", DWORD),
("lpReserved", LPTSTR),
("lpDesktop", LPTSTR),
("lpTitle", LPTSTR),
("dwX", DWORD),
("dwY", DWORD),
("dwXSize", DWORD),
("dwYSize", DWORD),
("dwXCountChars", DWORD),
("dwYCountChars", DWORD),
("dwFillAttribute",DWORD),
("dwFlags", DWORD),
("wShowWindow", WORD),
("cbReserved2", WORD),
("lpReserved2", LPBYTE),
("hStdInput", HANDLE),
("hStdOutput", HANDLE),
("hStdError", HANDLE),
]
class PROCESS_INFORMATION(Structure):
_fields_ = [
("hProcess", HANDLE),
("hThread", HANDLE),
("dwProcessId", DWORD),
("dwThreadId", DWORD),
]
# my_debugger.py
from ctypes import *
from my_debugger_defines import *
kernel32 = windll.kernel32
class debugger():
def init (self):
pass
def load(self,path_to_exe):
# dwCreation flag determines how to create the process
# set creation_flags = CREATE_NEW_CONSOLE if you want
# to see the calculator GUI
creation_flags = DEBUG_PROCESS
# instantiate the structs
startupinfo = STARTUPINFO()
process_information = PROCESS_INFORMATION()
# The following two options allow the started process
# to be shown as a separate window. This also illustrates
# how different settings in the STARTUPINFO struct can affect
# the debuggee.
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0
# We then initialize the cb variable in the STARTUPINFO struct
# which is just the size of the struct itself
startupinfo.cb = sizeof(startupinfo)
if kernel32.CreateProcessA(path_to_exe,
None,
None,
None,
None,
creation_flags,
None,
None,
byref(startupinfo),
byref(process_information)):
print "[*] We have successfully launched the process!" print "[*] PID: %d" % process_information.dwProcessId
else:
print "[*] Error: 0x%08x." % kernel32.GetLastError()
```
現在我們將構造一個簡短的測試模塊確定一下一切都能正常工作。調用 my_test.py,保 證前面的文件都在同一個目錄下。
```
#my_test.py
import my_debugger
debugger = my_debugger.debugger() debugger.load("C:\\WINDOWS\\system32\\calc.exe")
```
如果你是通過命令行或者 IDE 手動輸入上面的代碼,將會新產生一個進程也就是你鍵 入程序名,然后返回進程 ID(PID),最后結束。如果你用上面的例子 calc.exe,你將看不到 計算器的圖形界面出現。因為進程沒有把界面繪畫到屏幕上,它在等待調試器繼續執行的命 令。很快我們就能讓他繼續執行下去了。不過在這之前,我們已經找到了如何產生一個進程 用于調試,現在讓我們實現另一個功能,附加到一個正在運行的進程。
為了附加到指定的進程,就必須先得到它的句柄。許多后面將用到的函數都需要句柄做 參數,同時我們也能在調試之前確認是否有權限調試它(如果附加都不行,就別提調試了)。 這個任務由 OpenProcess()完成,此函數由 kernel32.dll 庫倒出,原型如下:
```
HANDLE WINAPI OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle
DWORD dwProcessId
);
```
dwDesiredAccess 參數決定了我們希望對將要打開的進程擁有什么樣的權限(當然是 越 大 越 好 root is hack )。 因 為 要 執 行 調 試 , 我 們 設 置 成 PROCESS_ALL_ACCESS 。 bInheritHandle 參數設置成 False,dwProcessId 參數設置成我們希望獲得句柄的進程 ID ,也就是前面獲得的 PID。如果函數成功執行,將返回一個目標進程的句柄。
接下來用 DebugActiveProcess()函數附加到目標進程:
```
BOOL WINAPI DebugActiveProcess(
DWORD dwProcessId
);
```
把需要 a 附加的 PID 傳入。一旦系統認為我們有權限訪問目標進程,目標進程就假定 我們的調試器已經準備好處理調試事件,然后把進程的控制權轉移給調試器。調試器接著循 環調用 WaitForDebugEvent()以便俘獲調試事件。函數原型如下:
```
BOOL WINAPI WaitForDebugEvent(
LPDEBUG_EVENT lpDebugEvent,
DWORD dwMilliseconds
);
```
第一個參數指向 DEBUG_EVENT 結構,這個結構描述了一個調試事件。第二個參數設 置成 INFINITE(無限等待),這樣 WaitForDebugEvent() 就不用返回,一直等待直到一個事 件產生。
調試器捕捉的每一個事件都有相關聯的事件處理函數,在程序繼續執行前可以完成不同 的 操 作 。 當 處 理 函 數 完 成 了 操 作 , 我 們 希 望 進 程 繼 續 執 行 用 , 這 時 候 再 調 用 ContinueDebugEvent()。原型如下:
```
BOOL WINAPI ContinueDebugEvent(
DWORD dwProcessId,
DWORD dwThreadId,
DWORD dwContinueStatus
);
```
dwProcessId 和 dwThreadId 參數由 DEBUG_EVENT 結構里的數據填充,當調試器捕捉 到調試事件的時候,也就是 WaitForDebugEvent()成功執行的時候,進程 ID 和線程 ID 就以 及初始化好了。dwContinueStatus 參數告訴進程是繼續執行(DBG_CONTINUE),還是產生異 常(DBG_EXCEPTION_NOT_HANDLED)。
還剩下一件事沒做,從進程分離出來:把進程 ID 傳遞給 DebugActiveProcessStop()。 現在我們把這些全合在一起,擴展我們的 my_debugger 類,讓他擁有附加和分離一個進程的功能。同時加上打開一個進程和獲得進程句柄的能力。最后在我們的主循環里完成事 件處理函數。打開 my_debugger.py 鍵入以下代碼。
提示:所有需要的結構,聯合和常量都定義在了 debugger_defines.py 文件里,完整的代碼可 以從 [http://www.nostarch.com/ghpython.htm](http://www.nostarch.com/ghpython.htm) 下載。
```
#my_debugger.py
from ctypes import *
from my_debugger_defines import *
kernel32 = windll.kernel32
class debugger():
def init (self):
self.h_process = None
self.pid = None
self.debugger_active = False
def load(self,path_to_exe):
...
print "[*] We have successfully launched the process!"
print "[*] PID: %d" % process_information.dwProcessId
# Obtain a valid handle to the newly created process
# and store it for future access
self.h_process = self.open_process(process_information.dwProcessId)
...
def open_process(self,pid):
h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS,pid,False)
return h_process
def attach(self,pid):
self.h_process = self.open_process(pid)
# We attempt to attach to the process
# if this fails we exit the call
if kernel32.DebugActiveProcess(pid):
self.debugger_active = True
self.pid = int(pid)
self.run()
else:
print "[*] Unable to attach to the process."
def run(self):
# Now we have to poll the debuggee for
# debugging events
while self.debugger_active == True:
self.get_debug_event()
def get_debug_event(self):
debug_event = DEBUG_EVENT()
continue_status= DBG_CONTINUE
if kernel32.WaitForDebugEvent(byref(debug_event),INFINITE):
# We aren't going to build any event handlers
# just yet. Let's just resume the process for now.
raw_input("Press a key to continue...")
self.debugger_active = False
kernel32.ContinueDebugEvent( \
debug_event.dwProcessId, \
debug_event.dwThreadId, \
continue_status )
def detach(self):
if kernel32.DebugActiveProcessStop(self.pid):
print "[*] Finished debugging. Exiting..."
return True
else:
print "There was an error" return False
```
現在讓我們修改下測試套件以便使用新創建的函數。
```
#my_test.py
import my_debugger
debugger = my_debugger.debugger()
pid = raw_input("Enter the PID of the process to attach to: ")
debugger.attach(int(pid))
debugger.detach()
```
按以下的步驟進行測試(windows 下):
1. 選擇 開始->運行->所有程序->附件->計算器
2. 右擊桌面低端的任務欄,從退出的菜單中選擇任務管理器。
3. 選擇進程面板.
4. 如果你沒看到 PID 欄,選擇 查看->選擇列
5. 確保進程標識符(PID)前面的確認框是選中的,然后單擊 OK。
6. 找到 calc.exe 相關聯的 PID
7. 執行 my_test.py 同時前面找到的 PID 傳遞給它。
8. 當 Press a key to continue...打印在屏幕上的時候,試著操作計算器的界面。你應該什么鍵都 按不了。這是因為進程被調試器掛起來了,等待進一步的指示。
9. 在你的 Python 控制臺里按任何的鍵,腳本將輸出別的信息,熱愛后結束。
1. 現在你能夠操作計算器了。
如果一切都如描繪的一樣正常工作,把下面兩行從 my_debugger.py 中注釋掉:
```
# raw_input("Press any key to continue...")
# self.debugger_active = False
```
現在我們已經講解了獲取進程句柄的基礎知識,以及如何創建一個進程,附加一個運行 的進程,接下來讓我們給調試器加入更多高級的功能。
- 序
- 1 搭建開發環境
- 1.1 操作系統準備
- 1.2 獲取和安裝 Python2.5
- 1.3 配置 Eclipse 和 PyDev
- 2 調試器設計
- 2.1 通用 CPU 寄存器
- 2.2 棧
- 2.3 調試事件
- 2.4 斷點
- 3 自己動手寫一個 windows 調試器
- 3.2 獲得 CPU 寄存器狀態
- 3.3 實現調試事件處理
- 3.4 全能的斷點
- 4 PyDBG---純 PYTHON 調試器
- 4.1 擴展斷點處理
- 4.2 處理訪問違例
- 4.3 進程快照
- 5 IMMUNITY----最好的調試器
- 5.1 安裝 Immunity 調試器
- 5.2 Immunity Debugger 101
- 5.3 Exploit 開發
- 5.4 搞定反調試機制
- 6 HOOKING
- 6.1 用 PyDbg 實現 Soft Hooking
- 6.2 Hard Hooking
- 7 Dll 和代碼注入
- 7.1 創建遠線程
- 7.2 邪惡的代碼
- 8 FUZZING
- 8.1 Bug 的分類
- 8.2 File Fuzzer
- 8.3 改進你的 Fuzzer
- 9 SULLEY
- 9.1 安裝 Sulley
- 9.2 Sulley primitives
- 9.3 獵殺 WarFTPD
- 10 Fuzzing Windows 驅動
- 10.1 驅動通信
- 10.2 用 Immunity fuzzing 驅動
- 10.4 構建 Driver Fuzzer
- 11 IDAPYTHON --- IDA 腳本
- 11.1 安裝 IDAPython
- 11.2 IDAPython 函數
- 11.3 腳本例子
- 12 PyEmu
- 12.1 安裝 PyEmu
- 12.2 PyEmu 一覽
- 12.3 IDAPyEmu