
Fuzzing de alto desempenho usando traduções binárias de riscv para x86 e técnicas modernas de fuzzing
Data de início: dezembro de 2021
Este é um fuzzer greybox baseado em emulação e guiado por cobertura que utiliza um compilador Just-In-Time personalizado para alcançar desempenho quase nativo. Ele funciona elevando binários ELF RISC-V para uma representação intermediária antes de compilá-los via JIT para x86 durante a execução. Durante a compilação JIT, o código é instrumentado para permitir melhorias de fuzzing, como rastreamento de cobertura, asan, cmpcov ou fuzzing baseado em snapshots.

O objetivo deste projeto é destacar os benefícios de usar um ambiente emulado para fuzzing. Muitos fuzzers anteriores baseados em emulação existem, mas quase todos usam exclusivamente o mecanismo de emulação qemu para a emulação subjacente. Embora esse mecanismo tenha um compilador just-in-time bastante maduro e gere código muito bom, ele não foi projetado para fuzzing. Durante o fuzzing, pretendemos executar o mesmo processo milhares de vezes por segundo. Isso abre espaço para otimizações especializadas das quais o qemu não faz uso intenso, como reutilizar o mesmo espaço de memória para cada execução do processo e redefinir apenas uma quantidade limitada de memória por meio de mecanismos de dirty bits.
De muitas maneiras, isso é mais uma prova de conceito na qual eu queria trabalhar para aprender sobre o funcionamento interno de compiladores e ter um playground baseado em emulação para experimentar várias técnicas de fuzzing, como diferentes métricas de cobertura, escalonadores de seeds e fuzzing baseado em snapshots. Com mais otimizações de JIT e, principalmente, extensões para incluir arquiteturas mais populares como mips ou arm, isso poderia, no entanto, certamente ser usado para realizar fuzzing eficiente em código-fonte fechado que não pode simplesmente ser instrumentado por meio de recompilação.
Com base nos testes que fiz até agora, o sfuzz tem significativamente menos overhead do que muitos outros fuzzers populares, o que resulta em desempenho muito rápido, especialmente para casos de fuzz pequenos.
Mais detalhes sobre os recursos/escolhas feitas para este fuzzer estão listados no blogpost que o acompanha (https://seal9055.com/blog/fuzzing/sfuzz) e nos arquivos de documentação listados abaixo:
Todo este fuzzer é escrito em rust; portanto, após clonar o repositório, basta executar cargo build --release para compilar.
Como o fuzzer atualmente suporta apenas RISC-V, o alvo precisa ser compilado para RISC-V usando o toolchain abaixo (ou um similar). Alternativamente, se você já tiver um binário RISC-V, isso funcionará perfeitamente bem também.
Uma vez configurado, basta criar diretórios de entrada/saída, adicionar alguns arquivos de seed iniciais ao diretório de entrada e iniciar o fuzzer.
./sfuzz -i in -o out -- ./test_cases/simple_test @@
Flags adicionais podem ser passadas por meio de opções de linha de comando para especificar o número de threads, habilitar fuzzing com snapshots, adicionar um dicionário ao mutador etc. As opções adicionais podem ser listadas executando sfuzz com a flag -h.
Se você deseja testar o fuzzer contra alguns alvos de complexidade variada, o progrem_generator em tools/program_generator pode ser usado para gerar automaticamente programas de complexidade variada. Observe que você precisará de um toolchain RISC-V para então compilar o alvo.
Isso configura um toolchain para compilar binários riscv que podem ser carregados/usados por este projeto.
Riscv compiler/tooling:
sudo apt-get install autoconf automake autotools-dev curl python3 libmpc-dev libmpfr-dev \
libgmp-dev gawk build-essential bison flex texinfo gperf libtool patchutils bc zlib1g-dev \
libexpat-dev
git clone https://github.com/riscv/riscv-gnu-toolchain && cd riscv-gnu-toolchain
./configure --prefix=/opt/riscv --with-arch=rv64i
sudo make
Debugger:
gdb-multiarch
Esta lista representa um conjunto de recursos que pretendo implementar no futuro.