From 99788d036804747bab06e97375f5fefbd7ee2a23 Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Fri, 22 Jan 2016 18:19:00 +0100 Subject: [PATCH] Writting a debugger is fun :D --- TODO | 2 + windows/debug.py | 145 ++++++++++++++++++++++++++++------ windows/syswow64.py | 16 +++- windows/test/mytest.py | 8 +- windows/utils/pythonutils.py | 5 ++ windows/utils/winutils.py | 4 +- windows/vectored_exception.py | 17 +--- windows/winobject.py | 22 ++++++ 8 files changed, 173 insertions(+), 46 deletions(-) diff --git a/TODO b/TODO index 6573af3..cdae1c8 100644 --- a/TODO +++ b/TODO @@ -3,6 +3,8 @@ TODO: - ProcessMemory object ? (metasm like) - Extend Registry feature (write) + - remove pe_parse.transform_ctypes_fields (use utils.transform_ctypes_fields) + FIXME: - WMI - COM initialisation when injected in another process diff --git a/windows/debug.py b/windows/debug.py index 78996fd..49ca20f 100644 --- a/windows/debug.py +++ b/windows/debug.py @@ -33,13 +33,15 @@ class Debugger(object): def __init__(self, target): # Todo: accept PID / String / WinProcess self.target = target - winproxy.DebugActiveProcess(target.pid) - + #winproxy.DebugActiveProcess(target.pid) self.processes = {} self.threads = {} self.current_process = None self.current_thread = None + self.breakpoints = {} + self._break_metadata = {} + def _debug_event_generator(self): while True: debug_event = DEBUG_EVENT() @@ -51,85 +53,178 @@ class Debugger(object): raise ValueError('Unknow action : <0>'.format(action)) winproxy.ContinueDebugEvent(event.dwProcessId, event.dwThreadId, action) - def loop(self): - for x, debug_event in enumerate(self._debug_event_generator()): - #print(debug_event, debug_event.code) - self._dispatch_debug_event(debug_event) - self._finish_debug_event(debug_event, windef.DBG_CONTINUE) - # TODO: exit on process exit - def _update_debugger_state(self, debug_event): self.current_process = self.processes[debug_event.dwProcessId] self.current_thread = self.threads[debug_event.dwThreadId] + def _dispatch_debug_event(self, debug_event): handler = self.dwDebugEventCode_handlers.get(debug_event.dwDebugEventCode, self._handle_unknown_debug_event) return handler(self, debug_event) + def _dispatch_breakpoint(self, exception, addr): + bp = self.breakpoints[addr] + return bp(exception) + + def _setup_breakpoint(self, addr, type, target): + if type != 0: + raise NotImplementedError("BP TYPE != 0 (TODO)") + if target is None: + targets = self.processes.items() + else: + targets = [(target.pid, target)] + for pid, process in targets: + self._break_metadata[pid] = process.read_memory(addr, 1) + print("Write BP: {0} at {1}".format(process, addr)) + process.write_memory(addr, "\xcc") + return + @staticmethod def _handle_unknown_debug_event(self, debug_event): raise NotImplementedError("dwDebugEventCode = {0}".format(debug_event.dwDebugEventCode)) @handle_dwDebugEventCode(EXCEPTION_DEBUG_EVENT) def _handle_exception(self, debug_event): - print("_handle_exception") + exception = debug_event.u.Exception self._update_debugger_state(debug_event) + if self.current_process.bitness == 32: + exception.__class__ = windows.vectored_exception.EEXCEPTION_DEBUG_INFO32 + else: + exception.__class__ = windows.vectored_exception.EEXCEPTION_DEBUG_INFO64 + + excp_code = exception.ExceptionRecord.ExceptionCode + excp_addr = exception.ExceptionRecord.ExceptionAddress + + if excp_code == EXCEPTION_BREAKPOINT and excp_addr in self.breakpoints: + self._dispatch_breakpoint(exception, excp_addr) + else: # Do not trigger self.on_exception if breakpoint was registered + self.on_exception(exception) + + @handle_dwDebugEventCode(CREATE_THREAD_DEBUG_EVENT) def _handle_create_thread(self, debug_event): - print("_handle_create_thread") create_thread = debug_event.u.CreateThread self.current_thread = WinThread._from_handle(create_thread.hThread) self.threads[self.current_thread.tid] = self.current_thread + self.on_create_thread(create_thread) @handle_dwDebugEventCode(CREATE_PROCESS_DEBUG_EVENT) def _handle_create_process(self, debug_event): - print("_handle_create_process") create_process = debug_event.u.CreateProcessInfo self.current_process = WinProcess._from_handle(create_process.hProcess) self.current_thread = WinThread._from_handle(create_process.hThread) - # TODO: verif debug_event.dwProcessId for REAL process creation :) - # Voir ce qu'on fout en current ? le parent ? (!le fils ?) self.threads[self.current_thread.tid] = self.current_thread self.processes[self.current_process.pid] = self.current_process self._update_debugger_state(debug_event) - + self.on_create_process(create_process) + # TODO: clode hFile @handle_dwDebugEventCode(EXIT_PROCESS_DEBUG_EVENT) def _handle_exit_process(self, debug_event): self._update_debugger_state(debug_event) + exit_process = debug_event.u.ExitProcess + self.on_exit_process(exit_process) del self.processes[self.current_process.pid] - print("Remove PID {0}".format(self.current_process.pid)) - print("Bye") - exit() + # Hack IT, ContinueDebugEvent will close the HANDLE for us + # Should we make another handle instead ? + del self.current_process._handle + @handle_dwDebugEventCode(EXIT_THREAD_DEBUG_EVENT) def _handle_exit_thread(self, debug_event): self._update_debugger_state(debug_event) + exit_thread = debug_event.u.ExitThread + self.on_exit_thread(exit_thread) del self.threads[self.current_thread.tid] - print("Remove TID {0}".format(self.current_thread.tid)) + # Hack IT, ContinueDebugEvent will close the HANDLE for us + # Should we make another handle instead ? + del self.current_thread._handle @handle_dwDebugEventCode(LOAD_DLL_DEBUG_EVENT) def _handle_load_dll(self, debug_event): self._update_debugger_state(debug_event) load_dll = debug_event.u.LoadDll - print("_handle_load_dll") + self.on_load_dll(load_dll) @handle_dwDebugEventCode(UNLOAD_DLL_DEBUG_EVENT) def _handle_unload_dll(self, debug_event): self._update_debugger_state(debug_event) - pass - print("_handle_unload_dll") + unload_dll = debug_event.u.UnloadDll + self.on_unload_dll(unload_dll) @handle_dwDebugEventCode(OUTPUT_DEBUG_STRING_EVENT) def _handle_output_debug_string(self, debug_event): self._update_debugger_state(debug_event) - pass - print("_handle_output_debug_string") + debug_string = debug_event.u.DebugString + self.on_output_debug_string(debug_string) @handle_dwDebugEventCode(RIP_EVENT) def _handle_rip(self, debug_event): self._update_debugger_state(debug_event) + rip_info = debug_event.u.RipInfo + self.on_rip(rip_info) + + # Public callback + + def on_exception(self, exception): pass - print("_handle_rip") + + def on_create_process(self, create_process): + pass + + def on_exit_process(self, exit_process): + pass + + def on_create_thread(self, create_thread): + pass + + def on_exit_thread(self, exit_thread): + pass + + def on_load_dll(self, load_dll): + pass + + def on_unload_dll(self, unload_dll): + pass + + def on_output_debug_string(self, debug_string): + pass + + def on_rip(self, rip_info): + pass + + # Public API + + def loop(self): + for x, debug_event in enumerate(self._debug_event_generator()): + self._dispatch_debug_event(debug_event) + self._finish_debug_event(debug_event, windef.DBG_CONTINUE) + if not self.processes: + # No More process to debug + break + + def add_bp(self, bp, addr=None, target=None): + """TODO: use type for hardware breakpoints""" + if getattr(bp, "addr", None) is not None: + self.breakpoints[bp.addr] = bp.trigger + self._setup_breakpoint(addr, bp.type, target) + return + + # Non object breakpoint + if addr is None: + raise ValueError("No address: need a valid or parameter") + self.breakpoints[addr] = bp + self._setup_breakpoint(addr, bp.type, target) + return True + + +class Breakpoint(object): + type = 0 # REAL BP + def __init__(self, addr): + self.addr = addr + + def trigger(self, exception): + pass + diff --git a/windows/syswow64.py b/windows/syswow64.py index 9cb59fd..194a416 100644 --- a/windows/syswow64.py +++ b/windows/syswow64.py @@ -177,16 +177,28 @@ def get_current_process_syswow_peb_addr(): def get_current_process_syswow_peb(): current_process = windows.current_process - class CurrentProcessReadSyswow(): + class CurrentProcessReadSyswow(object): + bitness = 64 def read_memory(self, addr, size): buffer_addr = ctypes.create_string_buffer(size) windows.winproxy.NtWow64ReadVirtualMemory64(current_process.handle, addr, buffer_addr, size) return buffer_addr[:] - bitness = 64 peb_addr = get_current_process_syswow_peb_addr() return windows.winobject.RemotePEB64(peb_addr, CurrentProcessReadSyswow()) +class ReadSyswow64Process(object): + def __init__(self, target): + self.target = target + self.bitness = target.bitness + pass + + def read_memory(self, addr, size): + buffer_addr = ctypes.create_string_buffer(size) + windows.winproxy.NtWow64ReadVirtualMemory64(self.target.handle, addr, buffer_addr, size) + return buffer_addr[:] + + def get_syswow_ntdll_exports(): if get_syswow_ntdll_exports.value is not None: return get_syswow_ntdll_exports.value diff --git a/windows/test/mytest.py b/windows/test/mytest.py index 9701e5a..6c6973f 100644 --- a/windows/test/mytest.py +++ b/windows/test/mytest.py @@ -26,21 +26,21 @@ process_64bit_only = unittest.skipIf(not is_process_64_bits, "Test for 64bits pr if is_windows_32_bits: def pop_calc_32(): - return windows.utils.create_process(r"C:\Windows\system32\calc.exe", True) + return windows.utils.create_process(r"C:\Windows\system32\calc.exe", show_windows=True) def pop_calc_64(): raise WindowsError("Cannot create calc64 in 32bits system") else: def pop_calc_32(): - return windows.utils.create_process(r"C:\Windows\syswow64\calc.exe", True) + return windows.utils.create_process(r"C:\Windows\syswow64\calc.exe", show_windows=True) if is_process_32_bits: def pop_calc_64(): with windows.utils.DisableWow64FsRedirection(): - return windows.utils.create_process(r"C:\Windows\system32\calc.exe", True) + return windows.utils.create_process(r"C:\Windows\system32\calc.exe", show_windows=True) else: def pop_calc_64(): - return windows.utils.create_process(r"C:\Windows\system32\calc.exe", True) + return windows.utils.create_process(r"C:\Windows\system32\calc.exe", show_windows=True) @contextmanager diff --git a/windows/utils/pythonutils.py b/windows/utils/pythonutils.py index 730480c..47f2014 100644 --- a/windows/utils/pythonutils.py +++ b/windows/utils/pythonutils.py @@ -18,3 +18,8 @@ def swallow_ctypes_copy(ctypes_object): new_copy = type(ctypes_object)() ctypes.memmove(ctypes.byref(new_copy), ctypes.byref(ctypes_object), ctypes.sizeof(new_copy)) return new_copy + + +# type replacement based on name +def transform_ctypes_fields(struct, replacement): + return [(name, replacement.get(name, type)) for name, type in struct._fields_] \ No newline at end of file diff --git a/windows/utils/winutils.py b/windows/utils/winutils.py index 01f07c3..069dd5e 100644 --- a/windows/utils/winutils.py +++ b/windows/utils/winutils.py @@ -70,7 +70,7 @@ def create_console(): sys.stderr = console_stderr -def create_process(path, show_windows=False): +def create_process(path, dwCreationFlags=0, show_windows=False): """A convenient wrapper arround :func:`windows.winproxy.CreateProcessA`""" proc_info = PROCESS_INFORMATION() lpStartupInfo = None @@ -79,7 +79,7 @@ def create_process(path, show_windows=False): StartupInfo.cb = ctypes.sizeof(StartupInfo) StartupInfo.dwFlags = 0 lpStartupInfo = ctypes.byref(StartupInfo) - windows.winproxy.CreateProcessA(path, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=lpStartupInfo) + windows.winproxy.CreateProcessA(path, dwCreationFlags=dwCreationFlags, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=lpStartupInfo) proc = [p for p in windows.system.processes if p.pid == proc_info.dwProcessId][0] return proc diff --git a/windows/vectored_exception.py b/windows/vectored_exception.py index 7511525..31fa296 100644 --- a/windows/vectored_exception.py +++ b/windows/vectored_exception.py @@ -57,19 +57,11 @@ EnhancedEXCEPTION_RECORD32 = generate_enhanced_exception_record(EXCEPTION_RECORD EnhancedEXCEPTION_RECORD64 = generate_enhanced_exception_record(EXCEPTION_RECORD64, "64") -#class EnhancedEXCEPTION_RECORD(EXCEPTION_RECORD): -# @property -# def ExceptionCode(self): -# real_code = super(EnhancedEXCEPTION_RECORD, self).ExceptionCode -# return exception_name_by_value.get(real_code, 'UNKNOW_EXCEPTION({0})'.format(hex(real_code))) -# -# @property -# def ExceptionAddress(self): -# x = super(EnhancedEXCEPTION_RECORD, self).ExceptionAddress -# if x is None: -# return 0x0 -# return x +class EEXCEPTION_DEBUG_INFO32(ctypes.Structure): + _fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EnhancedEXCEPTION_RECORD32}) +class EEXCEPTION_DEBUG_INFO64(ctypes.Structure): + _fields_ = windows.utils.transform_ctypes_fields(EXCEPTION_DEBUG_INFO, {"ExceptionRecord": EnhancedEXCEPTION_RECORD64}) class Eflags(int): _flags_ = [("CF", 1), @@ -120,7 +112,6 @@ class Eflags(int): def __hex__(self): return "{0}({1}:{2})".format(type(self).__name__, int.__hex__(self), self.dump()) - class EnhancedCONTEXTBase(): default_dump = () pc_reg = '' diff --git a/windows/winobject.py b/windows/winobject.py index 4abef20..431bbfc 100644 --- a/windows/winobject.py +++ b/windows/winobject.py @@ -52,6 +52,7 @@ class AutoHandle(object): def __del__(self): if hasattr(self, "_handle") and self._handle: + print("Del HANDLE {0} ({1})".format((self), self._handle)) self.CLOSE_FUNCTION(self._handle) @@ -233,6 +234,7 @@ class WinThread(THREADENTRY32, AutoHandle): # Really useful ? thread = [t for t in System().threads if t.tid == tid][0] # set AutoHandle _handle + print("New thread from handle {0}".format(handle)) thread._handle = handle return thread except IndexError: @@ -517,6 +519,7 @@ class WinProcess(PROCESSENTRY32, Process): pid = winproxy.GetProcessId(handle) proc = [p for p in windows.system.processes if p.pid == pid][0] proc._handle = handle + print("New Process from handle {0}".format(handle)) return proc @@ -693,6 +696,25 @@ class WinProcess(PROCESSENTRY32, Process): return RemotePEB32(self.peb_addr, self) return RemotePEB(self.peb_addr, self) + @utils.fixedpropety + def peb_syswow(self): + if not self.is_wow_64: + raise ValueError("Not a syswow process") + if windows.current_process.bitness == 64: + information_type = 0 + x = PROCESS_BASIC_INFORMATION() + windows.winproxy.NtQueryInformationProcess(self.handle, information_type, x) + peb_addr = ctypes.cast(x.PebBaseAddress, PVOID).value + return RemotePEB(peb_addr, self) + else: #current is 32bits + x = windows.remotectypes.transform_type_to_remote64bits(PROCESS_BASIC_INFORMATION) + # Fuck-it <3 + data = (ctypes.c_char * ctypes.sizeof(x))() + windows.syswow64.NtQueryInformationProcess_32_to_64(self.handle, ProcessInformation=data, ProcessInformationLength=ctypes.sizeof(x)) + peb_offset = x.PebBaseAddress.offset + peb_addr = struct.unpack("