trailofbits/ruzzy

GitHub: trailofbits/ruzzy

Ruzzy 是 Trail of Bits 开发的面向 Ruby 代码和 Ruby C 扩展的覆盖率引导模糊测试工具,可用于自动发现安全漏洞和内存错误。

Stars: 113 | Forks: 7

# Ruzzy [![测试](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/6a195d4aaa105202.svg)](https://github.com/trailofbits/ruzzy/actions/workflows/test.yml) [![宝石版本](https://img.shields.io/gem/v/ruzzy)](https://rubygems.org/gems/ruzzy) 一个面向纯 Ruby 代码和 Ruby [C 扩展](https://ruby-doc.org/3.3.0/extension_rdoc.html)的覆盖率引导模糊测试工具。 Ruzzy 深受 Google 的 [Atheris](https://github.com/google/atheris)(Python 模糊测试工具)启发。与 Atheris 一样,Ruzzy 使用 [libFuzzer](https://llvm.org/docs/LibFuzzer.html) 进行覆盖率插桩和模糊测试引擎。在模糊测试 C 扩展时,Ruzzy 还支持 [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) 和 [UndefinedBehaviorSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html)。如果您想了解更多关于 Ruzzy 设计的灵感来源,请参阅我们的论文:[Design and Implementation of a Coverage-Guided Ruby Fuzzer](https://dl.acm.org/doi/10.1145/3675741.3675749)。 目录: - [安装](#installing) - [使用](#using) - [快速开始](#getting-started) - [模糊测试纯 Ruby 代码](#fuzzing-pure-ruby-code) - [模糊测试 Ruby C 扩展](#fuzzing-ruby-c-extensions) - [API](#api) - [FuzzedDataProvider](#fuzzeddataprovider) - [漏洞案例](#trophy-case) - [开发](#developing) - [编译](#compiling) - [测试](#testing) - [代码检查](#linting) - [发布](#releasing) - [延伸阅读](#further-reading) # 安装 目前,Ruzzy 仅支持 Linux x86-64 和 AArch64/ARM64。如果您想在 Mac 或 Windows 上运行 Ruzzy,可以构建 [`Dockerfile`](https://github.com/trailofbits/ruzzy/blob/main/Dockerfile) 和/或使用[开发环境](#developing)。Ruzzy 需要较新版本的 `clang`(已测试回溯到 `14.0.0`),建议使用[最新版本](https://github.com/llvm/llvm-project/releases)。 使用以下命令安装 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 ``` # 使用 ## 快速开始 Ruzzy 包含一个[示例程序](https://llvm.org/docs/LibFuzzer.html#toy-example)来演示其工作原理。首先,设置以下环境变量: ``` export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0" ```
理解这些选项并非必要,但如果您感兴趣可以点击此处查看。 ### `ASAN_OPTIONS` 1. 内存分配失败常见且影响较小(DoS),因此暂时跳过。 2. 与 Python 一样,Ruby 解释器[会泄漏数据](https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#leak-detection),因此暂时忽略这些。 3. Ruby 建议[禁用 sigaltstack](https://github.com/ruby/ruby/blob/master/doc/contributing/building_ruby.md#building-with-address-sanitizer)。
然后可以使用以下命令运行示例: ``` LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \ ruby -e 'require "ruzzy"; Ruzzy.dummy' ``` _`LD_PRELOAD` 的原因与 [Atheris](https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads) 相同。但是,与 `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.7.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`](https://github.com/trailofbits/ruzzy/blob/main/ext/dummy/dummy.c) 了解此违规发生的原因。 您可以使用以下命令重新运行崩溃用例: ``` LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \ ruby -e 'require "ruzzy"; Ruzzy.dummy' \ ./crash-253420c1158bc6382093d409ce2e9cff5806e980 ``` 以下 sanitizer 可用: - `Ruzzy::ASAN_PATH` 用于 [AddressSanitizer](https://clang.llvm.org/docs/AddressSanitizer.html) - `Ruzzy::UBSAN_PATH` 用于 [UndefinedBehaviorSanitizer](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) ## 模糊测试纯 Ruby 代码 让我们以一个小的 Ruby 脚本为例进行模糊测试。模糊测试纯 Ruby 代码需要两个 Ruby 脚本:tracer 脚本和模糊测试 harness。由于 Ruby 解释器的实现细节,tracer 脚本是必需的。除了它是必要的这一事实外,不需要了解此交互的细节。 首先,tracer 脚本,我们称之为 `test_tracer.rb`: ``` # frozen_string_literal: true require 'ruzzy' Ruzzy.trace('test_harness.rb') ``` 接下来,模糊测试 harness,我们称之为 `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 ': unhandled exception from /var/lib/gems/3.1.0/gems/ruzzy-0.7.0/lib/ruzzy.rb:15:in `c_fuzz' from /var/lib/gems/3.1.0/gems/ruzzy-0.7.0/lib/ruzzy.rb:15:in `fuzz' from /app/ruzzy/bin/test_harness.rb:35:in `' from bin/test_tracer.rb:7:in `require_relative' from bin/test_tracer.rb:7:in `
' ... 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` 以调用您的目标函数。 ## 模糊测试 Ruby C 扩展 让我们以 [`msgpack-ruby`](https://github.com/msgpack/msgpack-ruby) 库为例进行模糊测试。首先,安装 gem: ``` 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`。这些标志有助于模糊测试过程。它们启用了有用的功能,如 address sanitizer 和改进的堆栈跟踪信息。欲了解更多详细信息,请参阅 [AddressSanitizerFlags](https://github.com/google/sanitizers/wiki/AddressSanitizerFlags)。 接下来,我们需要一个用于 `msgpack` 的模糊测试 harness。对于有 [libFuzzer 经验](https://llvm.org/docs/LibFuzzer.html#fuzz-target)的人来说,以下内容可能很熟悉: ``` # 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 options](https://llvm.org/docs/LibFuzzer.html#options)。 要模糊测试您自己的目标,请修改 `test_one_input` `lambda` 以调用您的目标函数。 # API ## FuzzedDataProvider `Ruzzy::FuzzedDataProvider` 将原始模糊测试字节[拆分](https://github.com/google/fuzzing/blob/master/docs/split-inputs.md)为类型化的 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) ``` | 方法 | 描述 | 返回值 | |--------|-------------|---------| | `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` | 覆盖整个 double 范围的浮点数 | `Float` | | `consume_float_in_range(min, max)` | 在 [`min`, `max`] 范围内的浮点数 | `Float` | | `consume_probability` | 在 [0.0, 1.0] 范围内的浮点数 | `Float` | | `pick_value_in_list(list)` | 从 `list` 中随机选择一个元素 | 元素 | 当数据耗尽时,所有方法都返回默认值(`0`、`""`、`false`、`min`)。 # 漏洞案例 使用 Ruzzy 发现的漏洞: - `toml` gem:[#76](https://github.com/jm/toml/issues/76) - `toml-rb` gem:[#150](https://github.com/emancu/toml-rb/issues/150) - `ox` gem:[#351](https://github.com/ohler55/ox/issues/351) - Ruby `Marshal` 垃圾回收器崩溃:[#20941](https://bugs.ruby-lang.org/issues/20941) - XML 解析器差异:[REXML vs. Nokogiri](https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials/) # 开发 开发可以在本地进行,也可以使用本仓库中提供的 `Dockerfile`。 您可以使用以下命令构建 Ruzzy Docker 镜像: ``` docker build --tag ruzzy . ``` 然后,可以使用以下命令进入容器: ``` docker run -it -v $(pwd):/app/ruzzy --entrypoint /bin/bash ruzzy ``` ## 编译 我们使用 [`-compiler`](https://github.com/rake-compiler/rake-compiler) 来编译 Ruzzy 的 C 扩展。 您可以使用以下命令在容器中编译 C 扩展: ``` rake compile ``` ## 测试 我们使用 `rake` 单元测试来测试 Ruby 代码。 您可以使用以下命令在容器中运行测试: ``` LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \ rake test ``` ## 代码检查 我们使用 `rubocop` 来检查 Ruby 代码。 您可以使用以下命令在容器中运行 `rubocop`: ``` rubocop ``` ## 发布 当推送新的 git 标签时,Ruzzy 会自动[发布](https://github.com/trailofbits/ruzzy/actions/workflows/release.yml)到 [RubyGems](https://rubygems.org/gems/ruzzy)。 要发布新版本,请运行以下命令: ``` git tag vX.X.X ``` ``` git push --tags ``` # 延伸阅读 - Ruby C 扩展 - https://guides.rubygems.org/gems-with-extensions/ - https://www.rubyguides.com/2018/03/write-ruby-c-extension/ - https://rubyreferences.github.io/rubyref/advanced/extensions.html - https://silverhammermba.github.io/emberb/c/ - https://ruby-doc.org/3.3.0/extension_rdoc.html - https://ruby-doc.org/3.3.0/stdlibs/mkmf/MakeMakefile.html - https://github.com/flavorjones/ruby-c-extensions-explained - https://github.com/ruby/ruby/blob/v3_3_0/lib/mkmf.rb - Ruby 模糊测试 - https://github.com/twistlock/kisaten - https://github.com/richo/afl-ruby - https://github.com/krypt/FuzzBert - https://z2-2z.github.io/2024/jan/16/fuzzing-ruby-c-extensions-with-coverage-and-asan.html - https://bsidessf2018.sched.com/event/E6jC/fuzzing-ruby-and-c-extensions - Atheris - https://github.com/google/atheris/blob/master/native_extension_fuzzing.md - https://security.googleblog.com/2020/12/how-atheris-python-fuzzer-works.html - https://github.com/google/atheris/blob/2.3.0/setup.py - https://github.com/google/atheris/blob/2.3.0/src/native/core.cc - https://github.com/google/atheris/blob/2.3.0/src/native/tracer.cc - https://github.com/google/atheris/blob/2.3.0/src/native/counters.cc - https://github.com/google/atheris/blob/2.3.0/src/instrument_bytecode.py - 覆盖率 - https://calabi-yau.space/blog/sanitizer-coverage-interface.html - https://carstein.github.io/2020/05/21/writing-simple-fuzzer-4.html - https://h0mbre.github.io/Fuzzing-Like-A-Caveman-5/ - https://github.com/mirrorer/afl/blob/master/docs/technical_details.txt - https://lcamtuf.coredump.cx/afl/historical_notes.txt - https://www.code-intelligence.com/blog/the-magic-behind-feedback-based-fuzzing - https://blog.includesecurity.com/2024/04/coverage-guided-fuzzing-extending-instrumentation/ - https://git.sr.ht/~myrrc/ba-thesis/blob/master/thesis.pdf - https://www.politesi.polimi.it/bitstream/10589/173614/3/2021_04_Frighetto.pdf - https://wcventure.github.io/FuzzingPaper/Paper/SP18_ColLAFL.pdf - https://www.ndss-symposium.org/wp-content/uploads/2020/02/24422.pdf - https://mboehme.github.io/paper/ICSE22.pdf - https://www.usenix.org/system/files/raid2019-wang-jinghan.pdf
标签:AddressSanitizer, Clang, C扩展, fuzzing, libFuzzer, LLVM, pocsuite3, Ruby, Ruby安全, UndefinedBehaviorSanitizer, 二进制安全, 安全测试, 攻击性安全, 知识库, 覆盖引导测试, 软件测试