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編號,慢的話一周或者更長時間吧。

這些全部是我的摸索與嘗試,有錯誤或者有更好方案的歡迎評論區大家交流,我們共同進步


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM