오늘은 PDF-Writer라는 오픈 소스 프로젝트에 AFL Fuzzer를 붙여보겠습니다.
[AFL++] Software Testing Tool & Fuzzing Tool
https://github.com/AFLplusplus/AFLplusplus
[PDF-Writer] Open Source Software Link
https://github.com/galkahana/PDF-Writer
우선, 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
이제 본론으로 들어가서, 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 |