【Driller】Driller介绍及源码分析

再把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的基本流程为(这里可以学习学习):
  1. 设置参数,打开文件
  2. 初始化driller对象
  3. 通过driller的drill_generator方法进行符号执行,并生成满足afl需求的新输入文件
  4. 保存能执行新路径的输入,存入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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • 前言 最近打算读一读afl(american fuzzy lop) 的源码,为研究生做fuzzing测试做相应的准...
    ChijinZ阅读 10,959评论 3 3
  • 分享一下读过的最新的fuzzing论文,目前是用Xmind记录的,可能过于详细了些,未来会更加精简一点,以博客的方...
    bsauce阅读 6,766评论 0 8
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,209评论 0 17
  • 这两天跟学生上课,发现了他们都不大开心,除了一二三年级的学生以外,四年级以上的学生都基本上是这样的,他们开始思考人生
    这些与梦想飞舞的日子阅读 241评论 0 0