#!/usr/local/bin/ruby18
#
# $Id: msfpescan 9212 2010-05-03 17:13:09Z jduck $
# $Revision: 9212 $
#

msfbase = __FILE__
while File.symlink?(msfbase)
	msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end

$:.unshift(File.join(File.dirname(msfbase), 'lib'))
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_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...
#
# - It tries to error on the side of caution, so it will try for a
#   false negative vs a false positive.
# - It doesn't account for the entire PE image neccesairly
# - It wouldn't find hits that overlap sections
# - etc etc
#

opt = OptionParser.new

opt.banner = "Usage: #{$PROGRAM_NAME} [mode] <options> [targets]"
opt.separator('')
opt.separator('Modes:')

worker = nil
param  = {}

pe_klass = Rex::PeParsey::Pe

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|
		begin
			Rex::Arch::X86.reg_number(o)
		rescue
			puts "Invalid register \"#{o}\""
			exit(1)
		end
	}
	worker = Rex::PeScan::Scanner::JmpRegScanner
	param['args'] = regnums
end

opt.on('-p', '--poppopret', 'Search for pop+pop+ret combinations') do |t|
	worker = Rex::PeScan::Scanner::PopPopRetScanner
	param['args'] = t
end

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('-f', '--fingerprint', 'Attempt to identify the packer/compiler') do |t|
	worker = Rex::PeScan::Analyze::Fingerprint
	param['database'] = File.join(File.dirname(msfbase), 'data', 'msfpescan', 'identify.txt')
end

opt.on('-i', '--info', 'Display detailed information about the image') do |t|
	worker = Rex::PeScan::Analyze::Information
end

opt.on('-R', '--ripper [directory]', 'Rip all module resources to disk ') do |t|
	worker = Rex::PeScan::Analyze::Ripper
	param['dir'] = t
end

opt.on('--context-map [directory]', 'Generate context-map files') do |t|
	worker = Rex::PeScan::Analyze::ContextMapDumper
	param['dir'] = t
end

opt.separator('')
opt.separator('Options:')

opt.on('-M', '--memdump', 'The targets are memdump.exe directories') do |t|
	pe_klass = Rex::PeParsey::PeMemDump
end


opt.on('-A', '--after [bytes]', 'Number of bytes to show after match (-a/-b)') do |t|
	param['after'] = opt2i(t)
end

opt.on('-B', '--before [bytes]', 'Number of bytes to show before match (-a/-b)') do |t|
	param['before'] = opt2i(t)
end

opt.on('-D', '--disasm', 'Disassemble the bytes at this address') do |t|
	param['disasm'] = true
end

opt.on('-I', '--image-base [address]', 'Specify an alternate ImageBase') do |t|
	param['imagebase'] = opt2i(t)
end

opt.on('-F', '--filter-addresses [regex]', 'Filter addresses based on a regular expression') do |t|
	param['filteraddr'] = t
end

opt.on_tail("-h", "--help", "Show this message") do
	puts opt
	exit(1)
end

begin
	opt.parse!
rescue OptionParser::InvalidOption
	puts "Invalid option, try -h for usage"
	exit(1)
end

if (! worker)
	puts opt
	exit(1)
end


files = []

ARGV.each do |file|

	if(File.directory?(file))
		dir = Dir.open(file)
		dir.entries.each do |ent|
			path = File.join(file, ent)
			next if not File.file?(path)
			files << File.join(path)
		end
	else
		files << file
	end
end

files.each do |file|
	$stdout.puts ""

	param['file'] = file

	begin
		pe = pe_klass.new_from_file(file, true)
	rescue ::Interrupt
		raise $!
	rescue Rex::PeParsey::FileHeaderError
		next if $!.message == "Couldn't find the PE magic!"
		raise $!
	rescue Errno::ENOENT
		$stdout.puts("File does not exist: #{file}")
		next
	rescue ::Rex::PeParsey::SkipError
		next
	rescue ::Exception => e
		$stdout.puts "[#{file}] #{e.class}: #{e}"
		next
	end

	if (param['imagebase'])
		pe.image_base = param['imagebase'];
	end

	o = worker.new(pe)
	o.scan(param)

	pe.close

end
$stdout.puts ""
