mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
148 lines
5.5 KiB
Python
148 lines
5.5 KiB
Python
# Some python interpreters run in environments with restrictive ACLs (no Users/* execute) on bundled DLLs.
|
|
# The Microsoft Store version of python is the prime example of this.
|
|
#
|
|
# Remote execution of python is still possible by creating a minimal set of the dependencies outside of the restricted directory.
|
|
#
|
|
# This can be very helpful when operating PFW in environments with restrive GPOs / AppLocker.
|
|
|
|
|
|
import ctypes
|
|
import glob
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import time
|
|
import sys
|
|
import struct
|
|
|
|
import windows
|
|
from windows.generated_def.ntstatus import STATUS_THREAD_IS_TERMINATING
|
|
from windows.generated_def.windef import CREATE_SUSPENDED
|
|
from windows.generated_def.winstructs import PROCESS_INFORMATION, STARTUPINFOW
|
|
from windows.injection import RemotePythonError, \
|
|
find_python_dll_to_inject, get_dll_name_from_python_version, inject_python_command, load_dll_in_remote_process, retrieve_exc
|
|
|
|
|
|
print("Executable is: {0}".format(sys.executable))
|
|
|
|
CACHE_DIR = os.path.join(tempfile.gettempdir(), 'pfw_dllcache')
|
|
INTERPRETER_DIR = os.path.dirname(find_python_dll_to_inject(64)) # Tailor bitness to your needs
|
|
|
|
|
|
def mspython_acl_workaround(target, pydll_path):
|
|
"""
|
|
Works around mspython ACL restrictions on mspython interpreters
|
|
by copying the critical DLLs to a TEMP dir and orienting the interpreter
|
|
against that TEMP dir.
|
|
"""
|
|
|
|
if not os.path.exists(CACHE_DIR):
|
|
os.mkdir(CACHE_DIR)
|
|
|
|
for dll in [os.path.join(INTERPRETER_DIR, 'vcruntime140.dll'), pydll_path]:
|
|
cache_dll_path = os.path.join(CACHE_DIR, os.path.basename(dll))
|
|
try:
|
|
# Creates a copy of the DLL without bringing over restrictive ACLs
|
|
shutil.copyfile(dll, cache_dll_path)
|
|
except Exception as e:
|
|
# If its not writeable good chance these DLLs are just already loaded somewhere
|
|
print(e)
|
|
|
|
# Preloading python DLL and vcruntime so they don't get loaded from the path tree with restrictive ACLs
|
|
print("Injecting: {0}".format(cache_dll_path))
|
|
load_dll_in_remote_process(target, cache_dll_path)
|
|
|
|
for dll in glob.glob(os.path.join(INTERPRETER_DIR, 'dlls', '*')):
|
|
cache_dll_path = os.path.join(CACHE_DIR, os.path.basename(dll))
|
|
try:
|
|
# Dynamic lib DLLs with restrictive ACLs copied to unrestricted parent
|
|
shutil.copyfile(dll, cache_dll_path)
|
|
except Exception as e:
|
|
print(e)
|
|
|
|
target._workaround_applied = True
|
|
|
|
|
|
# Adapted from windows\winobject\process.py
|
|
def execute_python_code(process, code):
|
|
py_dll_name = get_dll_name_from_python_version()
|
|
pydll_path = find_python_dll_to_inject(process.bitness)
|
|
|
|
if not getattr(process, "_workaround_applied", None):
|
|
mspython_acl_workaround(process, pydll_path)
|
|
shellcode, pythoncode = inject_python_command(process, code, py_dll_name)
|
|
t = process.create_thread(shellcode, pythoncode)
|
|
return t
|
|
|
|
|
|
def safe_execute_python(process, code):
|
|
t = execute_python_code(process, code)
|
|
t.wait() # Wait termination of the thread
|
|
if t.exit_code == 0:
|
|
return True
|
|
if t.exit_code == STATUS_THREAD_IS_TERMINATING or process.is_exit:
|
|
raise WindowsError("{0} died during execution of python command".format(process))
|
|
if t.exit_code != 0xffffffff:
|
|
raise ValueError("Unknown exit code {0}".format(hex(t.exit_code)))
|
|
data = retrieve_last_exception_data(process)
|
|
raise RemotePythonError(data)
|
|
|
|
# Adapted from windows\injection.py
|
|
def retrieve_last_exception_data(process):
|
|
with process.allocated_memory(0x1000) as mem:
|
|
execute_python_code(process, retrieve_exc.format(mem)).wait()
|
|
size = struct.unpack("<I", process.read_memory(mem, ctypes.sizeof(ctypes.c_uint)))[0]
|
|
data = process.read_memory(mem + ctypes.sizeof(ctypes.c_uint), size)
|
|
return data
|
|
|
|
# First: show what happen when injecting mspython normally
|
|
print("Trying normal execute_python()")
|
|
proc1 = windows.utils.create_process(r"C:\Windows\system32\winver.exe")
|
|
try:
|
|
proc1.execute_python("2 + 2 == 5")
|
|
except Exception as e:
|
|
print(" Exception during proc1.execute_python():")
|
|
print(" {0}".format(repr(e)))
|
|
proc1.exit()
|
|
|
|
print("Trying mspython workaround:")
|
|
proc_info = PROCESS_INFORMATION()
|
|
StartupInfo = STARTUPINFOW()
|
|
StartupInfo.cb = ctypes.sizeof(StartupInfo)
|
|
windows.winproxy.CreateProcessW(
|
|
r"C:\Windows\system32\winver.exe",
|
|
dwCreationFlags=CREATE_SUSPENDED,
|
|
# Point PYTHONHOME to the interpreter dir so non-DLL libs can load
|
|
# Point PYTHONPATH to the newly created cache directory so DLL libs are loaded from there
|
|
lpEnvironment=('\0'.join('{}={}'.format(e, v) for e, v in os.environ.items()) + \
|
|
'\0PYTHONHOME={}\0PYTHONPATH={}\0\0'.format(INTERPRETER_DIR, CACHE_DIR)).encode(),
|
|
lpProcessInformation=ctypes.byref(proc_info),
|
|
lpStartupInfo=ctypes.byref(StartupInfo))
|
|
|
|
process = windows.winobject.process.WinProcess(pid=proc_info.dwProcessId, handle=proc_info.hProcess)
|
|
|
|
print(" Executing python code!")
|
|
safe_execute_python(process, """
|
|
import windows
|
|
windows.utils.create_console()
|
|
print('hello from inside the suspended process!', flush=True)
|
|
""")
|
|
|
|
process.threads[0].resume()
|
|
|
|
print(" Executing more python code!")
|
|
safe_execute_python(process, """
|
|
print('hello from inside the resumed process!', flush=True)
|
|
""")
|
|
|
|
print(" Executing an error python code!")
|
|
try:
|
|
safe_execute_python(process, """BAD_VARIABLE""")
|
|
except RemotePythonError as e:
|
|
print(" Expected error during safe_execute_python")
|
|
print(" {0}".format(e))
|
|
|
|
print(" Sleeping a little")
|
|
time.sleep(5)
|
|
print(" Killing target process !")
|
|
process.exit() |