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.
@@ -0,0 +1,326 @@
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/fuzzer/cli/command'
23
+ require 'ronin/fuzzer/repeater'
24
+ require 'ronin/fuzzer/fuzzer'
25
+
26
+ require 'shellwords'
27
+ require 'tempfile'
28
+ require 'socket'
29
+
30
+ module Ronin
31
+ module Fuzzer
32
+ class CLI
33
+ module Commands
34
+ #
35
+ # Performs basic fuzzing of files, commands or TCP/UDP services.
36
+ #
37
+ # ## Usage
38
+ #
39
+ # ronin-fuzzer fuzz [options]
40
+ #
41
+ # ## Options
42
+ #
43
+ # -v, --[no-]verbose Enable verbose output.
44
+ # -q, --[no-]quiet Disable verbose output.
45
+ # --[no-]silent Silence all output.
46
+ # -r [[PATTERN|/REGEXP/]:[METHOD|STRING*N[-M]]],
47
+ # --rule Fuzzing rules.
48
+ # -i, --input [FILE] Input file to fuzz.
49
+ # -o, --output [FILE] Output file path.
50
+ # -c [PROGRAM [OPTIONS|#string#|#path#] ...],
51
+ # --command Template command to run.
52
+ # -t, --tcp [HOST:PORT] TCP service to fuzz.
53
+ # -u, --udp [HOST:PORT] UDP service to fuzz.
54
+ # -p, --pause [SECONDS] Pause in between mutations.
55
+ #
56
+ # ## Examples
57
+ #
58
+ # ronin-fuzzer fuzz -i request.txt -r unix_path:bad_strings -o bad.txt
59
+ #
60
+ class Fuzz < Command
61
+
62
+ option :input, short: '-i',
63
+ value: {
64
+ type: String,
65
+ usage: 'FILE'
66
+ },
67
+ desc: 'Input file to fuzz'
68
+
69
+ option :rules, short: '-r',
70
+ value: {
71
+ type: Hash[String => String],
72
+ usage: '[PATTERN|/REGEXP/|STRING]:[METHOD|STRING*N[-M]]'
73
+ },
74
+ desc: 'Fuzzing rules'
75
+
76
+ option :output, short: '-o',
77
+ value: {
78
+ type: String,
79
+ usage: 'PATH'
80
+ },
81
+ desc: 'Output file path'
82
+
83
+ option :command, short: '-c',
84
+ value: {
85
+ type: String,
86
+ usage: '"PROGRAM [OPTIONS|#string#|#path#] ..."'
87
+ },
88
+ desc: 'Template command to run'
89
+
90
+ option :tcp, short: '-t',
91
+ value: {
92
+ type: String,
93
+ usage: 'HOST:PORT'
94
+ },
95
+ desc: 'TCP service to fuzz'
96
+
97
+ option :udp, short: '-u',
98
+ value: {
99
+ type: String,
100
+ usage: 'HOST:PORT'
101
+ },
102
+ desc: 'UDP service to fuzz'
103
+
104
+ option :pause, short: '-p',
105
+ value: {
106
+ type: Float,
107
+ usage: 'SECONDS'
108
+ },
109
+ desc: 'Pause in between mutations'
110
+
111
+ description 'Performs basic fuzzing of files'
112
+
113
+ examples [
114
+ "-i request.txt -o bad.txt -r unix_path:bad_strings"
115
+ ]
116
+
117
+ man_page 'ronin-fuzzer-fuzz.1'
118
+
119
+ #
120
+ # Sets up the fuzz command.
121
+ #
122
+ def run
123
+ unless options[:rules]
124
+ print_error "Must specify at least one fuzzing rule"
125
+ exit -1
126
+ end
127
+
128
+ rules = Hash[options[:rules].map { |pattern,substitution|
129
+ [parse_pattern(pattern), parse_substitution(substitution)]
130
+ }]
131
+
132
+ if options[:output]
133
+ @file_ext = File.extname(options[:output])
134
+ @file_name = @output.chomp(@file_ext)
135
+ elsif options[:command]
136
+ @command = shellwords(options[:command])
137
+ elsif (options[:tcp] || options[:udp])
138
+ @socket_class = if options[:tcp] then TCPSocket
139
+ elsif options[:udp] then UDPSocket
140
+ end
141
+
142
+ @host, @port = (options[:tcp] || options[:udp]).split(':',2)
143
+ @port = @port.to_i
144
+ end
145
+
146
+ data = if options[:input] then File.read(options[:input])
147
+ else $stdin.read
148
+ end
149
+
150
+ method = if options[:output]
151
+ method(:fuzz_file)
152
+ elsif options[:command]
153
+ method(:fuzz_command)
154
+ elsif (options[:tcp] || options[:udp])
155
+ method(:fuzz_network)
156
+ else
157
+ method(:print_fuzz)
158
+ end
159
+
160
+ fuzzer = Fuzzer::Fuzzer.new(rules)
161
+ fuzzer.each(data).each_with_index do |string,index|
162
+ method.call(string,index + 1)
163
+
164
+ sleep(pause) if pause?
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ include Shellwords
171
+
172
+ #
173
+ # Writes the fuzzed string to a file.
174
+ #
175
+ # @param [String] string
176
+ # The fuzzed string.
177
+ #
178
+ # @param [Integer] index
179
+ # The iteration number.
180
+ #
181
+ def fuzz_file(string,index)
182
+ path = "#{@file_name}-#{index}#{@file_ext}"
183
+
184
+ print_info "Creating file ##{index}: #{path} ..."
185
+
186
+ File.open(path,'wb') do |file|
187
+ file.write string
188
+ end
189
+ end
190
+
191
+ #
192
+ # Runs the fuzzed string in a command.
193
+ #
194
+ # @param [String] string
195
+ # The fuzzed string.
196
+ #
197
+ # @param [Integer] index
198
+ # The iteration number.
199
+ #
200
+ def fuzz_command(string,index)
201
+ Tempfile.open("ronin-fuzzer-#{index}") do |tempfile|
202
+ tempfile.write(string)
203
+ tempfile.flush
204
+
205
+ arguments = @command.map do |argument|
206
+ if argument.include?('#path#')
207
+ argument.sub('#path#',tempfile.path)
208
+ elsif argument.include?('#string#')
209
+ argument.sub('#string#',string)
210
+ else
211
+ argument
212
+ end
213
+ end
214
+
215
+ print_info "Running command #{index}: #{arguments.join(' ')} ..."
216
+
217
+ # run the command as it's own process
218
+ unless system(*arguments)
219
+ status = $?
220
+
221
+ if status.coredump?
222
+ # jack pot!
223
+ print_error "Process ##{status.pid} coredumped!"
224
+ else
225
+ # process errored out
226
+ print_warning "Process ##{status.pid} exited with status #{status.exitstatus}"
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ #
233
+ # Sends the fuzzed string to a TCP/UDP Service.
234
+ #
235
+ # @param [String] string
236
+ # The fuzzed string.
237
+ #
238
+ # @param [Integer] index
239
+ # The iteration number.
240
+ #
241
+ def fuzz_network(string,index)
242
+ print_debug "Connecting to #{@host}:#{@port} ..."
243
+ socket = @socket_class.new(@host,@port)
244
+
245
+ print_info "Sending message ##{index}: #{string.inspect} ..."
246
+ socket.write(string)
247
+ socket.flush
248
+
249
+ print_debug "Disconnecting from #{@host}:#{@port} ..."
250
+ socket.close
251
+ end
252
+
253
+ #
254
+ # Prints the fuzzed string to STDOUT.
255
+ #
256
+ # @param [String] string
257
+ # The fuzzed string.
258
+ #
259
+ # @param [Integer] index
260
+ # The iteration number.
261
+ #
262
+ def print_fuzz(string,index)
263
+ print_debug "String ##{index} ..."
264
+
265
+ puts string
266
+ end
267
+
268
+ #
269
+ # Parses a fuzz pattern.
270
+ #
271
+ # @param [String] string
272
+ # The string to parse.
273
+ #
274
+ # @return [Regexp, String]
275
+ # The parsed pattern.
276
+ #
277
+ def parse_pattern(string)
278
+ case string
279
+ when /^\/.+\/$/
280
+ Regexp.new(string[1..-2])
281
+ when /^[a-z][a-z_]+$/
282
+ const = string.upcase
283
+
284
+ if (Regexp.const_defined?(const) &&
285
+ Regexp.const_get(const).kind_of?(Regexp))
286
+ Regexp.const_get(const)
287
+ else
288
+ string
289
+ end
290
+ else
291
+ string
292
+ end
293
+ end
294
+
295
+ #
296
+ # Parses a fuzz substitution Enumerator.
297
+ #
298
+ # @param [String] string
299
+ # The string to parse.
300
+ #
301
+ # @return [Enumerator]
302
+ # The parsed substitution Enumerator.
303
+ #
304
+ def parse_substitution(string)
305
+ if string.include?('*')
306
+ string, lengths = string.split('*',2)
307
+
308
+ lengths = if lengths.include?('-')
309
+ min, max = lengths.split('-',2)
310
+
311
+ (min.to_i .. max.to_i)
312
+ else
313
+ lengths.to_i
314
+ end
315
+
316
+ Fuzzer::Repeater.new(lengths).each(string)
317
+ else
318
+ Fuzzer[string]
319
+ end
320
+ end
321
+
322
+ end
323
+ end
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,39 @@
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 'command_kit/commands'
23
+ require 'command_kit/commands/auto_load'
24
+
25
+ module Ronin
26
+ module Fuzzer
27
+ class CLI
28
+
29
+ include CommandKit::Commands
30
+ include CommandKit::Commands::AutoLoad.new(
31
+ dir: "#{__dir__}/cli/commands",
32
+ namespace: "#{self}::Commands"
33
+ )
34
+
35
+ command_name 'ronin-fuzzer'
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
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 Fuzzer
24
+ # Path to `ronin-fuzzer` root directory.
25
+ #
26
+ # @api private
27
+ ROOT = File.expand_path(File.join(__dir__,'..','..','..'))
28
+ end
29
+ end
@@ -0,0 +1,27 @@
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 Fuzzer
24
+ # ronin-fuzzer version
25
+ VERSION = '0.1.0.beta1'
26
+ end
27
+ end
@@ -0,0 +1,203 @@
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/template'
23
+ require 'ronin/fuzzing/repeater'
24
+ require 'ronin/fuzzing/fuzzer'
25
+ require 'ronin/fuzzing/mutator'
26
+ require 'ronin/fuzzing'
27
+ require 'ronin/support/text/core_ext/regexp'
28
+
29
+ class String
30
+
31
+ #
32
+ # Generate permutations of Strings from a format template.
33
+ #
34
+ # @param [Array(<String,Symbol,Enumerable>, <Integer,Array,Range>)] fields
35
+ # The fields which defines the string or character sets which will
36
+ # make up parts of the String.
37
+ #
38
+ # @yield [string]
39
+ # The given block will be passed each unique String.
40
+ #
41
+ # @yieldparam [String] string
42
+ # A newly generated String.
43
+ #
44
+ # @return [Enumerator]
45
+ # If no block is given, an Enumerator will be returned.
46
+ #
47
+ # @raise [ArgumentError]
48
+ # A given character set name was unknown.
49
+ #
50
+ # @raise [TypeError]
51
+ # A given string set was not a String, Symbol or Enumerable.
52
+ # A given string set length was not an Integer or Enumerable.
53
+ #
54
+ # @example Generate Strings with ranges of repeating sub-strings:
55
+ #
56
+ # @example Generate Strings with three alpha chars and one numeric chars:
57
+ # String.generate([:alpha, 3], :numeric) do |password|
58
+ # puts password
59
+ # end
60
+ #
61
+ # @example Generate Strings with two to four alpha chars:
62
+ # String.generate([:alpha, 2..4]) do |password|
63
+ # puts password
64
+ # end
65
+ #
66
+ # @example Generate Strings using alpha and punctuation chars:
67
+ # String.generate([Chars.alpha + Chars.punctuation, 4]) do |password|
68
+ # puts password
69
+ # end
70
+ #
71
+ # @example Generate Strings from a custom char set:
72
+ # String.generate([['a', 'b', 'c'], 3], [['1', '2', '3'], 3]) do |password|
73
+ # puts password
74
+ # end
75
+ #
76
+ # @example Generate Strings containing known Strings:
77
+ # String.generate("rock", [:numeric, 4]) do |password|
78
+ # puts password
79
+ # end
80
+ #
81
+ # @example Generate Strings with ranges of repeating sub-strings:
82
+ # String.generate(['/AA', (1..100).step(5)]) do |path|
83
+ # puts path
84
+ # end
85
+ #
86
+ # @api public
87
+ #
88
+ def self.generate(*fields,&block)
89
+ Ronin::Fuzzing::Template.new(fields).each(&block)
90
+ end
91
+
92
+ #
93
+ # Repeats the String.
94
+ #
95
+ # @param [Enumerable<Integer>, Integer] lengths
96
+ # The number of times to repeat the String.
97
+ #
98
+ # @yield [repeated]
99
+ # The given block will be passed every repeated String.
100
+ #
101
+ # @yieldparam [String] repeated
102
+ # A repeated version of the String.
103
+ #
104
+ # @return [Enumerator]
105
+ # If no block is given, an Enumerator will be returned.
106
+ #
107
+ # @raise [TypeError]
108
+ # `n` must either be Enumerable or an Integer.
109
+ #
110
+ # @example
111
+ # 'A'.repeating(100)
112
+ # # => "AAAAAAAAAAAAA..."
113
+ #
114
+ # @example Generates 100 upto 700 `A`s, increasing by 100 at a time:
115
+ # 'A'.repeating((100..700).step(100)) do |str|
116
+ # # ...
117
+ # end
118
+ #
119
+ # @example Generates 128, 1024, 65536 `A`s:
120
+ # 'A'.repeating([128, 1024, 65536]) do |str|
121
+ # # ...
122
+ # end
123
+ #
124
+ # @api public
125
+ #
126
+ def repeating(lengths,&block)
127
+ case lengths
128
+ when Integer
129
+ # if lengths is an Integer, simply multiply the String and return
130
+ repeated = (self * lengths)
131
+
132
+ yield repeated if block_given?
133
+ return repeated
134
+ else
135
+ return Ronin::Fuzzing::Repeater.new(lengths).each(self,&block)
136
+ end
137
+ end
138
+
139
+ #
140
+ # Incrementally fuzzes the String.
141
+ #
142
+ # @param [Hash{Regexp,String,Symbol => Enumerable,Symbol}] substitutions
143
+ # Patterns and their substitutions.
144
+ #
145
+ # @yield [fuzz]
146
+ # The given block will be passed every fuzzed String.
147
+ #
148
+ # @yieldparam [String] fuzz
149
+ # A fuzzed String.
150
+ #
151
+ # @return [Enumerator]
152
+ # If no block is given, an Enumerator will be returned.
153
+ #
154
+ # @example Replace every `e`, `i`, `o`, `u` with `(`, 100 `A`s and a `\0`:
155
+ # "the quick brown fox".fuzz(/[eiou]/ => ['(', ('A' * 100), "\0"]) do |str|
156
+ # p str
157
+ # end
158
+ #
159
+ # @example {String.generate} with {String#fuzz}:
160
+ # "GET /".fuzz('/' => String.generate(['A', 1..100])) do |str|
161
+ # p str
162
+ # end
163
+ #
164
+ # @example Replace a `Regexp::UNIX_PATH` with {Ronin::Fuzzing#format_strings}:
165
+ # "GET /downloads/".fuzz(unix_path: :format_string)
166
+ #
167
+ # @api public
168
+ #
169
+ def fuzz(substitutions={},&block)
170
+ Ronin::Fuzzing::Fuzzer.new(substitutions).each(self,&block)
171
+ end
172
+
173
+ #
174
+ # Permutes over every possible mutation of the String.
175
+ #
176
+ # @param [Hash{Regexp,String,Symbol => Enumerable,Symbol}] mutations
177
+ # The patterns and substitutions to mutate the String with.
178
+ #
179
+ # @yield [mutant]
180
+ # The given block will be yielded every possible mutant String.
181
+ #
182
+ # @yieldparam [String] mutant
183
+ # A mutated String.
184
+ #
185
+ # @return [Enumerator]
186
+ # If no block is given, an Enumerator will be returned.
187
+ #
188
+ # @raise [TypeError]
189
+ # A mutation pattern was not a Regexp, String or Symbol.
190
+ # A mutation substitution was not a Symbol or Enumerable.
191
+ #
192
+ # @example
193
+ # "hello old dog".mutate('e' => ['3'], 'l' => ['1'], 'o' => ['0']) do |str|
194
+ # puts str
195
+ # end
196
+ #
197
+ # @api public
198
+ #
199
+ def mutate(mutations={},&block)
200
+ Ronin::Fuzzing::Mutator.new(mutations).each(self,&block)
201
+ end
202
+
203
+ end
@@ -0,0 +1,22 @@
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/core_ext/string'