
UAFuzz: Use-After-Free 취약점을 위한 바이너리 레벨 지향적 퍼징
**정적 회색상자 퍼징(DGF)**은 AFLGo와 같이 사전 선택된 잠재적 취약 대상 위치에 대해 스트레스 테스트를 수행하는 것을 목표로 하며, 다양한 보안 컨텍스트에 적용됩니다: (1) 버그 재현, (2) 패치 테스트 또는 (3) 정적 분석 보고서 검증. 최근에는 정적 퍼징의 효과성과 효율성을 개선한 연구 작업이 더 많이 있습니다(awesome-directed-fuzzing 참조).
우리는 UAFuzz를 제안합니다. 이는 바이너리 수준에서 Use-After-Free(UAF) 버그 전용 정적 퍼저로, 이 버그 클래스의 특정 특성을 충족시키기 위해 정적 퍼징의 주요 구성 요소를 세심하게 조정합니다. UAF 버그는 힙 요소가 해제된 후 사용될 때 발생합니다. UAF 버그 탐지는 어렵습니다: (1) 복잡성 - Proof-of-Concept(PoC) 입력이 동일한 메모리 위치에서 세 가지 이벤트(할당, 해제, 사용)의 순서를 트리거해야 하며, 이는 테스트 프로그램의 여러 함수에 걸쳐 있으며 (2) 침묵 - 세그먼테이션 오류가 없습니다.
전반적으로 UAFuzz는 정적 퍼저와 유사한 워크플로우를 가지며, 아래 그림과 같이 퍼징 프로세스 전체에 걸쳐 주황색으로 강조 표시된 수정 사항이 있습니다. (1) 버그 재현 및 (2) 패치 테스트 애플리케이션에 초점을 맞추기 때문에 모든 메모리 관련 UAF 이벤트의 (대부분) 완전한 스택 추적을 가질 가능성이 더 높습니다. 기존의 일반 정적 접근 방식(대상이 독립적으로 선택될 수 있음)과 달리, 우리는 대상 간의 관계(예: UAF에 필수적인 순서)를 고려하여 정적성을 향상시킵니다. 첫째, UAFuzz의 정적 사전 계산은 바이너리 수준에서 빠릅니다. 둘째, 런타임에 퍼저를 대상으로 안내하기 위해 새로운 순서 인식 입력 메트릭을 도입합니다. 마지막으로, 예상 추적에서 모든 대상을 포함하는 잠재적 입력만 분류하고 버그를 트리거할 가능성이 적은 입력을 사전 필터링합니다.
자세한 내용은 RAID'20의 논문과 Black Hat USA'20의 발표를 참조하십시오. 또한 Sébastien Bardin, Matthieu Lemerre, Prof. Roland Groz, 특히 Ocaml에 대한 도움을 주신 Richard Bonichon(@rbonichon)에게 감사드립니다.
테스트 환경은 Ubuntu 16.04 64-bit입니다.
# Install Ocaml and prerequisite packages for BINSEC via OPAM
sudo apt update
sudo apt update
sudo apt install ocaml ocaml-native-compilers camlp4-extra opam emacs llvm-6.0-dev pkg-config protobuf-compiler libgmp-dev libzmq3-dev cmake valgrind
opam init
opam switch 4.05.0
opam depext conf-m4.1
opam install merlin ocp-indent caml-mode tuareg menhir ocamlgraph ocamlfind piqi zmq.5.0.0 zarith llvm.6.0.0
eval `opam config env`
# Install Python's packages (Python 2 for IDA's scripts)
sudo python -m pip install networkx pydot
sudo apt install graphviz
# Install Graph Easy
wget https://cpan.metacpan.org/authors/id/S/SH/SHLOMIF/Graph-Easy-0.76.tar.gz
tar xzf Graph-Easy-0.76.tar.gz
cd Graph-Easy-0.76
perl Makefile.PL; make test; sudo make install
export GRAPH_EASY_PATH=/usr/local/bin/graph-easy
# Checkout source code
git clone https://github.com/strongcourage/uafuzz.git
# Environment variables
export IDA_PATH = /path/to/ida-6.9/idaq
export GRAPH_EASY_PATH=/path/to/graph-easy
cd uafuzz; export UAFUZZ_PATH=`pwd`
# Compile source code
./scripts/build.sh uafuzz
# Help for IDA/UAFuzz interface
./binsec/src/binsec -ida-help
./binsec/src/binsec -uafuzz-help
우리의 퍼저는 AFL v2.52b의 QEMU 모드(퍼징용)와 BINSEC(경량 정적 분석용, uafuzz/README.md 참조)를 기반으로 구축되었습니다. 현재 IDA Pro v6.9를 사용하여 테스트된 바이너리의 제어 흐름 그래프(CFG)와 호출 그래프를 추출합니다(ida/README.md 참조).
uafuzz
├── binsec/src
│ └── ida: IDA의 CFG 및 호출 그래프를 가져오고 처리하는 플러그인
│ └── uafuzz: 퍼징 코드
│ │ └── afl-2.52b: AFL-QEMU 기반의 핵심 퍼징
│ │ └── uafuzz_*.ml(i): 정적 정보를 계산하고 AFL-QEMU와 통신하는 플러그인
└── scripts: 빌드 및 버그 분류를 위한 스크립트
먼저 간단한 UAF 버그를 고려합니다. AFL-QEMU와 소스 수준에서 대상이 지정된 정적 퍼저 AFLGo조차도 6시간 이내에 이 버그를 탐지할 수 없지만, UAFuzz는 Valgrind의 UAF 보고서를 활용하여 몇 분 안에 탐지할 수 있습니다.
# Run AFL-QEMU
$UAFUZZ_PATH/tests/example.sh aflqemu 360
# Run AFLGo given targets at source-level
$UAFUZZ_PATH/tests/example.sh aflgo 360
# Run UAFuzz
$UAFUZZ_PATH/tests/example.sh uafuzz 360 $UAFUZZ_PATH/tests/example/example.valgrind
실제 프로그램의 경우, UAF Fuzzing Benchmark를 평가에 사용합니다.
# Checkout the benchmark
git clone https://github.com/strongcourage/uafbench.git
cd uafbench; export UAFBENCH_PATH=`pwd`
readelf(Binutils)의 CVE-2018-20623에 대한 버그 재현 응용에서 UAFuzz를 실행하는 방법을 자세히 보여줍니다. Valgrind를 통해 얻은 이 UAF 버그의 스택 추적은 다음과 같습니다.
// stack trace for the bad Use
==5358== Invalid read of size 1
==5358== at 0x40A9393: vfprintf (vfprintf.c:1632)
==5358== by 0x40A9680: buffered_vfprintf (vfprintf.c:2320)
==5358== by 0x40A72E0: vfprintf (vfprintf.c:1293)
[6] ==5358== by 0x80AB881: error (elfcomm.c:43)
[5] ==5358== by 0x8086217: process_archive (readelf.c:19409)
[1] ==5358== by 0x80868EA: process_file (readelf.c:19588)
[0] ==5358== by 0x8086B01: main (readelf.c:19664)
// stack trace for the Free
==5358== Address 0x4221dc0 is 0 bytes inside a block of size 80 free'd
==5358== at 0x402D358: free (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
[4] ==5358== by 0x8086647: process_archive (readelf.c:19524)
[1] ==5358== by 0x80868EA: process_file (readelf.c:19588)
[0] ==5358== by 0x8086B01: main (readelf.c:19664)
// stack trace for the Alloc
==5358== Block was alloc'd at
==5358== at 0x402C17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
[3] ==5358== by 0x80AD97E: make_qualified_name (elfcomm.c:906)
[2] ==5358== by 0x8086350: process_archive (readelf.c:19435)
[1] ==5358== by 0x80868EA: process_file (readelf.c:19588)
[0] ==5358== by 0x8086B01: main (readelf.c:19664)
전처리 스크립트는 x86의 테스트된 바이너리와 Valgrind의 스택 추적을 입력으로 받아 다음 형식의 UAF 버그 추적(기본 블록 주소, 함수 이름)을 생성합니다:
[0] (0x8086ae1,main) -> [1] (0x80868de,process_file) -> [2] (0x808632c,process_archive) ->
[3, alloc] (0x80ad974,make_qualified_name) -> [4, free] (0x808663a,process_archive) ->
[5] (0x808620b,process_archive) -> [6, use] (0x80ab86a,error)
몇 가지 입력 매개변수(예: 실행할 퍼저, 시간 제한(분), 사전 정의된 대상(버그 보고서에서 추출))와 함께 퍼징 스크립트 템플릿을 제공합니다. 위의 예에서는 CVE-2018-20623.sh 스크립트를 사용하여 UAFuzz를 다음과 같이 실행합니다:
# Run UAFuzz with timeout 60 minutes
$UAFBENCH_PATH/CVE-2018-20623.sh uafuzz 60 $UAFBENCH_PATH/valgrind/CVE-2018-20623.valgrind
퍼징 시간 제한 후, UAFuzz는 어떤 입력이 예상 UAF 버그 추적의 모든 대상 위치를 순서대로 포함하는지 식별할 수 있습니다(예: ',all'로 끝나는 입력 이름). 따라서 UAFuzz는 Valgrind 또는 AddressSanitizer와 같은 기존 프로파일링 도구를 사용하여 원하는 버그를 트리거할 가능성이 있는 이러한 종류의 입력만 분류합니다.
GNU Patch의 CVE-2018-6952를 사용하여 수리 프로세스를 지원하기 위해 서로 다른 고유한 버그 트리거 입력을 생성하는 중요성을 설명합니다. GNU Patch에는 개발자가 수정한 이중 해제가 있었습니다(커밋 9c98635). 그러나 CVE-2018-6952의 스택 추적을 사용하여 UAFuzz는 최신 버전 2.7.6(커밋 76e7758)의 불완전한 버그 수정 CVE-2019-20633을 발견했으며, 버그 추적이 약간 다릅니다. 전반적으로 프로세스는 버그 재현 응용과 유사하지만, 대상 UAF 버그 추적을 식별하는 데 일부 수동 작업이 필요할 수 있습니다. fuzzing-corpus의 기존 버그에 대한 PoC 입력과 유효한 파일을 고품질 시드로 사용합니다.
# Fuzz patched version of CVE-2018-6952
$UAFBENCH_PATH/CVE-2019-20633.sh uafuzz 360 $UAFBENCH_PATH/valgrind/CVE-2018-6952.valgrind
가능한 하이브리드 접근 방식은 UAFuzz와 UAF에 대한 Ocaml로 작성된 유일한 바이너리 수준 정적 분석기인 GUEB를 결합하는 것입니다. 그러나 GUEB는 많은 거짓 양성을 생성하며 현재 복잡한 바이너리에서 제대로 작동하지 않습니다. 따라서 현재 GUEB를 개선하여 BINSEC에 통합한 다음 GUEB 보고서에서 추출된 대상을 사용하여 UAFuzz를 안내할 계획입니다. 계속 지켜봐 주십시오!