? ImageMagick 是一個易於使用和功能強大的圖片處理庫,很重要的一點是ImageMagick 在比較多的後端應用中出現(比如說:WordPress和Discuz!).挖掘大型CMS 框架的漏洞的難度比較高,那麽另一種思路則是通過挖掘CMS 框架使用到的第三方庫漏洞讓CMS 框架來觸發(比如:PHPMailer等第三方庫)。由ImageMagick 庫引發的CVE-2016-3714 CVE-2016-5118 遠程命令執行和兩個月前的Yahoo! ImageMagick Heartbleed 已經足以說明從ImageMagick 中觸發的漏洞對主機安全性的危害。那麽接下來我們來討論怎麽把Fuzzing 技術應用到ImageMagick 的漏洞挖掘上。
? Fuzzing ImageMagick 之前,先來了解ImageMagick 庫是怎麽樣使用的。
? ImageMagick 在編譯完成之後,在 utilities
magick
,這就是ImageMagick 圖形庫的命令行使用工具. magick
命令行工具使用如下: ./magick magick_command other_arguments
? magick_command 指的是執行命令。有:identify (查看圖像信息),convert (圖像處理與格式轉換)等,更多命令通過以下鏈接查詢: https://www.imagemagick.org/script/command-line-processing.php
? other_arguments 指的是命令參數。對於Fuzzing ImageMagick 庫,常用的命令是: ./magick convert
(接下來會慢慢討論為何要用convert 命令而不用其它命令)。對ImageMagick 做讀寫測試的命令: ./magick convert input_file.png output.bmp
。ImageMagick 庫則會讀取PNG 格式的圖片並且轉換成BMP 格式的圖像;只對ImageMagick 做讀測試的命令: ./magick convert input_file.png /dev/null
? 接下來,我們就開始要對ImageMagick 庫進行Fuzzing 了。先從github 上復制最新的代碼(建議使用git clone 而不是直接下載源碼),命令是:
? git clone https://github.com/ImageMagick/ImageMagick.git
? 每次ImageMagick 更新代碼,只需要在git pull 即可。
? AFL 在現在是很流行的Fuzzing 測試工具,關於它的介紹在此不多復述.它的使用方法如下:
? 1.在編譯項目時使用afl-clang 或afl-gcc 等AFL 編譯器編譯。
2.使用afl-fuzz 命令對程序進行fuzz,afl-fuzz 包含以下參數:
? afl-fuzz -i %input% -o %output% -t %timeout% -m %memory% fuzz_program fuzz_program_arguments
? -i %input% : 使用指定目錄下的測試樣本。 -o %output% : 把Fuzzing 結果保存到指定目錄。 ? -t %timeout% : 可選參數,程序運行超時值,以毫秒為單位。 -m %memory% : 可選參數,最大運行內存,以MB 為單位。 ? fuzz_program fuzz_program_arguments : 指定fuzzing 程序和程序運行參數。
? 對ubuntu 用戶來說,AFL 可以從apt 中直接安裝(sudo apt install afl)。或者從AFL 的官網上下載源碼,然後make install.關於Fuzzing 的樣本下載,MozillaSecurity 團隊有一份開源的測試樣本 https://github.com/MozillaSecurity/fuzzdata
? 回到ImageMagick 庫Fuzzing ,第一步先使用AFL 編譯器編譯:
cd ImageMagick ./configure CC="afl-clang" CXX="afl-clang++" make
? 編譯完成之後,接下來使用AFL 對ImageMagick 庫進行Fuzzing :
cd utilities mkdir fuzzing_output afl-fuzz -i ../../fuzzdata/samples/png -o fuzzing_output -t 300000 -m 200 ./magick convert @@ /dev/null
? 這句afl-fuzz 命令行的意思是:使用fuzzdata 中的PNG 測試樣本作為輸入,把Fuzzing 的結果保存到fuzzing_output 目錄中.afl-fuzz 程序運行30 秒超時值是為了讓ImageMagick 有更多的時間來執行代碼,很有可能Fuzzing 到一些執行很久循環代碼,被afl-fuzz 當作超時樣本來記錄,但這些樣本並不是Out-of-Memory 或者CPU exhaustion 類的漏洞.最後利用ImageMagick 的convert 命令來測試ImageMagick 的讀操作。
? 下面是Fuzzing 的情況,在AFL fuzzing 裏獲得了7 個CVE.
跑AFL 一定需要註意樣本,好的樣本能夠大大提升Fuzzing 效率。
? *參考鏈接: http://lcamtuf.coredump.cx/afl/
? 跑到新的崩潰文件時,往往需要進一步定位崩潰點.筆者推薦使用AddressSanitizer ,它是非常強大的內存檢測工具,它能夠檢測不同的內存漏洞以及內存泄漏(內存泄漏也算CVE ,具體請到CVE 官往搜索ImageMagick 的漏洞).我們需要在編譯的時候引入ASAN:
cd ImageMagick ./configure CC="gcc" CXX="g++" CFLAGS="-g -fsanitize=address" make
? 在CFLAGS 參數裏:-g 指編譯的時候添加符號信息,-fsanitize=address 則是讓編譯器在編譯時添加ASAN .然後在 utilities
下生成的 ./magick
程序就帶有ASAN 了.運行一個崩潰樣本,輸出如下:
fuzzing@ubuntu:~/fuzzing/ImageMagick/utilities$ ./magick convert all_fuzzing_format_2017_7_16_5_13_10/crash/heap-buffer-overflow-READ-0x7f58970bcdc4_output_ps_1500207243.43 output.ps ==61378==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7f06a32fedcc at pc 0x7f06aec0f9c2 bp 0x7ffcb67d5a30 sp 0x7ffcb67d5a20 READ of size 4 at 0x7f06a32fedcc thread T0 #0 0x7f06aec0f9c1 in GetPixelAlpha MagickCore/pixel-accessor.h:59 #1 0x7f06aec17ff8 in WritePSImage coders/ps.c:2046 #2 0x7f06ae7491c6 in WriteImage MagickCore/constitute.c:1114 #3 0x7f06ae749e42 in WriteImages MagickCore/constitute.c:1333 #4 0x7f06adf9c3eb in ConvertImageCommand MagickWand/convert.c:3280 #5 0x7f06ae094d98 in MagickCommandGenesis MagickWand/mogrify.c:183 #6 0x4017f1 in MagickMain utilities/magick.c:149 #7 0x4019d2 in main utilities/magick.c:180 #8 0x7f06ad80982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f) #9 0x401308 in _start (/home/fuzzing/fuzzing/ImageMagick/utilities/.libs/lt-magick+0x401308) 0x7f06a32fedcc is located 12 bytes to the right of 556480-byte region [0x7f06a3277000,0x7f06a32fedc0) allocated by thread T0 here: #0 0x7f06af3e5076 in __interceptor_posix_memalign (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x99076) #1 0x7f06ae8ed8de in AcquireAlignedMemory MagickCore/memory.c:262 #2 0x7f06ae6e4731 in OpenPixelCache MagickCore/cache.c:3523 #3 0x7f06ae6dd0d1 in GetImagePixelCache MagickCore/cache.c:1667 #4 0x7f06ae6ec1f0 in SyncImagePixelCache MagickCore/cache.c:5222 #5 0x7f06ae8b9609 in SetImageExtent MagickCore/image.c:2554 #6 0x7f06aec53c99 in ReadSGIImage coders/sgi.c:374 #7 0x7f06ae746068 in ReadImage MagickCore/constitute.c:497 #8 0x7f06ae748267 in ReadImages MagickCore/constitute.c:866 #9 0x7f06adf060ad in ConvertImageCommand MagickWand/convert.c:641 #10 0x7f06ae094d98 in MagickCommandGenesis MagickWand/mogrify.c:183 #11 0x4017f1 in MagickMain utilities/magick.c:149 #12 0x4019d2 in main utilities/magick.c:180 #13 0x7f06ad80982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f) SUMMARY: AddressSanitizer: heap-buffer-overflow MagickCore/pixel-accessor.h:59 GetPixelAlpha Shadow bytes around the buggy address: 0x0fe154657d60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0fe154657d70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0fe154657d80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0fe154657d90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0fe154657da0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0fe154657db0: 00 00 00 00 00 00 00 00 fa[fa]fa fa fa fa fa fa 0x0fe154657dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0fe154657dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0fe154657de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0fe154657df0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0fe154657e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Heap right redzone: fb Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack partial redzone: f4 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe ==61378==ABORTING
? ASAN 同樣也能用在漏洞挖掘上(比如Heap-overflow READ ,這樣的漏洞不容易觸發崩潰(除非讀到不能夠訪問的內存上,否則是不會產生崩潰的)).但是在AFL 裏,不可以在./configure 時把-fsanitize=address 直接添加在CFLAGS 裏面,而是需要在make 的時候這樣寫:
AFL_USE_ASAN=1 make
? 把ASAN 作為異常捕獲的,還有libFuzzer。
? libFuzzer 與AFL 的區別在於:libFuzzer 傾向於對某個功能或者函數來進行Fuzzing,AFL 則是對整體程序進行Fuzzing。下面是一段使用libFuzzer Fuzzing C-ares DNS 庫的示例代碼(CVE-2017-1000381):
#include <malloc.h> #include <ares.h> extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data,size_t size) { ares_naptr_reply* naptr_out = 0; ares_parse_naptr_reply(data,size,&naptr_out); if (0 != naptr_out) free(naptr_out); }
? LLVMFuzzerTestOneInput() 函數是libFuzzer 庫進行單元測試的調用點,這裏面的實現就是我們將要編寫的測試用例.libFuzzer 能夠更深入去挖掘潛在的漏洞,但是需要對源碼有一定的了解.以ImageMagick 為例,convert 命令中的圖片最後會被ReadImage() 調用去解析,ReadImage() 函數會根據目錄去打開文件再從中讀取數據來給指定的格式來解析.下面是針對ImageMagick 的圖片解析Fuzzing 代碼:
#include <malloc.h> #include <memory.h> #include <stdio.h> #include <stdlib.h> #define MAGICK_IMPLEMENTATION #define MagickExport #include "magick/common.h" #include "magick/image.h" #include "magick/error.h" #include "magick/constitute.h" #include "magick/magick_types.h" #include "magick/utility.h" #include "magick/magick.h" static const struct { char *name; unsigned char *magic; unsigned int length, offset; } StaticMagic[] = { #define MAGIC(name,offset,magic) {name,(unsigned char *)magic,sizeof(magic)-1,offset} MAGIC("WEBP", 8, "WEBP"), MAGIC("AVI", 0, "RIFF"), MAGIC("8BIMWTEXT", 0, "8\000B\000I\000M\000#"), MAGIC("8BIMTEXT", 0, "8BIM#"), MAGIC("8BIM", 0, "8BIM"), MAGIC("BMP", 0, "BA"), MAGIC("BMP", 0, "BM"), MAGIC("BMP", 0, "CI"), MAGIC("BMP", 0, "CP"), MAGIC("BMP", 0, "IC"), MAGIC("BMP", 0, "PI"), MAGIC("CALS", 21, "version: MIL-STD-1840"), MAGIC("CALS", 0, "srcdocid:"), MAGIC("CALS", 9, "srcdocid:"), MAGIC("CALS", 8, "rorient:"), MAGIC("CGM", 0, "BEGMF"), MAGIC("CIN", 0, "\200\052\137\327"), MAGIC("DCM", 128, "DICM"), MAGIC("DCX", 0, "\261\150\336\72"), MAGIC("DIB", 0, "\050\000"), MAGIC("DOT", 0, "digraph"), MAGIC("DPX", 0, "SDPX"), MAGIC("DPX", 0, "XPDS"), MAGIC("EMF", 40, "\040\105\115\106\000\000\001\000"), MAGIC("EPT", 0, "\305\320\323\306"), MAGIC("FAX", 0, "DFAX"), MAGIC("FIG", 0, "#FIG"), MAGIC("FITS", 0, "IT0"), MAGIC("FITS", 0, "SIMPLE"), MAGIC("FPX", 0, "\320\317\021\340"), MAGIC("GIF", 0, "GIF8"), MAGIC("HDF", 1, "HDF"), MAGIC("HPGL", 0, "IN;"), MAGIC("HPGL", 0, "\033E\033"), MAGIC("HTML", 1, "HTML"), MAGIC("HTML", 1, "html"), MAGIC("ILBM", 8, "ILBM"), MAGIC("IPTCWTEXT", 0, "\062\000#\000\060\000=\000\042\000&\000#\000\060\000;\000&\000#\000\062\000;\000\042\000"), MAGIC("IPTCTEXT", 0, "2#0=\042\042"), MAGIC("IPTC", 0, "\034\002"), MAGIC("JNG", 0, "\213JNG\r\n\032\n"), MAGIC("JPEG", 0, "\377\330\377"), MAGIC("JPC", 0, "\377\117"), MAGIC("JP2", 4, "\152\120\040\040\015"), MAGIC("MAT", 0, "MATLAB 5.0 MAT-file,"), MAGIC("MIFF", 0, "Id=ImageMagick"), MAGIC("MIFF", 0, "id=ImageMagick"), MAGIC("MNG", 0, "\212MNG\r\n\032\n"), MAGIC("MPC", 0, "id=MagickCache"), MAGIC("MPEG", 0, "\000\000\001\263"), MAGIC("PCD", 2048, "PCD_"), MAGIC("PCL", 0, "\033E\033"), MAGIC("PCX", 0, "\012\002"), MAGIC("PCX", 0, "\012\005"), MAGIC("PDB", 60, "vIMGView"), MAGIC("PDF", 0, "%PDF-"), MAGIC("PFA", 0, "%!PS-AdobeFont-1.0"), MAGIC("PFB", 6, "%!PS-AdobeFont-1.0"), MAGIC("PGX", 0, "PG ML"), MAGIC("PGX", 0, "PG LM"), MAGIC("PICT", 522, "\000\021\002\377\014\000"), MAGIC("PNG", 0, "\211PNG\r\n\032\n"), MAGIC("PBM", 0, "P1"), MAGIC("PGM", 0, "P2"), MAGIC("PPM", 0, "P3"), MAGIC("PBM", 0, "P4"), MAGIC("PGM", 0, "P5"), MAGIC("PPM", 0, "P6"), MAGIC("P7", 0, "P7 332"), /* XV Thumbnail */ MAGIC("PAM", 0, "P7"), /* Should be listed after "P7 332" */ MAGIC("PS", 0, "%!"), MAGIC("PS", 0, "\004%!"), MAGIC("PS", 0, "\305\320\323\306"), MAGIC("PSD", 0, "8BPS"), MAGIC("PWP", 0, "SFW95"), MAGIC("RAD", 0, "#?RADIANCE"), MAGIC("RAD", 0, "VIEW= "), MAGIC("RLE", 0, "\122\314"), MAGIC("SCT", 0, "CT"), MAGIC("SFW", 0, "SFW94"), MAGIC("SGI", 0, "\001\332"), MAGIC("SUN", 0, "\131\246\152\225"), MAGIC("SVG", 1, "?XML"), MAGIC("SVG", 1, "?xml"), MAGIC("TIFF", 0, "\115\115\000\052"), MAGIC("TIFF", 0, "\111\111\052\000"), MAGIC("BIGTIFF", 0, "\115\115\000\053\000\010\000\000"), MAGIC("BIGTIFF", 0, "\111\111\053\000\010\000\000\000"), MAGIC("VICAR", 0, "LBLSIZE"), MAGIC("VICAR", 0, "NJPL1I"), MAGIC("VIFF", 0, "\253\001"), MAGIC("WMF", 0, "\327\315\306\232"), MAGIC("WMF", 0, "\001\000\011\000"), MAGIC("WPG", 0, "\377WPC"), MAGIC("XBM", 0, "#define"), MAGIC("XCF", 0, "gimp xcf"), MAGIC("XPM", 1, "* XPM *"), MAGIC("XWD", 4, "\007\000\000"), MAGIC("XWD", 5, "\000\000\007") }; int random(const unsigned char* data,unsigned int size) { unsigned int random_code = 0; for (unsigned int data_index = 0;data_index < size;++data_index) random_code += data[data_index]; return random_code % (sizeof(StaticMagic) /sizeof(StaticMagic[0])); } #define GENARATE_FILE_NAME "./libFuzzerGenarateImageSample" extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data,unsigned int size) { int random_image_flag_index = random(data,size); unsigned int random_image_flag_offset = StaticMagic[random_image_flag_index].offset; unsigned int random_image_flag_length = StaticMagic[random_image_flag_index].length; unsigned int image_buffer_length = random_image_flag_offset + random_image_flag_length + size; unsigned char* image_buffer = (unsigned char*)malloc(image_buffer_length); memset(image_buffer,0,image_buffer_length); memcpy(image_buffer,StaticMagic[random_image_flag_index].name,StaticMagic[random_image_flag_index].length); FILE* file = fopen(GENARATE_FILE_NAME,"w"); if (NULL != file) { fwrite(image_buffer,1,image_buffer_length,file); fclose(file); printf("buffer=%s(0x%X), size=%d,input format=%s\n",image_buffer,image_buffer,image_buffer_length,StaticMagic[random_image_flag_index].name); ExceptionInfo exception; ImageInfo* read_image_info; ImageInfo* write_image_info; Image* image; GetExceptionInfo(&exception); read_image_info = CloneImageInfo((ImageInfo*)NULL); write_image_info = CloneImageInfo((ImageInfo*)NULL); strlcpy(read_image_info->filename,GENARATE_FILE_NAME,MaxTextExtent); strlcpy(write_image_info->filename,"/dev/null",MaxTextExtent); SetImageInfo(read_image_info,SETMAGICK_READ,&exception); SetImageInfo(write_image_info,SETMAGICK_WRITE,&exception); image = ReadImage(read_image_info,&exception); if (NULL != image) WriteImage(write_image_info,image); DestroyImageInfo(read_image_info); DestroyImageInfo(write_image_info); DestroyExceptionInfo(&exception); } free(image_buffer); return 0; } extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { InitializeMagick((const char*)argv[0]); return 1; }
使用libFuzzer Fuzzing 時,編譯時一定要添加ASAN :
cd ImageMagick ./configure CC="clang" CXX="clang++" CFLAGS="-O2 -g -fsanitize-coverage=trace-pc-guard -fsanitize=address" make cd utilities clang++ read_image_fuzzer.cc -O2 -g -fsanitize-coverage=trace-pc-guard -fsanitize=address -I .. -ljbig -lwebp -llcms2 -ltiff -lfreetype -ljpeg -lpng12 -lwmflite -lXext -lSM -lICE -lX11 -llzma -lbz2 -lxml2 -lz -lm -lgomp -lpthread ../magick/.libs/libMagickCore-7.Q16HDRI.a ../../libFuzzer/Fuzzer/libFuzzer.a -o read_image_fuzzer
? 第一次編譯ImageMagick 時,一定要把ASAN 也編譯進去,否則在ImageMagick 庫裏面跑出來漏洞libFuzzer 不會提示!接下來編譯我們的測試Fuzzer ,註意需要這兩次編譯都要用到clang 5 ,因為插件-fsanitize-coverage=trace-pc-guard 需要在高版本的clang 裏才有,否則libFuzzer 會沒有數據輸入(筆者也是在讀libFuzzer 的源碼裏發現的),後面需要引用的庫則根據自己的系統來添加,會有些差異。
? 關於更多libFuzzer 的知識,可以參考這兩個鏈接:
? 1、 https://github.com/Dor1s/libfuzzer-workshop
? 2、 http://llvm.org/docs/LibFuzzer.html
? 程序模型簡單地來說可以分為三部分:輸入,處理,輸出.無論是WEB 還是二進制都一樣成立, 漏洞會隱藏在這三個階段的某個角落裏 .舉個例子,輸入階段有:ETERNALBLUE ,S2-045 (上傳組件的異常信息中可以執行OGNL 導致GetShell ),CVE-2017-7269—IIS 6.0 WebDAV ;處理階段:各大SRC 裏的業務邏輯漏洞,BadKernel ;輸出階段:XSS 漏洞.Fuzzing 的時候也需要考慮到這點,盡可能讓程序所有的階段都能夠執行,達到更高的代碼覆蓋率
? 在前面AFL 與libFuzzer 的例子裏,我們暫時只Fuzzing ImageMagick 讀取圖片部分(對應到代碼的ReadXXXImage() 函數),接下來我們要針對ImageMagick 的寫圖片部分進行Fuzzing(對應到代碼的WriteXXXImage() 函數)
? 我們自己在實現Fuzzer 的時候,如何準確地檢測觸發的漏洞點是一個難題,現在已經有ASAN 作為強力的Bug 檢測工具,我們只需要針對Fuzzing 的邏輯來編寫即可.前面提到,ImageMagick 可以實現不同圖像格式之間的轉換,所以在Fuzzing 時要觸發到WriteXXXImage() 這類函數,必須要以指定的格式來輸出.比如說觸發WritePNGImage() 函數,magick 就必須要輸出.png 格式圖像;觸發WriteARTImage() 函數,magick 就必須要輸出.art 格式圖像,命令如下:
./magick convert test_image output.png ./magick convert test_image output.art
ImageMagick 庫支持很多格式的圖像轉換,更多信息可以在SourceInsight 裏搜索Write*Image() 函數:
? 最後得到支持輸出格式的列表:
imagemagick_output_format = ['output.aai','output.art','output.avs','output.bgr','output.bmp','output.braille','output.cals','output.cin','output.cip','output.clipboard','output.cmyk','output.dds','output.debug','output.dib','output.dpx','output.ept','output.exr','output.fax','output.fits','output.flif','output.fpx','output.gif','output.gray','output.histogram','output.hrz','output.html','output.icon','output.info','output.inline','output.ipl','output.jbig','output.jp2','output.jpeg','output.json','output.magick','output.map','output.mask','output.mat','output.matte','output.meta','output.miff','output.mono','output.mpc','output.mpeg','output.mpr','output.msl','output.mtv','output.mvg','output.null','output.otb','output.palm','output.pcd','output.pcl','output.pcx','output.pdb','output.pdf','output.pgx','output.pict','output.jng','output.mng','output.png','output.pnm','output.ps','output.ps2','output.ps3','output.psd','output.raw','output.rgb','output.rgf','output.sgi','output.sixelt','output.sun','output.svg','output.tga','output.thumbnail','output.ptif','output.tiff','output.txt','output.uil','output.uyvy','output.vicar','output.vid','output.viff','output.vips','output.wbmp','output.webp','output.xbm','output.picon','output.xpm','output.xtrn','output.xwd','output.ycbcr','output.ps3mask','output.group4','output.yuv''output.x']
然後給ImageMagick 來運行即可,代碼如下:
def run_imagemagick_convert(input_file,output_file) : process = subprocess.Popen(['./magick','convert',input_file,output_file],stdout = subprocess.PIPE,stderr = subprocess.PIPE) # 使用樣本運行magick convert process_timeout_exit = lambda : process.kill() timeout = threading.Timer(MAX_PROCESS_WAIT_TIME,process_timeout_exit) # 執行超時退出 timeout.start() process.wait() timeout.cancel() std_error_output = process.stderr.read() # 從stderr 中讀取ASAN 的崩潰信息輸出 if len(std_error_output) and not -1 == std_error_output.find('========') : # ASAN Check .. flag_address_sanitize = 'ERROR: AddressSanitizer: ' flag_address_sanitize_offset = std_error_output.find(flag_address_sanitize) flag_leak_sanitize = 'ERROR: LeakSanitizer: ' flag_leak_sanitize_offset = std_error_output.find(flag_leak_sanitize) if not -1 == flag_address_sanitize_offset : # 內存漏洞分析 crash_type = std_error_output[flag_address_sanitize_offset + len(flag_address_sanitize) : ] crash_type = crash_type[ : crash_type.find('on') ].strip() crash_type_detail = '' flag_at_pc = 'pc ' flag_at_pc_offset = std_error_output.find(flag_at_pc) crash_point = std_error_output[flag_at_pc_offset + len(flag_at_pc) : ] crash_point = crash_point[ : crash_point.find(' bp')] if not -1 == crash_type.find('buffer-overflow') : # stack and heap flag_of_size = ' of size' flag_of_size_offset = std_error_output.find(flag_of_size) crash_type_detail = std_error_output[ : flag_of_size_offset] crash_type_detail = crash_type_detail[crash_type_detail.rfind('\n') : ].strip() elif 'SEGV' == crash_type : # Null point reference .. flag_of_address = 'unknown address' flag_of_address_offset = std_error_output.find(flag_of_address) crash_type_detail = std_error_output[flag_of_address_offset + len(flag_of_address) : ] crash_type_detail = crash_type_detail[ : crash_type_detail.find('(')].strip() return True,crash_type,crash_type_detail,crash_point,std_error_output elif not -1 == flag_leak_sanitize_offset : # 內存泄漏分析 flag_direct_leak = 'Direct leak' flag_indirect_leak = 'Indirect leak' memory_leak_id = std_error_output.count(flag_direct_leak) + std_error_output.count(flag_indirect_leak) return True , 'Memory-Leak' , str(memory_leak_id) , None , std_error_output return True , None , None , None , std_error_output return False , None , None , None , None # 省略無關代碼 for graphicsmagick_input_file_index in graphicsmagick_input_file_list : # 樣本列表 for graphicsmagick_output_format_index in imagemagick_output_format : # 測試輸出格式列表 graphicsmagick_output = crash_dir_crash_output_dir + '/' + graphicsmagick_input_file_index + '_' + graphicsmagick_output_format_index result = run_graphicsmagick_convert(graphicsmagick_input_file_index_path,graphicsmagick_output) # 調用magick convert
? 還需要註意的一點是,ImageMagick 會在 /tmp
目錄下生成臨時文件而且它不會去刪除這些文件,所以還需要去手動刪除:
import os import time if __name__ == '__main__' : while True : file_list = os.listdir('/tmp') for file_index in file_list : if file_index.startswith('magick') : try : os.remove('/tmp/' + file_index) except : pass time.sleep(10)
? 上面的Fuzzer 需要依賴樣本,我們也可以使用Python 像libFuzzer 那樣生成數據到給ImageMagick 測試:
def build_random_data(data_length) : # 隨機生成指定長度的數據 data = http://www.tuicool.com/articles/b'' for data_index in range(data_length) : data += chr(random.randrange(255)) return data def build_random_image(data_length) : # 生成測試圖像 random_image_header = random.choice(image_header) # image_header 和libFuzzer 裏的ImageMagick 頭一樣 return build_random_data(random_image_header[1]) + random_image_header[2] + build_random_data(data_length) # 省去多余代碼 while True : random_image = build_random_image(max_fuzzing_image_length) write_file('fuzzing_genarate_image',random_image) for graphicsmagick_output_format_index in imagemagick_output_format : result = run_graphicsmagick_convert('fuzzing_genarate_image',fuzzing_dir + '/' +graphicsmagick_output_format_index) if result[0] : # catch crash ...
? 在此限於篇幅,所有Fuzzing 的完整代碼在我的github
.執行效果如下:
? 筆者已經使用這個Python Fuzzer 收獲了至少20 個CVE (數據生成挖到了3 個崩潰,利用樣本測試ImageMagick 寫測試至少獲得了17 個崩潰),在此通過這篇文章和各位讀者分析對於二進制軟件的Fuzzing 的一些常用技術,預祝大家順利挖到CVE .下面是挖掘到的漏洞列表:
联系客服