fuzzing 101이라는 실습을 따라 진행하면서 퍼징에 대해 공부해볼 것이다.
첫 번째 실습은 Xpdf에서 발생한 DoS 취약점을 퍼저를 돌려 크래시를 발생시키고, 이를 분석하는 실습니다. AFL++ 퍼저를 빌드하고 실행하는 방법까지 상세하게 설명되어 있다.
Exercise 1 - Xpdf
📌 The goal is to find a crash/PoC for CVE-2019-13288 in XPDF 3.02.
CVE-2019-13288
Xpdf 4.01.01에서 Parser.cc의 Parser::getObj() 함수는 제작된 파일을 통해 무한 재귀를 일으킬 수 있습니다. 원격 공격자는 이를 DoS 공격에 활용할 수 있습니다.
OS : Ubuntu 20.04.2 LTS (download)
fuzzing 101에서 실습 환경 VM을 제공해주기 때문에, 위 다운로드 링크를 통해 다운받아 사용했다.
VM 다운로드 후, 다운 순서대로 진행하면 AFL++ 퍼징을 돌릴 수 있게 된다.
퍼징을 돌리기 위해서는 gcc, pip와 같은 기본적인 패키지를 먼저 다운로드 해준다.
sudo apt update -y && sudo apt upgrade -y
sudo apt install build-essential gcc -y
sudo apt-get install python3-pip
Xpdf 설치 및 테스트 실행
Xpdf 설치 및 빌드
wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
tar -xvzf xpdf-3.02.tar.gz
./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install
실습의 타겟 프로그램인 Xpdf를 다운로드하고 빌드한다.
Sample 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
pdf의 샘플 파일을 다운로드 해준다.
pdfinfo 바이너리 테스트 실행
$HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta $HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf
빌드 된 Xpdf 바이너리 중에사 pdfinfo를 통해 샘플 pdf의 정보를 확인해보면, 잘 작동하는 것을 확인할 수 있다.
AFL++ 설치
AFL++ 설치 시 필요한 종속 패키지들을 먼저 설치한다.
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
빌드 시 unicornafl error
unicornafl 관련 에러가 발생했다. 무시하고 진행해도 문제 없었다.
AFL++ 설치 완료
afl-fuzz++ 설치 완료
AFL 컴파일러로 Xpdf 재빌드 및 실행
rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
이전에 테스트 실행을 위해 빌드했던 Xpdf를 삭제한다. AFL 컴파일러로 다시 빌드하기 위해서이다.
afl-clang-fast 컴파일러로 xpdf 빌드
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
AFL++로 pdftotext 바이너리 퍼저 실행
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 : 입력 파일(Seed)이 있는 디렉터리 경로
- o : AFL++ 퍼저가 실행되면서 발생하는 정보를 저장하는 디렉터리 경로
- s : 사용할 정적 무작위 시드
- @@ : AFL이 생성하는 파일을 @@이 입력된 부분에 input으로 대체
퍼저 실행 시 오류
AFL이 외부 유틸리티로 코어 덤프 알림을 보내도록 설정되어 있다고 한다.
/proc/sys/kernel/core_pattern 파일을 다음과 같이 수정하면 에러가 발생하지 않는다.
sudo su
echo core >/proc/sys/kernel/core_pattern
exit
퍼저 실행 화면
퍼저가 잘 실행되면, 위와 같은 화면을 볼 수 있다. 퍼저를 실행한지 1시간 39분이 지났고 9개의 크래시가 발생한 것을 확인할 수 있다.
퍼저의 크래시는 output 디렉터리 설정 경로 아래 /defalut/crashes 에 저장된다.
Crash reproduction
$ /home/fuzz/fuzzing_xpdf/install/bin/pdftotext id:000003,sig:11,src:001344,time:1451803,execs:399942,op:havoc,rep:7
발생한 크래시 파일을 pdftotext 바이너리의 인자로 직접 넣어본다.
해당 파일로 바이너리를 실행했을 때 크래시가 난다.
GDB 분석
gdb -q --args ./install/bin/pdftotext ./out/default/crashes/id:000000,sig:11,src:
pwngdb> r
gdb로 실행시켜보면 Segmentation fault가 뜬다.
backtrace를 보면 특정 부분이 반복된다. 따라서, 특정 부분이 재귀 호출되면서 스택 프레임을 계속 쌓아올리면서 크래시가 발생한 것이라고 볼 수 있다.
backtrace를 확인하면서 반복되는 코드 부분인 #13부터 #19까지 분석해볼 것이다.
#19
위 코드 부분에서 makeStream 함수를 호출한다. VScode의 ‘Ctrl + 왼쪽 클릭’
으로 해당 함수를 찾아갈 수 있다.
#18
makeStream 함수 안에서 dictLookup 함수가 호출된다. 이 역시 따라간다.
#17
lookup 함수를 호출한다.
#16
삼항 연산자를 통해 값을 리턴하고 있다. fetch 함수를 따라간다.
#15
fetch 함수를 따라간다.
#14
getObj 함수를 따라간다.
#13
#19 번째부터 backtrace로 따라오면서 #13까지 왔는데 #19와 동일한 코드로 도달했다.
코드가 실행될 때는 13번째부터 18번째까지 차례대로 호출되었을 것이며, 이 루틴이 재귀 호출되면서 크래시가 발생하는 것이다.
취약점 상세정보 비교
Parser.cc의 Parser::getObj() 함수가 무한 재귀를 일으킬 수 있다고 한다. 위 분석에서도 Parser::getObj() 부터 무한 재귀가 시작된다.