this class implements a high-level API over the Windows debugging primitives
# File metasm/os/windows.rb, line 1740 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
# File metasm/os/windows.rb, line 1761 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
# File metasm/os/windows.rb, line 2076 def break return if @state != :running # debugbreak() will create a new thread to 0xcc, but wont touch existing threads suspend end
# File metasm/os/windows.rb, line 1838 def check_pid(pid) WinOS.check_process(pid) end
# File metasm/os/windows.rb, line 1842 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
# File metasm/os/windows.rb, line 1771 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
# File metasm/os/windows.rb, line 1848 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
# File metasm/os/windows.rb, line 2070 def del_pid # tell Windows to release the PROCESS object WinAPI.debugactiveprocessstop(@pid) if WinAPI.respond_to?(:debugactiveprocessstop) super() end
# File metasm/os/windows.rb, line 2059 def del_tid # tell Windows to release the THREAD object WinAPI.continuedebugevent(@pid, @tid, @continuecode) super() end
do nothing, windows will send us a EXIT_PROCESS event
# File metasm/os/windows.rb, line 2066 def del_tid_notid nil while do_waitfordebug(10) and !@tid end
# File metasm/os/windows.rb, line 2096 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
# File metasm/os/windows.rb, line 2043 def do_check_target do_waitfordebug(0) end
# File metasm/os/windows.rb, line 1877 def do_continue(*a) @cpu.dbg_disable_singlestep(self) if @continuecode == :suspended resume else @state = :running WinAPI.continuedebugevent(@pid, @tid, @continuecode) end end
# File metasm/os/windows.rb, line 1904 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
# File metasm/os/windows.rb, line 1897 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
# File metasm/os/windows.rb, line 1887 def do_singlestep(*a) @cpu.dbg_enable_singlestep(self) if @continuecode == :suspended resume else @state = :running WinAPI.continuedebugevent(@pid, @tid, @continuecode) end end
# File metasm/os/windows.rb, line 2047 def do_wait_target do_waitfordebug(WinAPI::INFINITE) end
# File metasm/os/windows.rb, line 2051 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
# File metasm/os/windows.rb, line 2013 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
# File metasm/os/windows.rb, line 2034 def evt_ripevent(info={}) @state = :stopped @info = "rip_event" # wtf? callback_ripevent[info] if callback_ripevent continue unless info[:nocontinue] end
# File metasm/os/windows.rb, line 2025 def evt_unloadlibrary(info={}) @state = :stopped @info = "unload library" callback_unloadlibrary[info] if callback_unloadlibrary continue unless info[:nocontinue] end
# File metasm/os/windows.rb, line 1863 def get_reg_value(r) ctx[r] end
# File metasm/os/windows.rb, line 1806 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.cpusz) else raise 'unsupported architecture' end end
# File metasm/os/windows.rb, line 1817 def initialize_memory return if not @os_process @memory = os_process.memory end
# File metasm/os/windows.rb, line 1794 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
# File metasm/os/windows.rb, line 1800 def initialize_newtid super() # os_thread etc wait for CREATE_THREAD_DBGEVT @continuecode = WinAPI::DBG_CONTINUE #WinAPI::DBG_EXCEPTION_NOT_HANDLED end
called whenever we receive a handle to a new process being debugged, after initialisation of @os_process
# File metasm/os/windows.rb, line 1788 def initialize_osprocess initialize_cpu initialize_memory initialize_disassembler end
# File metasm/os/windows.rb, line 1858 def invalidate @ctx = nil super() end
# File metasm/os/windows.rb, line 2111 def kill(exitcode=0) os_process.terminate(exitcode) end
# File metasm/os/windows.rb, line 1830 def list_processes WinOS.list_processes end
# File metasm/os/windows.rb, line 1834 def list_threads os_process.threads end
# File metasm/os/windows.rb, line 1822 def mappings os_process.mappings end
# File metasm/os/windows.rb, line 1826 def modules os_process.modules end
# File metasm/os/windows.rb, line 2115 def pass_current_exception(doit = true) return if @continuecode == :suspended @continuecode = (doit ? WinAPI::DBG_EXCEPTION_NOT_HANDLED : WinAPI::DBG_CONTINUE) end
# File metasm/os/windows.rb, line 2090 def resume @state = :running @info = nil os_thread.resume end
# File metasm/os/windows.rb, line 1867 def set_reg_value(r, v) if @state == :running suspend ctx[r] = v resume else ctx[r] = v end end
# File metasm/os/windows.rb, line 1759 def shortname; 'windbg'; end
# File metasm/os/windows.rb, line 2082 def suspend os_thread.suspend invalidate @state = :stopped @info = 'thread suspended' @continuecode = :suspended end
# File metasm/os/windows.rb, line 1910 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