Fuzzing之后发现的crash分析(自我摸索的记录)

使用Fuzzing工具测试完成之后,如果发现了大量的crashes,我们需要分析crash是否为真的漏洞,同时需要在CVE平台上使用关键字查找这些漏洞是否已经被别人发现。

本次的整理是为跟我一样的初入此行时一片茫然的小伙伴们的,按照自己的理解,本次整理按照以下三个部分进行:POC去重、漏洞类型分析、CVE平台查新与提交

一、POC去重

可能多个POC触发同一个crashes
去重方式我居然已经记不清,后面碰到时再补充吧~~~~~

二、漏洞类型分析

包括三种分析方法,分别是crashwalk、GDB、Address Sanitizer,我比较推荐Address Sanitizer。

  1. crashwalk
    (需要注意:在执行AFL时,需要添加--参数)
    具体做法为:
    (1) 安装go: apt-get install gdb golang
    (2) 安装crashwalk:

    # mkdir go
    # export GOPATH=~/go
    # go get -u github.com/bnagy/crashwalk/cmd/...
    # ~/go/bin/cwtriage -root . -afl ./path/to/target @@
    对于测试结束结果进行分析:~/go/bin/cwtriage -root fuzzer2/crashes/ -match id -seen ~/afl-experient/binutils-2.29/binutils/objdump -d @@
    (同时输出到屏幕和一个名为crashwalk.db的数据库中,上面的-seen代表可以对数据库进行追加写入,通过~/go/bin/cwdump ./crashwalk.db > triage.txt,可以将漏洞进行分类到txt文件中)
    NOTES:需要AFL命令为afl-fuzz -i input -o output -- ./binutils/size @@
    # cwdump ./crashwalk.db > triage.txt
    
  2. GDB
    需要在编译时添加-g

    (gdb) file nasm
    Reading symbols from nasm...done.
    
    (gdb) run -felf ./input/seed1
    Starting program: /home/lbb/afl-experient/Tests/ASAN/nasm-2.14.02/nasm -felf ./input/seed1
    Program received signal SIGSEGV, Segmentation fault.
    expr2 (critical=critical@entry=0) at asm/eval.c:482
    482     e = expr3(critical);
    
    (gdb) info stack
    #0  expr2 (critical=critical@entry=0) at asm/eval.c:482
    #1  0x0000000000422941 in expr1 (critical=critical@entry=0) at asm/eval.c:456
    #2  0x0000000000422cc1 in expr0 (critical=0) at asm/eval.c:430
    #3  0x0000000000420233 in expr6 (critical=critical@entry=0) at asm/eval.c:857
    #4  0x0000000000421139 in expr5 (critical=critical@entry=0) at asm/eval.c:567
    #5  0x000000000042201c in expr4 (critical=critical@entry=0) at asm/eval.c:542
    #6  0x0000000000422101 in expr3 (critical=critical@entry=0) at asm/eval.c:508
    #7  0x00000000004225c1 in expr2 (critical=critical@entry=0) at asm/eval.c:482
    #8  0x0000000000422941 in expr1 (critical=critical@entry=0) at asm/eval.c:456
    #9  0x0000000000422cc1 in expr0 (critical=0) at asm/eval.c:430
    
  3. Address Sanitizer,最新版gcc的内存检测工具,用户可以使用-fsanitize=address标签对二进制文件进行编译,这样如果发生了内存访问错误,用户可以获得一份十分详尽的事件信息。

  • 编译源码时添加'-fsanitize=address'
    想要在错误消息中添加更好的堆栈跟踪,启用 -fno-omit-frame-pointer,此外还可以使用-O1进行一级优化的编译。

    具体做法为:
    (1) 对于单个程序编译,直接在编译时添加在命令行
    如:`gcc -g -fsanitize=address -O1 -fno-omit-frame-pointer ./test.c`
    (2) 对于含有Makefile的项目,在'CFLAGS'后面添加
    如:`CFLAGS		= -g -fsanitize=address ......`
    (3) 对于含有configure的项目
    ./configure CFLAGS='-g -fsanitize=address' 即可
    
  • 使用一段python代码对Fuzzing的crash进行批量化分析:
    (此处借鉴于安全客《从零开始学习fuzzing》,在此基础上做了一小部分修改)
    运行方式为

    # python3 /path/xxx.py /path/crashes /path/program [param]
    例如我将此python保存在 ~/mytest/crash_analyze.py,crashes存放在 ~/mytest/jhead-2.04/master/crashes,测试程序为 ~/mytest/jhead-2.04/jhead,因为jhead运行无参数,所以[param]缺省。
    # python3 ~/mytest/crash_analyze.py ~/mytest/jhead-2.04/master/crashes ~/mytest/jhead-2.04/jhead
    之后会在~/mytest/jhead-2.04/下创建analyze_output,所有分写结果全部保存于此
    
    #!/usr/bin/env python3
    
    import os
    from os import listdir
    from os import sys
    
    def get_files():
    
        #files = os.listdir("/root/crashes/")
        files = os.listdir(sys.argv[1])
        return files
    
    # argv[1]: crashes dir
    # argv[2]: program-name
    # argv[3]: param
    def triage_files(files):
    
        len_argv = len(sys.argv)
        # 漏洞类型的统计
        cout_crashes = {"SEGV": 0, "HBO": 0, "UNKNOWN":0}
        
        folder = os.path.exists("analyze_output")
        if not folder:
            os.makedirs("analyze_output")
        
        for x in files:
            if len_argv == 4:
                original_output = os.popen(sys.argv[2] + " " + sys.argv[3] + " " + x + " 2>&1").read()
            else:
                original_output = os.popen(sys.argv[2] + " " + os.path.join(sys.argv[1] ,x) + " 2>&1").read()
            output = original_output
    
            # Getting crash reason
            crash = ''
            if "SEGV" in output:
                crash = "SEGV"
                cout_crashes["SEGV"] += 1
            elif "heap-buffer-overflow" in output:
                crash = "HBO"
                cout_crashes["HBO"] += 1
            else:
                crash = "UNKNOWN"
                cout_crashes["UNKNOWN"] += 1
    
            address = ''
            operation = ''
            if crash == "HBO":
                output = output.split("\n")
                counter = 0
                target_line = ''
                while counter < len(output):
                    if output[counter] == "=================================================================":
                        target_line = output[counter + 1]
                        target_line2 = output[counter + 2]
                        counter += 1
                    else:
                        counter += 1
                target_line = target_line.split(" ")
                address = target_line[5].replace("0x","")
    
    
                target_line2 = target_line2.split(" ")
                operation = target_line2[0]
    
    
            elif crash == "SEGV":
                output = output.split("\n")
                counter = 0
                while counter < len(output):
                    if output[counter] == "=================================================================":
                        target_line = output[counter + 1]
                        target_line2 = output[counter + 2]
                        counter += 1
                    else:
                        counter += 1
                if "unknown address" in target_line:
                    address = "00000000"
                else:
                    address = None
    
                if "READ" in target_line2:
                    operation = "READ"
                elif "WRITE" in target_line2:
                    operation = "WRITE"
                else:
                    operation = None
    
            log_name = (x + "." + crash + "." + address + "." + operation)
            fn = os.path.join("analyze_output", log_name)
            f = open(fn,"w+")
            f.write(original_output)
            f.close()
    
        print("Numbers of the crash:")
        for ele in cout_crashes.items():
            print(ele)
    
    if __name__ == "__main__":
        if len(sys.argv) == 1:
            print("Please input \"crash_analyze.py --help \"")
        elif sys.argv[1] == "--help":
            print("argv[1]: crashes dir\n",\
                    "argv[2]: program-name\n",\
                    "argv[3]: param"
            )
        else:
            files = get_files()
            triage_files(files)
    

三、CVE平台查新与提交

  1. 查新:确定了分析得到的crash为漏洞之后,需要利用发现漏洞的关键信息在CVE平台上查找相应的漏洞是否已经被提交,还可以在所测软件所属平台上进行查询。
    例如我们在添加了'-fsanitize=address'编译得到的jhead-3.04上执行前面的crashes里包含的测试用例之后,得到了如下图信息,我们发现发生在jhead-3.04下的exif.c文件的Get32s函数出,通过定位到源码之后发现确实存在此漏洞。

  2. 我们利用关键字jhead``Get32s等在CVE网站进行搜索
    CVE网站:https://cve.mitre.org/

    • 如果发现相关信息,则需要点进去看相应版本和具体发生位置等信息(此漏洞为我1月份提交的)

    • 如果没有发现相关信息,并不意味着一定没有,我们需要扩大范围,例如前面是用两个关键词,可以直接用jhead关键词,还可以在其软件对应官网或其他平台

  3. 提交:我们可以将自己发现的漏洞的描述放在一个可以访问的网站,自己创建的、github上存放的、或其他平台上描述的(因为我也是摸着石头过河,参考一下CVE平台上的前辈们的链接),都可以,在CVE平台提交的时候提供相应链接即可。
    如何提交,这篇文章写得非常棒CVE申请的那些事

    • 描述:
      例如我提交的描述信息一般放在https://launchpad.net/ubuntu 因为此网站包含了一些ubuntu上的软件,直接定位的相应软件即可进行描述。

    • 提交:前面都准备完成之后就是在CVE网站上的提交了,提交时可以同时提交好几个,我有一次连续提交了3个matio的,但后没有任何相应,具体我也不清楚了(我的理解是有些软件比较快,有些比较慢吧,我刚查询完matio相关的CVE,发现依然停留在2019)。
    1. 查找相应的CNA,如果无法确定自己所提交软件的CNA,可以按照前面漏洞查新部分的方法,查找相同软件的CVE编号里描述的对应的CNA即可。如jhead属于MITRE Corporation,点击后面的Web链接即可。

    2. 申请CVE-ID,对其进行描述,比较关键的一点就是邮箱、申请的个数、还有下面的Discoverer(s)/Credits(即谁发现的)

    3. 全部提交完之后会返回一份邮件,应该是提示提交成功的,如果快的话一两天就能收到CVE编号,慢的话一周或者更长时间吧。

这些全部是我的摸索与尝试,有错误或者有更好方案的欢迎评论区大家交流,我们共同进步

posted @ 2020-09-11 11:31  libbin  阅读(3491)  评论(3编辑  收藏  举报