wowdump 0.2.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.
- package/LICENSE +1 -1
- package/README.md +17 -111
- package/dist/adapters/reader.js +33 -0
- package/dist/analysis/disassemble.js +77 -0
- package/dist/{frida-runtime.js → analysis/frida-runtime.js} +3 -25
- package/dist/analysis/runtime-script.js +36 -0
- package/dist/cli.js +563 -0
- package/dist/core/profile-engine.js +238 -0
- package/dist/frida-worker.js +99 -0
- package/dist/reader/broker.js +475 -0
- package/dist/reader/client.js +1 -0
- package/dist/reader/launcher.js +219 -0
- package/dist/reader/main.js +100 -0
- package/dist/reader/protocol.js +1 -0
- package/dist/reader/windows.js +242 -0
- package/dist/reader-main.js +2 -0
- package/dist/toolchain.js +123 -0
- package/package.json +19 -37
- package/skills/wowdump/SKILL.md +22 -0
- package/skills/wowdump/references/commands.md +63 -0
- package/skills/wowdump/references/disassemble.md +18 -0
- package/skills/wowdump/references/dynamic.md +54 -0
- package/skills/wowdump/references/evidence-workflow.md +41 -0
- package/skills/wowdump/references/profiles.md +34 -0
- package/skills/wowdump/references/request-schema.md +28 -0
- package/skills/wowdump/references/workflow.md +44 -0
- package/skills/wowdump/scripts/dynamic-session.js +133 -0
- package/dist/agent.js +0 -1335
- package/dist/analysis-path.js +0 -38
- package/dist/analysis-process-log.js +0 -146
- package/dist/broker-client.js +0 -411
- package/dist/broker-codec.js +0 -148
- package/dist/broker-core.js +0 -1045
- package/dist/broker-gateway.js +0 -447
- package/dist/broker-ledger.js +0 -196
- package/dist/broker-main.js +0 -291
- package/dist/broker-protocol.js +0 -119
- package/dist/broker-runtime.js +0 -1283
- package/dist/broker-server.js +0 -466
- package/dist/build-bundle-loader.js +0 -183
- package/dist/build-bundle.js +0 -11
- package/dist/discovery.js +0 -59
- package/dist/dry-run.js +0 -38
- package/dist/error-log.js +0 -71
- package/dist/focus-errors.js +0 -63
- package/dist/focus-service.js +0 -1855
- package/dist/focused-session.js +0 -1357
- package/dist/mcp-main.js +0 -51
- package/dist/mcp.js +0 -924
- package/dist/observability.js +0 -41
- package/dist/process-log-lock.js +0 -195
- package/dist/processes.js +0 -47
- package/dist/runtime-config.js +0 -399
- package/dist/session.js +0 -145
- package/dist/storage.js +0 -12
- package/dist/wow-analysis.js +0 -1430
- package/resources/builds/retail/12.0.7.68974/build-profile.json +0 -290
- package/resources/builds/retail/12.0.7.68974/data-sources.json +0 -1633
- package/resources/builds/retail/12.0.7.68974/lua-targets.jsonl +0 -5130
- package/resources/builds/retail/12.0.7.68974/manifest.json +0 -63
- package/resources/builds/retail/12.0.7.68974/signatures.json +0 -260
- /package/dist/{adapters.js → core/build-adapters.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Dynamic GumJS 编写指南
|
|
2
|
+
|
|
3
|
+
## 目标与优先级
|
|
4
|
+
|
|
5
|
+
dynamic 是默认主路径,负责从当前运行进程取得真实值和线索:模块基址、对象地址、函数地址、字节样本、字符串、返回值或事件证据。脚本可以使用静态阶段给出的候选 RVA,也可以自己做有界签名扫描;不要把未经验证的绝对地址当成长期 profile。
|
|
6
|
+
|
|
7
|
+
## 脚本入口
|
|
8
|
+
|
|
9
|
+
通过 `rpc.exports` 暴露一个小而明确的函数,例如 `collect`。CLI 的 `--args` 会放在 `globalThis.__WOWDUMP_INPUT__`,字段列表和读取上限从这里取得。
|
|
10
|
+
|
|
11
|
+
API 参考: [Frida JavaScript API](https://frida.re/docs/javascript-api/)。
|
|
12
|
+
|
|
13
|
+
一次普通 run 由 CLI 完成 attach 并加载脚本,脚本在同一次执行中完成 Hook、采集、限时和 cleanup。需要持续观察时,由脚本内部维护事件队列、采样计时器和清理逻辑,在一次 run 结束时返回结果;不增加单独的 `analyze focus` 命令,也暂不实现跨命令的 status、pause 或 checkpoint。
|
|
14
|
+
|
|
15
|
+
轻量模式保持不变:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
wowdump analyze dynamic --script <GumJS> --confirm
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`--script` 必须由 Agent 显式提供。仓库中的 `scripts/dynamic-session.js` 只是可复制和修改的示例,不是 CLI 默认脚本,也不会被自动执行。
|
|
22
|
+
|
|
23
|
+
## 允许的操作
|
|
24
|
+
|
|
25
|
+
- 枚举模块和导出
|
|
26
|
+
- 在指定模块或范围内扫描模式
|
|
27
|
+
- 读取有界内存并返回十六进制样本
|
|
28
|
+
- 短时 Hook、回调或事件监听
|
|
29
|
+
- 返回候选地址、RVA、对象关系和置信度
|
|
30
|
+
|
|
31
|
+
每次脚本都要设置读取范围、最大 Hook 数、最大事件数和结束时间。默认禁止 `Stalker`;除非用户明确要求并给出极小范围、时长和事件上限,否则不要启用它。Hook、事件和内存读取都必须有上限。
|
|
32
|
+
|
|
33
|
+
## 输出要求
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"buildKey": "retail@12.1.0.69587",
|
|
38
|
+
"pid": 33976,
|
|
39
|
+
"module": { "name": "Wow.exe", "moduleBase": "0x7ff7b5040000" },
|
|
40
|
+
"fields": {
|
|
41
|
+
"playerAuras": [{ "address": "0x...", "bytesHex": "...", "source": "frida" }]
|
|
42
|
+
},
|
|
43
|
+
"evidence": [{ "source": "frida", "kind": "runtime-sample" }]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
输出应足够让 IDA Pro MCP 或 iced-x86 在样本附近定位函数、字符串和 XREF,也应保留 Hook 的 `this`、参数、返回值和访问到的对象地址。拿不到字段时返回空数组和原因,不填充猜测地址。
|
|
48
|
+
|
|
49
|
+
## 与静态分析交替
|
|
50
|
+
|
|
51
|
+
- 已有候选 RVA:优先直接 Hook,观察真实调用和返回值。
|
|
52
|
+
- 没有候选 RVA:先做小范围模块/字节扫描;仍不明确时导出最小 text 样本交给 IDA 或 iced。
|
|
53
|
+
- 静态给出候选后回到 dynamic 验证,不要求一次静态分析解决全部字段。
|
|
54
|
+
- 运行时值、用户触发动作和短时事件优先由 Frida 完成;长期稳定的 RVA、指针链和类型才需要静态证据。
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 证据工作流
|
|
2
|
+
|
|
3
|
+
## 选择路径
|
|
4
|
+
|
|
5
|
+
先判断用户要的是“一次当前值”还是“可复用 Reader 字段”。一次当前值优先走 Dynamic;可复用字段才在 Dynamic 结果基础上补 IDA/iced 和 Reader 验证。两者可以来回切换。
|
|
6
|
+
|
|
7
|
+
## Dynamic(GumJS)
|
|
8
|
+
|
|
9
|
+
脚本通过 `rpc.exports` 暴露入口;CLI 会把 `--args` 放到 `globalThis.__WOWDUMP_INPUT__`。脚本可枚举模块、读取有界内存、扫描模式、安装短时 Hook 或监听事件。输出至少包含:
|
|
10
|
+
|
|
11
|
+
- `buildKey`、进程 PID、模块名、`moduleBase`
|
|
12
|
+
- 候选地址或 `rva`、字节样本、来源和采样时间
|
|
13
|
+
- 每个请求字段对应的线索
|
|
14
|
+
|
|
15
|
+
不要导出整段进程内存。把 `durationMs`、Hook 数量、事件数量和读取范围限制在请求所需范围内。
|
|
16
|
+
|
|
17
|
+
## Disassemble(按需使用)
|
|
18
|
+
|
|
19
|
+
`--runtime-export` 指向 dynamic JSON。只有 dynamic 线索不足以确认函数、布局或类型时才调用 IDA Pro MCP;没有时再用 `analyze disassemble` 的 iced-x86 解码。分析后回到 dynamic 验证。输出 profile 时,每个字段至少给出:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"name": "playerAuras",
|
|
24
|
+
"module": "Wow.exe",
|
|
25
|
+
"rva": "0x123456",
|
|
26
|
+
"pointerChain": [{ "offset": "0x20", "type": "pointer" }],
|
|
27
|
+
"type": "array",
|
|
28
|
+
"elementSize": 64,
|
|
29
|
+
"evidence": [{ "source": "ida-pro-mcp", "functionRva": "0x123000" }]
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
没有可靠定位时输出 `confidence: "candidate"`,不要填充猜测地址。
|
|
34
|
+
|
|
35
|
+
## Reader
|
|
36
|
+
|
|
37
|
+
Reader 根据模块名、RVA、指针链和类型执行原子读取;它不负责猜测结构,也不执行 Frida/IDA。一次读取结果应保留实际地址、请求大小、已读字节、解析值和错误状态。
|
|
38
|
+
|
|
39
|
+
需要连续数据时使用 `memory watch` 或重复 `memory read`。运行时模块基址可能变化,profile 保存 RVA,不保存本次运行的绝对地址。
|
|
40
|
+
|
|
41
|
+
## Verify
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Profile 生命周期
|
|
2
|
+
|
|
3
|
+
## 目录约定
|
|
4
|
+
|
|
5
|
+
每次 `targets` 确认 buildKey 后,创建对应目录:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
~/.wowdump/<buildKey>/
|
|
9
|
+
└── profile/ # 只放用户确认保存的 profile
|
|
10
|
+
├── player-state.json
|
|
11
|
+
└── combat-state.json
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
buildKey 只能作为单层目录名使用。保留 `@`、点和连字符;拒绝路径分隔符、`..` 和其他会改变目录层级的字符。不同 build 必须使用不同目录。
|
|
15
|
+
|
|
16
|
+
## 临时结果
|
|
17
|
+
|
|
18
|
+
动态导出、反汇编候选和未确认的 profile 放到本次任务的临时目录,例如:
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
~/.wowdump/runtime/<session-id>/
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
它们不是持久化 profile,任务结束后可以清理。不要把候选文件直接写入 `<buildKey>/profile/`。
|
|
25
|
+
|
|
26
|
+
## 保存流程
|
|
27
|
+
|
|
28
|
+
1. 汇总 dynamic 与必要的 IDA/iced 证据,检查 buildKey、模块、哈希、RVA 和字段证据一致;不要求固定先后顺序。
|
|
29
|
+
2. 向用户展示候选 profile 的路径、字段和验证结果,询问是否保存为该 build 的持久化 profile。
|
|
30
|
+
3. 用户确认后,将候选复制到 `~/.wowdump/<buildKey>/profile/<profile-id>.json`,保留 `buildKey`、来源和验证证据。
|
|
31
|
+
4. 目标文件已存在时先询问覆盖,或使用新的 profile-id;不要静默覆盖。
|
|
32
|
+
5. 后续读取优先使用这个绝对路径,或用 `wowdump profiles --directory ~/.wowdump/<buildKey>/profile` 列出它。
|
|
33
|
+
|
|
34
|
+
profile 只对记录的 buildKey 和模块布局有效。发现 buildKey 不匹配时停止读取并重新走分析流程,不修改旧 profile。
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 字段请求格式
|
|
2
|
+
|
|
3
|
+
先把用户要查的内容写成一个请求文件。字段名是本次任务的目标,不是地址;地址由 dynamic 和 disassemble 阶段产生。
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"schema": "wowdump.read-request.v1",
|
|
8
|
+
"target": { "pid": 33976, "buildKey": "retail@12.1.0.69587", "module": "Wow.exe" },
|
|
9
|
+
"fields": [
|
|
10
|
+
{ "name": "playerAuras", "kind": "aura", "scope": "player", "direction": "both" },
|
|
11
|
+
{ "name": "nearbyEnemies", "kind": "unit-list", "scope": "around-player" },
|
|
12
|
+
{ "name": "cooldowns", "kind": "cooldown", "scope": "player" }
|
|
13
|
+
],
|
|
14
|
+
"limits": { "maxUnits": 64, "maxAuras": 64, "maxEvents": 100, "durationMs": 5000 },
|
|
15
|
+
"dynamic": { "script": "discover-state.js", "stalker": { "enabled": false } }
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`kind` 和 `scope` 帮助选择脚本,不会被 CLI 当作固定语义。脚本可以返回不同的数据结构,但必须包含模块基址和每个候选的地址、RVA 或字节线索。
|
|
20
|
+
|
|
21
|
+
常见字段映射:
|
|
22
|
+
|
|
23
|
+
- 我方 Buff:`kind: "aura", scope: "player", direction: "helpful"`
|
|
24
|
+
- 敌方 Debuff:`kind: "aura", scope: "target", direction: "harmful"`
|
|
25
|
+
- 技能冷却:`kind: "cooldown", scope: "player"`
|
|
26
|
+
- 周围敌人:`kind: "unit-list", scope: "around-player"`
|
|
27
|
+
|
|
28
|
+
这些是查询意图。没有 reader-ready profile 定义前,不要把 dynamic 的一次性地址交给长期 reader。
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# 混合分析决策
|
|
2
|
+
|
|
3
|
+
## 先判断交付目标
|
|
4
|
+
|
|
5
|
+
- **只要当前值或短时事件**:优先 Dynamic。能从一次 GumJS 运行直接得到可信值,就结束本次查询,不生成持久化 profile。
|
|
6
|
+
- **要后续反复读取**:Dynamic 仍然先行,但必须补齐稳定的 RVA、指针链、类型、边界,并用 Reader 实际读通后再保存 profile。
|
|
7
|
+
|
|
8
|
+
## 每轮只解决一个证据缺口
|
|
9
|
+
|
|
10
|
+
| 当前缺口 | 下一步 | 成功标准 |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| 不知道模块/进程 | `wowdump targets` | PID、路径、buildKey、模块基址明确 |
|
|
13
|
+
| 不知道对象或函数是否被调用 | Dynamic GumJS 枚举、有限扫描或 Hook | 得到对象地址、候选函数或调用事件 |
|
|
14
|
+
| 有候选函数但不知道其语义 | Dynamic Hook,配合用户触发一次相关动作 | `this`、参数、返回值与目标字段相关 |
|
|
15
|
+
| 有调用但不知道字段偏移/类型 | IDA Pro MCP 局部反汇编;无 IDA 时 iced-x86 | 指令访问行为能解释对象和字段 |
|
|
16
|
+
| 静态候选需要运行时确认 | 回到 Dynamic Hook 或快照 | 候选 RVA 在当前模块命中且数据变化符合预期 |
|
|
17
|
+
| 字段定义已完成 | `wowdump memory read` | broker reader 实际读取成功,值与动态结果一致 |
|
|
18
|
+
|
|
19
|
+
## 选择顺序
|
|
20
|
+
|
|
21
|
+
1. 先检查是否已有同 build 的 `reader_ready` profile;有则直接 Reader,失败再回到 Dynamic。
|
|
22
|
+
2. 没有 profile 时优先执行 Agent 编写的 GumJS。脚本可以直接 Hook 已知 RVA,也可以在限定范围内扫描候选。
|
|
23
|
+
3. Dynamic 只拿到线索时,再让 IDA Pro MCP 分析对应函数或字节附近区域。不要对整个模块做无目标导出。
|
|
24
|
+
4. 没有 IDA 时使用 `analyze disassemble`/iced-x86 作为局部解码工具,得到候选后仍回到 Dynamic 验证。
|
|
25
|
+
5. 每轮最多扩大一个维度:函数数量、扫描范围、读取字节数或采样时长。连续两轮没有新增证据时,停止扩大并请求用户触发相关游戏动作或确认字段范围。
|
|
26
|
+
|
|
27
|
+
## 证据门槛
|
|
28
|
+
|
|
29
|
+
`reader_ready` 必须同时满足:
|
|
30
|
+
|
|
31
|
+
- buildKey、模块身份和模块布局匹配;
|
|
32
|
+
- RVA 来自同版本静态或运行时证据,且入口字节一致;
|
|
33
|
+
- `root`/对象获取路径、字段偏移、类型和边界有指令或运行时记录支持;
|
|
34
|
+
- broker-backed Reader 实际读取成功;
|
|
35
|
+
- 读取结果与 Dynamic 快照或 Hook 返回值一致,或差异有明确解释。
|
|
36
|
+
|
|
37
|
+
任何一项缺失都保留 `candidate`,继续在 Dynamic 与 IDA/iced 之间切换,不填充猜测地址。
|
|
38
|
+
|
|
39
|
+
## 运行约束
|
|
40
|
+
|
|
41
|
+
- GumJS 必须显式通过 `--script` 提供;每次运行自行负责 attach、Hook、采集、限时和 cleanup。
|
|
42
|
+
- Hook、扫描、事件、读取字节和总时长都设置上限;默认不启用 `Stalker`。
|
|
43
|
+
- 临时证据放在 `~/.wowdump/<buildKey>/runtime/<session>/`;只有用户确认的 profile 才进入 `profile/`。
|
|
44
|
+
- 不为单次查询修改 `src/` 或重新编译 CLI;实验逻辑放在临时 GumJS。
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copy-and-edit example for `wowdump analyze dynamic`.
|
|
3
|
+
* It is intentionally not selected or executed by the CLI automatically.
|
|
4
|
+
*/
|
|
5
|
+
"use strict";
|
|
6
|
+
|
|
7
|
+
const input = globalThis.__WOWDUMP_INPUT__ && typeof globalThis.__WOWDUMP_INPUT__ === "object"
|
|
8
|
+
? globalThis.__WOWDUMP_INPUT__
|
|
9
|
+
: {};
|
|
10
|
+
|
|
11
|
+
const limit = (value, fallback, maximum) => {
|
|
12
|
+
const number = Number(value);
|
|
13
|
+
if (!Number.isFinite(number) || number < 1) return fallback;
|
|
14
|
+
return Math.min(Math.floor(number), maximum);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const durationMs = limit(input.durationMs, 5000, 120000);
|
|
18
|
+
const maxEvents = limit(input.maxEvents, 100, 10000);
|
|
19
|
+
const maxHooks = limit(input.maxHooks, 1, 32);
|
|
20
|
+
const maxReadBytes = limit(input.maxReadBytes, 64, 256);
|
|
21
|
+
const events = [];
|
|
22
|
+
const candidates = [];
|
|
23
|
+
const errors = [];
|
|
24
|
+
const hooks = [];
|
|
25
|
+
let stopWaiting;
|
|
26
|
+
|
|
27
|
+
function record(event) {
|
|
28
|
+
if (events.length >= maxEvents) return false;
|
|
29
|
+
events.push({ timestamp: Date.now(), ...event });
|
|
30
|
+
if (events.length >= maxEvents && stopWaiting) stopWaiting();
|
|
31
|
+
return events.length < maxEvents;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function candidateAddress(candidate, module) {
|
|
35
|
+
if (!candidate || typeof candidate !== "object") return null;
|
|
36
|
+
if (candidate.address !== undefined) return ptr(String(candidate.address));
|
|
37
|
+
if (candidate.rva !== undefined) return module.base.add(ptr(String(candidate.rva)));
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function inModule(address, module) {
|
|
42
|
+
const end = module.base.add(module.size);
|
|
43
|
+
return address.compare(module.base) >= 0 && address.compare(end) < 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sampleBytes(address) {
|
|
47
|
+
try {
|
|
48
|
+
const range = Process.findRangeByAddress(address, "r--");
|
|
49
|
+
if (!range) return null;
|
|
50
|
+
const bytes = Memory.readByteArray(address, Math.min(maxReadBytes, range.size));
|
|
51
|
+
return bytes ? Array.from(new Uint8Array(bytes), value => value.toString(16).padStart(2, "0")).join("") : null;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
errors.push(`read ${address}: ${error}`);
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function installHook(address, name) {
|
|
59
|
+
if (hooks.length >= maxHooks) return;
|
|
60
|
+
try {
|
|
61
|
+
const listener = Interceptor.attach(address, {
|
|
62
|
+
onEnter(args) {
|
|
63
|
+
record({ kind: "enter", name, address: address.toString(), firstArg: args[0] ? args[0].toString() : null });
|
|
64
|
+
},
|
|
65
|
+
onLeave(retval) {
|
|
66
|
+
record({ kind: "leave", name, address: address.toString(), returnValue: retval.toString() });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
hooks.push(listener);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
errors.push(`hook ${name}: ${error}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
rpc.exports = {
|
|
76
|
+
async collect() {
|
|
77
|
+
const moduleName = typeof input.module === "string" && input.module.trim() ? input.module : "Wow.exe";
|
|
78
|
+
const module = Process.findModuleByName(moduleName);
|
|
79
|
+
if (!module) {
|
|
80
|
+
return { ok: false, module: { name: moduleName }, candidates: [], events: [], evidence: [], errors: [`module ${moduleName} was not found`] };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const requested = Array.isArray(input.candidates) ? input.candidates.slice(0, maxHooks) : [];
|
|
84
|
+
const resolved = [];
|
|
85
|
+
for (const [index, value] of requested.entries()) {
|
|
86
|
+
try {
|
|
87
|
+
const address = candidateAddress(value, module);
|
|
88
|
+
if (!address || !inModule(address, module)) {
|
|
89
|
+
errors.push(`candidate ${index} is outside ${moduleName}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const item = {
|
|
93
|
+
name: value && typeof value.name === "string" ? value.name : `candidate_${index}`,
|
|
94
|
+
address: address.toString(),
|
|
95
|
+
rva: address.sub(module.base).toString(),
|
|
96
|
+
bytesHex: sampleBytes(address),
|
|
97
|
+
source: "frida"
|
|
98
|
+
};
|
|
99
|
+
candidates.push(item);
|
|
100
|
+
resolved.push({ address, name: item.name });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
errors.push(`candidate ${index}: ${error}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const item of resolved) installHook(item.address, item.name);
|
|
107
|
+
let timer;
|
|
108
|
+
try {
|
|
109
|
+
await new Promise(resolve => {
|
|
110
|
+
stopWaiting = resolve;
|
|
111
|
+
timer = setTimeout(resolve, durationMs);
|
|
112
|
+
});
|
|
113
|
+
} finally {
|
|
114
|
+
stopWaiting = undefined;
|
|
115
|
+
if (timer) clearTimeout(timer);
|
|
116
|
+
for (const listener of hooks.splice(0)) {
|
|
117
|
+
try { listener.detach(); } catch (error) { errors.push(`cleanup: ${error}`); }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
ok: errors.length === 0,
|
|
123
|
+
module: { name: module.name, moduleBase: module.base.toString(), moduleSize: module.size },
|
|
124
|
+
moduleBase: module.base.toString(),
|
|
125
|
+
candidates,
|
|
126
|
+
events,
|
|
127
|
+
truncated: events.length >= maxEvents,
|
|
128
|
+
limits: { durationMs, maxEvents, maxHooks, maxReadBytes },
|
|
129
|
+
evidence: [{ source: "frida", kind: "dynamic-session", moduleBase: module.base.toString(), eventCount: events.length, hookCount: candidates.length }],
|
|
130
|
+
errors
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
};
|