367 lines
8.2 KiB
Ruby
367 lines
8.2 KiB
Ruby
# -*- coding: binary -*-
|
|
module Rex
|
|
module PeScan
|
|
module Analyze
|
|
|
|
require "rex/ui/text/table"
|
|
|
|
class Fingerprint
|
|
attr_accessor :pe
|
|
|
|
def initialize(pe)
|
|
self.pe = pe
|
|
end
|
|
|
|
def config(param)
|
|
@sigs = {}
|
|
|
|
name = nil
|
|
regx = ''
|
|
epon = 0
|
|
sidx = 0
|
|
|
|
fd = File.open(param['database'], 'rb')
|
|
fd.each_line do |line|
|
|
case line
|
|
when /^\s*#/
|
|
next
|
|
when /\[\s*(.*)\s*\]/
|
|
if (name)
|
|
@sigs[ name ] = [regx, epon]
|
|
end
|
|
name = $1 + " [#{ sidx+=1 }]"
|
|
epon = 0
|
|
next
|
|
when /signature\s*=\s*(.*)/
|
|
pat = $1.strip
|
|
regx = ''
|
|
pat.split(/\s+/).each do |c|
|
|
next if c.length != 2
|
|
regx << (c.index('?') ? '.' : "\\x#{c}")
|
|
end
|
|
when /ep_only\s*=\s*(.*)/
|
|
epon = ($1 =~ /^T/i) ? 1 : 0
|
|
end
|
|
end
|
|
|
|
if (name and ! @sigs[name])
|
|
@sigs[ name ] = [regx, epon]
|
|
end
|
|
|
|
fd.close
|
|
end
|
|
|
|
def scan(param)
|
|
config(param)
|
|
|
|
epa = pe.hdr.opt.AddressOfEntryPoint
|
|
buf = pe.read_rva(epa, 256) || ""
|
|
|
|
@sigs.each_pair do |name, data|
|
|
begin
|
|
if (buf.match(Regexp.new('^' + data[0], nil, 'n')))
|
|
$stdout.puts param['file'] + ": " + name
|
|
end
|
|
rescue RegexpError
|
|
$stderr.puts "Invalid signature: #{name} #{data[0]}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
class Information
|
|
attr_accessor :pe
|
|
|
|
def initialize(pe)
|
|
self.pe = pe
|
|
end
|
|
|
|
def add_fields(tbl, obj, fields)
|
|
fields.each do |name|
|
|
begin
|
|
tbl << [name, "0x%.8x" % obj.send(name)]
|
|
rescue ::NoMethodError => e
|
|
$stderr.puts "Invalid field #{name}"
|
|
end
|
|
end
|
|
end
|
|
|
|
def scan(param)
|
|
|
|
$stdout.puts "\n\n"
|
|
|
|
tbl = table("Image Headers", ['Name', 'Value'])
|
|
add_fields(tbl, pe.hdr.file, %W{
|
|
Characteristics
|
|
SizeOfOptionalHeader
|
|
PointerToSymbolTable
|
|
TimeDateStamp
|
|
NumberOfSections
|
|
Machine
|
|
})
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
|
|
tbl = table("Optional Image Headers", ['Name', 'Value'])
|
|
add_fields(tbl, pe.hdr.opt, %W{
|
|
ImageBase
|
|
Magic
|
|
MajorLinkerVersion
|
|
MinorLinkerVersion
|
|
SizeOfCode
|
|
SizeOfInitializeData
|
|
SizeOfUninitializeData
|
|
AddressOfEntryPoint
|
|
BaseOfCode
|
|
BaseOfData
|
|
SectionAlignment
|
|
FileAlignment
|
|
MajorOperatingSystemVersion
|
|
MinorOperatingSystemVersion
|
|
MajorImageVersion
|
|
MinorImageVersion
|
|
MajorSubsystemVersion
|
|
MinorSubsystemVersion
|
|
Win32VersionValue
|
|
SizeOfImage
|
|
SizeOfHeaders
|
|
CheckSum
|
|
Subsystem
|
|
DllCharacteristics
|
|
SizeOfStackReserve
|
|
SizeOfStackCommit
|
|
SizeOfHeapReserve
|
|
SizeOfHeapCommit
|
|
LoaderFlags
|
|
NumberOfRvaAndSizes
|
|
})
|
|
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
|
|
# Get DllCharacteristics (in Integer)
|
|
dllcharacteristics = pe.hdr.opt.struct[23].value
|
|
|
|
if (dllcharacteristics > 0)
|
|
tbl = table("DllCharacteristics", ['Flag', 'Value'])
|
|
|
|
# http://msdn.microsoft.com/en-us/library/ms680339(v=vs.85).aspx
|
|
traits = {
|
|
:ASLR => 'False', #IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
|
|
:Integrity => 'False', #IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY
|
|
:NX => 'False', #IMAGE_DLLCHARACTERISTICS_NX_COMPAT
|
|
:Isolation => 'False', #IMAGE_DLLCHARACTERISTICS_NO_ISOLATION
|
|
:SEH => 'False', #IMAGE_DLLCHARACTERISTICS_NO_SEH
|
|
:Bind => 'False', #IMAGE_DLLCHARACTERISTICS_NO_BIND
|
|
:WDM => 'False', #IMAGE_DLLCHARACTERISTICS_WDM_DRIVER
|
|
:Terminal => 'False' #IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
|
|
}
|
|
|
|
# Convert integer to an bit array
|
|
c_bits = ("%32d" %dllcharacteristics.to_s(2)).split('').map { |e| e.to_i }.reverse
|
|
|
|
# Check characteristics
|
|
traits[:ASLR] = 'True' if c_bits[6] == 1 #0x0040
|
|
traits[:Integrity] = 'True' if c_bits[7] == 1 #0x0080
|
|
traits[:NX] = 'True' if c_bits[8] == 1 #0x0100
|
|
traits[:Isolation] = 'True' if c_bits[9] == 1 #0x0200
|
|
traits[:SEH] = 'True' if c_bits[10] == 1 #0x0400
|
|
traits[:Bind] = 'True' if c_bits[11] == 1 #0x0800
|
|
traits[:WDM] = 'True' if c_bits[13] == 1 #2000
|
|
traits[:Terminal] = 'True' if c_bits[15] == 1 #0x8000
|
|
|
|
# Putting results to table
|
|
traits.each do |trait_name, trait_value|
|
|
tbl << [trait_name, trait_value]
|
|
end
|
|
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
end
|
|
|
|
if (pe.exports)
|
|
tbl = table("Exported Functions", ['Ordinal', 'Name', 'Address'])
|
|
pe.exports.entries.each do |ent|
|
|
tbl << [ent.ordinal, ent.name, "0x%.8x" % pe.rva_to_vma(ent.rva)]
|
|
end
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
end
|
|
|
|
# Rex::PeParsey::Pe doesn't seem to give us any offset information for each function,
|
|
# which makes it difficult to calculate the actual addresses for them. So instead we
|
|
# are using Metasm::COFF::ImportDirectory to do this task. The ability to see
|
|
# addresses is mainly for ROP.
|
|
if (pe.imports)
|
|
tbl = table("Imported Functions", ['Library', 'Address', 'Ordinal', 'Name'])
|
|
exefmt = Metasm::AutoExe.orshellcode{ Metasm.const_get('x86_64').new }
|
|
exe = exefmt.decode_file(pe._isource.file.path)
|
|
ibase = pe.image_base
|
|
exe_imports = exe.imports
|
|
exe_imports.each do |lib|
|
|
lib_name = lib.libname
|
|
ini_offset = lib.iat_p
|
|
func_table = lib.imports
|
|
offset = 0
|
|
func_table.each do |func|
|
|
func_addr = "0x%08x" %(ibase + ini_offset + offset)
|
|
tbl << [lib_name, func_addr, func.hint, func.name]
|
|
offset += 4
|
|
end
|
|
end
|
|
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
end
|
|
|
|
if(pe.config)
|
|
tbl = table("Configuration Header", ['Name', 'Value'])
|
|
add_fields(tbl, pe.config, %W{
|
|
Size
|
|
TimeDateStamp
|
|
MajorVersion
|
|
MinorVersion
|
|
GlobalFlagsClear
|
|
GlobalFlagsSet
|
|
CriticalSectionDefaultTimeout
|
|
DeCommitFreeBlockThreshold
|
|
DeCommitTotalFreeThreshold
|
|
LockPrefixTable
|
|
MaximumAllocationSize
|
|
VirtualMemoryThreshold
|
|
ProcessAffinityMask
|
|
ProcessHeapFlags
|
|
CSDVersion
|
|
Reserved1
|
|
EditList
|
|
SecurityCookie
|
|
SEHandlerTable
|
|
SEHandlerCount
|
|
})
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
end
|
|
|
|
|
|
if(pe.resources)
|
|
tbl = table("Resources", ['ID', 'Language', 'Code Page', 'Size', 'Name'])
|
|
pe.resources.keys.sort.each do |rkey|
|
|
res = pe.resources[rkey]
|
|
tbl << [rkey, res.lang, res.code, res.size, res.file]
|
|
end
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
end
|
|
|
|
tbl = table("Section Header", ["Name", "VirtualAddress", "SizeOfRawData", "Characteristics"])
|
|
pe.sections.each do |sec|
|
|
tbl << [ sec.name, *[sec.vma, sec.raw_size, sec.flags].map{|x| "0x%.8x" % x} ]
|
|
end
|
|
$stdout.puts tbl.to_s
|
|
$stdout.puts "\n\n"
|
|
|
|
end
|
|
|
|
def table(name, cols)
|
|
Rex::Ui::Text::Table.new(
|
|
'Header' => name,
|
|
'Columns' => cols
|
|
)
|
|
end
|
|
end
|
|
|
|
|
|
class Ripper
|
|
|
|
require "fileutils"
|
|
|
|
attr_accessor :pe
|
|
|
|
def initialize(pe)
|
|
self.pe = pe
|
|
end
|
|
|
|
def scan(param)
|
|
dest = param['dir']
|
|
|
|
if (param['file'])
|
|
dest = File.join(dest, File.basename(param['file']))
|
|
end
|
|
|
|
::FileUtils.mkdir_p(dest)
|
|
|
|
pe.resources.keys.sort.each do |rkey|
|
|
res = pe.resources[rkey]
|
|
path = File.join(dest, rkey.split('/')[1] + '_' + res.file)
|
|
|
|
fd = File.new(path, 'wb')
|
|
fd.write(res.data)
|
|
fd.close
|
|
end
|
|
end
|
|
end
|
|
|
|
class ContextMapDumper
|
|
|
|
attr_accessor :pe
|
|
|
|
def initialize(pe)
|
|
self.pe = pe
|
|
end
|
|
|
|
def scan(param)
|
|
dest = param['dir']
|
|
path = ''
|
|
|
|
::FileUtils.mkdir_p(dest)
|
|
|
|
if(not (param['dir'] and param['file']))
|
|
$stderr.puts "No directory or file specified"
|
|
return
|
|
end
|
|
|
|
if (param['file'])
|
|
path = File.join(dest, File.basename(param['file']) + ".map")
|
|
end
|
|
|
|
fd = File.new(path, "wb")
|
|
pe.all_sections.each do |section|
|
|
|
|
# Skip over known bad sections
|
|
next if section.name == ".data"
|
|
next if section.name == ".reloc"
|
|
|
|
offset = 0
|
|
while offset < section.size
|
|
byte = section.read(offset, 1)[0]
|
|
if byte != 0
|
|
chunkbase = pe.rva_to_vma(section.base_rva) + offset
|
|
data = ''
|
|
while byte != 0
|
|
data << byte
|
|
offset += 1
|
|
byte = 0
|
|
byte = section.read(offset, 1)[0] if offset < section.size
|
|
end
|
|
buff = nil
|
|
buff = [ 0x01, chunkbase, data.length, data].pack("CNNA*") if data.length > 0
|
|
|
|
fd.write(buff) if buff
|
|
end
|
|
offset += 1
|
|
end
|
|
|
|
end
|
|
|
|
|
|
fd.close
|
|
end
|
|
end
|
|
|
|
# EOC
|
|
|
|
end
|
|
end
|
|
end
|
|
|