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.
Files changed (44) hide show
  1. package/cordis.patch.yml +14 -0
  2. package/index.js +61 -0
  3. package/package.json +20 -0
  4. package/skills/iv8-env-runtime/SKILL.md +159 -0
  5. package/skills/iv8-env-runtime/assets/entry-report-template.txt +23 -0
  6. package/skills/iv8-env-runtime/assets/env_starter.py +100 -0
  7. package/skills/iv8-env-runtime/references/case-function-iwencai.md +96 -0
  8. package/skills/iv8-env-runtime/references/case-intercept-douyin.md +133 -0
  9. package/skills/iv8-env-runtime/references/config-defaults-cheatsheet.md +75 -0
  10. package/skills/iv8-env-runtime/references/cross-platform-install.md +108 -0
  11. package/skills/iv8-env-runtime/references/iv8-api-contract.md +97 -0
  12. package/skills/iv8-env-runtime/references/network-and-eventloop.md +113 -0
  13. package/skills/iv8-env-runtime/references/part1-entry-location.md +138 -0
  14. package/skills/iv8-env-runtime/references/part1-mcp-tool-map.md +77 -0
  15. package/skills/iv8-env-runtime/references/part2-discovery.md +80 -0
  16. package/skills/iv8-env-runtime/references/patching-principles.md +91 -0
  17. package/skills/iv8-env-runtime/references/trap-blacklist.md +60 -0
  18. package/skills/iv8-env-runtime/references/version-gotchas.md +128 -0
  19. package/skills/iv8-env-runtime/scripts/consistency_lint.py +211 -0
  20. package/skills/iv8-env-runtime/scripts/defaults_audit.py +143 -0
  21. package/skills/iv8-env-runtime/scripts/monitor_env.py +185 -0
  22. package/skills/iv8-env-runtime/scripts/network_bridge.py +147 -0
  23. package/skills/iv8-env-runtime/scripts/parity_check.py +175 -0
  24. package/skills/iv8-env-runtime/scripts/preflight.py +144 -0
  25. package/skills/iv8-env-runtime/scripts/reproduce_stub_error.py +165 -0
  26. package/skills/iv8-env-runtime/scripts/run_target.py +150 -0
  27. package/skills/iv8-jsvmp-recovery/SKILL.md +129 -0
  28. package/skills/iv8-jsvmp-recovery/agents/openai.yaml +4 -0
  29. package/skills/iv8-jsvmp-recovery/references/case-study-douyin-a-bogus.md +320 -0
  30. package/skills/iv8-jsvmp-recovery/references/controlled-oracle-and-equivalence.md +247 -0
  31. package/skills/iv8-jsvmp-recovery/references/static-trace-evidence.md +289 -0
  32. package/skills/iv8-jsvmp-recovery/references/vmp-domain-model.md +212 -0
  33. package/skills/iv8-jsvmp-recovery/scripts/verify-equivalence.py +281 -0
  34. package/skills/js-ast-deobfuscation/SKILL.md +140 -0
  35. package/skills/js-ast-deobfuscation/agents/openai.yaml +4 -0
  36. package/skills/js-ast-deobfuscation/references/anti-patterns.md +325 -0
  37. package/skills/js-ast-deobfuscation/references/first-pass.md +255 -0
  38. package/skills/js-ast-deobfuscation/references/interaction-and-stopping.md +758 -0
  39. package/skills/js-ast-deobfuscation/references/iv8-runtime-observation.md +773 -0
  40. package/skills/js-ast-deobfuscation/references/jsvmp-identification.md +328 -0
  41. package/skills/js-ast-deobfuscation/references/methods-and-evidence.md +460 -0
  42. package/skills/js-ast-deobfuscation/references/patterns-and-priority.md +516 -0
  43. package/skills/js-ast-deobfuscation/references/validation-contract.md +604 -0
  44. package/skills/js-ast-deobfuscation/scripts/prepare-source.mjs +921 -0
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ preflight.py — iv8 环境就绪硬前置门(阶段 P0)
5
+
6
+ 用途:
7
+ 在做任何补环境工作之前,先跑这一步。它按当前平台/架构/Python 版本判定
8
+ iv8 能否安装与运行,装错平台或版本直接硬阻断(sys.exit 非 0),并在成功时
9
+ 打印已安装的 iv8.__version__ —— 这个版本号是「单一真相源」,
10
+ references/version-gotchas.md 的所有版本门控(E1 btoa、E2 MessageChannel、
11
+ E4 WASM 等)都以此处打印的实测版本为准,不要凭记忆判断。
12
+
13
+ 用法:
14
+ python3 scripts/preflight.py
15
+ 退出码 0 = 环境就绪;非 0 = 阻断(终端会打印具体原因与修复指引)。
16
+
17
+ 约束(来自 iv8 README,实测 0.1.3):
18
+ - Windows x64 / Linux x86_64:pip 官方源可装(PyPI,Python 3.9–3.14)。
19
+ - Linux aarch64:需自查 manylinux aarch64 wheel 是否可得。
20
+ - macOS 仅 Apple Silicon(arm64) 实验版,不上 PyPI,去 GitHub Releases 下 wheel。
21
+ 支持 Python 3.11–3.14、macOS 14+。Intel Mac 或 Py<3.11 一律不支持。
22
+ - 项目强制用 uv 管理依赖(见 CLAUDE.md)。
23
+
24
+ 本文件属于 iv8-env-runtime skill。
25
+ 交叉引用:安装细节见 references/cross-platform-install.md;
26
+ 版本坑门控见 references/version-gotchas.md(消费本脚本打印的版本号)。
27
+ """
28
+
29
+ import platform
30
+ import sys
31
+
32
+ GH_RELEASES = "https://github.com/HanZzzzz000/iv8/releases"
33
+
34
+
35
+ def _py_tag():
36
+ """把 3.11 -> 'cp311',供拼 macOS wheel 名用。"""
37
+ return "cp{}{}".format(sys.version_info.major, sys.version_info.minor)
38
+
39
+
40
+ def _fail(msg):
41
+ """打印阻断原因并以非 0 退出。"""
42
+ print("\n[preflight][阻断] " + msg, file=sys.stderr)
43
+ sys.exit(2)
44
+
45
+
46
+ def _guide_install():
47
+ """按平台/架构/Python 版本给出安装指引;不支持的组合直接 _fail。
48
+
49
+ 返回 True 表示「本平台应当已经能 import iv8」(走 PyPI 或已下载的 wheel),
50
+ 仅用于决定后续冒烟测试的措辞。
51
+ """
52
+ system = platform.system()
53
+ machine = platform.machine().lower()
54
+ py = sys.version_info
55
+
56
+ print("[preflight] 平台检测:system={} machine={} python={}.{}.{}".format(
57
+ system, machine, py.major, py.minor, py.micro))
58
+
59
+ # ---- Windows ----
60
+ if system == "Windows":
61
+ if machine not in ("amd64", "x86_64"):
62
+ _fail("Windows 仅支持 x64(当前 machine={}),无预编译 wheel。".format(machine))
63
+ if not (3, 9) <= (py.major, py.minor) <= (3, 14):
64
+ _fail("iv8 支持 Python 3.9–3.14,当前 {}.{} 不在范围内。".format(py.major, py.minor))
65
+ print("[preflight] Windows x64:uv pip install iv8 (PyPI 官方源)")
66
+ return True
67
+
68
+ # ---- Linux ----
69
+ if system == "Linux":
70
+ if machine in ("x86_64", "amd64"):
71
+ if not (3, 9) <= (py.major, py.minor) <= (3, 14):
72
+ _fail("iv8 支持 Python 3.9–3.14,当前 {}.{} 不在范围内。".format(py.major, py.minor))
73
+ print("[preflight] Linux x86_64:uv pip install iv8 (PyPI,manylinux)")
74
+ return True
75
+ if machine in ("aarch64", "arm64"):
76
+ # aarch64 wheel 是否发布随版本变化,不写死结论,交给用户核对。
77
+ print("[preflight] Linux aarch64:README 明确的官方轮子是 x64 manylinux。")
78
+ print(" 请自查 PyPI/Releases 是否提供 manylinux aarch64 wheel:")
79
+ print(" uv pip install iv8 # 若报 no matching distribution 即无 aarch64 轮")
80
+ print(" 若确无 aarch64 wheel,则当前架构暂无本地安装路径。")
81
+ return False
82
+ _fail("Linux 未知架构 machine={},无对应 wheel。".format(machine))
83
+
84
+ # ---- macOS ----
85
+ if system == "Darwin":
86
+ if machine != "arm64":
87
+ _fail("macOS 仅支持 Apple Silicon(arm64) 实验版;当前 machine={}(Intel Mac 不支持)。".format(machine))
88
+ if not (3, 11) <= (py.major, py.minor) <= (3, 14):
89
+ _fail("macOS 实验版仅支持 Python 3.11–3.14(且需 macOS 14+);当前 {}.{} 不支持。".format(
90
+ py.major, py.minor))
91
+ tag = _py_tag()
92
+ wheel = "iv8-<ver>-{tag}-{tag}-macosx_14_0_arm64.whl".format(tag=tag)
93
+ print("[preflight] macOS Apple Silicon 实验版(不上 PyPI):")
94
+ print(" 1) 去 GitHub Releases 下载对应 wheel:{}".format(GH_RELEASES))
95
+ print(" 文件名形如(<ver> 用实际版本替换,勿写死):{}".format(wheel))
96
+ print(" 2) 用 uv 安装:uv pip install ./{}".format(wheel))
97
+ return False # 是否已装取决于用户是否下载过,冒烟测试会给出真实结论
98
+
99
+ _fail("未识别的操作系统 system={}。".format(system))
100
+
101
+
102
+ def _smoke():
103
+ """import iv8 冒烟:ctx.eval('1+1')==2 且 navigator.webdriver==False,并打印版本号。"""
104
+ try:
105
+ import iv8
106
+ except Exception as e: # noqa: BLE001 —— 需要把任何导入失败都转成清晰指引
107
+ _fail("import iv8 失败:{}\n"
108
+ " 说明 iv8 尚未安装或安装的 wheel 与当前平台/Python 不匹配。\n"
109
+ " 按上方安装指引用 uv 安装对应 wheel 后重跑本脚本。".format(e))
110
+
111
+ version = getattr(iv8, "__version__", "<未知>")
112
+
113
+ try:
114
+ with iv8.JSContext() as ctx:
115
+ one_plus_one = ctx.eval("1 + 1")
116
+ webdriver = ctx.eval("navigator.webdriver")
117
+ except Exception as e: # noqa: BLE001
118
+ _fail("iv8 已导入但 JSContext 冒烟失败:{}".format(e))
119
+
120
+ if one_plus_one != 2:
121
+ _fail("冒烟异常:ctx.eval('1 + 1') 返回 {!r}(应为 2)。".format(one_plus_one))
122
+ if webdriver is not False:
123
+ _fail("冒烟异常:navigator.webdriver 返回 {!r}(应为 False)。".format(webdriver))
124
+
125
+ # ★ 单一真相源:把实测版本号打印出来,供 version-gotchas 门控消费。
126
+ print("\n[preflight][通过] iv8 冒烟成功。")
127
+ print("[preflight] iv8.__version__ = {}".format(version))
128
+ print("[preflight] ↑ 该版本号是版本门控唯一真相;references/version-gotchas.md")
129
+ print(" 按此判断 E1(btoa)/E2(MessageChannel)/E4(WASM) 等坑是否已修,勿凭记忆。")
130
+ return version
131
+
132
+
133
+ def main():
134
+ print("=" * 64)
135
+ print("iv8-env-runtime · P0 preflight(环境就绪硬前置门)")
136
+ print("=" * 64)
137
+ _guide_install()
138
+ _smoke()
139
+ print("\n[preflight] P0 通过,可进入 P1(找入口)。")
140
+ return 0
141
+
142
+
143
+ if __name__ == "__main__":
144
+ sys.exit(main())
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """reproduce_stub_error.py —— P2 定位 stub 空壳窟窿:跑目标直到抛 TypeError,压缩打印异常栈。
4
+
5
+ 作用(对应 SKILL.md 的 P2「TypeError 栈=stub 窟窿」):
6
+ iv8 对很多浏览器 API 给了「接口级 stub」——存在但内部为空壳(典型:
7
+ MessageChannel 构造出来 port1/port2 为 null)。目标 JS 一旦真去用它(如
8
+ core-js 的 microtask 降级链会 new MessageChannel() 再取 port.postMessage),
9
+ 就在运行时抛 TypeError。本脚本分步 eval 目标(load → init → 触发),
10
+ 第一处抛错即捕获,并把冗长的混淆源码行压掉,只留「异常类型 + 位置摘要」,
11
+ 再打印一个「用 wrapNative 替换 stub」的提示模板。
12
+
13
+ 这是「三态」里的中间态:stub 空壳(替换)——区别于「真实现(别动)」与
14
+ 「陷阱缺失(绝不补)」。stub 的判定证据就是这里的 TypeError 栈。
15
+
16
+ 关系与边界:
17
+ - 上游:scripts/monitor_env.py 第⑥类「疑似缺失 API」提示哪里可能有窟窿;
18
+ 本脚本用「真跑抛错」坐实是不是 stub 空壳(缺失=没有该 API;stub=有但为空壳)。
19
+ - 下游:确认是 stub → 按 references/patching-principles.md 的 iv8 原生补丁契约,
20
+ 用 __iv8__.wrapNative 写一个有真机证据的最小替身。
21
+ 本脚本只复现 MessageChannel / Worker 的 core-js 降级链与 iv8 调度问题。
22
+ - MessageChannel/Worker null port 属已知坑 E2,版本门控见 references/version-gotchas.md。
23
+ - 权威 API 契约见 references/iv8-api-contract.md。
24
+
25
+ 用法:
26
+ python reproduce_stub_error.py <目标JS路径> [--init <init表达式文件>] [--trigger <触发表达式文件>]
27
+ [--env <environment JSON文件>] [--patch <预置替身JS文件>]
28
+
29
+ - 不给 --patch:复现原始 TypeError(先看窟窿在哪)
30
+ - 给 --patch:把替身 JS 先注入再跑(验证替换后是否跑通),
31
+ --patch 文件内应是一段用 __iv8__.wrapNative 写的替身(示例见文末提示模板)。
32
+ """
33
+
34
+ import argparse
35
+ import json
36
+ import os
37
+ import re
38
+ import sys
39
+
40
+ try:
41
+ import iv8
42
+ except ImportError:
43
+ sys.exit("未找到 iv8。先跑 scripts/preflight.py 确认安装(安装见 references/cross-platform-install.md)。")
44
+
45
+
46
+ # wrapNative 替换 stub 的提示模板(以 MessageChannel 为例,别照抄值,按目标改)
47
+ WRAPNATIVE_HINT = """\
48
+ [提示] 若确认是 stub 空壳(如 MessageChannel port=null),用 __iv8__.wrapNative 替换:
49
+
50
+ window.MessageChannel = __iv8__.wrapNative(function() {
51
+ var p1 = { onmessage: null }, p2 = { onmessage: null };
52
+ p1.postMessage = function(d) {
53
+ if (p2.onmessage) setTimeout(function(){ p2.onmessage({ data: d }); }, 0);
54
+ };
55
+ p2.postMessage = function(d) {
56
+ if (p1.onmessage) setTimeout(function(){ p1.onmessage({ data: d }); }, 0);
57
+ };
58
+ return { port1: p1, port2: p2 };
59
+ }, 'MessageChannel');
60
+
61
+ 要点:
62
+ - wrapNative 让替身 toString() 显示 [native code],降低可观测差异。
63
+ - 触发后需 __iv8__.eventLoop.advance(...) / drain() 排空 setTimeout 回调(微任务降级链)。
64
+ - 三态:stub=替换 / 真实现=别动 / 陷阱缺失=绝不补(见 references/patching-principles.md)。
65
+ - 完整替身必须按目标证据最小实现,并按 references/patching-principles.md 补后对账。
66
+ """
67
+
68
+
69
+ def summarize_exception(exc, limit=1200):
70
+ """压缩 iv8 异常字符串:混淆目标的源码行可能是整份几十 KB bundle,这里裁掉只留位置。"""
71
+ text = str(exc) or repr(exc)
72
+ text = re.sub(r"\s+", " ", text).strip()
73
+ # iv8 异常串常形如 "... ( @ <line> : <col> ) -> <整行源码>",裁掉 -> 之后
74
+ m = re.match(r"^(.*?\( @ \d+ : \d+ \))\s*->", text)
75
+ if m:
76
+ return m.group(1)
77
+ if len(text) > limit:
78
+ return text[:limit] + " ...<truncated>"
79
+ return text
80
+
81
+
82
+ def run_step(ctx, label, source):
83
+ """执行一步,成功返回 (True, value),抛错则打印压缩摘要返回 (False, None)。"""
84
+ print(f"\n== {label} ==")
85
+ try:
86
+ value = ctx.eval(source, name=label, to_py=True)
87
+ except Exception as exc: # noqa: BLE001 —— 就是要捕获任意运行时异常
88
+ print("FAILED")
89
+ print(" exception type :", type(exc).__name__)
90
+ print(" exception summary:", summarize_exception(exc))
91
+ return False, None
92
+ print("OK")
93
+ return True, value
94
+
95
+
96
+ def main():
97
+ ap = argparse.ArgumentParser(description="跑目标直到抛 TypeError,压缩打印,定位 stub 空壳窟窿")
98
+ ap.add_argument("js_path", help="目标 JS 文件路径")
99
+ ap.add_argument("--init", default="", help="加载后的初始化表达式文件(如 xxx.init({...}))")
100
+ ap.add_argument("--trigger", default="", help="触发签名/取数的表达式文件")
101
+ ap.add_argument("--env", default="", help="environment 画像 JSON 文件路径")
102
+ ap.add_argument("--patch", default="", help="预置替身 JS 文件(先注入再跑,验证替换是否跑通)")
103
+ args = ap.parse_args()
104
+
105
+ if not os.path.isfile(args.js_path):
106
+ sys.exit(f"目标 JS 不存在: {args.js_path}")
107
+
108
+ env = None
109
+ if args.env:
110
+ with open(args.env, "r", encoding="utf-8") as f:
111
+ env = json.load(f)
112
+
113
+ with open(args.js_path, "r", encoding="utf-8") as f:
114
+ js = f.read()
115
+
116
+ def read_opt(path):
117
+ if not path:
118
+ return ""
119
+ with open(path, "r", encoding="utf-8") as fh:
120
+ return fh.read()
121
+
122
+ patch_src = read_opt(args.patch)
123
+ init_src = read_opt(args.init)
124
+ trigger_src = read_opt(args.trigger) or "window.__iv8__.eventLoop.drain();"
125
+
126
+ print("目标 JS :", args.js_path)
127
+ print("JS 字节 :", len(js.encode("utf-8")))
128
+ print("预置替身 :", "有(--patch)" if patch_src else "无(复现原始报错)")
129
+
130
+ # prod 模式即可复现(不需要 debug 监控开销)
131
+ with iv8.JSContext(environment=env) as ctx:
132
+ if patch_src:
133
+ ok, _ = run_step(ctx, "inject patch (wrapNative 替身)", patch_src)
134
+ if not ok:
135
+ print("\n替身注入本身就抛错,先修替身。")
136
+ print(WRAPNATIVE_HINT)
137
+ return
138
+
139
+ ok, _ = run_step(ctx, "eval target", js)
140
+ if not ok:
141
+ print(WRAPNATIVE_HINT)
142
+ return
143
+
144
+ if init_src:
145
+ ok, _ = run_step(ctx, "init", init_src)
146
+ if not ok:
147
+ print(WRAPNATIVE_HINT)
148
+ return
149
+
150
+ ok, val = run_step(ctx, "trigger + drain", trigger_src)
151
+ if not ok:
152
+ print(WRAPNATIVE_HINT)
153
+ return
154
+
155
+ print("\n全部步骤跑通,未复现 stub 报错。")
156
+ if patch_src:
157
+ print("→ 替身有效:该 stub 窟窿已补上。")
158
+ else:
159
+ print("→ 目标此路径未触发 stub 窟窿(或缺口在别处)。")
160
+ if isinstance(val, list):
161
+ print(f"trigger 返回条目数: {len(val)}")
162
+
163
+
164
+ if __name__ == "__main__":
165
+ main()
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ run_target.py — iv8 标准 runner(阶段 P3,内联 drain-before-read 断言)
5
+
6
+ 用途:
7
+ 用最小的确定性流程把目标反爬 JS 在 iv8 里跑起来并收割签名/token:
8
+ 建 JSContext(environment=...) → 载入原始 JS → 求值入口表达式 → 收割结果。
9
+ 它把一条容易被忘、忘了就得到「空 netLog / 半成品 token」的铁律固化进代码:
10
+ ★ 读取 netLog.entries / 任何异步产物之前,必须先 eventLoop.drain() 推进事件循环。
11
+ 本脚本用 _read_netlog() 包装强制「先 drain 再读」,任何绕过它直接读 netLog
12
+ 的写法在本 runner 里会被断言拦下。
13
+
14
+ deploy 绑定值(aid / script id / bdms 版本 / u 数组 / golden sum 等)一律不写死,
15
+ 全部从函数参数或外部文件传入,运行时解析。
16
+
17
+ 用法(库调用,推荐):
18
+ from run_target import run
19
+ result = run(
20
+ target_js_path="target.js",
21
+ environment={...}, # 见 assets/env_starter.py 起手模板
22
+ entry_expr="window.getSign('/api/x')", # P1 定位到的真实生成入口
23
+ need_network=False,
24
+ )
25
+ # result = {"value": <入口返回值>, "netlog": [...已 drain 后的请求...]}
26
+
27
+ 用法(命令行):
28
+ python3 scripts/run_target.py target.js env.json "window.getSign('/api/x')"
29
+
30
+ 本文件属于 iv8-env-runtime skill。
31
+ 交叉引用:
32
+ - 入口表达式 entry_expr 怎么定位见 references/part1-entry-location.md;
33
+ - environment 该配什么见 references/config-defaults-cheatsheet.md 与
34
+ assets/env_starter.py;
35
+ - stub 窟窿(MessageChannel/Worker 等)的 iv8 原生替换契约见
36
+ references/patching-principles.md;
37
+ - 真实网络桥接(need_network=True 的四步握手)见 scripts/network_bridge.py。
38
+ """
39
+
40
+ import json
41
+ import sys
42
+
43
+
44
+ class DrainError(RuntimeError):
45
+ """未先 drain 事件循环就读取异步产物时抛出。"""
46
+
47
+
48
+ def _new_context(environment, config=None, time_mode="logical"):
49
+ """创建一个最小配置的 JSContext。import 失败给清晰指引(先跑 preflight)。"""
50
+ try:
51
+ import iv8
52
+ except Exception as e: # noqa: BLE001
53
+ raise RuntimeError(
54
+ "import iv8 失败:{};请先跑 scripts/preflight.py 确认 P0 环境就绪。".format(e))
55
+ return iv8.JSContext(environment=environment or {}, config=config or None,
56
+ time_mode=time_mode)
57
+
58
+
59
+ def _read_netlog(ctx, drained_flag):
60
+ """★ 唯一允许的 netLog 读取通道:调用前必须已 drain。
61
+
62
+ drained_flag 是一个单元素 list(可变引用),run() 在调用 drain 后置 True。
63
+ 若为 False 直接读 netLog,抛 DrainError —— 把「先 drain 再读」变成硬约束,
64
+ 而不是一句注释里的提醒。
65
+ """
66
+ if not drained_flag[0]:
67
+ raise DrainError(
68
+ "读取 netLog.entries 之前未调用 eventLoop.drain():异步 XHR/fetch 可能尚未落账,"
69
+ "会读到空或半成品。请先 ctx.eval(\"window.__iv8__.eventLoop.drain()\") 再读。")
70
+ return ctx.eval("window.__iv8__.netLog.entries", to_py=True)
71
+
72
+
73
+ def run(target_js_path, environment, entry_expr,
74
+ need_network=False, config=None, time_mode="logical",
75
+ stub_setup_js=None, drain_max=None):
76
+ """跑目标 JS 并收割结果。
77
+
78
+ 参数:
79
+ target_js_path : 原始目标 JS 文件路径(未改写)。
80
+ environment : iv8 画像 dict(location.href 通常必配,默认 about:blank)。
81
+ entry_expr : P1 定位到的真实「生成」入口表达式,其返回值即要收割的产物。
82
+ need_network : 目标是否会发真实请求。社区版无真实网络栈,True 时请改用
83
+ scripts/network_bridge.py 的桥接流程;这里仅收 netLog 便于观察。
84
+ config : 引擎行为标量(timezone/permissions.* 等),可选。
85
+ time_mode : 'logical'(默认)或 'system'(POW/时间差校验时用)。
86
+ stub_setup_js : 可选,载入目标前先注入的 stub 替换/wrapNative 补丁 JS 字符串。
87
+ 补丁应依据目标证据最小实现;本 runner 不内置固定清单。
88
+ drain_max : 传给 eventLoop.drain(max) 的上限,None=排空全部已到期任务。
89
+
90
+ 返回:
91
+ {"value": <entry_expr 求值结果>, "netlog": [...]}
92
+ """
93
+ with open(target_js_path, "r", encoding="utf-8") as f:
94
+ js_code = f.read()
95
+
96
+ drained_flag = [False] # 可变引用,drain 后置 True,给 _read_netlog 做硬断言
97
+ ctx = _new_context(environment, config=config, time_mode=time_mode)
98
+ try:
99
+ # 1) 载入目标之前先打有证据的最小 stub 补丁(如 MessageChannel/Worker 替换)
100
+ if stub_setup_js:
101
+ ctx.eval(stub_setup_js, name="stub_setup")
102
+
103
+ # 2) 载入原始目标 JS(不改写)
104
+ ctx.eval(js_code, name=target_js_path)
105
+
106
+ # 3) 求值真实生成入口,拿返回值
107
+ value = ctx.eval(entry_expr, name="entry_expr", to_py=True)
108
+
109
+ # 4) ★ 读任何异步产物(netLog)之前,强制 drain 推进事件循环
110
+ if drain_max is None:
111
+ ctx.eval("window.__iv8__.eventLoop.drain()")
112
+ else:
113
+ ctx.eval("window.__iv8__.eventLoop.drain({})".format(int(drain_max)))
114
+ drained_flag[0] = True
115
+
116
+ netlog = _read_netlog(ctx, drained_flag)
117
+
118
+ return {"value": value, "netlog": netlog}
119
+ finally:
120
+ try:
121
+ ctx.close()
122
+ except Exception: # noqa: BLE001 —— 关闭失败不应掩盖主流程结果
123
+ pass
124
+
125
+
126
+ def _load_json(path):
127
+ with open(path, "r", encoding="utf-8") as f:
128
+ return json.load(f)
129
+
130
+
131
+ def main(argv):
132
+ if len(argv) < 4:
133
+ print('用法:python3 run_target.py target.js env.json "<entry_expr>"', file=sys.stderr)
134
+ return 2
135
+ target_js = argv[1]
136
+ environment = _load_json(argv[2])
137
+ entry_expr = argv[3]
138
+
139
+ result = run(target_js, environment, entry_expr)
140
+ print("[run_target] 入口返回值:")
141
+ print(json.dumps(result["value"], ensure_ascii=False, indent=2, default=str))
142
+ print("[run_target] netLog 条目数(已 drain):{}".format(len(result["netlog"])))
143
+ for e in result["netlog"]:
144
+ if isinstance(e, dict):
145
+ print(" {} {}".format(e.get("method", ""), e.get("url", "")))
146
+ return 0
147
+
148
+
149
+ if __name__ == "__main__":
150
+ sys.exit(main(sys.argv))
@@ -0,0 +1,129 @@
1
+ ---
2
+ name: iv8-jsvmp-recovery
3
+ description: >-
4
+ 以受控 iv8 中的原始 JavaScript/JSVMP 作为参考实现,结合静态解释器、Bytecode、Handler、Opcode、Record、CFG 分析和动态 Trace,恢复从声明的原始输入到最终签名、Token 或密文的独立可执行映射,并以精确等价验收。用于 JSVMP/VMP 语义恢复、字节码反汇编或反编译、Handler 准确语义、Record/虚拟函数边界、调用图或控制流、字符串恢复、静态结果与 Trace 对账、受控输入试验、以及 Python/JavaScript 独立实现的端到端复现。不负责通用外层 AST 解混淆、iv8 安装与通用补环境,也不把服务器接受当作 VM 语义等价的唯一证明。
5
+ ---
6
+
7
+ # iv8 JSVMP Recovery
8
+
9
+ ## 主线
10
+
11
+ 始终围绕同一个目标展开分析:
12
+
13
+ > 以受控 iv8 中的原始程序作为参考实现,结合静态分析和动态 Trace,恢复从声明的原始输入到最终输出的独立可执行映射,并以精确等价作为验收标准。
14
+
15
+ 把 Handler 命名、Record 拆分、CFG、字符串解密、Trace 和中间值对账都视为恢复这个映射的证据或手段,不把它们自身当作最终目标。
16
+
17
+ ## 正确性定义
18
+
19
+ 先声明原始输入合同:
20
+
21
+ ```text
22
+ RawInput =
23
+ request
24
+ + environment
25
+ + behavior
26
+ + time
27
+ + randomness
28
+ + configuration
29
+ + initialState
30
+
31
+ OracleOutput = OriginalVMP_in_Controlled_iv8(RawInput)
32
+ RecoveredOutput = IndependentImplementation(RawInput)
33
+
34
+ 验收:RecoveredOutput === OracleOutput
35
+ ```
36
+
37
+ 把 `===` 解释为声明编码下的长度相同且逐字节或逐字符完全一致。不用“结构差不多”、“可以解密”、“服务器接受”或“某个中间值相同”代替这个命题。
38
+
39
+ 区分结论范围:
40
+
41
+ - 固定基线精确相等,证明该受控执行实例已被复现。
42
+ - 受控变体和静态全分支证据,用于扩大可声明的语义范围。
43
+ - 不要把一个固定实例的成功推广为所有环境、状态和未执行路径的函数等价。
44
+
45
+ ## 原始输入边界
46
+
47
+ 从宿主可观测事实开始建模。允许输入请求、环境属性、原始行为事件或初始队列状态、时间源、随机源、SDK 配置、存储/网络响应和共享初始状态。
48
+
49
+ 不把下列 VM 派生值伪装为原始输入:
50
+
51
+ - 已计算的摘要、风险位、特征向量或行为编码;
52
+ - 已组装的 core、header、prefix、checksum 或 material;
53
+ - 预计算的密钥流、密文、随机打包结果或最终签名。
54
+
55
+ 要求独立实现自己从 `RawInput` 计算这些派生值。
56
+
57
+ ## 领域判断
58
+
59
+ 保持以下区分:
60
+
61
+ - Handler 是解释器操作实现,Opcode 是程序数据中的编码,两者不必然一一对应。
62
+ - Record 是样本定义的 VM 程序单元,不天然等于源码函数、调用帧或基本块。
63
+ - 机械语义描述操作数、栈/寄存器、PC、Frame、Scope 和副作用;业务语义还需要连接真实输入、消费者和最终输出。
64
+ - 静态分析描述可能的分支和数据依赖;动态 Trace 证明某一受控输入下真实执行的路径和值。
65
+ - 使用 Trace 对账、反驳和定位首个分歧;不把一次 Trace 当作全路径证明。
66
+ - 只在字符串是加密/编码常量且恢复后能连接当前映射时解密;不把字节码数据、字符串索引或普通字面量统一视为“待解密字符串”。
67
+
68
+ ## 分析自由度
69
+
70
+ 根据证据自行选择正向、反向或交替分析;根据问题选择局部切片、主线 CFG 或全库 CFG;根据现有条件调整静态与动态的比例。
71
+
72
+ 不强制:
73
+
74
+ - 先恢复所有 Handler;
75
+ - 先反编译全部 Bytecode;
76
+ - 先建立完整 CFG 或调用图;
77
+ - 必须从入口正向或从输出反向;
78
+ - 把某个案例的分析顺序复制到其他 VM。
79
+
80
+ 根据当前声明的输出和证据缺口收束范围。如果局部主线已经足以从原始输入精确复现最终输出,不要为了形式完整继续扩展不影响该命题的全库细节。
81
+
82
+ ## 不可接受的伪正确
83
+
84
+ 不要作出以下完成声明:
85
+
86
+ - 写死最终输出或只复现一个常量字符串;
87
+ - 公开入口仍然接收 VM 预计算中间字段;
88
+ - 只因服务器接受请求就声称本地计算与原 VM 等价;
89
+ - 只因 Trace 中数值相同就填入未证明的业务名称;
90
+ - 把 Handler、Opcode、Record 或 Trace 覆盖数量直接当作源码函数数量;
91
+ - 修改原始程序或执行环境后,仍把结果冒充为原始 Oracle;
92
+ - 没有声明时间、随机、存储、网络、初始化顺序和共享状态。
93
+
94
+ ## 与相邻 Skill 的边界
95
+
96
+ - 使用 `js-ast-deobfuscation` 处理外层 JavaScript、打包器、字符串隐藏、解释器宿主边界和 JSVMP 识别。
97
+ - 使用 `iv8-env-runtime` 完成 iv8 安装、通用补环境、网络/事件循环接续和原始程序的稳定执行。
98
+ - 在本 Skill 中消费已确认的 VM 边界和受控 Oracle,恢复 VM 内部映射并构建独立实现。
99
+
100
+ 原始程序尚不能受控复现时,先建立 Oracle 条件;尚未确认 VM 闭环时,先完成外层识别。不在本 Skill 中重写相邻 Skill 的工具和环境知识。
101
+
102
+ ## 证据与产物
103
+
104
+ 记录并持续区分:
105
+
106
+ - 原始程序、Bytecode、配置和 `RawInput` 的版本与 Hash;
107
+ - 已证明的机械语义、有证据的业务解释、推断和未知;
108
+ - Oracle 运行条件、原始输出、Trace 覆盖和观测扰动;
109
+ - 独立实现的输入合同、中间对账点和最终等价报告;
110
+ - 完成声明的适用输入范围和未覆盖边界。
111
+
112
+ 不要强制统一产物文件名;优先沿用项目已有证据目录、坐标和格式。
113
+
114
+ ## Reference 路由
115
+
116
+ 只由本文件路由 reference;reference 不再继续路由其他 reference。
117
+
118
+ - 需要判断 Handler、Opcode、Record、Frame、Scope、VM 函数、基本块、CFG 或 VM 字符串时,读取 [vmp-domain-model.md](references/vmp-domain-model.md)。
119
+ - 需要建立受控 iv8 基线、定义 `RawInput`、区分派生字段、设计变体或声明精确等价时,读取 [controlled-oracle-and-equivalence.md](references/controlled-oracle-and-equivalence.md)。
120
+ - 需要结合静态与 Trace、确定证据范围、评估 Handler 语义或定位首个分歧时,读取 [static-trace-evidence.md](references/static-trace-evidence.md)。
121
+ - 当前对象是课程中的抖音 `a_bogus` 样本,或需要查看完整成功案例的证据结构时,读取 [case-study-douyin-a-bogus.md](references/case-study-douyin-a-bogus.md)。
122
+
123
+ 只读取当前问题需要的 reference。案例可用于提出假设,不能覆盖当前样本证据。
124
+
125
+ ## 精确等价脚本
126
+
127
+ 在声明固定输入精确等价时,执行 `scripts/verify-equivalence.py --help`,再用它对 `RawInput`、Oracle 输出和独立实现输出生成机器可读报告。
128
+
129
+ 该脚本只证明给定字节是否精确相等,不判断输入是否真正原始、实现是否独立,也不把单样本相等推广为全输入空间等价。
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "iv8 JSVMP Recovery"
3
+ short_description: "以受控 iv8 Oracle 恢复并精确验证 JSVMP 语义"
4
+ default_prompt: "Use $iv8-jsvmp-recovery to recover this JSVMP from declared raw inputs to an independently reproducible final output."