Files
whitemagic/openspec/changes/whitemagic-foundation/tasks.md
T
kbeandClaude Opus 4.8 af7e3dc1b9 Close out verification tasks 9.3 and 9.4
- 9.3: BlackMagic reference tests confirmed green (17 passing, 0 failing);
  WhiteMagic is additive and shares no source or build with it.
- 9.4: document the implementation deviations in the comparison doc's
  synthesis section and correct the InProcessReader architecture line
  (RPM-on-self, not direct deref).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 02:54:28 +02:00

88 lines
11 KiB
Markdown

## 1. Project Setup
- [x] 1.1 Create `WhiteMagic/WhiteMagic.csproj` targeting `net8.0-windows`, `AllowUnsafeBlocks=true`, nullable enabled, `TreatWarningsAsErrors`, `Platforms=x86;x64;AnyCPU`
- [x] 1.2 Create `WhiteMagicTest/WhiteMagicTest.csproj` (xUnit, `net8.0-windows`) referencing `WhiteMagic`
- [x] 1.3 Create `WhiteMagic.slnx` (SDK 10 default solution format) and add both projects. (Built on SDK 10; `net8.0-windows` targeting pack auto-restored.)
- [x] 1.4 Add `WhiteMagic/Native/` P/Invoke surface (`LibraryImport`): OpenProcess, Read/WriteProcessMemory, VirtualAllocEx/FreeEx/ProtectEx, CreateRemoteThread, Wow64Get/SetThreadContext, Get/SetThreadContext, LoadLibrary, GetProcAddress; add `SafeMemoryHandle`
- [x] 1.5 Verify empty projects build: `dotnet build WhiteMagic.slnx` — zero errors, zero warnings
## 2. Core Memory Access (spec: memory-access)
- [x] 2.1 Add tests for `MarshalCache<T>`: blittable size, marshal-required flag, IsIntPtr, computed-once behavior
- [x] 2.2 Implement `WhiteMagic/MarshalCache.cs` to pass 2.1. **Deviation (review):** split `Size` (managed `Unsafe.SizeOf<T>`, blittable/`MemoryMarshal` path) from `MarshalSize` (`Marshal.SizeOf<T>`, marshal path). A single size mis-sized structs whose unmanaged width differs — a `bool` field (managed 1 / unmanaged 4) over-read the blittable path; an inline `ByValTStr`/`ByValArray` under-sized the marshal path and overran the pinned buffer (heap corruption on write). `MemoryBase` now picks per `TypeRequiresMarshal` at all four IO sites. Unused fields (`SizeU`, `IsIntPtr`, `TypeCode`, `RealType`) dropped.
- [x] 2.3 Add tests for `MemoryBase` abstract contract + `ExternalReader` round-trip (`Read<T>`/`Write<T>`, arrays) using the current process as target
- [x] 2.4 Implement `WhiteMagic/MemoryBase.cs` (abstract) and `WhiteMagic/ExternalReader.cs` to pass 2.3. **Deviation (review):** shared RPM/WPM extracted to `WhiteMagic/RpmHelper.cs` so `ExternalReader` and `InProcessReader` stay byte-consistent — partial reads honored (returns exactly `bytesRead`), write returns actual bytes / 0 on total failure; `InProcessReader` guards `MainModule` like `ExternalReader`.
- [x] 2.5 Add tests for string read/write with encoding, null-terminator stop, and max length
- [x] 2.6 Implement `ReadString`/`WriteString` on `MemoryBase` to pass 2.5
- [x] 2.7 Add tests for relative/absolute addressing (`GetAbsolute`/`GetRelative`, `isRelative` flag)
- [x] 2.8 Implement addressing helpers to pass 2.7
- [x] 2.9 Add tests + implementation for `InProcessReader` (RPM/WPM on a self-handle — see D1 deviation note; direct deref rejected because .NET cannot catch `AccessViolationException`); verify shared `MemoryBase` API works for both readers
- [x] 2.10 Follow-up (found in review): `ReadString` scans 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 42` matches `{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)
- [x] 3.1 Add tests for `EmitU8`/`EmitU32`/`EmitU64` little-endian primitives
- [x] 3.2 Implement `WhiteMagic/Assembly/StubAssembler.cs` emitters + `IAssembler` interface to pass 3.1
- [x] 3.3 Add tests for x86 cdecl stub encoding (reverse push, call, `add esp, N*4`, ret) with known byte expectations
- [x] 3.4 Implement x86 cdecl stub to pass 3.3
- [x] 3.5 Add tests for stdcall (no caller cleanup), thiscall (ecx = this), fastcall (ecx/edx) x86 stubs
- [x] 3.6 Implement x86 stdcall/thiscall/fastcall stubs to pass 3.5
- [x] 3.7 Add tests for x64 stub argument-register placement and call
- [x] 3.8 Implement x64 stub to pass 3.7. **Deviation (review):** `BuildCallStub` takes `nuint[]` (was `uint[]`). x64 stub is Microsoft-x64-ABI compliant: allocates 32-byte shadow space, keeps 16-byte stack alignment at the inner `call` (frame `K ≡ 8 (mod 16)`, `K ≥ 0x20 + 8·stackArgs`), loads RCX/RDX/R8/R9 with full 64-bit `imm64` (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 by `MaxArguments` (256) to keep frame arithmetic overflow-free. **Runtime ABI now proven:** live-execution tests via `CreateRemoteThread` cover 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`: aligned `movaps` that #GPs unless the stub delivers a 16-byte-aligned stack, combined with a 5th stack arg).
- [x] 3.9 Confirm no FASM/`ManagedFasm` reference exists in `WhiteMagic` output (assert via a test that scans loaded references)
## 4. Crash-Safe Execution Slice (spec: remote-execution, function-hooking)
- [x] 4.1 Add tests for `PatchManager`/`Patch`: apply writes bytes, remove restores original, `IsApplied` reflects state
- [x] 4.2 Implement `WhiteMagic/Hooking/PatchManager.cs` + `Patch.cs` to pass 4.1
- [x] 4.3 Add tests for `DetourManager`/`Detour` in-process: apply redirects, `CallOriginal`, remove restores, named lookup
- [x] 4.4 Implement `WhiteMagic/Hooking/DetourManager.cs` + `Detour.cs` (inline jmp, x86/x64 form) to pass 4.3
- [x] 4.5 Add tests for instruction-boundary validation (aligned splice permitted, misaligned rejected when boundary info available)
- [x] 4.6 Implement minimal prologue length-decoder in `Detour.Apply` to pass 4.5. Default `StubAssembler` covers ONLY the common x86/x64 prologue shapes — enumerate the covered opcodes in code + XML doc (e.g. `push reg` 0x50-0x57, `mov edi,edi` 8B FF, `push ebp`/`mov ebp,esp` 55 8B EC, `sub esp,imm` 83 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.
- [x] 4.7 Add tests for auto-restore: disposing a `MemoryBase` reverts all active patches and detours
- [x] 4.8 Wire manager registration + `MemoryBase.Dispose` restore to pass 4.7
- [x] 4.9 Add tests for `MainThreadPump` queue 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)
- [x] 4.10 Implement `WhiteMagic/Execution/MainThreadPump.cs` (frame-function detour + thread-safe work queue + completion handles) to pass 4.9
- [x] 4.11 Add tests for `RemoteThreadExecutor.Execute<T>` (convention stub + wait + typed exit; no-process failure is deterministic)
- [x] 4.12 Implement `WhiteMagic/Execution/RemoteThreadExecutor.cs` and parameter marshalling (string/struct → remote alloc → free) to pass 4.11
## 5. Injection & Discovery (spec: dll-injection, memory-discovery)
- [x] 5.1 Add tests for pattern scanning: found (range/module/all-modules), wildcard mask, not-found returns Zero
- [x] 5.2 Implement `WhiteMagic/Discovery/PatternScanner.cs` to pass 5.1
- [x] 5.3 Add tests + implement scan result cache (repeat served from cache, clear rescans)
- [x] 5.4 Add tests + implement `WhiteMagic/Discovery/PeHeaderParser.cs` (sections, entry point)
- [x] 5.5 Add tests + implement `WhiteMagic/Memory/AllocatedMemory.cs` (named regions, typed read/write by name, address by name, free on dispose)
- [x] 5.6 Add tests + implement raw code injection (`InjectCode` at address and into fresh allocation)
- [x] 5.7 Add tests + implement DLL injection via remote thread (LoadLibrary), including bitness-mismatch and missing-file failures
- [x] 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)
- [x] 6.1 Add tests for `InProcessInvoker.CreateFunction<TDelegate>` calling a known in-process function directly
- [x] 6.2 Implement `WhiteMagic/Execution/InProcessInvoker.cs` (`Marshal.GetDelegateForFunctionPointer`) + vtable-entry helper to pass 6.1
- [x] 6.3 Document that the CLR-host managed loader (injecting `InProcessReader` into a foreign process) is a separate follow-up change
## 7. High-Level Ergonomics (spec: high-level-api)
- [x] 7.1 Add tests + implement `RemotePointer` indexer (`sharp[addr].Read/Write/Execute` relative to base)
- [x] 7.2 Add tests + implement `RemoteModule`/`RemoteFunction` (`sharp["mod"]["fn"]`) resolving export addresses and executing via a chosen strategy. **Deviation:** export resolution added to `PeHeaderParser.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 throw `NotSupportedException`. `RemoteModule` resolves the base via `Process.Modules` (name match tolerant of `.dll`/case). `RemoteFunction.Execute<T>` defaults to the always-available `RemoteThreadExecutor`; `Address` is exposed for pump routing and `CreateDelegate<T>` for the in-process tier. Tests cross-check resolved addresses against the OS `GetProcAddress` (direct export + forwarder) and execute `kernel32!GetCurrentProcessId` end-to-end.
- [x] 7.3 Add tests + implement `ManagedPeb`/`ManagedTeb` field reads
- [x] 7.4 Add tests + implement `WindowFactory`/`RemoteWindow` (enumerate, move/resize/title/activate/flash, query by class)
- [x] 7.5 Add tests + implement keyboard/mouse simulation (PostMessage + SendInput) to a target window
- [x] 7.6 Add tests + implement `Task`-based async execution wrappers over the executors and pump
- [x] 7.7 Add minimal facade (`WhiteMagic` entry type) exposing `Open`, readers, executors, managers, and the indexer
## 8. Optional Iced Backend (spec: managed-assembler)
- [ ] 8.1 Add `Iced` package reference behind an `IcedAssembler : IAssembler` in a way that keeps the default `StubAssembler` dependency-free
- [ ] 8.2 Add tests + implement `IcedAssembler.Assemble(text, origin)` for arbitrary mnemonics and origin-relative encoding
- [ ] 8.3 Add tests + wire full prologue instruction-boundary validation (D5) using the Iced disassembler when present
## 9. Verification
- [x] 9.1 Run full test suite: `dotnet test WhiteMagicTest/WhiteMagicTest.csproj` — all pass (180 pass, 4 integration/interactive skipped)
- [x] 9.2 Run full build (`dotnet build WhiteMagic.slnx`) — zero errors, zero new warnings in `WhiteMagic`
- [x] 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).
- [x] 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 the `InProcessReader` line in the architecture diagram.