Source code for pyjectify.windows.modules.pythonlib

from pyjectify.windows.core.defines import *
from pyjectify.windows.core.process import ProcessHandle
from pyjectify.windows.core.pe import PE

_py_prepare_hook = '''
import ctypes, inspect
func_name = bytes.fromhex('{func}').decode('utf8')
addr = ctypes.cast({ret_addr}, ctypes.POINTER(ctypes.c_ulonglong))
if func_name in globals():
    func = globals()[func_name]
    args = inspect.getfullargspec(func).annotations.values()
    if not args:
        args = [ctypes.c_void_p]
    if {ofunc}:
        globals()['o_'+func_name] = ctypes.WINFUNCTYPE(*args)({ofunc})
    addr.contents.value = ctypes.cast(ctypes.WINFUNCTYPE(*args)(func), ctypes.c_void_p).value
'''


[docs] class PythonLib: """This class provides methods to run Python inside a target process""" python_mod: int #: Handle to the Python library loaded in the target process def __init__(self, process: ProcessHandle, python_mod: PE | None = None) -> None: self._process = process self.python_mod = python_mod self._tstate = 0
[docs] def setprogramname(self, programname: str) -> None: """Set the value of the argv[0] argument to the main() function Args: programname: the new programname """ if programname: programname = bytes(programname, encoding='utf-16le') else: programname = b'\x00' addr = self._process.allocate(len(programname)) self._process.write(addr, programname) py_setprogramname = self.python_mod.base_addr + self.python_mod.exports['Py_SetProgramName'] thread = self._process.start_thread(py_setprogramname, addr) self._process.join_thread(thread) self._process.free(addr)
[docs] def setpath(self, pythonpath: str) -> None: """Set the default module search path Args: pythonpath: the PYTHON_PATH value """ if pythonpath: pythonpath = bytes(pythonpath, encoding='utf-16le') else: pythonpath = b'\x00' addr = self._process.allocate(len(pythonpath)) self._process.write(addr, pythonpath) py_setpath = self.python_mod.base_addr + self.python_mod.exports['Py_SetPath'] thread = self._process.start_thread(py_setpath, addr) self._process.join_thread(thread) self._process.free(addr)
[docs] def setpythonhome(self, pythonhome: str) -> None: """Set the default "home" directory This method calls Py_SetPythonHome Args: pythonhome: the PYTHON_HOME value """ if pythonhome: pythonhome = bytes(pythonhome, encoding='utf-16le') else: pythonhome = b'\x00' addr = self._process.allocate(len(pythonhome)) self._process.write(addr, pythonhome) py_setpythonhome = self.python_mod.base_addr + self.python_mod.exports['Py_SetPythonHome'] thread = self._process.start_thread(py_setpythonhome, addr) self._process.join_thread(thread) self._process.free(addr)
[docs] def isinitialized(self) -> bool: """Check if the Python interpreter is initialized in the target process This method calls Py_IsInitialized Returns: A boolean specifying if the Python interpreter is initialized """ py_isinitialized = self.python_mod.base_addr + self.python_mod.exports['Py_IsInitialized'] thread = self._process.start_thread(py_isinitialized) ret = self._process.join_thread(thread) return bool(ret)
[docs] def initialize(self, initsigs: int = 0) -> None: """Initialize the Python interpreter in the target process and release the GIL This method calls Py_IsInitialized + Py_InitializeEx + PyEval_SaveThread Args: initsigs: initsigs for Py_InitializeEx """ if not self.isinitialized(): py_initialize_ex = self.python_mod.base_addr + self.python_mod.exports['Py_InitializeEx'] py_eval_save_thread = self.python_mod.base_addr + self.python_mod.exports['PyEval_SaveThread'] ret = self._process.run_funcs([(py_initialize_ex, initsigs), (py_eval_save_thread, 0)]) self._tstate = ret[1]
[docs] def exec(self, py_code: str) -> None: """Execute python code in the target process (acquire and then release the GIL). Python interpreter MUST be initialized. This method calls PyEval_RestoreThread + PyRun_SimpleString + PyEval_SaveThread Args: py_code: the python code to run """ if not py_code: return py_eval_restore_thread = self.python_mod.base_addr + self.python_mod.exports['PyEval_RestoreThread'] py_run_simple_string = self.python_mod.base_addr + self.python_mod.exports['PyRun_SimpleString'] py_eval_save_thread = self.python_mod.base_addr + self.python_mod.exports['PyEval_SaveThread'] pycode_addr = self._process.allocate(len(py_code)) self._process.write(pycode_addr, py_code) ret = self._process.run_funcs([(py_eval_restore_thread, self._tstate), (py_run_simple_string, pycode_addr), (py_eval_save_thread, 0)]) self._tstate = ret[2] return
[docs] def finalize(self) -> None: """Undo all initializations of the Python interpreter in the target process This method calls PyEval_RestoreThread + Py_FinalizeEx """ py_eval_restore_thread = self.python_mod.base_addr + self.python_mod.exports['PyEval_RestoreThread'] py_finalize_ex = self.python_mod.base_addr + self.python_mod.exports['Py_FinalizeEx'] self._process.run_funcs([(py_eval_restore_thread, self._tstate), (py_finalize_ex, 0)]) self._tstate = 0
[docs] def prepare_hook(self, func: str, ofunc_addr: int = 0) -> int: """Utility for using a python function as a hook. This method returns the address of the function and optionally creates the global function o_{func} which points to the original function. Args: func: the name of the Python function to get the address ofunc_addr: address of the original function Returns: The address of the Python function. If the method succeeds, this address is nonzero. """ encoded_func = bytes(func, 'utf8').hex() ret_addr = self._process.allocate(8) py_code = _py_prepare_hook.format(func=encoded_func, ofunc=ofunc_addr, ret_addr=ret_addr) self.exec(py_code) addr = self._process.read(ret_addr, 8) self._process.free(ret_addr) return int.from_bytes(addr, 'little')