
Un fuzzer guiado por cobertura para código Ruby puro y extensiones C de Ruby
Un fuzzer guiado por cobertura para código Ruby puro y extensiones C de Ruby.
Ruzzy está fuertemente inspirado en Atheris, un fuzzer de Python. Ruzzy utiliza libFuzzer (o LibAFL) para su instrumentación de cobertura y su motor de fuzzing. Ruzzy también es compatible con AddressSanitizer y UndefinedBehaviorSanitizer al fuzzear extensiones C. Si desea obtener más información sobre la inspiración detrás de Ruzzy, consulte nuestro artículo: Design and Implementation of a Coverage-Guided Ruby Fuzzer.
Tabla de contenidos:
Ruzzy es compatible con Linux (x86-64, AArch64/ARM64) y macOS (Apple Silicon). En Windows, puede compilar el Dockerfile y/o utilizar el entorno de desarrollo. Ruzzy requiere una versión reciente de clang (probada hasta 14.0.0), preferiblemente la última versión. Para la configuración específica de macOS, consulte las notas para usuarios de macOS.
Instale Ruzzy con el siguiente 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
Hay mucho en juego aquí, así que vamos a desglosarlo:
MAKE sobrescribe el comando make al compilar la extensión C de Ruzzy. Esto le indica a make que respete las variables de entorno posteriores al compilar la extensión.clang adecuados. Esto garantiza que tengamos las funciones más recientes de clang, necesarias para un fuzzing adecuado.Si tiene problemas con la instalación, puede ejecutar el siguiente comando para obtener información de depuración:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Si su objetivo de fuzzing en Ruby puro hace un uso intensivo de regexps, instale regexp_parser:
gem install regexp_parser
Una vez instalado, Ruzzy utilizará automáticamente esta funcionalidad para muestrear (es decir, resolver) las regexps que encuentre durante el fuzzing. Esto permite que el fuzzer avance más allá de las condiciones de regexp y desbloquee cobertura adicional.
Ruzzy incluye un ejemplo de juguete para demostrar cómo funciona. Primero, configure la siguiente variable de entorno:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
ASAN_OPTIONSPuede ejecutar el ejemplo con el siguiente comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
LD_PRELOAD es necesario por las mismas razones que en Atheris. Sin embargo, a diferencia de ASAN_OPTIONS, probablemente no quiera exportarlo, ya que puede interferir con otros programas.
Debería producir rápidamente un fallo como el siguiente:
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 encontró correctamente la entrada ("HI") que produjo una violación de memoria. Para más información, consulte dummy.c para ver por qué ocurrió esta violación.
Puede volver a ejecutar el caso de fallo con el siguiente comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy' \
./crash-253420c1158bc6382093d409ce2e9cff5806e980
Los siguientes sanitizadores están disponibles:
Ruzzy::ASAN_PATH para AddressSanitizerRuzzy::UBSAN_PATH para UndefinedBehaviorSanitizerFuzzeemos un pequeño script de Ruby como ejemplo. El fuzzing de código Ruby puro requiere dos scripts de Ruby: un script de trazado (tracer) y un harness de fuzzing. El script de trazado es necesario debido a un detalle de implementación del intérprete de Ruby.
Primero, el script de trazado, llamémoslo test_tracer.rb:
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
A continuación, el harness de fuzzing, llamémoslo 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)
Puede ejecutar este archivo y comenzar a fuzzear con el siguiente comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Debería producir rápidamente un fallo como el siguiente: