再把Driller看一下,主要参考Driller工具分析,源码有更新。
1.Driller介绍
Driller是基于fuzz工具AFL和符号执行工具angr来实现的,当模糊程序卡住时调用符号执行来求解能够到达新路径的输入,使得fuzz能够快速突破条件判断语句。具体技术请参考文末Driller和angr的论文链接。
具体实现是,通过监测AFL的执行,我们可以决定什么时候开始符号执行以探索新路径。如果AFL执行了x轮后,bitmap上显示没有发现新的状态转换(也即新的代码块转移),说明AFL卡住了,这时候调用angr进行符号执行。每个具体输入对应于PathGroup中的单个路径, 在PathGroup的每一步中,检查每个分支以确保最新的跳转指令引入先前AFL未知的路径。 当发现这样的跳转时,SMT求解器被查询以创建一个输入来驱动执行到新的跳转。这个输入反馈给AFL,AFL在未来的模糊步骤中进行变异。 这个反馈循环使我们能够将昂贵的符号执行时间与廉价的模糊时间进行平衡,并且减轻了模糊对程序操作的低语义洞察力。
2.使用方法
安装教程参考Driller安装教程,也可直接使用docker更方便—shellphish/mechaphish。
(1)命令行
官方推荐的driller的使用方法是通过shellphuzz工具来使用,使用方式如下,“-i”选项指定afl-fuzz的线程数,“-d”选项指定driller(即符号执行工具)的线程数,如果不使用-d或者-d 0,则不使用符号执行。
# fuzz with 4 AFL cores
$ shellphuzz -i -c 4 /path/to/binary
# perform symbolic-assisted fuzzing with 4 AFL cores and 2 symbolic tracing (drilling) cores.
$ shellphuzz -i -c 4 -d 2 /path/to/binary
(2)代码调用
方法一:直接调用Fuzzer对象(包含了fuzz和angr)
#change from /fuzzer/shellphuzz.py
import driller
import time
import fuzzer
def test():
def robo_fuzzer():
"""fuzz a single cb,copy it from shellphuzz"""
work_path = './work'
print "[*] Drilling..."
drill_extension = driller.LocalCallback(num_workers=4)
grease_extension = None
# Timeout=1800
first_crash = True
stuck_callback = (
(lambda f: (grease_extension(f), drill_extension(f))) if drill_extension and grease_extension
else drill_extension or grease_extension
)
print "[*] Creating fuzzer..."
fuzz = fuzzer.Fuzzer(
"./20190529", "./work", afl_count=1, force_interval=None,
create_dictionary=False, stuck_callback=stuck_callback, time_limit=1000000
)
# start it!
print "[*] Starting fuzzer..."
fuzz.start()
start_time = time.time()
robo_fuzzer()
test()
方法二:Driller和AFL并行运行的过程(需安装AFL并提供接口),将driller中求解输入的部分和AFL fuzz的部分分别放在两个terminal运行 。
#terminal 1:用AFL进行fuzz
mkdir -p workdir/input
echo 'init' > workdir/input/seed1 # 提供初始化种子输入
echo core | sudo tee /proc/sys/kernel/core_pattern
afl-2.52b/afl-fuzz -M fuzzer-master -i workdir/input/ -o workdir/output/ -Q ./buggy
提供了一个运行脚本run_driller.py
#改编自/driller/driller/local_callback.py
#!/usr/bin/env python
import errno
import os
import os.path
import sys
import time
from driller import Driller
def save_input(content, dest_dir, count):
"""Saves a new input to a file where AFL can find it.
File will be named id:XXXXXX,driller (where XXXXXX is the current value of
count) and placed in dest_dir.
"""
name = 'id:%06d,driller' % count
with open(os.path.join(dest_dir, name), 'w') as destfile:
destfile.write(content)
def main():
if len(sys.argv) != 3:
print 'Usage: %s <binary> <fuzzer_output_dir>' % sys.argv[0]
sys.exit(1)
_, binary, fuzzer_dir = sys.argv
# Figure out directories and inputs
with open(os.path.join(fuzzer_dir, 'fuzz_bitmap')) as bitmap_file:
fuzzer_bitmap = bitmap_file.read()
source_dir = os.path.join(fuzzer_dir, 'queue')
dest_dir = os.path.join(fuzzer_dir, '..', 'driller', 'queue')
# Make sure destination exists
try:
os.makedirs(dest_dir)
except os.error as e:
if e.errno != errno.EEXIST:
raise
seen = set() # Keeps track of source files already drilled
count = len(os.listdir(dest_dir)) # Helps us name outputs correctly
# Repeat forever in case AFL finds something new
while True:
# Go through all of the files AFL has generated, but only once each
for source_name in os.listdir(source_dir):
if source_name in seen or not source_name.startswith('id:'):
continue
seen.add(source_name)
with open(os.path.join(source_dir, source_name)) as seedfile:
seed = seedfile.read()
#就是把卡住的种子和bitmap输入给driller,然后生成能执行新路径的输入,写到种子队列中去。
print 'Drilling input: %s' % seed
for _, new_input in Driller(binary, seed, fuzzer_bitmap).drill_generator():
save_input(new_input, dest_dir, count)
count += 1
# Try a larger input too because Driller won't do it for you
seed = seed + '0000'
print 'Drilling input: %s' % seed
for _, new_input in Driller(binary, seed, fuzzer_bitmap).drill_generator():
save_input(new_input, dest_dir, count)
count += 1
time.sleep(10)
if __name__ == '__main__':
main()
#terminal 2:调用driller
source venv/bin/activate
python run_driller.py ./buggy workdir/output/fuzzer-master
3.源码分析
这里主要分析shellphuzz、driller、afl部分的代码,主要解释3个工具如何一起工作,符号执行的时机是什么时候等。
(1) shellphuzz
shellphuzz工具是AFL工具的一层Python接口,可以看做afl的python封装。shellphuzz支持启动afl、添加slave worker、注入或删除测试case,检查性能数据,使用符号执行等。
shellphuzz启动流程:
#/fuzzer/shellphuzz.py
if args.driller_workers:
print "[*] Drilling..."
drill_extension = driller.LocalCallback(num_workers=args.driller_workers)
stuck_callback = (
(lambda f: (grease_extension(f), drill_extension(f))) if drill_extension and grease_extension
else drill_extension or grease_extension
)
print "[*] Creating fuzzer..."
fuzzer = fuzzer.Fuzzer(
args.binary, args.work_dir, afl_count=args.afl_cores, force_interval=args.force_interval,
create_dictionary=not args.no_dictionary, stuck_callback=stuck_callback, time_limit=args.timeout
)
# start it!
print "[*] Starting fuzzer..."
fuzzer.start()
start_time = time.time()
shellphuzz开始运行后,如果加了-d选项,会注册一个指向driller模块的callback;然后实例化一个Fuzzer类的对象,然后启动fuzzer。
#/fuzzer/fuzzer/fuzzer.py
def start(self):
'''
start fuzzing
'''
# spin up the AFL workers
self._start_afl() # 根据参数启动多个afl线程
# start the callback timer
self._timer.start() # 启动一个InfiniteTimer类的对象,会周期性的调用stuck_callback,即driller的callback。
(2)Driller
- driller_callback:
如上所述,shellphuzz通过计时器,周期性的调用driller的callback方法。该方法的代码如下:
#/driller/driller/local_callback.py:
def driller_callback(self, fuzz):
l.warning("Driller stuck callback triggered!")
# remove any workers that aren't running
self._running_workers = [x for x in self._running_workers if x.is_alive()]
# get the files in queue
queue = self._queue_files(fuzz)
#for i in range(1, fuzz.fuzz_id):
# fname = "fuzzer-%d" % i
# queue.extend(self.queue_files(fname))
# start drilling
not_drilled = set(queue) - self._already_drilled_inputs
if len(not_drilled) == 0:
l.warning("no inputs left to drill")
while len(self._running_workers) < self._num_workers and len(not_drilled) > 0:
to_drill_path = list(not_drilled)[0]
not_drilled.remove(to_drill_path)
self._already_drilled_inputs.add(to_drill_path)
proc = multiprocessing.Process(target=_run_drill, args=(self, fuzz, to_drill_path))
proc.start()
self._running_workers.append(proc)
__call__ = driller_callback
对队列queue中没有经过符号执行的输入文件进行符号执行,同一时间在运行的符号执行器的个数不超过-d选项指定的个数。
该方法最后调用_run_driller方法来启动driller。
- _run_driller:
_run_driller方法提取了driller所需要的参数,并调用main来真正启动driller
#/driller/driller/local_callback.py:
def _run_drill(drill, fuzz, _path_to_input_to_drill):
_binary_path = fuzz.binary_path
_fuzzer_out_dir = fuzz.out_dir
_bitmap_path = os.path.join(_fuzzer_out_dir, 'fuzzer-master', "fuzz_bitmap")
_timeout = drill._worker_timeout
l.warning("starting drilling of %s, %s", os.path.basename(_binary_path), os.path.basename(_path_to_input_to_drill))
args = (
"timeout", "-k", str(_timeout+10), str(_timeout),
sys.executable, os.path.abspath(__file__),
_binary_path, _fuzzer_out_dir, _bitmap_path, _path_to_input_to_drill
)
p = subprocess.Popen(args, stdout=subprocess.PIPE)
print p.communicate()
- main:
main的基本流程为(这里可以学习学习):
- 设置参数,打开文件
- 初始化driller对象
- 通过driller的drill_generator方法进行符号执行,并生成满足afl需求的新输入文件
- 保存能执行新路径的输入,存入driller_queue_dir目录
#/driller/driller/local_callback.py:
# this is for running with bash timeout
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Driller local callback")
parser.add_argument('binary_path')
parser.add_argument('fuzzer_out_dir')
parser.add_argument('bitmap_path')
parser.add_argument('path_to_input_to_drill')
parser.add_argument('--length-extension', help="Try extending inputs to driller by this many bytes", type=int)
args = parser.parse_args()
logcfg_file = os.path.join(os.getcwd(), '.driller.ini')
if os.path.isfile(logcfg_file):
logging.config.fileConfig(logcfg_file)
binary_path, fuzzer_out_dir, bitmap_path, path_to_input_to_drill = sys.argv[1:5]
fuzzer_bitmap = open(args.bitmap_path, "rb").read()
# create a folder
driller_dir = os.path.join(args.fuzzer_out_dir, "driller")
driller_queue_dir = os.path.join(driller_dir, "queue")
try: os.mkdir(driller_dir)
except OSError: pass
try: os.mkdir(driller_queue_dir)
except OSError: pass
l.debug('drilling %s', path_to_input_to_drill)
# get the input
inputs_to_drill = [open(args.path_to_input_to_drill, "rb").read()]
if args.length_extension:
inputs_to_drill.append(inputs_to_drill[0] + b'\0' * args.length_extension)
for input_to_drill in inputs_to_drill:
d = driller.Driller(args.binary_path, input_to_drill, fuzzer_bitmap)
count = 0
for new_input in d.drill_generator():
id_num = len(os.listdir(driller_queue_dir))
fuzzer_from = args.path_to_input_to_drill.split("sync/")[1].split("/")[0] + args.path_to_input_to_drill.split("id:")[1].split(",")[0]
filepath = "id:" + ("%d" % id_num).rjust(6, "0") + ",from:" + fuzzer_from
filepath = os.path.join(driller_queue_dir, filepath)
with open(filepath, "wb") as f:
f.write(new_input[1])
count += 1
l.warning("found %d new inputs", count)
关键代码在/driller/driller/driller_main.py中。
drill_generator:
生成满足需求的文件的driller接口。最终调用_drill_input这个方法真正的去实现符号执行。_drill_input:
沿着一个指定的trace流一步步进行符号执行(angr引擎,采用driller_core技术),如果发现新的路径则记录下来。
#/driller/driller/driller_main.py
def _drill_input(self):
"""
Symbolically step down a path with a tracer, trying to concretize inputs for unencountered
state transitions.
"""
# initialize the tracer
r = tracer.qemu_runner.QEMURunner(self.binary, self.input, argv=self.argv)
p = angr.Project(self.binary)
for addr, proc in self._hooks.items():
p.hook(addr, proc)
l.debug("Hooking %#x -> %s...", addr, proc.display_name)
if p.loader.main_object.os == 'cgc':
p.simos.syscall_library.update(angr.SIM_LIBRARIES['cgcabi_tracer'])
s = p.factory.entry_state(stdin=angr.SimFileStream, flag_page=r.magic)
else:
s = p.factory.full_init_state(stdin=angr.SimFileStream)
s.preconstrainer.preconstrain_file(self.input, s.posix.stdin, True)
simgr = p.factory.simulation_manager(s, save_unsat=True, hierarchy=False, save_unconstrained=r.crash_mode)
t = angr.exploration_techniques.Tracer(trace=r.trace, crash_addr=r.crash_addr, copy_states=True)
self._core = angr.exploration_techniques.DrillerCore(trace=r.trace)
simgr.use_technique(t)
simgr.use_technique(angr.exploration_techniques.Oppologist())
simgr.use_technique(self._core)
self._set_concretizations(simgr.one_active)
l.debug("Drilling into %r.", self.input)
l.debug("Input is %r.", self.input)
while simgr.active and simgr.one_active.globals['trace_idx'] < len(r.trace) - 1:
simgr.step()
# Check here to see if a crash has been found.
if self.redis and self.redis.sismember(self.identifier + '-finished', True):
return
if 'diverted' not in simgr.stashes:
continue
while simgr.diverted:
state = simgr.diverted.pop(0)
l.debug("Found a diverted state, exploring to some extent.")
w = self._writeout(state.history.bbl_addrs[-1], state)
if w is not None:
yield w
for i in self._symbolic_explorer_stub(state):
yield i
参考:
Driller工具分析—https://blog.csdn.net/Chen_zju/article/details/80791281
Driller安装教程—https://blog.csdn.net/xiaosatianyu/article/details/60874004
Driller的安装与使用—https://www.jianshu.com/p/6e3943474f41
Driller论文—https://sites.cs.ucsb.edu/~vigna/publications/2016_NDSS_Driller.pdf
angr论文—https://sites.cs.ucsb.edu/~vigna/publications/2016_SP_angrSoK.pdf