
Um fuzzer guiado por cobertura para código Ruby puro e extensões C do Ruby.
Um fuzzer guiado por cobertura para código Ruby puro e extensões C do Ruby.
Ruzzy é fortemente inspirado no Atheris do Google, um fuzzer para Python. Ruzzy usa libFuzzer (ou LibAFL) para sua instrumentação de cobertura e mecanismo de fuzzing. Ruzzy também suporta AddressSanitizer e UndefinedBehaviorSanitizer ao fazer fuzzing de extensões C. Se você quiser saber mais sobre a inspiração por trás do Ruzzy, consulte nosso artigo: Design and Implementation of a Coverage-Guided Ruby Fuzzer.
Índice:
O Ruzzy suporta Linux (x86-64, AArch64/ARM64) e macOS (Apple Silicon). No Windows, você pode compilar o Dockerfile e/ou usar o ambiente de desenvolvimento. O Ruzzy requer uma versão recente do clang (testada desde a 14.0.0), de preferência a versão mais recente. Para configuração específica de macOS, consulte as notas para usuários de macOS.
Instale o Ruzzy com o seguinte comando:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
gem install ruzzy
Há muita coisa acontecendo aqui, então vamos detalhar:
MAKE substitui o comando make ao compilar a extensão C do Ruzzy. Isso diz ao make para respeitar as variáveis de ambiente subsequentes ao compilar a extensão.clang adequados. Isso garante que tenhamos os recursos mais recentes do clang, que são necessários para um fuzzing adequado.Se você tiver problemas para instalar, execute o seguinte comando para obter saída de depuração:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Se o seu alvo de fuzzing em Ruby puro fizer uso intenso de regexps, instale o regexp_parser:
gem install regexp_parser
Após a instalação, o Ruzzy usará automaticamente essa funcionalidade para amostrar (ou seja, resolver) regexps encontradas durante o fuzzing. Isso permite que o fuzzer avance além de condições de regexp e desbloqueie cobertura adicional.
O Ruzzy inclui um exemplo simples para demonstrar como funciona. Primeiro, defina a seguinte variável de ambiente:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
ASAN_OPTIONSVocê pode então executar o exemplo com o seguinte comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
LD_PRELOAD é necessário pelas mesmas razões do Atheris. No entanto, ao contrário de ASAN_OPTIONS, você provavelmente não vai querer fazer export dele, pois pode interferir em outros programas.
Ele deve produzir rapidamente uma falha (crash) como a seguinte:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2527961537
...
==45==ERROR: AddressSanitizer: heap-use-after-free on address 0x50c0009bab80 at pc 0xffff99ea1b44 bp 0xffffce8a67d0 sp 0xffffce8a67c8
...
SUMMARY: AddressSanitizer: heap-use-after-free /var/lib/gems/3.1.0/gems/ruzzy-0.8.0/ext/dummy/dummy.c:18:24 in _c_dummy_test_one_input
...
==45==ABORTING
MS: 4 EraseBytes-CopyPart-CopyPart-ChangeBit-; base unit: 410e5346bca8ee150ffd507311dd85789f2e171e
0x48,0x49,
HI
artifact_prefix='./'; Test unit written to ./crash-253420c1158bc6382093d409ce2e9cff5806e980
Base64: SEk=
Podemos ver que ele encontrou corretamente a entrada ("HI") que produziu uma violação de memória. Para mais informações, veja dummy.c para entender por que essa violação ocorreu.
Você pode executar novamente o caso de falha com o seguinte comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy' \
./crash-253420c1158bc6382093d409ce2e9cff5806e980
Os seguintes sanitizers estão disponíveis:
Ruzzy::ASAN_PATH para AddressSanitizerRuzzy::UBSAN_PATH para UndefinedBehaviorSanitizerVamos fazer fuzzing de um pequeno script Ruby como exemplo. Fazer fuzzing de código Ruby puro requer dois scripts Ruby: um script de rastreamento e um harness de fuzzing. O script de rastreamento é necessário devido a um detalhe de implementação do interpretador Ruby.
Primeiro, o script de rastreamento; vamos chamá-lo de test_tracer.rb:
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Em seguida, o harness de fuzzing; vamos chamá-lo de test_harness.rb:
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
if input.length == 4
if input[0] == 'F'
if input[1] == 'U'
if input[2] == 'Z'
if input[3] == 'Z'
raise
end
end
end
end
end
end
test_one_input = lambda do |data|
fuzzing_target(data) # Your fuzzing target would go here
return 0
end
Ruzzy.fuzz(test_one_input)
Você pode executar este arquivo e iniciar o fuzzing com o seguinte comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Ele deve produzir rapidamente uma falha como a seguinte: