diff --git a/.gitignore b/.gitignore index 7e99e36..d5b6a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.pyc \ No newline at end of file +*.pyc +doc/build \ No newline at end of file diff --git a/TODO b/TODO index a256216..e0bf995 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,7 @@ TODO: - Documentation - ProcessMemory object ? (metasm like) + - Extend Registry feature (write + read 1 key) FIXME: - WMI diff --git a/ctypes_generation/winstruct.txt b/ctypes_generation/winstruct.txt index 21b8d4f..2daa6af 100644 --- a/ctypes_generation/winstruct.txt +++ b/ctypes_generation/winstruct.txt @@ -1459,4 +1459,14 @@ typedef struct _PROCESS_BASIC_INFORMATION { PVOID Reserved2[2]; ULONG_PTR UniqueProcessId; PVOID Reserved3; -} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; \ No newline at end of file +} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; + +typedef struct _JIT_DEBUG_INFO { + DWORD dwSize; + DWORD dwProcessorArchitecture; + DWORD dwThreadID; + DWORD dwReserved0; + ULONG64 lpExceptionAddress; + ULONG64 lpExceptionRecord; + ULONG64 lpContextRecord; +} JIT_DEBUG_INFO, *LPJIT_DEBUG_INFO; \ No newline at end of file diff --git a/doc/source/conf.py b/doc/source/conf.py index 6d0c527..977638b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -24,6 +24,8 @@ sys.path.append(os.path.abspath(__file__ + "..\..\..\..")) print("Adding <{0}>".format(sys.path[-1])) +os.environ["SPHINX_BUILD"] = "1" + # -- General configuration ------------------------------------------------ diff --git a/doc/source/iat_hook.rst b/doc/source/iat_hook.rst new file mode 100644 index 0000000..d891844 --- /dev/null +++ b/doc/source/iat_hook.rst @@ -0,0 +1,113 @@ +IAT hooking +""""""""""" + +.. note:: + + See sample :ref:`sample_iat_hook` + +Put a IAT hook +'''''''''''''' + +To setup your IAT hook you just need: + + * A callback that respect the :ref:`hook_protocol` + * The :class:`windows.pe_parse.IATEntry` to hook + + +You just need to use the function :func:`windows.pe_parse.IATEntry.set_hook` + +Putting a hook:: + + 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) + + +.. _hook_protocol: + +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 allows 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() + + +A hook callback must also embed some :ref:`Type Information ` + + +.. _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. + +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 + + +:mod:`windows.hooks` +'''''''''''''''''''' + +.. module:: windows.hooks + +.. autoclass:: windows.hooks.Callback + +.. autoclass:: windows.hooks.IATHook \ No newline at end of file diff --git a/doc/source/index.rst b/doc/source/index.rst index bb53f97..1d8170c 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -17,6 +17,9 @@ Contents: utils.rst native_exec.rst various.rst + iat_hook.rst + wip.rst + internals.rst sample.rst diff --git a/doc/source/internals.rst b/doc/source/internals.rst new file mode 100644 index 0000000..6c3cf84 --- /dev/null +++ b/doc/source/internals.rst @@ -0,0 +1,122 @@ +Internals +========= + +Because some horrible hacks of ``PythonForWindows`` are hidden and I wanted to talk about it. + +remotectypes.py +''''''''''''''' + +.. module:: windows.remotectypes + +Performing parsing of PEB / PE in remote process may be painful and i didn't want +to have two versions of all my parsing code. + +So I made a wrapper around :mod:`ctypes` that is able to do two things: + + - Transform a 32bits ctypes structure into a 64bits one and reverse + + 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. + + - 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`` + +We also need to be carreful about the inheritance, we need to inherit from "hidden" +:class:`ctypes` classes to keep the magic working. + + +This module exports the following API: + +.. autofunction:: transform_type_to_remote32bits + +.. autofunction:: transform_type_to_remote64bits + +Both functions return a class that represent the structure in a remote process. +The class.__init__ accept two arguments: + + * ``base_addr``: the address of the object in the remote process + * ``target``: an object with a method ``read_memory`` (so a :class:`windows.winobject.WinProcess` in our case) + +Example ``WinProcess.peb``:: + + def peb(self): + if windows.current_process.bitness == 32 and self.bitness == 64: + return RemotePEB64(self.peb_addr, self) + if windows.current_process.bitness == 64 and self.bitness == 32: + return RemotePEB32(self.peb_addr, self) + return RemotePEB(self.peb_addr, self) + +I am pretty sure that this code does NOT handle all the cases, so it might break some day. + +syswow64.py -- Crossing the heaven gate +''''''''''''''''''''''''''''''''''''''' + +.. module:: windows.syswow64 + +One of my goal with ``PythonForWindows`` is to have some abstraction of the bitness of the processes. +It means being able to work on a ``32bits Python`` or a ``64bits Python``. + +In the case of a 32bits python on a ``64bits`` system (``SysWow64``) it's not trivial to perform operation on +other ``64bits`` processes. For example directly calling :func:`CreateRemoteThread` will not work. + +To be able to perform those operation we must be able to execute code in the ``64bits`` part of our +``SysWow64`` process. + +.. note:: + + TODO link to ``Heaven Gate`` + +For that we need to jump to the 64bits segment of our process, execute some code then return. +To do so, we need to use some ``far jump`` / ``far ret`` with the segments selector ``0x23`` (CS_32bits) and ``0x33`` (CS_64bits). + +The generation of this is quite ugly in my case. +This code is in: + +.. function:: execute_64bits_code_from_syswow + +Once we are able to execute some code in the ``64bits`` part we need to create the code that will call our API (in NTDLL). +To do that, I rely on the type information already present in the function of :mod:`windows.winproxy`. +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: + +.. function:: generate_syswow64_call + +One problem I encountered is that our function must be able to pass values of 64bits, so passing arguments by register is not possible. + +For now I allocate a buffer where a python wrapper copy the parameters and the x64 stub retrieves them from here. + +(It might be possible to do something by creating a WINCFUNC with only ULONG64 parameters). + +.. function:: try_generate_stub_target + +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 + * 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. + +.. class:: Syswow64ApiProxy + +Existing function are: + +.. function:: NtCreateThreadEx_32_to_64 + +.. function:: NtQueryInformationProcess_32_to_64 + +.. function:: NtQueryInformationThread_32_to_64 + +.. function:: NtQueryVirtualMemory_32_to_64 + +.. function:: NtGetContextThread_32_to_64 + + diff --git a/doc/source/native_exec.rst b/doc/source/native_exec.rst index 1a47d9b..9630c0f 100644 --- a/doc/source/native_exec.rst +++ b/doc/source/native_exec.rst @@ -1,14 +1,15 @@ +.. module:: windows.native_exec + ``windows.native_exec`` -- Native Code Execution ************************************************ -.. currentmodule:: windows.native_exec The :mod:`windows.native_exec` allows to create `Python` functions calling native code. it also provide a simple assembler for x86 and x64. The :mod:`windows.native_exec` provides those functions: -.. automodule:: windows.native_exec +.. autofunction:: windows.native_exec.create_function The :mod:`windows.native_exec` also contains some submodules: * :mod:`windows.native_exec.cpuid` diff --git a/doc/source/process.rst b/doc/source/process.rst index a8e1c8f..0f9ab37 100644 --- a/doc/source/process.rst +++ b/doc/source/process.rst @@ -65,8 +65,41 @@ The :class:`PEB` is accessible via ``process.peb`` and is of type :class:`PEB`. .. autoclass:: LoadedModule -.. warning:: +PEFile +"""""" - TODO: pe_parse.PEFile (sorry) but example at :ref:`sample_peb_exploration` +:mod:`windows.pe_parse` +''''''''''''''''''''''' + +.. module:: windows.pe_parse + +.. autofunction:: windows.pe_parse.GetPEFile + +.. autoclass:: PEFile +.. autoclass:: IATEntry + + .. data:: addr + + :class:`int` : Address of the IAT Entry + + .. data:: ord + + :class:`int` : Ordinal of the imported function + + .. data:: name + + :class:`int` : Name of the imported function + + .. data:: value + + :class:`int` : The content (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` \ No newline at end of file diff --git a/doc/source/sample.rst b/doc/source/sample.rst index 892a923..e6f90a2 100644 --- a/doc/source/sample.rst +++ b/doc/source/sample.rst @@ -99,6 +99,31 @@ Output:: Sections: [, , , , ] +.. _sample_iat_hook: + +IAT hooking +""""""""""" + +.. literalinclude:: ..\..\samples\iat_hook.py + +Output:: + + (cmd λ) python iat_hook.py + Asking for + Hook called | hKey = 0x12d687 | lpSubKey = + Secret key asked, returning magic handle 0x12345678 + Result = 0x12345678 + + Asking for + Hook called | hKey = 0x12d687 | lpSubKey = + Asked for a failing key: returning 0x2a + WindowsError(42, 'Windows Error 0x2A') + + Asking for + Hook called | hKey = 0x80000001L | lpSubKey = + Non-secret key : calling normal function + Result = 0x108 + .. _sample_network_exploration: :class:`Network` - socket exploration diff --git a/doc/source/windows.rst b/doc/source/windows.rst index f449cc4..c7f8b5f 100644 --- a/doc/source/windows.rst +++ b/doc/source/windows.rst @@ -1,33 +1,33 @@ The ``windows`` module ********************** -The ``windows`` module is the module installed by :file:`setup.py` (that does not exists right now). +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` - + * ``current_process`` of type :class:`windows.winobject.CurrentProcess` + * ``current_thread`` of type :class:`windows.winobject.CurrentThread` + The submodules that you might use by themself are: * :mod:`windows.native_exec` * :mod:`windows.winproxy` * :mod:`windows.utils` - + .. _object_system: - + 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/doc/source/wip.rst b/doc/source/wip.rst new file mode 100644 index 0000000..479fd7b --- /dev/null +++ b/doc/source/wip.rst @@ -0,0 +1,25 @@ +Early Work In Progress +====================== + +Here are some features that are still work in progress. Code might be unstable and/or ultra-ugly. + +Wintrust -- Signature check +""""""""""""""""""""""""""" + +Should it juste be part of :mod:`windows.utils` ? + +.. module:: windows.wintrust + +.. autofunction:: windows.wintrust.check_signature + + + +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 22dce5b..9a40802 100644 --- a/samples/iat_hook.py +++ b/samples/iat_hook.py @@ -24,8 +24,8 @@ def open_reg_hook(hKey, lpSubKey, ulOptions, samDesired, phkResult, real_functio return 42 print(" Non-secret key : calling normal function") return real_function() - - + + # Get the peb of our process peb = windows.current_process.peb @@ -38,7 +38,7 @@ adv_imports = pythondll_module.pe.imports['advapi32.dll'] # Get RegOpenKeyExA iat entry RegOpenKeyExA_iat = [n for n in adv_imports if n.name == "RegOpenKeyExA"][0] -# Setup our hook +# Setup our hook RegOpenKeyExA_iat.set_hook(open_reg_hook) @@ -48,6 +48,7 @@ print("Asking for ") v = _winreg.OpenKey(1234567, "MY_SECRET_KEY") print("Result = " + hex(v.handle)) +print("") print("Asking for ") try: v = _winreg.OpenKey(1234567, "MY_FAIL_KEY") @@ -55,6 +56,7 @@ try: except WindowsError as e: print(repr(e)) +print("") print("Asking for ") try: v = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software") diff --git a/samples/registry.py b/samples/registry.py new file mode 100644 index 0000000..c0cde1f --- /dev/null +++ b/samples/registry.py @@ -0,0 +1,28 @@ +import sys +import os.path +import pprint +sys.path.append(os.path.abspath(__file__ + "\..\..")) + +import windows + +registry = windows.system.registry +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("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 for the JIT Debugger") + +jit_debug_key = registry["HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug"] + +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 diff --git a/windows/__init__.py b/windows/__init__.py index 0882167..7b6906d 100644 --- a/windows/__init__.py +++ b/windows/__init__.py @@ -27,3 +27,16 @@ import windows.wmi import windows.utils __all__ = ["system", "VirtualProtected", 'current_process', 'current_thread', 'winproxy'] + +import os + +if bool(os.environ.get("SPHINX_BUILD", 0)): + # I know it's shameful + # But it's the only way I can think of right know to get a full class + # of PEFile for documentation purpose u_u + + ppe = windows.current_process.peb.modules[0].pe + windows.pe_parse.PEFile = type(ppe) + iat_entry = ppe.imports.values()[0][0] + windows.pe_parse.IATEntry = type(iat_entry) + diff --git a/windows/generated_def/winstructs.py b/windows/generated_def/winstructs.py index 2c3735a..f1f6726 100644 --- a/windows/generated_def/winstructs.py +++ b/windows/generated_def/winstructs.py @@ -36,7 +36,7 @@ HCERTSTORE = PVOID HCRYPTMSG = PVOID VOID = DWORD -structs = ['_LIST_ENTRY', '_PEB_LDR_DATA', '_LSA_UNICODE_STRING', '_RTL_USER_PROCESS_PARAMETERS', '_PEB', '_SECURITY_ATTRIBUTES', '_SYSTEM_VERIFIER_INFORMATION', '_LDR_DATA_TABLE_ENTRY', '_IMAGE_FILE_HEADER', '_IMAGE_DATA_DIRECTORY', '_IMAGE_SECTION_HEADER', '_IMAGE_OPTIONAL_HEADER64', '_IMAGE_OPTIONAL_HEADER', '_IMAGE_NT_HEADERS64', '_IMAGE_NT_HEADERS', '_IMAGE_IMPORT_DESCRIPTOR', '_IMAGE_IMPORT_BY_NAME', '_IMAGE_EXPORT_DIRECTORY', '_MEMORY_BASIC_INFORMATION', '_MEMORY_BASIC_INFORMATION32', '_MEMORY_BASIC_INFORMATION64', '_STARTUPINFOA', '_STARTUPINFOW', '_PROCESS_INFORMATION', '_FLOATING_SAVE_AREA', '_CONTEXT32', '_WOW64_FLOATING_SAVE_AREA', '_WOW64_CONTEXT', '_M128A', '_CONTEXT64', 'tagPROCESSENTRY32W', 'tagPROCESSENTRY32', 'tagTHREADENTRY32', '_LUID', '_LUID_AND_ATTRIBUTES', '_TOKEN_PRIVILEGES', '_TOKEN_ELEVATION', '_OSVERSIONINFOA', '_OSVERSIONINFOW', '_OSVERSIONINFOEXA', '_OSVERSIONINFOEXW', '_OVERLAPPED', '_MIB_TCPROW_OWNER_PID', '_MIB_TCPTABLE_OWNER_PID', '_MIB_UDPROW_OWNER_PID', '_MIB_UDPTABLE_OWNER_PID', '_MIB_UDP6ROW_OWNER_PID', '_MIB_UDP6TABLE_OWNER_PID', '_MIB_TCP6ROW_OWNER_PID', '_MIB_TCP6TABLE_OWNER_PID', '_MIB_TCPROW', '_EXCEPTION_RECORD', '_EXCEPTION_RECORD32', '_EXCEPTION_RECORD64', '_EXCEPTION_POINTERS64', '_EXCEPTION_POINTERS32', '_DEBUG_PROCESSOR_IDENTIFICATION_ALPHA', '_DEBUG_PROCESSOR_IDENTIFICATION_AMD64', '_DEBUG_PROCESSOR_IDENTIFICATION_IA64', '_DEBUG_PROCESSOR_IDENTIFICATION_X86', '_DEBUG_PROCESSOR_IDENTIFICATION_ARM', '_DEBUG_PROCESSOR_IDENTIFICATION_ALL', '_SYMBOL_INFO', '_MODLOAD_DATA', '_SYSTEM_MODULE32', '_SYSTEM_MODULE64', '_SYSTEM_MODULE_INFORMATION32', '_SYSTEM_MODULE_INFORMATION64', 'tagSAFEARRAYBOUND', 'tagSAFEARRAY', '_DEBUG_BREAKPOINT_PARAMETERS', '_DEBUG_REGISTER_DESCRIPTION', '_DEBUG_STACK_FRAME', '_DEBUG_LAST_EVENT_INFO_BREAKPOINT', '_DEBUG_LAST_EVENT_INFO_EXCEPTION', '_DEBUG_LAST_EVENT_INFO_EXIT_THREAD', '_DEBUG_LAST_EVENT_INFO_EXIT_PROCESS', '_DEBUG_LAST_EVENT_INFO_LOAD_MODULE', '_DEBUG_LAST_EVENT_INFO_UNLOAD_MODULE', '_DEBUG_LAST_EVENT_INFO_SYSTEM_ERROR', '_DEBUG_SPECIFIC_FILTER_PARAMETERS', '_DEBUG_EXCEPTION_FILTER_PARAMETERS', '_GUID', '_CRYPTOAPI_BLOB', 'WINTRUST_FILE_INFO_', '_CRYPT_ATTRIBUTE', '_CTL_ENTRY', '_CRYPT_ATTRIBUTE', '_CRYPT_ATTRIBUTES', '_CRYPT_ALGORITHM_IDENTIFIER', '_CMSG_SIGNER_INFO', '_CERT_EXTENSION', '_CTL_USAGE', '_CTL_INFO', '_CTL_CONTEXT', 'WINTRUST_CATALOG_INFO_', 'WINTRUST_BLOB_INFO_', '_CRYPT_BIT_BLOB', '_CERT_PUBLIC_KEY_INFO', '_CERT_INFO', '_CERT_CONTEXT', 'WINTRUST_SGNR_INFO_', '_FILETIME', 'WINTRUST_CERT_INFO_', '_TMP_WINTRUST_UNION_TYPE', '_WINTRUST_DATA', '_PROCESS_BASIC_INFORMATION'] +structs = ['_LIST_ENTRY', '_PEB_LDR_DATA', '_LSA_UNICODE_STRING', '_RTL_USER_PROCESS_PARAMETERS', '_PEB', '_SECURITY_ATTRIBUTES', '_SYSTEM_VERIFIER_INFORMATION', '_LDR_DATA_TABLE_ENTRY', '_IMAGE_FILE_HEADER', '_IMAGE_DATA_DIRECTORY', '_IMAGE_SECTION_HEADER', '_IMAGE_OPTIONAL_HEADER64', '_IMAGE_OPTIONAL_HEADER', '_IMAGE_NT_HEADERS64', '_IMAGE_NT_HEADERS', '_IMAGE_IMPORT_DESCRIPTOR', '_IMAGE_IMPORT_BY_NAME', '_IMAGE_EXPORT_DIRECTORY', '_MEMORY_BASIC_INFORMATION', '_MEMORY_BASIC_INFORMATION32', '_MEMORY_BASIC_INFORMATION64', '_STARTUPINFOA', '_STARTUPINFOW', '_PROCESS_INFORMATION', '_FLOATING_SAVE_AREA', '_CONTEXT32', '_WOW64_FLOATING_SAVE_AREA', '_WOW64_CONTEXT', '_M128A', '_CONTEXT64', 'tagPROCESSENTRY32W', 'tagPROCESSENTRY32', 'tagTHREADENTRY32', '_LUID', '_LUID_AND_ATTRIBUTES', '_TOKEN_PRIVILEGES', '_TOKEN_ELEVATION', '_OSVERSIONINFOA', '_OSVERSIONINFOW', '_OSVERSIONINFOEXA', '_OSVERSIONINFOEXW', '_OVERLAPPED', '_MIB_TCPROW_OWNER_PID', '_MIB_TCPTABLE_OWNER_PID', '_MIB_UDPROW_OWNER_PID', '_MIB_UDPTABLE_OWNER_PID', '_MIB_UDP6ROW_OWNER_PID', '_MIB_UDP6TABLE_OWNER_PID', '_MIB_TCP6ROW_OWNER_PID', '_MIB_TCP6TABLE_OWNER_PID', '_MIB_TCPROW', '_EXCEPTION_RECORD', '_EXCEPTION_RECORD32', '_EXCEPTION_RECORD64', '_EXCEPTION_POINTERS64', '_EXCEPTION_POINTERS32', '_DEBUG_PROCESSOR_IDENTIFICATION_ALPHA', '_DEBUG_PROCESSOR_IDENTIFICATION_AMD64', '_DEBUG_PROCESSOR_IDENTIFICATION_IA64', '_DEBUG_PROCESSOR_IDENTIFICATION_X86', '_DEBUG_PROCESSOR_IDENTIFICATION_ARM', '_DEBUG_PROCESSOR_IDENTIFICATION_ALL', '_SYMBOL_INFO', '_MODLOAD_DATA', '_SYSTEM_MODULE32', '_SYSTEM_MODULE64', '_SYSTEM_MODULE_INFORMATION32', '_SYSTEM_MODULE_INFORMATION64', 'tagSAFEARRAYBOUND', 'tagSAFEARRAY', '_DEBUG_BREAKPOINT_PARAMETERS', '_DEBUG_REGISTER_DESCRIPTION', '_DEBUG_STACK_FRAME', '_DEBUG_LAST_EVENT_INFO_BREAKPOINT', '_DEBUG_LAST_EVENT_INFO_EXCEPTION', '_DEBUG_LAST_EVENT_INFO_EXIT_THREAD', '_DEBUG_LAST_EVENT_INFO_EXIT_PROCESS', '_DEBUG_LAST_EVENT_INFO_LOAD_MODULE', '_DEBUG_LAST_EVENT_INFO_UNLOAD_MODULE', '_DEBUG_LAST_EVENT_INFO_SYSTEM_ERROR', '_DEBUG_SPECIFIC_FILTER_PARAMETERS', '_DEBUG_EXCEPTION_FILTER_PARAMETERS', '_GUID', '_CRYPTOAPI_BLOB', 'WINTRUST_FILE_INFO_', '_CRYPT_ATTRIBUTE', '_CTL_ENTRY', '_CRYPT_ATTRIBUTE', '_CRYPT_ATTRIBUTES', '_CRYPT_ALGORITHM_IDENTIFIER', '_CMSG_SIGNER_INFO', '_CERT_EXTENSION', '_CTL_USAGE', '_CTL_INFO', '_CTL_CONTEXT', 'WINTRUST_CATALOG_INFO_', 'WINTRUST_BLOB_INFO_', '_CRYPT_BIT_BLOB', '_CERT_PUBLIC_KEY_INFO', '_CERT_INFO', '_CERT_CONTEXT', 'WINTRUST_SGNR_INFO_', '_FILETIME', 'WINTRUST_CERT_INFO_', '_TMP_WINTRUST_UNION_TYPE', '_WINTRUST_DATA', '_PROCESS_BASIC_INFORMATION', '_JIT_DEBUG_INFO'] enums = ['_SYSTEM_INFORMATION_CLASS', '_MEMORY_INFORMATION_CLASS', '_THREAD_INFORMATION_CLASS', '_TCP_TABLE_CLASS', '_VARENUM', '_UDP_TABLE_CLASS', '_MIB_TCP_STATE', '_TOKEN_INFORMATION_CLASS', '_IMAGEHLP_SYMBOL_TYPE_INFO', '_PROCESSINFOCLASS'] @@ -1881,3 +1881,17 @@ class _PROCESS_BASIC_INFORMATION(Structure): PPROCESS_BASIC_INFORMATION = POINTER(_PROCESS_BASIC_INFORMATION) PROCESS_BASIC_INFORMATION = _PROCESS_BASIC_INFORMATION +# Struct _JIT_DEBUG_INFO definitions +class _JIT_DEBUG_INFO(Structure): + _fields_ = [ + ("dwSize", DWORD), + ("dwProcessorArchitecture", DWORD), + ("dwThreadID", DWORD), + ("dwReserved0", DWORD), + ("lpExceptionAddress", ULONG64), + ("lpExceptionRecord", ULONG64), + ("lpContextRecord", ULONG64), + ] +LPJIT_DEBUG_INFO = POINTER(_JIT_DEBUG_INFO) +JIT_DEBUG_INFO = _JIT_DEBUG_INFO + diff --git a/windows/hooks.py b/windows/hooks.py index f5cee61..f92a5b6 100644 --- a/windows/hooks.py +++ b/windows/hooks.py @@ -9,6 +9,7 @@ from .generated_def.winstructs import * class Callback(object): + """Give type information to hook callback""" def __init__(self, *types): self.types = types @@ -66,11 +67,13 @@ class IATHook(object): return res def enable(self): + """Enable the IAT hook""" with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE): self.entry.value = self.stub self.is_enable = True def disable(self): + """Disable the IAT hook""" with utils.VirtualProtected(self.entry.addr, ctypes.sizeof(PVOID), PAGE_EXECUTE_READWRITE): self.entry.value = self.entry.nonhookvalue self.is_enable = False diff --git a/windows/pe_parse.py b/windows/pe_parse.py index a5577b2..78fc735 100644 --- a/windows/pe_parse.py +++ b/windows/pe_parse.py @@ -47,7 +47,12 @@ def get_structure_transformer_for_target(target): return ctypes_structure_transformer, create_structure_at -def PEFile(baseaddr, target=None): +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 + + :rtype: :class:`PEFile` + """ proc_bitness = windows.current_process.bitness if target is None: targetedbitness = proc_bitness @@ -100,9 +105,14 @@ def PEFile(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 + """ _fields_ = [ ("value", PVOID)] + + @classmethod def create(cls, addr, ord, name): self = create_structure_at(cls, addr) @@ -117,12 +127,25 @@ def PEFile(baseaddr, target=None): return '<{0} "{1}" ordinal {2}>'.format(self.__class__.__name__, self.name, self.ord) def set_hook(self, callback, types=None): + """Setup a hook on the entry and return it. + + :param callback: the hook + + .. note:: + + see :ref:`hook_protocol` + + :rtype: :class:`windows.hooks.IATHook` + """ + + hook = hooks.IATHook(self, callback, types) self.hook = hook hook.enable() return hook def remove_hook(self): + """Remove the hook on the entry""" if self.hook is None: return False self.hook.disable() @@ -130,6 +153,7 @@ def PEFile(baseaddr, target=None): return True class PEFile(object): + """Represent a PE loaded in a process (current or remote)""" def __init__(self): self.baseaddr = baseaddr @@ -183,6 +207,10 @@ def PEFile(baseaddr, target=None): @utils.fixedpropety def exports(self): + """The exports of the PE in a dict. Keys are ordinal (:class:`int`) and name (:class:`str`). + The values are the addresses of the exports. + + :type: {(:class:`int` or :class:`str`) : :class:`int`}""" res = {} exp_dir = self.get_EXPORT_DIRECTORY() if exp_dir is None: @@ -197,6 +225,11 @@ def PEFile(baseaddr, target=None): # TODO: get imports by parsing other modules exports if no INT @utils.fixedpropety def imports(self): + """The imports of the PE in a dict. + Keys are the names of DLL to import from and values are :class:`list` + of :class:`IATEntry` + + :type: {:class:`str` : [:class:`IATEntry`]}""" res = {} for import_descriptor in self.get_IMPORT_DESCRIPTORS(): INT = import_descriptor.get_INT() @@ -292,6 +325,4 @@ def PEFile(baseaddr, target=None): return create_structure_at(IMAGE_NT_HEADERS32, baseaddr + self.e_lfanew) return create_structure_at(IMAGE_NT_HEADERS64, baseaddr + self.e_lfanew) - return current_pe - -tst = PEFile.__code__.co_consts[13] + return current_pe \ No newline at end of file diff --git a/windows/registry.py b/windows/registry.py index d92a01b..e4c6c57 100644 --- a/windows/registry.py +++ b/windows/registry.py @@ -40,6 +40,7 @@ class PyHKey(object): @property def subkeys(self): + """The subkeys of the registry key""" res = [] with ExpectWindowsError(259): for i in itertools.count(): @@ -48,6 +49,7 @@ class PyHKey(object): @property def values(self): + """The values of the registry key""" res = [] with ExpectWindowsError(259): for i in itertools.count(): @@ -78,7 +80,7 @@ HKEY_USERS = PyHKey(DummyPHKEY(_winreg.HKEY_USERS, "HKEY_USERS"), "", _winreg.KE class Registry(object): - """The ``Windows`` registry: a read only mapping""" + """The ``Windows`` registry: a read only (for now) mapping""" registry_base_keys = { "HKEY_LOCAL_MACHINE" : HKEY_LOCAL_MACHINE, diff --git a/windows/winobject.py b/windows/winobject.py index 3849595..2c66526 100644 --- a/windows/winobject.py +++ b/windows/winobject.py @@ -685,7 +685,7 @@ class LoadedModule(LDR_DATA_TABLE_ENTRY): :type: :class:`windows.pe_parse.PEFile` """ - return pe_parse.PEFile(self.baseaddr) + return pe_parse.GetPEFile(self.baseaddr) class WinUnicodeString(LSA_UNICODE_STRING): @@ -760,7 +760,7 @@ class RemoteLoadedModule(rctypes.RemoteStructure.from_structure(LoadedModule)): :type: :class:`windows.pe_parse.PEFile` """ - return pe_parse.PEFile(self.baseaddr, target=self._target) + return pe_parse.GetPEFile(self.baseaddr, target=self._target) class RemotePEB(rctypes.RemoteStructure.from_structure(PEB)): @@ -793,7 +793,7 @@ if CurrentProcess().bitness == 32: :type: :class:`windows.pe_parse.PEFile` """ - return pe_parse.PEFile(self.baseaddr, target=self._target) + return pe_parse.GetPEFile(self.baseaddr, target=self._target) class RemotePEB64(rctypes.transform_type_to_remote64bits(PEB)): @@ -825,7 +825,7 @@ if CurrentProcess().bitness == 64: :type: :class:`windows.pe_parse.PEFile` """ - return pe_parse.PEFile(self.baseaddr, target=self._target) + return pe_parse.GetPEFile(self.baseaddr, target=self._target) class RemotePEB32(rctypes.transform_type_to_remote32bits(PEB)): diff --git a/windows/wintrust.py b/windows/wintrust.py index 2cee0d7..4bf568c 100644 --- a/windows/wintrust.py +++ b/windows/wintrust.py @@ -38,7 +38,10 @@ WTD_STATEACTION_AUTO_CACHE = 0x00000003 WTD_STATEACTION_AUTO_CACHE_FLUSH = 0x00000004 def check_signature(filename): - print("Filename is <{0}>".format(repr(filename))) + """Check if ``filename`` is a valid signed file + + :return: 0 if file have a valid signature + """ file_data = WINTRUST_FILE_INFO() file_data.cbStruct = ctypes.sizeof(WINTRUST_FILE_INFO) file_data.pcwszFilePath = filename diff --git a/windows/wmi.py b/windows/wmi.py index 2d5988c..e5e9692 100644 --- a/windows/wmi.py +++ b/windows/wmi.py @@ -111,6 +111,7 @@ class SimpleVariant(ctypes.Structure): return bool(self.aslong) class WmiRequester(object): + """Perform WMI request: NOT STABLE""" INSTANCE = None def __new__(cls): if cls.INSTANCE is not None: @@ -136,6 +137,10 @@ class WmiRequester(object): def select(self, frm, attrs): + """Select `attrs` from ``frm`` + + :rtype: list of dict + """ enumerator = IEnumWbemClassObject() self.service.ExecQuery("WQL", "select * from {0}".format(frm), 0x20, 0, ctypes.byref(enumerator))