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 = ['[email protected]',]
self.sender = '[email protected]'
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 测试别的程序了。