Source code for memobj.process.windows.module
"""
process.get_symbol_address("user32.dll", "GetCursorPos") -> int
process.get_module("user32.dll").get_symbols()["GetCursorPos"] -> int
Module(...)
.name
.get_symbols()
.base_address
.executable_path
.size
.handle (windows only)
.process (multi-process helper)
"""
import ctypes
from typing import TYPE_CHECKING, Self
from collections.abc import Iterator
from memobj.process import Module
from .utils import CheckWindowsOsError, ModuleEntry32
if TYPE_CHECKING:
from .process import WindowsProcess
INVALID_HANDLE_VALUE: int = -1
TH32CS_SNAPMODULE: int = 0x8
[docs]
class WindowsModule(Module):
_symbols: dict[str, int] | None = None
# TODO: make a user facing iterface to this copying the object so it isn't changed while they're using it
# TODO: get wide character variants working
# adapted to python from https://learn.microsoft.com/en-us/windows/win32/toolhelp/traversing-the-module-list
@staticmethod
def _iter_modules(process: "WindowsProcess") -> Iterator[ModuleEntry32]:
"""
Note that the yielded modules are only valid for one iteration, i.e. references to them should not
be stored
"""
with CheckWindowsOsError():
# https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot
module_snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot(
TH32CS_SNAPMODULE, process.process_id
)
if module_snapshot == INVALID_HANDLE_VALUE:
raise ValueError("Creating module snapshot failed")
module_entry = ModuleEntry32()
module_entry.dwSize = ctypes.sizeof(ModuleEntry32)
# https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-module32first
success = ctypes.windll.kernel32.Module32First(
module_snapshot, ctypes.byref(module_entry)
)
if success == 0:
raise ValueError("Get first module failed")
yield module_entry
# https://learn.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-module32next
while (
ctypes.windll.kernel32.Module32Next(
module_snapshot, ctypes.byref(module_entry)
)
!= 0
):
yield module_entry
# https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
ctypes.windll.kernel32.CloseHandle(module_snapshot)
[docs]
@classmethod
def from_name(
cls, process: "WindowsProcess", name: str, *, ignore_case: bool = True
) -> Self:
if ignore_case:
name = name.lower()
for module in cls._iter_modules(process):
module_name = module.szModule.decode()
# use another variable to preserve case in WindowsModule object
if ignore_case:
compare_name = module_name.lower()
else:
compare_name = module_name
if compare_name == name:
return cls(
name=module_name,
base_address=module.modBaseAddr,
executable_path=module.szExePath.decode(),
size=module.modBaseSize,
process=process,
)
raise ValueError(f"No modules named {name}")
[docs]
@classmethod
def get_all_modules(cls, process: "WindowsProcess") -> list[Self]:
modules: list[Self] = []
for module in cls._iter_modules(process):
module_name = module.szModule.decode()
modules.append(
cls(
name=module_name,
base_address=module.modBaseAddr,
executable_path=module.szExePath.decode(),
size=module.modBaseSize,
process=process,
)
)
return modules
[docs]
def get_symbol_with_name(self, name: str) -> int:
try:
return self.get_symbols()[name]
except KeyError:
raise ValueError(f"No symbol named {name}")
[docs]
def get_symbols(self) -> dict[str, int]:
if self._symbols is not None:
return self._symbols
# lazy import windows only library
import pefile
portable_executable = pefile.PE(self.executable_path)
# this api is really bad
if not hasattr(portable_executable, "DIRECTORY_ENTRY_EXPORT"):
self._symbols = {}
return {}
symbols: dict[str, int] = {}
for export in portable_executable.DIRECTORY_ENTRY_EXPORT.symbols: # type: ignore
if export.name:
symbols[export.name.decode()] = export.address + self.base_address
else:
symbols[f"Ordinal {export.ordinal}"] = (
export.address + self.base_address
)
self._symbols = symbols
return symbols