feat(VM): 吃透 Chaos VM 程序结构层 —— 模块注册表 + collect 槽位表可静态导出
补上 VM_SPEC §5/§6 自己点出的缺口("不是 VM 的问题, 而是模块系统 + 宿主调用序列")。
新增 tools/tdc_program.py(程序结构分析器):
* closures / entries / 字面串折叠 / 边界感知的函数体切分
* module_registry(): 识别顶层 "registry[id] = factory" 装配模式 -> 62 个模块工厂
* call_graph / shared_bodies / find_strings / --func 导出自身指令
修掉两个真 bug:
* tdc_disasm: RET_TRUE / THROW 未当终结符 -> 控制流穿过函数结尾线性走下去,
把父函数续段误判给夹在中间的兄弟函数(@4940 曾误归 @4362, 实为 @4160)
* tdc_lift: 终结符判定写死 opcode 数值 19/49 -> 改为按语义名
新增 tools/tdc_layout.py(静态导出 cd 槽位表):
装配链已完全厘清:
顶层 = 62 个模块工厂的注册表(工厂形参 (module, exports, require))
-> 清单模块 arr[arr.length] = require(N) <<< 这串 require 的顺序就是 cd 槽位序
-> 分发模块按 .on/.reset/.get 分三数组
-> 装配模块 r3='{"cd":[' + for(...) r3 += r8.get() + '],"sd":{...}'
跨构建实测: core_js 的 require 串与线上构建的串**不同**(槽位序按构建随机化, 与 #31 一致);
语义对位已验证若干槽(userAgent->cd[3], href->cd[4], languages->cd[5] 命中)。
VM_SPEC 新增 §9 程序结构层(模块系统 / 入口 / collect 装配 / 为何决定 12)。
selftest 全绿。
This commit is contained in:
@@ -264,3 +264,69 @@ E.push(function g() {
|
||||
2. 与 `extract_xtea_key`(Node 神谕)逐字对齐后,替换掉最后一个 Node 依赖;
|
||||
3. 把 lift 的栈模型补全(`SWAP/SET_STACK_LEN/REPLACE_TOP_IMM` 的表达式还原),消除残留的 `r<?>`。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 9. 程序结构层:模块系统 / 入口 / collect 装配(2026-10-11 补,工具 `tools/tdc_program.py`)
|
||||
|
||||
§5/§6 曾把缺口写成「不是 VM 的问题,而是模块系统 + 宿主调用序列」。本节把它补成确定规格。
|
||||
|
||||
### 9.1 顶层就是「模块注册表装配」
|
||||
|
||||
```
|
||||
@0000: SET_STACK_LEN 3; ALLOC_REG 2; JUMP @1518
|
||||
@1518: r3 = CREATE_CLOSURE(target=@7, params=[p0->r3]) // VM 外层包装
|
||||
...
|
||||
@1538: PUSH_IMM [0]; REVERSE_TOP2; JUMP -> @2085
|
||||
@2085: CREATE_CLOSURE(target=@1544, params=[p0->r3,p1->r4,p2->r5]); SET_PROP // registry[0] = 工厂@1544
|
||||
@2096: PUSH_IMM [1]; REVERSE_TOP2; JUMP -> @2914
|
||||
@2914: CREATE_CLOSURE(target=@2102, ...); SET_PROP // registry[1] = 工厂@2102
|
||||
... // 共 62 个
|
||||
```
|
||||
|
||||
即源码级的 `registry[id] = function (module, exports, require) { ... }`(CommonJS)。
|
||||
**模块工厂的形参固定为 (r3=module, r4=exports, r5=require)**;模块体内 `r5(N)` 就是 `require(N)`。
|
||||
|
||||
> `tools/tdc_program.py --modules` 直接导出这张表。
|
||||
|
||||
### 9.2 代码布局的坑(把工具改对了)
|
||||
|
||||
- 函数体是**不连续**的:父函数的初始化序列与子函数体交错排布,父函数用 `JUMP` 跳过子函数体。
|
||||
所以「按 pc 大小猜归属」全错(实测把父函数续段误判给夹在中间的兄弟函数)。
|
||||
正确做法:**边界感知走法** —— 从入口走控制流,遇到**另一个函数入口**就停下。
|
||||
- 反汇编器曾漏掉终结符:`RET_TRUE`/`THROW` 之后**没有 fall-through**,否则控制流会穿过函数结尾
|
||||
线性走下去,把后续所有代码都算进当前函数。已修(按语义名定位,不写死 opcode 数值)。
|
||||
|
||||
### 9.3 collect 的装配链(`{"cd":[...],"sd":{...}}` 是怎么出来的)
|
||||
|
||||
1. **清单模块**:某个模块返回一个数组,构造方式是接连的
|
||||
|
||||
```
|
||||
DUP?; PUSH_IMM_ARRAY [6]; "length" ... ; LOAD_REG_IMM [5]; PUSH_IMM [N]; CALL_FUNC [1]; SET_PROP
|
||||
```
|
||||
|
||||
语义即 `arr[arr.length] = require(N)`。**这串 require 的顺序就是 `cd` 的槽位顺序。**
|
||||
(core_js 那份:37 个 require + 1 个字面量槽;线上构建实测是**另一串** id。)
|
||||
2. **分发模块**:一个闭包把清单按 `.on` / `.reset` / `.get` 分进三个数组
|
||||
(`caps=[r5=r9,r6=r12,r7=r13,r8=r14]`,即 出=r12/r13/r14)。
|
||||
3. **装配模块**:`r3 = '{"cd":['`,然后
|
||||
|
||||
```
|
||||
for (r7 = 0; r7 < r15.length; r7++) { r8 = r15[r7]; v = r8.get(); ... 追加 r3 ... }
|
||||
r3 += '],'; ... r4["cd"] = r3
|
||||
```
|
||||
|
||||
即**按数组顺序**调每个采集器 `.get()` 并序列化 → `cd` 槽位序 = 清单数组顺序。
|
||||
|
||||
### 9.4 为什么这件事决定 errorCode=12
|
||||
|
||||
- 槽位序**按构建随机化**(5 份同日样本 5/5 互不相同,见 `har/1ms_dianxuan/PROGRESS.md` #31)。
|
||||
- 仓库里的 `models/success_collect_template.b64` 是**某一次**的顺序
|
||||
(其指纹与 `签到成功-1.har` 那一次逐槽一致,#32)。
|
||||
- 拿旧顺序发的 collect,会被服务端按**本次构建**的槽位表解释成「自相矛盾的环境」(例如
|
||||
`cd[1]` 该是 `[360,360]` 却是 `"MacIntel"`)→ 风控判 12。
|
||||
- 这正是「换 IP / 换遥测后端 / 换传输指纹 / 换 cookie / 长静默全都无效」的原因:
|
||||
它们**都不动槽位序**。
|
||||
|
||||
> 因此纯算路线必须**按本次构建**导出槽位表(`tools/tdc_layout.py`),
|
||||
> 并用**本次 prehandle 的 sid** 去取那份 tdc.js(真机 URL 还多带 `appid=<aid>`)。
|
||||
|
||||
@@ -3696,7 +3696,6 @@
|
||||
@06336: CALL_METHOD [1]
|
||||
@06338: POP_TRY
|
||||
@06339: RET_TRUE
|
||||
@06340: JUMP target=@6564
|
||||
@06343: ALLOC_REG [9]
|
||||
@06345: PUSH_TRY catchTarget=@6274 catchVarReg=r9
|
||||
@06348: PUSH_EMPTY_STR
|
||||
@@ -7430,7 +7429,6 @@
|
||||
@12694: PUSH_IMM [0]
|
||||
@12696: POP_TRY
|
||||
@12697: RET_TRUE
|
||||
@12698: JUMP target=@12789
|
||||
@12701: ALLOC_REG [5]
|
||||
@12703: PUSH_TRY catchTarget=@12690 catchVarReg=r5
|
||||
@12706: PUSH_IMM_ARRAY [3]
|
||||
@@ -7620,7 +7618,6 @@
|
||||
@13033: PUSH_STR_CHAR [112]
|
||||
@13035: POP_TRY
|
||||
@13036: RET_TRUE
|
||||
@13037: JUMP target=@13357
|
||||
@13040: ALLOC_REG [6]
|
||||
@13042: PUSH_TRY catchTarget=@13024 catchVarReg=r6
|
||||
@13045: PUSH_IMM_ARRAY [3]
|
||||
@@ -8358,7 +8355,6 @@
|
||||
@14327: PUSH_TRUE
|
||||
@14328: POP_TRY
|
||||
@14329: RET_TRUE
|
||||
@14330: JUMP target=@14518
|
||||
@14333: ALLOC_REG [5]
|
||||
@14335: PUSH_TRY catchTarget=@14323 catchVarReg=r5
|
||||
@14338: PUSH_IMM_ARRAY [3]
|
||||
@@ -9904,7 +9900,6 @@
|
||||
@17076: PUSH_UNDEFINED
|
||||
@17077: POP_TRY
|
||||
@17078: RET_TRUE
|
||||
@17079: JUMP target=@17101
|
||||
@17082: ALLOC_REG [11]
|
||||
@17084: PUSH_TRY catchTarget=@17072 catchVarReg=r11
|
||||
@17087: PUSH_IMM_ARRAY [8]
|
||||
@@ -10715,7 +10710,6 @@
|
||||
@18571: PUSH_UNDEFINED
|
||||
@18572: POP_TRY
|
||||
@18573: RET_TRUE
|
||||
@18574: JUMP target=@18892
|
||||
@18577: ALLOC_REG [6]
|
||||
@18579: PUSH_TRY catchTarget=@18567 catchVarReg=r6
|
||||
@18582: PUSH_IMM_ARRAY [3]
|
||||
@@ -15799,7 +15793,6 @@
|
||||
@27198: PUSH_FALSE
|
||||
@27199: POP_TRY
|
||||
@27200: RET_TRUE
|
||||
@27201: JUMP target=@27362
|
||||
@27204: ALLOC_REG [5]
|
||||
@27206: PUSH_TRY catchTarget=@27194 catchVarReg=r5
|
||||
@27209: PUSH_IMM_ARRAY [3]
|
||||
@@ -16195,7 +16188,6 @@
|
||||
@27855: POP
|
||||
@27856: POP_TRY
|
||||
@27857: RET_TRUE
|
||||
@27858: JUMP target=@27900
|
||||
@27861: ALLOC_REG [5]
|
||||
@27863: PUSH_TRY catchTarget=@27829 catchVarReg=r5
|
||||
@27866: PUSH_IMM_ARRAY [3]
|
||||
@@ -16262,7 +16254,6 @@
|
||||
@27972: PUSH_FALSE
|
||||
@27973: POP_TRY
|
||||
@27974: RET_TRUE
|
||||
@27975: JUMP target=@28477
|
||||
@27978: ALLOC_REG [9]
|
||||
@27980: PUSH_TRY catchTarget=@27968 catchVarReg=r9
|
||||
@27983: PUSH_IMM_ARRAY [3]
|
||||
@@ -16604,7 +16595,6 @@
|
||||
@28575: PUSH_FALSE
|
||||
@28576: POP_TRY
|
||||
@28577: RET_TRUE
|
||||
@28578: JUMP target=@29390
|
||||
@28581: ALLOC_REG [12]
|
||||
@28583: PUSH_TRY catchTarget=@28571 catchVarReg=r12
|
||||
@28586: PUSH_IMM_ARRAY [3]
|
||||
@@ -17157,7 +17147,6 @@
|
||||
@29482: PUSH_FALSE
|
||||
@29483: POP_TRY
|
||||
@29484: RET_TRUE
|
||||
@29485: JUMP target=@29919
|
||||
@29488: ALLOC_REG [9]
|
||||
@29490: PUSH_TRY catchTarget=@29478 catchVarReg=r9
|
||||
@29493: PUSH_IMM_ARRAY [3]
|
||||
@@ -17502,7 +17491,6 @@
|
||||
@30087: PUSH_FALSE
|
||||
@30088: POP_TRY
|
||||
@30089: RET_TRUE
|
||||
@30090: JUMP target=@30935
|
||||
@30093: ALLOC_REG [10]
|
||||
@30095: PUSH_TRY catchTarget=@30083 catchVarReg=r10
|
||||
@30098: PUSH_IMM_ARRAY [3]
|
||||
@@ -18345,7 +18333,6 @@
|
||||
@31579: POP_TRY
|
||||
@31580: POP_TRY
|
||||
@31581: RET_TRUE
|
||||
@31582: JUMP target=@31627
|
||||
@31585: ALLOC_REG [10]
|
||||
@31587: PUSH_TRY catchTarget=@31574 catchVarReg=r10
|
||||
@31590: PUSH_IMM_ARRAY [4]
|
||||
@@ -19626,7 +19613,6 @@
|
||||
@33841: PUSH_EMPTY_STR
|
||||
@33842: POP_TRY
|
||||
@33843: RET_TRUE
|
||||
@33844: JUMP target=@34228
|
||||
@33847: ALLOC_REG [8]
|
||||
@33849: PUSH_TRY catchTarget=@33837 catchVarReg=r8
|
||||
@33852: PUSH_IMM_ARRAY [3]
|
||||
@@ -24144,7 +24130,6 @@
|
||||
@42082: POP
|
||||
@42083: PUSH_TRUE
|
||||
@42084: RET_TRUE
|
||||
@42085: JUMP target=@42025
|
||||
@42088: PUSH_FALSE
|
||||
@42089: RET_TRUE
|
||||
@42090: CREATE_CLOSURE target=@41870 caps=[r5=r3,r6=r4] params=[p0->r3]
|
||||
@@ -25676,7 +25661,6 @@
|
||||
@44769: PUSH_STR_CHAR [114]
|
||||
@44771: NEW_METHOD [1]
|
||||
@44773: THROW
|
||||
@44774: POP_TRY
|
||||
@44775: IS_EXCEPTION
|
||||
@44776: PUSH_UNDEFINED
|
||||
@44777: RET_TRUE
|
||||
|
||||
Reference in New Issue
Block a user