mirror of
https://github.com/hakril/PythonForWindows
synced 2026-06-08 14:31:45 +00:00
feedback revisions
This commit is contained in:
Vendored
+12
-2
@@ -577,15 +577,20 @@
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="windows.winobject.process.WinProcess.execute_python">
|
||||
<span class="sig-name descname"><span class="pre">execute_python</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">pycode</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/windows/winobject/process.html#WinProcess.execute_python"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#windows.winobject.process.WinProcess.execute_python" title="Link to this definition">¶</a></dt>
|
||||
<dd><p>Execute Python code into the remote process.</p>
|
||||
<dd><p>Execute Python code in the remote process.</p>
|
||||
<p>This function waits for the remote process to end and
|
||||
raises an exception if the remote thread raised one</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This method is incompatible with Microsoft Store builds of python, as the interpreter DLLs do not grant execute to Users,
|
||||
see workaround: <a class="reference external" href="https://github.com/hakril/PythonForWindows/tree/master/samples/process/msstore_interpreter_remote_python.py">https://github.com/hakril/PythonForWindows/tree/master/samples/process/msstore_interpreter_remote_python.py</a></p>
|
||||
</div>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="windows.winobject.process.WinProcess.execute_python_unsafe">
|
||||
<span class="sig-name descname"><span class="pre">execute_python_unsafe</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">pycode</span></span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/windows/winobject/process.html#WinProcess.execute_python_unsafe"><span class="viewcode-link"><span class="pre">[source]</span></span></a><a class="headerlink" href="#windows.winobject.process.WinProcess.execute_python_unsafe" title="Link to this definition">¶</a></dt>
|
||||
<dd><p>Execute Python code into the remote process.</p>
|
||||
<dd><p>Execute Python code in the remote process.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Return type<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><dl class="field-list simple">
|
||||
@@ -596,6 +601,11 @@ raises an exception if the remote thread raised one</p>
|
||||
</p>
|
||||
</dd>
|
||||
</dl>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This method is incompatible with Microsoft Store builds of python, as the interpreter DLLs do not grant execute to Users,
|
||||
see workaround: <a class="reference external" href="https://github.com/hakril/PythonForWindows/tree/master/samples/process/msstore_interpreter_remote_python.py">https://github.com/hakril/PythonForWindows/tree/master/samples/process/msstore_interpreter_remote_python.py</a></p>
|
||||
</div>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# 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 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_last_exception_data
|
||||
|
||||
|
||||
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:
|
||||
# If its not writeable good chance these DLLs are just already loaded somewhere
|
||||
pass
|
||||
# Preloading python DLL and vcruntime so they don't get loaded from the path tree with restrictive ACLs
|
||||
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:
|
||||
pass
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
print("Starting target")
|
||||
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)
|
||||
""")
|
||||
|
||||
process.wait()
|
||||
+7
-12
@@ -31,9 +31,7 @@ class TestCurrentProcessWithCheckGarbage(object):
|
||||
# Use module filename because this executable can be:
|
||||
# 1. A PyInstaller exe
|
||||
# 2. A Windows App execution alias (Microsoft Store builds)
|
||||
current_proc_filename = ctypes.create_string_buffer(1000)
|
||||
windows.winproxy.GetModuleFileNameA(None, current_proc_filename, 1000)
|
||||
assert os.path.basename(current_proc_filename.value.decode()) in windows.current_process.peb.modules[0].name
|
||||
assert os.path.basename(windows.current_process.peb.ProcessParameters[0].ImagePathName.str) in windows.current_process.peb.modules[0].name
|
||||
|
||||
def test_get_current_process_exe(self):
|
||||
exe = windows.current_process.peb.exe
|
||||
@@ -42,15 +40,12 @@ class TestCurrentProcessWithCheckGarbage(object):
|
||||
exe.bitness == exe_by_module.bitness
|
||||
|
||||
def test_current_process_pe_imports(self):
|
||||
python_module = windows.current_process.peb.modules[0]
|
||||
imp = python_module.pe.imports
|
||||
python_dll_regex = re.compile(r'python[0-9.]+dll', re.IGNORECASE)
|
||||
python_dll_imp = next((i for i in imp.keys() if python_dll_regex.match(i)), None)
|
||||
assert python_dll_imp is not None, 'Python dll not in python imports'
|
||||
|
||||
imp_id_iat = imp[python_dll_imp][0]
|
||||
mod_base = windows.winproxy.LoadLibraryA(python_dll_imp.encode())
|
||||
assert windows.winproxy.GetProcAddress(mod_base, imp_id_iat.name.encode()) == imp_id_iat.value
|
||||
k32_mod = windows.current_process.peb.modules[2]
|
||||
imp = k32_mod.pe.imports
|
||||
assert "ntdll.dll" in imp.keys(), 'ntdll.dll not in python imports'
|
||||
fn_id_iat = [f for f in imp["ntdll.dll"] if f.name == "NtCreateFile"][0]
|
||||
ntdll_base = windows.winproxy.LoadLibraryA(b"ntdll.dll")
|
||||
assert windows.winproxy.GetProcAddress(ntdll_base, b"NtCreateFile") == fn_id_iat.value
|
||||
|
||||
def test_current_process_pe_exports(self):
|
||||
mods = [m for m in windows.current_process.peb.modules if m.name == "kernel32.dll"]
|
||||
|
||||
+7
-1
@@ -33,7 +33,13 @@ def init():
|
||||
return initsecurity()
|
||||
|
||||
def initsecurity(): # Should take some parameters..
|
||||
return winproxy.CoInitializeSecurity(0, -1, None, 0, 0, RPC_C_IMP_LEVEL_IMPERSONATE, 0,0,0)
|
||||
try:
|
||||
winproxy.CoInitializeSecurity(0, -1, None, 0, 0, RPC_C_IMP_LEVEL_IMPERSONATE, 0,0,0)
|
||||
except OSError as e:
|
||||
if e.winerror & 0xFFFFFFFF != gdef.RPC_E_TOO_LATE:
|
||||
# RPC_E_TOO_LATE can happen when the python environment invokes CoInitializeSecurity before we get to it
|
||||
# mspython builds do this consistently.
|
||||
raise e
|
||||
|
||||
|
||||
class Dispatch(interfaces.IDispatch):
|
||||
|
||||
@@ -389,13 +389,6 @@ def execute_python_code(process, code):
|
||||
# Cache the value ?
|
||||
py_dll_name = get_dll_name_from_python_version()
|
||||
pydll_path = find_python_dll_to_inject(process.bitness)
|
||||
|
||||
# NB: Sandboxing on Windows Store apps prevents remote loading DLLs stored under "C:\Program Files*\WindowsApps\*"
|
||||
# This is relevant for remote python execution, as the sandboxed python process is the _only_ one that can
|
||||
# access its CRT and shared libraries.
|
||||
if '\\windowsapps\\' in pydll_path.lower():
|
||||
raise ValueError('Cannot execute remote python code from a sandboxed python interpreter. Install python outside of the Microsoft Store to resolve.')
|
||||
|
||||
if sys.version_info.major == 3:
|
||||
# FOr py3, we may have a per-user install.
|
||||
# Meaning that the vcruntime140.dll will not be in the injected process path
|
||||
|
||||
@@ -1127,7 +1127,10 @@ class WinProcess(Process):
|
||||
return [m for m in self.peb.modules if m.baseaddr == dllbase][0]
|
||||
|
||||
def execute_python(self, pycode):
|
||||
"""Execute Python code into the remote process.
|
||||
"""Execute Python code in the remote process.
|
||||
|
||||
This method is incompatible with Microsoft Store builds of python, as the interpreter DLLs do not grant execute to Users/*.
|
||||
See: samples/process/msstore_interpreter_remote_python.py for a workaround.
|
||||
|
||||
This function waits for the remote process to end and
|
||||
raises an exception if the remote thread raised one
|
||||
@@ -1135,7 +1138,10 @@ class WinProcess(Process):
|
||||
return injection.safe_execute_python(self, pycode)
|
||||
|
||||
def execute_python_unsafe(self, pycode):
|
||||
"""Execute Python code into the remote process.
|
||||
"""Execute Python code in the remote process.
|
||||
|
||||
This method is incompatible with Microsoft Store builds of python, as the interpreter DLLs do not grant execute to Users/*.
|
||||
See: samples/process/msstore_interpreter_remote_python.py for a workaround.
|
||||
|
||||
:rtype: :rtype: :class:`WinThread` or :class:`DeadThread` : The thread executing the python code
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user