# # Copyright (c) 2006-2012 Hal Brodigan (postmodern.mod3 at gmail.com) # # This file is part of Ronin Support. # # Ronin Support 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 Support 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 Support. If not, see . # require 'ronin/extensions/regexp' require 'ronin/fuzzing/fuzzing' require 'combinatorics/generator' require 'combinatorics/list_comprehension' require 'combinatorics/power_set' require 'chars' class String # # Generate permutations of Strings from a format template. # # @param [Array(, )] template # The template which defines the string or character sets which will # make up parts of the String. # # @yield [string] # The given block will be passed each unique String. # # @yieldparam [String] string # A newly generated String. # # @return [Enumerator] # If no block is given, an Enumerator will be returned. # # @raise [ArgumentError] # A given character set name was unknown. # # @raise [TypeError] # A given string set was not a String, Symbol or Enumerable. # A given string set length was not an Integer or Enumerable. # # @example Generate Strings with ranges of repeating sub-strings: # # @example Generate Strings with three alpha chars and one numeric chars: # String.generate([:alpha, 3], :numeric) do |password| # puts password # end # # @example Generate Strings with two to four alpha chars: # String.generate([:alpha, 2..4]) do |password| # puts password # end # # @example Generate Strings using alpha and punctuation chars: # String.generate([Chars.alpha + Chars.punctuation, 4]) do |password| # puts password # end # # @example Generate Strings from a custom char set: # String.generate([['a', 'b', 'c'], 3], [['1', '2', '3'], 3]) do |password| # puts password # end # # @example Generate Strings containing known Strings: # String.generate("rock", [:numeric, 4]) do |password| # puts password # end # # @example Generate Strings with ranges of repeating sub-strings: # String.generate(['/AA', (1..100).step(5)]) do |path| # puts path # end # # @since 0.3.0 # # @api public # def self.generate(*template) return enum_for(:generate,*template) unless block_given? sets = [] template.each do |pattern| set, length = pattern set = case set when String [set].each when Symbol name = set.to_s.upcase unless Chars.const_defined?(name) raise(ArgumentError,"unknown charset #{set.inspect}") end Chars.const_get(name).each_char when Enumerable set else raise(TypeError,"set must be a String, Symbol or Enumerable") end case length when Integer length.times { sets << set.dup } when Array, Range sets << Combinatorics::Generator.new do |g| length.each do |sublength| superset = Array.new(sublength) { set.dup } superset.comprehension { |strings| g.yield strings.join } end end when nil sets << set else raise(TypeError,"length must be an Integer, Range or Array") end end sets.comprehension do |strings| new_string = '' strings.each do |string| new_string << case string when Integer string.chr else string.to_s end end yield new_string end return nil end # # Repeats the String. # # @param [Enumerable, Integer] n # The number of times to repeat the String. # # @yield [repeated] # The given block will be passed every repeated String. # # @yieldparam [String] repeated # A repeated version of the String. # # @return [Enumerator] # If no block is given, an Enumerator will be returned. # # @raise [TypeError] # `n` must either be Enumerable or an Integer. # # @example # 'A'.repeating(100) # # => "AAAAAAAAAAAAA..." # # @example Generates 100 upto 700 `A`s, increasing by 100 at a time: # 'A'.repeating((100..700).step(100)) do |str| # # ... # end # # @example Generates 128, 1024, 65536 `A`s: # 'A'.repeating([128, 1024, 65536]) do |str| # # ... # end # # @api public # # @since 0.4.0 # def repeating(n) if n.kind_of?(Integer) # if n is an Integer, simply multiply the String and return repeated = (self * n) yield repeated if block_given? return repeated end return enum_for(:repeating,n) unless block_given? unless n.kind_of?(Enumerable) raise(TypeError,"argument must be Enumerable or an Integer") end n.each do |length| yield(self * length) end return self end # # Incrementally fuzzes the String. # # @param [Hash{Regexp,String => #each}] substitutions # Patterns and their substitutions. # # @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. # # @example Replace every `e`, `i`, `o`, `u` with `(`, 100 `A`s and a `\0`: # "the quick brown fox".fuzz(/[eiou]/ => ['(', ('A' * 100), "\0"]) do |str| # p str # end # # @example {String.generate} with {String#fuzz}: # "GET /".fuzz('/' => String.generate(['A', 1..100])) do |str| # p str # end # # @since 0.3.0 # # @api public # def fuzz(substitutions={}) return enum_for(:fuzz,substitutions) unless block_given? substitutions.each do |pattern,substitution| pattern = case pattern when Regexp pattern when String Regexp.new(Regexp.escape(pattern)) when Symbol Regexp.const_get(pattern.to_s.upcase) else raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp") end substitution = case substitution when Enumerable substitution when Symbol Ronin::Fuzzing[substitution] else raise(TypeError,"substitutions must be Enumerable or a Symbol") end scanner = StringScanner.new(self) 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 substitute.call(self[index,length]) when Integer substitute.chr else substitute.to_s end fuzzed = dup fuzzed[index,length] = substitute yield fuzzed end end end end # # Permutes over every possible mutation of the String. # # @param [Hash{Regexp,String,Symbol => Symbol,Enumerable}] mutations # The patterns and substitutions to mutate the String with. # # @yield [mutant] # The given block will be yielded every possible mutant String. # # @yieldparam [String] mutant # A mutated String. # # @return [Enumerator] # If no block is given, an Enumerator will be returned. # # @raise [TypeError] # A mutation pattern was not a Regexp, String or Symbol. # A mutation substitution was not a Symbol or Enumerable. # # @example # "hello old dog".mutate('e' => ['3'], 'l' => ['1'], 'o' => ['0']) do |str| # puts str # end # # @since 0.4.0 # # @api public # def mutate(mutations={}) return enum_for(:mutate,mutations) unless block_given? matches = Set[] mutations.each do |pattern,mutation| pattern = case pattern when Regexp pattern when String Regexp.new(Regexp.escape(pattern)) when Symbol Regexp.const_get(pattern.to_s.upcase) else raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp") end mutation = case mutation when Symbol Ronin::Fuzzing[mutation] when Enumerable mutation else raise(TypeError,"mutation #{mutation.inspect} must be a Symbol or Enumerable") end scanner = StringScanner.new(self) while scanner.scan_until(pattern) length = scanner.matched_size index = scanner.pos - length original = scanner.matched mutator = Combinatorics::Generator.new do |g| mutation.each do |mutate| g.yield case mutate when Proc mutate.call(original) when Integer mutate.chr else mutate.to_s end end end matches << [index, length, mutator] end end matches.powerset do |submatches| # ignore the empty Set next if submatches.empty? # sort the submatches by index submatches = submatches.sort_by { |index,length,mutator| index } sets = [] prev_index = 0 submatches.each do |index,length,mutator| # add the previous substring to the set of Strings if index > prev_index sets << [self[prev_index,index - prev_index]] end # add the mutator to the set of Strings sets << mutator prev_index = index + length end # add the remaining substring to the set of Strings if prev_index < self.length sets << [self[prev_index..-1]] end sets.comprehension { |strings| yield strings.join } end end end