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 argparse
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Tuple
|
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]]:
|
def find_manifest_module(p: tp.Program) -> Tuple[int, int, List[int]]:
|
||||||
"""找"清单模块": 体内 `push(require(N))` 次数最多者。
|
"""找"清单模块": 体内 `push(require(N))` 次数最多者。
|
||||||
|
|
||||||
@@ -130,18 +226,28 @@ def main() -> int:
|
|||||||
print(f"真机明文 cd 槽数: {len(plain_cd)}")
|
print(f"真机明文 cd 槽数: {len(plain_cd)}")
|
||||||
|
|
||||||
table = []
|
table = []
|
||||||
print("\n槽位 模块id 工厂entry 语义线索 真机值")
|
print("\n槽位 模块id 工厂entry 取值语义(静态还原) 真机值")
|
||||||
print("-" * 108)
|
print("-" * 112)
|
||||||
for i, m in enumerate(seq):
|
for i, m in enumerate(seq):
|
||||||
tgt = reg.get(m)
|
tgt = reg.get(m)
|
||||||
strs = transitive_strings(p, tgt) if tgt else []
|
expr = slot_value_expr(p, tgt) if tgt else None
|
||||||
hints = env_hints(strs)
|
# 有些模块只是"再 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 = ""
|
val = ""
|
||||||
if plain_cd is not None and i < len(plain_cd):
|
if plain_cd is not None and i < len(plain_cd):
|
||||||
v = repr(plain_cd[i])
|
v = repr(plain_cd[i])
|
||||||
val = v if len(v) <= 34 else v[:34] + "..."
|
val = v if len(v) <= 30 else v[:30] + "..."
|
||||||
table.append({"slot": i, "module": m, "entry": tgt, "hints": hints})
|
sem = expr or ""
|
||||||
print(f" {i:2d} {m:4d} @{tgt:<7} {str(hints)[:34]:<34} {val}")
|
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:
|
if plain_cd is not None:
|
||||||
print(f"\n(真机 cd 里还有 {len(plain_cd) - len(seq)} 个槽不来自 require —— "
|
print(f"\n(真机 cd 里还有 {len(plain_cd) - len(seq)} 个槽不来自 require —— "
|
||||||
|
|||||||
@@ -175,6 +175,110 @@ class Program:
|
|||||||
out.append((mid, tgt))
|
out.append((mid, tgt))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
# ---------------------------------------------------------- 轻量符号求值
|
||||||
|
def symbolic_assignments(self, entry: int, limit: int = 30) -> List[Tuple[str, str]]:
|
||||||
|
"""对一个小函数(如采集器模块)做**线性符号求值**, 还原 `rN = <表达式>`。
|
||||||
|
|
||||||
|
只覆盖这类小模块实际用到的形状: 字面串 / BIND_THIS / MAKE_(REG_)PROP_REF /
|
||||||
|
GET_PROP / GET_GLOBAL_PROP / NEW_FUNC / CALL_FUNC / CALL_METHOD / STORE_REG /
|
||||||
|
LOAD_REG_IMM / REVERSE_TOP2 / DUP / 算术。
|
||||||
|
|
||||||
|
目的: 把"这一槽取的是什么值"变成可读路径, 例如 `c.navigator.userAgent`。
|
||||||
|
说明: 遇到分支时按 pc 顺序近似处理, 结果用于**定位语义**, 不作为执行依据。
|
||||||
|
"""
|
||||||
|
regs: Dict[int, str] = {}
|
||||||
|
out: List[Tuple[str, str]] = []
|
||||||
|
st: List[Tuple[str, str]] = [] # ("reg", n) | ("sym", text)
|
||||||
|
|
||||||
|
def top() -> Tuple[str, str]:
|
||||||
|
return st[-1] if st else ("sym", "?")
|
||||||
|
|
||||||
|
def popn(n: int) -> List[Tuple[str, str]]:
|
||||||
|
r = st[-n:] if n else []
|
||||||
|
del st[len(st) - n:]
|
||||||
|
return r
|
||||||
|
|
||||||
|
for inst in self.function_code(entry):
|
||||||
|
n, ops = inst["name"], inst["operands"] or []
|
||||||
|
if n == "PUSH_IMM_ARRAY":
|
||||||
|
st.append(("reg", ops[0]))
|
||||||
|
elif n == "PUSH_EMPTY_STR":
|
||||||
|
st.append(("sym", ""))
|
||||||
|
elif n == "PUSH_STR_CHAR":
|
||||||
|
k, v = top()
|
||||||
|
st[-1] = ("sym", v + chr(ops[0] & 0xFF))
|
||||||
|
elif n == "PUSH_IMM":
|
||||||
|
st.append(("sym", str(ops[0])))
|
||||||
|
elif n == "PUSH_UNDEFINED":
|
||||||
|
st.append(("sym", "undefined"))
|
||||||
|
elif n == "PUSH_NULL":
|
||||||
|
st.append(("sym", "null"))
|
||||||
|
elif n == "PUSH_TRUE":
|
||||||
|
st.append(("sym", "true"))
|
||||||
|
elif n == "PUSH_FALSE":
|
||||||
|
st.append(("sym", "false"))
|
||||||
|
elif n == "LOAD_REG_IMM":
|
||||||
|
st.append(("sym", regs.get(ops[0], f"r{ops[0]}")))
|
||||||
|
elif n == "LOAD_REG_INDIRECT":
|
||||||
|
k, v = st.pop() if st else ("sym", "?")
|
||||||
|
st.append(("sym", regs.get(int(v), f"r{v}") if str(v).isdigit() else v))
|
||||||
|
elif n == "BIND_THIS":
|
||||||
|
k, v = st.pop() if st else ("sym", "?")
|
||||||
|
st.append(("sym", f"c.{v}"))
|
||||||
|
elif n == "MAKE_PROP_REF":
|
||||||
|
key = top()[1]
|
||||||
|
popn(1)
|
||||||
|
base = top()[1]
|
||||||
|
popn(1)
|
||||||
|
st.append(("sym", f"{base}.{key}"))
|
||||||
|
elif n == "MAKE_REG_PROP_REF":
|
||||||
|
key = top()[1]
|
||||||
|
popn(1)
|
||||||
|
k, v = st.pop() if st else ("sym", "?")
|
||||||
|
st.append(("sym", f"{regs.get(v, f'r{v}')}.{key}"))
|
||||||
|
elif n == "GET_GLOBAL_PROP":
|
||||||
|
st[-1] = ("sym", f"c.{top()[1]}")
|
||||||
|
elif n in ("GET_PROP", "GET_ELEM"):
|
||||||
|
pass
|
||||||
|
elif n in ("ADD", "SUB", "MUL", "DIV", "MOD", "BIT_AND", "BIT_OR", "BIT_XOR",
|
||||||
|
"SHL", "SHR", "SHR_U", "EQ", "EQ_STRICT", "GREATER"):
|
||||||
|
if len(st) >= 2:
|
||||||
|
b = top()[1]
|
||||||
|
popn(1)
|
||||||
|
a = top()[1]
|
||||||
|
popn(1)
|
||||||
|
st.append(("sym", f"({a} {n} {b})"))
|
||||||
|
elif n in ("NEW_FUNC", "NEW_METHOD"):
|
||||||
|
args = popn(int(ops[0]) if ops else 0)
|
||||||
|
fn = st.pop()[1] if st else "?"
|
||||||
|
st.append(("sym", f"new {fn}({','.join(x[1] for x in args)})"))
|
||||||
|
elif n == "CALL_FUNC":
|
||||||
|
args = popn(int(ops[0]) if ops else 0)
|
||||||
|
fn = st.pop()[1] if st else "?"
|
||||||
|
st.append(("sym", f"{fn}({','.join(x[1] for x in args)})"))
|
||||||
|
elif n == "CALL_METHOD":
|
||||||
|
args = popn(int(ops[0]) if ops else 0)
|
||||||
|
tgt = st.pop() if st else ("sym", "?")
|
||||||
|
st.append(("sym", f"{tgt[1]}({','.join(x[1] for x in args)})"))
|
||||||
|
elif n == "STORE_REG":
|
||||||
|
v = st.pop()[1] if st else "?"
|
||||||
|
k, ref = st.pop() if st else ("sym", "?")
|
||||||
|
if k == "reg":
|
||||||
|
regs[ref] = v
|
||||||
|
out.append((f"r{ref}", v))
|
||||||
|
elif n == "DUP":
|
||||||
|
if st:
|
||||||
|
st.append(st[-1])
|
||||||
|
elif n == "REVERSE_TOP2":
|
||||||
|
if len(st) >= 2:
|
||||||
|
st[-1], st[-2] = st[-2], st[-1]
|
||||||
|
elif n in ("POP", "SET_PROP", "ALLOC_REG", "SET_STACK_LEN", "REPLACE_TOP_IMM"):
|
||||||
|
if n in ("POP", "SET_PROP") and st:
|
||||||
|
st.pop()
|
||||||
|
if len(out) >= limit:
|
||||||
|
break
|
||||||
|
return out
|
||||||
|
|
||||||
def find_strings(self, pattern: str) -> List[Tuple[int, str, int]]:
|
def find_strings(self, pattern: str) -> List[Tuple[int, str, int]]:
|
||||||
"""返回 (pc, 串, 所属子程序入口)。"""
|
"""返回 (pc, 串, 所属子程序入口)。"""
|
||||||
rx = re.compile(pattern, re.I)
|
rx = re.compile(pattern, re.I)
|
||||||
|
|||||||
Reference in New Issue
Block a user