From b459e291c4db305414f3b73bce359e6aea44613c Mon Sep 17 00:00:00 2001 From: Clement Rouault Date: Thu, 7 Jan 2016 19:26:40 +0100 Subject: [PATCH] More doc + regitry PyPhKey.get() --- README.md | 41 +++++++--------- TODO | 2 +- ctypes_generation/windef.txt | 19 ++++++- doc/source/com.rst | 12 +++++ doc/source/conf.py | 2 +- doc/source/iat_hook.rst | 6 +-- doc/source/index.rst | 5 +- doc/source/internals.rst | 14 +++--- doc/source/native_exec.rst | 20 +++++--- doc/source/network.rst | 8 ++- doc/source/process.rst | 20 +++++++- doc/source/registry.rst | 22 ++++++++- doc/source/sample.rst | 41 +++++++++++++++- doc/source/utils.rst | 6 +-- doc/source/various.rst | 7 +-- doc/source/windows.rst | 6 ++- doc/source/winproxy.rst | 14 +++--- doc/source/wip.rst | 4 +- samples/iat_hook.py | 4 +- samples/network.py | 9 +--- samples/registry.py | 18 +++---- samples/remote_calc.py | 8 +-- windows/generated_def/windef.py | 16 +++++- windows/native_exec/cpuid.py | 12 ++--- windows/native_exec/simple_x64.py | 4 +- windows/native_exec/simple_x86.py | 8 +-- windows/network.py | 6 +-- windows/pe_parse.py | 16 ++++-- windows/registry.py | 35 ++++++++++--- windows/syswow64.py | 1 - windows/winobject.py | 82 ++++++++++++++++++------------- windows/winproxy.py | 2 +- 32 files changed, 313 insertions(+), 157 deletions(-) create mode 100644 doc/source/com.rst diff --git a/README.md b/README.md index 1970b0a..b965ff9 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # PythonForWindows -PythonForWindows is a base of code aimed to make interaction with Windows (on X86/X64) easier (with both 32 and 64 bits Python). -It's goal is to offer abstractions around some of the OS features in a (I hope) pythonic way. +PythonForWindows is a base of code aimed to make interaction with `Windows` (on X86/X64) easier (with both 32 and 64 bits Python). +Its goal is to offer abstractions around some of the OS features in a (I hope) pythonic way. It also tries to make the barrier between python and native execution thiner in both ways. -There is no external dependencies but it relies heavily on the ctypes modules. +There is no external dependencies but it relies heavily on the `ctypes` modules. -Some of this code is clean (IMHO) and some parts are just a wreck that work for now. -Let say that the codebase evolves with my needs and my curiosity. +Some of this code is clean (IMHO) and some parts are just a wreck that works for now. +Let's say that the codebase evolves with my needs and my curiosity. You can find some examples of code in the `samples/` directory. @@ -15,48 +15,43 @@ You can find some examples of code in the `samples/` directory. ### Processes / Threads -PythonForWindows offer a object oriented around processes of the system and allow you to: +PythonForWindows offers objects around processes and allows you to: - Retrieve basic process informations (pid, name, ppid, bitness, ...) - - Perform basic interprocess operation (alloc, create thread, read/write memory) + - Perform basic interprocess operation (allocation, create thread, read/write memory) - Explore the PEB (Process Environment Block) - - Execute native and python code in the context of the process. - -I try by best to make those features available for every cross-bitness processes (32 <-> 64 in both ways). -This involve relying on non-documented Windows function/behaviour and also injecting code in the 64bits world of a Syswow64 process. + - Execute `native` and `Python` code in the context of a process. + +I try by best to make those features available for every cross-bitness processes (`32 <-> 64` in both ways). +This involve relying on non-documented `Windows` function/behaviour and also injecting code in the 64bits world of a `Syswow64` process. All those operations are also available for the `current_process`. -You can also make some operation of threads (suspend/resume/wait/get( or set) context/ kill) +You can also make some operation on threads (suspend/resume/wait/get(or set) context/ kill) ### IAT Hook This codebase is born from my need to have IAT hooks implemented in Python. -So the features is present (see `samples/`) +So the features is present (see online documentation) ### Winproxy -A pythonic wrapper around some Windows functions. Arguments name and order are the same, +A wrapper around some Windows functions. Arguments name and order are the same, but some have default values and the functions raise exception on call error (I don't like 'if' around all my call). ### Native execution -To make the barrier beetwen native and python code, -PythonForWindows allows you to create native function callable from Python (thanks ctypes) and also embded +To make the barrier between `native` and `Python` code, +PythonForWindows allows you to create native function callable from Python (thanks `ctypes`) and also embed a simple x86/x64 assembler. -### COM on Python - -Some code to call a COM interface from Python or create a COM object implemented in Python. - - -### Other stuff +## Other stuff Some code are just exploration and need improvement like: - Wintrust - WMI - - Registry access - Exception + - COM diff --git a/TODO b/TODO index e0bf995..21a7c55 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,7 @@ TODO: - Documentation - ProcessMemory object ? (metasm like) - - Extend Registry feature (write + read 1 key) + - Extend Registry feature (write) FIXME: - WMI diff --git a/ctypes_generation/windef.txt b/ctypes_generation/windef.txt index 901b335..9562b28 100644 --- a/ctypes_generation/windef.txt +++ b/ctypes_generation/windef.txt @@ -538,4 +538,21 @@ #define DEBUG_EVENT_CHANGE_SYMBOL_STATE 0x00001000 #define WOW64_MAXIMUM_SUPPORTED_EXTENSION 512 -#define WOW64_SIZE_OF_80387_REGISTERS 80 \ No newline at end of file +#define WOW64_SIZE_OF_80387_REGISTERS 80 + + +#define REG_NONE ( 0 ) +#define REG_SZ ( 1 ) +#define REG_EXPAND_SZ ( 2 ) + +#define REG_BINARY ( 3 ) +#define REG_DWORD ( 4 ) +#define REG_DWORD_LITTLE_ENDIAN ( 4 ) +#define REG_DWORD_BIG_ENDIAN ( 5 ) +#define REG_LINK ( 6 ) +#define REG_MULTI_SZ ( 7 ) +#define REG_RESOURCE_LIST ( 8 ) +#define REG_FULL_RESOURCE_DESCRIPTOR ( 9 ) +#define REG_RESOURCE_REQUIREMENTS_LIST ( 10 ) +#define REG_QWORD ( 11 ) +#define REG_QWORD_LITTLE_ENDIAN ( 11 ) diff --git a/doc/source/com.rst b/doc/source/com.rst new file mode 100644 index 0000000..d26e9f8 --- /dev/null +++ b/doc/source/com.rst @@ -0,0 +1,12 @@ +COM - Component Object Model +"""""""""""""""""""""""""""" + +A module to call ``COM`` interfaces from `Python` or +``COM`` vtable in python. + +This code is only used in :mod:`windows.wmi`. +The ability to create ``COM`` vtable is used in LKD + +.. note:: + + TODO: link to LKD code \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index 977638b..abf1b4a 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -43,7 +43,7 @@ extensions = [ 'sphinx.ext.autodoc', ] -autodoc_default_flags = ['show-inheritance', 'inherited-members'] +autodoc_default_flags = ['inherited-members'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/source/iat_hook.rst b/doc/source/iat_hook.rst index d891844..2f98d42 100644 --- a/doc/source/iat_hook.rst +++ b/doc/source/iat_hook.rst @@ -5,8 +5,8 @@ IAT hooking See sample :ref:`sample_iat_hook` -Put a IAT hook -'''''''''''''' +Putting an IAT hook +''''''''''''''''''' To setup your IAT hook you just need: @@ -71,7 +71,7 @@ A hook callback must also embed some :ref:`Type Information ` Callback type information -------------------------- -In order make the magic behind Python Hook Callback, :mod:`ctypes` need to have type information about the API parameters. +In order make the magic behind hook callback, :mod:`ctypes` needs 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. diff --git a/doc/source/index.rst b/doc/source/index.rst index 1d8170c..19e2b7d 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -13,10 +13,10 @@ Contents: :numbered: windows.rst + various.rst + native_exec.rst winproxy.rst utils.rst - native_exec.rst - various.rst iat_hook.rst wip.rst internals.rst @@ -28,4 +28,5 @@ Indices and tables * :ref:`modindex` * :ref:`search` +* :ref:`genindex` diff --git a/doc/source/internals.rst b/doc/source/internals.rst index 6c3cf84..24b13c9 100644 --- a/doc/source/internals.rst +++ b/doc/source/internals.rst @@ -17,13 +17,13 @@ So I made a wrapper around :mod:`ctypes` that is able to do two things: This is done by replacing the ``c_void_p``/``c_char_p`` by ``DWORD`` or ``QWORD`` and rewriting a wrapper around the :mod:`ctypes` ``POINTER`` and other stuff. - I might not works for every structure by i didn't have any problem for now. + It might not works for every structure by i didn't have any problem for now. - Read the memory in another process For this one I rewrote a class that use the standard :mod:`ctypes` structure - offset-size calcultation, extract those information when asked for a field and read it from the target process. - We just need to take care of special cases: ``POINTER`` / ``ARRAY`` / ``STRING`` + offset-size calculation, extracts those information when asked for a field and read it from the target process. + We just need to take care of special cases: ``POINTER`` / ``ARRAY`` / ``STRING`` / .. We also need to be carreful about the inheritance, we need to inherit from "hidden" :class:`ctypes` classes to keep the magic working. @@ -85,7 +85,7 @@ With these information we are able to know * The name of the API * The number of arguments -With that I generate the correct x64 stub (using :mod:`windows.native_exec.simple_x64`). With the function: +Then I generate the correct x64 stub (using :mod:`windows.native_exec.simple_x64`) with the function: .. function:: generate_syswow64_call @@ -100,10 +100,10 @@ For now I allocate a buffer where a python wrapper copy the parameters and the x The final result is a ``Python`` function like the one in :mod:`windows.winproxy` * It copies the arguments in the buffer - * Jump on the 32->64 stub + * Jumps on the 32->64 stub * X64 bits code retrieves the arguments in the buffer and setup the registers and the stack for the call - * Call the API - * Return to 32bits mode. + * Calls the API + * Returns to 32bits mode. .. class:: Syswow64ApiProxy diff --git a/doc/source/native_exec.rst b/doc/source/native_exec.rst index 9630c0f..77daec5 100644 --- a/doc/source/native_exec.rst +++ b/doc/source/native_exec.rst @@ -4,10 +4,10 @@ ************************************************ -The :mod:`windows.native_exec` allows to create `Python` functions calling native code. -it also provide a simple assembler for x86 and x64. +:mod:`windows.native_exec` allows to create `Python` functions calling native code. +it also provides a simple assembler for x86 and x64. -The :mod:`windows.native_exec` provides those functions: +:mod:`windows.native_exec` provides those functions: .. autofunction:: windows.native_exec.create_function @@ -47,7 +47,7 @@ Demo:: The :mod:`windows.native_exec.simple_x86` module allows to create simple x86 code. Its features are: - * Forward - Backward jump (using label) + * Forward - Backward jump (using labels) * Non-string interface for conditional/context dependent generation @@ -59,12 +59,13 @@ The assembler instructions are `Python` object that may accept arguments represe the mnemonic operands. These parameters can be of type: - * str (register) - * int (int) - * mem_access (memory access) + * :class:`str` (register) + * :class:`int` (int) + * :class:`mem_access` (memory access) .. autoclass:: windows.native_exec.simple_x86.mem_access :members: prefix, base, index, scale, disp + :exclude-members: count The :class:`mem_access` object can be created: * By hand @@ -72,6 +73,7 @@ The :class:`mem_access` object can be created: * Using :func:`mem` .. autofunction:: windows.native_exec.simple_x86.create_displacement +.. autofunction:: windows.native_exec.simple_x86.deref .. autofunction:: windows.native_exec.simple_x86.mem Instruction assembling:: @@ -116,6 +118,7 @@ Another example from a project:: INPUT_BUFFER_PORT = x86.mem('[ECX + 4]') INPUT_BUFFER_VALUE = x86.mem('[ECX + 8]') + out_ioctl = x86.MultipleInstr() out_ioctl += x86.Cmp(IO_STACK_INPUT_BUFFER_LEN, 0xc) # size indicator / port / value out_ioctl += x86.Jnz(":FAIL") out_ioctl += x86.Mov('ECX', IO_STACK_INPUT_BUFFER) @@ -140,6 +143,9 @@ Another example from a project:: out_ioctl += x86.Mov('EAX', 0x0C000000D) out_ioctl += x86.Ret() + out_ioctl.get_code() + '\x81~\x08\x0c\x00\x00\x00u&\x8bN\x10\x8bQ\x04\x8bA\x08\x8b\t\x81\xf9\x01\x00\x00\x00u\x03\xee\xeb\r\x81\xf9\x02\x00\x00\x00u\x04f\xef\xeb\x01\xef1\xc0\xc3\xc7\xc0\r\x00\x00\xc0\xc3' + :mod:`windows.native_exec.simple_x64` -- X64 Assembler """""""""""""""""""""""""""""""""""""""""""""""""""""" diff --git a/doc/source/network.rst b/doc/source/network.rst index abdbef4..a9ca9c5 100644 --- a/doc/source/network.rst +++ b/doc/source/network.rst @@ -8,14 +8,12 @@ Network See sample :ref:`sample_network_exploration` -:class:`Network` class -"""""""""""""""""""""" - .. autoclass:: windows.network.Network -Connection classes -"""""""""""""""""" +Connections +""""""""""" .. autoclass:: windows.network.TCP4Connection + .. autoclass:: windows.network.TCP6Connection \ No newline at end of file diff --git a/doc/source/process.rst b/doc/source/process.rst index 0f9ab37..610f8d4 100644 --- a/doc/source/process.rst +++ b/doc/source/process.rst @@ -12,6 +12,7 @@ CurrentProcess .. autoclass:: CurrentProcess :members: + :show-inheritance: :inherited-members: CurrentThread @@ -19,6 +20,7 @@ CurrentThread .. autoclass:: CurrentThread :members: + :show-inheritance: :inherited-members: WinProcess @@ -30,6 +32,7 @@ WinProcess .. autoclass:: WinProcess :members: + :show-inheritance: :inherited-members: @@ -38,11 +41,13 @@ WinThread .. autoclass:: WinThread :members: + :show-inheritance: :inherited-members: .. autoclass:: DeadThread :members: + :show-inheritance: :inherited-members: @@ -56,17 +61,23 @@ The :class:`PEB` is accessible via ``process.peb`` and is of type :class:`PEB`. See sample :ref:`sample_peb_exploration` +PEB +''' + .. autoclass:: PEB :members: :inherited-members: .. autoclass:: WinUnicodeString +LoadedModule +'''''''''''' + .. autoclass:: LoadedModule -PEFile -"""""" +PEFile - Parsing loaded PE +"""""""""""""""""""""""""" :mod:`windows.pe_parse` ''''''''''''''''''''''' @@ -75,8 +86,13 @@ PEFile .. autofunction:: windows.pe_parse.GetPEFile +PEFile +^^^^^^ + .. autoclass:: PEFile +IATEntry +^^^^^^^^ .. autoclass:: IATEntry diff --git a/doc/source/registry.rst b/doc/source/registry.rst index 28e340b..62d7ffa 100644 --- a/doc/source/registry.rst +++ b/doc/source/registry.rst @@ -3,8 +3,28 @@ Registry .. module:: windows.registry +.. note:: + + See sample :ref:`sample_registry` + +Registry +"""""""" .. autoclass:: Registry :special-members: __getitem__ -.. autoclass:: PyHKey \ No newline at end of file + +PyHKey +"""""" + +.. autoclass:: PyHKey + + .. function:: __getitem__(name) + + Alias for :func:`open_subkey` + +KeyValue +"""""""" + +.. autoclass:: KeyValue + :exclude-members: count, index \ No newline at end of file diff --git a/doc/source/sample.rst b/doc/source/sample.rst index e6f90a2..4061284 100644 --- a/doc/source/sample.rst +++ b/doc/source/sample.rst @@ -148,4 +148,43 @@ Output:: Traceback (most recent call last): File ".\network.py", line 45, in s.send("LAIT") - socket.error: [Errno 10054] An existing connection was forcibly closed by the remote host \ No newline at end of file + socket.error: [Errno 10054] An existing connection was forcibly closed by the remote host + + +.. _sample_registry: + +:class:`Registry` +""""""""""""""""" + +.. literalinclude:: ..\..\samples\registry.py + +Output:: + + (cmd λ) python.exe registry.py + Registry is <> + HKEY_CURRENT_USER is <> + HKEY_CURRENT_USER subkeys names are: + ['AppEvents', + 'AppXBackupContentType', + 'Console', + 'Control Panel', + 'Environment', + 'EUDC', + 'Identities', + 'Keyboard Layout', + 'Network', + 'Printers', + 'Software', + 'System', + 'Volatile Environment'] + Opening 'Software' in HKEY_CURRENT_USER: + We can also open it in one access: + Looking at CurrentVersion + Key is + values are: + [KeyValue(name='SoftwareType', value=u'System', type=1), + KeyValue(name='RegisteredOwner', value=u'hakril', type=1), + KeyValue(name='InstallDate', value=0, type=4), + ... + KeyValue(name='PathName', value=u'C:\\Windows', type=1)] + registered owner = diff --git a/doc/source/utils.rst b/doc/source/utils.rst index a32d105..c4b7806 100644 --- a/doc/source/utils.rst +++ b/doc/source/utils.rst @@ -1,4 +1,4 @@ -``windows.utils`` -- Pythonic Windows Utilities +``windows.utils`` -- Windows Utilities *********************************************** .. module:: windows.utils @@ -14,13 +14,13 @@ VirtualProtected .. autoclass:: windows.utils.VirtualProtected :no-show-inheritance: - + DisableWow64FsRedirection ''''''''''''''''''''''''' .. autoclass:: windows.utils.DisableWow64FsRedirection :no-show-inheritance: - + Helper functions """""""""""""""" diff --git a/doc/source/various.rst b/doc/source/various.rst index a23d87b..8777621 100644 --- a/doc/source/various.rst +++ b/doc/source/various.rst @@ -2,13 +2,14 @@ The :mod:`windows` objects ========================== Through the :ref:`system ` object many classes representing various `Windows` -part are accessible. +parts are accessible. This sections describes them by group of relation. .. toctree:: - :maxdepth: 2 + :maxdepth: 3 process.rst registry.rst - network.rst \ No newline at end of file + network.rst + com.rst \ No newline at end of file diff --git a/doc/source/windows.rst b/doc/source/windows.rst index c7f8b5f..fdd29e7 100644 --- a/doc/source/windows.rst +++ b/doc/source/windows.rst @@ -2,7 +2,9 @@ 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. + +This module exports some objects representing the current state of the system. +It also offers some submodules aimed to help the interfacing with ``Windows`` and native code execution. The defaults objects accessible in ``windows`` are: * ``system`` of type :class:`windows.winobject.System` @@ -19,6 +21,8 @@ The submodules that you might use by themself are: The ``system`` object """"""""""""""""""""" +.. currentmodule:: windows.winobject + .. autoclass:: windows.winobject.System :no-show-inheritance: diff --git a/doc/source/winproxy.rst b/doc/source/winproxy.rst index 21a99d2..a0706f2 100644 --- a/doc/source/winproxy.rst +++ b/doc/source/winproxy.rst @@ -3,15 +3,15 @@ .. module:: windows.winproxy -The :mod:`windows.winproxy` module tries to be a pythontic wrapper around windows API of various DLL. -It also hevily relies on :mod:`ctypes`. +:mod:`windows.winproxy` tries to be a pythontic wrapper around windows API of various DLL. +It also heavily relies on :mod:`ctypes` and :mod:`windows.generated_def.winfuncs` -Here are the thing to know about :mod:`windows.winproxy` - * All of this is based on :mod:`windows.generated_def.winfunc` +Here are the things to know about :mod:`windows.winproxy` + * All of this is based on :mod:`windows.generated_def.winfuncs` * DLL is loaded the first time an API of it is called * All parameters can be passed by ordinal or keyword - * The call with fail if an argument with default value ``NeededParamater`` have be called without another value. - * The call will raise a subclasse of :class:`WindowsError` if it fail. + * The call will fail if an argument with default value ``NeededParamater`` have been called without another value. + * The call will raise a subclass of :class:`WindowsError` if it fails. * Some functions are 'transparent proxy' it means that all parameters are mandatory Example: ``VirtualAlloc`` @@ -60,7 +60,7 @@ Calling it TypeError: VirtualAlloc: Missing Mandatory parameter """ - # Error raise exception + # Error raises exception windows.winproxy.VirtualAlloc(dwSize=0xffffffff) """ Traceback (most recent call last): diff --git a/doc/source/wip.rst b/doc/source/wip.rst index 479fd7b..6d4e610 100644 --- a/doc/source/wip.rst +++ b/doc/source/wip.rst @@ -13,13 +13,11 @@ Should it juste be part of :mod:`windows.utils` ? .. autofunction:: windows.wintrust.check_signature +.. module:: windows.wmi WMI -- WMI request """""""""""""""""" Unstable code: not fully tested, ugly COM initialisation - -.. module:: windows.wmi - .. autoclass:: windows.wmi.WmiRequester \ No newline at end of file diff --git a/samples/iat_hook.py b/samples/iat_hook.py index 9a40802..1074cf6 100644 --- a/samples/iat_hook.py +++ b/samples/iat_hook.py @@ -13,7 +13,7 @@ import windows @windows.hooks.RegOpenKeyExACallback def open_reg_hook(hKey, lpSubKey, ulOptions, samDesired, phkResult, real_function): print(" Hook called | hKey = {0} | lpSubKey = <{1}>".format(hex(hKey), lpSubKey.value)) - # Out hook can choose to call the real_function or not + # Our hook can choose to call the real_function or not if "SECRET" in lpSubKey.value: print(" Secret key asked, returning magic handle 0x12345678") # We must respect the hooked method return-value interface @@ -39,11 +39,9 @@ adv_imports = pythondll_module.pe.imports['advapi32.dll'] RegOpenKeyExA_iat = [n for n in adv_imports if n.name == "RegOpenKeyExA"][0] # Setup our hook - RegOpenKeyExA_iat.set_hook(open_reg_hook) # Use python native module _winreg that call 'RegOpenKeyExA' - print("Asking for ") v = _winreg.OpenKey(1234567, "MY_SECRET_KEY") print("Result = " + hex(v.handle)) diff --git a/samples/network.py b/samples/network.py index 5ebc94b..5eeab44 100644 --- a/samples/network.py +++ b/samples/network.py @@ -10,30 +10,23 @@ if not windows.utils.check_is_elevated(): print("!!! Demo will fail because closing a connection require elevated process !!!") print("Working on ipv4") - conns = windows.system.network.ipv4 -print("== Listening ==") +print("== Listening ==") print("Some listening connections: {0}".format([c for c in conns if not c.established][:3])) print("Listening ports are : {0}".format([c.local_port for c in conns if not c.established])) - print("== Established ==") - print("Some established connections: {0}".format([c for c in conns if c.established][:3])) TARGET_HOST = "localhost" TARGET_PORT = 80 - print("== connection to {0}:{1} ==".format(TARGET_HOST, TARGET_PORT)) - s = socket.create_connection((TARGET_HOST, TARGET_PORT)) - our_connection = [c for c in windows.system.network.ipv4 if c.established and c.remote_port == TARGET_PORT and c.remote_addr == s.getpeername()[0]] print("Our connection is {0}".format(our_connection)) - print("Sending YOP") s.send("YOP") print("Closing socket") diff --git a/samples/registry.py b/samples/registry.py index c0cde1f..4a3608a 100644 --- a/samples/registry.py +++ b/samples/registry.py @@ -11,18 +11,18 @@ print("Registry is <{0}>".format(registry)) current_user = registry["HKEY_CURRENT_USER"] print("HKEY_CURRENT_USER is <{0}>".format(current_user)) subkeys_name = [s.name for s in current_user.subkeys] -print("HKEY_CURRENT_USER subkeys names are is <{0}>".format(pprint.pprint(subkeys_name))) +print("HKEY_CURRENT_USER subkeys names are:") +pprint.pprint(subkeys_name) print("Opening 'Software' in HKEY_CURRENT_USER: {0}".format(current_user["Software"])) - print("We can also open it in one access: {0}".format(registry[r"HKEY_CURRENT_USER\Sofware"])) +print("Looking at CurrentVersion") -print("Looking for the JIT Debugger") +windows_info = registry["HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion"] +print("Key is {0}".format(windows_info)) -jit_debug_key = registry["HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug"] +print("values are:") +pprint.pprint(windows_info.values) -print("Key is {0}".format(jit_debug_key)) - -print("values are: {0}".format(pprint.pprint(jit_debug_key.values))) - -print() \ No newline at end of file +registered_owner = windows_info.get("RegisteredOwner") +print("registered owner = <{0}>".format(registered_owner)) \ No newline at end of file diff --git a/samples/remote_calc.py b/samples/remote_calc.py index a33b62b..77c85f9 100644 --- a/samples/remote_calc.py +++ b/samples/remote_calc.py @@ -40,7 +40,7 @@ print("Reading allocated memory : <{0}>".format(repr(calc.read_memory(addr, 20)) # Remote Execution -print("Execution some native code in our calc (write 0x424242 at allocated address + return 0x1337") +print("Execution some native code in our calc (write 0x424242 at allocated address + return 0x1337)") if calc.bitness == 32: # Let's generate some native code @@ -54,7 +54,7 @@ else: code += x64.Mov(x64.mem("[RAX]"), 0x42424242) code += x64.Mov("RAX", 0x1337) code += x64.Ret() - + print("Executing native code !") t = calc.execute(code.get_code()) t.wait() @@ -80,10 +80,10 @@ try: except windows.injection.RemotePythonError as e: print("Remote ERROR !") print(e) - + print("That's all ! killing the calc") calc.exit() - + diff --git a/windows/generated_def/windef.py b/windows/generated_def/windef.py index 5f0fb57..48e6cec 100644 --- a/windows/generated_def/windef.py +++ b/windows/generated_def/windef.py @@ -490,4 +490,18 @@ DEBUG_EVENT_CHANGE_DEBUGGEE_STATE = Flag("DEBUG_EVENT_CHANGE_DEBUGGEE_STATE", 0x DEBUG_EVENT_CHANGE_ENGINE_STATE = Flag("DEBUG_EVENT_CHANGE_ENGINE_STATE", 0x00000800) DEBUG_EVENT_CHANGE_SYMBOL_STATE = Flag("DEBUG_EVENT_CHANGE_SYMBOL_STATE", 0x00001000) WOW64_MAXIMUM_SUPPORTED_EXTENSION = Flag("WOW64_MAXIMUM_SUPPORTED_EXTENSION", 512) -WOW64_SIZE_OF_80387_REGISTERS = Flag("WOW64_SIZE_OF_80387_REGISTERS", 80) \ No newline at end of file +WOW64_SIZE_OF_80387_REGISTERS = Flag("WOW64_SIZE_OF_80387_REGISTERS", 80) +REG_NONE = Flag("REG_NONE", ( 0 )) +REG_SZ = Flag("REG_SZ", ( 1 )) +REG_EXPAND_SZ = Flag("REG_EXPAND_SZ", ( 2 )) +REG_BINARY = Flag("REG_BINARY", ( 3 )) +REG_DWORD = Flag("REG_DWORD", ( 4 )) +REG_DWORD_LITTLE_ENDIAN = Flag("REG_DWORD_LITTLE_ENDIAN", ( 4 )) +REG_DWORD_BIG_ENDIAN = Flag("REG_DWORD_BIG_ENDIAN", ( 5 )) +REG_LINK = Flag("REG_LINK", ( 6 )) +REG_MULTI_SZ = Flag("REG_MULTI_SZ", ( 7 )) +REG_RESOURCE_LIST = Flag("REG_RESOURCE_LIST", ( 8 )) +REG_FULL_RESOURCE_DESCRIPTOR = Flag("REG_FULL_RESOURCE_DESCRIPTOR", ( 9 )) +REG_RESOURCE_REQUIREMENTS_LIST = Flag("REG_RESOURCE_REQUIREMENTS_LIST", ( 10 )) +REG_QWORD = Flag("REG_QWORD", ( 11 )) +REG_QWORD_LITTLE_ENDIAN = Flag("REG_QWORD_LITTLE_ENDIAN", ( 11 )) \ No newline at end of file diff --git a/windows/native_exec/cpuid.py b/windows/native_exec/cpuid.py index b32abbf..b08cd88 100644 --- a/windows/native_exec/cpuid.py +++ b/windows/native_exec/cpuid.py @@ -8,7 +8,7 @@ from windows.generated_def.winstructs import * def _bitness(): - """Return 32 or 64""" + """Returns 32 or 64""" import platform bits = platform.architecture()[0] return int(bits[:2]) @@ -82,7 +82,7 @@ do_cpuid64 = native_function.create_function(cpuid64_code.get_code(), [DWORD, DW def x86_cpuid(req): - """Perform a CPUID in 32bits mode + """Performs a CPUID in 32bits mode :rtype: :class:`X86CpuidResult` """ @@ -92,7 +92,7 @@ def x86_cpuid(req): def x64_cpuid(req): - """Perform a CPUID in 64bits mode + """Performs a CPUID in 64bits mode :rtype: :class:`X86CpuidResult` """ @@ -108,7 +108,7 @@ else: _do_cpuid = x64_cpuid def do_cpuid(req): - """Perform a CPUID for the current process bitness + """Performs a CPUID for the current process bitness :rtype: :class:`X86CpuidResult` """ @@ -116,7 +116,7 @@ def do_cpuid(req): def get_vendor_id(): - """Extract the VendorId string from CPUID + """Extracts the VendorId string from CPUID :rtype: :class:`str` """ @@ -136,7 +136,7 @@ def is_amd_proc(): def get_proc_family_model(): - """Extract the family and model based on vendorId + """Extracts the family and model based on vendorId :rtype: (ComputedFamily, ComputedModel) """ diff --git a/windows/native_exec/simple_x64.py b/windows/native_exec/simple_x64.py index 556b2c5..442d538 100644 --- a/windows/native_exec/simple_x64.py +++ b/windows/native_exec/simple_x64.py @@ -1059,7 +1059,7 @@ if in_IDA: def test_code(): s = MultipleInstr() s += Mov('r8', 'r14') - s += Label(':SUCE') + s += Label(':A_POLITICALLY_CORRECT_LABEL_NAME') s += Jnz(':END') s += Add('r14', 0x12345678) s += Dec('r9') @@ -1068,7 +1068,7 @@ if in_IDA: s += Mov('r8', 'rdx') s += Jnz(':END') s += Mov('r8', 'rdx') - s += Jnz(':SUCE') + s += Jnz(':A_POLITICALLY_CORRECT_LABEL_NAME') s += Mov('r9', 'r10') s += Label(':END') s += Ret() diff --git a/windows/native_exec/simple_x86.py b/windows/native_exec/simple_x86.py index c6eada9..3b4de56 100644 --- a/windows/native_exec/simple_x86.py +++ b/windows/native_exec/simple_x86.py @@ -136,7 +136,7 @@ class X86(object): def create_displacement(base=None, index=None, scale=None, disp=0, prefix=None): - """Create an X86 memory access description""" + """Creates a X86 memory access description""" if index is not None and scale is None: scale = 1 if scale and index is None: @@ -147,12 +147,14 @@ def create_displacement(base=None, index=None, scale=None, disp=0, prefix=None): def deref(disp): + """Create a memory access for an immediate value ``Ex: [0x42424242]``""" return create_displacement(disp=disp) def mem(data): - """Parse a memory access string of format [EXPR] or seg:[EXPR] - EXPR may describe: BASE | INDEX * SCALE | DISPLACEMENT or any combinaison (in this order) + """Parse a memory access string of format ``[EXPR]`` or ``seg:[EXPR]`` + + ``EXPR`` may describe: ``BASE | INDEX * SCALE | DISPLACEMENT`` or any combinaison (in this order) """ if not isinstance(data, str): raise TypeError("mem need a string to parse") diff --git a/windows/network.py b/windows/network.py index 0a5b477..4440277 100644 --- a/windows/network.py +++ b/windows/network.py @@ -58,7 +58,7 @@ class TCP4Connection(MIB_TCPROW_OWNER_PID): @property def remote_host(self): """Identification of the remote hostname. - Equals ``remote_addr`` if resolution fail + Equals ``remote_addr`` if the resolution fails :type: :class:`str` or :class:`int` """ @@ -124,12 +124,12 @@ class TCP6Connection(MIB_TCP6ROW_OWNER_PID): @property def remote_proto(self): - """Equals to self.remote_port for Ipv6""" + """Equals to ``self.remote_port`` for Ipv6""" return self.remote_port @property def remote_host(self): - """Equals to self.remote_addr for Ipv6""" + """Equals to ``self.remote_addr`` for Ipv6""" return self.remote_addr def close(self): diff --git a/windows/pe_parse.py b/windows/pe_parse.py index 78fc735..2493c9e 100644 --- a/windows/pe_parse.py +++ b/windows/pe_parse.py @@ -48,10 +48,13 @@ def get_structure_transformer_for_target(target): def GetPEFile(baseaddr, target=None): - """Return a :class:`PEFile` to explore a PE loaded at `baseaddr` in process `target`. - If target is ``None`` it refers the curent process + """Returns a :class:`PEFile` to explore a PE loaded at `baseaddr` in process `target`. :rtype: :class:`PEFile` + + .. note:: + + If target is ``None`` it refers to the curent process """ proc_bitness = windows.current_process.bitness if target is None: @@ -106,7 +109,7 @@ def GetPEFile(baseaddr, target=None): class IATEntry(ctypes.Structure): """Represent an entry in the IAT of a module - | Can be used to get resolved value and setup hook + Can be used to get resolved value and setup hook """ _fields_ = [ ("value", PVOID)] @@ -136,8 +139,13 @@ def GetPEFile(baseaddr, target=None): see :ref:`hook_protocol` :rtype: :class:`windows.hooks.IATHook` - """ + .. warning:: + + This works only for PEFile with the current process as target. + """ + if target is not None: + raise NotImplementedError("Setting hook in remote process (use python code injection)") hook = hooks.IATHook(self, callback, types) self.hook = hook diff --git a/windows/registry.py b/windows/registry.py index e4c6c57..5c02344 100644 --- a/windows/registry.py +++ b/windows/registry.py @@ -1,7 +1,10 @@ import _winreg -import windows import itertools -import collections +from collections import namedtuple + +import windows +from windows.generated_def.windef import KEY_READ + class ExpectWindowsError(object): @@ -14,11 +17,14 @@ class ExpectWindowsError(object): def __exit__(self, etype, e, tb): return (etype == WindowsError and e.winerror == self.errornumber) -KeyValue = collections.namedtuple("KeyValue", ["name", "value", "type"]) + +KeyValue = namedtuple("KeyValue", ["name", "value", "type"]) +"""A registry value (name, value, type)""" + class PyHKey(object): """A windows registry key""" - def __init__(self, surkey, name, sam=_winreg.KEY_READ): + def __init__(self, surkey, name, sam=KEY_READ): self.surkey = surkey self.name = name self.fullname = self.surkey.fullname + "\\" + self.name if self.name else self.surkey.name @@ -40,7 +46,9 @@ class PyHKey(object): @property def subkeys(self): - """The subkeys of the registry key""" + """The subkeys of the registry key + + :type: [:class:`PyHKey`] - A list of keys""" res = [] with ExpectWindowsError(259): for i in itertools.count(): @@ -49,14 +57,28 @@ class PyHKey(object): @property def values(self): - """The values of the registry key""" + """The values of the registry key + + :type: [:class:`KeyValue`] - A list of values""" res = [] with ExpectWindowsError(259): for i in itertools.count(): res.append(_winreg.EnumValue(self.phkey, i)) return [KeyValue(*r) for r in res] + def get(self, value_name): + """Retrieves the value ``value_name`` + + :rtype: :class:`KeyValue` + """ + data = _winreg.QueryValueEx(self.phkey, value_name) + return KeyValue(value_name, data[0], data[1]) + def open_subkey(self, name): + """Open the subkey ``name`` + + :rtype: :class:`PyHKey` + """ return PyHKey(self, name, self.sam) def reopen(self, new_sam): @@ -108,4 +130,3 @@ class Registry(object): if base_name not in self.registry_base_keys: raise ValueError("Unknow registry base key <{0}>".format(base_name)) return self.registry_base_keys[base_name][subkey] - diff --git a/windows/syswow64.py b/windows/syswow64.py index 2af15e9..9cb59fd 100644 --- a/windows/syswow64.py +++ b/windows/syswow64.py @@ -202,7 +202,6 @@ get_syswow_ntdll_exports.value = None class Syswow64ApiProxy(object): - APIDLL = None """Create a python wrapper around a function""" def __init__(self, winproxy_function): self.winproxy_function = winproxy_function diff --git a/windows/winobject.py b/windows/winobject.py index 2c66526..72a95c2 100644 --- a/windows/winobject.py +++ b/windows/winobject.py @@ -119,7 +119,9 @@ class WinThread(THREADENTRY32, AutoHandle): """Represent a thread """ @utils.fixedpropety def tid(self): - """Thread ID""" + """Thread ID + + :type: :class:`int`""" return self.th32ThreadID @utils.fixedpropety @@ -166,7 +168,7 @@ class WinThread(THREADENTRY32, AutoHandle): @property def start_address(self): """The start address of the thread - + :type: :class:`int` """ if windows.current_process.bitness == 32 and self.owner.bitness == 64: @@ -199,7 +201,7 @@ class WinThread(THREADENTRY32, AutoHandle): @property def is_exit(self): """Is ``True`` if the thread is terminated - + :type: :class:`bool` """ return self.exit_code != STILL_ACTIVE @@ -207,7 +209,7 @@ class WinThread(THREADENTRY32, AutoHandle): @property def exit_code(self): """The exit code of the thread : ``STILL_ACTIVE`` means the process is not dead - + :type: :class:`int` """ res = DWORD() @@ -246,7 +248,7 @@ class DeadThread(AutoHandle): @property def is_exit(self): """Is ``True`` if the thread is terminated - + :type: :class:`bool` """ return self.exit_code != STILL_ACTIVE @@ -254,7 +256,7 @@ class DeadThread(AutoHandle): @property def exit_code(self): """The exit code of the thread : ``STILL_ACTIVE`` means the process is not dead - + :type: :class:`int` """ res = DWORD() @@ -265,7 +267,7 @@ class DeadThread(AutoHandle): class Process(AutoHandle): @utils.fixedpropety def is_wow_64(self): - """Is ``True`` if the process is a SysWow64 process (32bit process on 64bits system). + """``True`` if the process is a SysWow64 process (32bit process on 64bits system). :type: :class:`bool` """ @@ -296,7 +298,7 @@ class Process(AutoHandle): @property def exit_code(self): """The exit code of the process : ``STILL_ACTIVE`` means the process is not dead - + :type: :class:`int` """ res = DWORD() @@ -305,21 +307,24 @@ class Process(AutoHandle): @property def is_exit(self): - """Is ``True`` if the process is terminated - + """``True`` if the process is terminated + :type: :class:`bool` """ return self.exit_code == STILL_ACTIVE def execute(self, code): - """Execute some raw code in the context of the process""" + """Execute some native code in the context of the process + + :return: The return value of the native code + :rtype: :class:`int`""" x = self.virtual_alloc(len(code)) self.write_memory(x, code) return self.create_thread(x, 0) def query_memory(self, addr): - """Query the memory informations about page at ```addr`` - + """Query the memory informations about page at ``addr`` + :rtype: :class:`MEMORY_BASIC_INFORMATION` """ if windows.current_process.bitness == 32 and self.bitness == 64: @@ -340,7 +345,7 @@ class Process(AutoHandle): def memory_state(self): """Yield the memory information for the whole address space of the process - + :yield: :class:`MEMORY_BASIC_INFORMATION` """ addr = 0 @@ -358,7 +363,9 @@ class CurrentThread(AutoHandle): """The current thread""" @utils.fixedpropety def tid(self): - """Thread ID""" + """Thread ID + + :type: :class:`int`""" return winproxy.GetCurrentThreadId() @utils.fixedpropety @@ -380,7 +387,7 @@ class CurrentThread(AutoHandle): return winproxy.ExitThread(code) def wait(self): - """Raise ValueError to prevent deadlock :D""" + """Raise ``ValueError`` to prevent deadlock :D""" raise ValueError("wait() on current thread") @@ -451,9 +458,10 @@ class CurrentProcess(Process): return int(bits[:2]) def virtual_alloc(self, size): - """Allocate memory in the current process + """Allocate memory in the process - :returns: :class:`int` + :return: The address of the allocated memory + :rtype: :class:`int` """ return winproxy.VirtualAlloc(dwSize=size) @@ -464,7 +472,11 @@ class CurrentProcess(Process): return True def read_memory(self, addr, size): - """Read size from addr""" + """Read ``size`` from ``addr`` + + :return: The data read + :rtype: :class:`str` + """ dbgprint('Read CurrentProcess Memory', 'READMEM') buffer = (c_char * size).from_address(addr) return buffer[:] @@ -472,10 +484,7 @@ class CurrentProcess(Process): def create_thread(self, lpStartAddress, lpParameter, dwCreationFlags=0): """Create a new thread - .. note:: - CreateThread https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453%28v=vs.85%29.aspx - - :rtype: :class:`WinThread` or :class:`DeadThread` + :rtype: :class:`WinThread` or :class:`DeadThread` """ handle = winproxy.CreateThread(lpStartAddress=lpStartAddress, lpParameter=lpParameter, dwCreationFlags=dwCreationFlags) return WinThread._from_handle(handle) @@ -485,7 +494,7 @@ class CurrentProcess(Process): return winproxy.ExitProcess(code) def wait(self): - """Raise ValueError to prevent deadlock :D""" + """Raise ``ValueError`` to prevent deadlock :D""" raise ValueError("wait() on current thread") @@ -527,7 +536,8 @@ class WinProcess(PROCESSENTRY32, Process): def virtual_alloc(self, size): """Allocate memory in the process - :rtype: :class:`int` + :return: The address of the allocated memory + :rtype: :class:`int` """ return winproxy.VirtualAllocEx(self.handle, dwSize=size) @@ -544,7 +554,11 @@ class WinProcess(PROCESSENTRY32, Process): return winproxy.ReadProcessMemory(self.handle, addr, lpBuffer=buffer_addr, nSize=size) def read_memory(self, addr, size): - """Read `size` from `addr`""" + """Read ``size`` from ``addr`` + + :return: The data read + :rtype: :class:`str` + """ buffer = ctypes.create_string_buffer(size) self.low_read_memory(addr, ctypes.byref(buffer), size) return buffer[:] @@ -569,14 +583,14 @@ class WinProcess(PROCESSENTRY32, Process): def read_memory_into(self, addr, struct): """Read a :mod:`ctypes` struct from `addr` - + :returns: struct""" self.low_read_memory(addr, ctypes.byref(struct), ctypes.sizeof(struct)) return struct def create_thread(self, addr, param): """Create a remote thread - + :rtype: :class:`WinThread` or :class:`DeadThread` """ if windows.current_process.bitness == 32 and self.bitness == 64: @@ -593,15 +607,15 @@ class WinProcess(PROCESSENTRY32, Process): return self.create_thread(LoadLibrary, x) def execute_python(self, pycode): - """Execute Python code into the remote process. - + """Execute Python code into the remote process. + This function waits for the remote process to end and raises an exception if the remote thread raised one""" return injection.safe_execute_python(self, pycode) def execute_python_unsafe(self, pycode): """Execute Python code into the remote process. - + Unsafe means that no information are returned about the execution of the thread """ return injection.execute_python_code(self, pycode) @@ -609,7 +623,7 @@ class WinProcess(PROCESSENTRY32, Process): @utils.fixedpropety def peb_addr(self): """The address of the PEB - + :type: :class:`int` """ if windows.current_process.bitness == 32 and self.bitness == 64: @@ -636,7 +650,7 @@ class WinProcess(PROCESSENTRY32, Process): @utils.fixedpropety def peb(self): """The PEB of the remote process (see :mod:`remotectypes`) - + :type: :class:`PEB` """ if windows.current_process.bitness == 32 and self.bitness == 64: @@ -692,7 +706,7 @@ class WinUnicodeString(LSA_UNICODE_STRING): """LSA_UNICODE_STRING with a nice `__repr__`""" fields = [f[0] for f in LSA_UNICODE_STRING._fields_] """The fields of the structure""" - + def __repr__(self): return """<{0} "{1}" at {2}>""".format(type(self).__name__, self.Buffer, hex(id(self))) diff --git a/windows/winproxy.py b/windows/winproxy.py index d1b6ba8..738a44f 100644 --- a/windows/winproxy.py +++ b/windows/winproxy.py @@ -573,7 +573,7 @@ def RegGetValueA(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData): @Advapi32Proxy('RegGetValueW', kernel32_zero_check) -def RegGetValueW(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData): +def RegGetValueW(hkey, lpSubKey=None, lpValue=NeededParameter, dwFlags=0, pdwType=None, pvData=None, pcbData=None): return RegGetValueW.ctypes_function(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData)