## 2.4 斷點
當我們需要讓被調試程序暫停的時候就需要用到斷點。通過暫停進程,我們能觀察 變量,堆棧參數以及內存數據,并且記錄他們。斷點有非常多的好處,當你調試進程的時候 這些功能會讓你覺得很舒爽。斷點主要分成三種:軟件斷點,硬件斷點,內存斷點。他們有 非常相似的工作方式,但實現的手段卻各不相同。
### 2.4.1 軟件斷點
軟件斷點具體而言就是在 CPU 執行到特定位置的代碼的時候使其暫停。軟件斷點 將會使你在調試過程中用的最多的斷點。軟件斷點的本質就是一個單字節的指令,用于暫停 被執行程序,并將控制權轉移給調試器的斷點處理函數。在搞明白它是如何工作之前你必須 先弄清楚在 x86 匯編里指令和操作碼的差別。
匯編指令是 CPU 執行的命令的高級表示方法。舉個例子:
```
MOV EAX, EBX
```
這個指令告訴 CPU 把存儲在 EBX 寄存器里的東西放到 EAX 寄存器里。相當簡單,不 是嗎?然而 CPU 根本不明白剛才的指令,它必須被轉化成一種叫做操作碼的東西。操作碼(opcode)就是 operation code,是 CPU 能理解并執行的語言。前面的匯編指令轉化成操作碼 就是下面這樣:
```
8BC3
```
如你說見,幕后正在進行的操作相當的令人困惑,但這確實是 CPU 的語言。你可 以把匯編指令想象成 CPU 們的 DNS(一種解析域名和 IP 的網絡服務)。你不用再一個個的 記憶復雜難懂的操作碼(類似 IP 地址),取而代之的是簡單的匯編的指令,最后這些指令都 會被匯編器轉換成操作碼。在日常的調試中你很少會用到操作碼,但是他們對于理解軟件斷 點的用途非常重要。
如果我們先前講解的指令發生在 0x4433221 這個地址,一般是這樣顯示的:
```
0x44332211: 8BC3 MOV EAX, EBX
```
這里顯示了地址,操作碼,和高級的匯編指令。為了在這個地址設置斷點,暫停 CPU, 我們將從 2 個字節的 8BC3 操作碼中換出一個單字節的操作碼。這個單字節的操作碼也就 是 3 號中斷指令(INT 3),一條能讓 CPU 暫停的指令。3 號中斷轉換成操作碼就是 0xCC。 這里是設置斷點前和設置斷點后的對比:
在斷點被設置前的操作碼
```
0x44332211: 8BC3 MOV EAX, EBX
```
斷點被設置后的操作碼
```
0x44332211: CCC3 MOV EAX, EBX
```
很明顯原操作碼中的 8B 被替換成了 CC。當 CPU 執行到這個操作碼的時候,CPU 暫停, 并觸發一個 INT3(3 號中斷)事件。調試器自身能處理這個事件,但是為了設計我們自己的調 試器,明白調試器是如何具體操作的很重要。當調試器被告知在目標地址設置一個斷點,它 首先讀取目標地址的第一個字節的操作碼,然后保存起來,同時把地址存儲在內部的中斷列 表中。接著,調試器把一個字節操作碼 CC 寫入剛才的地址。當 CPU 執行到 CC 操作碼的 時候就會觸發一個 INT3 中斷事件,此時調試器就能捕捉到這個事件。調試器繼續判斷這個 發生中斷事件的地址(通過 EIP 指針,指令指針)是不是自己先前設置斷點的地址。如果在調 試器內部的斷點列表中找到了這個地址,就將設置斷點前存儲起來的操作碼寫回到目標地 址,這樣進程被調試器恢復后就能正常的執行。圖 2-3 對此進行了詳細的描繪。

圖 2-3:軟件斷點的處理過程
有兩種類型的軟件斷點可以被設置:一次性斷點和持續性斷點。一次性斷點意味著,一 旦斷點被觸發(命中)一次,它就會從內部中斷列表清除掉。一個持久性斷點在 CPU 觸發 后會重新存儲在內部的斷點列表里,以后每次運行到這里還會中斷。
然而軟件斷點有一個問題:當你改變了被調試程序的內存數據的時候,你同時改變了運 行時的軟件的循環冗余碼校驗合(CRC)。CRC 是一種校驗數據是否被改變的函數,它被廣 泛的應用于文件,內存,文本,網絡數據包和任何你想監視數據改變的地方。 CRC 將一定 范圍內的數據進行 hash(散列)計算,在逆向工程中一般是對進程的內存數據進行運算, 然后將 hash 值和此前原始的 hash 值進行比較,以判斷數據是否被改變。如果不同說明數據 被改動了,校驗失敗。這點很重要,因為病毒程序經常檢測程序在內存中運行的代碼的 CRC 值是否相同,不同說明數據被修改,則自動殺死自己。為了在這種特殊的情況下也能正常的 進行調試工作,就要使用硬件斷點了。
### 2.4.2 硬件斷點
硬件斷點非常有用,尤其是當想在一小塊區域內設置斷點,但是又不能修改它們的時候。
這種類型的斷點被設置在 CPU 級別,并用特定的寄存器:調試寄存器。一個 CPU 一般會有 8 個調試寄存器(DR0 寄存器到 DR7 寄存器),它們被用于管理硬件斷點。調試寄存器 DR0 到調試寄存器 DR3 存儲硬件斷點地址。這意味著你同一時間內最多只能有 4 個硬件斷點。 DR4 和 DR5 保留。DR6 是狀態寄存器,說明了被斷點觸發的調試事件的類型。DR7 本質上 是一個硬件斷點的開關寄存器,同時也存儲了斷點的不同類型。通過在 DR7 寄存器里設置 不同標志,能夠創建以下幾種斷點:
+ 當特定的地址上有指令執行的時候中斷
+ 當特定的地址上有數據可以寫入的時候
+ 當特定的地址上有數據讀或者寫但不執行的時候 這非常有用,當你要設置特定的斷點(至多 4 個),又不能修改運行的進程的時候。
圖 2-4 顯示了與硬件斷點的狀態,長度和地址相關的字段。


圖 2-4:DR7 寄存器決定了斷點的類型
0-7 位是硬件斷點的激活與關閉開關。在這七位中 L 和 G 字段是局部和全局作用域的 標志。我把兩個位都設置了,以我的經驗用戶模式的調試中只設置一個就能工作。 8-25 位 在我們一般的調試中用不到,在 x86 的手冊上你可以找到關于這些字節的詳細解釋。16-31 位決定了設置在 4 個斷點寄存器中硬件斷點的類型與長度。
和軟件斷點不同,硬件斷點不是用 INT3 中斷,而是用 INT1(1 號中斷).INT1 負責硬件 中斷和步進事件。步進( Single-step )意味著一步一步的執行指令,從而精確的觀察關鍵 代碼以便監視數據的變化。在 CPU 每次執行代碼之前,都會先確認當前將執行的代碼的地 址是否是硬件斷點的地址,同時也要確認是否有代碼要訪問被設置了硬件斷點的內存區域。 如果任何儲存在 DR0-DR3 中的地址所指向的區域被訪問了,就會觸發 INT1 中斷,同時暫 停 CPU。如果沒有,CPU 執行代碼,到下一行代碼時,CPU 繼續重復上面的檢查。
硬件斷點極其有用,但是也有一些限制。一方面你同一時間只能設置四個斷點,另 一方面斷點起作用的區域只有 4 個字節(也就是檢測 4 個字節的內存數據改變)。如果你想 跟蹤一大塊內存數據,就辦不到了。為了解決這個問題,你就要用到內存斷點。
### 2.4.3 內存斷點
內存斷點其實不是真正的斷點。當一個調試器設置了一個內存斷點的時候,它其實是 改變了內存中某個塊或者頁的權限。一個內存頁是操作系統處理的最小的內存單位。一個內 存頁被申請成功以后,就擁有了一個權限集,它決定了內存該如何被訪問。下面是一些內存 頁的訪問權限的例子:
+ 可執行頁 允許執行但不允許讀或寫,否則拋出訪問異常
+ 可讀頁 只允許從頁面中讀取數據,其余的則拋出訪問異常
+ 可寫頁 允許將數據寫入頁面
任何對保護頁的訪問都會引發異常,之后頁面恢復訪問前的狀態
大多數系統允許你綜合這些權限。舉個例子,你能有在內存中創建一個頁面,既能 讀又能寫,同時另一個頁面既能讀又能執行。每一個操作系統都有內建的函數讓你查詢當前 內存頁(并不是所有的)的權限,并且修改它們。參考圖 2-5 觀察不同權限的內存頁面數據 是如何訪問的。

圖 2-5: 各種不同權限的內存頁 這里我們感興趣的是保護頁(Guard Page) 。這種類型的頁面常被用于:分離堆和棧或者
確保一部分內存數據不會增長出邊界。另一種情況,就是當一個特定的內存塊被進程命中(訪 問)了,就暫停進程。舉個例子,如果我們在逆向一個網絡服務程序,在其接收到網絡數據 包以后,我們在存儲數據包的內存上設置保護頁,接著運行程序,一旦有任何對保護頁的訪 問,都會使 CPU 暫停,拋出一個保護頁調試異常,這時候我們就能確定程序是在什么時候 用什么方式訪問接收到的數據了。之后再進一步跟蹤觀察訪問內存的指令,繼而確定程序對數據做了什么操作。這種斷點同時也解決了軟件斷點數據更新的問題,因為我們沒有修改任 何運行著的代碼。
到目前為止,我們已經講解完了調試器的基礎知識和工作原理,接下來我們要親自動手 寫一個 Python 調試器,這個基于 Windows 的輕量級調試器,將會用到我們目前學到的所有 知識。
- 序
- 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