
Fuzzer direcionado em nível binário, especializado na detecção de vulnerabilidades Use-After-Free por meio de métricas de entrada sensíveis à ordenação e análise estática, permitindo reprodução de bugs, testes de patches e verificação de relatórios.
Fuzzing Direcionado em Caixa Cinza (DGF) como o AFLGo visa realizar testes de estresse em locais alvo potencialmente vulneráveis pré-selecionados, com aplicações em diferentes contextos de segurança: (1) reprodução de bugs, (2) teste de patches ou (3) verificação de relatórios de análise estática. Recentemente, há mais trabalhos de pesquisa que melhoraram a eficácia e eficiência do fuzzing direcionado (veja awesome-directed-fuzzing).
Propomos o UAFuzz que é um fuzzer direcionado dedicado a bugs Use-After-Free (UAF) em nível binário ajustando cuidadosamente os componentes-chave do fuzzing direcionado para atender às características específicas desta classe de bug. Bugs UAF aparecem quando um elemento do heap é usado após ter sido liberado. Detectar bugs UAF é difícil: (1) complexidade porque uma entrada de Prova de Conceito (PoC) precisa disparar uma sequência de três eventos – alloc, free e use – no mesmo local de memória, abrangendo várias funções do programa testado e (2) silêncio sem falha de segmentação.
No geral, o UAFuzz tem um fluxo de trabalho semelhante aos fuzzers direcionados, com nossas modificações destacadas em laranja ao longo de todo o processo de fuzzing, conforme mostrado na figura a seguir. Como focamos em (1) reprodução de bugs e (2) aplicações de teste de patches, é mais provável que tenhamos stack traces (principalmente) completos de todos os eventos UAF relacionados à memória. Ao contrário das abordagens direcionadas gerais existentes, onde os alvos podem ser selecionados independentemente, levamos em consideração a relação entre os alvos (por exemplo, a ordenação que é essencial para UAFs) para melhorar o direcionamento. Primeiro, o pré-cálculo estático do UAFuzz é rápido em nível binário. Segundo, introduzimos novas métricas de entrada cientes da ordenação para guiar o fuzzer em direção aos alvos em tempo de execução. Finalmente, triamos apenas entradas potenciais que cobrem todos os alvos no trace esperado e pré-filtramos entradas para free que são menos prováveis de disparar o bug.
Mais detalhes em nosso artigo no RAID'20 e nossa palestra no Black Hat USA'20. Agradecimentos também a Sébastien Bardin, Matthieu Lemerre, Prof. Roland Groz e especialmente Richard Bonichon (@rbonichon) por sua ajuda em Ocaml.
Nosso ambiente testado é Ubuntu 16.04 64 bits.
# 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
Nosso fuzzer é construído sobre o AFL v2.52b no modo QEMU para fuzzing e BINSEC para análise estática leve (veja uafuzz/README.md). Atualmente usamos o IDA Pro v6.9 para extrair grafos de fluxo de controle (CFGs) e grafo de chamadas do binário testado (veja ida/README.md).
uafuzz
├── binsec/src
│ └── ida: a plugin to import and process IDA's CFGs and call graph
│ └── uafuzz: fuzzing code
│ │ └── afl-2.52b: core fuzzing built on top of AFL-QEMU
│ │ └── uafuzz_*.ml(i): a plugin to compute static information and communicate with AFL-QEMU
└── scripts: some scripts for building and bug triaging
Primeiro, consideramos um bug UAF simples. Tanto o AFL-QEMU quanto o fuzzer direcionado AFLGo com alvos em nível de código-fonte não conseguem detectar este bug em 6 horas, enquanto o UAFuzz pode detectá-lo em minutos com a ajuda de um relatório UAF do Valgrind.
# 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
Para programas do mundo real, usamos o UAF Fuzzing Benchmark para nossas avaliações.
# Checkout the benchmark
git clone https://github.com/strongcourage/uafbench.git
cd uafbench; export UAFBENCH_PATH=`pwd`
Mostramos em detalhes como executar o UAFuzz para aplicação de reprodução de bugs de CVE-2018-20623 do readelf (Binutils). Os stack traces deste bug UAF obtidos pelo Valgrind são os seguintes:
// 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)
O script de pré-processamento recebe o binário testado em x86 e os stack traces do Valgrind como entradas, e então gera o trace do bug UAF que é uma sequência de locais alvo no formato (basic_block_address,function_name) como o seguinte:
[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)
Fornecemos um template de script de fuzzing com vários parâmetros de entrada, por exemplo, o fuzzer que queremos executar, o tempo limite em minutos e os alvos predefinidos (ex.: extraídos do relatório de bug). Para o exemplo acima, usamos o script CVE-2018-20623.sh e executamos o UAFuzz como:
# Run UAFuzz with timeout 60 minutes
$UAFBENCH_PATH/CVE-2018-20623.sh uafuzz 60 $UAFBENCH_PATH/valgrind/CVE-2018-20623.valgrind
Após o tempo limite de fuzzing, o UAFuzz pode identificar quais entradas cobrem em sequência todos os locais alvo do trace do bug UAF esperado (ex.: nome de entrada que termina com ',all'). Assim, o UAFuzz tria apenas aqueles tipos de entradas que são prováveis de disparar o bug desejado usando ferramentas de profiling existentes como Valgrind ou AddressSanitizer.
Usamos CVE-2018-6952 do GNU Patch para ilustrar a importância de produzir diferentes entradas únicas que disparam bugs para favorecer o processo de reparo. Havia um double free no GNU Patch que foi corrigido pelos desenvolvedores (commit 9c98635). No entanto, ao usar os stack traces do CVE-2018-6952, o UAFuzz descobriu um reparo de bug incompleto CVE-2019-20633 da versão mais recente 2.7.6 (commit 76e7758), com uma pequena diferença no trace do bug. No geral, o processo é semelhante à aplicação de reprodução de bugs, exceto que algum trabalho manual pode ser necessário na identificação do trace do bug UAF alvo. Usamos entradas PoC de bugs existentes e arquivos válidos em fuzzing-corpus como sementes de alta qualidade.
# Fuzz patched version of CVE-2018-6952
$UAFBENCH_PATH/CVE-2019-20633.sh uafuzz 360 $UAFBENCH_PATH/valgrind/CVE-2018-6952.valgrind
Uma abordagem híbrida possível é combinar o UAFuzz com GUEB que é o único analisador estático em nível binário escrito em Ocaml para UAF. No entanto, o GUEB produz muitos falsos positivos e atualmente não é capaz de funcionar adequadamente com binários complexos. Então, atualmente melhoramos e integramos o GUEB no BINSEC e depois usamos alvos extraídos dos relatórios do GUEB para guiar o UAFuzz. Fique ligado!