diff --git a/doc/source/conf.py b/doc/source/conf.py index 825033c..6d0c527 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -20,15 +20,15 @@ import os # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) +sys.path.append(os.path.abspath(__file__ + "..\..\..\..")) - -sys.path.append(r"C:\Users\hakril\Documents\Work\PythonForWindows") +print("Adding <{0}>".format(sys.path[-1])) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +needs_sphinx = '1.2' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -56,7 +56,7 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'PyWindows' +project = u'PythonForWindows' copyright = u'2015, Clement Rouault' # The version info for the project you're documenting, acts as replacement for @@ -114,7 +114,8 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'alabaster' +html_theme = 'classic' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/doc/source/generated_doc.rst b/doc/source/generated_doc.rst deleted file mode 100644 index 615c4ea..0000000 --- a/doc/source/generated_doc.rst +++ /dev/null @@ -1,19 +0,0 @@ -GENERATED Documentation test -**************************** - -Testing1 -******** - -.. automodule:: windows.generated_def.winstructs - - - -Testing2 -******** - -.. autoclass:: _PEB_LDR_DATA -.. autoclass:: _PEB -.. autoclass:: _LIST_ENTRY - - - diff --git a/doc/source/index.rst b/doc/source/index.rst index bf4c039..b615f39 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -10,11 +10,9 @@ Contents: .. toctree:: :maxdepth: 2 + :numbered: - test.rst - pe_parse.rst - native_exec.rst - rem_python.rst + windows.rst utils.rst diff --git a/doc/source/native_exec.rst b/doc/source/native_exec.rst deleted file mode 100644 index c758b02..0000000 --- a/doc/source/native_exec.rst +++ /dev/null @@ -1,79 +0,0 @@ -Native code execution -*********************** - -.. automodule:: windows.native_exec - :members: generate_callback_stub, create_function - - -The native_function submodule -""""""""""""""""""""""""""""" - -.. automodule:: windows.native_exec.native_function - :members: create_function - - -Simple machine code generation -"""""""""""""""""""""""""""""" - -These modules allow you to write some simple x86 / x64 shellcode. This is useful to use in adequation to :func:`create_function`. -The instruction name are as explicit as possible with the following convention: - - * This is `intel syntax` so `dest, src` - * X specify a value passed as parameters:: - - Mov_EAX_X(0x42) # mov eax, 0x42 - - * D is for `dereference`:: - - Mov_EAX_DX(0x42424242) # mov EAX, [0x42424242] - Mov_DEAX_EDI() # mov [EAX], EDI - -All instructions follow this interface: - -.. py:class:: Instruction - - .. py:method:: get_code(self) - - :returns: :class:`str`: The raw code of the instruction - - .. py:method:: get_mnemo(self) - - :returns: :class:`str`: The mnemonic of the instruction - -Example:: - - import windows.native_exec.simple_x86 as x86 - - i = x86.Mov_EAX_X(0x42434445) - i.get_code() - # '\xb8EDCB' - i.get_mnemo() - # 'mov EAX, 0x42434445' - - -You can also use a :class:`MultipleInstr` instance to merge instructions - -Example:: - - import windows.native_exec.simple_x86 as x86 - - code = x86.MultipleInstr() - code += x86.Mov_EAX_X(0x42434445) - code += x86.Push_EAX() - code += x86.Ret() - code.get_code() - # '\xb8EDCBP\xc3' - print(code.get_mnemo()) - # mov EAX, 0x42434445 - # push EAX - # ret - -simple_x86 instructions ------------------------ - -.. automodule:: windows.native_exec.simple_x86 - -simple_x64 instructions ------------------------ - -.. automodule:: windows.native_exec.simple_x64 \ No newline at end of file diff --git a/doc/source/pe_parse.rst b/doc/source/pe_parse.rst deleted file mode 100644 index c880476..0000000 --- a/doc/source/pe_parse.rst +++ /dev/null @@ -1,179 +0,0 @@ -Loaded DLL Exploration and IAT hooks -************************************ - -List of loaded modules -"""""""""""""""""""""" - -Accessible using:: - - import windows - windows.current_process.peb.modules[int].pe - -..note:: - See: :class:`windows.winobject.PEB` and :class:`windows.winobject.LoadedModule` - - - -DLL Import and IAT -"""""""""""""""""" - -.. py:class:: PEFile - - .. py:attribute:: imports - - The imports of the PE - - .. note:: - This is a :class:`dict` DLLName -> [:class:`IATEntry`] - - Example:: - - import windows - k32 = windows.current_process.peb.modules[2] - # - k32.pe.imports.keys() - # ['kernelbase.dll', 'api-ms-win-core-profile-l1-1-0.dll', ...] - k32.pe.imports['kernelbase.dll'] - # [, , ...] - [entry for entry in k32.pe.imports['kernelbase.dll'] if entry.name == "lstrcmpiW"][0] - # - - -.. py:class:: IATEntry - - | Reprensent An entry in the IAT of a module - | Can be used to get resolved value and setup hook - - .. py:attribute:: name - - | :class:`int` : The name of the import - - - .. py:attribute:: ord - - | :class:`int` : The ordinal of the import - - - .. py:attribute:: addr - - | :class:`int` : The address of the IAT entry - - .. py:attribute:: value - - | :class:`int` : The destination of the IAT entry - - .. warning:: - - `value` is a descriptor. Setting its value will actually CHANGE THE IAT ENTRY, resulting in a segfault if no VirtualProtect have been done. - - .. note:: - - See: :class:`windows.utils.VirtualProtected` - - - .. py:method:: set_hook(self, callback, types=None) - - Setup a hook, `callback` should respect the :ref:`hook_protocol`. If `callback` have no :ref:`type_information`, `types` should provide them. - - -IAT Hooking -""""""""""" - -.. _hook_protocol: - -The hook protocol ------------------ - -Callback arguments -'''''''''''''''''' - -A hook callback must have the same number of argument as the hooked API, PLUS a last argument `real_function`. - -The `real_function` argument is a callable that represent the hooked API, it can be called in two ways: - - * Without argument, the call will be done with the argument originaly passed to your callback. This allow simple redirection to the real API. - - * With arguments it will simply call the API with these. - - Example:: - - def createfile_callback(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, real_function): - print("Trying to open {0}".format(lpFileName)) - if "secret" in lpFileName: - return 0xffffffff - # Perform the real call - return real_function() - - -.. _type_information: - -Type information -'''''''''''''''' - -In order make the magic behind Python Hook Callback, :mod:`ctypes` need to have type information about the API parameters. - -There is (again) two ways to give those informations to your hook callback. Both techniques use a decorator to setup type information to the callback. - - * Giving the type manualy using the decorator :class:`windows.hooks.Callback`:: - - from windows.hooks import * - # First type is return type, others are parameters types - @Callback(ctypes.c_void_p, ctypes.c_ulong) - def exit_callback(x, real_function): - print("Try to quit with {0} | {1}".format(x, type(x))) - if x == 3: - print("TRYING TO REAL EXIT") - return real_function(1234) - return 0x4242424243444546 - - * Using the `Callback` decorator generated from known functions:: - - from windows.hooks import * - # Decorator name is always API_NAME + "CallBack" - @CreateFileACallback - def createfile_callback(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, real_function): - print("Trying to open {0}".format(lpFileName)) - if "secret" in lpFileName: - return 0xffffffff - return real_function() - - .. note:: - - See the list of known functions - - -Put the hook ------------- - -To setup your IAT hook you just need: - - * A callback that respect the :ref:`hook_protocol` - * The :class:`IATEntry` to hook - - -You just need to use the function :func:`IATEntry.set_hook` - - - Full Example:: - - import windows - from windows.hooks import * - - @CreateFileACallback - def createfile_callback(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, real_function): - print("Trying to open {0}".format(lpFileName)) - if "secret" in lpFileName: - return 0xffffffff - return real_function() - - my_exe = windows.current_process.peb.modules[0] - imp = my_exe.pe.imports - - iat_create_file = [entry for entry in imp['kernel32.dll'] if entry.name == "CreateFileA"] - iat_create_file.set_hook(createfile_callback) - - - - - - diff --git a/doc/source/rem_python.rst b/doc/source/rem_python.rst deleted file mode 100644 index 7185e87..0000000 --- a/doc/source/rem_python.rst +++ /dev/null @@ -1,77 +0,0 @@ -Remote Python Injection -*********************** - -It's possible to inject the python interpreter into remote process. All you need to do is to use the method :func:`windows.winobject.WinProcess.execute_python`. - -Calling this function will trigger the interpreter injection and the python code execution. - -For simpler interaction interaction with the remote python, you can an RPC master linked to the remote interpreter. - -RPython 101 -''''''''''' - -RPython is a very simple Pythonic (I hope so) RPC slave-master. It's goal is to allow easy manipulation of a remote interpreter. - -The only action up to the `slave` is the creation of it's `name_pool`: a namespace of object accessible by the `master`. -After that the `slave` will just wait for request and return the desired object. - -All slave object seen by the master are proxy that redirect operation to the slave. - -RPython RPC to remote process -''''''''''''''''''''''''''''' - -For easy manipulation of a remote Python interpreter, you can use the RPCInjection module:: - - import windows - import RPCInjection - calc = [x for x in windows.system.processes if x.name == "calc.exe"][0] - master = RPCInjection.launch_remote_slave(calc) - # Master is a RPC-master to a python interpreter in calc.exe - master['windows'] - # |> - - # This is a way to get our own (python.exe) pid - windows.current_process.pid - 3624 - # This is a way to get the pid of calc.exe - master['windows'].current_process - # |> - master['windows'].current_process.pid - 5052 - # We can also play with the peb of the remote process - master['windows'].current_process.peb - # |> - master['windows'].current_process.peb.commandline - # |> - - - # we can import new stuff - - x['json'] - # ... - # RPython.exchange.RemoteKeyError: - # .... - # KeyError: u'json' - x.imp('json') - # - x['json'] - # |> - - -.. note:: - - The slave `name_pool` in RPCInjection is filled with :mod:`windows`, :mod:`__import__`, :mod:`ctypes` and :mod:`self` (the RemotePythonSlave object) - -The master.RemotePython -''''''''''''''''''''''' - -.. autoclass:: RPython.master.RemotePython - - .. py:method:: __getitem__ - - Alias to :func:`ask_by_name` - -Remote IAT Hooking -'''''''''''''''''' - -See Examples directory \ No newline at end of file diff --git a/doc/source/test.rst b/doc/source/test.rst deleted file mode 100644 index 10ba740..0000000 --- a/doc/source/test.rst +++ /dev/null @@ -1,13 +0,0 @@ -Overwiew of the `windows` objects -************************** - -object exported by `windows` -"""""""""""""""""""""""""""" - -.. automodule:: windows - -Principal classes for process exploration -""""""""""""""""""""""""""""""""""""""""" - -.. automodule:: windows.winobject - :members: diff --git a/doc/source/utils.rst b/doc/source/utils.rst index 27ce1a8..a32d105 100644 --- a/doc/source/utils.rst +++ b/doc/source/utils.rst @@ -1,4 +1,34 @@ -Helpers -******* +``windows.utils`` -- Pythonic Windows Utilities +*********************************************** -.. automodule:: windows.utils \ No newline at end of file +.. module:: windows.utils + +Context Managers +"""""""""""""""" + +:mod:`windows.utils` provides some context managers wrapping `standard` contextual operations +like ``VirtualProtect`` or ``SysWow Redirection`` + +VirtualProtected +'''''''''''''''' + +.. autoclass:: windows.utils.VirtualProtected + :no-show-inheritance: + +DisableWow64FsRedirection +''''''''''''''''''''''''' + +.. autoclass:: windows.utils.DisableWow64FsRedirection + :no-show-inheritance: + +Helper functions +"""""""""""""""" + +.. autofunction:: windows.utils.enable_privilege +.. autofunction:: windows.utils.check_is_elevated +.. autofunction:: windows.utils.check_debug +.. autofunction:: windows.utils.create_process +.. autofunction:: windows.utils.create_console +.. autofunction:: windows.utils.pop_shell +.. autofunction:: windows.utils.create_file_from_handle +.. autofunction:: windows.utils.get_handle_from_file \ No newline at end of file diff --git a/doc/source/windows.rst b/doc/source/windows.rst new file mode 100644 index 0000000..0ff4da7 --- /dev/null +++ b/doc/source/windows.rst @@ -0,0 +1,32 @@ +The ``windows`` module +********************** + +The ``windows`` module is the module installed by :file:`setup.py` (that does not exists right now). +This module export some object representing the current state of the system. It also offers some submodules aimed to help the interface with ``Windows`` and native code exection. + +The defaults objects accessible in ``windows`` are: + * ``system`` of type :class:`windows.winobject.System` + * ``current_process`` of type :class:`CurrentProcess` + * ``current_thread`` of type :class:`CurrentThread` + +The submodules that you might use by themself are: + * :mod:`windows.native_exec` + * :mod:`windows.winproxy` + * :mod:`windows.utils` + + +The ``system`` object +""""""""""""""""""""" + +.. autoclass:: windows.winobject.System + :no-show-inheritance: + + .. autoattribute:: windows.winobject.System.registry + :annotation: + + Object of class :class:`windows.registry.Registry` + + .. autoattribute:: windows.winobject.System.network + :annotation: + + Object of class :class:`windows.network.Network` \ No newline at end of file diff --git a/windows/__init__.py b/windows/__init__.py index c468570..0882167 100644 --- a/windows/__init__.py +++ b/windows/__init__.py @@ -24,5 +24,6 @@ current_thread = CurrentThread() import windows.vectored_exception import windows.wmi +import windows.utils __all__ = ["system", "VirtualProtected", 'current_process', 'current_thread', 'winproxy'] diff --git a/windows/dbgprint.py b/windows/dbgprint.py index a09df90..bd65d08 100644 --- a/windows/dbgprint.py +++ b/windows/dbgprint.py @@ -12,7 +12,7 @@ def get_stack_func_name(lvl): def do_dbgprint(msg, type=None): - if (options['cats'] is None) or type.upper() in options['cats']: + if ("ALL" in options['cats']) or type.upper() in options['cats']: frame, func = get_stack_func_name(2) logger = logging.getLogger(frame.f_globals['__name__'] + ":" + func) logger.debug(msg) @@ -26,7 +26,7 @@ def parse_option(s): if s[0] == "=": s = s[1:] if s: - cats = [x.upper() for x in s.split('-')] + cats = [x.upper().strip() for x in s.split('-')] options['cats'] = cats formt = 'DBG|%(name)s|%(message)s' diff --git a/windows/utils/winutils.py b/windows/utils/winutils.py index 880d3b2..01f07c3 100644 --- a/windows/utils/winutils.py +++ b/windows/utils/winutils.py @@ -43,18 +43,18 @@ def is_wow_64(hProcess): def create_file_from_handle(handle, mode="r"): - """Return a Python :class:`file` arround a windows HANDLE""" + """Return a Python :class:`file` around a ``Windows`` HANDLE""" fd = msvcrt.open_osfhandle(handle, os.O_TEXT) return os.fdopen(fd, mode, 0) def get_handle_from_file(f): - """Get the windows HANDLE of a python :class:`file`""" + """Get the ``Windows`` HANDLE of a python :class:`file`""" return msvcrt.get_osfhandle(f.fileno()) def create_console(): - """Create a new console displaying STDOUT + """Create a new console displaying STDOUT. Useful in injection of GUI process""" winproxy.AllocConsole() stdout_handle = winproxy.GetStdHandle(windef.STD_OUTPUT_HANDLE) @@ -71,6 +71,7 @@ def create_console(): def create_process(path, show_windows=False): + """A convenient wrapper arround :func:`windows.winproxy.CreateProcessA`""" proc_info = PROCESS_INFORMATION() lpStartupInfo = None if show_windows: @@ -84,7 +85,11 @@ def create_process(path, show_windows=False): def enable_privilege(lpszPrivilege, bEnablePrivilege): - """Enable of disable a privilege: enable_privilege(SE_DEBUG_NAME, True)""" + """ + Enable or disable a privilege:: + + enable_privilege(SE_DEBUG_NAME, True) + """ tp = TOKEN_PRIVILEGES() luid = LUID() hToken = HANDLE() @@ -105,7 +110,7 @@ def enable_privilege(lpszPrivilege, bEnablePrivilege): def check_is_elevated(): - """Return True if process is Admin""" + """Return ``True`` if process is Admin""" hToken = HANDLE() elevation = TOKEN_ELEVATION() cbsize = DWORD() @@ -117,8 +122,10 @@ def check_is_elevated(): def check_debug(): - """Check that kernel is in debug mode - beware if NOUMEX (https://msdn.microsoft.com/en-us/library/windows/hardware/ff556253(v=vs.85).aspx#_______noumex______)""" + """Check that kernel is in debug mode (beware of NOUMEX): + + https://msdn.microsoft.com/en-us/library/windows/hardware/ff556253(v=vs.85).aspx#_______noumex______ + """ hkresult = HKEY() cbsize = DWORD(1024) bufferres = (c_char * cbsize.value)() @@ -163,9 +170,8 @@ def get_kernel_modules(): class VirtualProtected(object): - """A context manager usable like `VirtualProtect` that will restore the old protection at exit - - Example:: + """ + A context manager usable like `VirtualProtect` that will restore the old protection at exit :: with utils.VirtualProtected(IATentry.addr, ctypes.sizeof(PVOID), windef.PAGE_EXECUTE_READWRITE): IATentry.value = 0x42424242 @@ -188,7 +194,14 @@ class VirtualProtected(object): class DisableWow64FsRedirection(object): - """A context manager that disable the Wow64 Fs Redirection""" + """ + A context manager that disable the SysWow64 Filesystem Redirection :: + + 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) + """ def __enter__(self): if windows.current_process.bitness == 64: return self diff --git a/windows/winobject.py b/windows/winobject.py index 9a224cb..88d98d0 100644 --- a/windows/winobject.py +++ b/windows/winobject.py @@ -6,6 +6,7 @@ import struct import windows import windows.network +import windows.registry import windows.syswow64 #import windows.vectored_exception import windows.winproxy as winproxy @@ -33,10 +34,12 @@ class AutoHandle(object): def handle(self): """A handle on the object + :type: HANDLE + + .. note:: The handle is automaticaly closed when the object is destroyed - :type: int """ if hasattr(self, "_handle"): @@ -53,10 +56,10 @@ class AutoHandle(object): class System(object): - """Represent the current windows system python is running on""" + """Represent the current ``Windows`` system ``Python`` is running on""" - network = windows.network.Network() - registry = windows.registry.Registry() + network = windows.network.Network() # Object of class :class:`windows.network.Network` + registry = windows.registry.Registry() # Object of class :class:`windows.registry.Registry` @property def processes(self): @@ -80,7 +83,7 @@ class System(object): def bitness(self): """The bitness of the system - :type: int -- 32 or 64 + :type: :class:`int` -- 32 or 64 """ if os.environ["PROCESSOR_ARCHITECTURE"].lower() != "x86":