Module: PWN::Plugins::Fuzz
- Defined in:
- lib/pwn/plugins/fuzz.rb
Overview
This plugin was created to support fuzzing various networking protocols. A request template with fuzz delimiters is combined with a payload and optional layered encodings, then replayed over TCP/UDP (optionally TLS).
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
- .file_format(opts = {}) ⇒ Object
-
.help ⇒ Object
Display Usage for this Module.
- .http(opts = {}) ⇒ Object
-
.socket(opts = {}) ⇒ Object
- Supported Method Parameters
socket_fuzz_results_arr = PWN::Plugins::Fuzz.socket( target: 'required - target host or ip', port: 'required - target port', protocol: 'optional - :tcp || :udp (defaults to tcp)', tls: 'optional - boolean connect to target socket using TLS (defaults to false)', fuzz_delimeter: 'optional - fuzz delimeter used in request to specify where payloads should reside (defaults to \u2665)', request: 'required - String object of socket request w/ \u001A as fuzz delimeter (e.g. "GET /\u001A\u001A HTTP/1.1\r\nHost: \u001A127..0.0.1\u001A\r\n\r\n")', payload: 'required - payload string', response_timeout: 'optional - float (defaults to 0.9)', request_rate_limit: 'optional - float (defaults to 0.3)', encoding: 'optional - :base64 || :hex || :html_entity || :url (Defaults to nil)', encoding_depth: 'optional - number of times to encode payload (defaults to 1)', char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)' ).
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. [email protected]
195 196 197 198 199 |
# File 'lib/pwn/plugins/fuzz.rb', line 195 public_class_method def self. "AUTHOR(S): 0day Inc. <[email protected]> " end |
.file_format(opts = {}) ⇒ Object
184 185 186 187 188 189 190 191 |
# File 'lib/pwn/plugins/fuzz.rb', line 184 public_class_method def self.file_format(opts = {}) path = opts[:path].to_s raise 'ERROR: path is required' if path.empty? seed = File.binread(path) dict = Array(opts[:dictionary] || ["\xff"]) dict.map { |blob| seed + blob.to_s } end |
.help ⇒ Object
Display Usage for this Module
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/pwn/plugins/fuzz.rb', line 203 public_class_method def self.help puts "USAGE: # Run socket and return its result #{self}.socket( target: 'required - target host or ip', port: 'required - target port', protocol: 'optional - :tcp || :udp (defaults to tcp)', tls: 'optional - boolean connect to target socket using TLS (defaults to false)', fuzz_delimeter: 'optional - fuzz delimeter used in request to specify where payloads should reside (defaults to \\u2665)', request: 'required - String object of socket request w/ \\u001A as fuzz delimeter (e.g. GET /\\u001A\\u001A HTTP/1.1\\r\\nHost: \\u001A127..0.0.1\\u001A\\r\\n\\r\\n)', payload: 'required - payload string', response_timeout: 'optional - float (defaults to 0.9)', request_rate_limit: 'optional - float (defaults to 0.3)', encoding: 'optional - :base64 || :hex || :html_entity || :url (Defaults to nil)', encoding_depth: 'optional - number of times to encode payload (defaults to 1)', char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)' ) # Expand an HTTP request template by substituting FUZZ with dictionary payloads. #{self}.http( request: 'required - HTTP request template containing FUZZ (or token:)', dictionary: 'optional - Array of payload strings', payloads: 'optional - alias for dictionary', token: 'optional - placeholder to replace (defaults to FUZZ)' ) # Append dictionary blobs to a seed file for coverage-guided or dumb file fuzzing. #{self}.file_format( path: 'required - filesystem path to the seed file', dictionary: 'optional - Array of binary/string mutations' ) # Print the AUTHOR(S) string for this module. #{self}.authors " constants.sort end |
.http(opts = {}) ⇒ Object
174 175 176 177 178 179 180 181 182 |
# File 'lib/pwn/plugins/fuzz.rb', line 174 public_class_method def self.http(opts = {}) template = opts[:request].to_s raise 'ERROR: request is required' if template.empty? token = (opts[:token] || 'FUZZ').to_s Array(opts[:dictionary] || opts[:payloads] || ['A']).map do |p| template.gsub(token, p.to_s) end end |
.socket(opts = {}) ⇒ Object
- Supported Method Parameters
socket_fuzz_results_arr = PWN::Plugins::Fuzz.socket( target: 'required - target host or ip', port: 'required - target port', protocol: 'optional - :tcp || :udp (defaults to tcp)', tls: 'optional - boolean connect to target socket using TLS (defaults to false)', fuzz_delimeter: 'optional - fuzz delimeter used in request to specify where payloads should reside (defaults to \u2665)', request: 'required - String object of socket request w/ \u001A as fuzz delimeter (e.g. "GET /\u001A\u001A HTTP/1.1\r\nHost: \u001A127..0.0.1\u001A\r\n\r\n")', payload: 'required - payload string', response_timeout: 'optional - float (defaults to 0.9)', request_rate_limit: 'optional - float (defaults to 0.3)', encoding: 'optional - :base64 || :hex || :html_entity || :url (Defaults to nil)', encoding_depth: 'optional - number of times to encode payload (defaults to 1)', char_encoding: 'optional - character encoding returned by PWN::Plugins::Char.list_encoders (defaults to UTF-8)' )
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 |
# File 'lib/pwn/plugins/fuzz.rb', line 29 public_class_method def self.socket(opts = {}) target = opts[:target].to_s.scrub port = opts[:port].to_i protocol = opts[:protocol] tls = opts[:tls] opts[:encoding].nil? ? encoding = nil : encoding = opts[:encoding].to_s.strip.chomp.scrub.downcase.to_sym opts[:encoding_depth].nil? ? encoding_depth = 1 : encoding_depth = opts[:encoding_depth].to_i opts[:char_encoding].nil? ? char_encoding = 'UTF-8' : char_encoding = opts[:char_encoding].to_s opts[:fuzz_delimeter].nil? ? fuzz_delimeter = "\u2665" : fuzz_delimeter = opts[:fuzz_delimeter] request = opts[:request].to_s.encode(char_encoding, 'UTF-8') payload = opts[:payload].to_s.encode(char_encoding, 'UTF-8') if encoding case encoding when :base64 if encoding_depth > 1 (1..encoding_depth).each do payload = Base64.strict_encode64(payload) end else payload = Base64.strict_encode64(payload) end when :hex if encoding_depth > 1 (1..encoding_depth).each do hex_payload = '' payload.each_byte { |b| hex_payload = "#{hex_payload}#{format('\x%02x', b)}" } payload = hex_payload end else hex_payload = '' payload.each_byte { |b| hex_payload = "#{hex_payload}#{format('\x%02x', b)}" } payload = hex_payload end when :html_entity if encoding_depth > 1 (1..encoding_depth).each do payload = HTMLEntities.new.encode(payload) end else payload = HTMLEntities.new.encode(payload) end when :url if encoding_depth > 1 (1..encoding_depth).each do payload = CGI.escape(payload) end else payload = CGI.escape(payload) end else raise "Encoding type: #{encoding} not supported." end end opts[:response_timeout].nil? ? response_timeout = 0.9 : response_timeout = opts[:response_timeout].to_f opts[:request_rate_limit].nil? ? request_rate_limit = 0.3 : request_rate_limit = opts[:request_rate_limit].to_f socket_fuzz_results_arr = [] # Find fuzz delimeter index numbers in request request_delim_index_arr = [] request.each_char.with_index do |char, char_index| request_delim_index_arr.push(char_index) if char == fuzz_delimeter end # request_delim_index_arr should always return an even length, # otherwise the request is missing a fuzz delimeter. request_delim_index_arr.each_slice(2).with_index do |placeholder_slice, placeholder_slice_index| this_socket_fuzz_result = {} begin_delim_char_index_shift_width = placeholder_slice_index * 2 begin_delim_char_index = placeholder_slice[0].to_i - begin_delim_char_index_shift_width end_delim_char_index_shift_width = (placeholder_slice_index * 2) + 2 end_delim_char_index = placeholder_slice[1].to_i - end_delim_char_index_shift_width this_request = request.dup.delete(fuzz_delimeter).encode(char_encoding, 'UTF-8') if end_delim_char_index.positive? this_request[begin_delim_char_index..end_delim_char_index] = payload else # begin_delim_char_index should always be 0 this_request[begin_delim_char_index] = payload end sock_obj = PWN::Plugins::Sock.connect( target: target, port: port, protocol: protocol, tls: tls ) this_socket_fuzz_result[:timestamp] = Time.now.strftime('%Y-%m-%d %H:%M:%S.%9N %z').to_s this_socket_fuzz_result[:request] = this_request this_socket_fuzz_result[:request_encoding] = this_request.encoding.name this_socket_fuzz_result[:request_len] = this_request.length # Send Fuzz Payload in its rawest form (as long as it will undump first) sock_obj.write(this_request.encode('ASCII-8BIT', undef: :replace).undump) does_respond = sock_obj.wait_readable(response_timeout) if does_respond response = sock_obj.read response_len = response.length this_socket_fuzz_result[:response] = response.to_s.inspect this_socket_fuzz_result[:response_len] = response_len else this_socket_fuzz_result[:response] = '' this_socket_fuzz_result[:response_len] = 0 end sleep request_rate_limit sock_obj = PWN::Plugins::Sock.disconnect(sock_obj: sock_obj) # TODO: dump into file once array reaches max length (avoid memory consumption issues) socket_fuzz_results_arr.push(this_socket_fuzz_result) rescue Errno::ECONNRESET => e response = e. this_socket_fuzz_result[:response] = response this_socket_fuzz_result[:response_len] = response.length sleep request_rate_limit sock_obj = PWN::Plugins::Sock.disconnect(sock_obj: sock_obj) unless sock_obj.nil? # TODO: dump into file once array reaches max length (avoid memory consumption issues) socket_fuzz_results_arr.push(this_socket_fuzz_result) next rescue StandardError => e response = "#{e.class}: #{e.message} #{e.backtrace}" this_socket_fuzz_result[:response] = response this_socket_fuzz_result[:response_len] = response.length sleep request_rate_limit sock_obj = PWN::Plugins::Sock.disconnect(sock_obj: sock_obj) unless sock_obj.nil? # TODO: dump into file once array reaches max length (avoid memory consumption issues) socket_fuzz_results_arr.push(this_socket_fuzz_result) next end socket_fuzz_results_arr rescue StandardError => e raise e ensure sock_obj = PWN::Plugins::Sock.disconnect(sock_obj: sock_obj) unless sock_obj.nil? end |