More work on the msfpescan port
git-svn-id: file:///home/svn/incoming/trunk@3599 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
da41886856
commit
17d2ba798b
|
@ -107,6 +107,7 @@ class Pe < PeBase
|
|||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Save the stuffs!
|
||||
#
|
||||
|
@ -116,20 +117,29 @@ class Pe < PeBase
|
|||
#
|
||||
|
||||
#
|
||||
# You shouldn't need to access these!
|
||||
# These should not be accessed directly
|
||||
#
|
||||
|
||||
|
||||
self._isource = isource
|
||||
|
||||
self._dos_header = dos_header
|
||||
self._file_header = file_header
|
||||
self._optional_header = optional_header
|
||||
self._section_headers = section_headers
|
||||
|
||||
|
||||
self.image_base = base
|
||||
self.sections = sections
|
||||
self.header_section = header_section
|
||||
|
||||
|
||||
self._config_header = _parse_config_header()
|
||||
|
||||
# These can be accessed directly
|
||||
self.hdr = HeaderAccessor.new
|
||||
self.hdr.dos = self._dos_header
|
||||
self.hdr.file = self._file_header
|
||||
self.hdr.opt = self._optional_header
|
||||
self.hdr.sections = self._section_headers
|
||||
self.hdr.config = self._config_header
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -141,6 +151,5 @@ class Pe < PeBase
|
|||
[ header_section ] + sections
|
||||
end
|
||||
|
||||
|
||||
end end end
|
||||
|
||||
|
|
|
@ -76,19 +76,40 @@ class PeBase
|
|||
[ 'uint32v', 'e_lfanew', 0 ]
|
||||
)
|
||||
|
||||
|
||||
class HeaderAccessor
|
||||
attr_accessor :dos, :file, :opt, :sections, :config
|
||||
def initialize
|
||||
end
|
||||
end
|
||||
|
||||
class GenericHeader
|
||||
attr_accessor :struct
|
||||
def initialize(_struct)
|
||||
self.struct = _struct
|
||||
end
|
||||
|
||||
# this sucks...
|
||||
# The following methods are just pass-throughs for struct
|
||||
|
||||
# Access a value
|
||||
def v
|
||||
struct.v
|
||||
end
|
||||
|
||||
# Access a value by array
|
||||
def [](*args)
|
||||
struct[*args]
|
||||
end
|
||||
|
||||
# Obtain an array of all fields
|
||||
def keys
|
||||
struct.keys
|
||||
end
|
||||
|
||||
def method_missing(meth, *args)
|
||||
v[meth.to_s] || (raise NoMethodError.new, meth)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class DosHeader < GenericHeader
|
||||
|
@ -350,7 +371,7 @@ class PeBase
|
|||
[ 'uint32v', 'SizeOfUninitializeData', 0 ],
|
||||
[ 'uint32v', 'AddressOfEntryPoint', 0 ],
|
||||
[ 'uint32v', 'BaseOfCode', 0 ],
|
||||
[ 'uint32v', 'BaseOfdata', 0 ],
|
||||
[ 'uint32v', 'BaseOfData', 0 ],
|
||||
[ 'uint32v', 'ImageBase', 0 ],
|
||||
[ 'uint32v', 'SectionAlignment', 0 ],
|
||||
[ 'uint32v', 'FileAlignment', 0 ],
|
||||
|
@ -563,7 +584,6 @@ class PeBase
|
|||
IMAGE_LOAD_CONFIG_DIRECTORY32 = Rex::Struct2::CStructTemplate.new(
|
||||
[ 'uint32v', 'Size', 0 ],
|
||||
[ 'uint32v', 'TimeDateStamp', 0 ],
|
||||
[ 'uint32v', 'TimeDateStamp', 0 ],
|
||||
[ 'uint16v', 'MajorVersion', 0 ],
|
||||
[ 'uint16v', 'MinorVersion', 0 ],
|
||||
[ 'uint32v', 'GlobalFlagsClear', 0 ],
|
||||
|
@ -584,7 +604,39 @@ class PeBase
|
|||
[ 'uint32v', 'SEHandlerCount', 0 ]
|
||||
)
|
||||
|
||||
class ConfigHeader < GenericHeader
|
||||
|
||||
end
|
||||
|
||||
def self._parse_config_header(rawdata)
|
||||
header = IMAGE_LOAD_CONFIG_DIRECTORY32.make_struct
|
||||
header.from_s(rawdata)
|
||||
ConfigHeader.new(header)
|
||||
end
|
||||
|
||||
def _parse_config_header
|
||||
|
||||
#
|
||||
# Get the data directory entry, size, etc
|
||||
#
|
||||
exports_entry = _optional_header['DataDirectory'][10]
|
||||
rva = exports_entry.v['VirtualAddress']
|
||||
size = exports_entry.v['Size']
|
||||
|
||||
return nil if size == 0
|
||||
|
||||
#
|
||||
# Ok, so we have the data directory, now lets parse it
|
||||
#
|
||||
|
||||
dirdata = _isource.read(rva_to_file_offset(rva), size)
|
||||
|
||||
header = IMAGE_LOAD_CONFIG_DIRECTORY32.make_struct
|
||||
header.from_s(dirdata)
|
||||
|
||||
ConfigHeader.new(header)
|
||||
end
|
||||
|
||||
#
|
||||
# Just a stupid routine to round an offset up to it's alignment.
|
||||
#
|
||||
|
@ -603,15 +655,16 @@ class PeBase
|
|||
|
||||
attr_accessor :_isource
|
||||
attr_accessor :_dos_header, :_file_header, :_optional_header,
|
||||
:_section_headers
|
||||
:_section_headers, :_config_header
|
||||
|
||||
attr_accessor :sections, :header_section, :image_base
|
||||
|
||||
attr_accessor :_imports_cache, :_imports_cached
|
||||
attr_accessor :_exports_cache, :_exports_cached
|
||||
attr_accessor :_relocations_cache, :_relocations_cached
|
||||
attr_accessor :_loadconfig_cache, :_loadconfig_cached
|
||||
|
||||
|
||||
attr_accessor :hdr
|
||||
|
||||
def self.new_from_file(filename, disk_backed = false)
|
||||
|
||||
file = ::File.new(filename)
|
||||
|
@ -959,28 +1012,5 @@ class PeBase
|
|||
return relocdirs
|
||||
end
|
||||
|
||||
def loadconfig
|
||||
if !_loadconfig_cached
|
||||
self._loadconfig_cache = _load_loadconfig
|
||||
self._loadconfig_cached = true
|
||||
end
|
||||
return _loadconfig_cache
|
||||
end
|
||||
|
||||
def _load_loadconfig
|
||||
|
||||
#
|
||||
# Get the data directory entry, size, etc
|
||||
#
|
||||
exports_entry = _optional_header['DataDirectory'][10]
|
||||
rva = exports_entry.v['VirtualAddress']
|
||||
size = exports_entry.v['Size']
|
||||
|
||||
return nil if size == 0
|
||||
|
||||
return size
|
||||
end
|
||||
|
||||
|
||||
end end end
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
module Rex
|
||||
module PeScan
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/pescan/scanner'
|
||||
require 'rex/pescan/search'
|
|
@ -0,0 +1,194 @@
|
|||
module Rex
|
||||
module PeScan
|
||||
module Scanner
|
||||
|
||||
class Generic
|
||||
|
||||
attr_accessor :pe, :regex
|
||||
|
||||
def initialize(pe)
|
||||
self.pe = pe
|
||||
end
|
||||
|
||||
def config(param)
|
||||
end
|
||||
|
||||
def scan(param)
|
||||
config(param)
|
||||
pe.all_sections.each do |section|
|
||||
hits = scan_section(section, param)
|
||||
hits.each do |hit|
|
||||
vma = pe.rva_to_vma(hit[0])
|
||||
msg = hit[1]
|
||||
$stdout.puts "0x%08x %s" % [ vma, msg ]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def scan_section(section, param={})
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
class JmpRegScanner < Generic
|
||||
|
||||
def config(param)
|
||||
regnums = param['args']
|
||||
|
||||
# build a list of the call bytes
|
||||
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
|
||||
jmps = _build_byte_list(0xe0, regnums)
|
||||
pushs1 = _build_byte_list(0x50, regnums)
|
||||
pushs2 = _build_byte_list(0xf0, regnums)
|
||||
|
||||
regexstr = '('
|
||||
if !calls.empty?
|
||||
regexstr += "\xff[#{calls}]|"
|
||||
end
|
||||
|
||||
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
|
||||
|
||||
self.regex = Regexp.new(regexstr)
|
||||
end
|
||||
|
||||
# build a list for regex of the possible bytes, based on a base
|
||||
# byte and a list of register numbers..
|
||||
def _build_byte_list(base, regnums)
|
||||
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
|
||||
end
|
||||
|
||||
def _ret_size(section, index)
|
||||
case section.read(index, 1)
|
||||
when "\xc3"
|
||||
return 1
|
||||
when "\xc2"
|
||||
return 3
|
||||
end
|
||||
|
||||
raise "wtf"
|
||||
end
|
||||
|
||||
def _parse_ret(data)
|
||||
if data.length == 1
|
||||
return "ret"
|
||||
else
|
||||
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def scan_section(section, param={})
|
||||
index = 0
|
||||
|
||||
hits = [ ]
|
||||
|
||||
while (index = section.index(regex, index)) != nil
|
||||
rva = section.offset_to_rva(index)
|
||||
message = ''
|
||||
|
||||
parse_ret = false
|
||||
|
||||
byte1 = section.read(index, 1)[0]
|
||||
|
||||
if byte1 == 0xff
|
||||
byte2 = section.read(index+1, 1)[0]
|
||||
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
|
||||
|
||||
case byte2 & 0xf8
|
||||
when 0xd0
|
||||
message = "call #{regname}"
|
||||
index += 2
|
||||
when 0xe0
|
||||
message = "jmp #{regname}"
|
||||
index += 2
|
||||
when 0xf0
|
||||
retsize = _ret_size(section, index+2)
|
||||
message = "push #{regname}; " + _parse_ret(section.read(index+2, retsize))
|
||||
index += 2 + retsize
|
||||
else
|
||||
raise "wtf"
|
||||
end
|
||||
else
|
||||
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
|
||||
retsize = _ret_size(section, index+1)
|
||||
message = "push #{regname}; " + _parse_ret(section.read(index+1, retsize))
|
||||
index += 1 + retsize
|
||||
end
|
||||
|
||||
hits << [ rva, message ]
|
||||
end
|
||||
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
class PopPopRetScanner < JmpRegScanner
|
||||
|
||||
def config(param)
|
||||
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
|
||||
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)")
|
||||
end
|
||||
|
||||
def scan_section(section, param={})
|
||||
|
||||
index = 0
|
||||
|
||||
hits = [ ]
|
||||
|
||||
while index < section.size && (index = section.index(regex, index)) != nil
|
||||
rva = section.offset_to_rva(index)
|
||||
message = ''
|
||||
|
||||
pops = section.read(index, 2)
|
||||
reg1 = Rex::Arch::X86.reg_name32(pops[0] & 0x7)
|
||||
reg2 = Rex::Arch::X86.reg_name32(pops[1] & 0x7)
|
||||
|
||||
message = "pop #{reg1}; pop #{reg2}; "
|
||||
|
||||
retsize = _ret_size(section, index+2)
|
||||
message += _parse_ret(section.read(index+2, retsize))
|
||||
|
||||
index += 2 + retsize
|
||||
|
||||
hits << [ rva, message ]
|
||||
end
|
||||
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
class RegexScanner < JmpRegScanner
|
||||
|
||||
def config(param)
|
||||
self.regex = Regexp.new(param['args'])
|
||||
end
|
||||
|
||||
def scan_section(section, param={})
|
||||
index = 0
|
||||
|
||||
hits = [ ]
|
||||
|
||||
while index < section.size && (index = section.index(regex, index)) != nil
|
||||
|
||||
idx = index
|
||||
buf = ''
|
||||
mat = nil
|
||||
|
||||
while (! (mat = buf.match(regex)))
|
||||
buf << section.read(idx, 1)
|
||||
idx += 1
|
||||
end
|
||||
|
||||
rva = section.offset_to_rva(index)
|
||||
|
||||
hits << [ rva, buf.unpack("H*") ]
|
||||
index += buf.length
|
||||
end
|
||||
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,40 @@
|
|||
module Rex
|
||||
module PeScan
|
||||
module Search
|
||||
|
||||
class DumpRVA
|
||||
attr_accessor :pe
|
||||
|
||||
def initialize(pe)
|
||||
self.pe = pe
|
||||
end
|
||||
|
||||
def config(param)
|
||||
@address = pe.vma_to_rva(param['args'])
|
||||
end
|
||||
|
||||
def scan(param)
|
||||
config(param)
|
||||
|
||||
# Adjust based on -A and -B flags
|
||||
pre = param['before'] || 0
|
||||
suf = param['after'] || 16
|
||||
|
||||
@address -= pre
|
||||
@address = 0 if (@address < 0 || ! @address)
|
||||
buf = pe.read_rva(@address, suf)
|
||||
$stdout.puts "0x%08x %s" % [ pe.rva_to_vma(@address), buf.unpack("H*") ]
|
||||
end
|
||||
end
|
||||
|
||||
class DumpOffset < DumpRVA
|
||||
def config(param)
|
||||
begin
|
||||
@address = pe.file_offset_to_rva(param['args'])
|
||||
rescue Rex::PeParsey::BoundsError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -160,6 +160,18 @@ class CStruct < SStruct
|
|||
return super(index, *other)
|
||||
end
|
||||
end
|
||||
|
||||
# Produce a list of field names
|
||||
def keys
|
||||
@name_table
|
||||
end
|
||||
|
||||
# Iterate through all fields and values
|
||||
def each_pair(&block)
|
||||
@name_table.each do |k|
|
||||
block.call(k, self.v[k])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# end Rex::Struct2
|
||||
|
|
196
msfpescan
196
msfpescan
|
@ -3,9 +3,15 @@
|
|||
$:.unshift(File.join(File.dirname(__FILE__), 'lib'))
|
||||
|
||||
require 'rex/peparsey'
|
||||
require 'rex/pescan'
|
||||
require 'rex/arch/x86'
|
||||
require 'optparse'
|
||||
|
||||
def opt2i(o)
|
||||
o.index("0x")==0 ? o.hex : o.to_i
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# Right now this program is a bit shakey...
|
||||
#
|
||||
|
@ -16,156 +22,60 @@ require 'optparse'
|
|||
# - etc etc
|
||||
#
|
||||
|
||||
class JmpRegScanner
|
||||
attr_accessor :regex
|
||||
|
||||
def initialize(regnums)
|
||||
# build a list of the call bytes
|
||||
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
|
||||
jmps = _build_byte_list(0xe0, regnums)
|
||||
pushs1 = _build_byte_list(0x50, regnums)
|
||||
pushs2 = _build_byte_list(0xf0, regnums)
|
||||
|
||||
regexstr = '('
|
||||
if !calls.empty?
|
||||
regexstr += "\xff[#{calls}]|"
|
||||
end
|
||||
|
||||
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
|
||||
|
||||
self.regex = Regexp.new(regexstr)
|
||||
end
|
||||
|
||||
# build a list for regex of the possible bytes, based on a base
|
||||
# byte and a list of register numbers..
|
||||
def _build_byte_list(base, regnums)
|
||||
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
|
||||
end
|
||||
|
||||
def _ret_size(section, index)
|
||||
case section.read(index, 1)
|
||||
when "\xc3"
|
||||
return 1
|
||||
when "\xc2"
|
||||
return 3
|
||||
end
|
||||
|
||||
raise "wtf"
|
||||
end
|
||||
|
||||
def _parse_ret(data)
|
||||
if data.length == 1
|
||||
return "ret"
|
||||
else
|
||||
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def scan_section(section)
|
||||
index = 0
|
||||
|
||||
hits = [ ]
|
||||
|
||||
while (index = section.index(regex, index)) != nil
|
||||
rva = section.offset_to_rva(index)
|
||||
message = ''
|
||||
|
||||
parse_ret = false
|
||||
|
||||
byte1 = section.read(index, 1)[0]
|
||||
|
||||
if byte1 == 0xff
|
||||
byte2 = section.read(index+1, 1)[0]
|
||||
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
|
||||
|
||||
case byte2 & 0xf8
|
||||
when 0xd0
|
||||
message = "call #{regname}"
|
||||
index += 2
|
||||
when 0xe0
|
||||
message = "jmp #{regname}"
|
||||
index += 2
|
||||
when 0xf0
|
||||
retsize = _ret_size(section, index+2)
|
||||
message = "push #{regname}; " + _parse_ret(section.read(index+2, retsize))
|
||||
index += 2 + retsize
|
||||
else
|
||||
raise "wtf"
|
||||
end
|
||||
else
|
||||
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
|
||||
retsize = _ret_size(section, index+1)
|
||||
message = "push #{regname}; " + _parse_ret(section.read(index+1, retsize))
|
||||
index += 1 + retsize
|
||||
end
|
||||
|
||||
hits << [ rva, message ]
|
||||
end
|
||||
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
class PopPopRetScanner < JmpRegScanner
|
||||
|
||||
def initialize
|
||||
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
|
||||
|
||||
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)")
|
||||
end
|
||||
|
||||
def scan_section(section)
|
||||
index = 0
|
||||
|
||||
hits = [ ]
|
||||
|
||||
while index < section.size && (index = section.index(regex, index)) != nil
|
||||
rva = section.offset_to_rva(index)
|
||||
message = ''
|
||||
|
||||
pops = section.read(index, 2)
|
||||
reg1 = Rex::Arch::X86.reg_name32(pops[0] & 0x7)
|
||||
reg2 = Rex::Arch::X86.reg_name32(pops[1] & 0x7)
|
||||
|
||||
message = "pop #{reg1}; pop #{reg2}; "
|
||||
|
||||
retsize = _ret_size(section, index+2)
|
||||
message += _parse_ret(section.read(index+2, retsize))
|
||||
|
||||
index += 2 + retsize
|
||||
|
||||
hits << [ rva, message ]
|
||||
end
|
||||
|
||||
return hits
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
opt = OptionParser.new
|
||||
|
||||
opt.banner = 'Usage: [options] [files]'
|
||||
opt.banner = "Usage: #{$PROGRAM_NAME} [mode] <options> [targets]"
|
||||
opt.separator('')
|
||||
opt.separator('Options:')
|
||||
opt.separator('Modes:')
|
||||
|
||||
worker = nil
|
||||
param = {}
|
||||
|
||||
scanner = nil
|
||||
pe_klass = Rex::PeParsey::Pe
|
||||
|
||||
opt.on('-j', '--jump [REG]', 'Find a jmp/call REG') do |t|
|
||||
opt.on('-j', '--jump [regA,regB,regC]', 'Search for jump equivalent instructions') do |t|
|
||||
# take csv of register names (like eax,ebx) and convert
|
||||
# them to an array of register numbers
|
||||
regnums = t.split(',').collect { |o| Rex::Arch::X86.reg_number(o) }
|
||||
scanner = JmpRegScanner.new(regnums)
|
||||
worker = Rex::PeScan::Scanner::JmpRegScanner
|
||||
param['args'] = regnums
|
||||
end
|
||||
opt.on('-p', '--poppopret', 'Find a pop/pop/ret') do |t|
|
||||
scanner = PopPopRetScanner.new
|
||||
|
||||
opt.on('-p', '--poppopret', 'Search for pop+pop+ret combinations') do |t|
|
||||
worker = Rex::PeScan::Scanner::PopPopRetScanner
|
||||
param['args'] = t
|
||||
end
|
||||
opt.on('-m', '--memdump', 'The files are memdump.exe images') do |t|
|
||||
|
||||
opt.on('-r', '--regex [regex]', 'Search for regex match') do |t|
|
||||
worker = Rex::PeScan::Scanner::RegexScanner
|
||||
param['args'] = t
|
||||
end
|
||||
|
||||
opt.on('-a', '--analyze-address [address]', 'Display the code at the specified address') do |t|
|
||||
worker = Rex::PeScan::Search::DumpRVA
|
||||
param['args'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.on('-b', '--analyze-offset [offset]', 'Display the code at the specified offset') do |t|
|
||||
worker = Rex::PeScan::Search::DumpOffset
|
||||
param['args'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.on('-m', '--memdump', 'The targets are memdump.exe directories') do |t|
|
||||
pe_klass = Rex::PeParsey::PeMemDump
|
||||
end
|
||||
|
||||
opt.separator('')
|
||||
opt.separator('Options:')
|
||||
|
||||
opt.on('-A', '--after [bytes]', 'Search for jump equivalent instructions') do |t|
|
||||
param['after'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.on('-B', '--before [bytes]', 'The targets are memdump.exe directories') do |t|
|
||||
param['before'] = opt2i(t)
|
||||
end
|
||||
|
||||
opt.parse!
|
||||
|
||||
ARGV.each do |file|
|
||||
|
@ -175,16 +85,10 @@ ARGV.each do |file|
|
|||
next if $!.message == "Couldn't find the PE magic!"
|
||||
raise $!
|
||||
end
|
||||
|
||||
pe.all_sections.each do |section|
|
||||
hits = scanner.scan_section(section)
|
||||
hits.each do |hit|
|
||||
vma = pe.rva_to_vma(hit[0])
|
||||
msg = hit[1]
|
||||
puts "0x%08x %s" % [ vma, msg ]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
o = worker.new(pe)
|
||||
o.scan(param)
|
||||
|
||||
pe.close
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue