본문 바로가기

SoftwareTesting

오픈소스 프로젝트에 AFL Fuzzer 붙이는 방법

오늘은 PDF-Writer라는 오픈 소스 프로젝트에  AFL Fuzzer를 붙여보겠습니다.

 

[AFL++] Software Testing Tool & Fuzzing Tool 

https://github.com/AFLplusplus/AFLplusplus

 

GitHub - AFLplusplus/AFLplusplus: The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enh

The fuzzer afl++ is afl with community patches, qemu 5.1 upgrade, collision-free coverage, enhanced laf-intel & redqueen, AFLfast++ power schedules, MOpt mutators, unicorn_mode, and a lot more!...

github.com

 

[PDF-Writer] Open Source Software Link 

https://github.com/galkahana/PDF-Writer

 

GitHub - galkahana/PDF-Writer: High performance library for creating, modiyfing and parsing PDF files in C++

High performance library for creating, modiyfing and parsing PDF files in C++ - GitHub - galkahana/PDF-Writer: High performance library for creating, modiyfing and parsing PDF files in C++

github.com

우선, Open Source Software에 Fuzzer를 붙이기 위해서는 해당 프로젝트에 대한 이해를 필요로 합니다. 

그렇기에, 아래 명령을 통해 우선 프로젝트를 Clone 해줍니다. 

git clone https://github.com/galkahana/PDF-Writer.git

올바르게 Clone을 하였다면 아래와 같이 PDF-Writer 디렉터리가 보이게 됩니다. 

이제 PDF-Writer 디렉터리로 들어가서 fuzz 디렉터리를 만들어줍니다. 

 

cd PDF-Writer && mkdir fuzz

위 명령을 실행한 후, 디렉터리를 보면 아래와 같이 fuzz 디렉터리가 잘 만들어진 걸 확인할 수 있습니다. 

이 프로젝트는 CMakeLists.txt 파일이 존재하는데, 프로젝트마다 빌드하는 방식들이 조금씩 달라서 cmake, make, configure 정도는 자주 쓰이는 빌드 방식이니 아래 링크에서 공부하면 도움이 됩니다. 

 

https://www.gnu.org/software/automake/manual/automake.html

 

automake

 

www.gnu.org

이제 본론으로 들어가서, CMakeLists.txt 파일이 있으니 cmake를 해주면 되는데, 아까 만든 fuzz 디렉터리로 이동한 후 아래 명령을 통해 빌드해줍니다.  

 

이제 테스트 하기 위해 프로젝트의 API들이 어떤 것들이 존재하며, 기본적으로 Test Driver가 구현되어 있는 게 있는지 확인해보고, 해당 내용을 기반으로 테스트 드라이버를 작성해줍니다.

 

아래 코드는 pdf_text.pdf 라는 이름의 PDF를 생성하고 들어온 입력을 PDF에 써주는 시나리오입니다. 

코드를 보면, fuzz_input에 AFL Fuzzer로부터 생성된 입력이 들어가게 됩니다. 

 

#include <iostream> 
#include <string> 
#include "../PDFWriter/PDFWriter.h" 
#include "../PDFWriter/PDFPage.h"
#include "../PDFWriter/PageContentContext.h" 
#include "../PDFWriter/PDFUsedFont.h" 
#include "../PDFWriter/AbstractContentContext.h"

int main(void){ 
	std::string fuzz_input ;  

	std::cin >> fuzz_input ; 
	// Created an A4 portrait page and then a page context for it. 
	PDFWriter pdf_writer; 
	pdf_writer.StartPDF("./pdf_text.pdf", ePDFVersion13) ;   
	
	PDFPage * page = new PDFPage() ; 
	page->SetMediaBox(PDFRectangle(0,0,595,842));
	PageContentContext* cxt = pdf_writer.StartPageContentContext(page); 
	
	PDFUsedFont * arial_font = pdf_writer.GetFontForFile("../TestMaterials/fonts/arial.ttf"); 

	AbstractContentContext::GraphicOptions path_stroke_options(AbstractContentContext::eStroke, 
    			AbstractContentContext::eRGB, AbstractContentContext::ColorValueForName("DarkMagenta"),4);

	AbstractContentContext::TextOptions text_options(arial_font /* font */,  
    			14 /* font size */ , AbstractContentContext::eGray /* colorSpace */,  0 /* inColorValue */);   
	PDFUsedFont::TextMeasures text_dimensions = arial_font->CalculateTextDimensions(fuzz_input, 14) ; 
	
	cxt->WriteText(10, 100, fuzz_input, text_options) ;
	DoubleAndDoublePairList pathPoints;
	
	pathPoints.push_back(DoubleAndDoublePair(10+text_dimensions.xMin,98+text_dimensions.yMin));
	pathPoints.push_back(DoubleAndDoublePair(10+text_dimensions.xMax,98+text_dimensions.yMin));
	cxt->DrawPath(pathPoints,path_stroke_options);
	pathPoints.clear();
	
	pathPoints.push_back(DoubleAndDoublePair(10+text_dimensions.xMin,102+text_dimensions.yMax));
	pathPoints.push_back(DoubleAndDoublePair(10+text_dimensions.xMax,102+text_dimensions.yMax));
	cxt->DrawPath(pathPoints,path_stroke_options);

	pdf_writer.EndPageContentContext(cxt) ;
	
	pdf_writer.WritePageAndRelease(page); 
	
	pdf_writer.EndPDF(); 
	return 0 ; 
}

이제 테스트 드라이버가 컴파일 될 수 있도록 하기 위해 아카이브 파일과 특정 헤더가 존재하는 파일 경로를 같이 넘겨주면 됩니다. 

이러한 빌드하는 작업을 일관적으로 하기 위해 build.sh라는 쉘 스크립트를 구현해주면 편리하게 빌드할 수 있습니다. 

 

아래와 같이 쉘 스크립트를 구현해주었습니다. 

#!/bin/bash 

CURR_PATH=$(pwd)
ARCHIVE_FILE=$CURR_PATH/PDFWriter/libPDFWriter.a
FREE_TYPE=$CURR_PATH/FreeType/libFreeType.a
LIB_AESGM=$CURR_PATH/LibAesgm/libLibAesgm.a
LIB_TIFF=$CURR_PATH/LibTiff/libLibTiff.a
LIB_JPEG=$CURR_PATH/LibJpeg/libLibJpeg.a
LIB_PNG=$CURR_PATH/LibPng/libLibPng.a
ZLIB=$CURR_PATH/ZLib/libZlib.a
FUZZ_FILES=$(ls fuzz*)

for NAME in $FUZZ_FILES ; 
do 
	FILE_NAME=$(echo $NAME | cut -f 1 -d '.')
	EXT=$(echo $NAME | cut -d'.' -f2)

	if [ "$EXT" = "cpp" ] ; then 
		afl-clang-fast++ -I../FreeType/include/ -o $FILE_NAME $NAME $ARCHIVE_FILE $FREE_TYPE $LIB_AESGM $LIB_TIFF $LIB_JPEG $LIB_PNG $ZLIB 
#afl-clang-fast++ -I../FreeType/include/ -o $FILE_NAME $NAME ./PDFWriter/libPDFWriter.a ./FreeType/libFreeType.a ./LibAesgm/libLibAesgm.a ./LibTiff/libLibTiff.a ./LibJpeg/libLibJpeg.a ./LibPng/libLibPng.a ./ZLib/libZlib.a
	elif [ "$EXT" = "c" ] ; then 
		afl-clang-fast -I../FreeType/include/ -o $FILE_NAME $NAME $ARCHIVE_FILE $FREE_TYPE $LIB_AESGM $LIB_TIFF $LIB_JPEG $LIB_PNG

	fi
done

위 스크립트에서 핵심 부분은 컴파일 할 때 -I 옵션 뒤에 헤더 파일이 존재하는 경로를 넘겨주고 뒤에 아카이브 파일들을 덧붙여 컴파일하는 것입니다. 위와 같이 스크립트 문을 구현할 경우, sh build.sh 명령 하나로 fuzz로 시작하는 모든 테스트 드라이버를 컴파일할 수 있다는 장점이 있으며 쉘 스크립트에 익숙해질 수 있습니다 :)

 

이렇게 빌드 스크립트를 모두 작성하였다면, 아래 명령 실행해주시면 됩니다. 

 

$ sh build.sh 

빌드를 마무리 하였다면, 아래 명령을 통해 이제 AFL을 돌려봅시다. 

$ afl-fuzz -i [input directory path] -o [output directory path] [execution path] 

단, 이때 주의할 점은 input directory에 Seed 값을 가진 파일이 존재해야 하며, output directory는 존재하지 않는 이름으로 해주어야 합니다. 또한 아래와 같이 오류가 뜰 경우 아래 내용을 복사하여 afl-fuzz 앞에 붙여주시면 올바르게 동작합니다. 

 

AFL_SKIP_CPUFREQ=1 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1

위 명령을 실행하게 되면, 아래와 같이 잘 실행되는 것을 확인할 수 있습니다. 

'SoftwareTesting' 카테고리의 다른 글

What is the Software Testing?  (0) 2021.11.25
[OSS-Fuzz] Suricata TestDriver Analysis  (0) 2021.11.16