Files
whitemagic/docs/architecture.md
T
kbe 6300bebe33 Fix API documentation and examples; add comprehensive documentation suite
- Example5: Fix format string bugs (alignment specifier placement, SafeMemoryHandle formatting)
- Example4: Fix DetourManager.Detour() → Create() in all doc strings
- Example3: Fix MemoryBase.CreateFunction() → InProcessInvoker.CreateFunction() in doc strings
- Examples 1-5: Correct all runtime errors and API mismatches vs actual WhiteMagic API
- README: Fix DetourManager.Detour() examples to use Create(); add missing code fence markers
- Add WhiteMagic.Examples project with 5 comprehensive example files (40+ sub-examples)
- Add docfx.json and toc.md for DocFX API reference generation
- Add 5 conceptual guides: architecture, memory-access, execution-models, hooking, troubleshooting
- Ensure zero errors, zero warnings across all projects (net8.0-windows)

Doc strings now teach correct APIs; runtime format bugs eliminated; build succeeds.
2026-07-22 22:26:07 +02:00

10 KiB

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<T> 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!HeapAllocNTDLL.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:

var ptr = magic[baseAddress];
int value = ptr.Read<int>(offset);
ptr.Write(999, offset);

RemoteModule/RemoteFunction

Module and export resolution:

var module = magic["user32"];
var fn = module["MessageBoxA"];
int result = fn.Execute<int>(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:

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:

public interface IAssembler
{
    byte[] Assemble(string assemblyText, ulong origin = 0);
}

DetourManager.PrologueLengthResolver

Custom prologue validation:

magic.DetourManager.PrologueLengthResolver = (address, minBytes) =>
    // Custom validation logic
    return requiredLength;

Performance Characteristics

Memory Access

Operation Cost Notes
Read<T> (blittable) Low MemoryMarshal.Read, no alloc
Read<T> (marshalled) Medium Marshal.PtrToStructure
Read<T> (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