Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
1 contributor

Users who have contributed to this file

279 lines (227 sloc) 6.42 KB
$:.unshift("C:\\Lib\\metasm")
require 'metasm'
require 'optparse'
class DbgHelpAPI < ::Metasm::DynLdr
DBGHELP_DLL = File.join(File.dirname(__FILE__), 'data/dbghelp.dll')
new_api_c <<EOS, DBGHELP_DLL
#line #{__LINE__}
typedef int BOOL;
typedef char CHAR;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned __int64 DWORD64;
typedef void *HANDLE;
typedef unsigned __int64 *PDWORD64;
typedef void *PVOID;
typedef unsigned long ULONG;
typedef unsigned long ULONG_PTR;
typedef unsigned __int64 ULONG64;
typedef const CHAR *PCSTR;
typedef CHAR *PSTR;
// Machine Type for StackWalk64
#define IMAGE_FILE_MACHINE_I386 0x014c
#define IMAGE_FILE_MACHINE_IA64 0x0200
#define IMAGE_FILE_MACHINE_AMD64 0x8664
typedef struct _KDHELP64 {
DWORD64 Thread;
DWORD ThCallbackStack;
DWORD ThCallbackBStore;
DWORD NextCallback;
DWORD FramePointer;
DWORD64 KiCallUserMode;
DWORD64 KeUserCallbackDispatcher;
DWORD64 SystemRangeStart;
DWORD64 KiUserExceptionDispatcher;
DWORD64 StackBase;
DWORD64 StackLimit;
DWORD64 Reserved[5];
} KDHELP64, *PKDHELP64;
typedef enum _tagAddrMode {
AddrMode1616 = 0,
AddrMode1632 = 1,
AddrModeReal = 2,
AddrModeFlat = 3
} ADDRESS_MODE;
typedef struct _tagADDRESS64 {
DWORD64 Offset;
WORD Segment;
ADDRESS_MODE Mode;
} ADDRESS64, *LPADDRESS64;
typedef struct _tagSTACKFRAME64 {
ADDRESS64 AddrPC;
ADDRESS64 AddrReturn;
ADDRESS64 AddrFrame;
ADDRESS64 AddrStack;
ADDRESS64 AddrBStore;
PVOID FuncTableEntry;
DWORD64 Params[4];
BOOL Far;
BOOL Virtual;
DWORD64 Reserved[3];
KDHELP64 KdHelp;
} STACKFRAME64, *LPSTACKFRAME64;
BOOL __stdcall StackWalk64(
DWORD MachineType,
HANDLE hProcess,
HANDLE hThread,
LPSTACKFRAME64 StackFrame,
PVOID ContextRecord,
PVOID ReadMemoryRoutine,
PVOID FunctionTableAccessRoutine,
PVOID GetModuleBaseRoutine,
PVOID TranslateAddress
);
EOS
def self.get_stack_frames(process, thread, context)
ph = process.handle
th = thread.handle
ctx = context.dup
stack_frame = self.alloc_c_struct('STACKFRAME64')
ret_stack_frames = []
# Suspend the thread to ensure correct result
thread.suspend()
begin
# Update Thread Context
ctx.update()
stack_frame.AddrPC.Offset = ctx.eip
stack_frame.AddrPC.Mode = 3 # AddrModeFlat
stack_frame.AddrStack.Offset = ctx.esp
stack_frame.AddrStack.Mode = 3
stack_frame.AddrFrame.Offset = ctx.ebp
stack_frame.AddrFrame.Mode = 3
loop do
begin
ret = self.stackwalk64(IMAGE_FILE_MACHINE_I386, ph, th, stack_frame, ctx, 0, 0, 0, 0)
rescue => e
ret = nil
end
break if (ret.zero? or ret.nil?)
ret_stack_frames << {
:ip => stack_frame.AddrPC.Offset,
:sp => stack_frame.AddrStack.Offset,
:bp => stack_frame.AddrFrame.Offset,
:ret => stack_frame.AddrReturn.Offset
}
end
rescue => e
# Error
ensure
# Resume the thread
thread.resume()
end
ret_stack_frames
end
def self.each_stack_frame(process, thread, context, &block)
self.get_stack_frames(process, thread, context).map do |sf|
block.call(sf)
end
end
end
def _vmsg(msg)
if $options[:verbose]
if $options[:verbose_file]
$options[:verbose_file].puts("[*] #{msg}")
else
$stdout.puts("[*] #{msg}")
end
end
end
def _msg(msg)
$stdout.puts("[+] #{msg}")
end
def _emsg(msg)
$stderr.puts("[-] #{msg}")
end
def pe_valid_module_addr?(pr, addr)
pr.modules.each do |mod|
mod_pe = ::Metasm::LoadedPE.load(pr.memory[mod.addr, mod.size])
mod_pe.decode_header
mod_pe.sections.each do |section|
next unless section.characteristics.include? "MEM_EXECUTE"
next unless ((addr >= (mod.addr + section.virtaddr)) and (addr < (mod.addr + section.virtaddr + section.rawsize)))
# We have a valid addr within executable section of a module
return true
end
end
return false
end
def scan_thread_context(pr, th, ctx)
eip = ctx[:eip]
_msg("Scanning Thread Id: #{th.tid} EIP: 0x%08x" % [eip])
unless pe_valid_module_addr?(pr, eip)
_msg("Possibly Injected Code Found @ 0x%08x [ThreadId: %d]" % [eip, th.tid])
return
end
_msg("Scanning Thread Stack Frame (Tid: #{th.tid})")
fc = 0
DbgHelpAPI.each_stack_frame(pr, th, ctx) do |frame|
_vmsg("ThreadId: 0x%04d Frame[#{fc}] EIP: 0x%08x ESP: 0x%08x EBP: 0x%08x RET: 0x%08x" %
[th.tid, frame[:ip], frame[:sp], frame[:bp], frame[:ret]])
unless pe_valid_module_addr?(pr, frame[:ip])
_msg("Possibly Injected Code Found at StackFrame[#{fc}] ThreadId: #{th.tid} Addr: 0x%08x" % [frame[:ip]])
end
fc += 1
end
end
def dump_process_modules(pr)
_vmsg("Dumping process modules for: #{pr.modules[0].path}")
_vmsg("")
pr.modules[1, pr.modules.size - 1].each do |mod|
_vmsg("LoadAddr: 0x%08x FilePath: %s" % [mod.addr, mod.path])
end
_vmsg("")
end
def scan_process(pr)
dump_process_modules(pr) if $options[:module_dump]
_msg "Scanning: #{pr.modules[0].path}"
pr.threads.each do |tid|
thread = ::Metasm::WinOS::Thread.new(tid.to_i, nil, pr)
context = thread.context
context.update
scan_thread_context(pr, thread, context)
end
end
if __FILE__ == $0
$options = {
:pids => [],
:verbose => false,
:verbose_file => nil
}
opp = OptionParser.new do |opts|
opts.banner = "Usage: ProcScan.rb [options]"
opts.on("-p", "--pid [PIDS]", "PIDs to scan (default: all) (eg: 1,2,3,4)") do |v|
$options[:pids] = v.to_s.split(",").map {|i| i.strip.to_i }
end
opts.on("-v", "--verbose", "Print verbose messages") do
$options[:verbose] = true
end
opts.on("--log [FILE]", "Log verbose messages to file instead of $stdout") do |file|
$options[:verbose_file] = File.open(file.to_s, "a")
end
opts.on("--dump-modules", "Verbosely dump process module information") do
$options[:module_dump] = true
end
opts.on("-h") do
puts opp
exit
end
end
if ARGV.empty?
print "** WARN: Scan all processes? This might take some time. Proceeed (yes/no)?: "
exit(1) if $stdin.gets.to_s.strip !~ /^y/i
else
opp.parse!
end
::Metasm::WinOS.list_processes.each do |process|
if process.modules.empty?
# We don't have priv
_emsg("Cannot enumerate process with PID: %d (insufficient privilege)" % [process.pid]) if $options[:pids].empty?
next
elsif process.pid == ::Process.pid
# Scanning self will result in deadlock due to SuspendThread(..)
next
end
scan_process(process) if ($options[:pids].empty?) or ($options[:pids].include?(process.pid))
end
end
You can’t perform that action at this time.