ronin-fuzzer 0.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|