• 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("
      ")
                  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("
      ")
                  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
      ",
                      "argv[2]: program-name
      ",
                      "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编号,慢的话一周或者更长时间吧。

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

  • 相关阅读:
    go语言入门(三)
    go语言入门(二)
    Nand Flash 基础
    哈希技术
    NorFlash基础
    二阶构造模式
    C++基础知识汇总
    Arm寄存器介绍及汇编基础
    Linux Makefile详解
    Linux链接器脚本详解
  • 原文地址:https://www.cnblogs.com/libbin/p/fuzzing-crash-analyze.html
Copyright © 2020-2023  润新知