diff --git a/doc/source/debug.rst b/doc/source/debug.rst new file mode 100644 index 0000000..e9f5910 --- /dev/null +++ b/doc/source/debug.rst @@ -0,0 +1,46 @@ +:mod:`windows.debug` -- Debugging +================================= + +.. module:: windows.debug + +.. note:: + + See sample :ref:`sample_debugger` + +:class:`Debugger` +""""""""""""""""" + +The :class:`Debugger` is the base class to perform the debugging of a remote process. +The :class:`Debugger` have some functions called on given event that can be implemented by subclasses. + +.. autoclass:: Debugger + :members: + + .. automethod:: __init__ + + + +:class:`Breakpoint` +""""""""""""""""""" + +Standard breakpoints types expect an address as argument. + +An address can be: + + * An :class:`int` + * A :class:`str` of form (breakpoint will be put when ``DLL`` is loaded): + + * ``"DLL!ApiName"`` + * ``"DLL!Offset"`` where offset is a int ("16", "0x10", ..) + + +When a breakpoint is hit, its ``trigger`` function is called with the debugger and a +``DEBUG_EXECEPTION_EVENT`` structure as argument. + + +.. autoclass:: Breakpoint + :members: + +.. autoclass:: HXBreakpoint + :members: + :inherited-members: \ No newline at end of file diff --git a/doc/source/exception.rst b/doc/source/exception.rst new file mode 100644 index 0000000..c966e8d --- /dev/null +++ b/doc/source/exception.rst @@ -0,0 +1,96 @@ +Exception and Context related structures +======================================== + +.. module:: windows.exception + + +This module regroups all the Exception/Context related structures and functions. +Most of the structures are the Windows structure with a prefix ``E`` (For enhanced) + +Those structure have the same fields that the normal windows ones but its types might vary for a simpler use. + + +This module also define the decorator :func:`VectoredException` which allows to play with ``Vectored Exception Handler`` in Python +See sample :ref:`sample_vectoredexception` + +Exception Records +''''''''''''''''' + +.. autoclass:: EEXCEPTION_RECORD + :members: + :inherited-members: + +.. autoclass:: EEXCEPTION_RECORD32 + :inherited-members: + +.. autoclass:: EEXCEPTION_RECORD64 + :members: + :inherited-members: + +EXCEPTION DEBUG INFO +'''''''''''''''''''' + +.. autoclass:: EEXCEPTION_DEBUG_INFO32 + :members: + :inherited-members: + + .. data:: ExceptionRecord + + :type: :class:`EEXCEPTION_RECORD32` + + +.. autoclass:: EEXCEPTION_DEBUG_INFO64 + :members: + :inherited-members: + + .. data:: ExceptionRecord + + :type: :class:`EEXCEPTION_RECORD64` + +Context +''''''' + +.. autoclass:: ECONTEXT32 + :members: + :inherited-members: + +.. autoclass:: ECONTEXTWOW64 + :members: + :inherited-members: + +.. autoclass:: ECONTEXT64 + :members: + :inherited-members: + +.. autoclass:: EEflags + :members: + +.. autoclass:: EDr7 + :members: + +EXCEPTION POINTERS +'''''''''''''''''' + +.. autoclass:: EEXCEPTION_POINTERS + :members: + + .. data:: ExceptionRecord + + :type: POINTER to :class:`EEXCEPTION_RECORD` + + .. data:: ContextRecord + + :type: POINTER to :class:`ECONTEXT32` or :class:`ECONTEXT64` + + +.. _vectoredexception: + +Vectored Exception +'''''''''''''''''' + +.. note:: + + See sample :ref:`sample_vectoredexception` + +.. autoclass:: VectoredException + :members: \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst index d06a0fd..446bd28 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -4,7 +4,7 @@ contain the root `toctree` directive. Welcome to PythonForWindows's documentation! -===================================== +============================================ Contents: @@ -17,6 +17,7 @@ Contents: native_exec.rst winproxy.rst utils.rst + debug.rst iat_hook.rst wip.rst internals.rst diff --git a/doc/source/native_exec.rst b/doc/source/native_exec.rst index 77daec5..f46e723 100644 --- a/doc/source/native_exec.rst +++ b/doc/source/native_exec.rst @@ -181,3 +181,56 @@ Demo:: +:mod:`windows.native_exec.nativeutils` -- Native utility functions +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.. module:: windows.native_exec.nativeutils + +This module contains some native-code functions that can be used for various purposes. +Each function export a label that allow another :class:`MultipleInstr` to call the code of the function. + +The current functions are: + + * ``StrlenW64`` A 64bits wide-string STRLEN (``Label(":FUNC_STRLENW64")``) + * ``StrlenA64`` A 64bits ASCII STRLEN (``Label(":FUNC_STRLENA64")``) + * ``GetProcAddress64`` A 64bits export resolver (``Label(":FUNC_GETPROCADDRESS64")``) + + * Arg1: The DLL (wstring) + * Arg2: The API (string) + * Return value: + + * 0xfffffffffffffffe if the DLL is not found + * 0xffffffffffffffff if the API is not found + * The address of the function + + * ``StrlenW32`` A 32bits wide-string STRLEN (``Label(":FUNC_STRLENW32")``) + * ``StrlenA32`` A 32bits ASCII STRLEN (``Label(":FUNC_STRLENA32")``) + * ``GetProcAddress32`` A 32bits export resolver (``Label(":FUNC_GETPROCADDRESS32")``) + + * Arg1: The DLL (wstring) + * Arg2: The API (string) + * Return value: + + * 0xfffffffe if the DLL is not found + * 0xffffffff if the API is not found + * The address of the function + +To use those functions in a :class:`MultipleInstr` just call the label in your code and append the function at +the end of your :class:`MultipleInstr` + + +Example:: + + RemoteManualLoadLibray = x86.MultipleInstr() + + RemoteManualLoadLibray += x86.Mov("ECX", x86.mem("[ESP + 4]")) + RemoteManualLoadLibray += x86.Push(x86.mem("[ECX + 4]")) + RemoteManualLoadLibray += x86.Push(x86.mem("[ECX]")) + RemoteManualLoadLibray += x86.Call(":FUNC_GETPROCADDRESS32") + RemoteManualLoadLibray += x86.Push(x86.mem("[ECX + 8]")) + RemoteManualLoadLibray += x86.Call("EAX") # LoadLibrary + RemoteManualLoadLibray += x86.Pop("ECX") + RemoteManualLoadLibray += x86.Pop("ECX") + RemoteManualLoadLibray += x86.Ret() + + RemoteManualLoadLibray += GetProcAddress32 \ No newline at end of file diff --git a/doc/source/process.rst b/doc/source/process.rst index 610f8d4..d9f732c 100644 --- a/doc/source/process.rst +++ b/doc/source/process.rst @@ -50,6 +50,13 @@ WinThread :show-inheritance: :inherited-members: +Token +''''' + +.. autoclass:: Token + :members: + :inherited-members: + PEB Exploration """"""""""""""" diff --git a/doc/source/sample.rst b/doc/source/sample.rst index 4061284..8eba591 100644 --- a/doc/source/sample.rst +++ b/doc/source/sample.rst @@ -188,3 +188,97 @@ Output:: ... KeyValue(name='PathName', value=u'C:\\Windows', type=1)] registered owner = + + +.. _sample_vectoredexception: + +:func:`VectoredException` +""""""""""""""""""""""""" + +In local process +'''''''''''''''' + +.. literalinclude:: ..\..\samples\veh_segv.py + +Output:: + + (cmd λ) python.exe veh_segv.py + Protected page is at <0x1db0000> + Setting page protection to + + ==Entry of VEH handler== + Instr at 0x1d1ab574 accessed to addr 0x1db0000 + Resetting page protection to + ==Entry of VEH handler== + Exception of type EXCEPTION_SINGLE_STEP(0x80000004L) + Resetting page protection to + Value 1 read + + ==Entry of VEH handler== + Instr at 0x1d1ab574 accessed to addr 0x1db0010 + Resetting page protection to + ==Entry of VEH handler== + Exception of type EXCEPTION_SINGLE_STEP(0x80000004L) + Resetting page protection to + Value 2 read + + +In remote process +''''''''''''''''' + +.. literalinclude:: ..\..\samples\remote_veh_segv.py + +Output:: + + (cmd λ) python .exe.\samples\remote_veh_segv.py + (In another console) + + Tracing execution in module: + Protected page is at 0x7ffa3c700000L + + Instr at 0x7ffa3c70f0f0L accessed to addr 0x7ffa3c70f0f0L (gdi32.dll) + Exception of type EXCEPTION_SINGLE_STEP(0x80000004L) + Resetting page protection to + + Instr at 0x7ffa3c70f0f5L accessed to addr 0x7ffa3c70f0f5L (gdi32.dll) + Exception of type EXCEPTION_SINGLE_STEP(0x80000004L) + Resetting page protection to + + Instr at 0x7ffa3c70f0faL accessed to addr 0x7ffa3c70f0faL (gdi32.dll) + Exception of type EXCEPTION_SINGLE_STEP(0x80000004L) + Resetting page protection to + + Instr at 0x7ffa3c70f0ffL accessed to addr 0x7ffa3c70f0ffL (gdi32.dll) + Exception of type EXCEPTION_SINGLE_STEP(0x80000004L) + Resetting page protection to + + Instr at 0x7ffa3c70f100L accessed to addr 0x7ffa3c70f100L (gdi32.dll) + No more tracing ! + + +.. _sample_debugger: + +Debugging +""""""""" + +.. literalinclude:: ..\..\samples\debugger.py + +Ouput:: + + (cmd λ) python.exe .\samples\debugger.py + Loading + Got exception EXCEPTION_BREAKPOINT(0x80000003L) at 0x77a73bad + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Loading + Ask to load : exiting process \ No newline at end of file diff --git a/doc/source/various.rst b/doc/source/various.rst index 8777621..78abdd3 100644 --- a/doc/source/various.rst +++ b/doc/source/various.rst @@ -10,6 +10,7 @@ This sections describes them by group of relation. :maxdepth: 3 process.rst + exception.rst registry.rst network.rst com.rst \ No newline at end of file diff --git a/samples/remote_veh_segv.py b/samples/remote_veh_segv.py index 068d51e..07316a9 100644 --- a/samples/remote_veh_segv.py +++ b/samples/remote_veh_segv.py @@ -3,42 +3,62 @@ import windows.test from windows.generated_def.winstructs import * -#c = windows.test.pop_calc_64() - - -c = windows.test.pop_calc_64(dwCreationFlags=CREATE_SUSPENDED) - - python_code = """ import windows import ctypes import windows -from windows.vectored_exception import VectoredException +from windows.exception import VectoredException import windows.generated_def.windef as windef from windows.generated_def.winstructs import * windows.utils.create_console() +module_to_trace = "gdi32.dll" +nb_repeat = [5] + @VectoredException def handler(exc): - print("POUET EXCEPTION") if exc[0].ExceptionRecord[0].ExceptionCode == EXCEPTION_ACCESS_VIOLATION: + print("") target_addr = ctypes.cast(exc[0].ExceptionRecord[0].ExceptionInformation[1], ctypes.c_void_p).value - print("Instr at {0} accessed to addr {1}".format(hex(exc[0].ExceptionRecord[0].ExceptionAddress), hex(target_addr))) - windows.winproxy.VirtualProtect(target_page, 0x1000, windef.PAGE_READWRITE) + print("Instr at {0} accessed to addr {1} ({2})".format(hex(exc[0].ExceptionRecord[0].ExceptionAddress), hex(target_addr), module_to_trace)) + windows.winproxy.VirtualProtect(target_page, code_size, windef.PAGE_EXECUTE_READWRITE) + nb_repeat[0] -= 1 + if nb_repeat[0]: + exc[0].ContextRecord[0].EEFlags.TF = 1 + else: + print("No more tracing !") + return windef.EXCEPTION_CONTINUE_EXECUTION + else: + print("Exception of type {0}".format(exc[0].ExceptionRecord[0].ExceptionCode)) + print("Resetting page protection to ") + windows.winproxy.VirtualProtect(target_page, code_size, windef.PAGE_READWRITE) return windef.EXCEPTION_CONTINUE_EXECUTION - return windef.EXCEPTION_CONTINUE_SEARCH windows.winproxy.AddVectoredExceptionHandler(0, handler) -target_page = windows.current_process.virtual_alloc(0x1000) -print("Protected page is at {0}".format(hex(target_page))) -windows.winproxy.VirtualProtect(target_page, 0x1000, windef.PAGE_NOACCESS) +print("Tracing execution in module: <{0}>".format(module_to_trace)) -print("YOLO <3") -print(ctypes.c_uint.from_address(target_page + 0x42).value) +module = [x for x in windows.current_process.peb.modules if x.name == module_to_trace][0] +target_page = module.baseaddr +code_size = module.pe.get_OptionalHeader().SizeOfCode + +print("Protected page is at {0}".format(hex(target_page))) +windows.winproxy.VirtualProtect(target_page, code_size, windef.PAGE_READWRITE) """ +c = windows.test.pop_calc_64(dwCreationFlags=CREATE_SUSPENDED) +x = c.execute_python(python_code) + +c.threads[0].resume() + +import time +time.sleep(0.1) + +for t in c.threads: + t.suspend() + +time.sleep(1) +c.exit() -x = c.execute_python(python_code) \ No newline at end of file diff --git a/samples/veh_segv.py b/samples/veh_segv.py index 95245ba..5770507 100644 --- a/samples/veh_segv.py +++ b/samples/veh_segv.py @@ -1,21 +1,23 @@ import ctypes import windows -from windows.vectored_exception import VectoredException +from windows.exception import VectoredException import windows.generated_def.windef as windef from windows.generated_def.winstructs import * @VectoredException def handler(exc): - print("POUET") + print("==Entry of VEH handler==") if exc[0].ExceptionRecord[0].ExceptionCode == EXCEPTION_ACCESS_VIOLATION: target_addr = ctypes.cast(exc[0].ExceptionRecord[0].ExceptionInformation[1], ctypes.c_void_p).value print("Instr at {0} accessed to addr {1}".format(hex(exc[0].ExceptionRecord[0].ExceptionAddress), hex(target_addr))) + print("Resetting page protection to ") windows.winproxy.VirtualProtect(target_page, 0x1000, windef.PAGE_READWRITE) exc[0].ContextRecord[0].EEFlags.TF = 1 return windef.EXCEPTION_CONTINUE_EXECUTION else: - print("HAHAH {0}".format(exc[0].ExceptionRecord[0].ExceptionCode)) + print("Exception of type {0}".format(exc[0].ExceptionRecord[0].ExceptionCode)) + print("Resetting page protection to ") windows.winproxy.VirtualProtect(target_page, 0x1000, windef.PAGE_NOACCESS) return windef.EXCEPTION_CONTINUE_EXECUTION @@ -23,25 +25,14 @@ def handler(exc): windows.winproxy.AddVectoredExceptionHandler(0, handler) target_page = windows.current_process.virtual_alloc(0x1000) -print("Protected page is at {0}".format(hex(target_page))) +print("Protected page is at <{0}>".format(hex(target_page))) +print("Setting page protection to ") windows.winproxy.VirtualProtect(target_page, 0x1000, windef.PAGE_NOACCESS) +print("") v = ctypes.c_uint.from_address(target_page).value -print("POINT1") +print("Value 1 read") + +print("") v = ctypes.c_uint.from_address(target_page + 0x10).value -print("POINT2") - - - -# (cmd) python.exe samples\veh_segv.py -#Protected page is at 0x3f0000 -#POUET -#Instr at 0x1d1ab5f4 accessed to addr 0x3f0000 -#POUET -#HAHAH EXCEPTION_SINGLE_STEP(0x80000004L) -#POINT1 -#POUET -#Instr at 0x1d1ab5f4 accessed to addr 0x3f0010 -#POUET -#HAHAH EXCEPTION_SINGLE_STEP(0x80000004L) -#POINT2 \ No newline at end of file +print("Value 2 read") diff --git a/windows/debug.py b/windows/debug.py index 88dfd5e..efe2163 100644 --- a/windows/debug.py +++ b/windows/debug.py @@ -32,7 +32,7 @@ class Debugger(object): def __init__(self, target, already_debuggable=False): """``target`` must be a WinProcess. - ``already_debuggable`` must be set to ``True`` if process is already expecting a debugger (created with DEBUG_PROCESS)""" + ``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 @@ -45,7 +45,6 @@ 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}} @@ -55,12 +54,6 @@ class Debugger(object): 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) @@ -103,7 +96,6 @@ class Debugger(object): return x def _resolve(self, addr, target): - print("Resolving <{0}> for {1}".format(addr, self.current_process)) if not isinstance(addr, basestring): return addr dll, api = addr.split("!") @@ -397,58 +389,11 @@ 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): """Add a breakpoint, bp can be: * a :class:`Breakpoint` (addr and type must be None) - * any callable (addr and type must NOT be None) + * any callable (addr and type must NOT be None) (NON-TESTED) If the ``bp`` type is ``STANDARD_BP``, target can be None (all targets) or a process.