diff --git a/docs/engine-re.md b/docs/engine-re.md index 80a5581..b5dfd97 100644 --- a/docs/engine-re.md +++ b/docs/engine-re.md @@ -48,13 +48,23 @@ Related: `docs/scjump-progression.md` (the SCJUMP decoder that hit this wall), ` `GetProcAddress`-resolved into private pointer tables, invisible to a static IAT scan. Report/dump left at `build/pe-sieve/process_/` (disposable). - **→ The right approach (queued, Frida-based; the real "Task B"):** name the dynamically-resolved APIs at - their call sites via the LIVE process. (1) Frida-read the loaded modules' export tables → `{runtime_addr - → dll!Func}`; (2) read the engine module's resolved import-pointer storage from the SAME live process, - resolve each stored pointer → name, and record **`RVA → name`** (RVAs in the fixed `0x400000` main module - are ASLR-stable; only the DLL targets relocate, and we resolve those live); (3) apply the `RVA → name` - labels to the `/v2` Ghidra image. All plain-JS Frida reads (no spawn, no patching — anti-tamper-safe). - This replaces the pe-sieve step; spec/plan when picked up. + **→ The Frida import-map approach — ✅ DONE 2026-07-09 (replaced pe-sieve).** Named the + dynamically-resolved APIs at their call sites via the LIVE process. `tools/frida/map_imports.py` + (read-only, plain-JS): (1) Frida-reads all loaded modules' export tables → `{runtime_addr → dll!Func}` + (23,342 exports); (2) scans the `0x400000` module for aligned DWORDs holding those addresses → `RVA → + name` (ASLR-stable: RVAs into the fixed main module transfer to the dump even though the DLL targets + relocate); (3) a `run_script_inline` pass labels the `/v2` image `imp__`. **Result: the packer's + rebuilt core IAT lives at RVA `0x16f000` (VA `0x56f000`) — 248 imports labeled** (kernel32 129, user32 57, + winmm 20, gdi32 17, advapi32/ole/oleaut/version/ntdll), 0 clobbers. **Validated:** `FUN_0044f390` now reads + `(*imp_kernel32_CreateFileA)` / `(*imp_kernel32_SetFilePointer)` at its resolver I/O; `sleep_timer_arm` reads + `(*imp_winmm_timeGetTime)()` — pinning the long-standing `DAT_0056f3d4` = **timeGetTime**. Tool: + `tools/frida/map_imports.py [--recon]` → `build/import-map.json`; plan + `docs/superpowers/plans/2026-07-09-frida-import-map.md`. + **Known limit (by design):** only the module-resident IAT is labelable. `d3d9`/`shell32`/`dsound`/CRT are + `GetProcAddress`-resolved into HEAP (not in the `0x400000` dump), so they aren't labeled — and D3D9 is used + via COM vtables (`Present` = device vtable slot 17, see `probe_present.py`), not an import thunk, so this + costs us nothing on the render path. 29 isolated singleton matches were set aside (`build/import-map-singletons.json`), + not auto-applied. --- diff --git a/docs/tools-reference.md b/docs/tools-reference.md index 32541ee..890546d 100644 --- a/docs/tools-reference.md +++ b/docs/tools-reference.md @@ -155,6 +155,7 @@ texture ops (no GPU context) — run windowed for real scenes. User args (after | `tools/frida/find_globals_base.py` | Runtime-global RE (SHELVED — see `docs/global-memory-re.md`): flat-int32 signature scan for the VM global array. Finds nothing → layout isn't flat. | `--build-sig` · `py -3.11 -u -X utf8 tools/frida/find_globals_base.py [pid]` | `*INIT` → `build/globals-signature.json`; scans running game | | `tools/frida/find_global_by_sequence.py` | Runtime-global RE (SHELVED): differential resId value-scan + stability filter. Finds stack proxies; proved `G[0x62424]` is a transient arg-register. | `py -3.11 -u -X utf8 tools/frida/find_global_by_sequence.py [pid]` | running game + index → stdout | | `tools/frida/dump_engine.py` | ★ **Dump the UNPACKED engine code** from the live process for offline static RE (native handlers). `AGE.EXE` unpacks in-place at `0x400000`; Kelebek VAs map `VA−0x400000` = file-off. Validated via the AGF-decoder landmark `+0x74f1f`. | `py -3.11 -u -X utf8 tools/frida/dump_engine.py [pid]` | running game → `build/engine-dump/{manifest.json,range_.bin}` | +| `tools/frida/map_imports.py` (+ `map_imports_full.py`) | ★ **Name dynamically-resolved Win32 APIs** in the Ghidra image. Read-only: maps live-process module exports → `{addr→dll!Func}`, scans the `0x400000` module for pointer matches → `RVA→name` (ASLR-stable). `--recon` = clustering report (the gate); default writes the map. Applied to `/v2` via a `run_script_inline` pass → 248 `imp__` labels at the RVA `0x16f000` IAT (validated: CreateFileA/SetFilePointer/timeGetTime). Pure scan/cluster logic unit-tested (`test_map_imports.py`). | `py -3.11 -u -X utf8 tools/frida/map_imports.py [--recon]` | running game → `build/import-map.json` (+ `-singletons.json`) | | `tools/frida/probe_handlers.py` | Probe which region the interpreter executes from (module vs heap). Confirmed: **operand-fetch `+0x1b940` fires ~8500/s ⇒ interpreter runs from the module `0x400000`** (handlers hookable by dump address). | `py -3.11 -u -X utf8 tools/frida/probe_handlers.py [pid]` | running game → stdout (per-hook fire counts) | | `tools/frida/capture_gfx_objects.py` | Capture the native gfx object-manager state: grab engine ctx (`esi` via operand-fetch `ecx`), poll the object-record array `[esi+0x53d64]` (20×120B; `field[0]=0xffffffff`=free, cmd-type at rec+0x24). **⚠ Its "0 CG records ⇒ drift is state-divergence" reading was DISPROVEN** (Ghidra: op 0x215 read settles the drift as a native command-buffer op — `docs/engine-re.md`; the poll observed the record array, not the lookup map that drives the branch, and cmd-buffer records are transient). Kept as a runtime-observation tool. | `py -3.11 -u -X utf8 tools/frida/capture_gfx_objects.py [pid] [secs]` | running game → `build/gfx-objects.jsonl` | | `tools/frida/probe_frame_cadence.py` | **Frame-cadence probe** (`docs/engine-re.md` "Frame cadence — live measurement"): plain-JS hook on operand-fetch `0x41b940` (grab ctx + count exec rate) + system-DLL message/timing hooks; auto-buckets by Ctrl/skip-bit. Measured: exec **rate-limited** ~1788 ops/sec normal, ~4× fast-forward. **Read-only/import-only — never CModule-hook the hot interpreter (crashes the game).** Play actively during capture; hold Ctrl the back half. | `py -3.11 -u -X utf8 tools/frida/probe_frame_cadence.py [secs] [proc]` | running game → `build/frida-frame-cadence.jsonl` + stdout report |