Section 8 of the whitemagic-foundation change. - 8.1: reference Iced 1.21.0 behind IcedAssembler:IAssembler. The default StubAssembler path never touches Iced; only constructing IcedAssembler pulls it into a behavioral path. - 8.2: IcedAssembler.Assemble bridges Intel-syntax text onto Iced's fluent Assembler by reflection (Iced ships no text parser). Registers, immediates and labels are supported with origin-relative encoding; memory operands throw NotSupportedException. - 8.3: IcedAssembler.GetPrologueLength decodes arbitrary instructions via Iced's Decoder. DetourManager.PrologueLengthResolver (new delegate) defaults to the built-in PrologueDecoder and is swappable to the Iced resolver, threaded into each Detour. This lifts the "partial boundary safety" caveat on the hooking slice when Iced is opted in. Also gitignore test-run TestResults artifacts. Tests: 221 passing, 4 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
12 KiB
1. Project Setup
- 1.1 Create
WhiteMagic/WhiteMagic.csprojtargetingnet8.0-windows,AllowUnsafeBlocks=true, nullable enabled,TreatWarningsAsErrors,Platforms=x86;x64;AnyCPU - 1.2 Create
WhiteMagicTest/WhiteMagicTest.csproj(xUnit,net8.0-windows) referencingWhiteMagic - 1.3 Create
WhiteMagic.slnx(SDK 10 default solution format) and add both projects. (Built on SDK 10;net8.0-windowstargeting pack auto-restored.) - 1.4 Add
WhiteMagic/Native/P/Invoke surface (LibraryImport): OpenProcess, Read/WriteProcessMemory, VirtualAllocEx/FreeEx/ProtectEx, CreateRemoteThread, Wow64Get/SetThreadContext, Get/SetThreadContext, LoadLibrary, GetProcAddress; addSafeMemoryHandle - 1.5 Verify empty projects build:
dotnet build WhiteMagic.slnx— zero errors, zero warnings
2. Core Memory Access (spec: memory-access)
- 2.1 Add tests for
MarshalCache<T>: blittable size, marshal-required flag, IsIntPtr, computed-once behavior - 2.2 Implement
WhiteMagic/MarshalCache.csto pass 2.1. Deviation (review): splitSize(managedUnsafe.SizeOf<T>, blittable/MemoryMarshalpath) fromMarshalSize(Marshal.SizeOf<T>, marshal path). A single size mis-sized structs whose unmanaged width differs — aboolfield (managed 1 / unmanaged 4) over-read the blittable path; an inlineByValTStr/ByValArrayunder-sized the marshal path and overran the pinned buffer (heap corruption on write).MemoryBasenow picks perTypeRequiresMarshalat all four IO sites. Unused fields (SizeU,IsIntPtr,TypeCode,RealType) dropped. - 2.3 Add tests for
MemoryBaseabstract contract +ExternalReaderround-trip (Read<T>/Write<T>, arrays) using the current process as target - 2.4 Implement
WhiteMagic/MemoryBase.cs(abstract) andWhiteMagic/ExternalReader.csto pass 2.3. Deviation (review): shared RPM/WPM extracted toWhiteMagic/RpmHelper.cssoExternalReaderandInProcessReaderstay byte-consistent — partial reads honored (returns exactlybytesRead), write returns actual bytes / 0 on total failure;InProcessReaderguardsMainModulelikeExternalReader. - 2.5 Add tests for string read/write with encoding, null-terminator stop, and max length
- 2.6 Implement
ReadString/WriteStringonMemoryBaseto pass 2.5 - 2.7 Add tests for relative/absolute addressing (
GetAbsolute/GetRelative,isRelativeflag) - 2.8 Implement addressing helpers to pass 2.7
- 2.9 Add tests + implementation for
InProcessReader(RPM/WPM on a self-handle — see D1 deviation note; direct deref rejected because .NET cannot catchAccessViolationException); verify sharedMemoryBaseAPI works for both readers - 2.10 Follow-up (found in review):
ReadStringscans for the null terminator byte-by-byte, so for UTF-16/UTF-32 it can match a misaligned multi-byte null across a char boundary (e.g."A"+U+4200 =41 00 00 42matches{00,00}at offset 1) and can miss a terminator split across the 64-byte chunk boundary. Harmless for ASCII/UTF-8 (single-byte encodings). Fix: align the scan to the encoding's code-unit width and carry the last(nullLen-1)bytes across chunks. Add a UTF-16 test.
3. Managed Assembler (spec: managed-assembler)
- 3.1 Add tests for
EmitU8/EmitU32/EmitU64little-endian primitives - 3.2 Implement
WhiteMagic/Assembly/StubAssembler.csemitters +IAssemblerinterface to pass 3.1 - 3.3 Add tests for x86 cdecl stub encoding (reverse push, call,
add esp, N*4, ret) with known byte expectations - 3.4 Implement x86 cdecl stub to pass 3.3
- 3.5 Add tests for stdcall (no caller cleanup), thiscall (ecx = this), fastcall (ecx/edx) x86 stubs
- 3.6 Implement x86 stdcall/thiscall/fastcall stubs to pass 3.5
- 3.7 Add tests for x64 stub argument-register placement and call
- 3.8 Implement x64 stub to pass 3.7. Deviation (review):
BuildCallStubtakesnuint[](wasuint[]). x64 stub is Microsoft-x64-ABI compliant: allocates 32-byte shadow space, keeps 16-byte stack alignment at the innercall(frameK ≡ 8 (mod 16),K ≥ 0x20 + 8·stackArgs), loads RCX/RDX/R8/R9 with full 64-bitimm64(no >4 GiB pointer truncation), and writes stack args above the shadow window (no return-address clobber). x86 rejects args >uint.MaxValue. Argument count bounded byMaxArguments(256) to keep frame arithmetic overflow-free. Runtime ABI now proven: live-execution tests viaCreateRemoteThreadcover 5-arg register+stack delivery (Execute_sums_register_and_stack_arguments), 16-byte entry alignment arithmetically (Execute_delivers_16byte_aligned_stack_to_callee), and a hardware-alignment-sensitive SSE callee (Execute_runs_sse_callee_with_five_args: alignedmovapsthat #GPs unless the stub delivers a 16-byte-aligned stack, combined with a 5th stack arg). - 3.9 Confirm no FASM/
ManagedFasmreference exists inWhiteMagicoutput (assert via a test that scans loaded references)
4. Crash-Safe Execution Slice (spec: remote-execution, function-hooking)
- 4.1 Add tests for
PatchManager/Patch: apply writes bytes, remove restores original,IsAppliedreflects state - 4.2 Implement
WhiteMagic/Hooking/PatchManager.cs+Patch.csto pass 4.1 - 4.3 Add tests for
DetourManager/Detourin-process: apply redirects,CallOriginal, remove restores, named lookup - 4.4 Implement
WhiteMagic/Hooking/DetourManager.cs+Detour.cs(inline jmp, x86/x64 form) to pass 4.3 - 4.5 Add tests for instruction-boundary validation (aligned splice permitted, misaligned rejected when boundary info available)
- 4.6 Implement minimal prologue length-decoder in
Detour.Applyto pass 4.5. DefaultStubAssemblercovers ONLY the common x86/x64 prologue shapes — enumerate the covered opcodes in code + XML doc (e.g.push reg0x50-0x57,mov edi,edi8B FF,push ebp/mov ebp,esp55 8B EC,sub esp,imm83 EC / 81 EC, REX-prefixed forms). On any opcode outside the set, refuse the splice (do not guess). Full arbitrary-prologue validation is gated on the optional Iced backend (task 8.3) — document that slices 2-5 ship partial boundary safety. Resolved (8.3):DetourManager.PrologueLengthResolvernow acceptsIcedAssembler.GetPrologueLengthfor full instruction-boundary validation of arbitrary prologues; the built-in decoder remains the zero-dependency default. - 4.7 Add tests for auto-restore: disposing a
MemoryBasereverts all active patches and detours - 4.8 Wire manager registration +
MemoryBase.Disposerestore to pass 4.7 - 4.9 Add tests for
MainThreadPumpqueue semantics: item runs on hooked thread, result returned, throwing item surfaces exception and pump survives, dispose uninstalls hook (use a self-hosted frame-loop harness in-process) - 4.10 Implement
WhiteMagic/Execution/MainThreadPump.cs(frame-function detour + thread-safe work queue + completion handles) to pass 4.9 - 4.11 Add tests for
RemoteThreadExecutor.Execute<T>(convention stub + wait + typed exit; no-process failure is deterministic) - 4.12 Implement
WhiteMagic/Execution/RemoteThreadExecutor.csand parameter marshalling (string/struct → remote alloc → free) to pass 4.11
5. Injection & Discovery (spec: dll-injection, memory-discovery)
- 5.1 Add tests for pattern scanning: found (range/module/all-modules), wildcard mask, not-found returns Zero
- 5.2 Implement
WhiteMagic/Discovery/PatternScanner.csto pass 5.1 - 5.3 Add tests + implement scan result cache (repeat served from cache, clear rescans)
- 5.4 Add tests + implement
WhiteMagic/Discovery/PeHeaderParser.cs(sections, entry point) - 5.5 Add tests + implement
WhiteMagic/Memory/AllocatedMemory.cs(named regions, typed read/write by name, address by name, free on dispose) - 5.6 Add tests + implement raw code injection (
InjectCodeat address and into fresh allocation) - 5.7 Add tests + implement DLL injection via remote thread (LoadLibrary), including bitness-mismatch and missing-file failures
- 5.8 Add tests + implement DLL injection via thread-hijack (save/redirect/restore context) with x86 and x64 stubs
6. In-Process Tier (spec: remote-execution)
- 6.1 Add tests for
InProcessInvoker.CreateFunction<TDelegate>calling a known in-process function directly - 6.2 Implement
WhiteMagic/Execution/InProcessInvoker.cs(Marshal.GetDelegateForFunctionPointer) + vtable-entry helper to pass 6.1 - 6.3 Document that the CLR-host managed loader (injecting
InProcessReaderinto a foreign process) is a separate follow-up change
7. High-Level Ergonomics (spec: high-level-api)
- 7.1 Add tests + implement
RemotePointerindexer (sharp[addr].Read/Write/Executerelative to base) - 7.2 Add tests + implement
RemoteModule/RemoteFunction(sharp["mod"]["fn"]) resolving export addresses and executing via a chosen strategy. Deviation: export resolution added toPeHeaderParser.GetExportAddress(PE32/PE32+ export directory walk) and follows export forwarders (e.g.kernel32!HeapAlloc→NTDLL.RtlAllocateHeap) into other loaded modules; ordinal forwarders and unresolvable API-set targets throwNotSupportedException.RemoteModuleresolves the base viaProcess.Modules(name match tolerant of.dll/case).RemoteFunction.Execute<T>defaults to the always-availableRemoteThreadExecutor;Addressis exposed for pump routing andCreateDelegate<T>for the in-process tier. Tests cross-check resolved addresses against the OSGetProcAddress(direct export + forwarder) and executekernel32!GetCurrentProcessIdend-to-end. - 7.3 Add tests + implement
ManagedPeb/ManagedTebfield reads - 7.4 Add tests + implement
WindowFactory/RemoteWindow(enumerate, move/resize/title/activate/flash, query by class) - 7.5 Add tests + implement keyboard/mouse simulation (PostMessage + SendInput) to a target window
- 7.6 Add tests + implement
Task-based async execution wrappers over the executors and pump - 7.7 Add minimal facade (
WhiteMagicentry type) exposingOpen, readers, executors, managers, and the indexer
8. Optional Iced Backend (spec: managed-assembler)
- 8.1 Add
Icedpackage reference behind anIcedAssembler : IAssemblerin a way that keeps the defaultStubAssemblerdependency-free. Iced 1.21.0 added toWhiteMagic.csproj; only constructingIcedAssemblerpulls it into a behavioral path.StubAssemblernever references it. - 8.2 Add tests + implement
IcedAssembler.Assemble(text, origin)for arbitrary mnemonics and origin-relative encoding. Deviation: Iced ships a fluent code assembler and a decoder but no text parser, soAssemblebridges Intel-syntax text onto Iced'sAssemblerby reflection — the mnemonic selects the matching fluent method and operands bind to registers (reflected fromAssemblerRegisters), immediates, or labels; origin-relative encoding viaAssembler.Assemble(writer, origin). Register/immediate/label operands and label-relative branches are supported; memory operands ([reg+disp]) throwNotSupportedException(a caller needing those emits bytes directly). Tests round-trip via Iced's decoder and assert origin-relative branch targets. - 8.3 Add tests + wire full prologue instruction-boundary validation (D5) using the Iced disassembler when present.
IcedAssembler.GetPrologueLengthdecodes arbitrary instructions via Iced'sDecoder;DetourManager.PrologueLengthResolver(aPrologueLengthResolverdelegate) defaults to the built-inPrologueDecoderand is swappable to the Iced resolver, threaded into eachDetour. Tests prove Iced resolves a prologue (mov rax,rcx=48 8B C1) the built-in decoder rejects.
9. Verification
- 9.1 Run full test suite:
dotnet test WhiteMagicTest/WhiteMagicTest.csproj— all pass (180 pass, 4 integration/interactive skipped) - 9.2 Run full build (
dotnet build WhiteMagic.slnx) — zero errors, zero new warnings inWhiteMagic - 9.3 Confirm existing BlackMagic/its tests are unchanged and still green. WhiteMagic is a separate, git-ignored project under
reference/sharing no source or build with BlackMagic;dotnet test reference/Blackmagic/BlackMagic.slnx= 17 passing, 0 failing (only pre-existing XML-doc warnings). - 9.4 Update
docs/memory-library-comparison.md"WhiteMagic — synthesis" section with any deviations discovered during implementation. Added a "Deviations discovered during implementation" subsection (D1 RPM-on-self, MarshalCache size split, x64 ABI + SSE proof, export forwarders, partial prologue safety, injection bitness, bounds/protection hardening) and corrected theInProcessReaderline in the architecture diagram.