wowdump 0.3.9 → 0.3.11

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.
@@ -73,7 +73,7 @@ class WindowsHandle {
73
73
  }
74
74
  }
75
75
  }
76
- export class WindowsNativeReader {
76
+ export class WindowsNativeMemory {
77
77
  async enumerateModules(pid) {
78
78
  const snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
79
79
  if (!snapshot)
@@ -237,6 +237,6 @@ export function enumerateWindowsProcesses() {
237
237
  }
238
238
  return rows;
239
239
  }
240
- export function createWindowsNativeReader() {
241
- return new WindowsNativeReader();
240
+ export function createWindowsNativeMemory() {
241
+ return new WindowsNativeMemory();
242
242
  }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "./memory/main.js";
package/dist/toolchain.js CHANGED
@@ -74,6 +74,21 @@ export async function initializeWowdumpHome(options = {}) {
74
74
  await rename(temporary, destination);
75
75
  (existing ? result.updated : result.created).push(destination);
76
76
  }
77
+ // Retire only explicitly known package references; preserve personal files.
78
+ for (const name of ["windbg.md", "request-schema.md"]) {
79
+ const obsolete = join(skillDirectory, "references", name);
80
+ try {
81
+ await readFile(obsolete);
82
+ const saved = join(backupDirectory, "references", name);
83
+ await mkdir(dirname(saved), { recursive: true });
84
+ await rename(obsolete, saved);
85
+ result.backups.push(saved);
86
+ }
87
+ catch (error) {
88
+ if (error.code !== "ENOENT")
89
+ throw error;
90
+ }
91
+ }
77
92
  return result;
78
93
  }
79
94
  export async function bootstrapToolchain(options = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "wowdump",
3
- "version": "0.3.9",
4
- "description": "WoW native memory analysis CLI with WinDbg CDB evidence, IDA/iced-x86 disassembly, and an elevated reader broker",
3
+ "version": "0.3.11",
4
+ "description": "WoW native memory engine CLI with streaming section dumps, IDA/iced-x86 analysis and build-scoped profiles",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -44,6 +44,6 @@
44
44
  },
45
45
  "allowScripts": {
46
46
  "koffi@2.16.3": true,
47
- "wowdump@0.3.9": true
47
+ "wowdump@0.3.11": true
48
48
  }
49
49
  }
@@ -1,24 +1,24 @@
1
1
  ---
2
2
  name: wowdump
3
- description: 使用 wowdump CLI 定位和读取 WoW 原生内存字段,结合 CDB、IDA Pro MCP 或 iced-x86 证据生成并验证 Reader profile。
3
+ description: 使用原生内存引擎读取 WoW 运行时数据、导出运行时段,结合 IDA Pro MCP 或 iced-x86 定位字段并验证 build profile。
4
4
  ---
5
5
 
6
6
  # wowdump
7
7
 
8
- 先明确要读的对象、字段和单位。例如“角色属性”中的暴击等级与暴击百分比是不同字段;不要用 Aura、资源值等其他结果替代。
8
+ 根据用户目标确定要观察的对象、所需数据及结果形式;仅在歧义会影响执行时询问。
9
9
 
10
- 1. 运行 `wowdump targets`,使用返回的 PID、路径、buildKey 和哈希;多个目标且用户尚未指定时请用户选择。
11
- 2. 有匹配 build/hash 的已验证 profile 时直接 Reader 读取。用户要求全新分析时,创建新 session,不使用旧候选作为本轮发现。
12
- 3. 未知字段按 [定位流程](references/workflow.md) 收集证据;需要静态分析时先读 [IDA 与局部反汇编](references/disassemble.md)
13
- 4. [Profile 格式与生命周期](references/profiles.md) 构建候选,按 [验证方法](references/evidence-workflow.md) 分别验证读取、字段含义和复用能力。
14
- 5. 返回字段、单位、观测时间和证据路径。用户确认持久化后保存 profile;已有保存授权时沿用。
10
+ 1. 运行 `wowdump targets`,取得真实 PIDbuildKey、路径和哈希。多个目标时让用户选择。
11
+ 2. 有匹配且适用的正式 profile,直接 `memory read`;用户要求重新发现时,不拿旧候选冒充新证据。
12
+ 3. 未知字段按 [定位流程](references/workflow.md) 选择证据。需要当前代码或数据段时先 [原生导出](references/memory.md),随后优先使用可用的 IDA Pro MCP;没有 IDA 时用随包的 iced-x86 做 [局部 64 位反汇编](references/disassemble.md)。不必每次全量导出。
13
+ 4. 根据模块 RVA、指针链和字段类型生成候选,按 [验证流程](references/evidence-workflow.md) 对照实际读取、对象身份和数值变化。一次读对不代表可跨重启复用。
14
+ 5. 返回请求的数据、必要的解释、观测时间和证据。验证完成且用户确认后,将正式 profile 保存到 `~/.wowdump/<buildKey>/profile/`;临时候选留在 runtime session。
15
15
 
16
16
  按需查阅:
17
- - [命令参数](references/commands.md):CLI 用法。
18
- - [字段定义](references/request-schema.md):目标含义不明确时。
19
- - [CDB 使用与退出限制](references/windbg.md):执行 dump 或 debug 前。
20
- - [字段挖掘方法与角色属性案例](references/character-stats-case.md):未知字段、根指针不稳定或静态分析卡住时参考;学习证据链,不照抄地址。
17
+ - [命令](references/commands.md):准确参数。
18
+ - [内存引擎](references/memory.md):导出、页权限、短读与监控。
19
+ - [Profile](references/profiles.md):字段格式和生命周期。
20
+ - [任务范围](references/task-scope.md):对象、数据含义或输出要求不明确时。
21
21
 
22
- 目录统一为 `~/.wowdump/<buildKey>/`:`database/` 保存静态数据库,`runtime/<session>/` 保存本轮证据与候选,`profile/` 保存已确认的持久化接口。Reader 读取 profile。broker 首次 UAC 后复用,空闲 20 分钟退出。
22
+ `database/` 是静态分析缓存,`runtime/<session>/` 是本轮证据,`profile/` 是日常读取接口。profile 是字段地址与解析规则的声明。broker 首次 UAC 后复用,空闲 20 分钟退出。
23
23
 
24
- 脚本和任务证据放在 runtime session,具体地址由本轮分析确定。保留禁用 Frida Hook Stalker 的约束。
24
+ 不使用实时调试器、断点、Hook、注入或 Stalker。IDA 仅用于离线静态分析。内存读取不暂停目标,跨多次读取的结果不是原子快照。
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 这次真正奏效的思路
6
6
 
7
- **从取值代码确认字段,从对象身份确认根,最后用 Reader 验证整条路径。** 找到一个数值或一条当前能读的指针链,只完成了其中一部分。
7
+ **从取值代码确认字段,从对象身份确认根,最后用 内存引擎 验证整条路径。** 找到一个数值或一条当前能读的指针链,只完成了其中一部分。
8
8
 
9
9
  ```text
10
10
  目标字段、类型和单位
@@ -24,10 +24,10 @@
24
24
  2. **静态分析提供结构,不代替实时值。** 使用原始 PE 和同 build/hash 的运行时段;优先 IDA MCP,缺少时用 iced-x86 做局部分析。按原 RVA 映射,结合 `.rdata` 名称、类型和 `.pdata` 边界,而不是只看少量 `.text` 字节。函数边界是线索,重叠代码仍需按控制流确认。
25
25
  3. **工具的空结果也要验证。** 用一条已知 RIP 相对指令检查解码器输出,再信任批量检索。先证明脚本能找到已知样本,别把接口用错造成的零结果解释成程序没有结构。
26
26
  4. **按真实路径解码。** 遇到重叠字节、异常指令密集区,回到有证据的入口和跳转目标。只有证明条件互补且中间不改变相关标志,才把两条同目标条件跳转折叠。中间值不是返回值;指针计算失败时保留失败假设,不凭低位相似宣布成功。
27
- 5. **把值与对象身份绑在一起。** 页可读、数值合理、几个字段相等,支持对象候选;还要找 GUID 或其他稳定身份来源,区分自己、目标、附近同类型对象和旧缓存。CDB 与 Reader 一致证明读取实现一致,角色面板或独立状态变化才补充字段语义证据。
27
+ 5. **把值与对象身份绑在一起。** 页可读、数值合理、几个字段相等,支持对象候选;还要找 GUID 或其他稳定身份来源,区分自己、目标、附近同类型对象和旧缓存。独立读取结果一致支持读取实现一致,角色面板或独立状态变化才补充字段语义证据。
28
28
  6. **查根的生命周期,而不是挑最短链。** 查看初始化、查找、插入、删除、扩容和清理路径。遇到容器时,确认容量、步长、键、值及空/删除槽含义;固定结构偏移可以存,哈希槽位、列表序号和本轮对象地址不应伪装成固定结构。
29
29
  7. **优先找稳定身份路径。** getter 的快速缓存路径难还原时,继续看缓存未命中分支:它可能显式取得身份并查对象。本次正是从回退分支找到玩家 GUID 来源,避开了未完整还原的缓存指针算法;这是一种可选策略,不代表所有 getter 都有这样的分支。
30
- 8. **验证完整的读取规则。** 用实际 profile 读取,而不只运行临时脚本;保留原始字节、解析值、命令和时间。查表要有上限、唯一命中、对象身份复核和变化检测。普通 Reader 对落盘文件再读一次,确认正式接口确实可用。
30
+ 8. **验证完整的读取规则。** 用实际 profile 读取,而不只运行临时脚本;保留原始字节、解析值、命令和时间。查表要有上限、唯一命中、对象身份复核和变化检测。普通 内存引擎 对落盘文件再读一次,确认正式接口确实可用。
31
31
 
32
32
  ### 卡住时如何选下一步
33
33
 
@@ -55,7 +55,7 @@
55
55
 
56
56
  ## 先找字段含义,再找对象
57
57
 
58
- API 名称所在的只读数据不是 getter 函数。需要沿注册关系、代码引用和真实控制流,确定取值指令、对象来源与字段类型。getter 的函数 RVA 与存放对象指针的全局槽位 RVA 也不是同一个地址;Reader 读取后者,不执行 getter。
58
+ API 名称所在的只读数据不是 getter 函数。需要沿注册关系、代码引用和真实控制流,确定取值指令、对象来源与字段类型。getter 的函数 RVA 与存放对象指针的全局槽位 RVA 也不是同一个地址;内存引擎 读取后者,不执行 getter。
59
59
 
60
60
  线性反汇编遇到重叠指令或混淆分支时,按有证据的跳转目标重新解码。不要把错误路径的算式当成指针算法。本次一条重建算法生成了无效高位、看似正确的低 32 位;低位只能缩小候选范围,页检查和多字段匹配才支持进一步调查。
61
61
 
@@ -76,9 +76,8 @@ API 名称所在的只读数据不是 getter 函数。需要沿注册关系、
76
76
 
77
77
  用 VirtualQueryEx 结果限定可读区间,每次读取截断到当前 region 末尾;短读保留错误,不用零填补。多个候选要对照一组字段,而不是只匹配一个常见数值。角色切换可能改变数值,也可能更换对象,需记录观测时刻。
78
78
 
79
- 本次 Reader 与一次 CDB 非侵入读取分别得到:智力 2669、耐力 31204、生命值 624080、暴击约 17.391304%、急速倍率约 0.818332613(换算约 22.199700%)、精通原始值约 31.326086。这是历史观测,不是下一次查询的预期常量。
79
+ 历史观测得到:智力 2669、耐力 31204、生命值 624080、暴击约 17.391304%、急速倍率约 0.818332613(换算约 22.199700%)、精通原始值约 31.326086。这是历史观测,不是下一次查询的预期常量。
80
80
 
81
- CDB 使用方式和退出限制见 [windbg.md](windbg.md)。本次 `-pd` 曾出现退出码为零但命令未执行;改用 `-pvr` 并以 `qd` 结束后才取得实际读数。必须核对输出标记、读数、超时和目标状态,单凭 exitCode:0 不足以认定成功。此案例没有 Hook,也没有调用未知原生函数。
82
81
 
83
82
  ## 从对象反查根:一次命中不代表稳定
84
83
 
@@ -118,8 +117,7 @@ CDB 使用方式和退出限制见 [windbg.md](windbg.md)。本次 `-pd` 曾出
118
117
  以下文件位于 `~/.wowdump/retail@12.1.0.69587/runtime/character-stats-11628/`,不是 npm 包内容。其他机器缺少这些文件时,不将案例当作可执行 profile。
119
118
 
120
119
  - `candidate-comparison.json`:多候选、面板字段对照。
121
- - `cdb-current-stats-pvr.json`、`cdb-stats-summary.json`:独立读取结果及限制。
122
- - `character-stats-profile-verification.json`:会话绝对地址 profile 的 Reader 验证。
120
+ - `character-stats-profile-verification.json`:会话绝对地址 profile 的 内存引擎 验证。
123
121
  - `root-indirect-scan.json`:模块根候选与间接引用。
124
122
  - `root-static-xrefs.json`:已作废的错误检索结果;使用 root-xrefs-corrected.json 和 root-functions-corrected.txt。
125
123
  - `candidate.profile.json`、`rva-candidate-verify.json`:最短 RVA 候选及其失败的语义复核。
@@ -127,4 +125,4 @@ CDB 使用方式和退出限制见 [windbg.md](windbg.md)。本次 `-pd` 曾出
127
125
  - `root-59c5ec8.candidate.profile.json`、`local-cli-candidate-verify.json`:修订后的本地 CLI 直接验证 candidate,仍得到六项匹配读数;validation 区分读取与未检查项目,候选状态不变。
128
126
  - `player-guid-source-verify.json`:玩家 GUID 与对象 GUID 的实时对照。
129
127
  - `current-code-check.json`:当前进程关键代码与静态段的字节比对。
130
- - `guid-lookup-live-verify.json`、`formal-profile-read.json`:按键查表的三轮 verify,以及正式 profile 的普通 Reader 读取结果。
128
+ - `guid-lookup-live-verify.json`、`formal-profile-read.json`:按键查表的三轮 verify,以及正式 profile 的普通 内存引擎 读取结果。
@@ -1,6 +1,6 @@
1
1
  # wowdump 命令参考
2
2
 
3
- Windows 下内存和 CDB 请求都连接同一个管理员 broker。首次连接请求 UAC,后续请求复用该进程,空闲 20 分钟退出。
3
+ Windows 下所有原生内存请求连接同一个管理员 broker。首次连接请求 UAC,后续请求复用该进程,空闲 20 分钟退出。
4
4
 
5
5
  ## 发现目标
6
6
 
@@ -11,7 +11,7 @@ wowdump targets --pid <pid>
11
11
 
12
12
  使用输出中的 `pid`、`path`、`buildKey` 和哈希。多个目标时先让用户选择。
13
13
 
14
- ## CDB 段导出
14
+ ## 原生段导出
15
15
 
16
16
  ```powershell
17
17
  wowdump analyze runtime `
@@ -22,30 +22,15 @@ wowdump analyze runtime `
22
22
  --confirm
23
23
  ```
24
24
 
25
- 输出写入 `~/.wowdump/<buildKey>/runtime/<session>/dump/`,包括 `manifest.json`、`.text.bin` 等二进制和 `cdb.log`。stdout 只返回路径、段摘要、字节数和 SHA-256;不会嵌入大段字节。
25
+ 输出写入 `~/.wowdump/<buildKey>/runtime/<session>/dump/`,包括 `manifest.json`、`.text.bin` 等二进制和 `progress.json`。stdout 只返回路径、段摘要、字节数和 SHA-256;不会嵌入大段字节。
26
26
 
27
- ## CDB 断点和命令
27
+ ## 局部反汇编
28
28
 
29
- ```powershell
30
- wowdump analyze debug `
31
- --pid <pid> `
32
- --build "<buildKey>" `
33
- --rva <confirmedRva> `
34
- --kind breakpoint `
35
- --max-hits 3 `
36
- --duration-ms 3000 `
37
- --confirm
38
-
39
- wowdump analyze debug `
40
- --pid <pid> `
41
- --build "<buildKey>" `
42
- --kind command `
43
- --cdb-commands '["lm","r","~"]' `
44
- --cdb-args '["-lines"]' `
45
- --confirm
29
+ ```text
30
+ wowdump analyze disassemble --file <section.bin> --rva <startRva> --offset <fileOffset> --size 4096 --max-instructions 256
46
31
  ```
47
32
 
48
- `breakpoint` 只接受一个静态确认的 RVA,输出寄存器、返回地址、线程 ID 和页面证据。`command` 允许 Agent 提供有界 CDB 命令数组;所有参数通过 `spawn` 数组传递,禁止 shell 拼接。
33
+ iced-x86 默认 64 位。`--rva` 是本窗口第一字节的 RVA,不是段首 RVA;`--offset` 是文件内偏移,接受十进制或 0x 整数。输出仅是局部指令与 RIP 相对目标,不是反编译结果或完整 XREF 数据库。
49
34
 
50
35
  ## 数据库和验证
51
36
 
@@ -56,17 +41,27 @@ wowdump analyze runtime --pid <pid> --build "<buildKey>" --kind verify --profile
56
41
 
57
42
  `verify` 接受 candidate 和 reader_ready,执行 profile 的 RVA、指针链、lookup 和类型读取,不读取 IDA 数据库或 dump 文件,也不修改候选状态。validation.read 为 passed 仅表示读取成功;字段语义、根语义和跨进程复用不由该命令认证。补齐相关证据并得到用户确认后,才保存到 `~/.wowdump/<buildKey>/profile/`。
58
43
 
59
- ## Reader
44
+ ## 原生内存引擎
60
45
 
61
46
  ```powershell
62
47
  wowdump memory read `
63
48
  --pid <pid> `
64
49
  --build "<buildKey>" `
65
- --profile "$HOME\.wowdump\<buildKey>\profile\player-state.json" `
66
- --field playerStats
50
+ --profile "$HOME\.wowdump\<buildKey>\profile\object-state.json" `
51
+ --field <fieldName>
67
52
  wowdump memory regions --pid <pid> --start <startAddress> --end <endAddress> --max-regions 20000
68
53
  ```
69
54
 
70
- Reader 使用 `ReadProcessMemory` 和 `VirtualQueryEx` 做有界读取,返回实际地址、读取长度、解析值和证据。
55
+ 内存引擎使用 `ReadProcessMemory` 和 `VirtualQueryEx` 做有界读取,返回实际地址、读取长度、解析值和证据。
56
+
57
+ 导出限制见 [memory.md](memory.md);candidate 验证步骤见 [profiles.md](profiles.md)。尖括号参数需替换后运行。
58
+
59
+ ## 有界监控
60
+
61
+ ```text
62
+ wowdump memory watch start --pid <pid> --address <address> --size 4 --interval 250 --max-samples 100
63
+ wowdump memory watch poll --id <watchId> --after 0
64
+ wowdump memory watch stop --id <watchId>
65
+ ```
71
66
 
72
- 断点退出限制见 [windbg.md](windbg.md);candidate 验证步骤见 [profiles.md](profiles.md)。尖括号参数需替换后运行。
67
+ poll 使用返回的序列号续读,结束后 stop。当前 watch 仅支持固定地址,不接受动态 profile;动态根须重复 `memory read`。`wowdump target` 可查看 broker 状态,首次请求可能启动引擎并请求 UAC。
@@ -6,7 +6,7 @@
6
6
 
7
7
  用 database status 获取数据库路径和状态。身份匹配且 ready 时优先打开已有数据库;不存在或 stale 时以原始 Wow.exe 创建。写库前使用环境提供的数据库锁;工具缺少锁能力时保持单一写入者并记录限制。保存后检查文件与状态,不能把打开 session 当作完成建库,也不手工改成 ready 冒充完成。
8
8
 
9
- 原始 PE 是主输入,保留节区、导入和异常信息。需要运行时证据时先读 [windbg.md](windbg.md),用 dump 导出 .text/.rdata/.pdata,按需加 .data。段 .bin 是 overlay,不作为完整程序打开。检查 manifest 身份、大小、哈希及 shortRead。
9
+ 原始 PE 是主输入,保留节区、导入和异常信息。需要运行时证据时先读 [memory.md](memory.md),用 dump 导出 .text/.rdata/.pdata,按需加 .data。段 .bin 是 overlay,不作为完整程序打开。检查 manifest 身份、大小、哈希及 shortRead。
10
10
 
11
11
  ## 地址映射
12
12
 
@@ -21,38 +21,19 @@ runtime dump 的绝对指针包含 ASLR。改变段加载地址不等于修正
21
21
 
22
22
  用实际支持的健康查询或轻量读取检查 session;支持 TTL 时可延长空闲时间。失联后保存证据,重新打开同一数据库并重试一次。仍失败则转 iced-x86 或报告环境问题。broker 与 IDA worker 生命周期独立。
23
23
 
24
- ## iced-x86 局部解码示例
25
-
26
- 在任务 session 创建 .cjs 文件。传入已安装 wowdump package.json 绝对路径以解析随包依赖;全局安装根可通过 npm root -g 获取。
27
-
28
- 参数依次为 package.json、段 .bin、sectionRva、起始 RVA、字节数、manifest.moduleBase。示例有界解码一个窗口,输出指令 RVA,不提供自动反编译或全局 XREF。
29
-
30
- ~~~javascript
31
- const fs = require("node:fs");
32
- const { createRequire } = require("node:module");
33
- const [pkg, file, section, start, size, base] = process.argv.slice(2);
34
- const { Decoder, DecoderOptions, Formatter, FormatterSyntax } = createRequire(pkg)("iced-x86");
35
- const rva = BigInt(start), sectionRva = BigInt(section), moduleBase = BigInt(base);
36
- const offset = rva - sectionRva, length = Number(size);
37
- const bytes = fs.readFileSync(file);
38
- if (offset < 0n || !Number.isSafeInteger(length) || length < 1 || length > 65536 ||
39
- offset + BigInt(length) > BigInt(bytes.length)) throw new Error("Invalid byte window");
40
- const decoder = new Decoder(64, bytes.subarray(Number(offset), Number(offset) + length), DecoderOptions.None);
41
- const formatter = new Formatter(FormatterSyntax.Intel);
42
- decoder.ip = moduleBase + rva;
43
- try {
44
- while (decoder.canDecode) {
45
- const instruction = decoder.decode();
46
- try {
47
- console.log(JSON.stringify({ rva: "0x" + (instruction.ip - moduleBase).toString(16),
48
- text: formatter.format(instruction), invalid: instruction.isInvalid }));
49
- if (instruction.isInvalid) break;
50
- } finally { instruction.free(); }
51
- }
52
- } finally { decoder.free(); formatter.free(); }
53
- ~~~
54
-
55
- 起点应来自函数边界或已确认控制流,任意字节可解码不代表代码。RIP 相对目标由下一条指令地址加有符号位移得到,地址运算使用 BigInt。窗口不足时按控制流扩展并记录范围;解码成功不证明字段语义。
24
+ ## 没有 IDA:iced-x86
25
+
26
+ npm 包安装,无需额外下载。使用 `wowdump analyze disassemble`:
27
+
28
+ ```text
29
+ wowdump analyze disassemble --file <dump/.text.bin> --rva <startRva> --offset <startRva-minus-sectionRva> --size 4096 --max-instructions 256
30
+ ```
31
+
32
+ 先从 manifest 计算段内偏移;`--rva` 设置窗口首字节的地址。输出 `memoryRva` 是 RIP 相对内存目标;段中的绝对指针仍需减去 manifest.moduleBase 才是 RVA。
33
+
34
+ 起点应来自函数边界、异常目录或已确认控制流。任意字节能解码不代表是代码;跳转、返回、无效指令和窗口边界需要 Agent 判断。出现大片无效指令时先核对映射与缺口,原始磁盘代码可能与运行时不同。
35
+
36
+ iced-x86 不自动生成伪代码、结构体或全局 XREF。Agent 根据局部指令跟踪引用、数据流和对象来源,按证据扩展窗口;最后仍须得到可验证的模块根 RVA 和相对偏移,而不是把指令清单当 profile。
56
37
 
57
38
  ## IDA MCP 来源
58
39
 
@@ -3,11 +3,11 @@
3
3
  每个字段分别记录三层结论:
4
4
 
5
5
  1. **读取有效**:build/hash 匹配,指针链有来源,读取完整,类型和边界正确。保留地址、dataHex、bytesRead、complete、解析值及命令结果。短读或错误不转换为零值。
6
- 2. **语义有效**:与同一时刻角色面板或其他独立观测对照。百分比、等级、基础值和有效值分开;记录允许的舍入误差。请用户配合一次可控变化(例如换装),读取变化前后值,核对变化方向和幅度。一次相等仅是候选支持证据。
6
+ 2. **语义有效**:选择与该数据匹配的独立依据,核对对象身份、含义和观测条件。数值检查范围、单位及误差;标识、文本或枚举检查编码与取值;集合检查成员、过滤条件与边界。适合变化验证时,对照一次可控状态变化前后的结果;稳定数据可用结构关系或独立来源验证,不强求数值变化。一次相等仅是候选支持证据。
7
7
  3. **复用有效**:新进程或重启后的 moduleBase 变化时,同一 profile 仍解析到正确对象。未做这一步就标记“跨进程未验证”,不要报告长期稳定。
8
8
 
9
9
  不要求用户每次都重新配合已有验证;新字段首次建立语义时才补相应证据。输出 evidence 文件包含字段名、来源文件/函数 RVA、假设、原始读数、观测时间、对照值、转换公式及验证状态。多个字段逐个记录,局部成功不代表全部成功。
10
10
 
11
- `analyze runtime --kind verify` 返回分项 validation:read 为 passed 表示读取成功,fieldSemantics、rootSemantics、crossProcess 为 not_checked。它不做角色面板对照,不自动证明语义或重启复用,也不修改候选状态。旧版本的 verified:true 同样只代表读取调用成功。候选验证见 [profiles.md](profiles.md)。
11
+ `analyze runtime --kind verify` 返回分项 validation:read 为 passed 表示读取成功,fieldSemantics、rootSemantics、crossProcess 为 not_checked。它不执行独立语义对照,不自动证明语义或重启复用,也不修改候选状态。候选验证见 [profiles.md](profiles.md)。
12
12
 
13
13
  dump 的日志、manifest 和段字节属于取证材料;检查每段大小、哈希和 shortRead 后再引用。分析输出中的 RVA、结构偏移、类型和调用关系必须能追溯到这些材料或原始 PE。
@@ -0,0 +1,23 @@
1
+ # 原生内存引擎
2
+
3
+ 通过管理员 broker 调用 `VirtualQueryEx` 和 `ReadProcessMemory`,不创建调试会话,不暂停线程或修改内存。已有 profile 使用原来的 schema,无需迁移。
4
+
5
+ ## 导出
6
+
7
+ `wowdump analyze runtime --pid <pid> --build <buildKey> --kind dump --confirm`
8
+
9
+ 默认 `.text .rdata .pdata`;需要状态表时显式增加 `.data`。参数 `--sections`、`--max-section-bytes`、`--max-total-bytes`、`--duration-ms` 控制范围和期限。超过大小限制会报错,不静默截断。`--chunk-bytes` 可调块大小(上限 1 MiB),默认每块 64 KiB,broker 直接写 `.bin`,IPC 不传整段字节。
10
+
11
+ 每次创建新 session,不自动复用历史 dump。`--output-dir` 必须指向尚不存在的目录,防止覆盖证据。输出包括 `manifest.json`、段文件和 `progress.json`;可在执行期间读取 progress 查看已处理字节数。
12
+
13
+ manifest v4 记录 build/hash、模块基址、首选基址、段 RVA、大小、读取大小、SHA-256、缺口、起止时间。不可读/保护页不强读;短读和失败范围写入 `gaps`,对应文件位置用零占位以保留 RVA 映射。`complete:false` 时命令失败但保留 manifest 和文件供诊断,零占位不是目标真实字节。超时和其他中途失败清理本次不完整目录。
14
+
15
+ 分析前检查目标身份、`complete`、`gaps` 和文件哈希。即使 complete 为 true,也不表示原子快照:程序可能在两次读取之间更新数据。`.data` 只代表该 session,不能作为新进程的当前值。
16
+
17
+ ## 验证与观察
18
+
19
+ `analyze runtime --kind verify --profile <candidate.json> --confirm` 由内存引擎解析 RVA、指针链和 lookup,不依赖 IDA 或二进制段文件。读取成功仅证明此时的读取路径可用,语义仍需要独立对照。
20
+
21
+ 原始地址观察可用 `memory watch start/poll/stop`,限制间隔和样本数。固定地址会在对象重建后失效;动态 profile 暂不支持 watch,应重复执行 `memory read`,每次重新解析根和指针链。不要固化临时地址替代正式 profile。
22
+
23
+ 遇到目标退出、身份变化或短读,保留证据并重新发现目标,不无限重试旧地址。读取可能影响性能,不承诺绝对无影响。
@@ -15,7 +15,7 @@
15
15
  ~~~json
16
16
  {
17
17
  "schema": "wowdump.profile.v1",
18
- "id": "example-stat",
18
+ "id": "example-field",
19
19
  "buildKey": "fixture@1",
20
20
  "executableSha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
21
21
  "readerStatus": "candidate",
@@ -32,7 +32,7 @@
32
32
  }
33
33
  ~~~
34
34
 
35
- 替换为本轮证实的身份、RVA、偏移和类型。直接模块标量无需解引用。数组使用 layout.kind:"array"、item、stride、count;动态计数格式先核对当前 Reader 并设置 maxItems。结构使用 layout.kind:"struct" 和 fields。复杂布局先核对当前实现或验证用例,不自创 schema。
35
+ 替换为本轮证实的身份、RVA、偏移和类型。直接模块标量无需解引用。数组使用 layout.kind:"array"、item、stride、count;动态计数格式先核对当前内存引擎 并设置 maxItems。结构使用 layout.kind:"struct" 和 fields。复杂布局先核对当前实现或验证用例,不自创 schema。
36
36
 
37
37
  ## 验证候选
38
38
 
@@ -56,8 +56,8 @@ buildKey 使用 targets 返回的安全单层目录名。profile 必须带真实
56
56
  ### 持久化前检查根指针
57
57
 
58
58
  - 持久化字段从当前 build 的模块 RVA 出发,随后使用有证据支持的相对偏移和解引用;根 RVA 必须落在对应模块范围内。堆地址减 moduleBase 不会变成模块 RVA。
59
- - 指针扫描命中只说明某个槽位当时引用该对象。先确认槽位的含义、所属结构和生命周期,排除队列、缓存、目标单位或任意列表项。重复读取与当前对象对照;指针改变后仍可读不代表仍是玩家。
60
- - 单独记录根语义、字段语义、同会话重读、换角色和跨进程验证。允许明确披露尚未进行的重启测试;根语义尚未确认的扫描命中继续留在 runtime,不凭 reader_ready 或 verified:true 提升为持久化接口。
59
+ - 指针扫描命中只说明某个槽位当时引用该对象。先确认槽位的含义、所属结构和生命周期,排除队列、缓存、目标单位或任意列表项。重复读取与当前对象对照;指针改变后仍可读不代表仍是请求的对象。
60
+ - 单独记录根语义、字段语义、同会话重读、对象切换和跨进程验证。允许明确披露尚未进行的重启测试;根语义尚未确认的扫描命中继续留在 runtime,不凭 reader_ready 或 verified:true 提升为持久化接口。
61
61
  - 绝对地址 profile 只适合本轮调查。证据里的历史地址可留在 runtime 文件,持久化 profile 引用该证据路径,不携带其本轮对象地址作为读取根。
62
62
 
63
63
  具体反例见 [角色属性案例](character-stats-case.md)。这些是保存决策规则,不表示当前 CLI 已自动执行全部检查。
@@ -70,4 +70,8 @@ lookup 参数:countRoot 和 keyRoot 是普通根定义(rva 或会话 address
70
70
 
71
71
  单次表读取上限 1 MiB,keySize 上限 64,stride 上限 4096,maxItems 上限 65536;实际容量超限返回 LOOKUP_LIMIT。键为空、没有命中、重复命中、对象键不符分别返回 LOOKUP_KEY_EMPTY、LOOKUP_NOT_FOUND、LOOKUP_AMBIGUOUS、LOOKUP_OBJECT_MISMATCH。选择后复读根、容量、键和命中条目;发现变化返回 LOOKUP_CHANGED,不沿用旧槽位。
72
72
 
73
- 这些复核不是进程冻结或原子快照保证;目标持续变化时保留失败证据,有限重试。lookup profile 必须使用 0.3.9 或更新版本 Reader;旧版可能忽略未知参数,不要交给旧 Reader 执行。
73
+ 这些复核不是进程冻结或原子快照保证;目标持续变化时保留失败证据,有限重试。lookup profile 必须使用 0.3.9 或更新版本内存引擎;旧版可能忽略未知参数,不要交给旧内存引擎 执行。
74
+
75
+ ## 命名兼容
76
+
77
+ 产品名为 Memory Engine(原生内存引擎)。`readerStatus` / `reader_ready` 是现有 profile v1 的序列化字段和值,继续保留;不要为了改名把现有 JSON 改成未定义的新字段。
@@ -0,0 +1,16 @@
1
+ # 确定任务范围
2
+
3
+ 从用户请求和上下文提取实际需要的约束,不把固定问卷强加给每次任务。目标明确时直接执行;以下内容仅用于消除影响分析的歧义,不是 CLI 请求 schema。
4
+
5
+ | 维度 | 需要明确的内容 |
6
+ | --- | --- |
7
+ | 对象 | 单个实例、指定集合还是全局状态;通过什么身份或条件选中 |
8
+ | 数据 | 标量、标识、文本、位标志、结构、集合或派生结果 |
9
+ | 范围 | 集合的过滤条件、数量上限,以及结果是否覆盖用户要求的全部范围 |
10
+ | 含义 | 原始存储值还是计算结果;适用的类型、编码、单位、基准与转换规则 |
11
+ | 时间 | 当前快照、两次状态对照,还是有界持续观察 |
12
+ | 输出 | 一次查询结果、调查证据、候选 profile,还是可复用的正式 profile |
13
+
14
+ 只记录与本次任务有关的项。没有单位的数据不强加单位,没有可用观测依据的转换不猜公式。集合计数应与其选择条件一起报告,不能把一个可见子集当成全部对象。
15
+
16
+ 用户只要当前结果且有匹配 profile 时直接读取;要求重新发现或解释结构时,建立新的分析证据。对象生命周期、过滤规则或语义尚不明确时,先保留候选,不把相似数据当作请求的结果。
@@ -4,6 +4,13 @@
4
4
 
5
5
  运行 targets 后确认身份,检查 database status。为当前任务保留 runtime session,记录目标字段、已证实事实、候选、已排除假设和下一项证据缺口。继续任务时读取这份记录,避免重复全量扫描。
6
6
 
7
+ ## 选择路径
8
+
9
+ - 已知正式 profile:直接读取所需字段,检查身份与短读;不启动静态分析。
10
+ - 未知字段:明确数据含义、类型和选择条件。用名字、相关函数、结构关系提出假设,再按需要导出相关段并用 IDA 分析。IDA MCP 不可用时使用 iced-x86,记录其没有反编译和全局 XREF 的限制。
11
+ - 需要变化证据:请用户做一次明确、可逆的状态改变,前后重新解析 profile 比较。不要为了观察值而挂调试器。
12
+ - 需要持续观察:原始地址用有界 watch;对象会移动的字段使用重复 profile read。固定地址监控不保证对象身份。
13
+
7
14
  ## 定位字段
8
15
 
9
16
  从与目标相关的字符串、符号、调用点或已有本轮证据开始,按需使用 IDA;具体输入和地址映射见 [disassemble.md](disassemble.md)。
@@ -11,13 +18,25 @@
11
18
  - 名称字符串只说明存在该名称。检查其引用、邻近表项、注册调用和重定位线索,确定哪里存放函数指针。没有直接 XREF 时,检查 RIP 相对引用和表间接访问;不要把字符串 RVA 当作函数入口。
12
19
  - 沿已确认函数分析参数来源、调用者、返回路径和实际内存访问。区分 getter、包装函数和格式化函数;看见名称或一条 load 指令不足以证明字段含义。
13
20
  - 沿对象来源回溯到可解释的根:模块全局指针、索引表或对象容器。记录每次“加偏移”及“解引用”。函数 RVA 与堆对象字段偏移分开记录。
14
- - 区分读取原始字段和计算结果。若返回值由多个量计算,记录各依赖及公式;Reader 没有相应表达能力时明确返回原始量,不把它标成最终面板值。
15
- - 用页面查询和小范围读取检验指针链、类型、容器边界。需要调用证据时先读 [windbg.md](windbg.md) 中的当前限制。
21
+ - 区分读取原始字段和计算结果。若返回值由多个量计算,记录各依赖及公式;内存引擎没有相应表达能力时明确返回原始量,不把它标成已经计算完成的结果。
22
+ - 用页面查询和小范围读取检验指针链、类型、容器边界。通过独立状态变化验证字段,不调用未知函数或附加实时调试器。
16
23
 
17
24
  一次只扩大足以检验假设的范围,但没有找到候选时继续换证据路径:字符串引用失败可检查注册表和调用者;入口存在但对象不明可回溯根;值不匹配可检查类型、派生公式和状态条件。每次记录排除理由。工具失败不等于字段不存在。
18
25
 
19
- ## 转交 Reader
26
+ ## 转交内存引擎
20
27
 
21
28
  根据证据填写 [profiles.md](profiles.md) 的真实字段结构,执行 [evidence-workflow.md](evidence-workflow.md) 的验证。页面可读、数值合理和名称相似都不能单独证明语义正确。
22
29
 
23
30
  真正缺少目标进程、工具入口或可观察状态时,明确缺少的条件并保留进度;不要以“少量扫描没结果”代替结论。
31
+
32
+ ## 恢复与交付
33
+
34
+ 任务中断后先读 session 的 target、候选和验证结果,核对当前 PID/build/hash;身份一致才沿用证据。对象重建、目标切换或进程重启后重新解析对象,旧堆地址不复用。
35
+
36
+ 工具返回错误时区分身份不匹配、页不可读、根查找失败、窗口缺失。根据具体缺口调整一次有依据的查询,不把失败当作字段不存在;发生进程异常则停止当前操作并保留证据。
37
+
38
+ 最终报告列出请求的数据、适用的类型或单位、观察时间、证据路径和实际未验证项。原始数据与推导结果分开说明;不以其他容易获取的数据替代请求。
39
+
40
+ ## 可选案例
41
+
42
+ 需要了解一条完整证据链时,可参考 [角色属性案例](character-stats-case.md)。案例只展示方法,不限定工具的对象范围,也不提供可直接套用的地址或预期值。
@@ -1,93 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { mkdir, open, rm, writeFile } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
4
- function safeName(name) {
5
- const value = String(name ?? "section");
6
- return /^[A-Za-z0-9_.-]+$/.test(value) ? value : "section";
7
- }
8
- /** Streams section bytes into files and writes a compact runtime manifest. */
9
- export class RuntimeDumpWriter {
10
- outputDirectory;
11
- options;
12
- sections = new Map();
13
- aborted = false;
14
- constructor(options) {
15
- this.options = options;
16
- this.outputDirectory = resolve(options.outputDirectory);
17
- }
18
- async initialize() {
19
- await mkdir(this.outputDirectory, { recursive: true });
20
- }
21
- async writeChunk(meta, data) {
22
- if (this.aborted)
23
- throw new Error("runtime dump writer is aborted");
24
- if (!data)
25
- throw new Error("runtime dump chunk has no binary payload");
26
- const name = safeName(meta.section);
27
- const offset = Number(meta.offset);
28
- if (!Number.isSafeInteger(offset) || offset < 0)
29
- throw new Error(`invalid ${name} chunk offset`);
30
- let state = this.sections.get(name);
31
- if (!state) {
32
- const file = join(this.outputDirectory, `${name}.bin`);
33
- state = { name, file, handle: await open(file, "w"), hash: createHash("sha256"), offset: 0, bytes: 0 };
34
- this.sections.set(name, state);
35
- }
36
- if (offset !== state.offset)
37
- throw new Error(`${name} chunk offset ${offset} does not follow ${state.offset}`);
38
- const bytes = Buffer.from(data);
39
- await state.handle.write(bytes, 0, bytes.length, state.offset);
40
- state.hash.update(bytes);
41
- state.offset += bytes.length;
42
- state.bytes += bytes.length;
43
- }
44
- async finalize(value) {
45
- if (this.aborted)
46
- throw new Error("runtime dump writer is aborted");
47
- for (const state of this.sections.values())
48
- await state.handle.close();
49
- const rawSections = Array.isArray(value.sections) ? value.sections : [];
50
- for (const section of rawSections) {
51
- const name = safeName(section.name);
52
- if (!this.sections.has(name))
53
- await writeFile(join(this.outputDirectory, `${name}.bin`), Buffer.alloc(0));
54
- }
55
- const sections = rawSections.map((section) => {
56
- const name = safeName(section.name);
57
- const state = this.sections.get(name);
58
- return {
59
- ...section,
60
- ...(state ? { file: state.file, bytes: state.bytes, sha256: state.hash.digest("hex") } : { file: join(this.outputDirectory, `${name}.bin`), bytes: 0, sha256: null })
61
- };
62
- });
63
- const manifest = {
64
- schema: "wowdump.runtime-dump.v3",
65
- kind: "dump",
66
- buildKey: this.options.buildKey,
67
- pid: this.options.pid,
68
- executableSha256: this.options.executableSha256 ?? null,
69
- preferredImageBase: this.options.preferredImageBase ?? null,
70
- moduleBase: typeof value.module?.moduleBase === "string"
71
- ? value.module.moduleBase
72
- : typeof value.module?.base === "string" ? value.module.base : null,
73
- moduleSize: this.options.moduleSize ?? (typeof value.module?.moduleSize === "number" ? value.module.moduleSize : null),
74
- module: value.module ?? null,
75
- totalBytes: sections.reduce((sum, section) => sum + Number(section.bytes ?? 0), 0),
76
- truncated: value.truncated === true || sections.some(section => section.truncated === true),
77
- limits: value.limits ?? null,
78
- dumpParameters: this.options.dumpParameters ?? null,
79
- sections,
80
- evidence: value.evidence ?? [],
81
- generatedAt: new Date().toISOString()
82
- };
83
- const manifestFile = join(this.outputDirectory, "manifest.json");
84
- await writeFile(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
85
- return { ok: true, kind: "dump", manifestFile, outputDirectory: this.outputDirectory, manifest, sections: sections.map(section => ({ name: section.name, bytes: section.bytes, sha256: section.sha256 })) };
86
- }
87
- async abort() {
88
- this.aborted = true;
89
- for (const state of this.sections.values())
90
- await state.handle.close().catch(() => undefined);
91
- await rm(this.outputDirectory, { recursive: true, force: true }).catch(() => undefined);
92
- }
93
- }