## 4.3 進程快照
PyDbg 提供了一個非常酷的功能,進程快照。使用進程快照的時候,我們就能夠冰凍進 程,獲取進程的內存數據。以后我們想要讓進程回到這個時刻的狀態,只要使用這個時刻的 快照就行了。
### 4.3.1 獲得進程快照
第一步,在一個準確的時間獲得一份目標進程的精確快照。為了使得快照足夠精確,需 要得到所有線程以及 CPU 上下文,還有進程的整個內存。將這些數據存儲起來,下次我們 需要恢復快照的時候就能用的到。
為了防止在獲取快照的時候,進程的數據或者狀態被修改,需要將進程掛起來,這個任 務由 suspend_all_threads()完成。掛起進程之后,可以用 process_snapshot()獲取快照。快照完 成之后,用 resume_all_threads()恢復掛起的進程,讓程序繼續執行。當某個時刻我們需要將 進程恢復到從前的狀態,簡單的 process_restore()就行了。這看起來是不是太簡單了?
現在新建個 snapshot.py 試驗下,代碼的功能就是我們輸入"snap"的時候創建一個快照, 輸入"restore"的時候將進程恢復到快照時的狀態。
```
#snapshot.py
from pydbg import *
from pydbg.defines import *
import threading
import time
import sys
class snapshotter(object):
def init (self,exe_path):
self.exe_path = exe_path
self.pid = None
self.dbg = None
self.running = True
# Start the debugger thread, and loop until it sets the PID
# of our target process
pydbg_thread = threading.Thread(target=self.start_debugger) pydbg_thread.setDaemon(0)
pydbg_thread.start()
while self.pid == None:
time.sleep(1)
# We now have a PID and the target is running; let's get a
# second thread running to do the snapshots
monitor_thread = threading.Thread(target=self.monitor_debugger) monitor_thread.setDaemon(0)
monitor_thread.start()
def monitor_debugger(self):
while self.running == True:
input = raw_input("Enter: 'snap','restore' or 'quit'")
input = input.lower().strip()
if input == "quit":
print "[*] Exiting the snapshotter."
self.running = False self.dbg.terminate_process()
elif input == "snap":
print "[*] Suspending all threads." self.dbg.suspend_all_threads()
print "[*] Obtaining snapshot."
self.dbg.process_snapshot()
print "[*] Resuming operation."
self.dbg.resume_all_threads()
elif input == "restore":
print "[*] Suspending all threads." self.dbg.suspend_all_threads()
print "[*] Restoring snapshot."
self.dbg.process_restore()
print "[*] Resuming operation."
self.dbg.resume_all_threads()
def start_debugger(self): self.dbg = pydbg()
pid = self.dbg.load(self.exe_path)
self.pid = self.dbg.pid
self.dbg.run()
exe_path = "C:\\WINDOWS\\System32\\calc.exe"
snapshotter(exe_path)
```
那么第一步就是在調試器內部創建一個新線程,并用此啟動目標進程。通過使用分開的線程,就能將被調試的進程和調試器的操作分開,這樣我們輸入不同的快照命令進行操作的 時候,就不用強迫被調試進程暫停。當創建新線程的代碼返回了有效的 PID,我們就創建另 一個線程,接受我們輸入的調試命令。之后這個線程根據我們輸入的命令決定不同的操作(快 照,恢復快照,結束程序)。
我們之所以選擇計算器作為例子,是因為通過操作圖形界面 ,可以更清晰的看到,快 照的作用。先在計算器里輸入一些數據,然后在終端里輸入"snap"進行快照,之后再在計算器 里進行別的操作。最后就當的輸入"restore",你將看到,計算器回到了最初時快照的狀態。 使用這種方法我們能夠將進程恢復到任意我們希望的狀態。
現在讓我們將所有的新學的 PyDbg 知識,創建一個 fuzz 輔助工具,幫助我們找到軟件 的漏洞,并自動處理奔潰事件。
### 4.3.2 組合代碼
我們已經介紹了一些 PyDbg 非常有用的功能,接下來要構建一個工具用來根除應用程 序中出現的可利用的漏洞。在我們平常的開發過程中,有些函數是非常危險的,很容易造成 緩沖區溢出,字符串問題,以及內存出錯,對這些函數需要重點關注。
工具將定位于危險函數,并跟蹤它們的調用。當我們認為函數被危險調用了,就將 4 堆棧中的 4 個參數接觸引用,彈出棧,并且在函數產生溢出之前對進程快照。如果這次訪問 違例了,我們的腳本將把進程恢復到,函數被調用之前的快照。并從這開始,單步執行,同 時 反 匯 編 每 個 執 行 的 代 碼 , 直 到 我 們 也 拋 出 了 訪 問 違 例 , 或 者 執 行 完 了 MAX_INSTRUCTIONS(我們要監視的代碼數量)。無論什么時候當你看到一個危險的函數 在處理你輸入的數據的時候,嘗試操作數據 crash 數據都似乎值得。這是創造出我們的漏洞 利用程序的第一步。
開動代碼,建立 danger_track.py,輸入下面的代碼。
```
#danger_track.py
from pydbg import *
from pydbg.defines import *
import utils
# This is the maximum number of instructions we will log
# after an access violation MAX_INSTRUCTIONS = 10
# This is far from an exhaustive list; add more for bonus points dangerous_functions = {
"strcpy" : "msvcrt.dll",
"strncpy" : "msvcrt.dll",
"sprintf" : "msvcrt.dll", "vsprintf": "msvcrt.dll"
}
dangerous_functions_resolved = {}
crash_encountered = False
instruction_count = 0
def danger_handler(dbg):
# We want to print out the contents of the stack; that's about it
# Generally there are only going to be a few parameters, so we will
# take everything from ESP to ESP+20, which should give us enough
# information to determine if we own any of the data esp_offset = 0
print "[*] Hit %s" % dangerous_functions_resolved[dbg.context.Eip]
print "================================================================="
while esp_offset <= 20:
parameter = dbg.smart_dereference(dbg.context.Esp + esp_offset)
print "[ESP + %d] => %s" % (esp_offset, parameter)
esp_offset += 4
print "=================================================================\n
dbg.suspend_all_threads()
dbg.process_snapshot()
dbg.resume_all_threads()
return DBG_CONTINUE
def access_violation_handler(dbg):
global crash_encountered
# Something bad happened, which means something good happened :)
# Let's handle the access violation and then restore the process
# back to the last dangerous function that was called
if dbg.dbg.u.Exception.dwFirstChance:
return DBG_EXCEPTION_NOT_HANDLED
crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(dbg)
print crash_bin.crash_synopsis()
if crash_encountered == False:
dbg.suspend_all_threads()
dbg.process_restore()
crash_encountered = True
# We flag each thread to single step
for thread_id in dbg.enumerate_threads():
print "[*] Setting single step for thread: 0x%08x" % thread_id
h_thread = dbg.open_thread(thread_id)
dbg.single_step(True, h_thread)
dbg.close_handle(h_thread)
# Now resume execution, which will pass control to our
# single step handler
dbg.resume_all_threads()
return DBG_CONTINUE
else:
dbg.terminate_process()
return DBG_EXCEPTION_NOT_HANDLED
def single_step_handler(dbg):
global instruction_count
global crash_encountered
if crash_encountered:
if instruction_count == MAX_INSTRUCTIONS:
dbg.single_step(False)
return DBG_CONTINUE
else:
# Disassemble this instruction
instruction = dbg.disasm(dbg.context.Eip)
print "#%d\t0x%08x : %s" % (instruction_count,dbg.context.Eip, instruction)
instruction_count += 1
dbg.single_step(True)
return DBG_CONTINUE
dbg = pydbg()
pid = int(raw_input("Enter the PID you wish to monitor: "))
dbg.attach(pid)
# Track down all of the dangerous functions and set breakpoints
for func in dangerous_functions.keys():
func_address = dbg.func_resolve( dangerous_functions[func],func )
print "[*] Resolved breakpoint: %s -> 0x%08x" % ( func, func_address )
dbg.bp_set( func_address, handler = danger_handler )
dangerous_functions_resolved[func_address] = func
dbg.set_callback( EXCEPTION_ACCESS_VIOLATION, access_violation_handler )
dbg.set_callback( EXCEPTION_SINGLE_STEP, single_step_handler )
dbg.run()
```
通過之前對 PyDbg 的諸多講解,這段代碼應該看起來不那么難了吧。測試這個腳本的 最好方法,就是運行一個有漏洞價格的程序,然后讓腳本附加到進程,和程序交互,嘗試 crash 程序。
我們已經對 PyDbg 有了一定的了解,不過這只是它強大功能的一部分,還有更多的東 西,需要你自己去挖掘。再好的東西也滿足不了那些"懶惰"的 hacker。PyDbg 固然強大,方 便的擴展,自動化調試。不過每次要完成任務的時候,都要自己動手編寫代碼。接下來介紹 的 Immunity Debugger 彌補了這點,完美的結合了圖形化調試和腳本調試。它能讓你更懶, 哈。讓我們繼續。
- 序
- 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