Skip to content

Repository files navigation

PythonForWindows

Pytest

PythonForWindows (PFW) 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.

Let's say that the codebase evolves with my needs, my researches and my curiosity.

Complete online documentation is available here You can find some examples of code in the samples directory or online.

PythonForWindows is principally known for:

If you have any issue, question or suggestion do not hesitate to create an issue or reach me out. I am always glad to have feedbacks from people using this project.

Installation

PythonForWindows is available on Pypi an this can be installed with

python -m pip install PythonForWindows

You can also install PythonForWindows by cloning it and using the setup.py script:

python setup.py install

Encoding & unicode

PythonForWindows support python2.7 & python3 and is currently tested for Python2.7, 3.6 & 3.11 via Github Workflow

Since 1.0.0, the code uses "wide APIs" whenever possible and accept/returns python3 str (py2.7 unicode type) almost everywhere. Any functions/APIs not accepting unicode string can be considered a bug if its not stated explicitly in the documentation.

Python2

PythonForWindows continues to support python2.7 as its the only way to have it running on Windows XP & Windows Server 2003 which are sadly still seen in production. Encoding errors at print time might be awkward for unicode string on python2, see the PythonForWindows encoding guide in the documentation.

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)

>>>importwindows>>>windows.current_process.bitness32>>>windows.current_process.token.integritySECURITY_MANDATORY_MEDIUM_RID(0x2000)
>>>proc= [pforpinwindows.system.processesifp.name=="notepad.exe"][0]
>>>proc<WinProcess"notepad.exe"pid16520at0x544e410>>>>proc.bitness64>>>proc.peb.modules[:3]
[<RemoteLoadedModule64"notepad.exe"at0x3671e90>, <RemoteLoadedModule64"ntdll.dll"at0x3671030>, <RemoteLoadedModule64"kernel32.dll"at0x3671080>]
>>>k32=proc.peb.modules[2]
>>>hex(k32.pe.exports["CreateFileW"])
'0x7ffee6761550L'>>>proc.threads[0]
<WinThread17688owner"notepad.exe"at0x53b47f0>>>>hex(proc.threads[0].context.Rip)
'0x7ffee68b54b0L'>>>proc.execute_python("import os")
True>>>proc.execute_python("exit(os.getpid() + 1)")
# execute_python raise if process diedTraceback (mostrecentcalllast):
...
WindowsError: <WinProcess"notepad.exe"pid16520 (DEAD) at0x579f610>diedduringexecutionofpythoncommand>>>calc<WinProcess"notepad.exe"pid16520 (DEAD) at0x579f610>>>>calc.exit_code16521L

System information

Information about the Windows computer running the script are available through the windows.system object.

>>>windows.system<windows.winobject.system.Systemobjectat0x03FEED10>>>>windows.system.bitness64>>>windows.system.computer_name'DESKTOP-VKUGISR'>>>windows.system.product_typeVER_NT_WORKSTATION(0x1)
>>>windows.system.version
(10, 0)
>>>windows.system.version_name'Windows 10'>>>windows.system.build_number'10.0.15063.608'# windows.system also contains dynamic lists about processes / threads / handles / ...>>>windows.system.handles[-2:]
[<Handlevalue=<0x5cc>inprocesspid=14360>, <Handlevalue=<0x28e4>inprocesspid=14360>]
>>>windows.system.processes[:2]
[<WinProcess"[System Process]"pid0at0x433f7d0>, <WinProcess"System"pid4at0x433fd30>]
>>>windows.system.logicaldrives[0]
<LogicalDrive "C:\" (DRIVE_FIXED)>>>>windows.system.services[23]
<ServiceA"Appinfo"SERVICE_RUNNING(0x4)>

IAT Hook

This codebase is born from my need to have IAT hooks implemented in Python. So the features is present (See online documentation 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).

>>>importwindows>>>help(windows.winproxy.VirtualAlloc)
# Help on function VirtualAlloc in module windows.winproxy:# VirtualAlloc(lpAddress=0, dwSize=NeededParameter, flAllocationType=MEM_COMMIT(0x1000L), flProtect=PAGE_EXECUTE_READWRITE(0x40L))# Errcheck:# raise WinproxyError if result is 0# Positional arguments>>>windows.winproxy.VirtualAlloc(0, 0x1000)
34537472# Keyword arguments>>>windows.winproxy.VirtualAlloc(dwSize=0x1000)
34603008# NeededParameter must be provided>>>windows.winproxy.VirtualAlloc()
"""Traceback (most recent call last):File "<stdin>", line 1, in <module>File "windows\winproxy.py", line 264, in VirtualAlloc return VirtualAlloc.ctypes_function(lpAddress, dwSize, flAllocationType, flProtect)File "windows\winproxy.py", line 130, in perform_call raise TypeError("{0}: Missing Mandatory parameter <{1}>".format(self.func_name, param_name))TypeError: VirtualAlloc: Missing Mandatory parameter <dwSize>"""# Error raises exception>>>windows.winproxy.VirtualAlloc(dwSize=0xffffffff)
"""Traceback (most recent call last):File "<stdin>", line 1, in <module>File "windows\winproxy.py", line 264, in VirtualAlloc return VirtualAlloc.ctypes_function(lpAddress, dwSize, flAllocationType, flProtect)File "windows\winproxy.py", line 133, in perform_call return self._cprototyped(*args)File "windows\winproxy.py", line 59, in kernel32_error_check raise WinproxyError(func_name)windows.winproxy.error.WinproxyError: VirtualAlloc: [Error 87] The parameter is incorrect."""

Native execution

To make the barrier between native and Python code thinner, PythonForWindows allows you to create native function callable from Python (thanks to ctypes) and also embed a simple x86/x64 assembler.

>>>importwindows.native_exec.simple_x86asx86>>>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# Assemblers can also be used in a more standard way>>>x86.assemble("cmp edi, 0; jnz :end; mov eax, 1; label :end; ret")
'\x81\xff\x00\x00\x00\x00u\x06\xc7\xc0\x01\x00\x00\x00\xc3'

Token / Security Descriptor

Objects easing access to some information about Token and SecurityDescriptor are also available.

>>>importwindows.security>>>importwindows.generated_defasgdef>>>tok=windows.current_process.token>>>tok<TokenTokenId=0x6a2b4550Type=TokenPrimary(0x1)>>>>tok.usernameu'hakril'>>>tok.typetagTOKEN_TYPE.TokenPrimary(0x1)
>>>tok.integritySECURITY_MANDATORY_MEDIUM_RID(0x2000)
>>>tok.duplicate(type=gdef.TokenImpersonation, impersonation_level=gdef.SecurityIdentification)
<TokenTokenId=0x6a3532ceType=TokenImpersonation(0x2) ImpersonationLevel=SecurityIdentification(0x1)># Security Descriptor>>>sd=windows.security.SecurityDescriptor.from_filename("c:\windows\system32\kernel32.dll")
>>>sd<SecurityDescriptorobjectat0x054E3DF0>>>>windows.utils.lookup_sid(sd.owner)
(u'NT SERVICE', u'TrustedInstaller')
>>>sd.dacl<Aclcount=6>>>>list(sd.dacl)
[<AccessAllowedACEmask=2032127>, <AccessAllowedACEmask=1179817>, <AccessAllowedACEmask=1179817>, <AccessAllowedACEmask=1179817>, <AccessAllowedACEmask=1179817>, <AccessAllowedACEmask=1179817>]
>>>sd.dacl[1].sid<PSID"S-1-5-32-544">

Wintrust

To easily script some signature check script, PythonForWindows implements some wrapper functions around wintrust.dll

>>>importwindows.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(0x800b0100))

WMI

To extract/play with even more information about the system, PythonForWindows is able to perform WMI request.

>>>importwindows>>>windows.system.wmi.select<boundmethodWmiNamespace.selectof<WmiNamespace"root\cimv2">>>>>windows.system.wmi.select("Win32_Process")[:3]
[<WmiObjectinstanceof"Win32_Process">, <WmiObjectinstanceof"Win32_Process">, <WmiObjectinstanceof"Win32_Process">]# Get WMI data for current process>>>windows.system.wmi.select("Win32_Process")[42]["Name"]
u'svchost.exe'>>>wmi_cp= [pforpinwindows.system.wmi.select("Win32_Process") ifint(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.

>>>importwindows>>>fromwindows.generated_defimportKEY_WRITE, KEY_READ, REG_QWORD>>>registry=windows.system.registry>>>cuuser_software=registry(r'HKEY_CURRENT_USER\Software')
>>>cuuser_software<PyHKey"HKEY_CURRENT_USER\Software">>>>cuuser_software.samKEY_READ(0x20019)
# Explore subkeys>>>cuuser_software.subkeys[:3]
[<PyHKey"HKEY_CURRENT_USER\Software\7-Zip">, <PyHKey"HKEY_CURRENT_USER\Software\AppDataLow">, <PyHKey"HKEY_CURRENT_USER\Software\Audacity">]
>>>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)]

Object manager

PythonForWindows uses the native Windows NT API to display some information about the object in the Object Manager's name space. Just like the well-known tools winobj.exe

>>>windows.system.object_manager.root<KernelObject"\" (type="Directory")># The objects of type "Directory" can be acceded just like a dict>>>list(windows.system.object_manager.root)[:3]
[u'PendingRenameMutex', u'ObjectTypes', u'storqosfltport']
# Find an object by its path>>>windows.system.object_manager["KnownDLLs\\kernel32.dll"]
<KernelObject"\KnownDLLs\kernel32.dll" (type="Section")>>>>k32=windows.system.object_manager["KnownDLLs\\kernel32.dll"]
>>>k32.name, k32.fullname, k32.type
('kernel32.dll', '\\KnownDLLs\\kernel32.dll', u'Section')
# Follow SymbolicLink object>>>windows.system.object_manager["\\KnownDLLs\\KnownDLLPath"]
<KernelObject"\KnownDLLs\KnownDLLPath" (type="SymbolicLink")>>>>windows.system.object_manager["\\KnownDLLs\\KnownDLLPath"].targetu'C:\\WINDOWS\\System32'

Scheduled Task

The windows.system.task_scheduler object allows to query and create scheduled task.

This part is still in developpement and the API may evolve

>>>windows.system.task_scheduler<TaskServiceat0x4774670>>>>windows.system.task_scheduler.root<TaskFolder "\"at0x4774710>>>>task=windows.system.task_scheduler.root.tasks[2]
>>>task<Task"DemoTask"at0x47748f0>>>>task.nameu'DemoTask'# Explore task actions>>>task.definition.actions[1]
<ExecActionat0x4774800>>>>task.definition.actions[1].pathu'c:\\windows\\python\\python.exe'>>>task.definition.actions[1].argumentsu'yolo.py --test'

Event logs

The windows.system.event_log object allows to query event logs.

This part is still in developpement and the API may evolve

>>>windows.system.event_log<windows.winobject.event_log.EvtlogManagerobjectat0x04A78270># Find a channel by its name>>>chan=windows.system.event_log["Microsoft-Windows-Windows Firewall With Advanced Security/Firewall"]
>>>chan<EvtChannel"Microsoft-Windows-Windows Firewall With Advanced Security/Firewall"># Open .evtx files>>>windows.system.event_log["test.evtx"]
<EvtFile"test.evtx"># Query a channel for all events>>>chan.query().all()[:2]
[<EvtEventid="2004"time="2018-07-12 07:44:08.081504">, <EvtEventid="2006"time="2018-07-12 07:57:59.806938">]
# Query a channel for some ids>>>chan.query(ids=2004).all()[:2]
[<EvtEventid="2004"time="2018-07-12 07:44:08.081504">, <EvtEventid="2004"time="2018-07-12 07:57:59.815156">]
# Query a channel via XPATH>>>evt=chan.query("Event/EventData[Data='Netflix']").all()[0]
# Explore event information>>>evt<EvtEventid="2006"time="2018-07-17 10:32:39.160423">>>>evt.data
{u'ModifyingUser': 69828304, u'RuleName': u'Netflix', u'ModifyingApplication': ...}

ALPC-RPC

ALPC

Classes around Advanced Local Procedure Call (ALPC) syscalls allows to simply write client and server able to send ALPC messages.

>>>importwindows.alpc# Test server juste reply to each message with "REQUEST '{msg_data}' RECEIVED">>>client=windows.alpc.AlpcClient(r"\RPC Control\PythonForWindowsTESTPORT")
>>>response=client.send_receive("Hello world !")
>>>response<windows.alpc.AlpcMessageobjectat0x04C0D5D0>>>>response.data"REQUEST 'Hello world !' RECEIVED"

Full client/server code for this example is available is the ALPC samples along with a more complex example.

RPC

An RPC-Client based using ALPC communication is also integred

# Server (port ALPC '\RPC Control\HelloRpc') offers:# Interface '41414141-4242-4343-4444-45464748494a' version 1.0# Method 1 -> int Add(int a, int b) -> return a + b# This Test server is a real RPC Server using rpcrt4.dll and compiled with VS2015.>>>importwindows.rpc>>>fromwindows.rpcimportndr>>>client=windows.rpc.RPCClient(r"\RPC Control\HelloRpc")
>>>client<windows.rpc.client.RPCClientobjectat0x0411E130>>>>iid=client.bind("41414141-4242-4343-4444-45464748494a")
>>>ndr_params=ndr.make_parameters([ndr.NdrLong] *2)
# NDR pack + Make RPC call to method 1.>>>resp=client.call(iid, 1, ndr_params.pack([41414141, 1010101]))
# Unpack the NDR response>>>result=ndr.NdrLong.unpack(ndr.NdrStream(resp))
>>>result42424242

A sample with the User Account Control (UAC) and one with lsasrv.dll are available in the RPC samples.

Debugger

PythonForWindows provides a standard debugger to debug other processes.

importwindowsimportwindows.debugimportwindows.testimportwindows.native_exec.simple_x86asx86importwindows.generated_defasgdeffromwindows.testimportpop_proc_32classMyDebugger(windows.debug.Debugger):
defon_exception(self, exception):
code=exception.ExceptionRecord.ExceptionCodeaddr=exception.ExceptionRecord.ExceptionAddressprint("Got exception {0} at 0x{1:x}".format(code, addr))
ifcode==gdef.EXCEPTION_ACCESS_VIOLATION:
print("Access Violation: kill target process")
self.current_process.exit()
calc=windows.test.pop_proc_32(dwCreationFlags=gdef.DEBUG_PROCESS)
d=MyDebugger(calc)
calc.execute(x86.assemble("int3; mov [0x42424242], EAX; ret"))
d.loop()
## Ouput ##GotexceptionEXCEPTION_BREAKPOINT(0x80000003) at0x77e13c7dGotexceptionEXCEPTION_BREAKPOINT(0x80000003) at0x230000GotexceptionEXCEPTION_ACCESS_VIOLATION(0xc0000005) at0x230001AccessViolation: killtargetprocess

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)

importwindowsfromwindows.generated_def.winstructsimport*importwindows.native_exec.simple_x86asx86classSingleSteppingDebugger(windows.debug.LocalDebugger):
SINGLE_STEP_COUNT=4defon_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-=1ifself.SINGLE_STEP_COUNT:
returnself.single_step()
returnEXCEPTION_CONTINUE_EXECUTIONclassRewriteBreakpoint(windows.debug.HXBreakpoint):
deftrigger(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 nopwindows.current_process.write_memory(self.addr, "\x90\x90")
# Ask for a single steppingreturndbg.single_step()
d=SingleSteppingDebugger()
# Infinite loop + nop + retcode=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 loopt=windows.current_process.create_thread(func.code_addr, 0)
# Add a breakpoint on the infinite loopd.add_bp(RewriteBreakpoint(func.code_addr))
t.wait()
print("Done!")
## Output ##Codeaddr=0x6a0002GOTANHXBPat0x6a0002EXCEPTION !!!! GotaEXCEPTION_SINGLE_STEP(0x80000004) at0x6a0003EXCEPTION !!!! GotaEXCEPTION_SINGLE_STEP(0x80000004) at0x6a0004EXCEPTION !!!! GotaEXCEPTION_SINGLE_STEP(0x80000004) at0x6a0005EXCEPTION !!!! GotaEXCEPTION_SINGLE_STEP(0x80000004) at0x770c7c04Done!

The local debugger handles

  • Standard breakpoint int3
  • Hardware Execution breakpoint DrX

Symbols

Classes around the Symbols APIs of dbghelp.dll are also implemented and can be used independently of the Debugger. The path of dbghelp.dll can also be given via the PFW_DBGHELP_PATH environment variable.

# Python3>>>fromwindows.debugimportsymbols>>># symbols.set_dbghelp_path(MY_DBGHELP_PATH)>>>symbols.engine.options=0# Disable defered load>>>sh=symbols.VirtualSymbolHandler()
>>>ntmod=sh.load_file(r"c:\windows\system32\ntdll.dll", addr=0x420000)
>>>ntmod<SymbolModulename="ntdll"type=SymPdbpdb="ntdll.pdb"addr=0x420000>>>>ntmod.name'ntdll'>>>ntmod.path'c:\\windows\\system32\\ntdll.dll'>>>ntmod.pdb'c:\\Symbols\\ntdll.pdb\\8D5D5ED5D5B8AA609A82600C14E3004D1\\ntdll.pdb'>>>sym=sh["ntdll!LdrLoadDll"]
>>>sym<SymbolInfoWname="LdrLoadDll"start=0x44a160tag=SymTagFunction>>>>sym.fullname'ntdll!LdrLoadDll'>>>hex(sym.addr)
'0x44a160'>>>sh.search("ntdll!*CreateFile")
[<SymbolInfoWname="EtwpCreateFile"start=0x47d9ectag=SymTagFunction>, <SymbolInfoWname="EtwpCreateFile"start=0x47d9ectag=SymTagPublicSymbol>, <SymbolInfoWname="NtCreateFile"start=0x4c03e0tag=SymTagPublicSymbol>, <SymbolInfoWname="ZwCreateFile"start=0x4c03e0tag=SymTagPublicSymbol>, <SymbolInfoWname="__imp_NtCreateFile"start=0x55cb70tag=SymTagPublicSymbol>]
# Some types exploration>>>peb=sh.get_type("ntdll!_PEB")
>>>peb<SymbolTypename="_PEB"tag=_SymTagEnum.SymTagUDT(0xb)>>>>peb.size2000>>>peb.children[:3]
[<SymbolTypename="InheritedAddressSpace"tag=_SymTagEnum.SymTagData(0x7)>, <SymbolTypename="ReadImageFileExecOptions"tag=_SymTagEnum.SymTagData(0x7)>, <SymbolTypename="BeingDebugged"tag=_SymTagEnum.SymTagData(0x7)>]
>>>peb.children[2].offset2

Other stuff (see doc / samples)

  • Network
  • COM

Acknowledgments

  • clmntb for his initial work on windows.security

About

A codebase aimed to make interaction with Windows and native execution easier

Topics

Resources

Stars

633 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages