
एक कवरेज-निर्देशित फ़ज़र जो शुद्ध Ruby कोड और Ruby C एक्सटेंशन के लिए है
शुद्ध Ruby कोड और Ruby C एक्सटेंशन के लिए एक कवरेज-निर्देशित फज़र।
Ruzzy, Google के Atheris (एक Python फज़र) से बहुत प्रभावित है। Ruzzy अपने कवरेज इंस्ट्रुमेंटेशन और फ़ज़िंग इंजन के लिए libFuzzer (या LibAFL) का उपयोग करता है। Ruzzy, C एक्सटेंशन की फ़ज़िंग करते समय AddressSanitizer और UndefinedBehaviorSanitizer का भी समर्थन करता है। यदि आप 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 पर्यावरण चर Ruzzy C एक्सटेंशन को संकलित करते समय make कमांड को ओवरराइड करता है। यह 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 AddressSanitizer के लिएRuzzy::UBSAN_PATH UndefinedBehaviorSanitizer के लिएआइए एक उदाहरण के रूप में एक छोटी Ruby स्क्रिप्ट को फ़ज़ करें। शुद्ध Ruby कोड की फ़ज़िंग के लिए दो Ruby स्क्रिप्ट की आवश्यकता होती है: एक ट्रेसर स्क्रिप्ट और एक फ़ज़िंग हार्नेस। 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
इसे शीघ्र ही निम्नलिखित जैसी क्रैश उत्पन्न करनी चाहिए:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 2311041000
...
/app/ruzzy/bin/test_harness.rb:12:in `block in <top (required)>': unhandled exception
from /var/lib/gems/3.1.0/gems/ruzzy-0.8.0/lib/ruzzy.rb:15:in `c_fuzz'
from /var/lib/gems/3.1.0/gems/ruzzy-0.8.0/lib/ruzzy.rb:15:in `fuzz'
from /app/ruzzy/bin/test_harness.rb:35:in `<top (required)>'
from bin/test_tracer.rb:7:in `require_relative'
from bin/test_tracer.rb:7:in `<main>'
...
SUMMARY: libFuzzer: fuzz target exited
MS: 1 CopyPart-; base unit: 24b4b428cf94c21616893d6f94b30398a49d27cc
0x46,0x55,0x5a,0x5a,
FUZZ
artifact_prefix='./'; Test unit written to ./crash-aea2e3923af219a8956f626558ef32f30a914ebc
Base64: RlVaWg==
हम देख सकते हैं कि इसने सही ढंग से उस इनपुट ("FUZZ") को खोज लिया जिसने एक अपवाद उत्पन्न किया।
अपने स्वयं के लक्ष्य को फ़ज़ करने के लिए, test_one_input lambda को संशोधित करें ताकि वह आपके लक्ष्य फ़ंक्शन को कॉल करे।
आइए एक उदाहरण के रूप में msgpack-ruby लाइब्रेरी को फ़ज़ करें। पहले, जेम स्थापित करें:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install msgpack
Ruzzy को संकलित करते समय उपयोग किए जाने वाले पर्यावरण चरों के अलावा, हम CFLAGS और CXXFLAGS निर्दिष्ट कर रहे हैं। ये फ़्लैग फ़ज़िंग प्रक्रिया में सहायता करते हैं। वे एड्रेस सैनिटाइज़र और बेहतर स्टैक ट्रेस जानकारी जैसी उपयोगी कार्यक्षमताएँ सक्षम करते हैं। अधिक जानकारी के लिए AddressSanitizerFlags देखें।
इसके बाद, हमें msgpack के लिए एक फ़ज़िंग हार्नेस चाहिए। निम्नलिखित libFuzzer अनुभव वालों से परिचित हो सकता है:
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# We're looking for memory corruption, not Ruby exceptions
end
return 0
end
Ruzzy.fuzz(test_one_input)
चलिए इस फ़ाइल को fuzz_msgpack.rb कहते हैं। आप इस फ़ाइल को चला सकते हैं और निम्नलिखित कमांड से फ़ज़िंग शुरू कर सकते हैं:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
libFuzzer विकल्पों को Ruby स्क्रिप्ट में इस प्रकार पास किया जा सकता है:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb /path/to/corpus
अधिक जानकारी के लिए libFuzzer विकल्प देखें।
अपने स्वयं के लक्ष्य को फ़ज़ करने के लिए, test_one_input lambda को संशोधित करें ताकि वह आपके लक्ष्य फ़ंक्शन को कॉल करे।
Ruzzy मॉड्यूल शीर्ष-स्तरीय प्रवेश बिंदुओं को उजागर करता है।
Ruzzy::FuzzedDataProvider रॉ फ़ज़र बाइट्स को टाइप किए गए Ruby मानों में विभाजित करता है।
test_one_input = lambda do |data|
fdp = Ruzzy::FuzzedDataProvider.new(data)
name = fdp.consume_random_length_string(50)
age = fdp.consume_int_in_range(0, 150)
score = fdp.consume_float_in_range(0.0, 100.0)
role = fdp.pick_value_in_list(['admin', 'user', 'guest'])
User.new(name: name, age: age, score: score, role: role).validate!
end
Ruzzy.fuzz(test_one_input)
जब डेटा समाप्त हो जाता है तो सभी विधियाँ डिफ़ॉल्ट मान (0, "", false, min) लौटाती हैं।
macOS पर Ruzzy को Homebrew-स्थापित LLVM (Apple Clang में libFuzzer शामिल नहीं है) और एक गैर-सिस्टम Ruby की आवश्यकता होती है (सिस्टम Ruby /usr/bin/ruby SIP-संरक्षित है, जो Ruby शुरू होने से पहले DYLD_* पर्यावरण चरों को हटा देता है)।
brew install llvm ruby
कोई भी गैर-सिस्टम Ruby काम करता है (brew, rbenv, asdf), लेकिन शिम-आधारित संस्करण प्रबंधकों के लिए नीचे चेतावनियाँ देखें।
Homebrew Clang पथ और macOS-उपयुक्त लिंकर फ़्लैग का उपयोग करें:
MAKE="make --environment-overrides V=1" \
CC="$(brew --prefix llvm)/bin/clang" \
CXX="$(brew --prefix llvm)/bin/clang++" \
LDSHARED="$(brew --prefix llvm)/bin/clang -dynamic -bundle -undefined dynamic_lookup" \
LDSHAREDXX="$(brew --prefix llvm)/bin/clang++ -dynamic -bundle -undefined dynamic_lookup" \
gem install ruzzy
LD_PRELOAD के बजाय DYLD_INSERT_LIBRARIES का उपयोग करें:
DYLD_INSERT_LIBRARIES=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
macOS पर Ruzzy::ASAN_PATH और Ruzzy::UBSAN_PATH .dylib फ़ाइलों में हल होते हैं।
asdf, rbenv) DYLD_* env vars हटाते हैं। ये शिम #!/usr/bin/env bash का उपयोग करते हैं और /usr/bin/env SIP-संरक्षित है, इसलिए macOS Ruby शुरू होने से पहले DYLD_INSERT_LIBRARIES हटा देता है। या तो Homebrew Ruby का उपयोग करें (जिसमें शिम नहीं है) या स्थापित Ruby बाइनरी का पूर्ण पथ लागू करें:
DYLD_INSERT_LIBRARIES=$(/path/to/ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
/path/to/ruby your_fuzzer.rb
DYLD_INSERT_LIBRARIES में ASan dylib को शामिल करने से स्टार्टअप के दौरान प्रक्रिया हैंग हो जाती है। यदि आप Ruzzy को लॉन्च पर हैंग होते देखते हैं, तो Homebrew LLVM को अपडेट करें (brew upgrade llvm).Ruzzy का उपयोग करके पाए गए बग:
toml जेम: #76toml-rb जेम: #150ox जेम: #351, #410Marshal कचरा संग्रहकर्ता क्रैश: #20941redcarpet जेम: #813विकास स्थानीय रूप से या इस रिपॉजिटरी में दिए गए Dockerfile का उपयोग करके किया जा सकता है।
आप निम्नलिखित कमांड से Ruzzy Docker इमेज बना सकते हैं:
docker build --tag ruzzy .
फिर, निम्नलिखित कमांड का उपयोग करके कंटेनर में शेल में प्रवेश कर सकते हैं:
docker run -it -v $(pwd):/app/ruzzy --entrypoint /bin/bash ruzzy
हम Ruzzy के C एक्सटेंशन को संकलित करने के लिए rake-compiler का उपयोग करते हैं।
आप निम्नलिखित कमांड से कंटेनर के अंदर C एक्सटेंशन संकलित कर सकते हैं:
rake compile
हम Ruby कोड का परीक्षण करने के लिए rake यूनिट टेस्ट का उपयोग करते हैं।
आप निम्नलिखित कमांड से कंटेनर के अंदर परीक्षण चला सकते हैं:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
rake test
हम Ruby कोड को लिंट करने के लिए rubocop का उपयोग करते हैं।
आप निम्नलिखित कमांड से कंटेनर के अंदर rubocop चला सकते हैं:
rubocop
जब कोई नया git टैग पुश किया जाता है तो Ruzzy स्वचालित रूप से RubyGems पर जारी किया जाता है।
नया संस्करण जारी करने के लिए निम्नलिखित कमांड चलाएँ:
git tag vX.X.X
git push --tags
| विधि | विवरण |
|---|
Ruzzy.fuzz(test_one_input, args = DEFAULT_ARGS) | test_one_input (एक proc/lambda जो रॉ बाइट्स लेता है) को फ़ज़ करें। |
Ruzzy.trace(harness_script) | harness_script को Ruby शाखा-कवरेज इंस्ट्रुमेंटेशन के साथ लपेटें, फिर उसे require करें। शुद्ध-Ruby फ़ज़िंग के लिए आवश्यक। |
Ruzzy.dummy | बंडल किए गए टॉय हार्नेस (heap-use-after-free डेमो) को फ़ज़ करें। |
Ruzzy.dummy_test_one_input(data) | टॉय हार्नेस स्वयं। |
| स्थिरांक | विवरण |
|---|
Ruzzy::ASAN_PATH | ASan + फ़ज़र रैपर का पथ। LD_PRELOAD (Linux) / DYLD_INSERT_LIBRARIES (macOS) के साथ उपयोग करें। |
Ruzzy::UBSAN_PATH | वही, UBSan के लिए। |
Ruzzy::EXT_PATH | ext/cruzzy बिल्ड निर्देशिका का पथ। |
Ruzzy::DEFAULT_ARGS | फ़ज़र को पास किए गए डिफ़ॉल्ट तर्क। |
| विधि | विवरण | रिटर्न |
|---|
remaining_bytes | शेष अप्रयुक्त बाइट्स की संख्या | Integer |
consume_bytes(count) | count तक रॉ बाइट्स का उपभोग करें | बाइनरी String |
consume_random_length_string(max_length) | एक परिवर्तनीय-लंबाई स्ट्रिंग का उपभोग करें; गैर-\ बाइट के बाद \ पर समाप्त होता है | String |
consume_remaining_bytes | सभी शेष बाइट्स का उपभोग करें | बाइनरी String |
consume_remaining_as_string | consume_remaining_bytes का उपनाम | बाइनरी String |
consume_uint(count) | count बाइट्स से अहस्ताक्षरित पूर्णांक | Integer |
consume_int(count) | count बाइट्स से हस्ताक्षरित (दो का पूरक) पूर्णांक | Integer |
consume_int_in_range(min, max) | [min, max] में समान रूप से वितरित पूर्णांक | Integer |
consume_bool | एक बाइट (LSB) से बूलियन | true/false |
consume_float | पूर्ण डबल श्रेणी में फैला फ्लोट | Float |
consume_float_in_range(min, max) | [min, max] में फ्लोट | Float |
consume_probability | [0.0, 1.0] में फ्लोट | Float |
pick_value_in_list(list) | list से यादृच्छिक तत्व | तत्व |