1- # Some python interpreters run in environments with restrictive ACLs (no Users/* execute) on bundled DLLs.
2- # The Microsoft Store version of python is the prime example of this.
3- #
4- # Remote execution of python is still possible by creating a minimal set of the dependencies outside of the restricted directory.
1+ # Some python interpreters run in environments with restrictive ACLs (no Users/* execute) on bundled DLLs.
2+ # The Microsoft Store version of python is the prime example of this.
3+ #
4+ # Remote execution of python is still possible by creating a minimal set of the dependencies outside of the restricted directory.
55#
66# This can be very helpful when operating PFW in environments with restrive GPOs / AppLocker.
77
1212import shutil
1313import tempfile
1414import time
15+ import sys
16+ import struct
1517
1618import windows
1719from windows .generated_def .ntstatus import STATUS_THREAD_IS_TERMINATING
1820from windows .generated_def .windef import CREATE_SUSPENDED
1921from windows .generated_def .winstructs import PROCESS_INFORMATION , STARTUPINFOW
2022from windows .injection import RemotePythonError , \
21- find_python_dll_to_inject , get_dll_name_from_python_version , inject_python_command , load_dll_in_remote_process , retrieve_last_exception_data
23+ find_python_dll_to_inject , get_dll_name_from_python_version , inject_python_command , load_dll_in_remote_process , retrieve_exc
24+
2225
26+ print ("Executable is: {0}" .format (sys .executable ))
2327
2428CACHE_DIR = os .path .join (tempfile .gettempdir (), 'pfw_dllcache' )
2529INTERPRETER_DIR = os .path .dirname (find_python_dll_to_inject (64 )) # Tailor bitness to your needs
2832def mspython_acl_workaround (target , pydll_path ):
2933 """
3034 Works around mspython ACL restrictions on mspython interpreters
31- by copying the critical DLLs to a TEMP dir and orienting the interpreter
32- against that TEMP dir.
35+ by copying the critical DLLs to a TEMP dir and orienting the interpreter
36+ against that TEMP dir.
3337 """
3438
3539 if not os .path .exists (CACHE_DIR ):
@@ -40,27 +44,32 @@ def mspython_acl_workaround(target, pydll_path):
4044 try :
4145 # Creates a copy of the DLL without bringing over restrictive ACLs
4246 shutil .copyfile (dll , cache_dll_path )
43- except :
47+ except Exception as e :
4448 # If its not writeable good chance these DLLs are just already loaded somewhere
45- pass
49+ print (e )
50+
4651 # Preloading python DLL and vcruntime so they don't get loaded from the path tree with restrictive ACLs
52+ print ("Injecting: {0}" .format (cache_dll_path ))
4753 load_dll_in_remote_process (target , cache_dll_path )
4854
4955 for dll in glob .glob (os .path .join (INTERPRETER_DIR , 'dlls' , '*' )):
5056 cache_dll_path = os .path .join (CACHE_DIR , os .path .basename (dll ))
5157 try :
5258 # Dynamic lib DLLs with restrictive ACLs copied to unrestricted parent
5359 shutil .copyfile (dll , cache_dll_path )
54- except :
55- pass
60+ except Exception as e :
61+ print (e )
62+
63+ target ._workaround_applied = True
5664
5765
5866# Adapted from windows\winobject\process.py
5967def execute_python_code (process , code ):
6068 py_dll_name = get_dll_name_from_python_version ()
6169 pydll_path = find_python_dll_to_inject (process .bitness )
62-
63- mspython_acl_workaround (process , pydll_path )
70+
71+ if not getattr (process , "_workaround_applied" , None ):
72+ mspython_acl_workaround (process , pydll_path )
6473 shellcode , pythoncode = inject_python_command (process , code , py_dll_name )
6574 t = process .create_thread (shellcode , pythoncode )
6675 return t
@@ -78,23 +87,41 @@ def safe_execute_python(process, code):
7887 data = retrieve_last_exception_data (process )
7988 raise RemotePythonError (data )
8089
81-
82- print ("Starting target" )
90+ # Adapted from windows\injection.py
91+ def retrieve_last_exception_data (process ):
92+ with process .allocated_memory (0x1000 ) as mem :
93+ execute_python_code (process , retrieve_exc .format (mem )).wait ()
94+ size = struct .unpack ("<I" , process .read_memory (mem , ctypes .sizeof (ctypes .c_uint )))[0 ]
95+ data = process .read_memory (mem + ctypes .sizeof (ctypes .c_uint ), size )
96+ return data
97+
98+ # First: show what happen when injecting mspython normally
99+ print ("Trying normal execute_python()" )
100+ proc1 = windows .utils .create_process (r"C:\Windows\system32\winver.exe" )
101+ try :
102+ proc1 .execute_python ("2 + 2 == 5" )
103+ except Exception as e :
104+ print (" Exception during proc1.execute_python():" )
105+ print (" {0}" .format (repr (e )))
106+ proc1 .exit ()
107+
108+ print ("Trying mspython workaround:" )
83109proc_info = PROCESS_INFORMATION ()
84110StartupInfo = STARTUPINFOW ()
85111StartupInfo .cb = ctypes .sizeof (StartupInfo )
86112windows .winproxy .CreateProcessW (
87- r"C:\Windows\system32\winver.exe" ,
88- dwCreationFlags = CREATE_SUSPENDED ,
113+ r"C:\Windows\system32\winver.exe" ,
114+ dwCreationFlags = CREATE_SUSPENDED ,
89115 # Point PYTHONHOME to the interpreter dir so non-DLL libs can load
90116 # Point PYTHONPATH to the newly created cache directory so DLL libs are loaded from there
91117 lpEnvironment = ('\0 ' .join ('{}={}' .format (e , v ) for e , v in os .environ .items ()) + \
92118 '\0 PYTHONHOME={}\0 PYTHONPATH={}\0 \0 ' .format (INTERPRETER_DIR , CACHE_DIR )).encode (),
93- lpProcessInformation = ctypes .byref (proc_info ),
119+ lpProcessInformation = ctypes .byref (proc_info ),
94120 lpStartupInfo = ctypes .byref (StartupInfo ))
121+
95122process = windows .winobject .process .WinProcess (pid = proc_info .dwProcessId , handle = proc_info .hProcess )
96123
97- print ("Executing python code!" )
124+ print (" Executing python code!" )
98125safe_execute_python (process , """
99126import windows
100127windows.utils.create_console()
@@ -103,9 +130,19 @@ def safe_execute_python(process, code):
103130
104131process .threads [0 ].resume ()
105132
106- print ("Executing more python code!" )
133+ print (" Executing more python code!" )
107134safe_execute_python (process , """
108135print('hello from inside the resumed process!', flush=True)
109136""" )
110137
111- process .wait ()
138+ print (" Executing an error python code!" )
139+ try :
140+ safe_execute_python (process , """BAD_VARIABLE""" )
141+ except RemotePythonError as e :
142+ print (" Expected error during safe_execute_python" )
143+ print (" {0}" .format (e ))
144+
145+ print (" Sleeping a little" )
146+ time .sleep (5 )
147+ print (" Killing target process !" )
148+ process .exit ()
0 commit comments