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
- .scaffold(opts = {}) ⇒ Object
- .shellcode(opts = {}) ⇒ Object
- .u16(opts = {}) ⇒ Object
- .u32(opts = {}) ⇒ Object
- .u64(opts = {}) ⇒ Object
- .u8(opts = {}) ⇒ Object
Class Method Details
.authors ⇒ Object
209 210 211 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 209 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
162 163 164 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 162 public_class_method def self.fmtstr(opts = {}) fmt_writes(opts) end |
.from_crash(opts = {}) ⇒ Object
201 202 203 204 205 206 207 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 201 public_class_method def self.from_crash(opts = {}) 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 158 159 160 |
# 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? if PWN::Plugins::PreflightChecker.bin?(name: 'r2') sid = PWN::Plugins::Radare2.open(path: path) PWN::Plugins::Radare2.cmd(session: sid, cmd: '/R') elsif PWN::Plugins::PreflightChecker.bin?(name: 'ROPgadget') `ROPgadget --binary #{path}`.to_s else { error: 'r2 and ROPgadget missing', hint: 'pwn setup --profile re' } end end |
.help ⇒ Object
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 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 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 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 213 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' ) # 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' ) # 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 produced by a fuzz campaign. #{self}.from_crash( path: 'required - filesystem path of crash.json', from_crash: 'optional - alias for path' ) # Print the AUTHOR(S) string for this module. #{self}.authors " constants.sort end |
.io(opts = {}) ⇒ Object
166 167 168 169 170 171 172 173 174 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 166 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]) 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 |
.scaffold(opts = {}) ⇒ Object
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
# File 'lib/pwn/plugins/exploit_dev.rb', line 176 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 = <<~RB # frozen_string_literal: true # scaffold for #{path} # arch=#{triage[:arch]} pie=#{triage.dig(:protections, :pie)} canary=#{triage.dig(:protections, :canary)} require 'pwn' bin = #{path.inspect} cyclic = PWN::Plugins::ExploitDev.cyclic(length: 200) io = PWN::Plugins::ExploitDev.io(cmd: bin) PWN::Plugins::ProcessTube.write_line(id: io[:id], line: cyclic) RB out = opts[:out].to_s out = File.join(Dir.home, '.pwn', 'artifacts', 'scaffolds', "#{File.basename(path)}.rb") if out.empty? FileUtils.mkdir_p(File.dirname(out)) File.write(out, body) { path: out, triage: triage, gadgets: gadgets.first(8) } end |
.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 |