mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
Working on debugger (str address + rewrite + test)
This commit is contained in:
+318
-79
@@ -1,3 +1,5 @@
|
||||
import os.path
|
||||
|
||||
import windows
|
||||
import windows.winproxy as winproxy
|
||||
|
||||
@@ -26,13 +28,16 @@ class DEBUG_EVENT(DEBUG_EVENT):
|
||||
return self.KNOWN_EVENT_CODE.get(self.dwDebugEventCode, self.dwDebugEventCode)
|
||||
|
||||
class Debugger(object):
|
||||
"""A debugger based on standard Win32 API. Handle standard (int3) and Hardware-Exec Breakpoints"""
|
||||
def __init__(self, target, already_debuggable=False):
|
||||
"""``target`` must be a WinProcess.
|
||||
|
||||
def __init__(self, target):
|
||||
# Todo: accept PID / String / WinProcess
|
||||
``already_debuggable`` must be set to ``True`` if process is already expecting a debugger (created with DEBUG_PROCESS)"""
|
||||
self._init_dispatch_handlers()
|
||||
self.target = target
|
||||
self.is_target_launched = False
|
||||
#winproxy.DebugActiveProcess(target.pid)
|
||||
if not already_debuggable:
|
||||
winproxy.DebugActiveProcess(target.pid)
|
||||
self.processes = {}
|
||||
self.threads = {}
|
||||
self.current_process = None
|
||||
@@ -40,6 +45,7 @@ class Debugger(object):
|
||||
# List of breakpoints
|
||||
self.breakpoints = {}
|
||||
self._pending_breakpoints = {} #Breakpoints to put in new process / threads
|
||||
self._pending_address = {} # Breakpoints that address have not been resolved yet
|
||||
# Values rewritten by "\xcc"
|
||||
self._memory_save = defaultdict(dict)
|
||||
# Dict of {tid : {drx taken : BP}}
|
||||
@@ -47,6 +53,16 @@ class Debugger(object):
|
||||
# Breakpoints to reput..
|
||||
self._breakpoint_to_reput = {}
|
||||
|
||||
self._module_by_process = {}
|
||||
|
||||
#TODO: remove this: THIS IS A TEST
|
||||
self._breakpoints_new_targets = {}
|
||||
self._breakpoint_resolvable_address = {}
|
||||
|
||||
self._pending_breakpoints_new = {}
|
||||
|
||||
self._pending_breakpoints_new = defaultdict(list)
|
||||
|
||||
|
||||
def _init_dispatch_handlers(self):
|
||||
dbg_evt_dispatch = {}
|
||||
@@ -60,7 +76,6 @@ class Debugger(object):
|
||||
dbg_evt_dispatch[RIP_EVENT] = self._handle_rip
|
||||
dbg_evt_dispatch[OUTPUT_DEBUG_STRING_EVENT] = self._handle_output_debug_string
|
||||
self._DebugEventCode_dispatch = dbg_evt_dispatch
|
||||
# TODO: breakpoint type dispatch
|
||||
|
||||
def _debug_event_generator(self):
|
||||
while True:
|
||||
@@ -78,68 +93,136 @@ class Debugger(object):
|
||||
self.current_thread = self.threads[debug_event.dwThreadId]
|
||||
|
||||
def _dispatch_debug_event(self, debug_event):
|
||||
#print("DISPATCH {0}".format(DEBUG_EVENT.KNOWN_EVENT_CODE.get(debug_event.dwDebugEventCode)))
|
||||
handler = self._DebugEventCode_dispatch.get(debug_event.dwDebugEventCode, self._handle_unknown_debug_event)
|
||||
return handler(debug_event)
|
||||
|
||||
def _dispatch_breakpoint(self, exception, addr):
|
||||
bp = self.breakpoints[addr]
|
||||
bp = self.breakpoints[self.current_process.pid][addr]
|
||||
x = bp.trigger(self, exception)
|
||||
return x
|
||||
|
||||
def _setup_breakpoint_BP(self, bp, targets):
|
||||
for target in targets:
|
||||
if not isinstance(target, WinProcess):
|
||||
raise ValueError("Cannot put standard breakpoint on target {0} (not a process)".format(target))
|
||||
self._memory_save[target.pid][bp.addr] = target.read_memory(bp.addr, 1)
|
||||
#print("Write BP: {0} at {1}".format(process, addr))
|
||||
target.write_memory(bp.addr, "\xcc")
|
||||
def _resolve(self, addr, target):
|
||||
if not isinstance(addr, basestring):
|
||||
return addr
|
||||
dll, api = addr.split("!")
|
||||
dll = dll.lower()
|
||||
modules = self._module_by_process[target.pid]
|
||||
mod = None
|
||||
if dll in modules:
|
||||
mod = [modules[dll]]
|
||||
if not mod:
|
||||
return None
|
||||
# TODO: optim exports are the same for whole system (32 vs 64 bits)
|
||||
# I don't have to reparse the exports each time..
|
||||
exports = mod[0].exports
|
||||
if api not in exports:
|
||||
raise ValueError("Unknown API <{0}> in DLL {1}".format(api, dll))
|
||||
return exports[api]
|
||||
|
||||
def _setup_breakpoint_HXBP(self, bp, targets):
|
||||
all_threads = []
|
||||
for target in targets:
|
||||
if isinstance(target, WinProcess):
|
||||
for t in target.threads:
|
||||
all_threads.append(t)
|
||||
elif isinstance(target, WinThread):
|
||||
all_threads.append(target)
|
||||
|
||||
def add_pending_breakpoint(self, bp, target):
|
||||
self._pending_breakpoints_new[target].append(bp)
|
||||
|
||||
def _setup_breakpoint(self, bp, targets):
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
if targets is None:
|
||||
if bp.type == STANDARD_BP: #TODO: better..
|
||||
targets = self.processes
|
||||
else:
|
||||
raise ValueError("Unknow HXBP target type for <{0}>".format(target))
|
||||
for target_thread in all_threads:
|
||||
x = self._hardware_breakpoint[target_thread.tid]
|
||||
if all(pos in x for pos in range(4)):
|
||||
raise ValueError("Cannot put {0} in {1} (DRx full)".format(bp, target_thread))
|
||||
empty_drx = str([pos for pos in range(4) if pos not in x][0])
|
||||
ctx = target_thread.context
|
||||
ctx.EDr7.GE = 1
|
||||
ctx.EDr7.LE = 1
|
||||
|
||||
setattr(ctx.EDr7, "L" + empty_drx, 1)
|
||||
setattr(ctx, "Dr" + empty_drx, bp.addr)
|
||||
x[int(empty_drx)] = bp
|
||||
target_thread.set_context(ctx)
|
||||
targets = self.threads
|
||||
for target in targets:
|
||||
return _setup_method(bp, target)
|
||||
|
||||
|
||||
def _setup_pending_breakpoints(self, target):
|
||||
# TODO: good format of data ? (dict and we just use values)
|
||||
# TODO: need to handle threads ?
|
||||
# TODO: handle target/expected_target is a thread :)
|
||||
# Can it happen ?
|
||||
pending_todo = list(self._pending_breakpoints.values())
|
||||
for bp, expected_target in pending_todo:
|
||||
# Valid addr ? (in non-loaded module: raise / pass ?)
|
||||
if expected_target is None or expected_target.pid == target.pid:
|
||||
if isinstance(target, WinThread):
|
||||
if bp.type == STANDARD_BP:
|
||||
continue # Standard BP are set on wide process, nothing to do on a thread
|
||||
x = self._hardware_breakpoint[target.tid]
|
||||
# Ignore BP on thread_create that have already been
|
||||
# put by the process_create event
|
||||
if bp in x.values():
|
||||
continue
|
||||
def _setup_breakpoint_BP(self, bp, target):
|
||||
if not isinstance(target, WinProcess):
|
||||
raise ValueError("SETUP STANDARD_BP on {0}".format(target))
|
||||
|
||||
addr = self._resolve(bp.addr, target)
|
||||
if addr is None:
|
||||
return False
|
||||
self._memory_save[target.pid][addr] = target.read_memory(addr, 1)
|
||||
self.breakpoints[target.pid][addr] = bp
|
||||
target.write_memory(addr, "\xcc")
|
||||
return True
|
||||
|
||||
def _setup_breakpoint_HXBP(self, bp, target):
|
||||
if not isinstance(target, WinThread):
|
||||
raise ValueError("SETUP HXBP_BP on {0}".format(target))
|
||||
# Todo: opti, not reparse exports for all thread of the same process..
|
||||
addr = self._resolve(bp.addr, target.owner)
|
||||
if addr is None:
|
||||
return False
|
||||
x = self._hardware_breakpoint[target.tid]
|
||||
if all(pos in x for pos in range(4)):
|
||||
raise ValueError("Cannot put {0} in {1} (DRx full)".format(bp, target))
|
||||
empty_drx = str([pos for pos in range(4) if pos not in x][0])
|
||||
ctx = target.context
|
||||
ctx.EDr7.GE = 1
|
||||
ctx.EDr7.LE = 1
|
||||
setattr(ctx.EDr7, "L" + empty_drx, 1)
|
||||
setattr(ctx, "Dr" + empty_drx, addr)
|
||||
x[int(empty_drx)] = bp
|
||||
target.set_context(ctx)
|
||||
self.breakpoints[target.owner.pid][addr] = bp
|
||||
return True
|
||||
|
||||
def _setup_pending_breakpoints_new_process(self, new_process):
|
||||
for bp in self._pending_breakpoints_new[None]:
|
||||
if bp.apply_to_target(new_process): #BP for thread or process ?
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
_setup_method(bp, [target])
|
||||
# TODO REMOVE PENDING HERE if target is not None..
|
||||
_setup_method(bp, new_process)
|
||||
|
||||
for bp in list(self._pending_breakpoints_new[new_process.pid]):
|
||||
if bp.apply_to_target(new_process):
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
if _setup_method(bp, new_process):
|
||||
self._pending_breakpoints_new[new_process.pid].remove(bp)
|
||||
|
||||
def _setup_pending_breakpoints_new_thread(self, new_thread):
|
||||
for bp in self._pending_breakpoints_new[None]:
|
||||
if bp.apply_to_target(new_thread): #BP for thread or process ?
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
_setup_method(bp, new_thread)
|
||||
|
||||
for bp in self._pending_breakpoints_new[new_thread.owner.pid]:
|
||||
if bp.apply_to_target(new_thread):
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
_setup_method(bp, new_thread)
|
||||
|
||||
for bp in list(self._pending_breakpoints_new[new_thread.tid]):
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
if _setup_method(bp, new_thread):
|
||||
self._pending_breakpoints_new[new_thread.tid].remove(bp)
|
||||
|
||||
|
||||
def _setup_pending_breakpoints_load_dll(self, dll_name):
|
||||
for bp in self._pending_breakpoints_new[None]:
|
||||
if isinstance(bp.addr, basestring):
|
||||
target_dll = bp.addr.split("!")[0]
|
||||
if target_dll == dll_name:
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
if bp.apply_to_target(self.current_process):
|
||||
_setup_method(bp, self.current_process)
|
||||
else:
|
||||
for t in self.current_process.threads:
|
||||
_setup_method(bp, t)
|
||||
|
||||
for bp in self._pending_breakpoints_new[self.current_process.pid]:
|
||||
if isinstance(bp.addr, basestring):
|
||||
target_dll = bp.addr.split("!")[0]
|
||||
if target_dll == dll_name:
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
_setup_method(bp, self.current_process)
|
||||
|
||||
for thread in self.current_process.threads:
|
||||
for bp in self._pending_breakpoints_new[thread.tid]:
|
||||
if isinstance(bp.addr, basestring):
|
||||
target_dll = bp.addr.split("!")[0]
|
||||
if target_dll == dll_name:
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
_setup_method(bp, self.thread)
|
||||
|
||||
def _pass_breakpoint(self, addr):
|
||||
process = self.current_process
|
||||
@@ -147,7 +230,7 @@ class Debugger(object):
|
||||
process.write_memory(addr, self._memory_save[process.pid][addr])
|
||||
regs = thread.context
|
||||
regs.EFlags |= (1 << 8)
|
||||
regs.Eip -= 1
|
||||
regs.pc -= 1
|
||||
thread.set_context(regs)
|
||||
self._breakpoint_to_reput[thread.tid] = addr #Register pending breakpoint for next single step
|
||||
|
||||
@@ -161,14 +244,13 @@ class Debugger(object):
|
||||
self._update_debugger_state(debug_event)
|
||||
|
||||
if windows.current_process.bitness == 32:
|
||||
exception.__class__ = windows.vectored_exception.EEXCEPTION_DEBUG_INFO32
|
||||
exception.__class__ = windows.exception.EEXCEPTION_DEBUG_INFO32
|
||||
else:
|
||||
exception.__class__ = windows.vectored_exception.EEXCEPTION_DEBUG_INFO64
|
||||
exception.__class__ = windows.exception.EEXCEPTION_DEBUG_INFO64
|
||||
|
||||
excp_code = exception.ExceptionRecord.ExceptionCode
|
||||
excp_addr = exception.ExceptionRecord.ExceptionAddress
|
||||
|
||||
if excp_code in [EXCEPTION_BREAKPOINT, STATUS_WX86_BREAKPOINT] and excp_addr in self.breakpoints:
|
||||
if excp_code in [EXCEPTION_BREAKPOINT, STATUS_WX86_BREAKPOINT] and excp_addr in self.breakpoints[self.current_process.pid]:
|
||||
continue_flag = self._dispatch_breakpoint(exception, excp_addr)
|
||||
self._pass_breakpoint(excp_addr)
|
||||
return continue_flag
|
||||
@@ -179,9 +261,10 @@ class Debugger(object):
|
||||
# Re-put the breakpoint
|
||||
self.current_process.write_memory(addr, "\xcc")
|
||||
return DBG_CONTINUE
|
||||
elif excp_addr in self.breakpoints:
|
||||
elif excp_addr in self.breakpoints[self.current_process.pid]:
|
||||
# Verif that's not a standard BP ?
|
||||
bp = self.breakpoints[excp_addr]
|
||||
bp = self.breakpoints[self.current_process.pid][excp_addr]
|
||||
#import pdb;pdb.set_trace()
|
||||
bp.trigger(self, exception)
|
||||
ctx = self.current_thread.context
|
||||
ctx.EEFlags.RF = 1
|
||||
@@ -193,6 +276,23 @@ class Debugger(object):
|
||||
return self.on_exception(exception)
|
||||
|
||||
|
||||
def _get_loaded_dll(self, load_dll):
|
||||
if not load_dll.lpImageName:
|
||||
pe = windows.pe_parse.GetPEFile(load_dll.lpBaseOfDll, self.current_process)
|
||||
return pe.export_name
|
||||
try:
|
||||
addr = self.current_process.read_ptr(load_dll.lpImageName)
|
||||
except:
|
||||
addr = None
|
||||
|
||||
if not addr:
|
||||
pe = windows.pe_parse.GetPEFile(load_dll.lpBaseOfDll, self.current_process)
|
||||
return pe.export_name
|
||||
|
||||
if load_dll.fUnicode:
|
||||
return self.current_process.read_wstring(addr)
|
||||
return self.current_process.read_string(addr)
|
||||
|
||||
def _handle_create_process(self, debug_event):
|
||||
"""Handle CREATE_PROCESS_DEBUG_EVENT"""
|
||||
create_process = debug_event.u.CreateProcessInfo
|
||||
@@ -201,8 +301,11 @@ class Debugger(object):
|
||||
self.current_thread = WinThread._from_handle(create_process.hThread)
|
||||
self.threads[self.current_thread.tid] = self.current_thread
|
||||
self.processes[self.current_process.pid] = self.current_process
|
||||
self.breakpoints[self.current_process.pid] = {}
|
||||
self._module_by_process[self.current_process.pid] = {}
|
||||
self._update_debugger_state(debug_event)
|
||||
self._setup_pending_breakpoints(self.current_process)
|
||||
self._setup_pending_breakpoints_new_process(self.current_process)
|
||||
self._setup_pending_breakpoints_new_thread(self.current_thread)
|
||||
return self.on_create_process(create_process)
|
||||
# TODO: clode hFile
|
||||
|
||||
@@ -225,7 +328,8 @@ class Debugger(object):
|
||||
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._setup_pending_breakpoints(self.current_thread)
|
||||
#import pdb;pdb.set_trace()
|
||||
self._setup_pending_breakpoints_new_thread(self.current_thread)
|
||||
return self.on_create_thread(create_thread)
|
||||
|
||||
|
||||
@@ -245,6 +349,10 @@ class Debugger(object):
|
||||
"""Handle LOAD_DLL_DEBUG_EVENT"""
|
||||
self._update_debugger_state(debug_event)
|
||||
load_dll = debug_event.u.LoadDll
|
||||
dll = self._get_loaded_dll(load_dll)
|
||||
dll_name = os.path.basename(dll).lower()
|
||||
self._module_by_process[self.current_process.pid][dll_name] = windows.pe_parse.GetPEFile(load_dll.lpBaseOfDll, self.current_process)
|
||||
self._setup_pending_breakpoints_load_dll(dll_name)
|
||||
return self.on_load_dll(load_dll)
|
||||
|
||||
def _handle_unload_dll(self, debug_event):
|
||||
@@ -267,6 +375,7 @@ class Debugger(object):
|
||||
|
||||
# Public API
|
||||
def loop(self):
|
||||
"""Debugging loop: handle event / dispatch to breakpoint. Returns when all targets are dead"""
|
||||
for debug_event in self._debug_event_generator():
|
||||
dbg_continue_flag = self._dispatch_debug_event(debug_event)
|
||||
if dbg_continue_flag is None:
|
||||
@@ -275,8 +384,63 @@ class Debugger(object):
|
||||
if not self.processes:
|
||||
break
|
||||
|
||||
#def add_bp(self, bp, addr=None, type=None, target=None):
|
||||
# """Add a breakpoint, bp can be:
|
||||
#
|
||||
# * a :class:`Breakpoint` (addr and type must be None)
|
||||
# * any callable (addr and type must NOT be None)
|
||||
#
|
||||
# If the ``bp`` type is ``STANDARD_BP``, target can be None (all targets) or a process.
|
||||
#
|
||||
# If the ``bp`` type is ``HARDWARE_EXEC_BP``, target can be None (all targets), a process or a thread.
|
||||
# """
|
||||
# if getattr(bp, "addr", None) is None:
|
||||
# if addr is None or type is None:
|
||||
# raise ValueError("SUCK YOUR NONE")
|
||||
# bp = ProxyBreakpoint(bp, addr, type)
|
||||
# else:
|
||||
# if addr is not None or type is not None:
|
||||
# raise ValueError("Given <addr|type> by parameters but BP object have them")
|
||||
# del addr
|
||||
# del type
|
||||
# if target is None:
|
||||
# # Raise on multiple pending at same addr ?
|
||||
# # We will add the pending breakpoint to other new processes
|
||||
# if bp.addr in self._pending_breakpoints:
|
||||
# raise ValueError("Pending breakpoint already at {0}".format(hex(bp.addr)))
|
||||
# self._pending_breakpoints[bp.addr] = (bp, target)
|
||||
# targets = self.processes.values()
|
||||
# if targets is None:
|
||||
# return
|
||||
# else:
|
||||
# targets = [target]
|
||||
# if bp.addr in self.breakpoints:
|
||||
# raise ValueError("Breakpoint already at {0}".format(hex(bp.addr)))
|
||||
#
|
||||
# #self.breakpoints[bp.addr] = bp
|
||||
#
|
||||
# if isinstance(bp.addr, basestring):
|
||||
# dll, api = bp.addr.split("!")
|
||||
# dll = dll.lower()
|
||||
# if dll not in self._pending_address: #TODO: default dict
|
||||
# self._pending_address[dll] = []
|
||||
# self._pending_address[dll].append((api, bp))
|
||||
#
|
||||
# _setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
# _setup_method(bp, targets)
|
||||
# return True
|
||||
|
||||
|
||||
def add_bp(self, bp, addr=None, type=None, target=None):
|
||||
"""TODO: use type for hardware breakpoints"""
|
||||
"""Add a breakpoint, bp can be:
|
||||
|
||||
* a :class:`Breakpoint` (addr and type must be None)
|
||||
* any callable (addr and type must NOT be None)
|
||||
|
||||
If the ``bp`` type is ``STANDARD_BP``, target can be None (all targets) or a process.
|
||||
|
||||
If the ``bp`` type is ``HARDWARE_EXEC_BP``, target can be None (all targets), a process or a thread.
|
||||
"""
|
||||
if getattr(bp, "addr", None) is None:
|
||||
if addr is None or type is None:
|
||||
raise ValueError("SUCK YOUR NONE")
|
||||
@@ -286,59 +450,68 @@ class Debugger(object):
|
||||
raise ValueError("Given <addr|type> by parameters but BP object have them")
|
||||
del addr
|
||||
del type
|
||||
|
||||
if target is None:
|
||||
# Raise on multiple pending at same addr ?
|
||||
# We will add the pending breakpoint to other new processes
|
||||
if bp.addr in self._pending_breakpoints:
|
||||
raise ValueError("Pending breakpoint already at {0}".format(hex(bp.addr)))
|
||||
self._pending_breakpoints[bp.addr] = (bp, target)
|
||||
targets = self.processes.values()
|
||||
if targets is None:
|
||||
return
|
||||
else:
|
||||
targets = [target]
|
||||
if bp.addr in self.breakpoints:
|
||||
raise ValueError("Breakpoint already at {0}".format(hex(bp.addr)))
|
||||
self.breakpoints[bp.addr] = bp
|
||||
_setup_method = getattr(self, "_setup_breakpoint_" + bp.type)
|
||||
_setup_method(bp, targets)
|
||||
return True
|
||||
# Need to add it to all other breakpoint
|
||||
self.add_pending_breakpoint(bp, None)
|
||||
elif target is not None:
|
||||
# Check that targets are accepted
|
||||
if target not in self.processes + self.threads:
|
||||
if target == self.target: # Original target (that have not been lauched yet)
|
||||
return self.add_pending_breakpoint(bp, target)
|
||||
else:
|
||||
raise ValueError("Unknown target {0}".format(target))
|
||||
return self._setup_breakpoint(bp, target)
|
||||
|
||||
# Public callback
|
||||
def on_exception(self, exception):
|
||||
"""Called on exception event other that known breakpoint"""
|
||||
pass
|
||||
|
||||
def on_create_process(self, create_process):
|
||||
"""Called on create_process event"""
|
||||
pass
|
||||
|
||||
def on_exit_process(self, exit_process):
|
||||
"""Called on exit_process event"""
|
||||
pass
|
||||
|
||||
def on_create_thread(self, create_thread):
|
||||
"""Called on create_thread event"""
|
||||
pass
|
||||
|
||||
def on_exit_thread(self, exit_thread):
|
||||
"""Called on exit_thread event"""
|
||||
pass
|
||||
|
||||
def on_load_dll(self, load_dll):
|
||||
"""Called on load_dll event"""
|
||||
pass
|
||||
|
||||
def on_unload_dll(self, unload_dll):
|
||||
"""Called on unload_dll event"""
|
||||
pass
|
||||
|
||||
def on_output_debug_string(self, debug_string):
|
||||
"""Called on debug_string event"""
|
||||
pass
|
||||
|
||||
def on_rip(self, rip_info):
|
||||
"""Called on rip_info event"""
|
||||
pass
|
||||
|
||||
|
||||
class Breakpoint(object):
|
||||
"""An standard (Int3) breakpoint (type == ``STANDARD_BP``)"""
|
||||
type = STANDARD_BP # REAL BP
|
||||
def __init__(self, addr):
|
||||
self.addr = addr
|
||||
|
||||
def apply_to_target(self, target):
|
||||
return isinstance(target, WinProcess)
|
||||
|
||||
def trigger(self, dbg, exception):
|
||||
"""Called when breakpoint is hit"""
|
||||
pass
|
||||
|
||||
class ProxyBreakpoint(Breakpoint):
|
||||
@@ -351,7 +524,73 @@ class ProxyBreakpoint(Breakpoint):
|
||||
return self.target(dbg, exception)
|
||||
|
||||
class HXBreakpoint(Breakpoint):
|
||||
"""An hardware-execution breakpoint (type == ``HARDWARE_EXEC_BP``)"""
|
||||
type = HARDWARE_EXEC_BP
|
||||
|
||||
def apply_to_target(self, target):
|
||||
return isinstance(target, WinThread)
|
||||
|
||||
|
||||
## Test a fun little thing
|
||||
|
||||
from windows.exception import VectoredException
|
||||
import ctypes
|
||||
|
||||
class LocalDebugger(object):
|
||||
def __init__(self):
|
||||
self.breakpoints = {}
|
||||
self._memory_save = {}
|
||||
self._reput_breakpoint = {}
|
||||
|
||||
self.callback_vectored = VectoredException(self.callback)
|
||||
windows.winproxy.AddVectoredExceptionHandler(0, self.callback_vectored)
|
||||
|
||||
def get_exception_code(self):
|
||||
return self.current_exception[0].ExceptionRecord[0].ExceptionCode
|
||||
|
||||
def get_exception_context(self):
|
||||
return self.current_exception[0].ContextRecord[0]
|
||||
|
||||
def single_step(self):
|
||||
self.get_exception_context().EEFlags.TF = 1
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def _pass_breakpoint(self, addr, single_step):
|
||||
with windows.utils.VirtualProtected(addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(addr, self._memory_save[addr])
|
||||
self._reput_breakpoint[windows.current_thread.tid] = self.breakpoints[addr], single_step
|
||||
return self.single_step()
|
||||
|
||||
def callback(self, exc):
|
||||
self.current_exception = exc
|
||||
exp_code = self.get_exception_code()
|
||||
exp_addr = self.get_exception_context().get_pc()
|
||||
|
||||
if exp_code == EXCEPTION_BREAKPOINT and exp_addr in self.breakpoints:
|
||||
continue_value = self.breakpoints[exp_addr].trigger(self, exc)
|
||||
single_step = self.get_exception_context().EEFlags.TF # single step activated by breakpoint
|
||||
return self._pass_breakpoint(exp_addr, single_step)
|
||||
|
||||
if exp_code == EXCEPTION_SINGLE_STEP and windows.current_thread.tid in self._reput_breakpoint:
|
||||
bp, single_step = self._reput_breakpoint[windows.current_thread.tid]
|
||||
self._memory_save[bp.addr] = windows.current_process.read_memory(bp.addr, 1)
|
||||
with windows.utils.VirtualProtected(bp.addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(bp.addr, "\xcc")
|
||||
del self._reput_breakpoint[windows.current_thread.tid]
|
||||
if single_step:
|
||||
return self.on_exception(exc)
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
return self.on_exception(exc)
|
||||
|
||||
def on_exception(self, exc):
|
||||
return windef.EXCEPTION_CONTINUE_EXECUTION
|
||||
|
||||
def add_bp(self, bp):
|
||||
if bp.type != STANDARD_BP:
|
||||
raise NotImplementedError("Add non standard-BP in LocalKernelDebugger")
|
||||
self.breakpoints[bp.addr] = bp
|
||||
self._memory_save[bp.addr] = windows.current_process.read_memory(bp.addr, 1)
|
||||
|
||||
with windows.utils.VirtualProtected(bp.addr, 1, PAGE_EXECUTE_READWRITE):
|
||||
windows.current_process.write_memory(bp.addr, "\xcc")
|
||||
return
|
||||
+19
-6
@@ -197,10 +197,15 @@ def GetPEFile(baseaddr, target=None):
|
||||
export_directory_addr = baseaddr + export_directory_rva
|
||||
return create_structure_at(self._IMAGE_EXPORT_DIRECTORY, export_directory_addr)
|
||||
|
||||
class PESection(ctypes_structure_transformer(IMAGE_SECTION_HEADER)):
|
||||
@utils.fixedpropety
|
||||
def name(self):
|
||||
return ctypes.c_char_p(ctypes.addressof(self.Name)).value
|
||||
class PESection((IMAGE_SECTION_HEADER)):
|
||||
if target is None:
|
||||
@property
|
||||
def name(self):
|
||||
return ctypes.c_char_p(self.Name).value.decode()
|
||||
else:
|
||||
@property
|
||||
def name(self):
|
||||
return create_structure_at(ctypes.c_char_p, self._base_addr).value.decode()
|
||||
|
||||
def __repr__(self):
|
||||
return "<PESection \"{0}\">".format(self.name)
|
||||
@@ -209,8 +214,11 @@ def GetPEFile(baseaddr, target=None):
|
||||
def sections(self):
|
||||
nt_header = self.get_NT_HEADER()
|
||||
nb_section = nt_header.FileHeader.NumberOfSections
|
||||
base_section = ctypes.addressof(nt_header) + ctypes.sizeof(nt_header)
|
||||
sections_array = create_structure_at(self.PESection * nb_section, base_section)
|
||||
if target is None:
|
||||
base_section = ctypes.addressof(nt_header) + ctypes.sizeof(nt_header)
|
||||
else:
|
||||
base_section = nt_header._base_addr + ctypes.sizeof(nt_header)
|
||||
sections_array = create_structure_at((self.PESection * nb_section), base_section)
|
||||
return list(sections_array)
|
||||
|
||||
@utils.fixedpropety
|
||||
@@ -230,6 +238,11 @@ def GetPEFile(baseaddr, target=None):
|
||||
res[rva_name.str] = rva_addr.addr
|
||||
return res
|
||||
|
||||
@utils.fixedpropety
|
||||
def export_name(self):
|
||||
"""The Name attribute of the ``EXPORT_DIRECTORY``"""
|
||||
return self.get_EXPORT_DIRECTORY().Name.str
|
||||
|
||||
# TODO: get imports by parsing other modules exports if no INT
|
||||
@utils.fixedpropety
|
||||
def imports(self):
|
||||
|
||||
+111
-9
@@ -16,6 +16,7 @@ import windows.native_exec.nativeutils as nativeutils
|
||||
|
||||
from windows.generated_def.winstructs import *
|
||||
|
||||
from windows.native_exec.nativeutils import GetProcAddress64, GetProcAddress32
|
||||
|
||||
|
||||
is_process_32_bits = windows.current_process.bitness == 32
|
||||
@@ -195,6 +196,7 @@ class WindowsTestCase(unittest.TestCase):
|
||||
mods = [m for m in calc.peb.modules if m.name == "kernel32.dll"]
|
||||
self.assertTrue(mods, 'Could not find "kernel32.dll" in calc32')
|
||||
k32 = mods[0]
|
||||
mods[0].pe.sections # Just see if it's parse
|
||||
get_current_proc_id = k32.pe.exports['GetCurrentProcessId']
|
||||
# TODO: check get_current_proc_id value (but we cannot do 64->32 injection for now)
|
||||
#if is_process_64_bits:
|
||||
@@ -219,6 +221,7 @@ class WindowsTestCase(unittest.TestCase):
|
||||
mods = [m for m in calc.peb.modules if m.name == "kernel32.dll"]
|
||||
self.assertTrue(mods, 'Could not find "kernel32.dll" in calc32')
|
||||
k32 = mods[0]
|
||||
mods[0].pe.sections
|
||||
get_current_proc_id = k32.pe.exports['GetCurrentProcessId']
|
||||
data = calc.virtual_alloc(0x1000)
|
||||
remote_python_code = """
|
||||
@@ -456,7 +459,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
self.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
d = MyDbg(calc)
|
||||
d = MyDbg(calc, already_debuggable=True)
|
||||
d.loop()
|
||||
|
||||
def test_simple_standard_breakpoint(self):
|
||||
@@ -478,7 +481,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
LdrLoadDll32 = calcref.peb.modules[1].pe.exports["LdrLoadDll"]
|
||||
calcref.exit()
|
||||
|
||||
d = windows.debug.Debugger(calc)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
d.add_bp(TSTBP(LdrLoadDll32))
|
||||
d.loop()
|
||||
|
||||
@@ -491,6 +494,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
TEST_CASE.assertEqual(dbg.current_process.pid, calc.pid)
|
||||
TEST_CASE.assertEqual(dbg.current_process.read_memory(self.addr, 1), "\xcc")
|
||||
TEST_CASE.assertEqual(dbg.current_thread.context.pc - 1, self.addr)
|
||||
data[0] += 1
|
||||
d.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
@@ -502,7 +506,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
LdrLoadDll32 = calcref.peb.modules[1].pe.exports["LdrLoadDll"]
|
||||
calcref.exit()
|
||||
|
||||
d = windows.debug.Debugger(calc)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
calc.execute("\xc3")
|
||||
calc.execute("\xc3")
|
||||
calc.execute("\xc3")
|
||||
@@ -528,7 +532,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
LdrLoadDll32 = calcref.peb.modules[1].pe.exports["LdrLoadDll"]
|
||||
calcref.exit()
|
||||
|
||||
d = windows.debug.Debugger(calc)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
d.add_bp(TSTBP(LdrLoadDll32))
|
||||
d.loop()
|
||||
|
||||
@@ -551,7 +555,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
d.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
d = windows.debug.Debugger(calc)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
addr = calc.virtual_alloc(0x1000)
|
||||
calc.write_memory(addr, "\x90" * 8)
|
||||
d.add_bp(TSTBP(addr, 0))
|
||||
@@ -577,7 +581,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
raise NotImplementedError("Should fail before")
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
d = windows.debug.Debugger(calc)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
addr = calc.virtual_alloc(0x1000)
|
||||
calc.write_memory(addr, "\x90" * 8 + "\xc3")
|
||||
d.add_bp(TSTBP(addr, 0))
|
||||
@@ -609,8 +613,8 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
|
||||
def trigger(self, dbg, exc):
|
||||
TEST_CASE.assertNotEqual(len(dbg.current_process.threads), 1)
|
||||
for t in dbg.current_process.threads:
|
||||
TEST_CASE.assertNotEqual(t.context.Dr7, 0)
|
||||
#for t in dbg.current_process.threads:
|
||||
# TEST_CASE.assertNotEqual(t.context.Dr7, 0)
|
||||
if data[0] == 0: #First time we got it ! create new thread
|
||||
data[0] = 1
|
||||
calc.create_thread(addr, 0)
|
||||
@@ -618,7 +622,7 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
d.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
d = MyDbg(calc)
|
||||
d = MyDbg(calc, already_debuggable=True)
|
||||
addr = calc.virtual_alloc(0x1000)
|
||||
calc.write_memory(addr, "\x90" * 2 + "\xc3")
|
||||
d.add_bp(TSTBP(addr, 0))
|
||||
@@ -627,6 +631,104 @@ class DebuggerTestCase(unittest.TestCase):
|
||||
# Used to verif we actually called the Breakpoints
|
||||
TEST_CASE.assertEqual(data[0], 1)
|
||||
|
||||
def test_simple_breakpoint_name_addr(self):
|
||||
TEST_CASE = self
|
||||
data = [0]
|
||||
class TSTBP(windows.debug.Breakpoint):
|
||||
def trigger(self, dbg, exc):
|
||||
addr = exc.ExceptionRecord.ExceptionAddress
|
||||
TEST_CASE.assertEqual(dbg.current_process.pid, calc.pid)
|
||||
TEST_CASE.assertEqual(dbg.current_process.read_memory(addr, 1), "\xcc")
|
||||
TEST_CASE.assertEqual(dbg.current_thread.context.pc - 1, addr)
|
||||
data[0] += 1
|
||||
d.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
d.add_bp(TSTBP("ntdll.dll!LdrLoadDll"))
|
||||
d.loop()
|
||||
TEST_CASE.assertEqual(data[0], 1)
|
||||
|
||||
def test_simple_hardware_breakpoint_name_addr(self):
|
||||
TEST_CASE = self
|
||||
data = [0]
|
||||
class TSTBP(windows.debug.HXBreakpoint):
|
||||
def trigger(self, dbg, exc):
|
||||
addr = exc.ExceptionRecord.ExceptionAddress
|
||||
TEST_CASE.assertEqual(dbg.current_process.pid, calc.pid)
|
||||
TEST_CASE.assertEqual(dbg.current_thread.context.pc, dbg._resolve(self.addr, dbg.current_process))
|
||||
TEST_CASE.assertNotEqual(dbg.current_thread.context.Dr7, 0)
|
||||
TEST_CASE.assertNotEqual(dbg.current_process.read_memory(addr, 1), "\xcc")
|
||||
data[0] += 1
|
||||
d.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
d.add_bp(TSTBP("ntdll.dll!LdrLoadDll"))
|
||||
d.loop()
|
||||
TEST_CASE.assertEqual(data[0], 1)
|
||||
|
||||
def perform_manual_getproc_loadlib_32_yolo(self, target, dll_name):
|
||||
dll = "KERNEL32.DLL\x00".encode("utf-16-le")
|
||||
api = "LoadLibraryA\x00"
|
||||
dll_to_load = dll_name + "\x00"
|
||||
|
||||
RemoteManualLoadLibray = x86.MultipleInstr()
|
||||
code = RemoteManualLoadLibray
|
||||
code += x86.Mov("ECX", x86.mem("[ESP + 4]"))
|
||||
code += x86.Push(x86.mem("[ECX + 4]"))
|
||||
code += x86.Push(x86.mem("[ECX]"))
|
||||
code += x86.Call(":FUNC_GETPROCADDRESS32")
|
||||
code += x86.Push(x86.mem("[ECX + 8]"))
|
||||
code += x86.Call("EAX") # LoadLibrary
|
||||
code += x86.Pop("ECX")
|
||||
code += x86.Pop("ECX")
|
||||
code += x86.Ret()
|
||||
RemoteManualLoadLibray += GetProcAddress32
|
||||
|
||||
addr = target.virtual_alloc(0x1000)
|
||||
addr2 = addr + len(dll)
|
||||
addr3 = addr2 + len(api)
|
||||
addr4 = addr3 + len(dll_to_load)
|
||||
target.write_memory(addr, dll)
|
||||
target.write_memory(addr2, api)
|
||||
target.write_memory(addr3, dll_to_load)
|
||||
target.write_qword(addr4, addr)
|
||||
target.write_qword(addr4 + 4, addr2)
|
||||
target.write_qword(addr4 + 0x8, addr3)
|
||||
t = target.execute(RemoteManualLoadLibray.get_code(), addr4)
|
||||
return t
|
||||
|
||||
|
||||
def test_hardware_breakpoint_name_addr(self):
|
||||
TEST_CASE = self
|
||||
data = [0]
|
||||
class TSTBP(windows.debug.HXBreakpoint):
|
||||
def trigger(self, dbg, exc):
|
||||
addr = exc.ExceptionRecord.ExceptionAddress
|
||||
TEST_CASE.assertEqual(dbg.current_process.pid, calc.pid)
|
||||
TEST_CASE.assertEqual(dbg.current_thread.context.pc, dbg._resolve(self.addr, dbg.current_process))
|
||||
TEST_CASE.assertNotEqual(dbg.current_thread.context.Dr7, 0)
|
||||
TEST_CASE.assertNotEqual(dbg.current_process.read_memory(addr, 1), "\xcc")
|
||||
data[0] += 1
|
||||
if data[0] == 1:
|
||||
# Perform a loaddll in a new thread :)
|
||||
# See if it's trigger a bp
|
||||
t = TEST_CASE.perform_manual_getproc_loadlib_32_yolo(dbg.current_process, "wintrust.dll")
|
||||
self.new_thread = t
|
||||
if hasattr(self, "new_thread") and dbg.current_thread.tid == self.new_thread.tid:
|
||||
for t in dbg.current_process.threads:
|
||||
TEST_CASE.assertNotEqual(t.context.Dr7, 0)
|
||||
d.current_process.exit()
|
||||
|
||||
calc = pop_calc_32(dwCreationFlags=DEBUG_PROCESS)
|
||||
d = windows.debug.Debugger(calc, already_debuggable=True)
|
||||
d.add_bp(TSTBP("ntdll.dll!LdrLoadDll"))
|
||||
# Code that will load wintrust !
|
||||
d.loop()
|
||||
#TEST_CASE.assertEqual(data[0], 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
alltests = unittest.TestSuite()
|
||||
|
||||
Reference in New Issue
Block a user