diff --git a/windows/debug.py b/windows/debug.py index 2f54802..aa5794f 100644 --- a/windows/debug.py +++ b/windows/debug.py @@ -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 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 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 \ No newline at end of file diff --git a/windows/pe_parse.py b/windows/pe_parse.py index 2493c9e..2f2f9e6 100644 --- a/windows/pe_parse.py +++ b/windows/pe_parse.py @@ -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 "".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): diff --git a/windows/test/mytest.py b/windows/test/mytest.py index f762776..4dda20a 100644 --- a/windows/test/mytest.py +++ b/windows/test/mytest.py @@ -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()