Skip to content

Commit 92b3cfc

Browse files
committed
Adapt sample + docs for microsoft-store-python-injection
1 parent 082b131 commit 92b3cfc

File tree

4 files changed

+89
-23
lines changed

4 files changed

+89
-23
lines changed

docs/source/sample.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ Output
6969

7070
.. _token_sample:
7171

72+
73+
Microsoft Store Python Injection
74+
''''''''''''''''''''''''''''''''
75+
76+
Python execution in remote process fails with Microsoft Store builds of pythons (`mspython`), as the interpreter DLLs do not grant execute to Users.
77+
This sample shows a workaround by user https://github.com/dariushoule by copying needed mspython files to a temporary directory and injecting those instead.
78+
79+
.. literalinclude:: ..\..\samples\process\msstore_interpreter_remote_python.py
80+
81+
Output
82+
83+
.. literalinclude:: samples_output\process_msstore_interpreter_remote_python.txt
84+
85+
7286
Token
7387
"""""
7488

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
PS C:\Users\hakril\PythonForWindows> py .\samples\process\msstore_interpreter_remote_python.py
2+
Executable is: C:\Users\hakril\AppData\Local\Microsoft\WindowsApps\PythonSoftwareFoundation.Python.3.13_qbz5n2kfra8p0\python.exe
3+
Trying normal execute_python()
4+
Exception during proc1.execute_python():
5+
InjectionFailedError('Injection of <c:\\program files\\windowsapps\\pythonsoftwarefoundation.python.3.13_3.13.496.0_x64__qbz5n2kfra8p0\\vcruntime140.dll> failed')
6+
Trying mspython workaround:
7+
Executing python code!
8+
Injecting: C:\Users\hakril\AppData\Local\Temp\pfw_dllcache\vcruntime140.dll
9+
Injecting: C:\Users\hakril\AppData\Local\Temp\pfw_dllcache\python313.dll
10+
Executing more python code!
11+
Executing an error python code!
12+
Expected error during safe_execute_python
13+
b'Traceback (most recent call last):\n File "<string>", line 1, in <module>\nNameError: name \'BAD_VARIABLE\' is not defined\n'
14+
Sleeping a little
15+
Killing target process !
Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
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

@@ -12,14 +12,18 @@
1212
import shutil
1313
import tempfile
1414
import time
15+
import sys
16+
import struct
1517

1618
import windows
1719
from windows.generated_def.ntstatus import STATUS_THREAD_IS_TERMINATING
1820
from windows.generated_def.windef import CREATE_SUSPENDED
1921
from windows.generated_def.winstructs import PROCESS_INFORMATION, STARTUPINFOW
2022
from 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

2428
CACHE_DIR = os.path.join(tempfile.gettempdir(), 'pfw_dllcache')
2529
INTERPRETER_DIR = os.path.dirname(find_python_dll_to_inject(64)) # Tailor bitness to your needs
@@ -28,8 +32,8 @@
2832
def 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
5967
def 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:")
83109
proc_info = PROCESS_INFORMATION()
84110
StartupInfo = STARTUPINFOW()
85111
StartupInfo.cb = ctypes.sizeof(StartupInfo)
86112
windows.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
'\0PYTHONHOME={}\0PYTHONPATH={}\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+
95122
process = windows.winobject.process.WinProcess(pid=proc_info.dwProcessId, handle=proc_info.hProcess)
96123

97-
print("Executing python code!")
124+
print(" Executing python code!")
98125
safe_execute_python(process, """
99126
import windows
100127
windows.utils.create_console()
@@ -103,9 +130,19 @@ def safe_execute_python(process, code):
103130

104131
process.threads[0].resume()
105132

106-
print("Executing more python code!")
133+
print(" Executing more python code!")
107134
safe_execute_python(process, """
108135
print('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()

windows/winobject/process.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,7 +1134,7 @@ def execute_python(self, pycode):
11341134
11351135
.. note::
11361136
This method is incompatible with Microsoft Store builds of python, as the interpreter DLLs do not grant execute to Users.
1137-
See workaround: https://github.com/hakril/PythonForWindows/tree/master/samples/process/msstore_interpreter_remote_python.py
1137+
See workaround: https://hakril.github.io/PythonForWindows/build/html/sample.html#microsoft-store-python-injection
11381138
"""
11391139
return injection.safe_execute_python(self, pycode)
11401140

@@ -1145,7 +1145,7 @@ def execute_python_unsafe(self, pycode):
11451145
11461146
.. note::
11471147
This method is incompatible with Microsoft Store builds of python, as the interpreter DLLs do not grant execute to Users.
1148-
See workaround: https://github.com/hakril/PythonForWindows/tree/master/samples/process/msstore_interpreter_remote_python.py
1148+
See workaround: https://hakril.github.io/PythonForWindows/build/html/sample.html#microsoft-store-python-injection
11491149
"""
11501150
return injection.execute_python_code(self, pycode)
11511151

0 commit comments

Comments
 (0)