Eidolon (d2r_loader.dll) — Complete Reverse-Engineering Dossier
Document version: v4 (definitive, supersedes eidolon_analysis.md and all round-specific docs)
Date: 2026-04-17
Subject: D2R PTR 3.2 snapshot, d2r_loader.dll (codenamed "Eidolon")
Status: Consolidated synthesis of rounds 1-13 of static-IDA agents + live Lua probes + injector-side regression analysis.
This document is self-contained. A fresh analyst should read this and nothing else to come up to speed on Eidolon. All prior docs (
eidolon_analysis.md,round_6_findings.mdthroughround_13_findings.md,round_10_final_defense_plan.md) are superseded.
1. Executive Summary
Eidolon is the in-process front-half of a Blizzard cloud attestation/telemetry harness shipped inside the Diablo II: Resurrected loader. It is not a kernel anti-cheat, not a memory scanner, not a bytecode VM, and not a code-section hashing engine.
What Eidolon is:
- A synthetic-IAT polymorphic import system that hides its real Win32 surface from static analysis (248 heap trampolines, per-API randomized decode chains, PEB.Ldr-bound).
- A telemetry pipeline (
Standard.*events) totelemetry-in.battle.net, pinned-TLS + AWS SigV4 signed. - A VEH + UEH pair that ships crash telemetry out-of-process before WER tears the process down.
- A host for two broker tokens (Aegis crash slot, Warden data slot) consumed by a sibling Blizzard process via
ReadProcessMemory. - A decoy canary IID table for 39 DLLs designed to fool
CFF Explorer,PE-Bearand IDA auto-import recovery. - Wrapped in OLLVM/Tigress control-flow flattening throughout, with no true bytecode interpreter (Round 5 A4; reconfirmed R6–R13).
What Eidolon does NOT do (all verified):
- No inline caller-provenance / stack-walk check (R6 I1, reconfirmed R13 I27/I35 after R12 mis-reversal).
- No CFG/XFG enforcement (R6 CFG probe).
- No bot-DLL blocklist scanning (R6 I5; module enumeration exists per R13 I28 but is passive).
- No on-disk
.texthash beyondWinVerifyTrustself-verify. - No DRx arming via
NtSetContextThread(R13 I33 — usesCONTEXT_FULL, notCONTEXT_DEBUG_REGISTERS). - No
ProcessInstrumentationCallback(R13 I29). - No rolling-checksum integrity check (R13 I30 —
dword_7FF927C21FE0is a CFF opaque predicate).
Practical consequence for the analysis process: the PTR 3.2 hijack-injection regression is not caused by anything in d2r_loader.dll. It is most plausibly caused by the out-of-process Warden broker observing thread state via RPM. The correct defense is to replace the hijack with an NtCreateThreadEx(StartAddress = LoadLibraryA, ...) helper thread (Option 2). Eight Tier-1 defenses in total (see §16).
2. Architecture Overview
┌───────────────────────────────────────────┐
│ d2r_loader.dll (Eidolon) │
│ │
PE loader ──► DllMain ──► ┌─────┐ seeds ┌──────────────┐ │
│ TLS │───────────►│ gs:[0x20] │ per-thread│
│ cb │ │ FiberData │ state ptr │
└──┬──┘ └──────────────┘ │
│ │
▼ │
┌────────────────────────────┐ decrypts IDD from .eid │
│ eidolon_iat_resolver_main │────► rolling-XOR layer 1 │
│ (per-DLL, 6 MBA rounds) │ │
└─────────────┬──────────────┘ │
│ for each API │
▼ │
┌───────────────────────────────────────┐ │
│ .eid emitter (runtime-decrypted │ VirtualAlloc(RWX) │
│ shellcode — R13 I34 not in .text) │─────────────► Trampoline │
└─────────────┬─────────────────────────┘ arena @ │
│ emits polymorphic thunk 0x2049BCF0000│
▼ │
┌───────────────────────────┐ │
│ Synthetic IAT thunk table │ 248 entries │
│ byte_7FF927BFE190 │ indirect call sites │
└──┬────────────────────────┘ │
│ │
│ ┌─────────────────────────────────┐ │
└─────────►│ CFF megadispatcher 0x7FF9264624A0│ │
│ (405 KB single OLLVM-flattened │ │
│ function, 104K insns, 25K BBs) │ │
└─────────┬───────────────────────┘ │
│ re-entrant, mutable cookie │
▼ dword_7FF927C71A4C │
┌──────────────────┐ │
│ VEH dispatcher │ sub_7FF926CEFDA0 │
│ → fiber-scheduler│ → 0x7FF92728D200 (inner) │
└─────────┬────────┘ │
│ │
┌────────────────┴──────────┐ │
▼ ▼ │
┌──────────────┐ ┌───────────────────┐ │
│ BTel shipper │ │ UEH (telem) │ │
│ cert-pinned │ │ warden_emit_excn │ │
│ SigV4 HTTPS │ │ 0x7FF926072B40 │ │
└──────┬───────┘ └──────────┬────────┘ │
│ │ │
│ reads │ returns CONTINUE_SEARCH │
▼ ▼ │
telemetry-in.battle.net WER kills the process │
│
Aegis token slot @ 0x488CC0 ──┐ │
Warden token slot @ 0x488CC8 ──┤ read via RPM by │
39-entry canary IID 0x7FF928101A70 out-of-proc broker │
(DECOY — R12 I26 / R11 I17) │
│
└───────────────────────────────────────────────────────────────────────────┘
The entire system has one runtime-exposed export (Ordinal_1) consumed by D2R.exe at IAT slot RVA 0x15D7BF0. The CFF megacaller at 0x7FF9277703C0 drives 6 call sites into the megadispatcher, with dispatch keyed on the mutable cookie at dword_7FF927C71A4C (current 0x2EB7282F, R7 I7). The entire CFF mega-system runs inside the VEH/fiber callback (R7 I7, R8 I12, R13 I35).
3. The Synthetic IAT System
3.1 Layout and mechanics
Eidolon replaces the standard PE import table with a runtime-populated thunk table. The canonical runtime artifacts:
| Artifact | Location | Role |
|---|---|---|
| Synthetic IAT thunk table | byte_7FF927BFE190 (RVA 0x1DAE190) | 248 slots of qword trampoline pointers (R8 live probe) |
| Trampoline arena | 0x2049BCF0000 (heap RWX, observed session) | Per-API polymorphic thunks |
| Encrypted IDD | loader+0x22B1000 (RVA 0x22B1000) | Rolling-XOR-encrypted import-descriptor data (22 records × 0x28) |
| Plaintext name table | loader+0x22B1376 | Decoy canary DLL+API strings (R11 A17) |
| Decoy canary IID | 0x7FF928101A70 | 39 IMAGE_IMPORT_DESCRIPTOR-shaped rows, 1 canary API per DLL (R12 I26) |
| Resolver scratch output | qword_7FF927CD9D40 (r14 at resolution time) | 248 × 24-byte rows, {stg, arena_ptr, meta} (R11 I21) |
Each trampoline in the arena begins with 48 B8 imm64 (mov rax, imm) and ends with FF E0 (jmp rax). The middle is a per-API polymorphic decode chain mixing \{DEC, INC, ROL, ROR, ADD, SUB, XOR, OR, AND, NOT, NEG\} with up to 4 PEB.Ldr folds, producing the real target address. Frequency distribution of folds across 64 decoded thunks (R13):
| Fold count | Trampolines |
|---|---|
| 0 | 19 |
| 1 | 20 |
| 2 | 25 |
| 3 | 3 |
| 4 | 1 |
Thunk +0x40 has the shortest chain observed (mov rax, imm64; sub rax, imm32; ...) where the immediate 0x00007FF947B9124C already lies within the kernelbase range — so only one arithmetic op is needed (R8 live probe).
3.2 Emitter location
The trampoline emitter is NOT in .text (R13 I34 — definitive negative, confirmed after three rounds of dedicated static hunts in R8 I14, R11 I19/I23, R12 I23). Three independent static scans found:
- Zero
48 B8 ... FF E0templates in.rdata(only a spurious C++mov rax,0;retepilogue). - Zero
rep movsqtemplate copies. - Zero byte-store sequences emitting two adjacent trampoline opcodes.
The emitter lives in the encrypted .eid section (RVA 0x1F50000..0x22D0000, ~3.5 MB), decrypted at runtime into RWX memory. R13 I34 recommends a dynamic DR0 write-trap on the arena page as the only way to locate it. sub_7FF9271EBBD0 is the VirtualAlloc-backed arena allocator (R13 I31), not the emitter.
3.3 Per-DLL resolver protocol (R11 I21 symbolic equations)
The resolver entry at 0x7FF926042AF0 (eidolon_resolve_one_api per R11 rename; misnomer — it's actually per-DLL per R12 I24) executes 6 sequential MBA-mixer rounds per API, invoking sub_7FF927057340 each round.
Inputs consumed: 9 IDD slots at qword_7FF927C2AF80..AFD0 (stride 8, reads offsets 0, 8, 16, 24, 48, 80).
Per-round symbolic equations (R11 I21):
| Round | Feeder | Characteristic constants |
|---|---|---|
| g₁ | OR/mul/XOR chain | K₁₁=0x1B02AE194C243C67, K₁₂=0x05A16FBD1B2473A5, K₁₃=0x7B61D2BF5BA38ECA |
| g₂ | MD5-style F/G/I triad | 0x9747609F3E4F8C86, 0xF1CAC3D4AC04CB37, 0x23BB12D9C46919D7, 0xDC44ED263B96E628, 0x0EABF99C3A70E504 |
| g₃ | Polynomial | K₃₁=0x200F6DD696A203F2 |
| g₄ | Linear MBA-collapse | — |
| g₅ | imul(·, -0xB) + sum | name-hash tag |
| g₆ | 32-bit LCG | updates dword_7FF927C21FE0 |
Per-API scratch struct (at r13):
| Offset | Width | Contents |
|---|---|---|
+0x06 | WORD | name-hash tag (verification) |
+0x88 | QWORD | raw round-5 mixer output (decryption key / XOR mask) |
+0x90 | QWORD | encrypted pointer (single-deref) |
+0x98 | QWORD | arena trampoline pointer pre-commit |
Output-table commit (r14 buffer, stride 0x18, R11 I21):
lea rax, [rax + rax*2] ; *3
mov [r14 + rax*8 + 0x00], rcx ; round-5 staging ptr
mov [r14 + rax*8 + 0x08], rcx ; ARENA trampoline ptr (double-deref)
mov [r14 + rax*8 + 0x10], rcx ; metadata / NT-IAT entry ptrThe outer wrapper at 0x7FF927571DD0 (R11 I20, R12 I24) iterates per-DLL at stride 0x48, calling resolver_main 39 times. Arena allocation is done via sub_7FF9271EBBD0 → VirtualAlloc(NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE) (R13 I31, correcting R12 I24's operator new[] claim). The hardening pass sub_7FF926C8BC80 subsequently calls VirtualProtect → PAGE_EXECUTE_READ and FlushInstructionCache.
3.4 Verified-decoded API map (63 from R12 + 1 from R13)
See §14 for the full table. Headline finding (R11 I19 → R12 → R13): Eidolon's real Win32 surface includes thread control, module enumeration, crypto, registry, and CreateProcessW — but not RtlLookupFunctionEntry as a real caller (see §4 and §9).
4. The Decoy Canary Table
Location: 0x7FF928101A70 (RVA 0x22B1A70), 39 one-DLL-one-API IIDs (R12 I26).
Every record has sentinel FT = 0x01DAE188 and a 16-byte OFT pool holding exactly one API name plus a null. Record 0 is a pseudo-header (anomalous TimeDateStamp = 0x022B17FB); record 40 is a zero terminator. The 39 canary APIs include WinVerifyTrust, NtClose, RtlFreeHeap, GetAdaptersInfo, SetupDiGetDeviceInterfaceDetailW, WSAStartup (ord #116), SysAllocString (ord #7), etc.
Purpose: This table exists to mislead static tools — CFF Explorer, PE-Bear, IDA auto-import recovery all consume the standard IMAGE_IMPORT_DIRECTORY pointed here. Anyone grepping for "does Eidolon import RtlLookupFunctionEntry?" based on this table would answer no — which is what happened in R6 Agent I1 and R6 Agent I5.
R6 was misled for 2 rounds because of this. R6 I1 concluded "no stack-walking APIs imported", R6 I5 concluded "no Module32*" — both wrong, because the real imports are in the encrypted IDD + runtime thunk table, not this decoy. R12 I26 identified it as a decoy; R13 I27/I35 confirmed that although the real IAT does contain RtlLookupFunctionEntry, Eidolon itself never calls it (only the linked MSVC CRT does). The decoy misled the analysis in both directions: R6 underestimated Eidolon's visible surface; R12 briefly overestimated its capability.
There is no stable slot↔name correspondence between the canary table and the 248-thunk runtime table.
5. IDD Layer-1 Encryption
Round 6 Agent I4 identified the IDD encryption as a rolling 8-byte XOR with baked key 0xFFFF834A942B7856, phase-7, drop-leading-byte. R7 live probe (probe_r7_idd_decrypt.lua) confirmed by producing 18/22 fully-zero plaintext slots (the gold-standard signature of correct key recovery). R8 live sibling-IDD probe reproduced it for a second IDD at 0x7FF92431B8F9.
Canonical decryptor (R10):
# eidolon_idd_decrypt.py
KEY = bytes.fromhex("56782B944A83FFFF") # 0xFFFF834A942B7856 LE
def decrypt_idd(ciphertext: bytes) -> bytes:
"""Rolling 8-byte XOR, phase=7, drop the leading byte."""
out = bytearray(len(ciphertext))
for i, b in enumerate(ciphertext):
out[i] = b ^ KEY[(i + 7) % 8]
return bytes(out[1:])No layer-2 obfuscation exists. R9 I11 confirmed that the residual "fingerprint constants" in IDD slots B/C/D/E are plaintext integrity tags, not XOR-keystream material. The IDD has 3 record types (R7 I6):
| Record type | Indices | Slot 0 | Slot 1 | Slot 4 (fingerprint) |
|---|---|---|---|---|
| A (DLL header) | 0, 1 | OS-version stamp | DLL hash (MD5-trunc) | 0xFFFF5C8E... per-DLL |
| B (per-API) | 2-20 | 0 | 0x9F81B0__XXXXXXXX per-API | 0xFFFF5C__34EB276E |
| C (sentinel) | 21 | different | different | closing checksum |
Record 21's encrypted bytes spell SETUPAPI because the name table at +0x376 overruns it — the last real record is 20.
6. Control-Flow Flattening (CFF)
There is no Eidolon bytecode VM. Rounds 3-4 labelled the big flattened functions as "VM dispatcher" / "VM state"; R5 A4 corrected this to OLLVM/Tigress CFF; R6 I2 + R7 I7 + R13 I35 reconfirmed.
| Property | Value |
|---|---|
| Megadispatcher | 0x7FF9264624A0 — 0x62F87 bytes, 104,211 instructions, 25,730 basic blocks, zero ret (tail-jmp exits only), balanced BST dispatch over 32-bit state (R6 I2) |
| Megacaller | 0x7FF9277703C0 — 6 call sites, each passes state ptr in r8=rsi and a (rcx, rdx, r9d) byte triplet (R7 I7) |
| Dispatch cookie (mutable) | dword_7FF927C71A4C — current 0x2EB7282F (R7 I7) |
| CFF state seed | dword_7FF927C8BFDC |
| Small CFF dispatcher | 0x7FF926ABF3E0 (one obfuscated function) |
| Anti-RE noise | 50+ rdtsc in first 40K insns, stack-watermark probes, call-to-middle-of-insn (R6 I2) |
Calling convention for all Eidolon CFF helpers: (rcx_byte, rdx_byte, r9d_byte, state_ptr) with r8 = rsi (R7 I7).
What sub_7FF9264E3480 is NOT: R6 I2 guessed "IAT resolver kernel"; R7 I8 proved it's an MD5 driver wrapping MD5_Transform at 0x7FF927B65F90. Called by IAT resolver to hash IDD records and by BTel to hash HTTP payloads.
The mega-system runs inside a VEH/fiber callback (R7 I7, R13 I35). The thunk eidolon_cff_dispatcher_clone_megacaller_thunk at 0x7FF927038110 is scheduled on a fiber by the VEH inner dispatcher at every heavy-path exception.
7. VEH / UEH Exception Machinery
7.1 VEH
| Item | Address | Notes |
|---|---|---|
| OS-registered VEH wrapper | sub_7FF926CEFDA0 | Prologue 56 57 53 48 83 EC 50 (R8 I12) |
| Inner dispatcher | eidolon_veh_handler_uses_fiberdata @ 0x7FF92728D200 | 5,979 bytes, 359 BBs (R13 I35) |
| VEH bootstrap stage | 0x7FF926C48FF0 | Dispatched via descriptor table 0x7FF927D3699C (R11 I22) |
| VEH installer | sub_7FF927464BC0 | Called from bootstrap stage |
| VEH fiber entry | 0x7FF927038110 | Where the CFF mega-system actually runs |
| Sibling CFF | sub_7FF92728E960 | Worth future investigation (R13 I35 flagged) |
R13 I35 full decompile of the inner dispatcher proves: filters 5 noisy exception codes to CONTINUE_SEARCH; heavy path reads TEB.FiberData, decrypts per-thread block via thunk +0x550 (TlsGetValue), schedules fiber via thunk +0x040 (CreateFiber-like), writes per-thread slot via thunk +0x558 (FlsSetValue), returns CONTINUE_SEARCH unless per-thread block's [+0x48] is pre-seeded.
The inner dispatcher never calls RtlCaptureContext, RtlLookupFunctionEntry, RtlRestoreContext, ZwQueryInformationProcess, the rolling checksum, or the panic stub. It is a demultiplexer, not a validator.
7.2 UEH
Registered at 0x7FF9273F4523 (R13 I32) in eidolon_antidbg_veh_install_self. The registered UEH is eidolon_warden_emit_exception_event @ 0x7FF926072B40 — same function that services VEH chain-walks (shared telemetry backend). Returns EXCEPTION_CONTINUE_SEARCH → WER does the actual kill. Previous UEH saved at qword_7FF927CDE840 for chain-call purposes.
The UEH emits a crash telemetry event (via the Aegis slot to crashy::Report) before returning, so by the time WER terminates the process, the analysis process has already been fingerprinted unless an injected payload's head UEH swallows the exception.
7.3 Key negative result
Neither the VEH nor the UEH does stack-walking (R13 I27, I35). This reverses R12's brief suggestion that the presence of RtlLookupFunctionEntry in the runtime IAT implied caller-provenance. All 100% of callsites for that API + RtlCaptureContext + RtlRestoreContext are in statically-linked MSVC CRT boilerplate (__scrt_fastfail, capture_current_context, __FrameHandler3::GetEstablisherFrame, __acrt_call_reportfault). Linker artifact, not Eidolon behaviour.
8. Anti-tamper Primitives (Complete Inventory)
| Primitive | Status | Location | Effect |
|---|---|---|---|
| Rolling CFF predicate | Present but NOT integrity (R13 I30) | dword_7FF927C21FE0 | CFF opaque predicate used as pseudo-random loop bounds. Safe to corrupt |
ZwQueryInformationProcess(DebugPort) | ONE callsite (R13 I29) | sub_7FF926C0AF80 @ 0x7FF926C0C983 via thunk +0x610 | Writes state to dword_7FF927C3EE94..EEAC cluster on detection. No direct kill |
ZwQueryInformationThread | ONE callsite (R13 I29) | eidolon_antidbg_drread_block_d via thunk +0x620 | DR-register read check. No direct kill |
| Module enumeration | Active at init (R13 I28) | sub_7FF926DF20B0 (CreateToolhelp32Snapshot + Module32First/NextW + CloseHandle) | Feeds VM state for watchdog; no static reachability to kill paths |
| Thread enumeration | Active at init (R13 I28) | sub_7FF92714B2E0 (Thread32First/Next) | Decoupled from suspend/context path. Passive |
| Thread suspend + SetContext | Active (R13 I33) | sub_7FF926A9A1C0 + sub_7FF926629CF0 | Uses CONTEXT_FULL (0x10000B), NOT CONTEXT_DEBUG_REGISTERS (0x10010). Purpose: RIP hijacking / stack poisoning. Does NOT arm DRx |
WinVerifyTrust self-verify | Present | Synthetic IAT | Silent abort on failure; target inferred to be d2r_loader.dll itself |
| RDTSC timing | Light | Multiple sites, CFF-hidden | Bracket-style rdtsc; work; rdtsc; sub |
| Gadget rate-guard | Present | Per-window call-rate throttle | Over-threshold raises Standard.Alert event |
| DR0..DR3 read | Present | eidolon_antidbg_drread_block_* | Reads only, never clears |
| Code-section hashing | Not present | — | No SHA-256/FNV/CRC over .text |
| VEH chain walker | Not present | — | VEH installed but chain not enumerated |
| PEB.Ldr DKOM scan | Not present | — | No walker for unlink detection |
vmcall / hypervisor probe | Not present (R2 phantom) | — | Byte coincidence |
IsDebuggerPresent | Not imported (R6 I3) | — | Deliberately avoided |
Eidolon deliberately does NOT import: RaiseFailFastException, NtTerminateProcess, NtRaiseException, NtRaiseHardError, RtlReportCriticalFailure, IsDebuggerPresent, CheckRemoteDebuggerPresent, DebugBreak, _exit, quick_exit, abort (R6 I3).
9. Kill Paths
Four in-DLL kill paths exist (R6 I3). All four terminate via the synthetic IAT thunk at byte_7FF927BFE190 + 0xB0 which resolves to ExitProcess (R9 I15, confirmed via UCRT's ?exit_or_terminate_process@@YAXI@Z calling through the same slot).
| Path | Symbol / Address | Trigger | Defense |
|---|---|---|---|
| A | sub_7FF9267C6B70 → TerminateProcess via eidolon_telem_veh_prev_handler_router @ 0x7FF926860A05 | VEH-fatal classification | Install head VEH before Warden VEH; direct syscalls / ROP to dodge |
| B | sub_7FF926C47950 (8 callers) | Fingerprint fail-closed (mutated state words dword_7FF927C92FAC..FD4) | Don't write Eidolon's obfuscator state words |
| C | sub_7FF926DE7590 (1 caller) | Early init integrity fail (eidolon_vm_static_init_k_byte_off60) | Fires only during init; post-init injection is safe |
| D (historical) | "Panic vtable slot" — refuted | — | No such slot. R6 I3 misread .pdata metadata; R7 I9 corrected |
Panic stub sub_7FF9268F1140 has ZERO direct static callers (R9 I15, reconfirmed R13 I34/I35). Either orphan code or reached only via runtime-dispatched calls from the .eid decrypted emitter. The "panic vtable slot at 0x7FF927D34F98" from R6 I3 was actually the BeginAddress DWORD of an IMAGE_RUNTIME_FUNCTION_ENTRY in .pdata (R7 I9). Patching it does nothing useful.
Single-shot kill disarm (Tier-3): atomically swap *((void**)0x7FF927BFE240) (content of byte_7FF927BFE190 + 0xB0) to a the analysis process ret stub. Disarms all paths simultaneously. Caveat: legitimate D2R shutdown also routes through this thunk, so conditional gating is required.
10. Telemetry & Out-of-Process Brokers
10.1 BTel HTTPS pipeline
| Field | Value |
|---|---|
| URL | https://telemetry-in.battle.net/ |
| User-Agent | telemetry-sdk-cpp/4.5.0 |
| Auth | Bearer <token> (rotated by login flow) |
| Signing | AWS SigV4 — Curl_output_aws_sigv4 statically linked |
| TLS cert-pin | Subject must contain both Telemetry and Blizzard Entertainment |
| Cert validator | eidolon_telem_cert_validator_blizzard @ 0x7FF926F8A5A0 |
| Payload hashing | MD5 via sub_7FF9261E50B0 (R11 I22 — BTel request-seal builder) |
9 Standard.* event types: Process.Start, Process.Finish, Network.Request.Start/Finish, Network.Connection.Start/Finish, Network.Reachability, Metric.MetricSet, Alert. Fan-out via eidolon_symmetra_emit_event.
Flow-rules engine is closed: only SamplingRule, ParticipationRule, RetryPolicyRule. No Eval/Exec — early RCE-lite concern was incorrect.
10.2 Broker token slots
| Slot | RVA | IDA VA | Live value (stable across sessions) | Role |
|---|---|---|---|---|
| Aegis (crash) | 0x488CC0 | 0x7FF9224F8CC0 | 0xC181000C8D41C829 | Crash-reporter consumer token |
| Warden | 0x488CC8 | 0x7FF9224F8CC8 | 0xC801D129B3802195 | Warden broker consumer token |
The DLL never reads these slots itself (R5 A2, reconfirmed R6 via xref analysis, R7 live probe). They are host-side tokens written once at init and consumed out-of-process via ReadProcessMemory by the broker. R7 I10 identifies the broker as most likely Battle.net Agent.exe (not yet confirmed via handle scan — see §17).
10.3 crashy::Report
Fixed 1 KiB struct populated by the VEH unhandled path, handed to Warden via Aegis slot. Contains: exception record, last-N telemetry IDs, CPU state, small backtrace. The out-of-process property is critical: even if Eidolon dies mid-handler, the report has already left the process.
11. Cryptographic Primitives
| Algorithm | Purpose | Location |
|---|---|---|
| Rolling 8-byte XOR (phase-7) | IDD layer-1 encryption | Baked key 0xFFFF834A942B7856 |
MD5 (Round-1 constants 0xD76AA478, 0xE8C7B756, 0x242070DB, 0xC1BDCEEE, 0xF57C0FAF, 0x4787C62A, shifts 7,12,17,22) | IDD integrity tags + BTel HTTP payload seal | Transform sub_7FF927B65F90; drivers sub_7FF9264E3480, sub_7FF927057340 |
FNV-1a (offset=0x811C9DC5, prime=0x01000193) | Module-name + export-name lookup during bootstrap | Constants at 0x7FF926AC02DC (basis), 0x7FF926AC01BB (prime); walker at 0x7FF926AC86F0 (R6 I4) |
| MD4/MD5 IV | Resolver constant decrypt | qword_7FF927BA7610 = 01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10 (R11 I20) |
| AWS SigV4 | Telemetry HTTPS signing | Curl_output_aws_sigv4 statically linked |
| TLS cert pin | Telemetry TLS | eidolon_telem_cert_validator_blizzard |
Bootstrap-cached module bases ntdll @ 0x7FF927C3CDC0, kernel32 @ 0x7FF927C3CDC8 are themselves PEB.Ldr-folded (R7 live probe: live values 0xDA077F72427B86B8 / 0xBFA554FF2A841A63) — Eidolon stores no plaintext module bases anywhere.
12. Complete Address Registry
Live addresses from session with loader base 0x7FF922070000 (IDA base 0x7FF925E50000, delta -0x3DE0000).
12.1 Core structures
| Item | Live VA | IDA VA | RVA |
|---|---|---|---|
| d2r_loader.dll base | 0x7FF922070000 | 0x7FF925E50000 | — |
| Aegis token slot | 0x7FF9224F8CC0 | 0x7FF927CD8CC0 | 0x488CC0 |
| Warden token slot | 0x7FF9224F8CC8 | 0x7FF927CD8CC8 | 0x488CC8 |
| Synthetic IAT thunk table | 0x7FF923E1E190 | 0x7FF927BFE190 | 0x1DAE190 |
| ExitProcess thunk slot | 0x7FF923E1E240 | 0x7FF927BFE240 | 0x1DAE240 (= +0xB0) |
| Encrypted IDD (primary) | 0x7FF924321000 | 0x7FF928101000 | 0x22B1000 |
| Plaintext name table | 0x7FF924321376 | 0x7FF928101376 | 0x22B1376 |
| Decoy canary IID | 0x7FF924321A70 | 0x7FF928101A70 | 0x22B1A70 |
| Sibling IDD (BTel-seal) | 0x7FF92431B8F9 | 0x7FF9280FB8F9 | — |
| Trampoline arena (one observed) | 0x2049BCF0000 (heap) | — | — |
.eid section | — | loader + 0x1F50000..0x22D0000 | 3.5 MB encrypted |
12.2 Code
| Item | Live VA | IDA VA | RVA |
|---|---|---|---|
Entry Ordinal_1 consumer in D2R.exe | — | D2R.exe + RVA 0x15D7BF0 | — |
| IAT resolver (per-DLL) | 0x7FF922262AF0 | 0x7FF926042AF0 | 0x1F2AF0 |
| Resolver outer wrapper | — | 0x7FF927571DD0 | — |
| Per-round MBA mixer | 0x7FF923277340 | 0x7FF927057340 | 0x1207340 |
| MD5 transform | — | 0x7FF927B65F90 | — |
| MD5 driver A (IDD + BTel) | — | 0x7FF9264E3480 | — |
| MD5 driver B (sibling) | — | 0x7FF927057340 (same addr, clone shape) | — |
| BTel request-seal builder | — | 0x7FF9261E50B0 | — |
| FNV walker / bootstrap | — | 0x7FF926AC86F0 | — |
| CFF megadispatcher | 0x7FF9226824A0 | 0x7FF9264624A0 | 0x6124A0 |
| CFF megacaller (6 sites) | — | 0x7FF9277703C0 | 0x19203C0 |
| Small CFF dispatcher | — | 0x7FF926ABF3E0 | — |
| VEH OS-registered wrapper | 0x7FF922F0FDA0 | 0x7FF926CEFDA0 | 0xE9FDA0 |
| VEH inner dispatcher | 0x7FF9234AD200 | 0x7FF92728D200 | 0x143D200 |
| VEH bootstrap stage | — | 0x7FF926C48FF0 | — |
| VEH installer | — | 0x7FF927464BC0 | — |
| Fiber entry (VEH-scheduled) | — | 0x7FF927038110 | — |
| UEH target | — | eidolon_warden_emit_exception_event @ 0x7FF926072B40 | — |
| UEH installer call | — | 0x7FF9273F4523 | — |
| Megacaller thunk (registered as VEH callback) | — | 0x7FF927038110 | — |
| Panic stub (ZERO callers) | 0x7FF922B11140 | 0x7FF9268F1140 | 0xAA1140 |
| TerminateProcess kernel | — | sub_7FF9267C6B70 | — |
| Fingerprint-fail killer | — | sub_7FF926C47950 | — |
| VM-init integrity killer | — | sub_7FF926DE7590 | — |
| Telemetry VEH router | — | 0x7FF926860A05 | — |
| Cert pin validator | — | 0x7FF926F8A5A0 | — |
| Module-enum walker | — | sub_7FF926DF20B0 | — |
| Thread-enum walker | — | sub_7FF92714B2E0 | — |
| Thread-hijack SetContext | — | sub_7FF926A9A1C0 + sub_7FF926629CF0 | — |
| ZwQIP callsite | — | sub_7FF926C0AF80 @ 0x7FF926C0C983 | — |
| VirtualAlloc arena allocator | — | sub_7FF9271EBBD0 @ 0x7FF9271EBC9C | — |
| VirtualProtect hardener | — | sub_7FF926C8BC80 @ 0x7FF926C8CA98 | — |
| TLS callback | — | eidolon_tls_callback_0 @ 0x7FF9270AC100 | — |
| Per-thread FiberData getter | — | 0x7FF92666A840 | — |
12.3 Data globals
| Item | IDA VA | Description |
|---|---|---|
| CFF megadispatcher cookie (mutable) | dword_7FF927C71A4C | Current 0x2EB7282F (R7 I7) |
| CFF state global (small) | dword_7FF927C8BFDC | — |
| Rolling CFF predicate (NOT integrity) | dword_7FF927C21FE0 | R13 I30: safe to corrupt |
| IAT-side CFF seed (constant) | dword_7FF927C93348 | 0xC33D52D5 |
| BTel-side CFF seed (constant) | dword_7FF927CA4168 | 0xB4395223 |
| BTel CFF edge table | 0x7FF927CA4070..4170 | 40 DWORDs |
| Primary CFF seed A | dword_7FF927C5FF38 | Build-time |
| Primary CFF seed B | dword_7FF927C5FF88 | Build-time |
| IAT-resolver CFF entry seed | dword_7FF927C5FDEC | — |
| Per-API encrypted seeds | qword_7FF927C2AF70, qword_7FF927C2AF80..AFD0 | 9 IDD slots |
Static scratch (r14) | qword_7FF927CD9D40 | 248 × 24-byte output table |
| PVA anti-debug cluster | dword_7FF927C3EE94..EEAC | ZwQIP state writes |
| Obfuscator state words | dword_7FF927C92FAC..FD4 | Kill-path B trigger |
| Cached ntdll base | 0x7FF927C3CDC0 | PEB.Ldr-folded |
| Cached kernel32 base | 0x7FF927C3CDC8 | PEB.Ldr-folded |
| Worker thread handle | qword_7FF927CDE8A0 | Don't touch |
| Saved old UEH | qword_7FF927CDE840 | Don't touch |
| MD4/MD5 IV | qword_7FF927BA7610 | 01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10 |
| IDD XOR key (baked) | 0xFFFF834A942B7856 | Rolling phase-7 |
13. Verified-Decoded API Map
Live-emulator walked 128-byte trampoline bodies, executed arithmetic chain + PEB.Ldr fold (live PEB.Ldr 0x00007FFA3AC528E0), matched final target against live exports of kernelbase/ntdll/kernel32 (4,389 total). Combined results from R11 I19 + R12 I23 + R13 extended emulator: 64 of 248 thunks decoded.
13.1 Kernelbase (49 APIs)
CreateEventW, CreateFiber, CreateProcessW, CreateThread, DeactivateActCtx, FlsAlloc, FlushInstructionCache, GetCommandLineW, GetComputerNameW, GetCurrentDirectoryW, GetCurrentProcessId, GetFinalPathNameByHandleW, GetModuleFileNameW, GetModuleHandleA, GetModuleHandleExW, GetModuleHandleW, GetOEMCP, GetProcessId, GetStartupInfoW, GetVersionExW, GlobalMemoryStatusEx, IsThreadAFiber, MapViewOfFile, Module32FirstW, Module32NextW, QueryPerformanceFrequency, ReadFile, ResetEvent, SetEndOfFile, SetEvent, SetFilePointer, SetFilePointerEx, SetLastError, SetStdHandle, SetThreadContext, SetUnhandledExceptionFilter, Sleep, SleepEx, SuspendThread, SystemTimeToTzSpecificLocalTime, Thread32First, TlsGetValue, UnmapViewOfFile, VerifyVersionInfoW, VirtualAlloc, VirtualFree, VirtualProtect, WaitForMultipleObjects
13.2 Ntdll (11 APIs)
RtlExitUserThread, RtlReleaseSRWLockShared, RtlTryAcquireSRWLockExclusive, RtlRunOnceInitialize, TpStartAsyncIoOperation, ZwContinue, ZwQueryInformationProcess, RtlCaptureContext, RtlLookupFunctionEntry, RtlRestoreContext, VerSetConditionMask
13.3 Kernel32 (6 APIs)
CryptDestroyKey, CryptEncrypt, GetUserNameW, RegCloseKey, RegOpenKeyExW (plus one unresolved target at 0x7FFA390A2900)
13.4 Key thunk-offset map
| Thunk offset | API |
|---|---|
+0x40 | CreateFiber-like (unresolved specific — very short chain) |
+0x60 | CreateProcessW |
+0x68 | CreateThread |
+0xB0 | ExitProcess (R9 I15, via UCRT signature) |
+0xB8 | RtlExitUserThread |
+0x110 | FlushInstructionCache |
+0x160 | GetComputerNameW |
+0x188 | GetCurrentProcessId |
+0x1E8 | GetFinalPathNameByHandleW |
+0x218 | GetModuleFileNameW |
+0x2F0 | GetVersionExW |
+0x380 | IsThreadAFiber |
+0x3C0 | MapViewOfFile |
+0x3D0 | Module32FirstW |
+0x3D8 | Module32NextW |
+0x450 | RtlReleaseSRWLockShared |
+0x4A0 | SetFilePointerEx |
+0x4A8 | SetLastError (R11 corrected earlier "release-SRW" guess) |
+0x4C0 | SetThreadContext |
+0x4D0 | SetUnhandledExceptionFilter |
+0x4F8 | TpStartAsyncIoOperation |
+0x500 | SuspendThread |
+0x530 | Thread32First |
+0x550 | TlsGetValue |
+0x558 | FlsSetValue (R13 I35 context) |
+0x560 | RtlTryAcquireSRWLockExclusive |
+0x578 | UnmapViewOfFile |
+0x598 | VirtualProtect |
+0x610 | ZwQueryInformationProcess |
+0x620 | ZwQueryInformationThread |
+0x628 | RtlCaptureContext (unused by Eidolon — CRT) |
+0x630 | RtlLookupFunctionEntry (unused by Eidolon — CRT) |
+0x640 | RtlRestoreContext (unused by Eidolon — CRT) |
+0x690 | CryptDestroyKey |
+0x698 | CryptEncrypt |
+0x6D0 | RegCloseKey |
The remaining 184 thunks are "modified PEB.Ldr fold" variants that require a more flexible fold matcher than the current emulator (R13).
14. Phantom List (30+ Debunked Claims)
Every hypothesis a future agent should NOT chase, organized by correcting round.
Round 5 corrections (of rounds 1-4)
- "5 hook trampolines in
.text" (R3 Agent W) → OLLVM obfuscation salts, not hooks. - "Foreign call to
0x7FF8B2858213" (R3 Agent W) → IDA mis-resolution of indirect call. - "Per-thread Eidolon TLS at MSVC
_tls_index" (R3 Agent Z) → Eidolon usesgs:[0x20](FiberData);_tls_indexrefs are MSVC C++ thread-local template plumbing. - "VM dispatch table at
0x7FF927D3A044" (R3 Agent H) →.pdatareconstruction singleton. - "FNV-1a IAT resolver at
0x7FF926ABF3E0" (R4 Agent F) → Address is the CFF dispatcher of one flattened function. - "Hyper-V / VMCALL probe" (R2 Agent 3) → byte coincidence.
- "SHA-256 hash over
.text" (R2 Agent B) → no hash exists. - "Eidolon VM with bytecode interpreter" (R3-R4) → OLLVM/Tigress CFF (R5 A4).
- "Aegis/Warden slots decoded in-DLL via PEB-Ldr" (R1-R4) → Host-side tokens (R5 A2).
- "Resolved-pointer table = 32-byte rows" (R4 + v1 doc) → variable-length records (R5 A3 → R6 I4 further correction).
Round 6 corrections (of round 5)
- "Resolved-pointer table = 16-byte rows" (R5 A3) → Actually variable-length DLL-marker + function-entry records.
- "Trampoline arena at single contiguous
0x2049C330000" (R5) → That was one trampoline's target, not an arena. Real arena (one of potentially multiple) at0x2049BCF0000(R8). - "Caller-provenance check lives in
d2r_loader.dll" (pre-R6 plan) → Not in this DLL (R6 I1, reconfirmed R13 I27/I35). - "CFG/XFG enforcement causes hijack failure" (post-R6 hypothesis) → Both binaries have
GuardFlags = 0x100only, function table empty, dispatch stub no-opFF E0(R6 CFG probe). - "IDD encryption is PEB.Ldr-keyed" (R5 hypothesis) → Baked constant
0xFFFF834A942B7856(R6 I4).
Round 7 corrections (of round 6)
- "
0x7FF927D34F98is a panic vtable slot" (R6 I3) →IMAGE_RUNTIME_FUNCTION_ENTRY.BeginAddressin.pdata(R7 I9). Patching does nothing. - "
sub_7FF9264E3480is the IAT resolver kernel" (R6 I2) → MD5 driver (R7 I8). - "Each megadispatcher call passes a distinct
r8op-tag" (R6 I2) →r8 = rsistate ptr; real op is(rcx, rdx, r9)triplet, dispatch keyed on mutable global cookie (R7 I7). - "Cached
ntdll/kernel32base globals are plaintext pointers" (R6 probe assumption) → PEB.Ldr-folded (R7 live probe).
Round 8 corrections
- "IAT resolver has 23 direct callees" (IDA
func_profilein R6-R7) → Only 5 real direct callees; 18 were CFG-flattening artifacts (R8 I13). - "The megacaller thunk is a VEH handler" (R7 I7 hypothesis) → It's a fiber-state CFF dispatcher; real VEH is
sub_7FF926CEFDA0(R8 I12). - "
byte_7FF927BFE190 + 0x40isAddVectoredExceptionHandler" (R7) → It's a lock-acquire-shape (R8 I12); R11 narrowed that+0x4A8 = SetLastErrorkills the "lock pair" narrative. - "Trampoline emitter has a static template in
.rdata" (implicit R1-R7) → No static template; per-byte arithmetic (R8 I14). - "
.pdatais wiped on disk and reconstructed at runtime" (R5) → Probably wrong for PTR 3.2; live probe showed.pdataintact (R6 live-probe correction).
Round 9 corrections
- "Panic stub
sub_7FF9268F1140has direct callers" (implicit R6) → Zero direct static callers (R9 I15). Orphan or runtime-computed dispatch only. - "IDD has layer-2 obfuscation" (R8 hypothesis) → No layer-2; the "fingerprint constants" are plaintext integrity tags (R9 I11).
Round 11-12 corrections
- "
sub_7FF92604CE40is the trampoline emitter" (R11 I19 heuristic) → CFF byte-aliasing artifacts misread as byte writes (R12 I23). Emitter is in.eid. - "
dword_7FF927C93348/dword_7FF927CA4168are per-API RNG counters" (R11 I19) → Zero write sites each; build-time embedded CFF seeds (R12 I25). - "Runtime IID at
0x7FF928101A70has 248 entries" (R9 I11 assumption) → 39 canary IIDs, decoy table (R12 I26). - "CRT
operator new[]allocates the arena" (R12 I24) →VirtualAllocwithPAGE_EXECUTE_READWRITE(R13 I31).
Round 13 corrections (of round 12)
- "Eidolon stack-walks for caller-provenance because
RtlLookupFunctionEntryis imported" (R12 reversal of R6) → R6 was right. 100% of callsites live in MSVC CRT boilerplate (R13 I27). Eidolon itself never calls stack-walk APIs (R13 I35 full VEH decompile). - "Rolling checksum
dword_7FF927C21FE0is an integrity check" (R11 I21 + R12 I25 hypothesis) → CFF opaque predicate; safe to corrupt (R13 I30). - "SuspendThread + SetThreadContext arms DRx" (implicit since R6) → Uses
CONTEXT_FULL (0x10000B), notCONTEXT_DEBUG_REGISTERS (0x10010). Purpose is RIP hijacking / stack poisoning (R13 I33). - "No module enumeration at all" (R6 I5) → Partial reversal:
sub_7FF926DF20B0DOES walk Toolhelp at init; but feeds passive VM state, not a kill-switch (R13 I28). PEB-unlink defense still sufficient.
15. Phantom-Debunk Index by Round
| Round | Phantoms debunked | Key agent |
|---|---|---|
| 5 | 10 (#1-10) | A1-A5 |
| 6 | 5 (#11-15) | I1, I5, CFG probe, I4, I4 |
| 7 | 4 (#16-19) | I7, I8, I9, live probe |
| 8 | 5 (#20-24) | I12, I13, I14, live probe |
| 9 | 2 (#25-26) | I11, I15 |
| 11-12 | 4 (#27-30) | I23, I25, I26, I31 |
| 13 | 4 (#31-34) | I27, I28, I30, I33 |
16. the analysis process Defense Plan (Consolidated R10 + R13)
16.1 Tier 1 — Required (8 defenses)
| # | Defense | Source | Rationale |
|---|---|---|---|
| 1 | Option-2 injector fix (NtCreateThreadEx(StartAddress=LoadLibraryA)) | R10 §1.1 | PTR 3.2 regression. Hijacked-thread shellcode spawns a helper thread whose Win32StartAddress + [rsp] both appear module-resident. Sidesteps whatever the actual out-of-process mechanism is. Implementation: injector/src/lib.rs, shellcode builder lines 406-497. Build with just build-all |
| 2 | Head-of-list VEH with magic tag 0xE0D1ABB0 | R10 §1.2, R13 I32 | Tag-match swallow our own exceptions so crashy::Report never fingerprints the analysis process. Non-tagged: pass through |
| 3 | Head UEH with same magic tag (NEW R13) | R13 I32 | Eidolon's UEH eidolon_warden_emit_exception_event emits telemetry before returning CONTINUE_SEARCH; our UEH must catch our own unhandled exceptions first |
| 4 | PEB-Ldr unlink + PE-header erase | R10 §1.3 | Keep even though R13 I28 showed Eidolon's module walker is passive — Warden broker historically scans modules |
| 5 | Hosts-file redirect 127.0.0.1 telemetry-in.battle.net | R10 §1.4 | Cert-pin ensures silent TLS failure; local Standard.Alert queue is bounded and non-escalating |
| 6 | Hook VirtualProtect (thunk +0x598) (NEW R13) | R13 I31 | Prevents Eidolon's post-emit hardening pass from making arena pages PAGE_EXECUTE_READ. Keeps trampolines writable for later patching |
| 7 | Hook NtSetContextThread (NEW R13) | R13 I33 | Blocks Eidolon from hijacking an injected payload's thread RIPs via CONTEXT_FULL writes |
| 8 | Overwrite thunk +0x610 (ZwQIP) (NEW R13) | R13 I29 | Single callsite for debug-port check. Shim to always return success + zero buffer. Surgical debugger-hide |
16.2 Tier 2 — Hygiene
Don't touch:
- Aegis slot
0x7FF9224F8CC0, Warden slot0x7FF9224F8CC8(host tokens) d2r_loader.dll.text(no integrity check today, butVirtualProtectdelta is cheap server-side addition)- Synthetic IAT thunk table
0x7FF923E1E190(corrupts Eidolon's imports) - Trampoline arena
0x2049BCF0000(PEB.Ldr-bound) qword_7FF927CDE8A0(Eidolon worker thread handle)qword_7FF927CDE840(saved old UEH — for chain call)dword_7FF927C3EE94..EEAC(PVA anti-debug cluster)dword_7FF927C5FF38,dword_7FF927C5FF88(CFF seeds)dword_7FF927C92FAC..FD4(fingerprint state — triggers kill-path B)- Eidolon's 3 named exports (
eidolon_run,g_warden_aegis_crash_callback_export,g_warden_telemetry_callback_export)
Safe to touch (R13 confirmations):
dword_7FF927C21FE0(rolling CFF predicate, not integrity — R13 I30)- Thunk
+0x598VirtualProtect (R13 I31) - Thunk
+0x610ZwQIP (R13 I29) - Panic stub
sub_7FF9268F1140(zero callers — R9 I15, R13 I34/I35) 0x7FF927D34F98(.pdatametadata, nothing dispatches through it — R7 I9)
Do:
- Install hooks AFTER Eidolon's VEH install completes
- Stay below gadget rate-guard threshold (unknown exact value)
- Leave DR0..DR3 at zero (reads without clears)
- Avoid rapid RDTSC in callbacks under Eidolon scrutiny
- Don't call
IsDebuggerPresentif debugger-attached
Eject path:
permanent_restorePE headersrelink_moduleinto all 3 PEB.Ldr listsRemoveVectoredExceptionHandlerfor our head VEHSetUnhandledExceptionFilter(nullptr)then restore Eidolon's saved UEH- Restore inline hooks
FreeLibraryAndExitThread
16.3 Tier 3 — Optional / speculative
- ExitProcess thunk swap — conditional single-shot disarm of all 4 kill paths via
*((void**)0x7FF927BFE240) = eidolon_ret_stub. Caveat: legitimate CRT shutdown uses the same slot; gate on "game still healthy" - Offline IDD decrypt — the Python decryptor in §5 is the full layer-1 pipeline
- Dynamic trampoline emitter hunt — DR0 write-trap on arena page (only way; R13 I34 definitive)
16.4 Coverage matrix
| Threat | Defense | Status |
|---|---|---|
| BTel telemetry leak | Hosts-file + cert-pin | Mitigated |
| crashy::Report capture of the analysis process fault | Head VEH + head UEH | Mitigated |
| Module enumeration by Warden | PE-header erase + PEB unlink | Mitigated |
| Aegis/Warden slot corruption | Don't touch | Avoided |
| Gadget over-rate | Throttle | Mitigated |
| HW BP detection | Don't use HW BPs | Avoided |
.text modification | Don't modify | Avoided (today) |
| Caller-provenance regression (PTR 3.2) | Option-2 helper thread | Open (fix designed, not yet built) |
Arena hardening (VirtualProtect→RX) | Hook VirtualProtect thunk | Open (R13 new) |
| Thread hijacking by Eidolon | Hook NtSetContextThread | Open (R13 new) |
| Debugger visibility via ZwQIP | Overwrite thunk +0x610 | Open (R13 new) |
| HostInfo correlation | None — accepted risk | Accepted |
17. Open Questions (Ranked by Importance)
| # | Question | Why it matters | Resolution path |
|---|---|---|---|
| 1 | Where exactly in .eid is the trampoline emitter? | Last big missing piece; enables offline target recovery for the 184 un-decoded thunks | Dynamic DR0 write-trap on arena page 0x2049BCF0000 + 0; catch first mov byte [arena], 0x48; dump containing page (R14 probe) |
| 2 | Who is the Warden broker PID? | Confirms the PTR 3.2 regression cause; lets the analysis process optionally hide from the broker | NtQuerySystemInformation(SystemHandleInformation) filtered to PROCESS_VM_READ handles pointing into D2R's PID (R14 handle-scan probe) |
| 3 | What does CreateProcessW at 0x7FF926B1192A launch? | Could be a crash-reporter subprocess or Warden-adjacent helper | Runtime capture via detour on CreateProcessW thunk +0x60 |
| 4 | What's in fiber entry 0x7FF927038110? | VEH schedules it; if there's any validator anywhere, it's here or in sibling sub_7FF92728E960 | Manual decompile or dynamic breakpoint + step |
| 5 | Decode remaining 184 trampolines | Full map of Eidolon's Win32 surface | Extend emulator to handle modified PEB.Ldr fold variants (insert arithmetic between fold anchors) |
| 6 | Does sub_7FF927544D30 ever flip ContextFlags to 0x10010? | Would change R13 I33's "no DRx arming" conclusion | Runtime watch on the SetThreadContext path |
| 7 | Is there an on-disk hash of d2r_loader.dll beyond WinVerifyTrust? | Would break single-byte mutations outside the running process | 1-byte mutation test + relaunch |
18. Dynamic Probe Roadmap (R14)
R14 probes being developed to answer §17:
probe_r14_arena_writewatch.lua— set DR0 write-trap on arena first page, catch the emitter instruction address, dump the containing.eiddecrypted page. Answers §17 Q1.probe_r14_handle_scan.lua— walkNtQuerySystemInformation(SystemHandleInformation), filter toPROCESS_VM_READhandles into D2R's PID, identify the broker. Answers §17 Q2.probe_r14_createprocess_watch.lua— detour thunk+0x60, loglpApplicationName+lpCommandLine. Answers §17 Q3.probe_r14_fiber_trace.lua— breakpoint0x7FF927038110entry, capture register state + first 256 bytes of fiber stack. Answers §17 Q4.probe_r14_fold_emulator_v2.py— flexible fold matcher that accepts arithmetic between fold anchors. Answers §17 Q5.probe_r14_ctxflags_watch.lua— DR1 onsub_7FF926629CF0'smov [rax], 0x10000Bsite, alert on any non-0x10000Bwrite. Answers §17 Q6.
19. Credits
Per-round contributors to the final truth:
| Round | Headline findings | Agents |
|---|---|---|
| 1-4 | Initial RE, many hypotheses (most later corrected) | Various (W, Z, H, F, 3, B) |
| 5 | "No VM", IAT resolver at 0x7FF926042AF0, slots are host-side tokens, trampoline writer inside megadispatcher CFF arm | A1-A5 |
| 6 | No in-DLL caller-provenance, no CFG enforcement, no bot-DLL scan (decoy-misled), 4 kill paths, MD5 driver at 0x7FF9264E3480, IDD XOR key 0xFFFF834A942B7856, FNV bootstrap | I1 (no provenance), I2 (megadispatcher), I3 (kill paths), I4 (IDD key + FNV), I5 (no bot-scan) |
| 7 | IDD decryption confirmed live, panic-vtable debunk, MD5-driver reclassification, megacaller op-tag correction, VEH/fiber callback structure, Warden broker identified as likely Battle.net Agent.exe | I6 (decrypt), I7 (op-tags), I8 (MD5), I9 (panic-vtable), I10 (broker) |
| 8 | Trampoline arena 0x2049BCF0000, per-trampoline polymorphism, sibling IDD, real VEH sub_7FF926CEFDA0, thunk-table byte_7FF927BFE190 is the real runtime table | I11 (2nd-layer), I12 (VEH thunk), I13 (callees), I14 (emitter hunt), I15 (panic callers) |
| 9 | Panic stub has zero callers; [byte_7FF927BFE190 + 0xB0] = ExitProcess; .pdata intact; plaintext name table is a decoy; 18 pure-arithmetic thunks decoded | I15 (zero callers), I16 (VEH correction), I17 (emulator), I18 (table layout) |
| 10 | Consolidated defense plan | (synthesis doc) |
| 11 | 17/18 thunks resolved to real API names; full fusion-math equations; per-DLL iteration model; sub_7FF9261E50B0 is BTel request-seal not 2nd IAT; 0x7FF926C48FF0 is VEH bootstrap | I19 (emitter candidates), I20 (prologue), I21 (equations), I22 (taxonomy) |
| 12 | 63 thunks decoded; "round-6 reversal" (later retracted); 39-entry canary IID identified as decoy; outer wrapper per-DLL; sub_7FF92604CE40 is not the emitter | I23 (emitter debunk), I24 (wrapper), I25 (counter-seeds), I26 (decoy IID) |
| 13 | Round-12 reversal retracted (R6 correct); module/thread enum are passive; rolling "checksum" is CFF predicate; thread hijack is RIP not DRx; arena is VirtualAlloc; UEH found; emitter in .eid (definitive); 8 Tier-1 defenses total | I27 (no stack-walk), I28 (enum is passive), I29 (ZwQIP), I30 (csum safe), I31 (arena alloc), I32 (UEH), I33 (no DRx), I34 (emitter in .eid), I35 (VEH full), I36 (no emitter in .text) |
| 14 | (pending R14 dynamic probes) | — |
End of dossier. For corrections or new findings, supersede this document with v5 rather than editing in place; preserve the round-round chain so future agents can trace the correctness history.