DLL inject нужен, когда код должен выполняться уже внутри target-процесса. Обычно это нужно для hook'ов, internal-логики, вызова внутренних функций и сценариев, где внешний reader уже неудобен.
Если нужен внешний фон, полезны DLL Injector, DLL Memory Hack и Code Caves & DLL's.
Ниже примеры даны для LocalProcess, потому что это самый прямой старт. Те же методы есть и у NativeLocalProcess. LCProcess для inject-сценариев обычно не берут.
Это самый простой маршрут.
using EyeAuras.Memory;
using var process = LocalProcess.ByProcessName("game");
process.InjectDll(@"C:\Tools\MyHack.dll");
Внутри Memory API этот вызов делает классический LoadLibraryW-инжект:
LoadLibraryW в target-процессеУникальные черты этого пути:
DllMain и TLS callbacksИменно поэтому InjectDll(...) почти всегда должен быть первым выбором, если вам просто нужно загрузить библиотеку и проверить гипотезу.
Что важно помнить:
LoadLibraryWЕсли InjectDll(...) это готовый high-level helper, то CreateThread(...) это уже более низкоуровневый строительный блок.
Memory API даёт его через IProcessControlApi:
var threadId = process.CreateThread(startAddress, parameter);
Это полезно, когда вы уже сами подготовили всё остальное:
На практике это часто выглядит так:
using System.Linq;
using System.Text;
using EyeAuras.Memory;
using var process = LocalProcess.ByProcessName("game");
var dllPath = @"C:\Tools\MyHack.dll";
var dllPathBytes = Encoding.Unicode.GetBytes(dllPath + "\0");
var remotePath = process.AllocateMemory(
dllPathBytes.Length,
MemoryProtectionType.ReadWrite);
process.Memory.Write(remotePath.Address, dllPathBytes);
using var kernel32 = process.MemoryOfModule("kernel32.dll");
var loadLibraryWExport = kernel32.ReadExports()
.First(x => x.ExportName == "LoadLibraryW");
var loadLibraryW = new IntPtr(kernel32.BaseAddress + (long) loadLibraryWExport.RVA);
var threadId = process.CreateThread(loadLibraryW, remotePath.Address);
По сути это уже "ручной" вариант того, что делает InjectDll(...), только без готовой обёртки.
Уникальные черты этого пути:
startAddress и parameterLoadLibraryW, но и любую другую подходящую функциюЕсли нужен запуск через APC, в Memory API для этого есть QueueUserApc(...).
Практический пример: положить путь к DLL в remote memory, найти LoadLibraryW и поставить его в очередь существующему потоку.
using System.Linq;
using System.Text;
using EyeAuras.Memory;
using var process = LocalProcess.ByProcessName("game");
var dllPath = @"C:\Tools\MyHack.dll";
var dllPathBytes = Encoding.Unicode.GetBytes(dllPath + "\0");
var remotePath = process.AllocateMemory(
IntPtr.Zero,
dllPathBytes.Length,
MemoryAllocationType.Commit | MemoryAllocationType.Reserve,
MemoryProtectionType.ReadWrite);
process.Memory.Write(remotePath.Address, dllPathBytes);
using var kernel32 = process.MemoryOfModule("kernel32.dll");
var loadLibraryWExport = kernel32.ReadExports()
.First(x => x.ExportName == "LoadLibraryW");
var loadLibraryW = new IntPtr(kernel32.BaseAddress + (long) loadLibraryWExport.RVA);
var threadId = process.GetThreads().First().ThreadId;
process.QueueUserApc(threadId, loadLibraryW, remotePath.Address);
Это уже полностью memory-api-шный маршрут:
AllocateMemory(...)process.Memory.Write(...)MemoryOfModule(...)ReadExports()QueueUserApc(...)Уникальные черты этого пути:
Главная особенность APC:
alertable waitИз этого следуют и практические последствия:
APC нельзя считать "мгновенным запуском"Этот маршрут нужен не для старта, а для случаев, когда вам действительно нужен запуск через уже существующий поток.
Если LoadLibraryW вам не подходит, в Memory API есть InjectDllViaManualMapping(...).
using EyeAuras.Memory;
using EyeAuras.Memory.Scaffolding;
using var process = LocalProcess.ByProcessName("game");
var mapping = process.InjectDllViaManualMapping(
@"C:\Tools\MyHack.dll",
LocalProcessMmapFlags.None);
Log.Info($"Base={mapping.BaseAddress.ToHexadecimal()}");
Log.Info($"EntryPoint={mapping.EntryPoint.ToHexadecimal()}");
using EyeAuras.Memory;
using var process = LocalProcess.ByProcessName("game");
var dllBytes = File.ReadAllBytes(@"C:\Tools\MyHack.dll");
var mapping = process.InjectDllViaManualMapping(dllBytes);
Уникальные черты этого пути:
byte[]BaseAddress и EntryPointТо есть это уже не "попросить Windows загрузить DLL", а отдельный loader-путь поверх Memory API.
InjectDllViaManualMapping(...) возвращает ManualMappedLibraryDescriptor:
BaseAddressEntryPointЭто полезно в advanced-сценариях, где вы хотите не просто загрузить DLL, а ещё и отдельно управлять её стартом или опираться на её базовый адрес.
LocalProcessMmapFlags.None — обычный маршрутLocalProcessMmapFlags.DiscardHeaders — удалить PE-заголовки после маппингаLocalProcessMmapFlags.SkipInitRoutines — не вызывать DllMain и TLS callbacksПрактический смысл флагов такой:
None — нормальный дефолтDiscardHeaders — уже более узкий сценарийSkipInitRoutines — advanced-режим, потому что библиотека может оказаться загруженной, но не инициализированнойЕсли включать SkipInitRoutines, то дальше вы уже сами отвечаете за запуск init-логики. В тестах EyeAuras для этого используются отдельные trampoline и явный вызов entrypoint через thread или APC.
PIC это position-independent code, то есть код, который может выполняться не по фиксированному адресу, а из произвольного места в памяти.
В практическом RE-мире это обычно:
Это уже самый продвинутый путь в этой статье, потому что здесь вы загружаете не DLL через loader и не готовую Windows API-функцию, а свой собственный исполняемый код.
Практически это обычно выглядит так:
using EyeAuras.Memory;
using var process = LocalProcess.ByProcessName("game");
byte[] picBytes = GetPicPayloadSomehow();
var remotePic = process.AllocateMemory(
picBytes.Length,
MemoryProtectionType.ReadWrite);
process.Memory.Write(remotePic.Address, picBytes);
process.ProtectMemory(remotePic.Address, picBytes.Length, MemoryProtectionType.ExecuteRead);
var threadId = process.CreateThread(remotePic.Address, IntPtr.Zero);
Здесь Memory API даёт вам базовые кирпичи:
AllocateMemory(...)process.Memory.Write(...)ProtectMemory(...)CreateThread(...)А вся ответственность за сам payload уже на вас:
Именно поэтому PIC лучше воспринимать не как "ещё один inject-метод", а как отдельный advanced-сценарий запуска своего кода внутри процесса.
Если нет специальной причины делать иначе:
InjectDll(...)CreateThread(...)QueueUserApc(...)InjectDllViaManualMapping(...)PICx86 и x64 смешивать нельзяInjectDll(...) любит полный и валидный путь на дискеCreateThread(...) требует, чтобы startAddress реально указывал на исполняемый код внутри target-процессаQueueUserApc(...) требует корректного потока и корректного момента выполненияmanual mapping не делает DLL автоматически совместимой с любым случаемPIC требует максимальной аккуратности, потому что здесь уже почти нет "страховочных" слоёв loader'а