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
@@ -0,0 +1,242 @@
1
+ import koffi from "koffi";
2
+ import { basename } from "node:path";
3
+ const PROCESS_QUERY_INFORMATION = 0x0400;
4
+ const PROCESS_VM_READ = 0x0010;
5
+ const PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
6
+ const HANDLE = koffi.pointer("WOWDUMP_HANDLE", koffi.opaque());
7
+ const BYTE_POINTER = koffi.pointer("WOWDUMP_BYTE", koffi.opaque());
8
+ const SIZE_T = process.arch === "x64" ? "uint64_t" : "uint32_t";
9
+ const kernel32 = koffi.load("kernel32.dll");
10
+ const shell32 = koffi.load("shell32.dll");
11
+ const OpenProcess = kernel32.func("OpenProcess", HANDLE, ["uint32_t", "bool", "uint32_t"]);
12
+ const ReadProcessMemory = kernel32.func("ReadProcessMemory", "bool", [HANDLE, "uint64_t", koffi.pointer("uint8_t"), SIZE_T, koffi.out(koffi.pointer(SIZE_T))]);
13
+ const VirtualQueryEx = kernel32.func("VirtualQueryEx", SIZE_T, [HANDLE, "uint64_t", koffi.pointer("uint8_t"), SIZE_T]);
14
+ const CloseHandle = kernel32.func("CloseHandle", "bool", [HANDLE]);
15
+ const GetLastError = kernel32.func("GetLastError", "uint32_t", []);
16
+ const QueryFullProcessImageNameW = kernel32.func("QueryFullProcessImageNameW", "bool", [HANDLE, "uint32_t", koffi.pointer("char16_t"), koffi.inout(koffi.pointer("uint32_t"))]);
17
+ const IsUserAnAdmin = shell32.func("IsUserAnAdmin", "bool", []);
18
+ const MODULEENTRY32W = koffi.struct("WOWDUMP_MODULEENTRY32W", {
19
+ dwSize: "uint32_t",
20
+ th32ModuleID: "uint32_t",
21
+ th32ProcessID: "uint32_t",
22
+ GlblcntUsage: "uint32_t",
23
+ ProccntUsage: "uint32_t",
24
+ modBaseAddr: BYTE_POINTER,
25
+ modBaseSize: "uint32_t",
26
+ hModule: HANDLE,
27
+ szModule: koffi.array("char16_t", 256, "String"),
28
+ szExePath: koffi.array("char16_t", 260, "String")
29
+ });
30
+ const PROCESSENTRY32W = koffi.struct("WOWDUMP_PROCESSENTRY32W", {
31
+ dwSize: "uint32_t",
32
+ cntUsage: "uint32_t",
33
+ th32ProcessID: "uint32_t",
34
+ th32DefaultHeapID: "uint64_t",
35
+ th32ModuleID: "uint32_t",
36
+ cntThreads: "uint32_t",
37
+ th32ParentProcessID: "uint32_t",
38
+ pcPriClassBase: "int32_t",
39
+ dwFlags: "uint32_t",
40
+ szExeFile: koffi.array("char16_t", 260, "String")
41
+ });
42
+ const CreateToolhelp32Snapshot = kernel32.func("CreateToolhelp32Snapshot", HANDLE, ["uint32_t", "uint32_t"]);
43
+ const Module32FirstW = kernel32.func("Module32FirstW", "bool", [HANDLE, koffi.inout(koffi.pointer(MODULEENTRY32W))]);
44
+ const Module32NextW = kernel32.func("Module32NextW", "bool", [HANDLE, koffi.inout(koffi.pointer(MODULEENTRY32W))]);
45
+ const Process32FirstW = kernel32.func("Process32FirstW", "bool", [HANDLE, koffi.inout(koffi.pointer(PROCESSENTRY32W))]);
46
+ const Process32NextW = kernel32.func("Process32NextW", "bool", [HANDLE, koffi.inout(koffi.pointer(PROCESSENTRY32W))]);
47
+ const TH32CS_SNAPMODULE = 0x00000008;
48
+ const TH32CS_SNAPMODULE32 = 0x00000010;
49
+ const TH32CS_SNAPPROCESS = 0x00000002;
50
+ function addressOf(handle) {
51
+ return `0x${koffi.address(handle).toString(16)}`;
52
+ }
53
+ function win32Error(operation) {
54
+ const win32 = Number(GetLastError());
55
+ const error = new Error(`${operation} failed with Win32 error ${win32}`);
56
+ error.code = "WIN32_ERROR";
57
+ error.win32 = win32;
58
+ return error;
59
+ }
60
+ class WindowsHandle {
61
+ raw;
62
+ pid;
63
+ id;
64
+ constructor(raw, pid) {
65
+ this.raw = raw;
66
+ this.pid = pid;
67
+ this.id = addressOf(raw);
68
+ }
69
+ close() {
70
+ if (this.raw !== null) {
71
+ CloseHandle(this.raw);
72
+ this.raw = null;
73
+ }
74
+ }
75
+ }
76
+ export class WindowsNativeReader {
77
+ async enumerateModules(pid) {
78
+ const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
79
+ if (!snapshot)
80
+ throw win32Error(`CreateToolhelp32Snapshot(${pid})`);
81
+ const modules = [];
82
+ try {
83
+ const entry = {
84
+ dwSize: koffi.sizeof(MODULEENTRY32W),
85
+ th32ModuleID: 0,
86
+ th32ProcessID: 0,
87
+ GlblcntUsage: 0,
88
+ ProccntUsage: 0,
89
+ modBaseAddr: null,
90
+ modBaseSize: 0,
91
+ hModule: null,
92
+ szModule: "",
93
+ szExePath: ""
94
+ };
95
+ let first = Module32FirstW(snapshot, entry);
96
+ while (first) {
97
+ const base = entry.modBaseAddr ? `0x${koffi.address(entry.modBaseAddr).toString(16)}` : "0x0";
98
+ modules.push({ name: String(entry.szModule), base, size: Number(entry.modBaseSize), path: String(entry.szExePath) });
99
+ first = Module32NextW(snapshot, entry);
100
+ }
101
+ if (modules.some(module => module.name.length > 0 || Boolean(module.path)))
102
+ return modules;
103
+ const path = await this.queryProcessPath(pid);
104
+ if (path)
105
+ return [{ name: basename(path), base: "0x0", size: 0, path }];
106
+ throw win32Error(`QueryFullProcessImageNameW(${pid})`);
107
+ }
108
+ finally {
109
+ CloseHandle(snapshot);
110
+ }
111
+ }
112
+ async queryProcessPath(pid) {
113
+ const handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION, false, pid);
114
+ if (!handle)
115
+ return undefined;
116
+ try {
117
+ const capacity = 32768;
118
+ const buffer = Buffer.alloc(capacity * 2);
119
+ const length = [capacity];
120
+ if (!QueryFullProcessImageNameW(handle, 0, buffer, length))
121
+ return undefined;
122
+ const count = Math.max(0, Math.min(Number(length[0]), capacity));
123
+ return buffer.toString("utf16le", 0, count * 2) || undefined;
124
+ }
125
+ finally {
126
+ CloseHandle(handle);
127
+ }
128
+ }
129
+ async openProcess(pid, rights) {
130
+ const requested = rights.reduce((mask, right) => {
131
+ if (right === "PROCESS_QUERY_INFORMATION")
132
+ return mask | PROCESS_QUERY_INFORMATION;
133
+ if (right === "PROCESS_VM_READ")
134
+ return mask | PROCESS_VM_READ;
135
+ return mask;
136
+ }, 0);
137
+ const handle = OpenProcess(requested || PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
138
+ if (!handle)
139
+ throw win32Error(`OpenProcess(${pid})`);
140
+ return new WindowsHandle(handle, pid);
141
+ }
142
+ async readProcessMemory(handle, address, size) {
143
+ const native = handle;
144
+ const bytes = Buffer.allocUnsafe(size);
145
+ const read = [0n];
146
+ const ok = ReadProcessMemory(native.raw, address, bytes, size, read);
147
+ const bytesRead = Number(read[0]);
148
+ if (!ok && bytesRead === 0)
149
+ throw win32Error(`ReadProcessMemory(${native.id}, ${`0x${address.toString(16)}`})`);
150
+ return { bytes: bytes.subarray(0, bytesRead), bytesRead };
151
+ }
152
+ async virtualQueryEx(handle, address) {
153
+ const native = handle;
154
+ const buffer = Buffer.alloc(48);
155
+ const result = Number(VirtualQueryEx(native.raw, address, buffer, buffer.length));
156
+ if (result === 0)
157
+ throw win32Error(`VirtualQueryEx(${native.id})`);
158
+ return {
159
+ baseAddress: `0x${buffer.readBigUInt64LE(0).toString(16)}`,
160
+ allocationBase: `0x${buffer.readBigUInt64LE(8).toString(16)}`,
161
+ allocationProtect: buffer.readUInt32LE(16),
162
+ regionSize: buffer.readBigUInt64LE(24).toString(),
163
+ state: buffer.readUInt32LE(32),
164
+ protect: buffer.readUInt32LE(36),
165
+ type: buffer.readUInt32LE(40),
166
+ bytesReturned: result
167
+ };
168
+ }
169
+ async isProcessAlive(pid) {
170
+ try {
171
+ const handle = await this.openProcess(pid, ["PROCESS_QUERY_INFORMATION"]);
172
+ handle.close();
173
+ return true;
174
+ }
175
+ catch {
176
+ return false;
177
+ }
178
+ }
179
+ elevationStatus() {
180
+ return {
181
+ elevated: Boolean(IsUserAnAdmin()),
182
+ processArchitecture: process.arch,
183
+ backend: "koffi/kernel32",
184
+ requestedRights: ["PROCESS_QUERY_INFORMATION", "PROCESS_VM_READ"]
185
+ };
186
+ }
187
+ }
188
+ /** Enumerate processes without spawning a shell or depending on Frida. */
189
+ export function enumerateWindowsProcesses() {
190
+ if (process.platform !== "win32")
191
+ return [];
192
+ const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
193
+ if (!snapshot)
194
+ throw win32Error("CreateToolhelp32Snapshot(processes)");
195
+ const rows = [];
196
+ try {
197
+ const entry = {
198
+ dwSize: koffi.sizeof(PROCESSENTRY32W),
199
+ cntUsage: 0,
200
+ th32ProcessID: 0,
201
+ th32DefaultHeapID: 0n,
202
+ th32ModuleID: 0,
203
+ cntThreads: 0,
204
+ th32ParentProcessID: 0,
205
+ pcPriClassBase: 0,
206
+ dwFlags: 0,
207
+ szExeFile: ""
208
+ };
209
+ let found = Process32FirstW(snapshot, entry);
210
+ while (found) {
211
+ const pid = Number(entry.th32ProcessID);
212
+ const name = String(entry.szExeFile);
213
+ let path = null;
214
+ if (pid > 0) {
215
+ const handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_QUERY_INFORMATION, false, pid);
216
+ if (handle) {
217
+ try {
218
+ const capacity = 32768;
219
+ const buffer = Buffer.alloc(capacity * 2);
220
+ const length = [capacity];
221
+ if (QueryFullProcessImageNameW(handle, 0, buffer, length)) {
222
+ const count = Math.max(0, Math.min(Number(length[0]), capacity));
223
+ path = buffer.toString("utf16le", 0, count * 2) || null;
224
+ }
225
+ }
226
+ finally {
227
+ CloseHandle(handle);
228
+ }
229
+ }
230
+ }
231
+ rows.push({ pid, name, path });
232
+ found = Process32NextW(snapshot, entry);
233
+ }
234
+ }
235
+ finally {
236
+ CloseHandle(snapshot);
237
+ }
238
+ return rows;
239
+ }
240
+ export function createWindowsNativeReader() {
241
+ return new WindowsNativeReader();
242
+ }
@@ -1,67 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { createServer } from "node:net";
3
- import { mkdir, rm, writeFile } from "node:fs/promises";
4
- import { dirname } from "node:path";
5
- import { serveReaderBroker } from "./reader-broker.js";
6
- import { createWindowsNativeReader } from "./windows-reader.js";
7
- function argument(name) {
8
- const index = process.argv.indexOf(name);
9
- return index >= 0 ? process.argv[index + 1] : undefined;
10
- }
11
- const listen = argument("--listen");
12
- const port = Number(argument("--port") ?? "0");
13
- const token = argument("--token");
14
- const metadata = argument("--metadata");
15
- let server;
16
- const broker = serveReaderBroker({
17
- attachStdio: !listen,
18
- backend: process.env.WOWDUMP_USE_FAKE_READER === "1" ? undefined : createWindowsNativeReader(),
19
- onShutdown: async () => {
20
- server?.close();
21
- if (metadata)
22
- await rm(metadata, { force: true }).catch(() => undefined);
23
- }
24
- });
25
- if (listen) {
26
- if (listen !== "127.0.0.1" || !token || !metadata || !Number.isSafeInteger(port) || port < 0 || port > 65535) {
27
- throw new Error("--listen requires 127.0.0.1, a valid --port, --token and --metadata");
28
- }
29
- server = createServer(socket => {
30
- socket.setEncoding("utf8");
31
- let buffer = "";
32
- let pending = Promise.resolve();
33
- socket.on("data", chunk => {
34
- buffer += chunk;
35
- while (buffer.includes("\n")) {
36
- const newline = buffer.indexOf("\n");
37
- const line = buffer.slice(0, newline);
38
- buffer = buffer.slice(newline + 1);
39
- pending = pending.then(async () => {
40
- try {
41
- const envelope = JSON.parse(line);
42
- if (envelope.token !== token || !envelope.request) {
43
- socket.write(`${JSON.stringify({ ok: false, command: "auth", timestamp: Date.now(), error: { code: "AUTH_FAILED", message: "invalid reader broker token" } })}\n`);
44
- return;
45
- }
46
- socket.write(`${JSON.stringify(await broker.handle(envelope.request))}\n`);
47
- }
48
- catch (error) {
49
- socket.write(`${JSON.stringify({ ok: false, command: "parse", timestamp: Date.now(), error: { code: "INVALID_JSON", message: error instanceof Error ? error.message : String(error) } })}\n`);
50
- }
51
- });
52
- }
53
- });
54
- });
55
- server.listen(port, listen, async () => {
56
- await mkdir(dirname(metadata), { recursive: true });
57
- const address = server?.address();
58
- if (!address || typeof address === "string")
59
- throw new Error("reader broker did not acquire a TCP endpoint");
60
- await writeFile(metadata, `${JSON.stringify({ schema: "wowdump.reader-broker.v1", host: listen, port: address.port, token, pid: process.pid })}\n`, { encoding: "utf8", mode: 0o600 });
61
- });
62
- }
63
- const shutdown = () => {
64
- void broker.shutdown("request").catch(() => process.exitCode = 1);
65
- };
66
- process.once("SIGINT", shutdown);
67
- process.once("SIGTERM", shutdown);
2
+ import "./reader/main.js";