190 lines
4.7 KiB
Ruby
190 lines
4.7 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'rex/text'
|
|
require 'rexml/document'
|
|
|
|
|
|
module Rex
|
|
module Exploitation
|
|
|
|
###
|
|
#
|
|
# This class provides methods to access the ROP database, in order to generate
|
|
# a ROP-compatible payload on the fly.
|
|
#
|
|
###
|
|
class RopDb
|
|
def initialize
|
|
@base_path = File.join(File.dirname(__FILE__), '../../../data/ropdb/')
|
|
end
|
|
|
|
public
|
|
|
|
|
|
#
|
|
# Returns true if a ROP chain is available, otherwise false
|
|
#
|
|
def has_rop?(rop_name)
|
|
File.exists?(File.join(@base_path, "#{rop_name}.xml"))
|
|
end
|
|
|
|
#
|
|
# 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.
|
|
#
|
|
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
|
|
|
|
|
|
#
|
|
# 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.
|
|
#
|
|
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
|
|
|
|
private
|
|
|
|
|
|
#
|
|
# 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.
|
|
#
|
|
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
|
|
|
|
|
|
#
|
|
# Returns a size that might contain one or more null bytes
|
|
#
|
|
def get_unsafe_size(s)
|
|
0xffffffff - s + 1
|
|
end
|
|
|
|
|
|
#
|
|
# Checks if a ROP chain is compatible
|
|
#
|
|
def has_target?(rop, target)
|
|
rop.elements.each('compatibility/target') { |t|
|
|
return true if t.text =~ /#{target}/i
|
|
}
|
|
return false
|
|
end
|
|
|
|
#
|
|
# Returns the database in XML
|
|
#
|
|
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
|
|
|
|
|
|
#
|
|
# Returns gadgets
|
|
#
|
|
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
|
|
end
|
|
|
|
end
|
|
end |