## 8.2 File Fuzzer
File format vulnerabilitie 文件格式化漏洞已經漸漸的成為了客戶端攻擊的流行方式,而 我們最感興趣的就是找出文件格式化分析時出現的漏洞。無論面對的目標是殺毒軟件還是文 檔閱讀器,我們都希望測試庫盡可能的全,最好是包含說有的文件格式。同時還要確保,我 們的 fuzzer 能準確的捕捉到崩潰信息,然后自動化的決策出是否是可利用的漏洞。最后還要 加入 emailing 的功能,在我們有成千上萬的測試案例的時候,你不會想傻傻的做在機器前看 數據流吧!
現在開始寫代碼,第一步,構造創建一個類框架,用于簡單的文件選擇。
```
#file_fuzzer.py
from pydbg import *
from pydbg.defines
import * import utils
import random
import sys
import struct
import threading
import os
import shutil
import time
import getopt
class file_fuzzer:
def init (self, exe_path, ext, notify):
self.exe_path = exe_path
self.ext = ext
self.notify_crash = notify
self.orig_file = None
self.mutated_file = None
self.iteration = 0
self.exe_path = exe_path
self.orig_file = None
self.mutated_file = None
self.iteration = 0
self.crash = None
self.send_notify = False
self.pid = None
self.in_accessv_handler = False
self.dbg = None
self.running = False
self.ready = False
# Optional
self.smtpserver = 'mail.nostarch.com'
self.recipients = ['jms@bughunter.ca',]
self.sender = 'jms@bughunter.ca'
self.test_cases = [ "%s%n%s%n%s%n", "\xff", "\x00", "A" ]
def file_picker( self ):
file_list = os.listdir("examples/")
list_length = len(file_list)
file = file_list[random.randint(0, list_length-1)] shutil.copy("examples\\%s" % file,"test.%s" % self.ext)
return file
```
類框架定義了一些全局變量,用于跟蹤記錄文件的基礎信息,這些文件將會在變形后加 入測試例。file_picker 函數使用內建的 Python 函數列出目錄內的所有文件,然后隨機選取一 個進行變形。
接下來我們要做一些線程方面的工作:加載 目標程序,跟蹤崩潰信息,在文檔分析完成之后終止目標程序。第一步,將目標程序加載進 一個調試線程,并且安裝自定義的訪問違例處理代碼。第二步,創建第二個線程,用于監視 調試的線程,并且負責在一段長度的時間之后殺死調試線程。最后還得附加一段 email 提醒 的代碼。
```
#file_fuzzer.py
...
def fuzz( self ):
while 1:
if not self.running: #(1)
# We first snag a file for mutation
self.test_file = self.file_picker()
self.mutate_file()
# Start up the debugger thread
pydbg_thread = threading.Thread(target=self.start_debugger)
pydbg_thread.setDaemon(0)
pydbg_thread.start()
while self.pid == None:
time.sleep(1)
# Start up the monitoring thread
monitor_thread = threading.Thread (target=self.monitor_debugger)
monitor_thread.setDaemon(0)
monitor_thread.start()
else:
self.iteration += 1
time.sleep(1)
# Our primary debugger thread that the application
# runs under
def start_debugger(self):
print "[*] Starting debugger for iteration: %d" % self.iteration
self.running = True
self.dbg = pydbg()
self.dbg.set_callback(EXCEPTION_ACCESS_VIOLATION,self.check_accessv)
pid = self.dbg.load(self.exe_path,"test.%s" % self.ext)
self.pid = self.dbg.pid
self.dbg.run()
# Our access violation handler that traps the crash
# information and stores it
def check_accessv(self,dbg):
if dbg.dbg.u.Exception.dwFirstChance:
return DBG_CONTINUE
print "[*] Woot! Handling an access violation!"
self.in_accessv_handler = True
crash_bin = utils.crash_binning.crash_binning()
crash_bin.record_crash(dbg)
self.crash = crash_bin.crash_synopsis()
# Write out the crash informations
crash_fd = open("crashes\\crash-%d" % self.iteration,"w")
crash_fd.write(self.crash)
# Now back up the files
shutil.copy("test.%s" % self.ext,"crashes\\%d.%s" % (self.iteration,self.ext))
shutil.copy("examples\\%s" % self.test_file,"crashes\\%d_orig.%s" % (self.iteration,self.ext))
self.dbg.terminate_process() self.in_accessv_handler = False
self.running = False
return DBG_EXCEPTION_NOT_HANDLED
# This is our monitoring function that allows the application
# to run for a few seconds and then it terminates it
def monitor_debugger(self):
counter = 0
print "[*] Monitor thread for pid: %d waiting." % self.pid,
while counter < 3:
time.sleep(1)
print counter,
counter += 1
if self.in_accessv_handler != True:
time.sleep(1)
self.dbg.terminate_process()
self.pid = None
self.running = False
else:
print "[*] The access violation handler is doing its business. Waiting."
while self.running:
time.sleep(1)
# Our emailing routine to ship out crash information
def notify(self):
crash_message = "From:%s\r\n\r\nTo:\r\n\r\nIteration: %d\n\nOutput:\n\n %s" % (self.sender, self.iteration, self.crash)
session = smtplib.SMTP(smtpserver)
session.sendmail(sender, recipients, crash_message)
session.quit()
return
```
我們已經有了個比較完整的流程,能夠順利的完成 fuzz 了,讓我們簡單的看看各個函 數的作用。第一步,通過 self.running 確保當前只有一個調試線程在執行或者訪問違例的處 理程序沒有在搜集崩潰數據。第二步,我們把隨即選擇到文件,傳入變形函數,這個函數會 在稍后實現。
一旦文件變形完成,第三步,我們就創建一個調試線程,啟動目標程序,并將上面隨即選中的文件的路徑名字,作為命令行參數傳入。接著一個條件循環,等待目標進程的創建。 當程序創建成功的時候,得到新的 PID,第四步,創建一個監視進程,確保在一段事件以后 殺死調試的程序。監視線程創建成功以后,我們就增加統計標志,然后加入主循環,等待一 次 fuzz 的完成,繼續下一次 fuzz。現在讓我們增加一個簡單的變形函數。
```
#file_fuzzer.py
...
def mutate_file( self ):
# Pull the contents of the file into a buffer
fd = open("test.%s" % self.ext, "rb")
stream = fd.read()
fd.close()
# The fuzzing meat and potatoes, really simple
# Take a random test case and apply it to a random position
# in the file
test_case = self.test_cases[random.randint(0,len(self.test_cases)-1)]
stream_length = len(stream)
rand_offset = random.randint(0, stream_length - 1 )
rand_len = random.randint(1, 1000)
# Now take the test case and repeat it
test_case = test_case * rand_len
# Apply it to the buffer, we are just
# splicing in our fuzz data
fuzz_file = stream[0:rand_offset]
fuzz_file += str(test_case)
fuzz_file += stream[rand_offset:]
# Write out the file
fd = open("test.%s" % self.ext, "wb")
fd.write( fuzz_file )
fd.close()
return
```
這是一個基礎的變形函數。我們從全部測試用例中隨即的選取一個;然后同樣隨即的獲取一個文件位移和需要附加的 fuzz 數據的長度。用位移和長度信息生成附加的 fuzz 數據, 最后將原始數據分片,在其中加入 fuzz 數據。一切完成后,把新生成的文件覆蓋原來的文 件。緊接著就是調試線程開始新一輪的測試了。現在讓我們實現命令行處理部分。
```
#file_fuzzer.py
...
def print_usage():
print "[*]"
print "[*] file_fuzzer.py -e <Executable Path> -x <File Extension>"
print "[*]"
sys.exit(0)
if name == " main ":
print "[*] Generic File Fuzzer."
# This is the path to the document parser
# and the filename extension to use
try:
opts, argo = getopt.getopt(sys.argv[1:],"e:x:n")
except getopt.GetoptError:
print_usage()
exe_path = None
ext = None
notify = False
for o,a in opts:
if o == "-e":
exe_path = a
elif o == "-x":
ext = a
elif o == "-n":
notify = True
if exe_path is not None and ext is not None:
fuzzer = file_fuzzer( exe_path, ext, notify )
fuzzer.fuzz()
else:
print_usage()
```
現在我們的 file_fuzzer.py 腳本已經能夠接收到命令行參數了。-e 標志指示需要 fuzz 的 目標程序的路徑。-x 選項是我們需要用于測試的文件的擴展名;舉個例子.txt 就說明我們要 用文本文件作為測試數據。-n 選項告訴 fuzzer 是否要接收通知。
最好的測試 fuzzer 的方法,就是在測試目標程序的時候觀察數據的變形結果 。在 fuzz 文本文件的時候,用 Windows 記事本是再好不過的了。因為你能夠直接的看到每一次的數 據的變化,比用十六進制編輯器和二進制對比工具方便很多。在啟動 file_fuzzer.py 腳本之 前,需要在腳本當前目錄下新建兩個目錄 examples 和 crashes 。然后在 examples 目錄下存 放幾個以.txt 結尾的文件,接著使用如下命令啟動腳本。
```
python file_fuzzer.py -e C:\\WINDOWS\\system32\\notepad.exe -x .txt
```
隨著記事本的啟動,你能看到被變形過的文件。在對變形之后的數據滿意以后,你就可以使用這個 file fuzzer 測試別的程序了。
- 序
- 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