Кастомный 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 и сразу отдаёте MemoryAccessor:
public sealed class MyDriverProcess : DisposableReactiveObject, IProcess
{
public MyDriverProcess(MyDriverClient driver, int processId, string? processName = null)
{
ProcessId = processId;
ProcessName = processName;
var memoryBackend = new MyDriverMemoryBackend(driver, processId).AddTo(Anchors);
Memory = new MemoryAccessor(Log, memoryBackend).AddTo(Anchors);
}
public string? ProcessName { get; }
public int ProcessId { get; }
public IProcessMemory Memory { get; }
public IReadOnlyList<ProcessModuleInformation> GetProcessModules() => driver.GetModules(ProcessId);
public IReadOnlyList<ProcessThreadInformation> GetThreads() => driver.GetThreads(ProcessId);
public MemoryBasicInformation VirtualQuery(IntPtr address) => driver.VirtualQuery(ProcessId, address);
public IReadOnlyList<MemoryBasicInformation> GetMemoryRegions() => driver.GetMemoryRegions(ProcessId);
}
Ключевая строка здесь одна:
Memory = new MemoryAccessor(Log, memoryBackend);
Именно она подключает весь верхний слой EyeAuras.
Ещё один вариант - 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.