ronin-fuzzer 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.github/workflows/ruby.yml +31 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +165 -0
- data/ChangeLog.md +9 -0
- data/Gemfile +39 -0
- data/README.md +91 -0
- data/Rakefile +34 -0
- data/bin/ronin-fuzzer +37 -0
- data/gemspec.yml +30 -0
- data/lib/ronin/fuzzer/cli/command.rb +37 -0
- data/lib/ronin/fuzzer/cli/commands/fuzz.rb +326 -0
- data/lib/ronin/fuzzer/cli.rb +39 -0
- data/lib/ronin/fuzzer/root.rb +29 -0
- data/lib/ronin/fuzzer/version.rb +27 -0
- data/lib/ronin/fuzzing/core_ext/string.rb +203 -0
- data/lib/ronin/fuzzing/core_ext.rb +22 -0
- data/lib/ronin/fuzzing/fuzzer.rb +112 -0
- data/lib/ronin/fuzzing/mutator.rb +163 -0
- data/lib/ronin/fuzzing/repeater.rb +81 -0
- data/lib/ronin/fuzzing/template.rb +133 -0
- data/lib/ronin/fuzzing.rb +365 -0
- data/man/ronin-fuzzer-fuzz.1 +95 -0
- data/man/ronin-fuzzer-fuzz.1.md +73 -0
- data/ronin-fuzzer.gemspec +61 -0
- data/spec/core_ext/string_spec.rb +87 -0
- data/spec/fuzzer_spec.rb +109 -0
- data/spec/fuzzing_spec.rb +24 -0
- data/spec/mutator_spec.rb +112 -0
- data/spec/repeater_spec.rb +57 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/template_spec.rb +54 -0
- metadata +149 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
#
|
2
|
+
# ronin-fuzzer - A Ruby library for generating, mutating, and fuzzing data.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of ronin-fuzzer.
|
7
|
+
#
|
8
|
+
# ronin-fuzzer is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU Lesser General Public License as published
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# ronin-fuzzer is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU Lesser General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU Lesser General Public License
|
19
|
+
# along with ronin-fuzzer. If not, see <https://www.gnu.org/licenses/>.
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/fuzzing'
|
23
|
+
require 'ronin/support/text/patterns'
|
24
|
+
|
25
|
+
require 'strscan'
|
26
|
+
|
27
|
+
module Ronin
|
28
|
+
module Fuzzing
|
29
|
+
#
|
30
|
+
# Fuzzing class that incrementally fuzzes a String, given substitution
|
31
|
+
# rules.
|
32
|
+
#
|
33
|
+
# @api semipublic
|
34
|
+
#
|
35
|
+
class Fuzzer
|
36
|
+
|
37
|
+
PATTERNS = Support::Text::Patterns
|
38
|
+
|
39
|
+
# Patterns and their substitutions
|
40
|
+
attr_reader :rules
|
41
|
+
|
42
|
+
#
|
43
|
+
# Initializes a new Fuzzer.
|
44
|
+
#
|
45
|
+
# @param [Hash{Regexp,String,Symbol => Enumerable,Symbol}] rules
|
46
|
+
# Patterns and their substitutions.
|
47
|
+
#
|
48
|
+
def initialize(rules)
|
49
|
+
@rules = {}
|
50
|
+
|
51
|
+
rules.each do |pattern,substitution|
|
52
|
+
pattern = case pattern
|
53
|
+
when Regexp then pattern
|
54
|
+
when String then Regexp.new(Regexp.escape(pattern))
|
55
|
+
when Symbol then PATTERNS.const_get(pattern.upcase)
|
56
|
+
else
|
57
|
+
raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
|
58
|
+
end
|
59
|
+
|
60
|
+
substitution = case substitution
|
61
|
+
when Enumerable then substitution
|
62
|
+
when Symbol then Fuzzing[substitution]
|
63
|
+
else
|
64
|
+
raise(TypeError,"substitutions must be Enumerable or a Symbol")
|
65
|
+
end
|
66
|
+
|
67
|
+
@rules[pattern] = substitution
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Incrementally fuzzes the String.
|
73
|
+
#
|
74
|
+
# @yield [fuzz]
|
75
|
+
# The given block will be passed every fuzzed String.
|
76
|
+
#
|
77
|
+
# @yieldparam [String] fuzz
|
78
|
+
# A fuzzed String.
|
79
|
+
#
|
80
|
+
# @return [Enumerator]
|
81
|
+
# If no block is given, an Enumerator will be returned.
|
82
|
+
#
|
83
|
+
def each(string)
|
84
|
+
return enum_for(__method__,string) unless block_given?
|
85
|
+
|
86
|
+
@rules.each do |pattern,substitution|
|
87
|
+
scanner = StringScanner.new(string)
|
88
|
+
indices = []
|
89
|
+
|
90
|
+
while scanner.scan_until(pattern)
|
91
|
+
indices << [scanner.pos - scanner.matched_size, scanner.matched_size]
|
92
|
+
end
|
93
|
+
|
94
|
+
indices.each do |index,length|
|
95
|
+
substitution.each do |substitute|
|
96
|
+
substitute = case substitute
|
97
|
+
when Proc then substitute[string[index,length]]
|
98
|
+
when Integer then substitute.chr
|
99
|
+
else substitute.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
fuzzed = string.dup
|
103
|
+
fuzzed[index,length] = substitute
|
104
|
+
yield fuzzed
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
#
|
2
|
+
# ronin-fuzzer - A Ruby library for generating, mutating, and fuzzing data.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of ronin-fuzzer.
|
7
|
+
#
|
8
|
+
# ronin-fuzzer is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU Lesser General Public License as published
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# ronin-fuzzer is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU Lesser General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU Lesser General Public License
|
19
|
+
# along with ronin-fuzzer. If not, see <https://www.gnu.org/licenses/>.
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'ronin/fuzzing'
|
23
|
+
require 'ronin/support/text/patterns'
|
24
|
+
|
25
|
+
require 'combinatorics/list_comprehension'
|
26
|
+
require 'combinatorics/power_set'
|
27
|
+
require 'combinatorics/generator'
|
28
|
+
require 'strscan'
|
29
|
+
require 'set'
|
30
|
+
|
31
|
+
module Ronin
|
32
|
+
module Fuzzing
|
33
|
+
#
|
34
|
+
# Fuzzer class that permutates over every mutation of a String, given
|
35
|
+
# mutation rules.
|
36
|
+
#
|
37
|
+
# @api semipublic
|
38
|
+
#
|
39
|
+
class Mutator
|
40
|
+
|
41
|
+
PATTERNS = Support::Text::Patterns
|
42
|
+
|
43
|
+
# Mutation rules
|
44
|
+
attr_reader :rules
|
45
|
+
|
46
|
+
#
|
47
|
+
# Initialize the Mutator.
|
48
|
+
#
|
49
|
+
# @param [Hash{Regexp,String,Symbol => Symbol,Enumerable}] rules
|
50
|
+
# The patterns and substitutions to mutate the String with.
|
51
|
+
#
|
52
|
+
# @raise [TypeError]
|
53
|
+
# A mutation pattern was not a Regexp, String or Symbol.
|
54
|
+
# A mutation substitution was not a Symbol or Enumerable.
|
55
|
+
#
|
56
|
+
def initialize(rules)
|
57
|
+
@rules = {}
|
58
|
+
|
59
|
+
rules.each do |pattern,mutation|
|
60
|
+
pattern = case pattern
|
61
|
+
when Regexp
|
62
|
+
pattern
|
63
|
+
when String
|
64
|
+
Regexp.new(Regexp.escape(pattern))
|
65
|
+
when Symbol
|
66
|
+
PATTERNS.const_get(pattern.upcase)
|
67
|
+
else
|
68
|
+
raise(TypeError,"cannot convert #{pattern.inspect} to a Regexp")
|
69
|
+
end
|
70
|
+
|
71
|
+
mutation = case mutation
|
72
|
+
when Enumerable
|
73
|
+
mutation
|
74
|
+
when Symbol
|
75
|
+
Ronin::Fuzzing[mutation]
|
76
|
+
else
|
77
|
+
raise(TypeError,"mutation #{mutation.inspect} must be a Symbol or Enumerable")
|
78
|
+
end
|
79
|
+
|
80
|
+
@rules[pattern] = mutation
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Permutes over every possible mutation of the String.
|
86
|
+
#
|
87
|
+
# @param [String] string
|
88
|
+
# The String to be mutated.
|
89
|
+
#
|
90
|
+
# @yield [mutant]
|
91
|
+
# The given block will be yielded every possible mutant String.
|
92
|
+
#
|
93
|
+
# @yieldparam [String] mutant
|
94
|
+
# A mutated String.
|
95
|
+
#
|
96
|
+
# @return [Enumerator]
|
97
|
+
# If no block is given, an Enumerator will be returned.
|
98
|
+
#
|
99
|
+
def each(string)
|
100
|
+
return enum_for(__method__,string) unless block_given?
|
101
|
+
|
102
|
+
matches = Set[]
|
103
|
+
|
104
|
+
@rules.each do |pattern,mutation|
|
105
|
+
scanner = StringScanner.new(string)
|
106
|
+
|
107
|
+
while scanner.scan_until(pattern)
|
108
|
+
length = scanner.matched_size
|
109
|
+
index = scanner.pos - length
|
110
|
+
original = scanner.matched
|
111
|
+
|
112
|
+
mutator = Combinatorics::Generator.new do |g|
|
113
|
+
mutation.each do |mutate|
|
114
|
+
g.yield case mutate
|
115
|
+
when Proc
|
116
|
+
mutate.call(original)
|
117
|
+
when Integer
|
118
|
+
mutate.chr
|
119
|
+
else
|
120
|
+
mutate.to_s
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
matches << [index, length, mutator]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
matches.powerset do |submatches|
|
130
|
+
# ignore the empty Set
|
131
|
+
next if submatches.empty?
|
132
|
+
|
133
|
+
# sort the submatches by index
|
134
|
+
submatches = submatches.sort_by { |index,length,mutator| index }
|
135
|
+
sets = []
|
136
|
+
prev_index = 0
|
137
|
+
|
138
|
+
submatches.each do |index,length,mutator|
|
139
|
+
# add the previous substring to the set of Strings
|
140
|
+
if index > prev_index
|
141
|
+
sets << [string[prev_index,index - prev_index]]
|
142
|
+
end
|
143
|
+
|
144
|
+
# add the mutator to the set of Strings
|
145
|
+
sets << mutator
|
146
|
+
|
147
|
+
prev_index = index + length
|
148
|
+
end
|
149
|
+
|
150
|
+
# add the remaining substring to the set of Strings
|
151
|
+
if prev_index < string.length
|
152
|
+
sets << [string[prev_index..-1]]
|
153
|
+
end
|
154
|
+
|
155
|
+
sets.comprehension { |strings| yield strings.join }
|
156
|
+
end
|
157
|
+
|
158
|
+
return nil
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#
|
2
|
+
# ronin-fuzzer - A Ruby library for generating, mutating, and fuzzing data.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of ronin-fuzzer.
|
7
|
+
#
|
8
|
+
# ronin-fuzzer is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU Lesser General Public License as published
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# ronin-fuzzer is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU Lesser General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU Lesser General Public License
|
19
|
+
# along with ronin-fuzzer. If not, see <https://www.gnu.org/licenses/>.
|
20
|
+
#
|
21
|
+
|
22
|
+
module Ronin
|
23
|
+
module Fuzzing
|
24
|
+
#
|
25
|
+
# Fuzzing class that generates repeated data.
|
26
|
+
#
|
27
|
+
# @api semipublic
|
28
|
+
#
|
29
|
+
class Repeater
|
30
|
+
|
31
|
+
# The lengths to repeat the data by
|
32
|
+
attr_reader :lengths
|
33
|
+
|
34
|
+
#
|
35
|
+
# Initializes a new Repeater.
|
36
|
+
#
|
37
|
+
# @param [Enumerable, Integer] lengths
|
38
|
+
# The lengths to repeat the data by.
|
39
|
+
#
|
40
|
+
# @raise [TypeError]
|
41
|
+
# `lengths` must either be Enumerable or an Integer.
|
42
|
+
#
|
43
|
+
def initialize(lengths)
|
44
|
+
@lengths = case lengths
|
45
|
+
when Integer
|
46
|
+
[lengths]
|
47
|
+
when Enumerable
|
48
|
+
lengths
|
49
|
+
else
|
50
|
+
raise(TypeError,"argument must be Enumerable or an Integer")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Enumerates through each length of repeated data.
|
56
|
+
#
|
57
|
+
# @param [#*] repeatable
|
58
|
+
# The repeatable data.
|
59
|
+
#
|
60
|
+
# @yield [repeated]
|
61
|
+
# The given block will be passed every repeated String.
|
62
|
+
#
|
63
|
+
# @yieldparam [String] repeated
|
64
|
+
# A repeated version of the String.
|
65
|
+
#
|
66
|
+
# @return [Enumerator]
|
67
|
+
# If no block is given, an Enumerator will be returned.
|
68
|
+
#
|
69
|
+
def each(repeatable)
|
70
|
+
return enum_for(__method__,repeatable) unless block_given?
|
71
|
+
|
72
|
+
@lengths.each do |length|
|
73
|
+
yield(repeatable * length)
|
74
|
+
end
|
75
|
+
|
76
|
+
return nil
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
#
|
2
|
+
# ronin-fuzzer - A Ruby library for generating, mutating, and fuzzing data.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2006-2022 Hal Brodigan (postmodern.mod3 at gmail.com)
|
5
|
+
#
|
6
|
+
# This file is part of ronin-fuzzer.
|
7
|
+
#
|
8
|
+
# ronin-fuzzer is free software: you can redistribute it and/or modify
|
9
|
+
# it under the terms of the GNU Lesser General Public License as published
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
11
|
+
# (at your option) any later version.
|
12
|
+
#
|
13
|
+
# ronin-fuzzer is distributed in the hope that it will be useful,
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
+
# GNU Lesser General Public License for more details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU Lesser General Public License
|
19
|
+
# along with ronin-fuzzer. If not, see <https://www.gnu.org/licenses/>.
|
20
|
+
#
|
21
|
+
|
22
|
+
require 'chars'
|
23
|
+
require 'combinatorics/generator'
|
24
|
+
require 'combinatorics/list_comprehension'
|
25
|
+
|
26
|
+
module Ronin
|
27
|
+
module Fuzzing
|
28
|
+
#
|
29
|
+
# Fuzzing class that generates Strings based on a template.
|
30
|
+
#
|
31
|
+
# @api semipublic
|
32
|
+
#
|
33
|
+
class Template
|
34
|
+
|
35
|
+
include Enumerable
|
36
|
+
|
37
|
+
#
|
38
|
+
# Initializes a new Fuzzing template.
|
39
|
+
#
|
40
|
+
# @param [Array(<String,Symbol,Enumerable>, <Integer,Array,Range>)] fields
|
41
|
+
# The fields which defines the string or character sets which will
|
42
|
+
# make up parts of the String.
|
43
|
+
#
|
44
|
+
# @raise [ArgumentError]
|
45
|
+
# A given character set name was unknown.
|
46
|
+
#
|
47
|
+
# @raise [TypeError]
|
48
|
+
# A given string set was not a String, Symbol or Enumerable.
|
49
|
+
# A given string set length was not an Integer or Enumerable.
|
50
|
+
#
|
51
|
+
def initialize(fields)
|
52
|
+
@enumerators = []
|
53
|
+
|
54
|
+
fields.each do |(set,length)|
|
55
|
+
enum = case set
|
56
|
+
when String
|
57
|
+
[set].each
|
58
|
+
when Enumerable
|
59
|
+
set.each
|
60
|
+
when Symbol
|
61
|
+
name = set.upcase
|
62
|
+
|
63
|
+
unless Chars.const_defined?(name)
|
64
|
+
raise(ArgumentError,"unknown charset #{set.inspect}")
|
65
|
+
end
|
66
|
+
|
67
|
+
Chars.const_get(name).each_char
|
68
|
+
else
|
69
|
+
raise(TypeError,"set must be a String, Symbol or Enumerable")
|
70
|
+
end
|
71
|
+
|
72
|
+
case length
|
73
|
+
when Integer
|
74
|
+
length.times { @enumerators << enum.dup }
|
75
|
+
when Array, Range
|
76
|
+
@enumerators << Combinatorics::Generator.new do |g|
|
77
|
+
length.each do |sublength|
|
78
|
+
superset = Array.new(sublength) { enum.dup }
|
79
|
+
|
80
|
+
superset.comprehension { |strings| g.yield strings.join }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
when nil
|
84
|
+
@enumerators << enum
|
85
|
+
else
|
86
|
+
raise(TypeError,"length must be an Integer, Range or Array")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# @see #initialize
|
93
|
+
#
|
94
|
+
def self.[](*fields)
|
95
|
+
new(fields)
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Generate permutations of Strings from a format template.
|
100
|
+
#
|
101
|
+
# @yield [string]
|
102
|
+
# The given block will be passed each unique String.
|
103
|
+
#
|
104
|
+
# @yieldparam [String] string
|
105
|
+
# A newly generated String.
|
106
|
+
#
|
107
|
+
# @return [Enumerator]
|
108
|
+
# If no block is given, an Enumerator will be returned.
|
109
|
+
#
|
110
|
+
def each
|
111
|
+
return enum_for(__method__) unless block_given?
|
112
|
+
|
113
|
+
@enumerators.comprehension do |fields|
|
114
|
+
string = ''
|
115
|
+
|
116
|
+
fields.each do |field|
|
117
|
+
string << case field
|
118
|
+
when Integer
|
119
|
+
field.chr
|
120
|
+
else
|
121
|
+
field.to_s
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
yield string
|
126
|
+
end
|
127
|
+
|
128
|
+
return nil
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|