Module: PWN::Plugins::ExploitDev
- Defined in:
- lib/pwn/plugins/exploit_dev.rb
Overview
p8/p16/p32/p64, cyclic, flat, fmt writes, gadget search.
Constant Summary collapse
- CYCLIC_ALPHA =
(('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a).freeze
Class Method Summary collapse
- .authors ⇒ Object
- .cyclic(opts = {}) ⇒ Object
- .cyclic_find(opts = {}) ⇒ Object
- .flat(opts = {}) ⇒ Object
- .fmt_writes(opts = {}) ⇒ Object
- .fmtstr(opts = {}) ⇒ Object
- .from_crash(opts = {}) ⇒ Object
- .gadgets(opts = {}) ⇒ Object
- .help ⇒ Object
- .io(opts = {}) ⇒ Object
- .libc_offsets(opts = {}) ⇒ Object
- .one_gadget(opts = {}) ⇒ Object
- .p16(opts = {}) ⇒ Object
- .p32(opts = {}) ⇒ Object
- .p64(opts = {}) ⇒ Object
- .p8(opts = {}) ⇒ Object
- .required_bins ⇒ Object
- .ret2libc(opts = {}) ⇒ Object
- .scaffold(opts = {}) ⇒ Object
- .shellcode(opts = {}) ⇒ Object
- .u16(opts = {}) ⇒ Object
- .u32(opts = {}) ⇒ Object
- .u64(opts = {}) ⇒ Object
- .u8(opts = {}) ⇒ Object
Class Method Details
.authors ⇒ Object
262 263 264 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 262 public_class_method def self. "AUTHOR(S):\n 0day Inc. <[email protected]>\n" end |
.cyclic(opts = {}) ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 49 public_class_method def self.cyclic(opts = {}) n = (opts[:length] || 100).to_i width = (opts[:width] || opts[:n] || 4).to_i width = 4 if width < 2 out = +'' i = 0 while out.length < n chunk = [] v = i width.times do chunk.unshift(CYCLIC_ALPHA[v % CYCLIC_ALPHA.length]) v /= CYCLIC_ALPHA.length end out << chunk.join i += 1 end out[0, n] end |
.cyclic_find(opts = {}) ⇒ Object
68 69 70 71 72 73 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 68 public_class_method def self.cyclic_find(opts = {}) needle = opts[:value] || opts[:subseq] hay = cyclic(length: (opts[:length] || 8_192).to_i, n: (opts[:n] || 4).to_i) blob = needle.is_a?(Integer) ? p32(value: needle, endian: opts[:endian]) : needle.to_s hay.index(blob) end |
.flat(opts = {}) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 75 public_class_method def self.flat(opts = {}) parts = Array(opts[:parts] || opts[:values] || opts[:arr]) endian = opts[:endian] parts.map do |p| case p when Integer then p32(value: p, endian: endian) when Array then p32(value: p[0], endian: endian) * p[1].to_i else p.to_s end end.join end |
.fmt_writes(opts = {}) ⇒ Object
136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 136 public_class_method def self.fmt_writes(opts = {}) addr = (opts[:addr] || 0).to_i value = (opts[:value] || 0).to_i offset = (opts[:offset] || 6).to_i { payload: "#{p32(value: addr)}%#{value}x%#{offset}$n", addr: addr, value: value, offset: offset } end |
.fmtstr(opts = {}) ⇒ Object
159 160 161 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 159 public_class_method def self.fmtstr(opts = {}) fmt_writes(opts) end |
.from_crash(opts = {}) ⇒ Object
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 198 public_class_method def self.from_crash(opts = {}) if opts[:crash].is_a?(Hash) crash = opts[:crash].transform_keys(&:to_sym) payload = (opts[:payload] || opts[:stdin]).to_s pc = crash[:pc].to_s.sub(/\A0x/i, '') blob = [pc].pack('H*') offset = cyclic_find(value: blob, length: [payload.bytesize, 8_192].max) unless payload.empty? || blob.empty? return crash.merge(offset: offset, payload_size: payload.bytesize) end path = (opts[:path] || opts[:from_crash]).to_s raise 'ERROR: path is required' if path.empty? raise "ERROR: file not found: #{path}" unless File.file?(path) JSON.parse(File.read(path), symbolize_names: true) end |
.gadgets(opts = {}) ⇒ Object
148 149 150 151 152 153 154 155 156 157 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 148 public_class_method def self.gadgets(opts = {}) path = opts[:path].to_s raise 'ERROR: path is required' if path.empty? row = PWN::Plugins::ROP.gadgets(opts) filt = (opts[:filter] || opts[:contains]).to_s return row if filt.empty? row.merge(gadgets: Array(row[:gadgets]).select { |g| g[:gadget].to_s.downcase.include?(filt.downcase) }) end |
.help ⇒ Object
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 266 public_class_method def self.help puts "USAGE: # List host binaries this module expects to be installed. #{self}.required_bins # Run p8 and return its result #{self}.p8( value: 'optional - integer or string to pack/encode', n: 'optional - count, width, or size', endian: 'optional - :little or :big byte order' ) # Run p16 and return its result #{self}.p16( value: 'optional - integer or string to pack/encode', n: 'optional - count, width, or size', endian: 'optional - :little or :big byte order' ) # Run p32 and return its result #{self}.p32( value: 'optional - integer or string to pack/encode', n: 'optional - count, width, or size', endian: 'optional - :little or :big byte order' ) # Run p64 and return its result #{self}.p64( value: 'optional - integer or string to pack/encode', n: 'optional - count, width, or size', endian: 'optional - :little or :big byte order' ) # Run u8 and return its result #{self}.u8( buf: 'optional - buf value consumed by #u8', data: 'optional - data value consumed by #u8', endian: 'optional - :little or :big byte order' ) # Run u16 and return its result #{self}.u16( buf: 'optional - buf value consumed by #u16', data: 'optional - data value consumed by #u16', endian: 'optional - :little or :big byte order' ) # Run u32 and return its result #{self}.u32( buf: 'optional - buf value consumed by #u32', data: 'optional - data value consumed by #u32', endian: 'optional - :little or :big byte order' ) # Run u64 and return its result #{self}.u64( buf: 'optional - buf value consumed by #u64', data: 'optional - data value consumed by #u64', endian: 'optional - :little or :big byte order' ) # Run cyclic and return its result #{self}.cyclic( length: 'optional - number of bytes or characters to generate', width: 'optional - cyclic de Bruijn sequence width in bytes', n: 'optional - count, width, or size' ) # Run cyclic find and return its result #{self}.cyclic_find( value: 'optional - integer or string to pack/encode (defaults to opts[:subseq])', subseq: 'optional - subseq value consumed by #cyclic_find', length: 'optional - number of bytes or characters to generate', n: 'optional - count, width, or size', endian: 'optional - :little or :big byte order' ) # Run flat and return its result #{self}.flat( parts: 'optional - parts value consumed by #flat', values: 'optional - values value consumed by #flat', arr: 'optional - arr value consumed by #flat', endian: 'optional - :little or :big byte order' ) # Run shellcode and return its result #{self}.shellcode( arch: 'optional - architecture string (as from objdump --info)', kind: 'optional - kind value consumed by #shellcode', payload: 'optional - payload value consumed by #shellcode', asm: 'required - assembly source (one instruction per line)', endian: 'optional - :little or :big byte order' ) # Run fmt writes and return its result #{self}.fmt_writes( addr: 'optional - address or flag (e.g. main or 0x401000)', value: 'optional - integer or string to pack/encode', offset: 'optional - offset value consumed by #fmt_writes' ) # Alias of fmt_writes for pwntools-style fmtstr helpers. #{self}.fmtstr( addr: 'optional - address or flag (e.g. main or 0x401000)', value: 'optional - integer or string to pack/encode', offset: 'optional - offset value consumed by #fmt_writes' ) # Open a local process or remote TCP tube for exploit IO. #{self}.io( kind: 'optional - process|remote|pty_session (defaults to process)', mode: 'optional - alias for kind', cmd: 'optional - command for process/pty_session', command: 'optional - alias for cmd', host: 'optional - remote host', target: 'optional - alias for host', port: 'optional - remote TCP port', pty: 'optional - false uses pipes for binary payloads (process default)' ) # Emit a ruby exploit scaffold bound to triage output. #{self}.scaffold( path: 'required - binary path to scaffold against', bin: 'optional - alias for path', out: 'optional - output .rb path (defaults under ~/.pwn/artifacts/scaffolds)' ) # Run gadgets and return its result #{self}.gadgets( path: 'required - filesystem path of the binary to search for gadgets', filter: 'optional - substring filter applied to gadget text', contains: 'optional - alias for filter', backend: 'optional - ropper|ROPgadget|rp|scan|objdump', constraints: 'optional - hash forwarded to ROP.filter', timeout: 'optional - gadget enumeration deadline in seconds' ) # Build a packed ret2libc payload from ELF GOT/PLT + pop rdi. #{self}.ret2libc( path: 'optional - filesystem path of the target ELF; required unless handoff evidence_path is set', handoff: 'optional - recon asset hash; evidence_path is used when path is omitted', asset: 'optional - alias for handoff', offset: 'optional - cyclic overflow offset in bytes (defaults to 72)', extra: 'optional - symbol name for the system() argument (defaults to cmd)', arg_sym: 'optional - alias for extra', arg: 'optional - explicit argument address', system: 'optional - explicit system address (defaults to PLT)', pop_rdi: 'optional - explicit pop rdi; ret address', ret: 'optional - extra ret gadget for stack alignment', pad: 'optional - pad payload to this many bytes so read() returns (defaults to 256)', backend: 'optional - gadget backend (defaults to scan)' ) # Resolve one_gadget offsets in a libc (needs the one_gadget gem/bin). #{self}.one_gadget( path: 'required - filesystem path to libc.so', libc: 'optional - alias for path' ) # Map named libc symbols to virtual addresses via BinaryParser. #{self}.libc_offsets( path: 'required - filesystem path to libc or any ELF', names: 'optional - Array of symbol names (defaults to system, execve, __libc_start_main)' ) # Load a crash.json or consume a GDBMI run_to_crash hash for cyclic offsets. #{self}.from_crash( path: 'optional - filesystem path of crash.json; required unless crash is supplied', from_crash: 'optional - alias for path', crash: 'optional - Hash from GDBMI.run_to_crash', payload: 'optional - crashing input used to recover a cyclic offset', stdin: 'optional - alias for payload' ) # Print the AUTHOR(S) string for this module. #{self}.authors " constants.sort end |
.io(opts = {}) ⇒ Object
163 164 165 166 167 168 169 170 171 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 163 public_class_method def self.io(opts = {}) kind = (opts[:kind] || opts[:mode] || 'process').to_s case kind when 'remote' PWN::Plugins::ProcessTube.connect(host: opts[:host] || opts[:target], port: opts[:port]) else PWN::Plugins::ProcessTube.spawn(cmd: opts[:cmd] || opts[:command], pty: opts.fetch(:pty, false)) end end |
.libc_offsets(opts = {}) ⇒ Object
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 118 public_class_method def self.libc_offsets(opts = {}) path = opts[:path].to_s raise 'ERROR: path is required' if path.empty? wanted = Array(opts[:names] || %w[system execve __libc_start_main]) syms = PWN::Plugins::BinaryParser.symbols(path: path, limit: 20_000) map = {} Array(syms).each do |s| n = s[:name].to_s next if n.empty? wanted.each do |w| map[w.to_s] = s[:value] if n == w.to_s || n.end_with?(w.to_s) end end { path: path, symbols: map } end |
.one_gadget(opts = {}) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 102 public_class_method def self.one_gadget(opts = {}) path = (opts[:path] || opts[:libc]).to_s raise 'ERROR: path is required' if path.empty? return { error: 'one_gadget missing', hint: 'pwn setup --profile re', path: path } unless PWN::Plugins::PreflightChecker.bin?(name: 'one_gadget') stdout, stderr, status = Open3.capture3('one_gadget', path) { path: path, stdout: stdout, stderr: stderr, exit: status.exitstatus, gadgets: stdout.scan(/0x[0-9a-fA-F]+/) } end |
.p16(opts = {}) ⇒ Object
21 22 23 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 21 public_class_method def self.p16(opts = {}) pack_int(value: opts[:value] || opts[:n], bytes: 2, endian: opts[:endian]) end |
.p32(opts = {}) ⇒ Object
25 26 27 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 25 public_class_method def self.p32(opts = {}) pack_int(value: opts[:value] || opts[:n], bytes: 4, endian: opts[:endian]) end |
.p64(opts = {}) ⇒ Object
29 30 31 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 29 public_class_method def self.p64(opts = {}) pack_int(value: opts[:value] || opts[:n], bytes: 8, endian: opts[:endian]) end |
.p8(opts = {}) ⇒ Object
17 18 19 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 17 public_class_method def self.p8(opts = {}) pack_int(value: opts[:value] || opts[:n], bytes: 1, endian: opts[:endian]) end |
.required_bins ⇒ Object
13 14 15 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 13 public_class_method def self.required_bins [] end |
.ret2libc(opts = {}) ⇒ Object
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 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 215 public_class_method def self.ret2libc(opts = {}) handoff = PWN::Plugins::Recon.handoff(handoff: opts[:handoff] || opts[:asset]) if opts[:handoff] || opts[:asset] path = opts[:path].to_s path = handoff[:evidence_path] if path.empty? && handoff raise 'ERROR: path is required' if path.empty? resolved = PWN::Plugins::BinaryParser.elf_resolve(path: path) gadgets = PWN::Plugins::ROP.gadgets(path: path, backend: opts[:backend] || 'scan') pop = opts[:pop_rdi] unless pop hit = Array(gadgets[:gadgets]).find { |row| row[:gadget].to_s.match?(/pop rdi/i) } pop = hit && hit[:address] end system = opts[:system] || resolved[:plt]['system'] || resolved[:symbols]['system'] extra = (opts[:extra] || opts[:arg_sym] || 'cmd').to_s arg = opts[:arg] || resolved[:symbols][extra] || resolved[:strings]['/bin/sh'] || resolved[:strings]['echo RET2LIBC_OK'] offset = (opts[:offset] || 72).to_i raise 'ERROR: missing pop rdi, system, or argument address' unless pop && system && arg align = opts[:ret] align ||= pop + 1 if pop && Array(gadgets[:gadgets]).any? { |row| row[:address] == pop && row[:gadget].to_s.match?(/pop rdi; ret/i) } align ||= Array(gadgets[:gadgets]).find { |row| row[:gadget].to_s == 'ret' && row[:address] != pop }&.fetch(:address, nil) packed = [pop, arg] packed << align if align && align != pop packed << system payload = ('A' * offset) + packed.map { |addr| p64(value: addr) }.join pad = (opts[:pad] || 256).to_i payload += "\x00" * (pad - payload.bytesize) if pad > payload.bytesize result = { payload: payload, payload_hex: payload.unpack1('H*'), offset: offset, pop_rdi: pop, ret: align, system: system, arg: arg, binsh: arg, plt: resolved[:plt], got: resolved[:got], symbols: resolved[:symbols], gadgets: gadgets[:gadgets] } return result unless handoff result.merge(handoff: handoff) end |
.scaffold(opts = {}) ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 173 public_class_method def self.scaffold(opts = {}) path = (opts[:path] || opts[:bin]).to_s raise 'ERROR: path is required' if path.empty? triage = {} triage = PWN::Plugins::BinaryParser.triage(path: path) if defined?(PWN::Plugins::BinaryParser) gadgets = [] gadgets = Array(self.gadgets(path: path)[:gadgets]) if File.file?(path) body = " # frozen_string_literal: true\n # scaffold for \#{path}\n # arch=\#{triage[:arch]} pie=\#{triage.dig(:protections, :pie)} canary=\#{triage.dig(:protections, :canary)}\n require 'pwn'\n bin = \#{path.inspect}\n cyclic = PWN::Plugins::ExploitDev.cyclic(length: 200)\n io = PWN::Plugins::ExploitDev.io(cmd: bin)\n PWN::Plugins::ProcessTube.write_line(id: io[:id], line: cyclic)\n RB\n out = opts[:out].to_s\n out = File.join(Dir.home, '.pwn', 'artifacts', 'scaffolds', \"\#{File.basename(path)}.rb\") if out.empty?\n FileUtils.mkdir_p(File.dirname(out))\n File.write(out, body)\n { path: out, triage: triage, gadgets: gadgets.first(8) }\nend\n" |
.shellcode(opts = {}) ⇒ Object
87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 87 public_class_method def self.shellcode(opts = {}) arch = (opts[:arch] || 'x86_64').to_s kind = (opts[:kind] || opts[:payload] || 'nop').to_s return execve_bin_sh_bytes(arch: arch) if kind == 'execve_bin_sh' asm = case kind when 'nop' then 'nop' when 'ret' then 'ret' else opts[:asm].to_s end raise 'ERROR: asm is required for custom shellcode' if asm.empty? PWN::Plugins::Assembly.asm_to_opcodes(asm: asm, arch: arch, endian: opts[:endian]) end |
.u16(opts = {}) ⇒ Object
37 38 39 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 37 public_class_method def self.u16(opts = {}) unpack_int(buf: opts[:buf] || opts[:data], bytes: 2, endian: opts[:endian]) end |
.u32(opts = {}) ⇒ Object
41 42 43 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 41 public_class_method def self.u32(opts = {}) unpack_int(buf: opts[:buf] || opts[:data], bytes: 4, endian: opts[:endian]) end |
.u64(opts = {}) ⇒ Object
45 46 47 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 45 public_class_method def self.u64(opts = {}) unpack_int(buf: opts[:buf] || opts[:data], bytes: 8, endian: opts[:endian]) end |
.u8(opts = {}) ⇒ Object
33 34 35 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 33 public_class_method def self.u8(opts = {}) unpack_int(buf: opts[:buf] || opts[:data], bytes: 1, endian: opts[:endian]) end |