From c73fc7934ac450788f2568ec3f74fc2a417ba4b1 Mon Sep 17 00:00:00 2001 From: hakril Date: Tue, 5 Jan 2016 08:43:40 +0100 Subject: [PATCH] Add doc on PEB + samples in sphinx --- doc/source/index.rst | 2 + doc/source/process.rst | 68 ++++++++++++++++++++++++ doc/source/registry.rst | 8 +++ doc/source/sample.rst | 99 +++++++++++++++++++++++++++++++++++ doc/source/various.rst | 13 +++++ doc/source/windows.rst | 3 +- samples/peb.py | 40 ++++++++++++++ windows/winobject.py | 112 ++++++++++++++++++++++++++++++---------- 8 files changed, 317 insertions(+), 28 deletions(-) create mode 100644 doc/source/process.rst create mode 100644 doc/source/registry.rst create mode 100644 doc/source/sample.rst create mode 100644 doc/source/various.rst create mode 100644 samples/peb.py diff --git a/doc/source/index.rst b/doc/source/index.rst index f7bbfa5..bb53f97 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -16,6 +16,8 @@ Contents: winproxy.rst utils.rst native_exec.rst + various.rst + sample.rst Indices and tables diff --git a/doc/source/process.rst b/doc/source/process.rst new file mode 100644 index 0000000..f9cf0bf --- /dev/null +++ b/doc/source/process.rst @@ -0,0 +1,68 @@ +Processes and Threads +""""""""""""""""""""" + +.. module:: windows.winobject + +CurrentProcess +'''''''''''''' + +.. note:: + + See sample :ref:`sample_current_process` + +.. autoclass:: CurrentProcess + :members: + :inherited-members: + +CurrentThread +''''''''''''' + +.. autoclass:: CurrentThread + :members: + :inherited-members: + +WinProcess +'''''''''' + +.. note:: + + See sample :ref:`sample_remote_process` + +.. autoclass:: WinProcess + :members: + :inherited-members: + + +WinThread +''''''''' + +.. autoclass:: WinThread + :members: + :inherited-members: + + +.. autoclass:: DeadThread + :members: + :inherited-members: + + +PEB Exploration +""""""""""""""" + +The :mod:`windows` module is able to parse the PEB of the current process or remote process. +The :class:`PEB` is accessible via ``process.peb`` and is of type :class:`PEB`. + +.. note:: + + See sample :ref:`sample_peb_exploration` + +.. autoclass:: PEB + :members: + :inherited-members: + +.. autoclass:: WinUnicodeString + +.. autoclass:: LoadedModule + + + diff --git a/doc/source/registry.rst b/doc/source/registry.rst new file mode 100644 index 0000000..2cba624 --- /dev/null +++ b/doc/source/registry.rst @@ -0,0 +1,8 @@ +Registry +"""""""" + +.. module:: windows.registry + +REGISTRY + +.. class:: Registry \ No newline at end of file diff --git a/doc/source/sample.rst b/doc/source/sample.rst new file mode 100644 index 0000000..34f18c0 --- /dev/null +++ b/doc/source/sample.rst @@ -0,0 +1,99 @@ +Samples of code +=============== + +.. _sample_current_process: + +``windows.current_process`` +""""""""""""""""""""""""""" + +.. literalinclude:: ..\..\samples\current_process.py + +Output:: + + (cmd λ) python32.exe current_process.py + current process is + current process is a <32> bits process + current process is a SysWow64 process ? + current process pid <7432> and ppid <5412> + Here are the current process threads: <[]> + Let's execute some native code ! (0x41 + 1) + Waiting for execution to finish ! + Native code returned <0x42L> + Allocating memory in current process + Allocated memory is at <0x3f0000> + Writing 'SOME STUFF' in allocation memory + Reading memory : <'SOME STUFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'> + +.. _sample_remote_process: + +Remote process : :class:`WinProcess` +"""""""""""""""""""""""""""""""""""" + +.. literalinclude:: ..\..\samples\remote_calc.py + +Output:: + + (cmd λ) python.exe remote_calc.py + Creating a calc + Looking for calcs in the processes + They are currently <1> calcs running on the system + Let's play with our calc: <> + Our calc pid is 8052 + Our calc is a <32> bits process + Our calc is a SysWow64 process ? + Our calc have threads ! <[, , ]> + Exploring our calc PEB ! + Command line is + Here are 3 loaded modules: [, , ] + Allocating memory in our calc + Allocated memory is at <0x5c90000> + Writing 'SOME STUFF' in allocated memory + Reading allocated memory : <'SOME STUFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'> + Execution some native code in our calc (write 0x424242 at allocated address + return 0x1337 + Executing native code ! + Return code = 0x1337L + Reading allocated memory : <'BBBB STUFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'> + Executing python code ! + Reading allocated memory : <'HELLO FROM CALC\x00\x00\x00\x00\x00'> + Trying to import in remote module 'FAKE_MODULE' + Remote ERROR ! + Traceback (most recent call last): + File "", line 3, in + File "", line 2, in func + ImportError: No module named FAKE_MODULE + + That's all ! killing the calc + + +.. _sample_peb_exploration: + +:class:`PEB` exploration +"""""""""""""""""""""""" + +.. literalinclude:: ..\..\samples\peb.py + +Output:: + + (cmd λ) python.exe peb.py + Exploring the current process PEB + PEB is <> + Commandline object is + Commandline string is u'python.exe peb.py ' + Imagepath + Printing some modules: + + + + + + === K32 === + Looking for kernel32.dll + Kernel32 module: + Module name = | Fullname = + Kernel32 is loaded at address 0x774c0000 + === K32 PE === + PE Representation of k32: + Here are some exports {0: 2001566688L, u'CreateFileA': 2001635616L, 42: 2001647872L, u'VirtualAlloc': 2001570704L} + Import DLL dependancies are (without api-*): [u'ntdll.dll', u'kernelbase.dll'] + IAT Entry for ntdll!NtCreateFile = | addr = 0x77541128L + Sections: [, , , , ] \ No newline at end of file diff --git a/doc/source/various.rst b/doc/source/various.rst new file mode 100644 index 0000000..05417ee --- /dev/null +++ b/doc/source/various.rst @@ -0,0 +1,13 @@ +The :mod:`windows` objects +========================== + +Through the :ref:`system ` object many classes representing various `Windows` +part are accessible. + +This sections describes them by group of relation. + +.. toctree:: + :maxdepth: 2 + + process.rst + registry.rst \ No newline at end of file diff --git a/doc/source/windows.rst b/doc/source/windows.rst index 0ff4da7..f449cc4 100644 --- a/doc/source/windows.rst +++ b/doc/source/windows.rst @@ -14,7 +14,8 @@ The submodules that you might use by themself are: * :mod:`windows.winproxy` * :mod:`windows.utils` - +.. _object_system: + The ``system`` object """"""""""""""""""""" diff --git a/samples/peb.py b/samples/peb.py new file mode 100644 index 0000000..69373dc --- /dev/null +++ b/samples/peb.py @@ -0,0 +1,40 @@ +import sys +import os.path +sys.path.append(os.path.abspath(__file__ + "\..\..")) + +import windows + +print("Exploring the current process PEB") +peb = windows.current_process.peb +print("PEB is <{0}>".format(peb)) +commandline = peb.commandline +print("Commandline object is {0}".format(commandline)) +print("Commandline string is {0}".format(repr(commandline.Buffer))) + +imagepath = peb.imagepath +print("Imagepath {0}".format(imagepath)) + +modules = peb.modules +print("Printing some modules: {0}".format("\n".join(str(m) for m in modules[:6]))) + +print("=== K32 ===") +print("Looking for kernel32.dll") +k32 = [m for m in modules if m.name == "kernel32.dll"][0] +print("Kernel32 module: {0}".format(k32)) + +print("Module name = <{0}> | Fullname = <{1}>".format(k32.name, k32.fullname)) +print("Kernel32 is loaded at address {0}".format(hex(k32.baseaddr))) + +print("=== K32 PE ===") +k32pe = k32.pe +print("PE Representation of k32: {0}".format(k32pe)) +exports = k32pe.exports +some_exports = dict((k,v) for k,v in exports.items() if k in [0, 42, "VirtualAlloc", "CreateFileA"]) +print("Here are some exports {0}".format(some_exports)) + +imports = k32pe.imports +print("Import DLL dependancies are (without api-*): {0}".format([x for x in imports.keys() if not x.startswith("api-")])) + +NtCreateFile_iat = [x for x in imports["ntdll.dll"] if x.name == "NtCreateFile"][0] +print("IAT Entry for ntdll!NtCreateFile = {0} | addr = {1}".format(NtCreateFile_iat, hex(NtCreateFile_iat.addr))) +print("Sections: {0}".format(k32pe.sections)) \ No newline at end of file diff --git a/windows/winobject.py b/windows/winobject.py index 88d98d0..3849595 100644 --- a/windows/winobject.py +++ b/windows/winobject.py @@ -32,15 +32,12 @@ class AutoHandle(object): @property def handle(self): - """A handle on the object + """An handle on the object :type: HANDLE - .. note:: The handle is automaticaly closed when the object is destroyed - - """ if hasattr(self, "_handle"): return self._handle @@ -48,6 +45,7 @@ class AutoHandle(object): return self._handle def wait(self, timeout=INFINITE): + """Wait for the object""" return winproxy.WaitForSingleObject(self.handle, timeout) def __del__(self): @@ -167,6 +165,10 @@ 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: res = ULONGLONG() windows.syswow64.NtQueryInformationThread_32_to_64(self.handle, ThreadQuerySetWin32StartAddress, byref(res), ctypes.sizeof(res)) @@ -180,12 +182,15 @@ class WinThread(THREADENTRY32, AutoHandle): return res.value def exit(self, code=0): + """Exit the thread""" return winproxy.TerminateThread(self.handle, code) def resume(self): + """Resume the thread""" return winproxy.ResumeThread(self.handle) def suspend(self): + """Suspend the thread""" return winproxy.SuspendThread(self.handle) def _get_handle(self): @@ -193,10 +198,18 @@ 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 @property def exit_code(self): + """The exit code of the thread : ``STILL_ACTIVE`` means the process is not dead + + :type: :class:`int` + """ res = DWORD() winproxy.GetExitCodeThread(self.handle, byref(res)) return res.value @@ -222,7 +235,7 @@ class WinThread(THREADENTRY32, AutoHandle): return DeadThread(handle, tid) class DeadThread(AutoHandle): - """A simple object arround an already dead thread""" + """An already dead thread""" def __init__(self, handle, tid=None): if tid is None: tid = winproxy.GetThreadId(handle) @@ -232,10 +245,18 @@ class DeadThread(AutoHandle): @property def is_exit(self): + """Is ``True`` if the thread is terminated + + :type: :class:`bool` + """ return self.exit_code != STILL_ACTIVE @property def exit_code(self): + """The exit code of the thread : ``STILL_ACTIVE`` means the process is not dead + + :type: :class:`int` + """ res = DWORD() winproxy.GetExitCodeThread(self.handle, byref(res)) return res.value @@ -244,11 +265,9 @@ class DeadThread(AutoHandle): class Process(AutoHandle): @utils.fixedpropety def is_wow_64(self): - """Is True if the process is a SysWow64 process + """Is ``True`` if the process is a SysWow64 process (32bit process on 64bits system). - This means a 32bits process on a 64bits system - - :type: bool + :type: :class:`bool` """ return utils.is_wow_64(self.handle) @@ -256,7 +275,7 @@ class Process(AutoHandle): def bitness(self): """The bitness of the process - :returns: int -- 32 or 64""" + :returns: :class:`int` -- 32 or 64""" if windows.system.bitness == 32: return 32 if self.is_wow_64: @@ -268,7 +287,6 @@ class Process(AutoHandle): """The threads of the process :type: [:class:`WinThread`] -- A list of Thread - """ return [thread for thread in windows.system.threads if thread.th32OwnerProcessID == self.pid] @@ -277,12 +295,20 @@ 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() winproxy.GetExitCodeProcess(self.handle, byref(res)) return res.value @property def is_exit(self): + """Is ``True`` if the process is terminated + + :type: :class:`bool` + """ return self.exit_code == STILL_ACTIVE def execute(self, code): @@ -292,6 +318,10 @@ class Process(AutoHandle): return self.create_thread(x, 0) def query_memory(self, addr): + """Query the memory informations about page at ```addr`` + + :rtype: :class:`MEMORY_BASIC_INFORMATION` + """ if windows.current_process.bitness == 32 and self.bitness == 64: res = MEMORY_BASIC_INFORMATION64() try: @@ -309,6 +339,10 @@ class Process(AutoHandle): return res def memory_state(self): + """Yield the memory information for the whole address space of the process + + :yield: :class:`MEMORY_BASIC_INFORMATION` + """ addr = 0 res = [] while True: @@ -386,7 +420,7 @@ class CurrentProcess(Process): def pid(self): """Process ID - :type: int + :type: :class:`int` """ return os.getpid() @@ -395,7 +429,7 @@ class CurrentProcess(Process): def ppid(self): """Parent Process ID - :type: int + :type: :class:`int` """ return [p for p in windows.system.processes if p.pid == self.pid][0].ppid @@ -411,7 +445,7 @@ class CurrentProcess(Process): def bitness(self): """The bitness of the process - :returns: int -- 32 or 64""" + :type: :class:`int` -- 32 or 64""" import platform bits = platform.architecture()[0] return int(bits[:2]) @@ -419,7 +453,7 @@ class CurrentProcess(Process): def virtual_alloc(self, size): """Allocate memory in the current process - :returns: int + :returns: :class:`int` """ return winproxy.VirtualAlloc(dwSize=size) @@ -440,6 +474,8 @@ class CurrentProcess(Process): .. note:: CreateThread https://msdn.microsoft.com/en-us/library/windows/desktop/ms682453%28v=vs.85%29.aspx + + :rtype: :class:`WinThread` or :class:`DeadThread` """ handle = winproxy.CreateThread(lpStartAddress=lpStartAddress, lpParameter=lpParameter, dwCreationFlags=dwCreationFlags) return WinThread._from_handle(handle) @@ -462,7 +498,7 @@ class WinProcess(PROCESSENTRY32, Process): def name(self): """Name of the process - :type: str + :type: :class:`str` """ return self.szExeFile[:].decode() @@ -470,7 +506,7 @@ class WinProcess(PROCESSENTRY32, Process): def pid(self): """Process ID - :type: int + :type: :class:`int` """ return self.th32ProcessID @@ -478,7 +514,7 @@ class WinProcess(PROCESSENTRY32, Process): def ppid(self): """Parent Process ID - :type: int + :type: :class:`int` """ return self.th32ParentProcessID @@ -491,7 +527,7 @@ class WinProcess(PROCESSENTRY32, Process): def virtual_alloc(self, size): """Allocate memory in the process - :returns: int + :rtype: :class:`int` """ return winproxy.VirtualAllocEx(self.handle, dwSize=size) @@ -532,12 +568,17 @@ class WinProcess(PROCESSENTRY32, Process): # return page_data[addr & 0xfff: (addr & 0xfff) + size] def read_memory_into(self, addr, struct): - """Read a :mod:`ctypes` struct from `addr`""" + """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""" + """Create a remote thread + + :rtype: :class:`WinThread` or :class:`DeadThread` + """ if windows.current_process.bitness == 32 and self.bitness == 64: thread_handle = HANDLE() windows.syswow64.NtCreateThreadEx_32_to_64(ThreadHandle=byref(thread_handle) ,ProcessHandle=self.handle, lpStartAddress=addr, lpParameter=param) @@ -552,15 +593,25 @@ 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""" + """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) @utils.fixedpropety def peb_addr(self): + """The address of the PEB + + :type: :class:`int` + """ if windows.current_process.bitness == 32 and self.bitness == 64: x = windows.remotectypes.transform_type_to_remote64bits(PROCESS_BASIC_INFORMATION) # Fuck-it <3 @@ -584,6 +635,10 @@ 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: return RemotePEB64(self.peb_addr, self) if windows.current_process.bitness == 64 and self.bitness == 32: @@ -599,9 +654,9 @@ class LoadedModule(LDR_DATA_TABLE_ENTRY): """An entry in the PEB Ldr list""" @property def baseaddr(self): - """base address of the module + """Base address of the module - :type: int + :type: :class:`int` """ return self.DllBase @@ -609,7 +664,7 @@ class LoadedModule(LDR_DATA_TABLE_ENTRY): def name(self): """Name of the module - :type: str + :type: :class:`str` """ return str(self.BaseDllName.Buffer).lower() @@ -617,7 +672,7 @@ class LoadedModule(LDR_DATA_TABLE_ENTRY): def fullname(self): """Full name of the module (path) - :type: str + :type: :class:`str` """ return self.FullDllName.Buffer.decode() @@ -635,6 +690,9 @@ class LoadedModule(LDR_DATA_TABLE_ENTRY): 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)))