class Rex::Exploitation::RopDb

This class provides methods to access the ROP database, in order to generate a ROP-compatible payload on the fly.

Public Class Methods

new() click to toggle source
# File lib/rex/exploitation/ropdb.rb, line 15
def initialize
  @base_path = File.join(File.dirname(__FILE__), '../../../data/ropdb/')
end

Public Instance Methods

generate_rop_payload(rop, payload, opts={}) click to toggle source

Returns a payload with the user-supplied stack-pivot, a ROP chain, and then shellcode. Arguments: rop - Name of the ROP chain payload - Payload in binary opts - A hash of optional arguments:

'nop'      - Used to generate nops with generate_sled()
'badchars' - Used in a junk gadget
'pivot'    - Stack pivot in binary
'target'   - A regex string search against the compatibility list.
'base'     - Specify a different base for the ROP gadgets.
# File lib/rex/exploitation/ropdb.rb, line 77
def generate_rop_payload(rop, payload, opts={})
  nop      = opts['nop']      || nil
  badchars = opts['badchars'] || ''
  pivot    = opts['pivot']    || ''
  target   = opts['target']   || ''
  base     = opts['base']     || nil

  rop = select_rop(rop, {'target'=>target, 'base'=>base})
  # Replace the reserved words with actual gadgets
  rop = rop.map {|e|
    if e == :nop
      sled = (nop) ? nop.generate_sled(4, badchars).unpack("V*")[0] : 0x90909090
    elsif e == :junk
      Rex::Text.rand_text(4, badchars).unpack("V")[0].to_i
    elsif e == :size
      payload.length
    elsif e == :unsafe_negate_size
      get_unsafe_size(payload.length)
    elsif e == :safe_negate_size
      get_safe_size(payload.length)
    else
      e
    end
  }.pack("V*")

  raise RuntimeError, "No ROP chain generated successfully" if rop.empty?

  return pivot + rop + payload
end
has_rop?(rop_name) click to toggle source

Returns true if a ROP chain is available, otherwise false

# File lib/rex/exploitation/ropdb.rb, line 25
def has_rop?(rop_name)
  File.exists?(File.join(@base_path, "#{rop_name}.xml"))
end
select_rop(rop, opts={}) click to toggle source

Returns an array of ROP gadgets. Each gadget can either be an offset, or a value (symbol or some integer). When the value is a symbol, it can be one of these: :nop, :junk, :size, :unsafe_negate_size, and :safe_negate_size Note if no RoP is found, it returns an empry array. Arguments: rop_name - name of the ROP chain. opts - A hash of optional arguments:

'target' - A regex string search against the compatibility list.
'base'   - Specify a different base for the ROP gadgets.
# File lib/rex/exploitation/ropdb.rb, line 40
def select_rop(rop, opts={})
  target = opts['target'] || ''
  base   = opts['base']   || nil

  raise RuntimeError, "#{rop} ROP chain is not available" if not has_rop?(rop)
  xml = load_rop(File.join(@base_path, "#{rop}.xml"))

  gadgets = []

  xml.elements.each("db/rop") { |e|
    name = e.attributes['name']
    next if not has_target?(e, target)

    if not base
      default = e.elements['gadgets'].attributes['base'].scan(/^0x([0-9a-f]+)$/i).flatten[0]
      base = default.to_i(16)
    end

    gadgets << parse_gadgets(e, base)
  }
  return gadgets.flatten
end

Private Instance Methods

get_safe_size(s) click to toggle source

Returns a size that's safe from null bytes. This function will keep incrementing the value of ā€œsā€ until it's safe from null bytes.

# File lib/rex/exploitation/ropdb.rb, line 114
def get_safe_size(s)
  safe_size = get_unsafe_size(s)
  while (safe_size.to_s(16).rjust(8, '0')).scan(/../).include?("00")
    safe_size -= 1
  end

  safe_size
end
get_unsafe_size(s) click to toggle source

Returns a size that might contain one or more null bytes

# File lib/rex/exploitation/ropdb.rb, line 127
def get_unsafe_size(s)
  0xffffffff - s + 1
end
has_target?(rop, target) click to toggle source

Checks if a ROP chain is compatible

# File lib/rex/exploitation/ropdb.rb, line 135
def has_target?(rop, target)
  rop.elements.each('compatibility/target') { |t|
    return true if t.text =~ /#{target}/i
  }
  return false
end
load_rop(file_path) click to toggle source

Returns the database in XML

# File lib/rex/exploitation/ropdb.rb, line 145
def load_rop(file_path)
  f = File.open(file_path, 'rb')
  xml = REXML::Document.new(f.read(f.stat.size))
  f.close
  return xml
end
parse_gadgets(e, image_base) click to toggle source

Returns gadgets

# File lib/rex/exploitation/ropdb.rb, line 156
def parse_gadgets(e, image_base)
  gadgets = []
  e.elements.each('gadgets/gadget') { |g|
    offset = g.attributes['offset']
    value  = g.attributes['value']

    if offset
      addr = offset.scan(/^0x([0-9a-f]+)$/i).flatten[0]
      gadgets << (image_base + addr.to_i(16))
    elsif value
      case value
      when 'nop'
        gadgets << :nop
      when 'junk'
        gadgets << :junk
      when 'size'
        gadgets << :size
      when 'unsafe_negate_size'
        gadgets << :unsafe_negate_size
      when 'safe_negate_size'
        gadgets << :safe_negate_size
      else
        gadgets << value.to_i(16)
      end
    else
      raise RuntimeError, "Missing offset or value attribute in '#{name}'"
    end
  }
  return gadgets
end