mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
Writting a debugger is fun :D
This commit is contained in:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_]
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 = ''
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user