
Ein abdeckungsgesteuerter Fuzzer für reinen Ruby-Code und Ruby-C-Erweiterungen.
Ein coverage-gesteuerter Fuzzer für reinen Ruby-Code und Ruby-C-Erweiterungen.
Ruzzy ist stark von Googles Atheris inspiriert, einem Python-Fuzzer. Ruzzy verwendet libFuzzer (oder LibAFL) für seine Coverage-Instrumentierung und als Fuzzing-Engine. Ruzzy unterstützt beim Fuzzing von C-Erweiterungen außerdem AddressSanitizer und UndefinedBehaviorSanitizer. Wenn Sie mehr über die Inspiration hinter Ruzzy erfahren möchten, lesen Sie unser Paper: Design and Implementation of a Coverage-Guided Ruby Fuzzer.
Inhaltsverzeichnis:
Ruzzy unterstützt Linux (x86-64, AArch64/ARM64) und macOS (Apple Silicon). Unter Windows können Sie das Dockerfile erstellen und/oder die Entwicklungsumgebung nutzen. Ruzzy benötigt eine aktuelle Version von clang (getestet ab 14.0.0), vorzugsweise die neueste Version. Informationen zur macOS-spezifischen Einrichtung finden Sie unter Hinweise für macOS-Benutzer.
Installieren Sie Ruzzy mit dem folgenden Befehl:
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
Hier passiert einiges; schauen wir uns das einmal genauer an:
MAKE überschreibt den make-Befehl beim Kompilieren der Ruzzy-C-Erweiterung. Dadurch wird make angewiesen, die nachfolgenden Umgebungsvariablen beim Kompilieren der Erweiterung zu berücksichtigen.clang-Binaries zum Einsatz kommen. So wird gewährleistet, dass die neuesten clang-Funktionen verfügbar sind, die für ein ordnungsgemäßes Fuzzing erforderlich sind.Sollten beim Installieren Probleme auftreten, können Sie den folgenden Befehl ausführen, um Debug-Ausgaben zu erhalten:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Wenn Ihr Fuzzing-Ziel für reinen Ruby-Code stark auf Regexps setzt, installieren Sie regexp_parser:
gem install regexp_parser
Nach der Installation nutzt Ruzzy diese Funktionalität automatisch, um Regexps, auf die es beim Fuzzing stößt, zu sampeln (d. h. zu lösen). Dadurch kann der Fuzzer an Regexp-Bedingungen vorbeigelangen und zusätzliche Coverage freischalten.
Ruzzy enthält ein Toy-Beispiel, um zu demonstrieren, wie es funktioniert. Setzen Sie zunächst die folgende Umgebungsvariable:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
ASAN_OPTIONSAnschließend können Sie das Beispiel mit dem folgenden Befehl ausführen:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
LD_PRELOAD wird aus denselben Gründen benötigt wie bei Atheris. Anders als ASAN_OPTIONS sollten Sie es jedoch wahrscheinlich nicht per export setzen, da es andere Programme stören kann.
Es sollte schnell einen Absturz wie den folgenden erzeugen:
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=
Wir können sehen, dass die Eingabe ("HI") korrekt gefunden wurde, die eine Speicherverletzung verursacht hat. Weitere Informationen dazu, warum diese Verletzung aufgetreten ist, finden Sie in dummy.c.
Sie können den Absturzfall mit dem folgenden Befehl erneut ausführen:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy' \
./crash-253420c1158bc6382093d409ce2e9cff5806e980
Die folgenden Sanitizer sind verfügbar:
Ruzzy::ASAN_PATH für AddressSanitizerRuzzy::UBSAN_PATH für UndefinedBehaviorSanitizerFuzzen wir als Beispiel ein kleines Ruby-Skript. Das Fuzzing von reinem Ruby-Code erfordert zwei Ruby-Skripte: ein Tracer-Skript und eine Fuzzing-Harness. Das Tracer-Skript ist aufgrund eines Implementierungsdetails des Ruby-Interpreters erforderlich.
Zuerst das Tracer-Skript; nennen wir es test_tracer.rb:
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Als Nächstes die Fuzzing-Harness; nennen wir sie 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)
Sie können diese Datei ausführen und mit dem folgenden Befehl das Fuzzing starten:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Es sollte schnell einen Absturz wie den folgenden erzeugen: