Writting a debugger is fun :D

This commit is contained in:
Clement Rouault
2016-01-22 18:19:00 +01:00
parent f6f206b842
commit 99788d0368
8 changed files with 173 additions and 46 deletions
+2
View File
@@ -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
+120 -25
View File
@@ -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 <bp.addr> or <addr> 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
+14 -2
View File
@@ -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
+4 -4
View File
@@ -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
+5
View File
@@ -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_]
+2 -2
View File
@@ -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
+4 -13
View File
@@ -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 = ''
+22
View File
@@ -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("<Q", data[x.PebBaseAddress.offset: x.PebBaseAddress.offset+8])[0]
return RemotePEB64(peb_addr, windows.syswow64.ReadSyswow64Process(self))
def exit(self, code=0):
"""Exit the process"""
return winproxy.TerminateProcess(self.handle, code)