ronin-rb/ronin-fuzzer

View on GitHub
lib/ronin/fuzzing/fuzzer.rb

Summary

Maintainability
A
1 hr
Test Coverage
#
# ronin-fuzzer - A Ruby library for generating, mutating, and fuzzing data.
#
# Copyright (c) 2006-2024 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of ronin-fuzzer.
#
# ronin-fuzzer is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-fuzzer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-fuzzer.  If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/fuzzing'
require 'ronin/support/text/patterns'

require 'strscan'

module Ronin
  module Fuzzing
    #
    # Fuzzing class that incrementally fuzzes a String, given substitution
    # rules.
    #
    # @api semipublic
    #
    class Fuzzer

      PATTERNS = Support::Text::Patterns

      # Patterns and their substitutions
      attr_reader :rules

      #
      # Initializes a new Fuzzer.
      #
      # @param [Hash{Regexp,String,Symbol => Enumerable,Symbol}] rules
      #   Patterns and their substitutions.
      #
      def initialize(rules)
        @rules = {}
        
        rules.each do |pattern,substitution|
          pattern = case pattern
                    when Regexp then pattern
                    when String then Regexp.new(Regexp.escape(pattern))
                    when Symbol then PATTERNS.const_get(pattern.upcase)
                    else
                      raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
                    end

          substitution = case substitution
                         when Enumerable then substitution
                         when Symbol     then Fuzzing[substitution]
                         else
                           raise(TypeError,"substitutions must be Enumerable or a Symbol")
                         end

          @rules[pattern] = substitution
        end
      end

      #
      # Incrementally fuzzes the String.
      #
      # @yield [fuzz]
      #   The given block will be passed every fuzzed String.
      #
      # @yieldparam [String] fuzz
      #   A fuzzed String.
      #
      # @return [Enumerator]
      #   If no block is given, an Enumerator will be returned.
      #
      def each(string)
        return enum_for(__method__,string) unless block_given?

        @rules.each do |pattern,substitution|
          scanner = StringScanner.new(string)
          indices = []

          while scanner.scan_until(pattern)
            indices << [scanner.pos - scanner.matched_size, scanner.matched_size]
          end

          indices.each do |index,length|
            substitution.each do |substitute|
              substitute = case substitute
                           when Proc    then substitute[string[index,length]]
                           when Integer then substitute.chr
                           else              substitute.to_s
                           end

              fuzzed = string.dup
              fuzzed[index,length] = substitute
              yield fuzzed
            end
          end
        end
      end

    end
  end
end