
Фаззер, управляемый покрытием, для чистого кода Ruby и C-расширений Ruby
Фаззер с управлением покрытием для чистого Ruby-кода и C-расширений Ruby.
Ruzzy во многом вдохновлён Atheris от Google — фаззером для Python. Ruzzy использует libFuzzer (или LibAFL) для инструментирования покрытия и в качестве движка фаззинга. Ruzzy также поддерживает AddressSanitizer и UndefinedBehaviorSanitizer при фаззинге C-расширений. Если вы хотите узнать больше о том, что вдохновило Ruzzy, см. нашу статью: Design and Implementation of a Coverage-Guided Ruby Fuzzer.
Содержание:
Ruzzy поддерживает Linux (x86-64, AArch64/ARM64) и macOS (Apple Silicon). В Windows вы можете собрать Dockerfile и/или использовать окружение для разработки. Ruzzy требует свежую версию clang (проверена работа начиная с 14.0.0), желательно последний релиз. Инструкции для macOS см. в примечаниях для пользователей macOS.
Установите Ruzzy следующей командой:
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
Здесь происходит много всего, поэтому разберём по частям:
MAKE переопределяет команду make при компиляции C-расширения Ruzzy. Она указывает make учитывать последующие переменные окружения при компиляции расширения.clang. Это обеспечивает наличие новейших возможностей clang, необходимых для корректного фаззинга.Если при установке возникнут проблемы, выполните следующую команду, чтобы получить отладочный вывод:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Если ваша цель фаззинга на чистом Ruby активно использует регулярные выражения, установите regexp_parser:
gem install regexp_parser
После установки Ruzzy автоматически использует эту возможность для сэмплирования (т.е. решения) регулярных выражений, встречающихся во время фаззинга. Это позволяет фаззеру проходить условия с регулярными выражениями и открывать дополнительное покрытие.
Ruzzy включает игрушечный пример для демонстрации работы. Сначала задайте следующую переменную окружения:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
ASAN_OPTIONSЗатем можно запустить пример следующей командой:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
LD_PRELOAD требуется по тем же причинам, что и в Atheris. Однако, в отличие от ASAN_OPTIONS, вероятно, не стоит export'ить его, так как он может мешать другим программам.
Он должен быстро привести к падению, как показано ниже:
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=
Мы видим, что он корректно нашёл входные данные ("HI"), вызвавшие нарушение памяти. Дополнительную информацию о том, почему произошло это нарушение, см. в dummy.c.
Вы можете повторно запустить аварийный случай следующей командой:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy' \
./crash-253420c1158bc6382093d409ce2e9cff5806e980
Доступны следующие санитайзеры:
Ruzzy::ASAN_PATH для AddressSanitizerRuzzy::UBSAN_PATH для UndefinedBehaviorSanitizerДавайте в качестве примера отфаззим небольшой Ruby-скрипт. Фаззинг чистого Ruby-кода требует двух Ruby-скриптов: скрипт-трассировщик и фаззинг-обвязку (fuzzing harness). Скрипт-трассировщик необходим из-за особенности реализации интерпретатора Ruby.
Сначала скрипт-трассировщик, назовём его test_tracer.rb:
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Затем фаззинг-обвязка, назовём её 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)
Вы можете запустить этот файл и начать фаззинг следующей командой:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Он должен быстро привести к падению, как показано ниже: