feat(VM): cd 槽位表可静态导出 + 槽位语义还原(符号求值)
tdc_program 新增 symbolic_assignments(): 对小程序做线性符号求值, 把字节码还原成
`r6 = c.navigator.userAgent` 这样的可读表达式(覆盖字面串/BIND_THIS/MAKE_(REG_)PROP_REF/
GET_GLOBAL_PROP/NEW_FUNC/CALL_*/STORE_REG/LOAD_REG_IMM/REVERSE_TOP2/DUP/算术)。
tdc_layout 新增:
* slot_value_expr(): 精确规则 —— 采集器模块把值存进某寄存器, `get` 闭包只返回它
(caps 的父寄存器就是取值寄存器); 含"模块里唯一闭包"的兜底
* slot_semantics(): 合并"工厂赋值链 + require 委派 + 内部闭包"三条线索
* 输出改成叶子语义 + 真机明文并排核对
实测确认(线上构建 + 同会话解出的真机 collect):
* 清单模块 = registry[7], 构造方式是 `r6[r6.length] = require(N)` 追加
-> **require 顺序即 cd 槽位顺序**(37 个 require + 1 个字面量槽 = 38, 与真机完全吻合)
* 语义已精确命中: cd[3] <- c.navigator.userAgent, cd[5] <- c.navigator.languages
* 仍有若干槽的语义标签未对齐(取值寄存器规则需再收口), 已在账本记录
This commit is contained in:
+113
-7
@@ -33,6 +33,7 @@ from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
@@ -56,6 +57,101 @@ ENV_KEYS = [
|
||||
]
|
||||
|
||||
|
||||
def _fold_str_before(code: List[dict], end: int, span: int = 40) -> Optional[str]:
|
||||
"""从 end 往前折叠一个字面串(PUSH_EMPTY_STR + 连续 PUSH_STR_CHAR)。"""
|
||||
chars: List[str] = []
|
||||
i = end
|
||||
while i >= 0 and i > end - span and code[i]["name"] == "PUSH_STR_CHAR":
|
||||
chars.append(chr(code[i]["operands"][0] & 0xFF))
|
||||
i -= 1
|
||||
if i >= 0 and code[i]["name"] == "PUSH_EMPTY_STR":
|
||||
return "".join(reversed(chars))
|
||||
return None
|
||||
|
||||
|
||||
def slot_value_expr(p: tp.Program, entry: int) -> Optional[str]:
|
||||
"""**精确**取一个槽位模块"这一槽的值是什么"。
|
||||
|
||||
规则(实测形状): 采集器模块最后做
|
||||
r3.exports.get = function () { ... return <被捕获的那个寄存器> ... }
|
||||
也就是 `get` 闭包**只返回它捕获的寄存器**。所以:
|
||||
1. 在工厂里找到被赋给属性 `get` 的那个 CREATE_CLOSURE;
|
||||
2. 从它的 caps 里取出**父寄存器号**;
|
||||
3. 该寄存器在工厂里的赋值表达式, 就是这一槽的语义。
|
||||
例: 模块 18 -> caps=[r3=r6], 工厂里 `r6 = c.navigator.userAgent`。
|
||||
"""
|
||||
code = p.function_code(entry)
|
||||
assigns = dict(p.symbolic_assignments(entry, limit=60))
|
||||
for k, inst in enumerate(code):
|
||||
if inst["name"] != "CREATE_CLOSURE":
|
||||
continue
|
||||
# 形状: <"get" 字面量>; REVERSE_TOP2; JUMP -> @site; @site: CREATE_CLOSURE
|
||||
j = k - 1
|
||||
if j >= 0 and code[j]["name"] == "JUMP":
|
||||
j -= 1
|
||||
if _fold_str_before(code, j) != "get":
|
||||
continue
|
||||
site = next((c for c in p.closures if c.pc == inst["pc"]), None)
|
||||
if site is None:
|
||||
continue
|
||||
for _child, parent in site.caps: # caps 是 (子寄存器, 父寄存器)
|
||||
expr = assigns.get(f"r{parent}")
|
||||
if expr:
|
||||
return expr
|
||||
# 兜底(覆盖更广): 采集器模块里通常**只有一个闭包**,它捕获的那个父寄存器就是取值寄存器
|
||||
for c in p.closures:
|
||||
if p.owner(c.pc) != entry or len(c.caps) != 1:
|
||||
continue
|
||||
_child, parent = c.caps[0]
|
||||
expr = assigns.get(f"r{parent}")
|
||||
if expr:
|
||||
return expr
|
||||
return None
|
||||
|
||||
|
||||
def slot_semantics(p: tp.Program, reg: Dict[int, int], mid: int, depth: int = 2) -> List[str]:
|
||||
"""还原某个槽位子模块"到底取什么值"。
|
||||
|
||||
三条线索合并(都来自 tdc_program 的符号求值, 不跑 JS):
|
||||
1. 模块工厂自身的赋值链, 例如 `r6 = c.navigator.userAgent`;
|
||||
2. **委派**: 有些模块只是 `r6 = r5(N)`(require 另一个模块再导出),要跟进去;
|
||||
3. **`get` 闭包内部**: canvas/WebGL 这类是延迟取值, 真正读环境在它内部创建的闭包里。
|
||||
"""
|
||||
exprs: List[str] = []
|
||||
seen: set = set()
|
||||
|
||||
def visit(m: int, d: int) -> None:
|
||||
if m in seen or d < 0 or m not in reg:
|
||||
return
|
||||
seen.add(m)
|
||||
entry = reg[m]
|
||||
for _r, v in p.symbolic_assignments(entry, limit=14):
|
||||
v = v.strip()
|
||||
if not v:
|
||||
continue
|
||||
exprs.append(v)
|
||||
mm = re.match(r"r\d+\((\d+)\)$", v) # 纯委派 r6 = r5(N)
|
||||
if mm:
|
||||
visit(int(mm.group(1)), d - 1)
|
||||
for c in p.closures: # 该模块内部创建的闭包(含 get 实现)
|
||||
if p.owner(c.pc) != entry:
|
||||
continue
|
||||
for _r, v in p.symbolic_assignments(c.target, limit=10):
|
||||
if v.strip():
|
||||
exprs.append(v.strip())
|
||||
mm = re.match(r"r\d+\((\d+)\)$", v.strip())
|
||||
if mm:
|
||||
visit(int(mm.group(1)), d - 1)
|
||||
|
||||
visit(mid, depth)
|
||||
uniq, out = set(), []
|
||||
for e in exprs:
|
||||
if e not in uniq:
|
||||
uniq.add(e)
|
||||
out.append(e)
|
||||
return out
|
||||
|
||||
|
||||
def find_manifest_module(p: tp.Program) -> Tuple[int, int, List[int]]:
|
||||
"""找"清单模块": 体内 `push(require(N))` 次数最多者。
|
||||
|
||||
@@ -130,18 +226,28 @@ def main() -> int:
|
||||
print(f"真机明文 cd 槽数: {len(plain_cd)}")
|
||||
|
||||
table = []
|
||||
print("\n槽位 模块id 工厂entry 语义线索 真机值")
|
||||
print("-" * 108)
|
||||
print("\n槽位 模块id 工厂entry 取值语义(静态还原) 真机值")
|
||||
print("-" * 112)
|
||||
for i, m in enumerate(seq):
|
||||
tgt = reg.get(m)
|
||||
strs = transitive_strings(p, tgt) if tgt else []
|
||||
hints = env_hints(strs)
|
||||
expr = slot_value_expr(p, tgt) if tgt else None
|
||||
# 有些模块只是"再 require 一层"(r6 = r5(M)),跟进一层
|
||||
mm = re.match(r"r\d+\((\d+)\)$", (expr or "").strip())
|
||||
if mm and int(mm.group(1)) in reg:
|
||||
sub = slot_value_expr(p, reg[int(mm.group(1))])
|
||||
if sub:
|
||||
expr = f"{sub} [via require({mm.group(1)})]"
|
||||
if not expr:
|
||||
expr = "; ".join(slot_semantics(p, reg, m)[:2]) if tgt else ""
|
||||
val = ""
|
||||
if plain_cd is not None and i < len(plain_cd):
|
||||
v = repr(plain_cd[i])
|
||||
val = v if len(v) <= 34 else v[:34] + "..."
|
||||
table.append({"slot": i, "module": m, "entry": tgt, "hints": hints})
|
||||
print(f" {i:2d} {m:4d} @{tgt:<7} {str(hints)[:34]:<34} {val}")
|
||||
val = v if len(v) <= 30 else v[:30] + "..."
|
||||
sem = expr or ""
|
||||
if len(sem) > 44:
|
||||
sem = sem[:44] + "..."
|
||||
table.append({"slot": i, "module": m, "entry": tgt, "expr": expr})
|
||||
print(f" {i:2d} {m:4d} @{tgt:<7} {sem:<46} {val}")
|
||||
|
||||
if plain_cd is not None:
|
||||
print(f"\n(真机 cd 里还有 {len(plain_cd) - len(seq)} 个槽不来自 require —— "
|
||||
|
||||
Reference in New Issue
Block a user