
Un fuzzer guidato dalla copertura per codice Ruby puro ed estensioni C di Ruby.
Un fuzzer guidato dalla copertura per codice Ruby puro ed estensioni C di Ruby.
Ruzzy è fortemente ispirato ad Atheris di Google, un fuzzer per Python. Ruzzy usa libFuzzer (o LibAFL) per la strumentazione della copertura e il motore di fuzzing. Ruzzy supporta anche AddressSanitizer e UndefinedBehaviorSanitizer quando si esegue il fuzzing di estensioni C. Se vuoi saperne di più sull'ispirazione alla base di Ruzzy, consulta il nostro articolo: Design and Implementation of a Coverage-Guided Ruby Fuzzer.
Indice dei contenuti:
Ruzzy supporta Linux (x86-64, AArch64/ARM64) e macOS (Apple Silicon). Su Windows, puoi creare il Dockerfile e/o usare l'ambiente di sviluppo. Ruzzy richiede una versione recente di clang (testata fino alla 14.0.0), preferibilmente l'ultima release. Per la configurazione specifica di macOS, vedi note per gli utenti macOS.
Installa Ruzzy con il seguente 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
C'è molto da spiegare qui, quindi analizziamolo:
MAKE sovrascrive il comando make durante la compilazione dell'estensione C di Ruzzy. Questo dice a make di rispettare le successive variabili d'ambiente durante la compilazione dell'estensione.clang corretti. Questo assicura di avere le funzionalità più recenti di clang, necessarie per un corretto fuzzing.Se riscontri problemi durante l'installazione, puoi eseguire il seguente comando per ottenere un output di debug:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Se il tuo target di fuzzing in Ruby puro fa un uso intensivo delle regexp, installa regexp_parser:
gem install regexp_parser
Una volta installato, Ruzzy userà automaticamente questa funzionalità per campionare (cioè risolvere) le regexp che incontra durante il fuzzing. Questo permette al fuzzer di superare le condizioni basate su regexp e sbloccare ulteriore copertura.
Ruzzy include un esempio giocattolo per dimostrare come funziona. Per prima cosa, imposta la seguente variabile d'ambiente:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
ASAN_OPTIONSPuoi quindi eseguire l'esempio con il seguente comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
LD_PRELOAD è richiesto per gli stessi motivi di Atheris. Tuttavia, a differenza di ASAN_OPTIONS, probabilmente non vorrai esportarlo con export poiché potrebbe interferire con altri programmi.
Dovrebbe produrre rapidamente un crash come il seguente:
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=
Possiamo vedere che ha trovato correttamente l'input ("HI") che ha prodotto una violazione di memoria. Per maggiori informazioni, vedi dummy.c per capire perché si è verificata questa violazione.
Puoi rieseguire il caso di crash con il seguente comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy' \
./crash-253420c1158bc6382093d409ce2e9cff5806e980
I seguenti sanitizer sono disponibili:
Ruzzy::ASAN_PATH per AddressSanitizerRuzzy::UBSAN_PATH per UndefinedBehaviorSanitizerFacciamo il fuzzing di un piccolo script Ruby come esempio. Il fuzzing di codice Ruby puro richiede due script Ruby: uno script tracer e un harness di fuzzing. Lo script tracer è necessario a causa di un dettaglio implementativo dell'interprete Ruby.
Prima, lo script tracer, chiamiamolo test_tracer.rb:
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Poi, l'harness di fuzzing, chiamiamolo 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)
Puoi eseguire questo file e iniziare il fuzzing con il seguente comando:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Dovrebbe produrre rapidamente un crash come il seguente: