dsh-js-reverse-plugin 0.1.0
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/cordis.patch.yml +14 -0
- package/index.js +61 -0
- package/package.json +20 -0
- package/skills/iv8-env-runtime/SKILL.md +159 -0
- package/skills/iv8-env-runtime/assets/entry-report-template.txt +23 -0
- package/skills/iv8-env-runtime/assets/env_starter.py +100 -0
- package/skills/iv8-env-runtime/references/case-function-iwencai.md +96 -0
- package/skills/iv8-env-runtime/references/case-intercept-douyin.md +133 -0
- package/skills/iv8-env-runtime/references/config-defaults-cheatsheet.md +75 -0
- package/skills/iv8-env-runtime/references/cross-platform-install.md +108 -0
- package/skills/iv8-env-runtime/references/iv8-api-contract.md +97 -0
- package/skills/iv8-env-runtime/references/network-and-eventloop.md +113 -0
- package/skills/iv8-env-runtime/references/part1-entry-location.md +138 -0
- package/skills/iv8-env-runtime/references/part1-mcp-tool-map.md +77 -0
- package/skills/iv8-env-runtime/references/part2-discovery.md +80 -0
- package/skills/iv8-env-runtime/references/patching-principles.md +91 -0
- package/skills/iv8-env-runtime/references/trap-blacklist.md +60 -0
- package/skills/iv8-env-runtime/references/version-gotchas.md +128 -0
- package/skills/iv8-env-runtime/scripts/consistency_lint.py +211 -0
- package/skills/iv8-env-runtime/scripts/defaults_audit.py +143 -0
- package/skills/iv8-env-runtime/scripts/monitor_env.py +185 -0
- package/skills/iv8-env-runtime/scripts/network_bridge.py +147 -0
- package/skills/iv8-env-runtime/scripts/parity_check.py +175 -0
- package/skills/iv8-env-runtime/scripts/preflight.py +144 -0
- package/skills/iv8-env-runtime/scripts/reproduce_stub_error.py +165 -0
- package/skills/iv8-env-runtime/scripts/run_target.py +150 -0
- package/skills/iv8-jsvmp-recovery/SKILL.md +129 -0
- package/skills/iv8-jsvmp-recovery/agents/openai.yaml +4 -0
- package/skills/iv8-jsvmp-recovery/references/case-study-douyin-a-bogus.md +320 -0
- package/skills/iv8-jsvmp-recovery/references/controlled-oracle-and-equivalence.md +247 -0
- package/skills/iv8-jsvmp-recovery/references/static-trace-evidence.md +289 -0
- package/skills/iv8-jsvmp-recovery/references/vmp-domain-model.md +212 -0
- package/skills/iv8-jsvmp-recovery/scripts/verify-equivalence.py +281 -0
- package/skills/js-ast-deobfuscation/SKILL.md +140 -0
- package/skills/js-ast-deobfuscation/agents/openai.yaml +4 -0
- package/skills/js-ast-deobfuscation/references/anti-patterns.md +325 -0
- package/skills/js-ast-deobfuscation/references/first-pass.md +255 -0
- package/skills/js-ast-deobfuscation/references/interaction-and-stopping.md +758 -0
- package/skills/js-ast-deobfuscation/references/iv8-runtime-observation.md +773 -0
- package/skills/js-ast-deobfuscation/references/jsvmp-identification.md +328 -0
- package/skills/js-ast-deobfuscation/references/methods-and-evidence.md +460 -0
- package/skills/js-ast-deobfuscation/references/patterns-and-priority.md +516 -0
- package/skills/js-ast-deobfuscation/references/validation-contract.md +604 -0
- package/skills/js-ast-deobfuscation/scripts/prepare-source.mjs +921 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# get_defaults() 导航 · 默认值体检表 —— 配不配值
|
|
2
|
+
|
|
3
|
+
> 服务于 **P2 发现 / P3 配环境**。核心问题:目标读的某个字段,iv8 默认是不是占位(about:blank/空)——占位就必须配真值。
|
|
4
|
+
> `iv8.JSContext.get_defaults()` 实测返回 **393 路径**;本页教你怎么在这 393 条里导航,而不是逐条背。
|
|
5
|
+
> 拉全量清单用 `scripts/defaults_audit.py`。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 0. 先分清:environment 还是 config
|
|
10
|
+
|
|
11
|
+
| 桶 | 放什么 | 能不能放数组 | 例子 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **environment** | **画像**:JS 可观测的指纹值 | **可以**(languages/brands 等) | navigator / screen / location / webgl / canvas / document 内容 |
|
|
14
|
+
| **config** | **引擎行为**:框架怎么跑 | **仅标量**,无数组 | `timezone`、`permissions.*` |
|
|
15
|
+
|
|
16
|
+
红线:画像塞进 config 不生效;数组塞进 config 报错。拿不准 → environment。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. 默认值体检表(占位就必配)
|
|
21
|
+
|
|
22
|
+
实测 `get_defaults()` 各前缀"空/占位 vs 有合理默认"的比例:
|
|
23
|
+
|
|
24
|
+
| 前缀 | 空/占位 | 有默认 | 结论 |
|
|
25
|
+
|---|---|---|---|
|
|
26
|
+
| **location** | **6** | 2 | `href=about:blank`、`hostname=localhost`、`origin/port/search/hash=''` → **几乎全占位,必配** |
|
|
27
|
+
| **canvas** | **4** | 1 | `toDataURL`/`toBlob` 系列全空(iv8 不真画像素)→ **靠 canvas 指纹的站必配** |
|
|
28
|
+
| navigator | 3 | **30** | 绝大多数有合理默认(UA=Chrome/platform=Win32/cores=8…);3 个空(`doNotTrack`/`vendorSub`/`userAgentData.model`)**真 Chrome 也空,别动** |
|
|
29
|
+
| screen | 0 | **11** | 全有默认(1920×1080×24),一般吃默认 |
|
|
30
|
+
| webgl | 0 | **71** | 全有默认(内置一套 GPU 画像),一般吃默认 |
|
|
31
|
+
| window | 3 | 18 | `origin`/`name`/`status` 空 → 个别站配 `window.origin` |
|
|
32
|
+
| document | 3 | 6 | `domain`/`referrer`/`dir` 空 → 个别站配 |
|
|
33
|
+
|
|
34
|
+
> 数字随版本可能微调;判断逻辑不变:**空/占位 = 你要负责喂真值,有默认 = 通常吃默认,只在对齐真机时才覆盖。**
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. 三层配置清单(照这个优先级配)
|
|
39
|
+
|
|
40
|
+
- **① 必配(默认就错,躲不掉)**:`location.*`。默认 `about:blank` → 不配则域名校验/cookie 域/拼 URL 全错、签名上下文错。**几乎每个案例都有 location** 就是这原因。
|
|
41
|
+
- **① 看站必配**:
|
|
42
|
+
- `canvas.fingerprint.toDataURL.png`(默认 `''`)—— 只有用 canvas 指纹的站;真值从真机抓注入。
|
|
43
|
+
- **DOM 内容**(页面元素/`<meta>`/`title`,默认空骨架)—— 只有读页面 DOM 的站;用 `innerHTML` 或 `page.load` 填真实 HTML(何时用哪个见 `references/part2-discovery.md`)。
|
|
44
|
+
- **② 有默认、通常只补 UA**:`navigator.userAgent`(默认某 Chrome 版本;要指定就配,因 UA 入签名)。其余 30 个 navigator 字段、screen、webgl **吃默认**,只在对齐真机时才配。
|
|
45
|
+
- **③ 别动**:`navigator.doNotTrack`/`vendorSub`/`userAgentData.model`(真 Chrome 也空)、DOM 的 **API**(createElement/querySelector…,iv8 自洽)、事件/crypto/timer。
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 3. 分层体检口诀(DOM 特别注意"API 齐 ≠ 内容有")
|
|
50
|
+
|
|
51
|
+
- **API 齐、内容空**:DOM 的 `createElement`/`querySelector`/`getElementById` 全自洽**不用补**;但 `document.title=''`、`documentElement.innerHTML` 仅 ~26 字符空壳、`querySelector('meta')=null`。读页面内容的站必须填真实 HTML。
|
|
52
|
+
- **location 几乎全占位**:默认 `about:blank`/`localhost`,是"默认就坑、躲不掉"的唯一前缀。
|
|
53
|
+
- **canvas 看站**:不真画像素,`toDataURL` 空串;图形指纹站必配,不用 canvas 的站不碰。
|
|
54
|
+
- **screen/webgl 全默认**:整套自洽,一般直接吃;只在需要伪装特定机型时才成套覆盖。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 4. 一致性红线(配指纹最易翻车)
|
|
59
|
+
|
|
60
|
+
改一个字段牵一串。**UA ↔ userAgentData ↔ platform ↔ 时区(timezone) ↔ 语言(language/languages) ↔ webgl(vendor/renderer) 必须整套对齐。**
|
|
61
|
+
改了 UA 说是 Mac,就别让 `userAgentData.platform` 还是 Windows、别让 webgl 还报 NVIDIA。
|
|
62
|
+
这条红线由 `scripts/consistency_lint.py` 静态拒错(配环境提交前先过 lint)。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 5. 怎么用 get_defaults() 导航
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import iv8
|
|
70
|
+
d = iv8.JSContext.get_defaults() # 393 路径 → 值
|
|
71
|
+
for path, val in sorted(d.items()):
|
|
72
|
+
if path.startswith("location"): # 只看某前缀
|
|
73
|
+
print(path, "=", repr(val))
|
|
74
|
+
```
|
|
75
|
+
把 debug 监控列出的"目标访问了哪些字段"与这里的默认值取交集:**目标读的 ∩ 默认占位的 = 你要配的清单**(P2 主力手段,见 `references/part2-discovery.md`)。全量导出与占位标注用 `scripts/defaults_audit.py`。
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# 跨平台安装(cross-platform-install)
|
|
2
|
+
|
|
3
|
+
> iv8 是**二进制编译扩展**(C++ V8,.so/.pyd),wheel 的平台 tag(cpXX / manylinux/macosx / 架构)
|
|
4
|
+
> 必须与你的 Python 精确匹配,不跨平台。安装前先跑 **scripts/preflight.py**——它硬门控平台/Python/架构,
|
|
5
|
+
> 装错组合直接阻断并打印实测已装版本(版本单一真相源,version-gotchas 消费它)。
|
|
6
|
+
> 项目强制用 **uv** 管理(见 CLAUDE.md)。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 平台矩阵(唯一权威分发路径)
|
|
11
|
+
|
|
12
|
+
| 平台 / 架构 | 分发渠道 | Python | 装法 | 状态 |
|
|
13
|
+
|---|---|---|---|---|
|
|
14
|
+
| **Windows x64** | PyPI | 3.9 – 3.14 | `uv pip install iv8` | 正式 |
|
|
15
|
+
| **Linux x64** | PyPI(manylinux 标准编译) | 3.9 – 3.14 | `uv pip install iv8` | 正式,CentOS/Ubuntu/Debian/Fedora 通用 |
|
|
16
|
+
| **Linux aarch64(arm64)** | 查 PyPI 有无 manylinux aarch64 wheel | 3.9 – 3.14 | 有则 `uv pip install iv8`;**无则暂无路** | 待查,不保证 |
|
|
17
|
+
| **macOS Apple Silicon(arm64)** | **GitHub Releases(不上 PyPI)** | 3.11 – 3.14 / macOS 14+ | 下 wheel → `uv pip install ./…whl` | **实验/灰度** |
|
|
18
|
+
| **Intel Mac(x86_64)** | 无 | — | — | **不支持**(阻断,转替代) |
|
|
19
|
+
| **macOS + Python <3.11** | 无 | — | — | **不支持**(阻断,转替代) |
|
|
20
|
+
|
|
21
|
+
> **为什么 macOS 走 Releases 不走 PyPI**:作者无 macOS 设备无法长期实测,arm64 版为社区实验/灰度性质
|
|
22
|
+
> (已通过离线 API 综合自测 173/173),刻意不发布到 PyPI。所以 macOS 上 `pip install iv8` **必然装不到**,
|
|
23
|
+
> 只能从 Releases 拿对应 wheel。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Windows x64 / Linux x64(PyPI 正式版)
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
uv venv --python 3.11 .venv
|
|
31
|
+
uv pip install --python .venv/bin/python iv8 requests
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- 建议 PyPI 官方源;部分第三方镜像同步延迟,可能取不到最新版。
|
|
35
|
+
- `requests` 一并装:社区版无网络栈,案例发真包靠它(见 references/network-and-eventloop.md)。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Linux aarch64(先查 wheel 是否可得)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# 探测 PyPI 是否为 aarch64 提供 manylinux wheel(不下载,只看有没有匹配文件)
|
|
43
|
+
uv pip install --python .venv/bin/python --no-deps --dry-run iv8
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- 若解析到 `manylinux*_aarch64` wheel → 正常 `uv pip install iv8 requests`。
|
|
47
|
+
- 若报"no matching distribution / 只有 x86_64 wheel" → **当前暂无 aarch64 路**。不要硬装 x64 wheel(架构不符必然
|
|
48
|
+
`import iv8` 崩)。明确告知用户此平台暂不支持,转 references/version-gotchas.md E7 提到的真机 CDP 路线或换 x64/Windows 机器。
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## macOS Apple Silicon(arm64 实验版,GitHub Releases)
|
|
53
|
+
|
|
54
|
+
wheel 文件名**三段必须精确匹配**(二进制扩展不跨平台):`cp311`(Py3.11) / `macosx_14_0`(macOS14+) / `arm64`(Apple Silicon)。
|
|
55
|
+
版本号、cpXX 以 Releases 页实际文件为准(**仅示例,运行时解析,勿写死某个版本**)。
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# ① 建独立 venv(uv,遵 CLAUDE.md,不污染系统 Python / 不与现有逆向项目依赖打架)
|
|
59
|
+
uv venv --python 3.11 .venv
|
|
60
|
+
|
|
61
|
+
# ② 装本地 wheel + requests。uv pip install 既支持本地路径,也支持直接喂 Releases URL:
|
|
62
|
+
# 路径法(先 curl -L 下到本地再装)
|
|
63
|
+
uv pip install --python .venv/bin/python ./iv8-0.1.3-cp311-cp311-macosx_14_0_arm64.whl requests
|
|
64
|
+
# URL 法(省去 curl,让 uv 直接拉)
|
|
65
|
+
uv pip install --python .venv/bin/python \
|
|
66
|
+
"https://github.com/HanZzzzz000/iv8/releases/download/v0.1.3/iv8-0.1.3-cp311-cp311-macosx_14_0_arm64.whl" requests
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- Python 必须 3.11–3.14 且 macOS 14+,否则 wheel tag 不匹配装不上。
|
|
70
|
+
- 装反 wheel(如把 cp312 装进 3.11 venv)→ `uv pip install` 会直接拒绝 tag 不符,属预期阻断,不是 bug。
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Intel Mac / macOS Python <3.11(阻断 + 替代)
|
|
75
|
+
|
|
76
|
+
无可用 wheel。preflight 检出后**直接阻断**,别尝试编译或硬装。替代路:
|
|
77
|
+
|
|
78
|
+
- 换 Apple Silicon 机 / 升到 Python 3.11+(若是 Intel Mac 则升 Python 也无用,仍无 x86_64 macOS wheel)。
|
|
79
|
+
- 换 Windows x64 / Linux x64 机器走 PyPI 正式版。
|
|
80
|
+
- 若只差临门一脚的物理量指纹(字体/audio),走真机 CDP 路线(见 references/version-gotchas.md E7),不依赖本机装 iv8。
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 验证:import iv8 冒烟测试
|
|
85
|
+
|
|
86
|
+
装完必跑(preflight.py 已内置这套断言):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
.venv/bin/python -c "import iv8; \
|
|
90
|
+
print('iv8', iv8.__version__ if hasattr(iv8,'__version__') else '(no __version__)'); \
|
|
91
|
+
import iv8 as I; ctx=I.JSContext(); \
|
|
92
|
+
assert ctx.eval('1+2')==3; \
|
|
93
|
+
print('UA:', ctx.eval('navigator.userAgent')); \
|
|
94
|
+
print('webdriver:', ctx.eval('navigator.webdriver')); \
|
|
95
|
+
print('typeof document:', ctx.eval('typeof document')); \
|
|
96
|
+
ctx.close()"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
预期:`1+2=3` / UA 为 Chrome/Windows 默认串 / `navigator.webdriver=False` / `typeof document='object'`。
|
|
100
|
+
四项全绿 = iv8 在本平台完整可用,可进 P1。任一失败 → 回本文平台矩阵核对 wheel tag,别往下推进。
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 相关文件
|
|
105
|
+
|
|
106
|
+
- scripts/preflight.py — P0 硬门:平台/Python/架构校验 + 打印实测版本 + 冒烟断言(版本单一真相源)。
|
|
107
|
+
- references/version-gotchas.md — 各版本专坑门控,消费 preflight 打印的版本。
|
|
108
|
+
- references/network-and-eventloop.md — 为什么要一并装 requests(社区版无网络栈)。
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# iv8 API 契约(权威·只准用这些)
|
|
2
|
+
|
|
3
|
+
> 本表是 iv8 社区版 0.1.3 实测过的**唯一权威 API 面**。写 P2/P3 代码时**照抄这里**,
|
|
4
|
+
> 不要臆造方法名或参数。拿不准某个能力有没有 → 它多半没有,走通用手段(requests + add_resource)。
|
|
5
|
+
> 版本相关的坑(btoa/MessageChannel/WASM…)见 `references/version-gotchas.md`(消费 preflight 的版本)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Python 侧:`iv8.JSContext`
|
|
10
|
+
|
|
11
|
+
### 1.1 构造参数
|
|
12
|
+
|
|
13
|
+
| 参数 | 类型 | 默认 | 说明 |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `mode` | str | `'prod'` | `'prod'` 零监控开销 / `'debug'` 开 API 调用链追踪 + 反射拦截 + DevTools |
|
|
16
|
+
| `environment` | dict | None | **画像**(JS 可观测的指纹值)。navigator/screen/location/webgl/canvas/document…**可含数组**(languages/brands) |
|
|
17
|
+
| `config` | dict | None | **引擎行为**(timezone/permissions.* 等)。**仅标量,不放数组/画像值** |
|
|
18
|
+
| `ignore_apis` | list | 内置默认 | 从 debug 监控日志排除的 API(默认已静音 Math/JSON/Array/TypedArray 降噪) |
|
|
19
|
+
| `time_mode` | str | `'logical'` | `'logical'` 逻辑时间 `sleep(5000)` 瞬间完成 / `'system'` 锚系统时间(POW、时间差校验用) |
|
|
20
|
+
| `js_api` | str | `'__iv8__'` | JS 侧工具对象挂载名 |
|
|
21
|
+
|
|
22
|
+
> environment vs config 的红线:**画像走 environment(可数组),引擎行为走 config(仅标量)**。塞反了不生效或报错。分类导航见 `references/config-defaults-cheatsheet.md`。
|
|
23
|
+
|
|
24
|
+
### 1.2 方法
|
|
25
|
+
|
|
26
|
+
| 方法 | 说明 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `eval(source, name="", line=-1, col=-1, to_py=False, devtools=True)` | 执行 JS,返回值自动转 Python。`to_py=True` 递归转嵌套对象/数组 |
|
|
29
|
+
| `close(gc="none")` | 释放上下文。`gc` 可选 `"low_memory"`(=`"v8"`/`True`) / `"aggressive"` 触发 GC |
|
|
30
|
+
| `add_resource(url, body, status=200, headers=None)` | 注入离线 HTTP 响应到 bundle(XHR/fetch/`<script src>` 从中匹配) |
|
|
31
|
+
| `with_devtools(port=9229, watch_apis=None, enable_console=True)` | 启用 CDP 调试,返回 ctx 便于链式 `with` |
|
|
32
|
+
| `expose(obj, name?)` / `expose(**kwargs)` | 把 Python 对象暴露到 JS 的 `__iv8__.data`(不污染 window) |
|
|
33
|
+
| `get_defaults()` | **类方法**,返回全部支持路径及默认值(实测 393 路径);配环境导航见 config-defaults-cheatsheet |
|
|
34
|
+
|
|
35
|
+
推荐上下文管理器:`with iv8.JSContext(...) as ctx:` —— 退出自动释放,别手动漏 close。
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. JS 侧工具对象 `window.__iv8__`
|
|
40
|
+
|
|
41
|
+
设计为**不可检测**:`typeof window.__iv8__ === "undefined"`,对目标 JS 完全隐身。
|
|
42
|
+
|
|
43
|
+
### 2.1 `__iv8__.eventLoop.*`(事件循环 / 时间控制)
|
|
44
|
+
|
|
45
|
+
| 方法 | 说明 |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `advance(total, step?)` | 分帧推进虚拟时间(默认步长 ~16.67ms),模拟 rAF 节奏 |
|
|
48
|
+
| `sleep(ms?, max?)` | 推进 ms 毫秒,按时间线顺序排空任务队列 |
|
|
49
|
+
| `tick(ms?)` | 推进 ms 并执行一轮事件循环 |
|
|
50
|
+
| `drain(max?)` | 排空所有已到期任务,**不推进时间** |
|
|
51
|
+
| `drainMicrotasks()` | 仅排空微任务(Promise.then/queueMicrotask/MutationObserver 回调) |
|
|
52
|
+
| `drainTimers()` | 仅处理已到期定时器回调 |
|
|
53
|
+
| `setAutoAdvanceStep(ms)` | 设 `performance.now()` 自动递增量(默认 0.001ms) |
|
|
54
|
+
| `setDateAdvanceStep(ms)` | 设 `Date.now()` 自动递增量(默认 1ms) |
|
|
55
|
+
|
|
56
|
+
### 2.2 其余工具
|
|
57
|
+
|
|
58
|
+
| 工具 | 说明 |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `__iv8__.page.load({baseURL, html, resources?, headers?})` | 流式加载 HTML:解析→暂停执行 `<script>`→处理样式表→派发 DOMContentLoaded/load→同步 URL |
|
|
61
|
+
| `__iv8__.input.dispatchMouseEvent(init)` | 派发可信鼠标事件(`isTrusted=true`),init 含 type/target/clientX/clientY/button/buttons |
|
|
62
|
+
| `__iv8__.input.dispatchPointerEvent(init)` | 派发可信指针事件 |
|
|
63
|
+
| `__iv8__.netLog.entries` | JS 侧发起的 XHR/fetch/导航请求日志数组(配 `to_py=True` 读) |
|
|
64
|
+
| `__iv8__.wrapNative(fn, name)` | 把 JS 函数伪装为 `[native code]`,降低临时补丁可观测差异 |
|
|
65
|
+
| `__iv8__.help()` | 打印所有可用工具 |
|
|
66
|
+
| `__iv8__.data.*` | Python `expose()` 暴露对象的落点 |
|
|
67
|
+
|
|
68
|
+
`page.load` 的 `resources` 简写 `url: '内容串'`,完整格式 `url: {body, status, headers}`。
|
|
69
|
+
`page.load` vs `innerHTML` 何时用哪个见 `references/part2-discovery.md`(是否需执行页面 `<script>`)。
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 3. ★ 误用 → 正确 对照表(新人最爱踩)
|
|
74
|
+
|
|
75
|
+
| 误用 | 为什么错 | 正确做法 |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| 在 JS 里直接 `fetch()`/在 Python 里指望 iv8 自动发真请求 | 社区版**无真实网络栈**,请求只从离线 bundle 匹配 | **network_bridge 模式**:Python `requests` 发真请求 → `ctx.add_resource(url,body,...)` 注入 → `eventLoop.drain()` 推进(见 `references/network-and-eventloop.md` / `scripts/network_bridge.py`) |
|
|
78
|
+
| `ctx.eval("debugger;")` 想断点 | iv8 禁用原生 `debugger;`(防目标无限 debugger 反调试),不触发 | 用 `ctx.eval("vdebugger;")` |
|
|
79
|
+
| `config={"navigator": {...}}` / `config` 里塞数组 | config 只吃标量引擎行为,画像/数组不认 | 画像走 `environment`;数组(languages/brands)也放 environment |
|
|
80
|
+
| 发完 XHR 立刻读结果 | 回调还在任务队列里没跑,读到 undefined | **先 `advance`/`drain` 再读**;run_target 内联 drain-before-read 断言兜底 |
|
|
81
|
+
| 补 `process`/`_phantom`/`StyleMedia` 让它"更像浏览器" | 这些真 Chrome 没有,补了 = 自曝陷阱 | **保持 undefined**(见 `references/trap-blacklist.md`) |
|
|
82
|
+
| 改目标 JS 源码插 `console.log` 看中间值 | 触发自校验(toString/校验和/文件 hash)走惩罚分支 | 原始 JS 零修改,**hook 靠外部**(包 prototype 方法 / defineProperty / expose 截获) |
|
|
83
|
+
| 用标准 `console.log` 上报被检测 | 反爬 JS 靠 console 行为差异识别调试环境 | `with_devtools(enable_console=False)` + JS 侧 `vconsole.log()`(仅 DevTools 可见) |
|
|
84
|
+
| 手动 `ctx = JSContext(); ... ` 忘 close | 泄漏 Isolate | 用 `with iv8.JSContext(...) as ctx:` 上下文管理器 |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. debug 能力(定位环境探测用)
|
|
89
|
+
|
|
90
|
+
`mode='debug'` + `with_devtools(port=..., watch_apis=[...], enable_console=False)`:
|
|
91
|
+
|
|
92
|
+
- **API 调用链追踪**:记录受监控浏览器 API 的属性读/写、方法调用、构造调用(Math/JSON/Array/TypedArray 默认静音,可 `ignore_apis` 调)。
|
|
93
|
+
- **反射拦截**:拦 `Object.keys` / `getOwnPropertyDescriptor` / `defineProperty` / `Reflect.ownKeys/get/has` / `Function.prototype.toString` / `JSON.parse/stringify`,把目标的**环境探测链路**打出来。
|
|
94
|
+
- **API 访问断点**:`watch_apis=["navigator.userAgent","document.cookie","canvas.toDataURL"]` 命中即在 DevTools 暂停。
|
|
95
|
+
- **面板**:Elements 看 DOM 树、Application 看 Cookie/Storage、单步 + 变量查看/修改。
|
|
96
|
+
|
|
97
|
+
debug 监控是 P2 发现"要配什么"的主力手段,配合脚本 `scripts/monitor_env.py`;用法见 `references/part2-discovery.md`。
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# 网络桥接与事件循环(network-and-eventloop)
|
|
2
|
+
|
|
3
|
+
> **一句话**:iv8 社区版**不内置真实网络传输栈**,也**不自动推进时间**。真请求要你自己发、
|
|
4
|
+
> 结果要你自己 drain。这两件事缺一不可,是 P3"跑通"阶段最常见的两个断点。
|
|
5
|
+
> 固化实现见 scripts/network_bridge.py 与 scripts/run_target.py(内联 drain-before-read 断言)。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 一、网络:社区版无真实网络栈
|
|
10
|
+
|
|
11
|
+
README 白纸黑字:社区版不直接发 HTTP/HTTPS,也不带 Chromium net 栈。XHR / fetch / 外联 `<script src>` /
|
|
12
|
+
`<link href>` / CSS `@import` **默认从离线 bundle 匹配响应**。TLS 指纹、cookie 池、代理全归你的 Python HTTP 客户端。
|
|
13
|
+
`netLog.entries` 只**记录** JS 侧发起的请求,不真发。
|
|
14
|
+
|
|
15
|
+
这不是缺陷,是分工:与 curl_cffi 工作流同构——**iv8 只算签名/token,真包由 Python(requests/curl_cffi)发**。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 二、桥接四步握手(缺一不可)
|
|
20
|
+
|
|
21
|
+
真实请求必须走这条闭环。任何一步漏做,JS 侧回调拿不到响应、Promise 永挂:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
① JS 发起 XHR/fetch → ② 事件循环暂停(回调挂起,等资源)
|
|
25
|
+
↓
|
|
26
|
+
④ eventLoop.drain() 推进 ← ③ Python 侧真发请求 + ctx.add_resource() 注入响应到离线 bundle
|
|
27
|
+
(XHR 回调命中刚注入的资源,responseText 就位)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**骨架(异步 XHR,先发起→再注入→再 drain)**:
|
|
31
|
+
```python
|
|
32
|
+
import requests, iv8
|
|
33
|
+
with iv8.JSContext() as ctx:
|
|
34
|
+
ctx.eval("window.__iv8__.page.load({baseURL:'https://example.com', html:'<html><body></body></html>'})")
|
|
35
|
+
# ① JS 发起异步请求
|
|
36
|
+
ctx.eval("""var x=new XMLHttpRequest(); x.open('GET','https://api.example.com/data');
|
|
37
|
+
x.onload=function(){window._r=x.responseText;}; x.send();""")
|
|
38
|
+
# ③ Python 真发 + 注入(这一步决定 TLS/cookie/代理)
|
|
39
|
+
resp = requests.get("https://api.example.com/data")
|
|
40
|
+
ctx.add_resource(url="https://api.example.com/data", body=resp.text,
|
|
41
|
+
status=resp.status_code, headers=dict(resp.headers))
|
|
42
|
+
# ④ 推进事件循环,回调才会执行
|
|
43
|
+
ctx.eval("window.__iv8__.eventLoop.drain()")
|
|
44
|
+
print(ctx.eval("window._r")) # 读结果前必已 drain
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**注入语义**:`add_resource(url, body, status=200, headers=None)` 与 `page.load` 的 `resources` 参数写入**同一个**离线 bundle。
|
|
48
|
+
HTML 解析期的外联资源、运行期的 XHR/fetch 都从中按 URL 匹配。`resources` 简写=直接给内容串;完整格式=`{body, status, headers}`。
|
|
49
|
+
|
|
50
|
+
**拦截式 SDK 的变体(不真发、只在 URL 上加签)**:某些 SDK(如字节 bdms)`init()` 装的是 XHR 拦截器,
|
|
51
|
+
在请求 URL 上加 `a_bogus=...` 等签名却不真发包。此时**不 drain 等响应**,而是从 `netLog.entries` 把"被签名后的最终 URL"捞回 Python,再用 requests 发。判来源与入口类型见 references/part1-entry-location.md。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 三、expose 桥接的 GIL 阻塞警告
|
|
56
|
+
|
|
57
|
+
另一种桥法:`ctx.expose(py_get, "pyGet")` 把 Python 函数挂到 JS 的 `__iv8__.data` 命名空间(不污染 window),
|
|
58
|
+
JS 里 `__iv8__.data.pyGet(url)` 直接调。
|
|
59
|
+
|
|
60
|
+
**GIL 硬约束**:JS 调用 expose 出去的 Python 函数时会**自动获取 GIL**——
|
|
61
|
+
```
|
|
62
|
+
Python eval() → 释放 GIL → V8 执行 → (JS 调 Python → 获取 GIL → 执行 → 释放) → V8 返回 → 获取 GIL
|
|
63
|
+
```
|
|
64
|
+
期间 V8 单线程暂停、其他 Python 线程全阻塞。所以:
|
|
65
|
+
|
|
66
|
+
- **expose 的函数要尽量轻量**,别在里面塞重活/长阻塞 IO——否则多线程并行优势被这一下抹平。
|
|
67
|
+
- 通过 expose 桥接的 HTTP 请求是**完全同步阻塞**的。高并发场景优先用 `add_resource()`/`netLog` 路线(四步握手),或把并发放在 Python 层。
|
|
68
|
+
- expose 三态:`expose(obj, "name")` 命名 / `expose(fn)` 用 `fn.__name__` 自动命名 / `expose(get=..., post=...)` 批量。数据(dict/list)也能 expose。
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 四、事件循环:读结果前必 drain
|
|
73
|
+
|
|
74
|
+
iv8 不自动推进时间。JS 里 `setTimeout`/Promise/XHR 回调**排进队列后不会自己跑**,你得手动排空。
|
|
75
|
+
**"算出 token 却读到 null/undefined"十有八九是漏 drain。**
|
|
76
|
+
|
|
77
|
+
| 方法 | 语义 | 何时用 |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `advance(total, step?)` | 分帧推进虚拟时间(默认步 ~16.67ms,模拟 rAF 节奏) | 有 rAF/setInterval/分帧动画的采集链 |
|
|
80
|
+
| `sleep(ms?, max?)` | 推进虚拟时间 ms 毫秒,按时间线顺序排空 | 目标里有明确 setTimeout 延时 |
|
|
81
|
+
| `tick(ms?)` | 推进 ms 并执行一轮事件循环 | 逐步调试、看一轮跑出什么 |
|
|
82
|
+
| `drain(max?)` | 排空所有**已到期**任务,**不推进时间** | 最常用:注入响应后收尾、同步触发后收割 |
|
|
83
|
+
| `drainMicrotasks()` | 仅排空微任务(Promise.then/queueMicrotask/MutationObserver) | async/await 卡在 await 微任务边界 |
|
|
84
|
+
| `drainTimers()` | 仅处理已到期定时器回调 | 只想触发定时器不碰其他 |
|
|
85
|
+
|
|
86
|
+
执行顺序对齐 HTML spec:**微任务先于宏任务**。例:`sync → promise → microtask → timeout-0`。
|
|
87
|
+
|
|
88
|
+
**async 采集常见组合**:`drainMicrotasks()` 先把 await 链推到第一个真延时 → `advance(150)` 越过 setTimeout → 再读结果。
|
|
89
|
+
core-js microtask 降级链(MessageChannel 坑,见 references/version-gotchas.md E2)配 `advance(50)+drain()` 驱动。
|
|
90
|
+
|
|
91
|
+
**drain-before-read 断言(run_target.py 内联固化)**:任何"读 token/读 window.\_result"之前,必须已调过对应的 drain/advance。
|
|
92
|
+
把它写成脚本层硬断言,而不是靠记性——这是"保证模型按预期"的脚本层一环。
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 五、time_mode:logical vs system
|
|
97
|
+
|
|
98
|
+
| 模式 | 行为 | 适用 |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `logical`(默认) | 纯逻辑虚拟时钟,`sleep(5000)` 瞬间完成,`Date.now()` 读虚拟时钟 | 绝大多数自动化、快速执行 |
|
|
101
|
+
| `system` | 系统时间锚定,JS 执行期 `Date.now()` 反映真实耗时 | **POW(工作量证明)、时间差/耗时校验**类 |
|
|
102
|
+
|
|
103
|
+
对齐细节:`setAutoAdvanceStep(ms)` 调 `performance.now()` 自增量(默认 0.001ms);`setDateAdvanceStep(ms)` 调 `Date.now()` 自增量(默认 1ms)。
|
|
104
|
+
**踩坑**:目标若校验"两个时间戳之差 ≈ 真实执行耗时"或算 POW,logical 模式下时间不流逝会露馅——换 `time_mode="system"`。反之纯签名场景用 logical 最快。
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 相关文件
|
|
109
|
+
|
|
110
|
+
- scripts/network_bridge.py — 固化四步握手(真发+注入+drain),别每次手搓。
|
|
111
|
+
- scripts/run_target.py — 跑目标、内联 drain-before-read 断言、收割 token。
|
|
112
|
+
- references/part1-entry-location.md — 判"本地生成/搬运/下发"与拦截式识别。
|
|
113
|
+
- references/version-gotchas.md — MessageChannel(E2)等 async 推进不动的引擎坑。
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Part1 · 入口定位:从真实出口倒推
|
|
2
|
+
|
|
3
|
+
> 本文 = P1「找入口」阶段的方法论主文档。目标:用 js-reverse MCP 在真实浏览器里,
|
|
4
|
+
> 从「删了就挂」的真实出口一路倒推,定位目标字段**真实生成入口**并给它分类。
|
|
5
|
+
> 工具怎么点 → 见 `references/part1-mcp-tool-map.md`;端到端实例 → 见
|
|
6
|
+
> `references/case-intercept-douyin.md`、`references/case-function-iwencai.md`。
|
|
7
|
+
>
|
|
8
|
+
> **退出闸(CHECKPOINT)**:`assets/entry-report-template.txt` 全字段填满,且末行
|
|
9
|
+
> 结论写「已定位」。任一字段只有猜测/调用栈痕迹而无动态边界证据 → 结论只能写「未定位」,
|
|
10
|
+
> 不许进 P2。
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0. 一句话心法
|
|
15
|
+
|
|
16
|
+
**入口定位不是先读懂混淆代码,而是让真实浏览器里的代码自己跑,在真实出口附近观察
|
|
17
|
+
参数「从无到有」的边界。** 顺着一个观察(请求里有个怪参数)一步步逼出真相,
|
|
18
|
+
不是预先知道答案;每一步用一个实验确认,实验说话。
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. 核心原则(四条铁律)
|
|
23
|
+
|
|
24
|
+
1. **先找真实出口,再从出口倒推 JS**。出口 = URL / header / body / cookie / storage /
|
|
25
|
+
XHR.open / fetch / sendBeacon / callback / return。
|
|
26
|
+
2. **同一个出口里的多个新增参数,逐个确认来源**。一个 JS 可以同时「生成 A」并「搬运 B」;
|
|
27
|
+
不能因为都在同一个 URL 里就默认来源相同。
|
|
28
|
+
3. **生成、读取、搬运、写入分开描述**。它们在调用栈里都会「出现」,但只有「生成」是入口。
|
|
29
|
+
4. **定位正确的核心证据是「上一层没有,下一层有」,不是「调用栈里出现过」**。
|
|
30
|
+
看到某 JS 出现在栈里,只能说明它参与了流程,不能说明它生成了目标参数。
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. 七步执行流程
|
|
35
|
+
|
|
36
|
+
1. **明确目标字段**:字段名、所在请求/存储位置、真实样例值。
|
|
37
|
+
2. **找到真实出口**:确认字段长在 URL query / header / body / cookie / storage 的哪里
|
|
38
|
+
(删掉它重放 → 被拦/空数据 ⇒ 确认它是门票,就是要逆的目标)。
|
|
39
|
+
3. **在出口处下断点**:优先断 XHR/fetch、cookie setter、storage setter 或目标函数返回点。
|
|
40
|
+
4. **从断点向上看调用栈,只给每层定角色**(见 §3 角色表),不硬读混淆代码。
|
|
41
|
+
5. **切到疑似 JS 的 frame,主动 evaluate**:改写前输入、改写后输出、中间结果、配置对象。
|
|
42
|
+
6. **对每个新增参数单独追来源**(见 §4 判定表)。
|
|
43
|
+
7. **做最小复现 + 对照实验**:模拟真实触发方式(而非臆造 sign 函数);不触发没有、触发后
|
|
44
|
+
出现;改输入/清空状态后结果符合预期变化。
|
|
45
|
+
|
|
46
|
+
> P1↔P2↔P3 可来回迭代:P2 起手跑发现缺 API,可能要回 P1 补看某帧;不是单向。
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 3. 调用栈角色表(只有「生成」= 入口)
|
|
51
|
+
|
|
52
|
+
| 角色 | 含义 | 是否等于生成入口 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| 加载 | 加载目标 SDK 或业务包 | 否 |
|
|
55
|
+
| 初始化 | 传入 config,安装 hook 或启动状态机 | 不一定 |
|
|
56
|
+
| 触发 | 发起请求、事件、队列或异步任务 | 不一定 |
|
|
57
|
+
| **生成** | **现场算出目标参数值** | **是** |
|
|
58
|
+
| 读取 | 从 cookie/storage/内部状态/响应缓存取已有值 | 否 |
|
|
59
|
+
| 搬运 | 把已有值写入 URL/header/body/storage | 否 |
|
|
60
|
+
| 发送 | 把最终请求送到网络出口 | 否 |
|
|
61
|
+
|
|
62
|
+
调用栈只能帮助定位,不能直接等于结论。定角色时优先找「哪一帧的输入里还没有目标值、
|
|
63
|
+
输出里已经有了」——那一帧才是生成帧。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 4. 参数来源判定表(逐参数写结论)
|
|
68
|
+
|
|
69
|
+
| 来源类型 | 判断标准 | 验证方式 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| 本地生成 | 上一层没有该值,下一层出现该值,中间结果 == 最终出口值 | 改输入后目标值随输入变化 |
|
|
72
|
+
| 已有状态搬运 | 出口值 == cookie/storage/全局状态/SDK 内部状态里的已有值 | 修改或清空已有状态后,出口值同步变化或消失 |
|
|
73
|
+
| 服务端下发 | 出口值 == 前序响应的 header/Set-Cookie/body/重定向参数 | 清 cookie/storage/network 后重放,比对前序响应与最终出口 |
|
|
74
|
+
| 未确认 | 只有调用栈/变量名/猜测/代码片段,无动态边界证据 | 只能写「未定位」 |
|
|
75
|
+
|
|
76
|
+
同一出口出现多个新增参数时,为**每个参数分别写来源结论**。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 5. 六大入口类型表
|
|
81
|
+
|
|
82
|
+
| 入口类型 | 判断标准 | 最小复现 |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| 函数式 | 调用函数后直接 return 目标结果 | 加载 JS → 调函数 → 读 return |
|
|
85
|
+
| 拦截式 | init 后 hook XHR/fetch,请求发出时改写参数 | init(config) → 触发 XHR/fetch → 从出口读结果 |
|
|
86
|
+
| 事件式 | 点击/输入/滑动/message 等事件后出现结果 | 准备状态 → dispatch/click/input → 读出口 |
|
|
87
|
+
| 状态机式 | 依赖异步任务/timer/microtask/队列推进 | 初始化 → 触发动作 → 推进队列(drain) → 读出口 |
|
|
88
|
+
| 采集式 | 读取环境后生成 cookie/sensor/telemetry | start/init → 触发采集 → 读 cookie/beacon/storage |
|
|
89
|
+
| 下发式 | 参数来自前序网络响应 | 清状态 → 重放 → 对比响应与后续出口 |
|
|
90
|
+
|
|
91
|
+
> 类型决定 P2/P3 的复现骨架:函数式 → 直接调函数(爱问财 hexin-v);拦截式 → 装 hook +
|
|
92
|
+
> 触发 + 捞 netLog(抖音 a_bogus);状态机/采集式 → 必须用 `__iv8__.eventLoop` 推进后再读,
|
|
93
|
+
> 见 `references/network-and-eventloop.md`。
|
|
94
|
+
> **本 skill 不涉及以真实鼠标轨迹/行为采集为核心的行为反爬边界与内容。**
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 6. 禁止结论清单(写了这些 = 未定位)
|
|
99
|
+
|
|
100
|
+
下面任一表达都不能算「已定位」:
|
|
101
|
+
|
|
102
|
+
- 可能是 / 大概是 / 应该是。
|
|
103
|
+
- 和某 SDK 有关。
|
|
104
|
+
- 调用栈里出现了某 JS。
|
|
105
|
+
- 变量名看起来像。
|
|
106
|
+
- 代码里搜到了字段名。
|
|
107
|
+
- 先把环境补齐后再看。
|
|
108
|
+
- 硬读混淆代码或 VMP 后推测。
|
|
109
|
+
|
|
110
|
+
**没有「上一层没有、下一层有」的动态边界证据时,结论只能写「未定位」。**
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## 7. 动态边界证据要采什么
|
|
115
|
+
|
|
116
|
+
在断点处直接 evaluate,把下面这些采下来(对应 `assets/entry-report-template.txt`):
|
|
117
|
+
|
|
118
|
+
- **上一层输入**:是否含目标字段?值是什么?
|
|
119
|
+
- **下一层输出**:是否含目标字段?值是什么?
|
|
120
|
+
- **中间结果**:哪个变量/返回值 == 最终目标字段?
|
|
121
|
+
- **配置对象**:当前命中了什么规则(如拦截白名单 paths)?
|
|
122
|
+
- **触发前状态**:JS 是否加载、init 是否完成、cookie/storage/config 是否存在。
|
|
123
|
+
- **主动触发方式**:调函数 / 触发 XHR/fetch / 事件 / timer / 队列 / getter。
|
|
124
|
+
- **输入参数**:this、arguments、URL、method、headers、body、config、事件对象。
|
|
125
|
+
- **输出读取位置**:从 URL/header/body/cookie/storage/netLog/return/callback 的哪里读。
|
|
126
|
+
- **对照实验**:不触发、触发、改输入、清状态、重放请求后的对照结果。
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 8. 结论输出
|
|
131
|
+
|
|
132
|
+
P1 结论必须落到 `assets/entry-report-template.txt` 的字段模板:目标字段 / 真实出口 /
|
|
133
|
+
直接相关 JS / 入口类型 / 来源类型 / 前置状态 / 主动触发方式 / 输入参数 /
|
|
134
|
+
**上一层没有的证据** / **下一层已有的证据** / **中间结果 == 最终结果的证据** /
|
|
135
|
+
输出读取方式 / 对照实验 / 结论(已定位|未定位)。
|
|
136
|
+
|
|
137
|
+
三条证据(上一层没有 / 下一层已有 / 中间结果==最终结果)**同时齐备**才允许写「已定位」。
|
|
138
|
+
齐备后进 P2(`references/part2-discovery.md`)。
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Part1 · 方法步骤 → js-reverse MCP 工具映射
|
|
2
|
+
|
|
3
|
+
> 配合 `references/part1-entry-location.md` 用:那篇讲「怎么想」,本篇讲「点哪个工具」。
|
|
4
|
+
> 只用 js-reverse MCP 提供的工具名,别臆造。所有 `scriptId` 随 reload/navigate/切帧过期,
|
|
5
|
+
> 优先用**脚本 URL** 定位。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. 步骤 → 工具映射表
|
|
10
|
+
|
|
11
|
+
| 七步流程 / 动作 | 首选工具 | 备选 / 配合 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| 明确目标字段·抓包看请求 | `list_network_requests` | `get_request_initiator`(拿到 reqid 后查发起 JS) |
|
|
14
|
+
| 找真实出口·定位发起 JS | `get_request_initiator` | `list_scripts`、`list_network_requests` |
|
|
15
|
+
| 出口处下断点(网络) | `break_on_xhr`(URL 子串) | `set_breakpoint_on_text`(按源码文本断) |
|
|
16
|
+
| 招①搜参数名/内部命名 | `search_in_sources` | `list_scripts`(不知道搜什么时) |
|
|
17
|
+
| 招②堵网络出口·验 native | `evaluate_script`(mainWorld=true) | 求值 `XMLHttpRequest.prototype.open.toString()` |
|
|
18
|
+
| 招③搜 hook 明文胎记 | `search_in_sources` | 命中后 `get_script_source` 读上下文 |
|
|
19
|
+
| 命中断点·看调用栈定角色 | `get_paused_info` | `select_frame`(切到疑似生成帧) |
|
|
20
|
+
| 帧内主动 evaluate 取边界证据 | `evaluate_script`(带 frame) | 见 §3 箭头函数取 arguments |
|
|
21
|
+
| 读某段源码 | `get_script_source` | `search_in_sources`(先定位行) |
|
|
22
|
+
| 单步跟进生成点 | `step` | `pause_or_resume` |
|
|
23
|
+
| 解除断点(防重入死锁) | `remove_breakpoint` | `list_breakpoints`(先查有哪些) |
|
|
24
|
+
| 切 iframe/帧做帧内工作 | `select_frame` | `select_page`(多页时先选页) |
|
|
25
|
+
| 触发采集/事件后看流量 | `list_network_requests` | `get_websocket_messages`(WS 流) |
|
|
26
|
+
|
|
27
|
+
> 网络与 cookie 证据是 page 级;源码/调试/帧内 evaluate 是 frame 级 → 做帧内工作前先
|
|
28
|
+
> `select_frame`。抓 cookie 诞生点用 `list_network_requests` 带 cookieName。
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 2. 入口类型 → 定位打法
|
|
33
|
+
|
|
34
|
+
| 入口类型 | 定位主线(工具序列) |
|
|
35
|
+
|---|---|
|
|
36
|
+
| 函数式 | `search_in_sources`(字段名/算法名) → `set_breakpoint_on_text` 在生成点 → `get_paused_info` 看栈 → 帧内 `evaluate_script` 证纯函数 |
|
|
37
|
+
| 拦截式 | 搜不到字段名(常态) → `evaluate_script` 验 `open/send/fetch` 是否 `[native code]` → `search_in_sources` 搜 hook 内部命名 → `break_on_xhr` 看栈一步到 SDK |
|
|
38
|
+
| 事件式 | `click_element`/`evaluate_script` 触发事件 → `break_on_xhr` 或 setter 断点 → `get_paused_info` 看栈 |
|
|
39
|
+
| 状态机式 | `break_on_xhr` 命中后 `remove_breakpoint` → 帧内 evaluate 观察队列/timer 状态 → 触发+推进后 `list_network_requests` |
|
|
40
|
+
| 采集式 | `list_network_requests`(cookieName 查 cookie 诞生) → `get_request_initiator` → 断点看栈 |
|
|
41
|
+
| 下发式 | `list_network_requests` 看前序响应 header/body → 比对后续出口值 == 前序响应值 |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. 卡点急救
|
|
46
|
+
|
|
47
|
+
**A. `search_in_sources` 无匹配 = 反爬常态,不是你错**
|
|
48
|
+
反爬几乎都把关键参数名动态拼接/编码,源码里没有明文。搜不到恰恰说明「对方在藏」。
|
|
49
|
+
立即换招:不再搜字段名,改**查网络出口 hook**——用 `evaluate_script`(mainWorld=true) 求值
|
|
50
|
+
`XMLHttpRequest.prototype.open.toString()` / `window.fetch.toString()`,不是 `[native code]`
|
|
51
|
+
就是被接管了,签名十有八九在这条 hook 链里注入。再 `search_in_sources` 搜 hook 内部属性名
|
|
52
|
+
(如 `secureOpenArgs`、`nativeXMLHttpRequestOpen` 这类 hook 自用、没被混淆的胎记)。
|
|
53
|
+
|
|
54
|
+
**B. 断点重入死锁 = 命中后立刻 `remove_breakpoint`**
|
|
55
|
+
webpack 转发壳 / `u[16]++` 这类点会被反复命中,尤其后续嵌套调用会再次暂停造成死锁。
|
|
56
|
+
两个手段:
|
|
57
|
+
- 下断点时加 `condition`,只在目标特征成立时停(如 `arguments[0]&&arguments[0].paths&&arguments[0].aid` 只在真 init 时命中)。
|
|
58
|
+
- 命中一次拿到帧引用后,马上 `remove_breakpoint`,再在帧内做 evaluate/复现,避免二次进入。
|
|
59
|
+
|
|
60
|
+
**C. 帧内 eval 读 `arguments` 用箭头函数**
|
|
61
|
+
普通 `function(){...}` 有自己的 `arguments`,取不到暂停帧的实参。用箭头函数——它不绑定
|
|
62
|
+
自己的 `arguments`,会取到暂停帧的:`evaluate_script(frame, "() => arguments[0]")` 拿到的
|
|
63
|
+
就是传给当前帧函数的第一个实参(如传给 init 的 config)。同理 `() => u[16]` 读帧内局部。
|
|
64
|
+
|
|
65
|
+
**D. 命中无数次 / 不知道断哪**:先 `list_scripts` + `get_request_initiator` 找发起 JS,
|
|
66
|
+
再对具体脚本 URL 下 `set_breakpoint_on_text`,比全局断点精准。
|
|
67
|
+
|
|
68
|
+
**E. `scriptId` 失效**:reload/navigate/切帧后旧 id 作废 → 用脚本 URL 重新 `get_script_source`。
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 4. 与后续阶段的衔接
|
|
73
|
+
|
|
74
|
+
P1 用上述工具把 `assets/entry-report-template.txt` 填满、结论=已定位后:
|
|
75
|
+
- 拦截式实例落地 → `references/case-intercept-douyin.md`
|
|
76
|
+
- 函数式实例落地 → `references/case-function-iwencai.md`
|
|
77
|
+
- 进入 iv8 起手跑 → `references/part2-discovery.md`
|