class Metasm::WinDebugger
this class implements a high-level API over the Windows debugging primitives
Attributes
auto_fix_fs_bug[RW]
callback_debugstring[RW]
callback_ripevent[RW]
callback_unloadlibrary[RW]
continuecode[RW]
os_process[RW]
os_thread[RW]
Public Class Methods
new(pidpath=nil)
click to toggle source
Calls superclass method
Metasm::Debugger.new
# File metasm/os/windows.rb, line 1737 def initialize(pidpath=nil) super() @pid_stuff_list << :os_process @tid_stuff_list << :os_thread << :ctx << :continuecode @auto_fix_fs_bug = false return if not pidpath begin npid = Integer(pidpath) attach(npid) rescue ArgumentError create_process(pidpath) end check_target until pid end
Public Instance Methods
attach(npid)
click to toggle source
# File metasm/os/windows.rb, line 1758 def attach(npid) WinAPI.debugactiveprocess(npid) WinAPI.debugsetprocesskillonexit(0) if WinAPI.respond_to?(:debugsetprocesskillonexit) 100.times { check_target break if pid } raise "attach failed" if not pid end
break()
click to toggle source
# File metasm/os/windows.rb, line 2073 def break return if @state != :running # debugbreak() will create a new thread to 0xcc, but wont touch existing threads suspend end
check_pid(pid)
click to toggle source
# File metasm/os/windows.rb, line 1835 def check_pid(pid) WinOS.check_process(pid) end
check_tid(tid)
click to toggle source
Calls superclass method
Metasm::Debugger#check_tid
# File metasm/os/windows.rb, line 1839 def check_tid(tid) # dont raise() on the first set_context when os_proc is not set yet return true if not os_process super(tid) end
create_process(target)
click to toggle source
# File metasm/os/windows.rb, line 1768 def create_process(target) startupinfo = WinAPI.alloc_c_struct('STARTUPINFOA', :cb => :size) processinfo = WinAPI.alloc_c_struct('PROCESS_INFORMATION') flags = WinAPI::DEBUG_PROCESS flags |= WinAPI::DEBUG_ONLY_THIS_PROCESS if not trace_children target = target.dup if target.frozen? # eg ARGV h = WinAPI.createprocessa(nil, target, nil, nil, 0, flags, nil, nil, startupinfo, processinfo) raise "CreateProcess: #{WinAPI.last_error_msg}" if not h set_context(processinfo.dwprocessid, processinfo.dwthreadid) @os_process = WinOS::Process.new(processinfo.dwprocessid, processinfo.hprocess) @os_thread = WinOS::Thread.new(processinfo.dwthreadid, processinfo.hthread, @os_process) initialize_osprocess check_target end
ctx()
click to toggle source
# File metasm/os/windows.rb, line 1845 def ctx if not @ctx # swapin_tid => gui.swapin_tid => getreg before we init os_thread in EventCreateThread return Hash.new(0) if not os_thread @ctx = os_thread.context @ctx.update end @ctx end
del_pid()
click to toggle source
Calls superclass method
Metasm::Debugger#del_pid
# File metasm/os/windows.rb, line 2067 def del_pid # tell Windows to release the PROCESS object WinAPI.debugactiveprocessstop(@pid) if WinAPI.respond_to?(:debugactiveprocessstop) super() end
del_tid()
click to toggle source
Calls superclass method
Metasm::Debugger#del_tid
# File metasm/os/windows.rb, line 2056 def del_tid # tell Windows to release the THREAD object WinAPI.continuedebugevent(@pid, @tid, @continuecode) super() end
del_tid_notid()
click to toggle source
do nothing, windows will send us a EXIT_PROCESS event
# File metasm/os/windows.rb, line 2063 def del_tid_notid nil while do_waitfordebug(10) and !@tid end
detach()
click to toggle source
# File metasm/os/windows.rb, line 2093 def detach del_all_breakpoints if not WinAPI.respond_to? :debugactiveprocessstop raise 'detach not supported' end # handle pending bp events # TODO check_target needs the Breakpoint objects... #pid = @pid ; 50.times { check_target } ; self.pid = pid # if we detach after a dbgevt and before calling continuedbgevent, the thread # may receive unhandled exceptions (eg BPX) and crash the process right after detach each_tid { do_continue if @state == :stopped } del_pid end
do_check_target()
click to toggle source
# File metasm/os/windows.rb, line 2040 def do_check_target do_waitfordebug(0) end
do_continue(*a)
click to toggle source
# File metasm/os/windows.rb, line 1874 def do_continue(*a) @cpu.dbg_disable_singlestep(self) if @continuecode == :suspended resume else @state = :running WinAPI.continuedebugevent(@pid, @tid, @continuecode) end end
do_disable_bpm(bp)
click to toggle source
# File metasm/os/windows.rb, line 1901 def do_disable_bpm(bp) @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}") WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof) WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] & ~WinAPI::PAGE_GUARD, @bpm_info) end
do_enable_bpm(bp)
click to toggle source
# File metasm/os/windows.rb, line 1894 def do_enable_bpm(bp) @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}") WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof) # TODO save original page perms, check bpm type (:w -> vprotect(PAGE_READONLY)), handle multiple bpm on same page WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] | WinAPI::PAGE_GUARD, @bpm_info) end
do_singlestep(*a)
click to toggle source
# File metasm/os/windows.rb, line 1884 def do_singlestep(*a) @cpu.dbg_enable_singlestep(self) if @continuecode == :suspended resume else @state = :running WinAPI.continuedebugevent(@pid, @tid, @continuecode) end end
do_wait_target()
click to toggle source
# File metasm/os/windows.rb, line 2044 def do_wait_target do_waitfordebug(WinAPI::INFINITE) end
do_waitfordebug(timeout)
click to toggle source
# File metasm/os/windows.rb, line 2048 def do_waitfordebug(timeout) @dbg_eventstruct ||= WinAPI.alloc_c_struct('_DEBUG_EVENT') if WinAPI.waitfordebugevent(@dbg_eventstruct, timeout) != 0 update_dbgev(@dbg_eventstruct) true end end
evt_debugstring(info={})
click to toggle source
# File metasm/os/windows.rb, line 2010 def evt_debugstring(info={}) @state = :stopped @info = "debugstring" log "Debugstring: #{info[:string].inspect}" callback_debugstring[info] if callback_debugstring # allow callback to skip this call to continue() by setting info[:nocontinue] = true continue unless info[:nocontinue] end
evt_ripevent(info={})
click to toggle source
# File metasm/os/windows.rb, line 2031 def evt_ripevent(info={}) @state = :stopped @info = "rip_event" # wtf? callback_ripevent[info] if callback_ripevent continue unless info[:nocontinue] end
evt_unloadlibrary(info={})
click to toggle source
# File metasm/os/windows.rb, line 2022 def evt_unloadlibrary(info={}) @state = :stopped @info = "unload library" callback_unloadlibrary[info] if callback_unloadlibrary continue unless info[:nocontinue] end
get_reg_value(r)
click to toggle source
# File metasm/os/windows.rb, line 1860 def get_reg_value(r) ctx[r] end
initialize_cpu()
click to toggle source
# File metasm/os/windows.rb, line 1803 def initialize_cpu # wait until we receive the CREATE_PROCESS_DBGEVT message return if not @os_process case WinAPI.host_cpu.shortname when 'ia32', 'x64' @cpu = Ia32.new(os_process.addrsz) else raise 'unsupported architecture' end end
initialize_memory()
click to toggle source
# File metasm/os/windows.rb, line 1814 def initialize_memory return if not @os_process @memory = os_process.memory end
initialize_newpid()
click to toggle source
Calls superclass method
Metasm::Debugger#initialize_newpid
# File metasm/os/windows.rb, line 1791 def initialize_newpid raise "non-existing pid #@pid" if pid and not WinOS.check_process(@pid) super() # os_process etc wait for CREATE_THREAD_DBGEVT end
initialize_newtid()
click to toggle source
Calls superclass method
Metasm::Debugger#initialize_newtid
# File metasm/os/windows.rb, line 1797 def initialize_newtid super() # os_thread etc wait for CREATE_THREAD_DBGEVT @continuecode = WinAPI::DBG_CONTINUE #WinAPI::DBG_EXCEPTION_NOT_HANDLED end
initialize_osprocess()
click to toggle source
called whenever we receive a handle to a new process being debugged, after initialisation of @os_process
# File metasm/os/windows.rb, line 1785 def initialize_osprocess initialize_cpu initialize_memory initialize_disassembler end
invalidate()
click to toggle source
Calls superclass method
Metasm::Debugger#invalidate
# File metasm/os/windows.rb, line 1855 def invalidate @ctx = nil super() end
kill(exitcode=0)
click to toggle source
# File metasm/os/windows.rb, line 2108 def kill(exitcode=0) os_process.terminate(exitcode) end
list_processes()
click to toggle source
# File metasm/os/windows.rb, line 1827 def list_processes WinOS.list_processes end
list_threads()
click to toggle source
# File metasm/os/windows.rb, line 1831 def list_threads os_process.threads end
mappings()
click to toggle source
# File metasm/os/windows.rb, line 1819 def mappings os_process.mappings end
modules()
click to toggle source
# File metasm/os/windows.rb, line 1823 def modules os_process.modules end
pass_current_exception(doit = true)
click to toggle source
# File metasm/os/windows.rb, line 2112 def pass_current_exception(doit = true) return if @continuecode == :suspended @continuecode = (doit ? WinAPI::DBG_EXCEPTION_NOT_HANDLED : WinAPI::DBG_CONTINUE) end
resume()
click to toggle source
# File metasm/os/windows.rb, line 2087 def resume @state = :running @info = nil os_thread.resume end
set_reg_value(r, v)
click to toggle source
# File metasm/os/windows.rb, line 1864 def set_reg_value(r, v) if @state == :running suspend ctx[r] = v resume else ctx[r] = v end end
shortname()
click to toggle source
# File metasm/os/windows.rb, line 1756 def shortname; 'windbg'; end
suspend()
click to toggle source
# File metasm/os/windows.rb, line 2079 def suspend os_thread.suspend invalidate @state = :stopped @info = 'thread suspended' @continuecode = :suspended end
update_dbgev(ev)
click to toggle source
# File metasm/os/windows.rb, line 1907 def update_dbgev(ev) # XXX ev is static, copy all necessary values before yielding to something that may call check_target set_context ev.dwprocessid, ev.dwthreadid invalidate @continuecode = WinAPI::DBG_CONTINUE # XXX reinterpret ev as struct32/64 depending on os_process.addrsz ? case ev.dwdebugeventcode when WinAPI::EXCEPTION_DEBUG_EVENT st = ev.exception str = st.exceptionrecord stf = st.dwfirstchance # non-zero = first chance @state = :stopped @info = "exception" # DWORD ExceptionCode; # DWORD ExceptionFlags; # struct _EXCEPTION_RECORD *ExceptionRecord; # PVOID ExceptionAddress; # DWORD NumberParameters; # ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; case str.exceptioncode when WinAPI::STATUS_ACCESS_VIOLATION, WinAPI::STATUS_GUARD_PAGE_VIOLATION if @auto_fix_fs_bug and ctx.fs != 0x3b # fix bug in xpsp1 where fs would get a random value in a debugee log "wdbg: #{pid}:#{tid} fix fs bug" if $DEBUG ctx.fs = 0x3b resume_badbreak return end mode = case str.exceptioninformation[0] when 0; :r when 1; :w when 8; :x end addr = str.exceptioninformation[1] evt_exception(:type => 'access violation', :st => str, :firstchance => stf, :fault_addr => addr, :fault_access => mode) when WinAPI::STATUS_BREAKPOINT, WinAPI::STATUS_WX86_BREAKPOINT # we must ack ntdll interrupts on process start # but we should not mask process-generated exceptions by default.. evt_bpx when WinAPI::STATUS_SINGLE_STEP, WinAPI::STATUS_WX86_SINGLE_STEP evt_hwbp_singlestep else @status_name ||= WinAPI.cp.lexer.definition.keys.grep(/^STATUS_/). sort.inject({}) { |h, c| h.update WinAPI.const_get(c) => c } type = @status_name[str.exceptioncode] || str.exceptioncode.to_s(16) evt_exception(:type => type, :st => str, :firstchance => stf) end when WinAPI::CREATE_THREAD_DEBUG_EVENT st = ev.createthread @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) @os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0 evt_newthread(:st => st) when WinAPI::CREATE_PROCESS_DEBUG_EVENT # XXX 32 vs 64 struct undecidable before we get hprocess.. st = ev.createprocess if not @os_process @os_process = WinOS::Process.new(@pid, st.hprocess) @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) initialize_osprocess else @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) end @os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0 hfile = st.hfile evt_newprocess(:st => st) WinAPI.closehandle(hfile) when WinAPI::EXIT_THREAD_DEBUG_EVENT st = ev.exitthread evt_endthread(:exitcode => st.dwexitcode) when WinAPI::EXIT_PROCESS_DEBUG_EVENT st = ev.exitprocess evt_endprocess(:exitcode => st.dwexitcode) when WinAPI::LOAD_DLL_DEBUG_EVENT st = ev.loaddll hfile = st.hfile evt_loadlibrary(:address => st.lpbaseofdll, :st => st) WinAPI.closehandle(hfile) when WinAPI::UNLOAD_DLL_DEBUG_EVENT st = ev.unloaddll evt_unloadlibrary(:address => st.lpbaseofdll) when WinAPI::OUTPUT_DEBUG_STRING_EVENT st = ev.debugstring str = WinAPI.decode_c_ary("__int#{st.funicode != 0 ? 16 : 8}", st.ndebugstringlength, @memory, st.lpdebugstringdata) if st.lpdebugstringdata str = str.to_array.pack('C*') rescue str.to_array.pack('v*') evt_debugstring(:string => str, :st => str) when WinAPI::RIP_EVENT st = ev.ripinfo evt_ripevent(:st => st) end end