Module: PWN::Plugins::ROP

Defined in:
lib/pwn/plugins/rop.rb

Overview

Read-only gadget enumeration; clobbers are conservative syntactic estimates.

Class Method Summary collapse

Class Method Details

.authors ⇒ Object



157
158
159
# File 'lib/pwn/plugins/rop.rb', line 157

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.filter(opts = {}) ⇒ Object

Raises:

  • (ArgumentError)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/pwn/plugins/rop.rb', line 48

public_class_method def self.filter(opts = {})
  constraints = opts[:constraints] || {}
  constraints = constraints.transform_keys(&:to_sym)
  allowed = i[contains max_instructions preserve min_address max_address]
  raise ArgumentError, "unsupported constraints: #{constraints.keys - allowed}" unless (constraints.keys - allowed).empty?

  Array(opts[:gadgets]).select do |row|
    preserved = Array(constraints[:preserve]).map { |register| canonical_register(register: register) }
    clobbered = Array(row[:regs_clobbered]).map { |register| canonical_register(register: register) }
    (!constraints[:contains] || row[:gadget].include?(constraints[:contains].to_s)) &&
      (!constraints[:max_instructions] || row[:gadget].split(';').length <= Integer(constraints[:max_instructions])) &&
      (preserved.empty? || (row[:clobbers_complete] && !preserved.intersect?(clobbered))) &&
      (!constraints[:min_address] || row[:address] >= Integer(constraints[:min_address])) &&
      (!constraints[:max_address] || row[:address] <= Integer(constraints[:max_address]))
  end
end

.gadgets(opts = {}) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/pwn/plugins/rop.rb', line 28

public_class_method def self.gadgets(opts = {})
  path = File.realpath(File.expand_path(opts[:path].to_s))
  backend = opts[:backend] || %w[ropper ROPgadget rp].find { |name| BinaryAnalysis.available?(name: name) } || 'scan'
  rows, status, warnings = collect_gadgets(path: path, backend: backend, timeout: opts.fetch(:timeout, 60))
  rows.each { |row| annotate(row: row) }
  {
    backend: backend,
    status: status,
    risk_level: 'low',
    path: path,
    sha256: Digest::SHA256.file(path).hexdigest,
    gadgets: filter(gadgets: rows, constraints: opts[:constraints] || {}),
    warnings: warnings
  }
rescue StandardError => e
  raise if %w[scan objdump].include?(backend.to_s) || e.is_a?(ArgumentError)

  gadgets(opts.merge(backend: 'scan')).tap { |result| result[:warnings] << "#{backend}: #{e.message}" }
end

.help ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/pwn/plugins/rop.rb', line 161

public_class_method def self.help
  puts "USAGE:
    # List expected analysis backend executables.
    #{self}.required_bins

    # Parse ropper, rp, or ROPgadget listings into queryable gadget records.
    #{self}.parse(
      text: 'required - raw gadget listing text',
      backend: 'optional - ropper|rp|ROPgadget (format hint only)'
    )

    # Enumerate read-only gadgets and conservatively filter register constraints.
    #{self}.gadgets(
      backend: 'optional - ropper|ROPgadget|rp|scan|objdump',
      constraints: 'optional - hash with contains, preserve register array, instruction and address bounds',
      path: 'required - filesystem path to the local artifact or binary',
      timeout: 'optional - positive subprocess or HTTP deadline in seconds'
    )

    # Filter already enumerated gadget records with an explicit constraint set.
    #{self}.filter(
      constraints: 'optional - hash with contains, preserve register array, instruction and address bounds',
      gadgets: 'required - array of normalized gadget records returned by enumeration'
    )

    # Display module authors.
    #{self}.authors
  "
  constants.sort
end

.parse(opts = {}) ⇒ Object



14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/pwn/plugins/rop.rb', line 14

public_class_method def self.parse(opts = {})
  text = opts[:text].to_s
  _backend = opts[:backend]
  text.lines.filter_map do |line|
    match = line.match(/^\s*(0x[0-9a-fA-F]+|[0-9a-fA-F]{6,})\s*:\s*(.+)$/)
    next unless match

    token = match[1]
    address = token.to_s.sub(/\A0x/i, '').to_i(16)
    gadget = match[2].strip.sub(/;\s*$/, '').gsub(/\s*;\s*/, '; ')
    annotate(row: { address: address, gadget: gadget })
  end
end

.required_bins ⇒ Object



10
11
12
# File 'lib/pwn/plugins/rop.rb', line 10

public_class_method def self.required_bins
  %w[ROPgadget ropper objdump]
end