## 3.2獲得 CPU 寄存器狀態
一個調試器必須能夠在任何時候都搜集到 CPU 的各個寄存器的狀態。當異常發生的時 候這能讓我們確定棧的狀態,目前正在執行的指令是什么,以及其他一些非常有用的信息。 要實現這個目的,首先要獲取被調試目標內部的線程句柄,這個功能由 OpenThread()實現. 函數原型如下:
```
HANDLE WINAPI OpenThread(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwThreadId
);
```
這看起來非常像 OpenProcess()的姐妹函數,除了這次是用線程標識符(thread identifier TID) 提到了進程標識符(PID)。
我們必須先獲得一個執行著的程序內部所有線程的一個列表,然后選擇我們想要的,再 用 OpenThread() 獲 取 它 的 句 柄 。 讓 我 研 究 下 如 何 在 一 個 系 統 里 枚 舉 線 程 ( enumerate threads)。
### 3.2.1 枚舉線程
為了得到一個進程里寄存器的狀態,我們必須枚舉進程內部所有正在運行的線程。線程 是進程中真正的執行體(大部分活都是線程干的),即使一個程序不是多線程的,它也至少 有一個線程,主線程。實現這一功能的是一個強大的函數 CreateToolhelp32Snapshot(),它由 kernel32.dll 導出。這個函數能枚舉出一個進程內部所有線程的列表,以加載的模塊(DLLs) 的列表,以及進程所擁有的堆的列表。函數原型如下:
```
HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
```
dwFlags 參數標志了我們需要收集的數據類型(線程,進程,模塊,或者堆 )。這里我 們把它設置成 TH32CS_SNAPTHREAD,也就是 0x00000004,表示我們要搜集快照 snapshot 中 所 有 已 經 注 冊 了 的 線 程 。 th32ProcessID 傳 入 我 們 要 快 照 的 進 程 , 不 過 它 只 對 TH32CS_SNAPMODULE, TH32CS_SNAPMODULE32, TH32CS_SNAPHEAPLIST, and TH32CS_SNAPALL 這幾個模塊有用,對 TH32CS_SNAPTHREAD 可是沒什么用的哦(后面 有說明)。當 CreateToolhelp32Snapshot()調用成功,就會返回一個快照對象的句柄,被接下 來的函數調以便搜集更多的數據。
一旦我們從快照中獲得了線程的列表,我們就能用 Thread32First()枚舉它們了。函數原型如下:
```
BOOL WINAPI Thread32First(
HANDLE hSnapshot,
LPTHREADENTRY32 lpte
);
```
hSnapshot 就 是 上 面 通 過 CreateToolhelp32Snapshot() 獲 得 鏡 像 句 柄 , lpte 指 向 一 個 THREADENTRY32 結構(必須初始化過)。這個結構在 Thread32First()在調用成功后自動填 充,其中包含了被發現的第一個線程的相關信息。結構定義如下:
```
typedef struct THREADENTRY32{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ThreadID;
DWORD th32OwnerProcessID;
LONG tpBasePri;
LONG tpDeltaPri;
DWORD dwFlags;
};
```
在這個結構中我們感興趣的是 dwSize, th32ThreadID, 和 th32OwnerProcessID 3 個參數。 dwSize 必須在 Thread32First()調用之前初始化,只要把值設置成 THREADENTRY32 結構的 大小就可以了。th32ThreadID 是我們當前發現的這個線程的 TID,這個參數可以被前面說過 的 OpenThread() 函數調用以打開此線程,進行別的操作。 th32OwnerProcessID 填充了當前 線 程 所 屬 進 程 的 PID 。 為 了 確 定 線 程 是 否 屬 于 我 們 調 試 的 目 標 進 程 , 需 要 將 th32OwnerProcessID 的值和目標進程對比,相等則說明這個線程是我們正在調試的。一旦我 們獲得了第一個線程的信息,我們就能通過調用 Thread32Next()獲取快照中的下一個線程條 目。它的參數和 Thread32First()一樣。循環調用 Thread32Next()直到列表的末端。
### 3.2.2 把所有的組合起來
現在我們已經獲得了一個線程的有效句柄,最后一步就是獲取所有寄存器的值。這就需 要通過 GetThreadContext()來實現。同樣我們也能用 SetThreadContext()改變它們。
```
BOOL WINAPI GetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
BOOL WINAPI SetThreadContext(
HANDLE hThread,
LPCONTEXT lpContext
);
```
hThread 參數是從 OpenThread() 返回的線程句柄,lpContext 指向一個 CONTEXT 結構, 其中存儲了所有寄存器的值。CONTEXT 非常重要,定義如下:
```
typedef struct CONTEXT {
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
FLOATING_SAVE_AREA FloatSave;
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegCs;
DWORD EFlags;
DWORD Esp;
DWORD SegSs;
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
};
```
如你說見所有的寄存器都在這個列表中了,包括調試寄存器和段寄存器。在我們剩下的 工作中,將大量的使用到這個結構,所以盡快的實習起來。
讓我們回來看看我們的老朋友 my_debugger.py 繼續擴展它,增加枚舉線程和獲取寄存 器的功能。
```
#my_debugger.py
class debugger():
...
def open_thread (self, thread_id):
h_thread = kernel32.OpenThread(THREAD_ALL_ACCESS, None,
thread_id)
if h_thread is not None:
return h_thread
else:
print "[*] Could not obtain a valid thread handle."
return False
def enumerate_threads(self):
thread_entry = THREADENTRY32()
36 Chapter 3
thread_list = []
snapshot = kernel32.CreateToolhelp32Snapshot(TH32CS
_SNAPTHREAD, self.pid)
if snapshot is not None:
# You have to set the size of the struct
# or the call will fail
thread_entry.dwSize = sizeof(thread_entry) success = kernel32.Thread32First(snapshot,
byref(thread_entry))
byref(thread_entry))
while success:
if thread_entry.th32OwnerProcessID == self.pid:
thread_list.append(thread_entry.th32ThreadID)
success = kernel32.Thread32Next(snapshot,
kernel32.CloseHandle(snapshot) return thread_list
else:
return False
def get_thread_context (self, thread_id):
context = CONTEXT()
context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS
# Obtain a handle to the thread
h_thread = self.open_thread(thread_id)
if kernel32.GetThreadContext(h_thread, byref(context)):
kernel32.CloseHandle(h_thread)
return context
else:
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))
list = debugger.enumerate_threads()
# For each thread in the list we want to
# grab the value of each of the registers Building a Windows Debugger 37
for thread in list:
thread_context = debugger.get_thread_context(thread)
# Now let's output the contents of some of the registers
print "[*] Dumping registers for thread ID: 0x%08x" % thread
print "[**] EIP: 0x%08x" % thread_context.Eip
print "[**] ESP: 0x%08x" % thread_context.Esp
print "[**] EBP: 0x%08x" % thread_context.Ebp
print "[**] EAX: 0x%08x" % thread_context.Eax
print "[**] EBX: 0x%08x" % thread_context.Ebx
print "[**] ECX: 0x%08x" % thread_context.Ecx
print "[**] EDX: 0x%08x" % thread_context.Edx
print "[*] END DUMP"
debugger.detach()
```
當你運行測試代碼,你將看到如清單 3-1 顯示的數據。
```
Enter the PID of the process to attach to: 4028
[*] Dumping registers for thread ID: 0x00000550
[**] EIP: 0x7c90eb94
[**] ESP: 0x0007fde0
[**] EBP: 0x0007fdfc
[**] EAX: 0x006ee208
[**] EBX: 0x00000000
[**] ECX: 0x0007fdd8
[**] EDX: 0x7c90eb94
[*] END DUMP
[*] Dumping registers for thread ID: 0x000005c0
[**] EIP: 0x7c95077b
[**] ESP: 0x0094fff8
[**] EBP: 0x00000000
[**] EAX: 0x00000000
[**] EBX: 0x00000001
[**] ECX: 0x00000002
[**] EDX: 0x00000003
[*] END DUMP
[*] Finished debugging. Exiting...
```
Listing 3-1:每個線程的 CPU 寄存器值
太酷了 ! 我們現在能夠在任何時候查詢所有寄存器的狀態了。試驗下不同的進程 ,看看能得到什么結果。到此為止我們已經完成了我們調試器的核心部分,是時間實現一些基礎 調試事件的處理函數了。
- 序
- 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