
Un fuzzer guidé par la couverture pour le code Ruby pur et les extensions C de Ruby
Un fuzzer guidé par la couverture pour le code Ruby pur et les extensions C de Ruby.
Ruzzy est fortement inspiré par Atheris de Google, un fuzzer pour Python. Ruzzy utilise libFuzzer (ou LibAFL) pour son instrumentation de couverture et son moteur de fuzzing. Ruzzy prend également en charge AddressSanitizer et UndefinedBehaviorSanitizer lors du fuzzing d'extensions C. Si vous souhaitez en savoir plus sur ce qui a inspiré Ruzzy, consultez notre article : Design and Implementation of a Coverage-Guided Ruby Fuzzer.
Table des matières :
Ruzzy prend en charge Linux (x86-64, AArch64/ARM64) et macOS (Apple Silicon). Sous Windows, vous pouvez construire le Dockerfile et/ou utiliser l'environnement de développement. Ruzzy nécessite une version récente de clang (testée jusqu'à 14.0.0), de préférence la dernière version. Pour une configuration spécifique à macOS, voir notes pour les utilisateurs de macOS.
Installez Ruzzy avec la commande suivante :
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
Il y a beaucoup de choses ici, alors décomposons-les :
MAKE remplace la commande make lors de la compilation de l'extension C de Ruzzy. Cela indique à make de respecter les variables d'environnement suivantes lors de la compilation de l'extension.clang. Cela nous assure de disposer des dernières fonctionnalités de clang, nécessaires pour un fuzzing correct.Si vous rencontrez des problèmes d'installation, vous pouvez exécuter la commande suivante pour obtenir une sortie de débogage :
RUZZY_DEBUG=1 gem install --verbose ruzzy
Si votre cible de fuzzing en Ruby pur utilise massivement des expressions régulières, installez regexp_parser :
gem install regexp_parser
Une fois installé, Ruzzy utilisera automatiquement cette fonctionnalité pour échantillonner (c'est-à-dire résoudre) les expressions régulières qu'il rencontre lors du fuzzing. Cela permet au fuzzer de dépasser les conditions liées aux expressions régulières et de débloquer une couverture supplémentaire.
Ruzzy inclut un exemple jouet pour démontrer son fonctionnement. Tout d'abord, définissez la variable d'environnement suivante :
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
ASAN_OPTIONSVous pouvez ensuite exécuter l'exemple avec la commande suivante :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
LD_PRELOAD est requis pour les mêmes raisons qu'Atheris. Cependant, contrairement à ASAN_OPTIONS, vous ne voulez probablement pas l'exporter car cela pourrait interférer avec d'autres programmes.
Il devrait rapidement produire un crash comme celui-ci :
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=
Nous pouvons voir qu'il a correctement trouvé l'entrée ("HI") qui a produit une violation mémoire. Pour plus d'informations, voir dummy.c pour comprendre pourquoi cette violation s'est produite.
Vous pouvez ré-exécuter le cas de crash avec la commande suivante :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy' \
./crash-253420c1158bc6382093d409ce2e9cff5806e980
Les sanitizers suivants sont disponibles :
Ruzzy::ASAN_PATH pour AddressSanitizerRuzzy::UBSAN_PATH pour UndefinedBehaviorSanitizerFuzzeons un petit script Ruby comme exemple. Le fuzzing de code Ruby pur nécessite deux scripts Ruby : un script de trace et un harnais de fuzzing. Le script de trace est nécessaire en raison d'un détail d'implémentation de l'interpréteur Ruby.
Tout d'abord, le script de trace, appelons-le test_tracer.rb :
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Ensuite, le harnais de fuzzing, appelons-le 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)
Vous pouvez exécuter ce fichier et commencer le fuzzing avec la commande suivante :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Il devrait rapidement produire un crash comme celui-ci :