Кастомный IProcess
Как подключить свой драйвер или свой способ чтения памяти к Memory API
Как сделать кастомный IProcess
Если у вас уже есть свой драйвер, bridge или другой способ читать память, почти никогда не нужно писать "ещё один Memory API". Обычно достаточно собрать два слоя:
IMemoryBackendдля rawread/writeIProcess, который описывает процесс и отдаётMemory
После этого поверх вашего backend'а сразу начинают работать типизированное чтение, MemoryOfModule(...), импорты, экспорты, сигнатуры и code cave.
На что ориентироваться в коде
NativeLocalProcess— хороший пример process-wrapper поверх уже готового backend'аLCProcess— хороший ориентир для backend'а, который в основном про чтение и анализKDProcess— пример backend'а с более глубоким control API
Минимальная схема
IMemoryBackend остаётся очень маленьким:
public interface IMemoryBackend : IDisposable, IMemorySpan
{
bool IsValid { get; }
bool TryReadMemory(IntPtr address, Span<byte> target);
bool TryWriteMemory(IntPtr address, ReadOnlySpan<byte> source);
}
Чаще всего именно сюда ложится ваш вызов драйвера, IOCTL, RPC или bridge к DMA.
Сверху реализация IProcess связывает транспорт с контекстом процесса. Входы интеграции — уже открытый клиент транспорта, PID в его адресном пространстве, сведения о модулях/потоках/регионах и явный владелец подключения. Универсального MyDriverClient в SDK нет: эти операции должен дать ваш транспорт.
Полный минимальный контракт сборки такой:
IMemoryBackendотдаётBaseAddress(long),MemorySize(int),IsValidи raw-чтение/запись по абсолютному адресу.trueозначает успешную операцию; интерфейс не сообщает число переданных байт и не принимает токен отмены. Не скрывайте частичное чтение как полное.- Создайте
new MemoryAccessor(log, backend)и верните его какIProcessMemory Memory. Типизированное чтение, строки и PE-helper'ы работают через этот accessor, их не нужно реализовывать в драйвере. IProcessтакже предоставляетIFluentLog Log,bool IsValid,string? ProcessName,int ProcessId,GetProcessModules(),GetThreads(),VirtualQuery(IntPtr)иGetMemoryRegions(). Модули возвращают реальные базы/размеры цели, без нихMemoryOfModuleне сможет выбрать диапазон.- Владелец останавливает потребителей и освобождает accessor, backend и транспорт согласно выбранному владению. Конструктор
MemoryAccessorне привязывает backend к своемуDispose; нельзя считать одну эту строку полной реализацией lifetime.
Результат — объект, который можно передать обычному коду без знания транспорта:
static int ReadValue(EyeAuras.Memory.IProcess process, long absoluteAddress)
{
return process.Memory.Read<int>(absoluteAddress);
}
Метод возвращает int или передаёт ошибку чтения вызывающему коду. Он заимствует процесс, не закрывает его и не доказывает работоспособность транспорта. Контракты сверены с EyeAuras 20fd3d4; подключение конкретного драйвера остаётся входом интеграции.
Ещё один вариант - DLL внутри процесса и IPC
Не каждый кастомный IProcess обязан читать память снаружи. Ещё один рабочий вариант - загрузить свою DLL внутрь процесса, поднять там транспорт и дальше общаться с ней по named pipe, shared memory или другому IPC.
В такой схеме внешний код обычно больше не ходит в память напрямую. Вместо этого он отправляет запрос агенту внутри процесса:
- прочитать диапазон памяти
- записать диапазон памяти
- вернуть список модулей
- вернуть список потоков
- вернуть карту памяти
Снаружи это всё равно обычно выглядит как тот же самый стек:
- transport client
IMemoryBackendповерх транспортаIProcessповерх backend'а
То есть верхний слой Memory API почти не меняется. Меняется только то, откуда реально берутся данные.
Такой подход полезен, если вам нужно:
- выполнять часть логики прямо внутри процесса
- читать данные, которые удобнее собирать internal-кодом
- поверх raw memory сразу отдавать более высокий уровень, например готовые структуры игры
У такого варианта есть и цена:
- сначала нужно как-то доставить
DLLвнутрь процесса - протокол, сериализация и versioning транспорта теперь на вас
- падение агента может уронить целевой процесс
- с точки зрения защиты это уже другой класс техники, не просто external read
Если вас интересует именно доставка DLL внутрь процесса, дальше стоит читать DLL inject.
Что стоит реализовать сразу
Если хотите, чтобы интеграция была действительно удобной, не ограничивайтесь одним TryReadMemory(...). Практический минимум такой:
MemoryGetProcessModules()GetThreads()VirtualQuery(...)GetMemoryRegions()
Без модулей вы теряете нормальный MemoryOfModule(...). Без регионов и VirtualQuery(...) хуже живёт анализ памяти.
Что можно добавить потом
Если ваш драйвер умеет больше, поверх IProcess можно объявить и дополнительные capability-интерфейсы:
IProcessControlApi— suspend, allocate, protect,remote threadIProcessSupportsManualMappingIProcessSupportsUserApc
То есть базовый путь такой:
- сначала сделать стабильное чтение и описание процесса
- потом добавить control API
- только потом идти в
DLL injectиmanual mapping
Частые ошибки
- Пытаться реализовать PE-парсинг, сигнатуры и строки прямо внутри драйверного класса.
- Не отдавать модули и карту памяти, ограничиваясь только raw read/write.
- Смешивать low-level backend и process-wrapper в один большой класс.
- Сразу начинать с инжекта, не проверив обычное чтение, модули и сигнатуры.
Если у игры нестандартный layout структур, это обычно решается не новым backend'ом, а отдельным mapper'ом поверх уже готового IProcess.