개요
첫 번째 예제는 Xpdf PDF 뷰어 퍼징이다.
목표는 XPDF 3.02에서 CVE-2019-13288에 대한 크래시/PoC를 찾는 것 이다.
CVE-2019-13288은 조작된 파일을 통해 무한 재귀를 일으킬 수 있는 취약점이다.
프로그램에서 호출된 각 함수는 스택에 스택 프레임을 할당하므로
함수가 너무 많이 반복적으로 호출되면 스택 메모리가 고갈되고 프로그램이 중단될 수 있다.
이는 DoS 공격에 활용될 수 있다.
https://cwe.mitre.org/data/definitions/674.html
환경
해당 예제는 ubuntu 20.04 버전을 권장하고 있으며
vmware 파일을 제공하고 있다.
(pw: fuzz)
https://drive.google.com/file/d/1_m1x-SHcm7Muov2mlmbbt8nkrMYp0Q3K/view?usp=sharing
xpdf 설치
프로젝트 디렉터리 생성
cd $HOME
mkdir fuzzing_xpdf && cd fuzzing_xpdf/
빌드 도구 설치
sudo apt install build-essential
Xpdf 3.02 다운로드
wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
tar -xvzf xpdf-3.02.tar.gz
Xpdf 빌드
cd xpdf-3.02
sudo apt update && sudo apt install -y build-essential gcc
./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install
빌드를 테스트를 위한 PDF 예제 다운로드
cd $HOME/fuzzing_xpdf
mkdir pdf_examples && cd pdf_examples
wget https://github.com/mozilla/pdf.js-sample-files/raw/master/helloworld.pdf
wget http://www.africau.edu/images/default/sample.pdf
wget https://www.melbpc.org.au/wp-content/uploads/2017/10/small-example-pdf-file.pdf
pdfinfo 바이너리 테스트
$HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta $HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf
AFL++ 설치
도커와 로컬 설치 방법을 알려주고 있는데
권장하는 방법인 로컬 설치로 진행했다.
(중간에 unicorn이랑 cmake 오류가 뜨긴 했는데 cmake는 설치해주고 unicorn 오류는 그냥 무시하긴 했다)
종속성 설치
sudo apt-get update
sudo apt-get install -y build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools
sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang
sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev
AFL++ 체크아웃 및 빌드
cd $HOME
git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
export LLVM_CONFIG="llvm-config-11"
make distrib
sudo make install
afl-fuzz를 입력했을 때 다음과 같이 나오면 정상
퍼징 준비
AFL은 Coverage-guided Fuzzer 이다.
새로운 실행 경로와 잠재적인 버그를 발견하기 위해 변경된 각 입력에 대한 적용 범위 정보를 수집한다.
소스 코드가 있는 경우 AFL은 계측을 사용하여 각 기본 블록(함수, 루프 등)의 시작 부분에 함수 호출을 삽입할 수 있다.
대상 애플리케이션에 대한 계측을 활성화하려면 AFL 컴파일러를 사용하여 코드를 컴파일해야 한다.
이전에 컴파일된 모든 개체 파일과 실행 파일 정리
rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
afl-clang-fast 컴파일러로 xpdf 빌드
(권한 부족 시 sudo make install)
export LLVM_CONFIG="llvm-config-11"
CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install
퍼징 수행
fuzzer 실행 명령어
afl-fuzz -i $HOME/fuzzing_xpdf/pdf_examples/ -o $HOME/fuzzing_xpdf/out/ -s 123 -- $HOME/fuzzing_xpdf/install/bin/pdftotext @@ $HOME/fuzzing_xpdf/output
각 옵션에 대한 간략한 설명
-i: 입력 사례(일명 파일 예제)를 넣어야 하는 디렉터리
-o: AFL++가 변형된 파일을 저장할 디렉터리
-s: 사용할 정적 무작위 시드
@@: AFL이 각 입력 파일 이름으로 대체할 자리 표시자 대상의 명령줄
다음과 같은 오류가 날 경우 대처방안
Hmm, your system is configured to send core dump notifications to an external utility...
sudo su
echo core >/proc/sys/kernel/core_pattern
exit
크래시 분석
크래시가 발견되면 중단하고 크래시를 분석하면 된다.
$HOME/fuzzing_xpdf/out/으로 가면 크래시 파일이 저장되어 있다.
크래시 파일 이름은 요런식이다.
id:000000,sig:11,src:001504+000002,time:924544,op:splice,rep:16
이 파일을 pdftotext 바이너리의 입력으로 전달한다.
$HOME/fuzzing_xpdf/install/bin/pdftotext '$HOME/fuzzing_xpdf/out/default/crashes/<your_filename>' $HOME/fuzzing_xpdf/output
실습했을 때 5개의 크래시를 발견했었는데
그 중 마지막 크래시에서 깃헙 내용과 동일한 크래시를 발견할 수 있었다.
gdb를 이용해 크래시 분석을 수행한다.
instrumentation 코드는 분석을 할 때 불필요하기 때문에 Xpdf를 다시 빌드한다.
rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
CFLAGS="-g -O0" CXXFLAGS="-g -O0" ./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install
gdb 실행 명령어
gdb --args $HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/<your_filename> $HOME/fuzzing_xpdf/output
run하면 크래시 지점에 도달하게 되는데
bt 명령으로 콜스택을 확인한다.
cve 설명에 나와있는 정보처럼 Parser.cc의 Parser::getObj() 함수가 많이 보인다.
쭉 살펴보면 다음과 같은 함수가 반복되는 것을 볼 수 있다.
Parser::getObj -> Parser::makeStream -> Object::dictLookup -> Dict::lookup -> Object::fetch -> XRef::fetch
어디서 호출되는지 소스 코드에서 찾아보면 다음과 같다.
Parser::getObj
// stream objects are not allowed inside content streams or
// object streams
if (allowStreams && buf2.isCmd("stream")) {
if ((str = makeStream(obj, fileKey, encAlgorithm, keyLength,
objNum, objGen)))
Parser::makeStream
// get stream start position
lexer->skipToNextLine();
pos = lexer->getPos();
// get length
dict->dictLookup("Length", &obj);
Object::dictLookup
inline Object *Object::dictLookup(char *key, Object *obj)
{ return dict->lookup(key, obj); }
Dict::lookup
Object *Dict::lookup(char *key, Object *obj) {
DictEntry *e;
return (e = find(key)) ? e->val.fetch(xref, obj) : obj->initNull();
}
Object::fetch
Object *Object::fetch(XRef *xref, Object *obj) {
return (type == objRef && xref) ?
xref->fetch(ref.num, ref.gen, obj) : copy(obj);
}
XRef::fetch
parser->getObj(obj, encrypted ? fileKey : (Guchar *)NULL,
encAlgorithm, keyLength, num, gen);
패치
패치된 버전인 4.02를 살펴보면 다음과 같다.
중첩 오브젝트의 최대값이 설정되고 해당 값을 넘을 수 없도록 패치됐다.
// Max number of nested objects. This is used to catch infinite loops
// in the object structure.
#define recursionLimit 500
https://github.com/antonio-morales/Fuzzing101/tree/main/Exercise%201
https://juntheworld.postype.com/post/13285052
'background > fuzzing' 카테고리의 다른 글
[fuzzing101] Exercise 3 - TCPdump (CVE-2017-13028) (2) (0) | 2024.04.05 |
---|---|
[fuzzing101] Exercise 3 - TCPdump (CVE-2017-13028) (1) (0) | 2024.04.04 |
[fuzzing101] Exercise 2 - libexif (CVE-2009-3895 & CVE-2012-2836) (2) (1) | 2024.04.01 |
[fuzzing101] Exercise 2 - libexif (CVE-2009-3895 & CVE-2012-2836) (1) (0) | 2024.03.30 |