feedback revisions

This commit is contained in:
Darius Houle
2025-01-26 23:38:50 -07:00
parent 6715b08ed7
commit 52859d19c4
6 changed files with 145 additions and 24 deletions
+12 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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):
-7
View File
@@ -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
+8 -2
View File
@@ -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
"""