wowdump 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +21 -53
  3. package/dist/adapters/reader.js +33 -0
  4. package/dist/analysis/disassemble.js +77 -0
  5. package/dist/{frida-runtime.js → analysis/frida-runtime.js} +48 -48
  6. package/dist/analysis/runtime-script.js +36 -0
  7. package/dist/cli.js +255 -189
  8. package/dist/core/profile-engine.js +238 -0
  9. package/dist/frida-worker.js +54 -55
  10. package/dist/{reader-broker.js → reader/broker.js} +15 -0
  11. package/dist/{reader-client.js → reader/client.js} +1 -1
  12. package/dist/{windows-launcher.js → reader/launcher.js} +16 -4
  13. package/dist/reader/main.js +100 -0
  14. package/dist/reader/protocol.js +1 -0
  15. package/dist/reader/windows.js +242 -0
  16. package/dist/reader-main.js +1 -66
  17. package/dist/toolchain.js +102 -573
  18. package/package.json +9 -10
  19. package/skills/wowdump/SKILL.md +22 -15
  20. package/skills/wowdump/references/commands.md +63 -0
  21. package/skills/wowdump/references/disassemble.md +18 -0
  22. package/skills/wowdump/references/dynamic.md +54 -0
  23. package/skills/wowdump/references/evidence-workflow.md +41 -0
  24. package/skills/wowdump/references/profiles.md +34 -0
  25. package/skills/wowdump/references/request-schema.md +28 -0
  26. package/skills/wowdump/references/workflow.md +44 -0
  27. package/skills/wowdump/scripts/dynamic-session.js +133 -0
  28. package/dist/agent.js +0 -1332
  29. package/dist/discovery.js +0 -48
  30. package/dist/dry-run.js +0 -36
  31. package/dist/error-log.js +0 -71
  32. package/dist/focused-session.js +0 -89
  33. package/dist/ghidra.js +0 -769
  34. package/dist/main.js +0 -66
  35. package/dist/observability.js +0 -41
  36. package/dist/processes.js +0 -44
  37. package/dist/session.js +0 -42
  38. package/dist/storage.js +0 -12
  39. package/dist/windows-reader.js +0 -102
  40. package/dist/wow-analysis.js +0 -1405
  41. package/skills/wowdump/commands.md +0 -44
  42. /package/dist/{adapters.js → core/build-adapters.js} +0 -0
  43. /package/dist/{types.js → core/types.js} +0 -0
package/dist/main.js DELETED
@@ -1,66 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { buildAdapterRegistry } from "./adapters.js";
3
- import { discoverInstalls } from "./discovery.js";
4
- import { createDryRunReport } from "./dry-run.js";
5
- import { listWowProcesses } from "./processes.js";
6
- const gameRoot = process.env.WOW_ROOT ?? "D:/Game/World of Warcraft";
7
- const agentFile = resolve(process.env.WOW_AGENT ?? "dist/agent.js");
8
- const output = resolve(process.env.WOW_ERRORS ?? "data/errors.jsonl");
9
- const pollMs = Number(process.env.WOW_POLL_MS ?? 1500);
10
- const installs = discoverInstalls(gameRoot);
11
- if (process.argv.includes("--discover")) {
12
- console.log(JSON.stringify(installs, null, 2));
13
- }
14
- else if (process.argv.includes("--dry-run")) {
15
- try {
16
- const processes = await listWowProcesses(installs);
17
- console.log(JSON.stringify(createDryRunReport(installs, processes, buildAdapterRegistry), null, 2));
18
- }
19
- catch (error) {
20
- console.error("process scan:", error);
21
- process.exitCode = 1;
22
- }
23
- }
24
- else {
25
- await runCollector();
26
- }
27
- async function runCollector() {
28
- const [{ ProcessSession }, { JsonlStore }] = await Promise.all([
29
- import("./session.js"),
30
- import("./storage.js")
31
- ]);
32
- console.log(`Discovered ${installs.length} WoW installs under ${gameRoot}`);
33
- const store = new JsonlStore(output);
34
- const sessions = new Map();
35
- async function poll() {
36
- const processes = await listWowProcesses(installs).catch(error => {
37
- console.error("process scan:", error);
38
- return [];
39
- });
40
- const live = new Set(processes.map(process => process.pid));
41
- for (const [pid, session] of sessions) {
42
- if (!live.has(pid)) {
43
- await session.detach();
44
- sessions.delete(pid);
45
- }
46
- }
47
- for (const process of processes) {
48
- if (sessions.has(process.pid))
49
- continue;
50
- const session = new ProcessSession(process, agentFile, store);
51
- await session.attach()
52
- .then(() => sessions.set(process.pid, session))
53
- .catch(error => console.error(`attach ${process.pid}:`, error));
54
- }
55
- }
56
- await poll();
57
- const timer = setInterval(() => void poll(), pollMs);
58
- const shutdown = async () => {
59
- clearInterval(timer);
60
- for (const session of sessions.values())
61
- await session.detach();
62
- process.exit(0);
63
- };
64
- process.once("SIGINT", shutdown);
65
- process.once("SIGTERM", shutdown);
66
- }
@@ -1,41 +0,0 @@
1
- export const OBSERVATION_TIERS = [
2
- "error_text",
3
- "source_location",
4
- "stack_trace",
5
- "vm_metadata"
6
- ];
7
- export const OBSERVATION_TIER_DEFINITIONS = Object.freeze([
8
- Object.freeze({
9
- tier: "error_text",
10
- capability: "errorMessage",
11
- description: "Read the existing error text at a verified error boundary"
12
- }),
13
- Object.freeze({
14
- tier: "source_location",
15
- capability: "sourceLocation",
16
- description: "Read source and line metadata when its layout is proven"
17
- }),
18
- Object.freeze({
19
- tier: "stack_trace",
20
- capability: "stackTrace",
21
- description: "Read an existing stack representation with bounded traversal"
22
- }),
23
- Object.freeze({
24
- tier: "vm_metadata",
25
- capability: "vmMetadata",
26
- description: "Read build-specific VM metadata only after an exact proof"
27
- })
28
- ]);
29
- export function getObservationTiers(adapter) {
30
- if (!adapter)
31
- return Object.freeze([]);
32
- return Object.freeze(OBSERVATION_TIER_DEFINITIONS
33
- .filter(definition => adapter.capabilities[definition.capability])
34
- .map(definition => definition.tier));
35
- }
36
- export function createObservationPolicy(adapter) {
37
- return Object.freeze({
38
- semanticReadOnly: true,
39
- tiers: getObservationTiers(adapter)
40
- });
41
- }
package/dist/processes.js DELETED
@@ -1,44 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { promisify } from "node:util";
3
- import { basename, resolve } from "node:path";
4
- const execFileAsync = promisify(execFile);
5
- export function parseWowProcessRows(stdout, installs) {
6
- if (!stdout.trim() || stdout.trim() === "null")
7
- return [];
8
- const decoded = JSON.parse(stdout);
9
- const rows = (Array.isArray(decoded) ? decoded : [decoded]).filter((row) => typeof row === "object" && row !== null && !Array.isArray(row));
10
- return rows.flatMap(row => {
11
- const pid = Number(row.ProcessId);
12
- if (!Number.isSafeInteger(pid) || pid <= 0)
13
- return [];
14
- const observedExecutable = String(row.ExecutablePath ?? "");
15
- let executable = observedExecutable;
16
- let install;
17
- if (observedExecutable) {
18
- if (basename(observedExecutable).toLowerCase() !== "wow.exe")
19
- return [];
20
- install = installs.find(candidate => resolve(candidate.executable).toLowerCase() === resolve(observedExecutable).toLowerCase());
21
- }
22
- else if (row.Name === "Wow.exe" && installs.length === 1) {
23
- install = installs[0];
24
- executable = install.executable;
25
- }
26
- if (!install)
27
- return [];
28
- return [{
29
- pid,
30
- executable,
31
- install,
32
- commandLine: String(row.CommandLine ?? "")
33
- }];
34
- });
35
- }
36
- export async function listWowProcesses(installs) {
37
- const { stdout } = await execFileAsync("powershell.exe", [
38
- "-NoProfile",
39
- "-NonInteractive",
40
- "-Command",
41
- "Get-CimInstance Win32_Process -Filter \"Name = 'Wow.exe'\" | Select-Object Name,ProcessId,ExecutablePath,CommandLine | ConvertTo-Json -Compress"
42
- ], { windowsHide: true });
43
- return parseWowProcessRows(stdout, installs);
44
- }
package/dist/session.js DELETED
@@ -1,42 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import * as frida from "frida";
3
- import { ErrorEventSchema } from "./types.js";
4
- export class ProcessSession {
5
- process;
6
- agentFile;
7
- store;
8
- session;
9
- script;
10
- constructor(process, agentFile, store) {
11
- this.process = process;
12
- this.agentFile = agentFile;
13
- this.store = store;
14
- }
15
- async attach() {
16
- if (this.session)
17
- return;
18
- this.session = await frida.attach(this.process.pid);
19
- const source = await readFile(this.agentFile, "utf8");
20
- this.script = await this.session.createScript(source);
21
- this.script.message.connect(async (message) => {
22
- if (message.type !== "send")
23
- return;
24
- const payload = message.payload;
25
- const parsed = ErrorEventSchema.safeParse({
26
- ...payload,
27
- pid: this.process.pid,
28
- buildKey: this.process.install.build.buildKey,
29
- flavor: this.process.install.flavor
30
- });
31
- if (parsed.success)
32
- await this.store.append(parsed.data);
33
- });
34
- await this.script.load();
35
- }
36
- async detach() {
37
- await this.script?.unload().catch(() => undefined);
38
- await this.session?.detach().catch(() => undefined);
39
- this.script = undefined;
40
- this.session = undefined;
41
- }
42
- }
package/dist/storage.js DELETED
@@ -1,12 +0,0 @@
1
- import { appendFile, mkdir } from "node:fs/promises";
2
- import { dirname } from "node:path";
3
- export class JsonlStore {
4
- file;
5
- constructor(file) {
6
- this.file = file;
7
- }
8
- async append(event) {
9
- await mkdir(dirname(this.file), { recursive: true });
10
- await appendFile(this.file, JSON.stringify(event) + "\n", "utf8");
11
- }
12
- }
@@ -1,102 +0,0 @@
1
- import koffi from "koffi";
2
- const PROCESS_QUERY_INFORMATION = 0x0400;
3
- const PROCESS_VM_READ = 0x0010;
4
- const HANDLE = koffi.pointer("WOWDUMP_HANDLE", koffi.opaque());
5
- const SIZE_T = process.arch === "x64" ? "uint64_t" : "uint32_t";
6
- const kernel32 = koffi.load("kernel32.dll");
7
- const shell32 = koffi.load("shell32.dll");
8
- const OpenProcess = kernel32.func("OpenProcess", HANDLE, ["uint32_t", "bool", "uint32_t"]);
9
- const ReadProcessMemory = kernel32.func("ReadProcessMemory", "bool", [HANDLE, "uint64_t", koffi.pointer("uint8_t"), SIZE_T, koffi.out(koffi.pointer(SIZE_T))]);
10
- const VirtualQueryEx = kernel32.func("VirtualQueryEx", SIZE_T, [HANDLE, "uint64_t", koffi.pointer("uint8_t"), SIZE_T]);
11
- const CloseHandle = kernel32.func("CloseHandle", "bool", [HANDLE]);
12
- const GetLastError = kernel32.func("GetLastError", "uint32_t", []);
13
- const IsUserAnAdmin = shell32.func("IsUserAnAdmin", "bool", []);
14
- function addressOf(handle) {
15
- return `0x${koffi.address(handle).toString(16)}`;
16
- }
17
- function win32Error(operation) {
18
- const win32 = Number(GetLastError());
19
- const error = new Error(`${operation} failed with Win32 error ${win32}`);
20
- error.code = "WIN32_ERROR";
21
- error.win32 = win32;
22
- return error;
23
- }
24
- class WindowsHandle {
25
- raw;
26
- pid;
27
- id;
28
- constructor(raw, pid) {
29
- this.raw = raw;
30
- this.pid = pid;
31
- this.id = addressOf(raw);
32
- }
33
- close() {
34
- if (this.raw !== null) {
35
- CloseHandle(this.raw);
36
- this.raw = null;
37
- }
38
- }
39
- }
40
- export class WindowsNativeReader {
41
- async openProcess(pid, rights) {
42
- const requested = rights.reduce((mask, right) => {
43
- if (right === "PROCESS_QUERY_INFORMATION")
44
- return mask | PROCESS_QUERY_INFORMATION;
45
- if (right === "PROCESS_VM_READ")
46
- return mask | PROCESS_VM_READ;
47
- return mask;
48
- }, 0);
49
- const handle = OpenProcess(requested || PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
50
- if (!handle)
51
- throw win32Error(`OpenProcess(${pid})`);
52
- return new WindowsHandle(handle, pid);
53
- }
54
- async readProcessMemory(handle, address, size) {
55
- const native = handle;
56
- const bytes = Buffer.allocUnsafe(size);
57
- const read = [0n];
58
- const ok = ReadProcessMemory(native.raw, address, bytes, size, read);
59
- const bytesRead = Number(read[0]);
60
- if (!ok && bytesRead === 0)
61
- throw win32Error(`ReadProcessMemory(${native.id}, ${`0x${address.toString(16)}`})`);
62
- return { bytes: bytes.subarray(0, bytesRead), bytesRead };
63
- }
64
- async virtualQueryEx(handle, address) {
65
- const native = handle;
66
- const buffer = Buffer.alloc(48);
67
- const result = Number(VirtualQueryEx(native.raw, address, buffer, buffer.length));
68
- if (result === 0)
69
- throw win32Error(`VirtualQueryEx(${native.id})`);
70
- return {
71
- baseAddress: `0x${buffer.readBigUInt64LE(0).toString(16)}`,
72
- allocationBase: `0x${buffer.readBigUInt64LE(8).toString(16)}`,
73
- allocationProtect: buffer.readUInt32LE(16),
74
- regionSize: buffer.readBigUInt64LE(24).toString(),
75
- state: buffer.readUInt32LE(32),
76
- protect: buffer.readUInt32LE(36),
77
- type: buffer.readUInt32LE(40),
78
- bytesReturned: result
79
- };
80
- }
81
- async isProcessAlive(pid) {
82
- try {
83
- const handle = await this.openProcess(pid, ["PROCESS_QUERY_INFORMATION"]);
84
- handle.close();
85
- return true;
86
- }
87
- catch {
88
- return false;
89
- }
90
- }
91
- elevationStatus() {
92
- return {
93
- elevated: Boolean(IsUserAnAdmin()),
94
- processArchitecture: process.arch,
95
- backend: "koffi/kernel32",
96
- requestedRights: ["PROCESS_QUERY_INFORMATION", "PROCESS_VM_READ"]
97
- };
98
- }
99
- }
100
- export function createWindowsNativeReader() {
101
- return new WindowsNativeReader();
102
- }