# PythonForWindows PythonForWindows is a base of code aimed to make interaction with `Windows` (on X86/X64) easier (for 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 thinner in both ways. There is no external dependencies but it relies heavily on the `ctypes` module. 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. Complete online documentation is available [here][ONLINE_DOC] You can find some examples of code in the [samples directory][SAMPLE_DIR] or [online][ONLINE_SAMPLE]. Parts of PythonForWindows are used in the [LKD project][LKD_GITHUB]. ## Overview ### Processes / Threads PythonForWindows offers objects around processes and allows you to: - Retrieve basic process informations (pid, name, ppid, bitness, ...) - 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 a process. I try my best to make those features available for every cross-bitness processes (`32 <-> 64` in both ways). This involves relying on non-documented `Windows` functions/behaviours 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 on threads (suspend/resume/wait/get(or set) context/ kill) ```python >>> import windows >>> windows.current_process.bitness 32 >>> calc = [p for p in windows.system.processes if p.name == "calc.exe"][0] >>> calc >>> calc.bitness 64 >>> calc.peb.modules[:3] [, , ] >>> k32 = calc.peb.modules[2] >>> hex(k32.pe.exports["CreateFileW"]) '0x7ffee6761550L' >>> calc.threads[0] >>> hex(calc.threads[0].context.Rip) '0x7ffee68b54b0L' >>> calc.execute_python("import os") True >>> calc.execute_python("exit(os.getpid() + 1)") # execute_python raise if process died Traceback (most recent call last): ... WindowsError: died during execution of python command >>> calc >>> calc.exit_code 6961L ``` ### IAT Hook This codebase is born from my need to have IAT hooks implemented in Python. So the features is present (See [online documentation][ONLINE_IATHOOK] about IAT hooks). ### Winproxy 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 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. ```python >>> import windows.native_exec.simple_x86 as x86 >>> code = x86.MultipleInstr() >>> code += x86.Mov("EAX", 41) >>> code += x86.Inc("EAX") >>> code += x86.Ret() >>> code.get_code() '\xc7\xc0)\x00\x00\x00@\xc3' # Create a function that takes no parameters and return an uint >>> f = windows.native_exec.create_function(code.get_code(), [ctypes.c_uint]) >>> f() 42L ``` ### Wintrust To easily script some signature check script, PythonForWindows implements some wrapper functions around ``wintrust.dll`` ```python >>> import windows.wintrust >>> windows.wintrust.is_signed(r"C:\Windows\system32\ntdll.dll") True >>> windows.wintrust.is_signed(r"C:\Windows\system32\python27.dll") False >>> windows.wintrust.full_signature_information(r"C:\Windows\system32\ntdll.dll") SignatureData(signed=True, catalog=u'C:\\Windows\\system32\\CatRoot\\{F750E6C3-38EE-11D1-85E5-00C04FC295EE}\\Package_35_for_KB3128650~31bf3856ad364e35~amd64~~6.3.1.2.cat', catalogsigned=True, additionalinfo=0L) >>> windows.wintrust.full_signature_information(r"C:\Windows\system32\python27.dll") SignatureData(signed=False, catalog=None, catalogsigned=False, additionalinfo=TRUST_E_NOSIGNATURE(0x800b0100L)) ``` ### WMI To extract/play with even more information about the system, PythonForWindows is able to perform WMI request. ```python >>> import windows >>> windows.system.wmi.select > >>> windows.system.wmi.select("Win32_Process", ["Name", "Handle"])[:4] [{'Handle': u'0', 'Name': u'System Idle Process'}, {'Handle': u'4', 'Name': u'System'}, {'Handle': u'412', 'Name': u'smss.exe'}, {'Handle': u'528', 'Name': u'csrss.exe'}] # Get WMI data for current process >>> wmi_cp = [p for p in windows.system.wmi.select("Win32_Process") if int(p["Handle"]) == windows.current_process.pid][0] >>> wmi_cp["CommandLine"], wmi_cp["HandleCount"] (u'"C:\\Python27\\python.exe"', 227) ``` ### Registry The project also contains some wrapping classes around `_winreg` for simpler use. ```python >>> import windows >>> from windows.generated_def import KEY_WRITE, KEY_READ, REG_QWORD >>> registry = windows.system.registry >>> cuuser_software = registry(r'HKEY_CURRENT_USER\Software') >>> cuuser_software >>> cuuser_software.sam KEY_READ(0x20019L) # Explore subkeys >>> cuuser_software.subkeys[:3] [, , ] >>> tstkey = registry('HKEY_CURRENT_USER\TestKey', KEY_WRITE | KEY_READ) # Get / Set individual value >>> tstkey["VALUE"] = 'a_value_for_my_key' >>> tstkey["VALUE"] KeyValue(name='VALUE', value=u'a_value_for_my_key', type=1) >>> tstkey["MYQWORD"] = (123456789987654321, REG_QWORD) # Default is REG_DWORD for int/long >>> tstkey["MYQWORD"] KeyValue(name='MYQWORD', value=123456789987654321L, type=11) # Explore Values >>> tstkey.values [KeyValue(name='MYQWORD', value=123456789987654321L, type=11), KeyValue(name='VALUE', value=u'a_value_for_my_key', type=1)] ``` ### Debugger PythonForWindows provides a standard debugger to debug other processes. ```python import windows import windows.debug import windows.test import windows.native_exec.simple_x86 as x86 from windows.test import pop_calc_32 from windows.generated_def import EXCEPTION_ACCESS_VIOLATION class MyDebugger(windows.debug.Debugger): def on_exception(self, exception): code = exception.ExceptionRecord.ExceptionCode addr = exception.ExceptionRecord.ExceptionAddress print("Got exception {0} at 0x{1:x}".format(code, addr)) if code == EXCEPTION_ACCESS_VIOLATION: print("Access Violation: kill target process") self.current_process.exit() calc = windows.test.pop_calc_32(dwCreationFlags=DEBUG_PROCESS) d = MyDebugger(calc) calc.execute(x86.assemble("int3; mov [0x42424242], EAX; ret")) d.loop() ## Ouput ## Got exception EXCEPTION_BREAKPOINT(0x80000003L) at 0x77e13c7d Got exception EXCEPTION_BREAKPOINT(0x80000003L) at 0x230000 Got exception EXCEPTION_ACCESS_VIOLATION(0xc0000005L) at 0x230001 Access Violation: kill target process ``` The debugger handles * Standard breakpoint ``int3`` * Hardware Execution breakpoint ``DrX`` * Memory breakpoint ``virtual protect`` #### LocalDebugger You can also debug your own process (or debug a process by injection) via the LocalDebugger. The LocalDebugger is an abstraction around Vectored Exception Handler (VEH) ```python import windows from windows.generated_def.winstructs import * import windows.native_exec.simple_x86 as x86 class SingleSteppingDebugger(windows.debug.LocalDebugger): SINGLE_STEP_COUNT = 4 def on_exception(self, exc): code = self.get_exception_code() context = self.get_exception_context() print("EXCEPTION !!!! Got a {0} at 0x{1:x}".format(code, context.pc)) self.SINGLE_STEP_COUNT -= 1 if self.SINGLE_STEP_COUNT: return self.single_step() return EXCEPTION_CONTINUE_EXECUTION class RewriteBreakpoint(windows.debug.HXBreakpoint): def trigger(self, dbg, exc): context = dbg.get_exception_context() print("GOT AN HXBP at 0x{0:x}".format(context.pc)) # Rewrite the infinite loop with 2 nop windows.current_process.write_memory(self.addr, "\x90\x90") # Ask for a single stepping return dbg.single_step() d = SingleSteppingDebugger() # Infinite loop + nop + ret code = x86.assemble("label :begin; jmp :begin; nop; ret") func = windows.native_exec.create_function(code, [PVOID]) print("Code addr = 0x{0:x}".format(func.code_addr)) # Create a thread that will infinite loop t = windows.current_process.create_thread(func.code_addr, 0) # Add a breakpoint on the infinite loop d.add_bp(RewriteBreakpoint(func.code_addr)) t.wait() print("Done!") ## Output ## Code addr = 0x6a0002 GOT AN HXBP at 0x6a0002 EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x6a0003 EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x6a0004 EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x6a0005 EXCEPTION !!!! Got a EXCEPTION_SINGLE_STEP(0x80000004L) at 0x770c7c04 Done! ``` The local debugger handles * Standard breakpoint ``int3`` * Hardware Execution breakpoint ``DrX`` ### Other stuff (see doc / samples) - Network - Services - COM [LKD_GITHUB]: https://github.com/sogeti-esec-lab/LKD/ [SAMPLE_DIR]: https://github.com/hakril/PythonForWindows/tree/master/samples [ONLINE_DOC]: http://hakril.github.io/PythonForWindows/ [ONLINE_SAMPLE]: http://hakril.github.io/PythonForWindows/build/html/sample.html [ONLINE_IATHOOK]: http://hakril.github.io/PythonForWindows/build/html/iat_hook.html