# WhiteMagic Architecture ## Overview WhiteMagic is built as a layered architecture that provides multiple levels of abstraction over Windows process-introspection APIs. This design allows consumers to choose the right level of control for their use case, from low-level memory operations to high-level ergonomic APIs. ## Layer Structure ``` ┌──────────────────────────────────────────────────────────────┐ │ Magic (Facade) │ │ High-level entry point: Magic.Open(), Magic.OpenInProcess() │ └───────────────────────┬──────────────────────────────────────┘ │ ┌───────────────┴───────────────┐ │ │ ┌───────▼─────────┐ ┌────────▼────────┐ │ MemoryBase │ │ High-Level API │ │ (Abstract) │ │ │ ├─────────────────┤ ├─────────────────┤ │ ExternalReader │ │ RemotePointer │ │ InProcessReader │ │ RemoteModule │ │ │ │ RemoteFunction │ │ ┌──────────────┐│ │ ManagedPeb/TEB │ │ │ Hooking ││ │ Window/Input │ │ │ DetourMgr ││ └─────────────────┘ │ │ PatchMgr ││ │ └──────────────┘│ └─────────┬───────┘ │ ┌─────▼─────┬───────────┬───────────┐ │ │ │ │ ┌───▼────┐ ┌────▼──┐ ┌──────▼──┐ ┌──────▼───┐ │Native │ │Discov.│ │Execution│ │Assembly │ │P/Invoke│ │Scan/PE│ │3-tier │ │IAssembler│ └────────┘ └───────┘ └─────────┘ └──────────┘ ``` ## Core Layer ### MemoryBase (Abstract) The foundation of WhiteMagic is the `MemoryBase` abstract class, which defines the contract for all memory operations: **Key responsibilities:** - Abstract memory read/write operations - Host for `MarshalCache` optimization - Owner of `PatchManager` and `DetourManager` - Relative/absolute addressing support **Design decision (D1):** Dual-mode readers share the same abstract interface, allowing high-level code (pattern scanning, patching, etc.) to work in both external and in-process modes without changes. ### ExternalReader Implements `MemoryBase` for out-of-process operations: **Mechanism:** Uses `ReadProcessMemory`/`WriteProcessMemory` via P/Invoke **Handle:** `SafeMemoryHandle` to target process **Use case:** Primary mode for automation hosts ### InProcessReader Implements `MemoryBase` for injected code: **Mechanism:** Uses `ReadProcessMemory`/`WriteProcessMemory` on self-handle **Handle:** `SafeMemoryHandle` to current process **Use case:** Once a managed DLL is injected, enables delegate calls and detours **Design decision (D1 revision):** InProcessReader uses RPM-on-self instead of unsafe direct pointer deref because .NET cannot catch `AccessViolationException`, so a bad deref kills the host with no soft-failure path. The in-process speed win moves to the delegate-call and detour paths, not the reader. ## Discovery Layer ### PatternScanner Scans target memory for byte patterns with wildcard support: **Features:** - IDA-style hex patterns (`48 8B ? ? ? ? ?`) - Optional caching to avoid repeated scans - Module-relative or absolute addressing ### PeHeaderParser Parses PE headers to extract export information: **Features:** - Export table parsing - Forwarder resolution (e.g., `kernel32!HeapAlloc` → `NTDLL.RtlAllocateHeap`) - Ordinal export support ## Execution Layer (Three-Tier Model) ### RemoteThreadExecutor **Mechanism:** `CreateRemoteThread` + hand-assembled convention stubs **Use for:** Thread-agnostic payloads (pure WinAPI, self-contained code, DLL injection) **Risk:** NOT crash-safe for single-threaded target state ### MainThreadPump **Mechanism:** Detour on per-frame function + work queue **Use for:** State-sensitive calls (default for target-state access) **Safety:** Crash-safe — runs on target's own thread **How it works:** 1. Installs a detour on a per-frame function (e.g., D3D `EndScene`) 2. Each frame, the hook drains a thread-safe queue 3. Work items run synchronously in target's context 4. Results/exceptions returned via `TaskCompletionSource` ### InProcessInvoker **Mechanism:** `Marshal.GetDelegateForFunctionPointer` **Use for:** In-process delegates after injection **Benefit:** Zero thread-crossing overhead ## Hooking Layer ### DetourManager **Scope:** In-process only **Features:** - Inline `E9 jmp` detours over function prologues - `CallOriginal` support via trampoline - `Apply`/`Remove` operations - Auto-restore on `Dispose` **Safety:** Prologue validation via `IAssembler.GetPrologueLength` (optional Iced backend) ### PatchManager **Scope:** Both external and in-process **Features:** - Named byte patches - `Apply`/`Remove`/`IsApplied` - Auto-restore on `Dispose` ## Assembly Layer ### IAssembler Seam Abstracts text → machine code generation: **Implementations:** - `StubAssembler` (default): Hand-emits calling-convention stubs, no dependency - `IcedAssembler` (optional): Wraps Iced for arbitrary assembly **Design decision (D3):** Keeps default configuration dependency-free; only callers needing arbitrary asm opt into Iced. ### Convention Stubs `StubAssembler` emits x86/x64 calling-convention trampolines: **Conventions supported:** cdecl, stdcall, thiscall, fastcall, x64 (Microsoft) **Encoding:** Deterministic byte emission, no parsing required ## High-Level Layer ### RemotePointer Indexer-based pointer arithmetic: ```csharp var ptr = magic[baseAddress]; int value = ptr.Read(offset); ptr.Write(999, offset); ``` ### RemoteModule/RemoteFunction Module and export resolution: ```csharp var module = magic["user32"]; var fn = module["MessageBoxA"]; int result = fn.Execute(CallConvention.Stdcall, args...); ``` ### ManagedPeb/ManagedTeb Managed views of Process Environment Block and Thread Environment Block: **Features:** - Typed field access - No manual structure marshalling ### Window/Input Window mutation and input simulation: **Features:** - Move/resize/title/activate/flash windows - Keyboard/mouse input without focus (where supported) ## Optimization Layer ### MarshalCache Per-type metadata caching: **Cached data:** - `Size` (managed blittable width) - `MarshalSize` (unmanaged interop width) - `TypeRequiresMarshal` (needs `Marshal.PtrToStructure`) - `IsIntPtr` (for special handling) **Performance:** Avoids per-call reflection and `Marshal.SizeOf` overhead ## Thread Safety ### ExternalReader **Thread-safe:** Yes (RPM/WPM are thread-safe) **Synchronization:** None required ### InProcessReader **Thread-safe:** Yes (RPM-on-self is thread-safe) **Synchronization:** None required ### MainThreadPump **Thread-safe:** Yes (uses `ConcurrentQueue`) **Synchronization:** Lock-free queue + `TaskCompletionSource` ### DetourManager/PatchManager **Thread-safe:** No **Synchronization:** Caller must synchronize ## Lifetime Management All disposable resources follow RAII: ```csharp using var magic = Magic.Open(process); // All handles, patches, and detours auto-restore on disposal ``` **Resources cleaned up:** - Process handles (`SafeMemoryHandle`) - Applied patches (`PatchManager`) - Active detours (`DetourManager`) - Allocated memory (`AllocatedMemory`) ## Error Handling **Strategy:** Explicit failures, silent success - **Read operations:** Throw on failure (Win32Exception) - **Write operations:** Return `false` on failure - **String reads:** Return `string.Empty` on failure - **Invalid handles:** Throw `InvalidOperationException` **Rationale:** Writes to another process can legitimately fail (protection changed, process exited); silent retry is often the right strategy. Reads are typically expected to succeed, so exceptions surface the problem immediately. ## Extensibility Points ### IAssembler Plug in custom assemblers: ```csharp public interface IAssembler { byte[] Assemble(string assemblyText, ulong origin = 0); } ``` ### DetourManager.PrologueLengthResolver Custom prologue validation: ```csharp magic.DetourManager.PrologueLengthResolver = (address, minBytes) => // Custom validation logic return requiredLength; ``` ## Performance Characteristics ### Memory Access | Operation | Cost | Notes | |-----------|------|-------| | `Read` (blittable) | Low | `MemoryMarshal.Read`, no alloc | | `Read` (marshalled) | Medium | `Marshal.PtrToStructure` | | `Read` (array) | Medium-High | Per-element marshalling | | `ReadBytes` | Low | Direct buffer copy | | `ReadString` | Medium | Encoding + allocation | ### Execution | Method | Latency | Safety | |--------|---------|--------| | `RemoteThreadExecutor` | ~1-2ms | Thread-agnostic only | | `MainThreadPump` | ~1 frame (16-33ms) | Crash-safe | | `InProcessInvoker` | <1μs | In-process only | ### Pattern Scanning | Mode | Cost | Notes | |------|------|-------| | Uncached | High | Scans entire module | | Cached | Low | O(1) after first scan | ## Design Principles 1. **Safety by default:** MainThreadPump prevents crashes from thread-affinity violations 2. **Bitness-agnostic:** Works on x86 and x64 without code changes 3. **No native dependencies:** Default configuration is pure managed 4. **Explicit operations:** Clear failure modes, no hidden retries 5. **Resource safety:** RAII-based cleanup prevents leaks 6. **Extensibility:** Seam points for assemblers, validators ## Further Reading - [Execution Models](./execution-models.md) — Deep dive on the three-tier execution strategy - [Memory Access](./memory-access.md) — MemoryBase readers and MarshalCache optimization - [Function Hooking](./hooking.md) — DetourManager and PatchManager internals - [Assembly Seam](./assembly-seam.md) — IAssembler abstraction and Iced integration