Files
yml2213 fbcf2d448a feat(反VM): 纯 Python 还原 XTEA 算法 + 从 tdc.js 运行时提取每构建密钥
做了什么
- 确认 collect 的加密算法就是教科书 XTEA (32 轮, delta=0x9E3779B9),
  并证明 crypto.py 原有的"带加盐"写法只是把两个密钥字折叠:
  K' = [k0, k1+salt1, k2+salt2, k3]
- tdc_worker.js 新增 captureKey 模式: 正则改写 VM 的 ADD/XOR opcode 为带日志版本,
  用反向引用自动识别栈变量名 (样本=g/雨云实时=j/1ms=M, 版本无关);
  匹配数不为 1 时显式报错, 不静默失败
- 密钥反推: (sum + K[j]) 是一次 ADD, 两操作数必有一个等于 r*DELTA,
  另一个就是密钥字; 再对 Top-N 候选做 4! 排列 + 明文内容特征验证
- 新增 recover_payload_template / generate_collect_from_template:
  运行时标定一次明文模板, 之后轨迹生成/组包/加密/编码全部在 Python
- 新增 tcaptcha/proxy.py 代理提取, client 支持 --proxy-api 遇 12 自动换 IP

为什么
- 服务端只认 collect 的解密结果, 而密钥是每构建随机的;
  把算法做成纯 Python 后, 只有"取密钥"依赖 Node, 抗发版能力最强

怎么验证的
- 纯 Python XTEA 加密结果与真实 tdc.js 产物前 48 字节逐字节一致
- 密钥按构建固定的证据: 同一脚本 3 次运行密文首块均为 f6a24e3e4fb5dd2b
- 三份脚本 (样本/雨云实时/1ms实时) 均成功复原密钥
- 雨云 aid 端到端: 纯 Python 出包 -> errorCode=0 + 真实 ticket
- 回归测试: tcaptcha/tests/test_xtea_key.py 11 项断言全过,
  文字点选求解 8/8; 新增 tcaptcha/selftest.py 一键复验

已知未解
- 1ms aid=195672344 在答案正确时仍返回 errorCode=12; 已排除密钥、
  遥测引擎、出口 IP (4 个住宅代理)、短时频控、静默窗口
2026-10-11 18:07:09 +08:00

25 KiB
Raw Permalink Blame History

逆向工程 Agent 工作指导(通用版)

本文件的用法:动手前先看 §0 一页速查定位场景;动手时按 §3 SOP 执行; 卡住或要下结论时查 §5 经验清单 与 附录 A(自身 bug 排查); 每轮结束按 §6.2 验收清单核对。

三条铁律(先记这三句,其余都是展开):

  1. 服务端是最高裁判——不校验的东西根本不逆;
  2. 工具先于结论——把"读代码"变成"查数据库",把"猜公式"变成"捕获值对照";
  3. 绝不做位置假设——布局会随输入漂移,用「内容特征 + 数学约束」识别结构。

目录

章节 内容 什么时候看
§0 场景 → 动作速查表 每次动手前
§1 Python / Git / 进度管理 项目初始化、每个里程碑
§2 四问决策链、四层模型、铁律、分层交付 定路线时
§3 反混淆 / 反平砍 / 反 VM / 反加密 / 成本闸门 拿到源码后
§4 为什么要工具、五件套、验收标准 动手读代码之前
§5 10 条踩坑沉淀 下结论前
§6 每轮节奏、交付验收、卡住升级 每轮开始/结束
附录 A 自身 bug 高频陷阱 推翻任何结论之前
附录 B 信号 → 动作 识别不出东西时

§0 一页速查

你现在的处境 立刻做什么 去哪节
刚拿到一个接口 / 抓包 先做服务端容忍度实验(变异参数看响应),画生死线 §2.1
服务端不校验某参数 停止逆向它,记入文档 §2.1
拿到混淆 JS/源码 先建工具(清点 + 提取 + 追踪 + 查询),再读 §4
代码读不懂、循环套 switch 识别为控制流平坦化,按 SOP 还原 §3.2
有大 switch + 数字数组 判定为字节码 VM,先反平砍解释器再写反汇编器 §3.3
输出是乱码/定长串 先试解码(剥头 → 标准 base64/hex → 魔改码表) §3.4
要猜某个字段的算法 别猜:运行时捕获该字段来源值,在输出里搜它的大端编码定位偏移 §5.2
公式在单样本上对上了 立刻做多维扰动 ≥10 样本 + 真实样本 + 运行时对照三重验证 §5.5
结论被推翻 在文档显式修正并登记"已排除的假设" §5.7
深挖几轮仍卡住 回 §0 重新问"服务端到底在不在乎这个细节" §6.3

§1 工程基础约束

1.1 Python 项目

  • 依赖用 uv 管理,项目级 venv:uv venv + uv add <包名>
  • 依赖按需安装:用到哪个加哪个,不要预先批量安装

1.2 Git

  • commit 信息用中文:类型(范围): 一句话总结 + 空行 + 要点列表(做了什么 / 为什么 / 怎么验证的)
  • 每个有意义的里程碑单独提交,不攒大提交
  • 提交信息含特殊字符时用 git commit -F <文件>,避免 shell 引号踩坑

1.3 进度与证据管理(长任务强制)

  • 任何进度、证据、灵感都落盘到文档,不依赖对话记忆

  • 按证据分类登记,四栏固定:

    栏位 要求
    已知 附证据(实验/抓包/源码位置)+ 确信度
    未知 附当前假设 + 确信度
    目标 本轮要推进到哪
    下一步 具体动作,可被下一轮直接执行
  • 结论必须带来源;没有证据的判断标注"假设"

  • 推翻旧结论时显式修正文档,不留过时错误判断(可另设"早期误判记录"备查)

  • 不经意的灵感、异常现象也要记录——它们经常是突破口

1.4 交付物形态

  • 产物是资产:可重复生成、可 diff、可复用,不是一次性分析结论
  • 深挖成果即使当前用不上也归档为储备,不删除
  • 每个交付物配一个一键复验入口(如 selftest)

§2 核心思路:自顶向下

2.1 四问决策链

第 0 问:服务端到底校验什么?        ← 永远的第一步,能砍掉 90% 工作量
第 1 问:目标数据的结构是什么?
第 2 问:输入特征集是什么?
第 3 问:变换算法是什么?
最后  :用目标语言原生重构,跳过一切 VM / 环境层

第 0 问的标准动作:对参数做变异(截断 / 置空 / 随机体 / 删字段 / 改版本号),用服务端响应画生死线。

  • 服务端不校验的 → 零工作,记入文档
  • 只校验格式/长度 → 结构正确的合成包即可,无需逐字节还原
  • 强校验 → 才值得还原算法;即便如此也优先还原"协议结构"而非"采集过程"(指纹值可用固定真实画像)

2.2 四层解构模型

层 内容 作用
L1 打包与模块拓扑 圈定核心加签模块,剔除开源库噪音
L2 混淆层(字符串表 / 平坦化 / 代理函数) 还原出可读结构
L3 虚拟机与探针层(字节码调度 / 环境检测) 得到操作码语义
L4 数据协议层(组包 / 压缩 / 加盐 / 自定义编码) 最终目标

自顶向下逐层剥离,但注意力始终放在 L4——L1/L2/L3 都是为读懂 L4 服务的。

2.3 三条铁律(展开)

铁律 1:服务端是最高裁判

见 §2.1。动手逆向任何算法之前先做容忍度实验。

铁律 2:拒绝盲目补环境(打地鼠陷阱)

错误做法:window is not defined 就补 window,报错就挂空函数,无休止缺啥补啥。

缺陷 说明
知其然不知其所以然 不理解风控意图与数据结构
极度脆弱 发版即全盘报废
性能低下 依赖 JSDOM/Headless 无法高并发
易被探测 Proxy/堆栈泄露伪造痕迹被拉黑

允许的最低限度:为让探针跑通而供给"真实语义的值"(有明确清单、有限次数),目的是采集语义。 一旦开始无清单地连续打补丁,立即停下回到自顶向下。

铁律 3:整体还原优先,拒绝零敲碎打

  • 反混淆 / 还原要做就一次性全量(常量池整体译出、全源码静态替换),产出可复用资产
  • 静态常量优先:译码器输出与运行时无关时,离线批量译出存表,不在运行时复刻译码器
  • 追踪 / 插桩注意开销形态:热路径上禁止跨边界函数调用与字符串分配 (用 TypedArray 直写、计数器、采样),否则会误判为"反调试"——先排除自身工具副作用再下结论

2.4 分层交付(不要追求一步到位的完美纯算)

级别 形态 成本 适用
① 纯算 目标语言原生实现 高(逆向成本) 最优:高并发、零依赖、抗环境变化
② 半自动 浏览器/沙箱低频维护会话与参数 + 纯算高频发包 中 算法某部分难还原但输入维度小
③ 桥接 Node V8 沙箱托管原 JS 暴露接口 低 保底,抗小版本更新
  • 先验证低层级方案是否已被服务端接受(§2.1),被接受就不要升级成本
  • 三级方案在代码里应是同一接口的三个实现,上层透明,可随时降级

§3 标准作业流程(SOP)

3.0 通用原则

  • 每还原一层就验证一次:与原版做功能等价对比(同输入同输出),多层叠加时不许跳层
  • 一切"静态读不懂"的部分,优先动态采集语义(探针 / 追踪),而不是硬读
  • 每种变换都有固定的三段式:识别特征 → 标准解法 → 验证方式

3.1 混淆还原(AST 反混淆)

识别特征:十六进制变量名、字符串数组+下标取值、代理函数、控制流平坦化、死代码注入、逗号表达式链。

标准管线(Babel/swc AST,缺一不可):

  1. 常量内联:字符串数组 + 解密函数 → 沙箱预执行 → 把所有 dec(a,b) 调用点静态替换为字面量 (输入固定输出固定的译码器,离线批量译出存表即可,不必理解译码器内部)
  2. 代理函数消除:function add(a,b){return a+b} 类转发函数折叠为原生表达式
  3. 死代码剔除:恒假分支、无用逗号表达式
  4. 产物必须可执行:每轮 AST 变换后做语法校验 + 功能等价验证,否则后续分析建立在坏产物上

⚠️ 第 1 步是后面一切搜索的前提:不内联就搜不到语义(platform 会写成 n(l[11],null,17,X))。

3.2 控制流平坦化还原(反平砍)

识别特征:for(;;){switch(状态变量){...}};状态字两级编码(如 case = 31&v,子 = 31&(v>>5))。

SOP:

  1. 先造工具,再读代码(不要手工抠案例)——目标是把"读机器"变成"查数据库":

    ① AST 清点全文件状态机 → 起始/结束/状态变量/mask/shift/判别式/子状态变量/各 case 区间
    ② 状态提取          → 每个 (case, 子状态) → {约束, 代码, 后继状态}
    ③ 运行时轨迹追踪     → TypedArray 环形缓冲记录实际状态序列
    ④ 执行序渲染        → 按真实顺序排列的代码片段 = 算法正文
    ⑤ 查询 CLI          → 搜状态代码 / 查某寄存器写入者 / 渲染某机器
    

    工具一旦成型,"这个字段谁写的""这段代码在哪台机器"都是秒级查询。

  2. 状态提取必须用 AST,不要手写括号配平:字符串/正则字面量里的 { } 会骗过配平器 (实战中 "[\\s\\S]{1,3}" 这类正则曾导致 case 边界全错)

  3. 与运行时轨迹联合:光有静态分支图不够,用真实执行序列排除未执行分支

  4. 交叉验证:工具提取出的转移必须与运行时观测到的转移逐条对齐,能对齐才敢用

  5. 输出"执行叙事":按真实顺序排列的已解析代码 = 算法正文

  6. 多层嵌套:外层 case 体里可能是另一个平砍机(状态变量名不同)→ 递归处理; 也可能整段是字节码程序(见 §3.3)

插桩注意:热路径禁函数调用/字符串分配(跨 realm 调用会内存爆炸,易误判为反调试)。

3.3 字节码 VM 还原(devirtualization)

识别特征:巨大的 switch 操作码分发器 + 数字数组字面量(push(18,1,4,0,...))+ Uint8Array 缓冲 + 闭包式子程序调用。

SOP:

  1. 先判定"是不是 VM":操作码分发器(读数字数组驱动 switch)+ 字节码缓冲 + 虚拟寄存器/栈 → 是
  2. 解释器通常自己也是平砍代码 → 按 §3.2 反平砍,得到操作码语义表 (每条 handler = 一条指令语义:寄存器读写 / 属性存取 / 算术 / 调用 / 闭包创建 / 异常)
  3. 提取字节码程序数组(含二进制字面量操作数)
  4. 写反汇编器:操作码表 × 字节码 → 可读指令流(机械工作,输入已齐)
  5. 读指令流得到算法;子程序调用(闭包创建)递归展开

解释器 + 字节码 = 完整算法,两者缺一不可;常量池在字节码操作数或独立表中。

3.4 加密与编码分析(反加密)

先分类,再决定策略——编码可逆,加密需要密钥。

第 1 步 判定类型

观察 判定
输出字符集是标准/魔改 base64、hex 编码类 → 直接解码看明文结构
同环境多次采样输出逐字节大范围变化,且与输入无固定对应 加密类(含 nonce / 滚动键)
定长输出 + 雪崩效应 哈希/签名类

第 2 步 分类处理

  • 编码类:剥头(版本前缀)→ 试标准 base64/hex(补 padding)→ 失败则找魔改码表 (在常量池里搜 64~66 字符表)或流式变换(逐字符 XOR / 位移,常见 charCode ^ 常数)
  • 加密类:
    • 差分分析:同环境改单一输入,看密文哪些区域变化 → 定位 nonce 区 / 指纹区 / 密文区
    • 找密钥材料:滚动键常见 key = (key*m + c) % 256、逐字符 XOR、查表替换 —— 在源码里搜算术特征(%256、^、异常常数)
    • 密钥来自环境/会话(cookie 等)时,纯算只需把该输入参数化,无需破解密钥本身
  • 哈希/签名类:先试常见族(MD5 / SHA1 / HMAC),用「签名 = H(公知字段拼接)」假设逐个拼接组合验证

第 3 步 字节级解剖(对任意黑盒输出)

  • 逐字节差分(多样本对比):恒定字节 = 指纹/版本;变化字节 = nonce/时间戳/密文
  • 找时间戳:在变化区搜当前 epoch ms/sec 的大端/小端表示
  • 区段边界:半恒定单字节常是长度 / 分隔符

3.5 成本闸门(什么该反、什么不该反)

  • 每次深入前重问:服务端校验这个吗?强度多少?
  • 逆向深度与校验强度匹配:不校验 → 零工作;格式校验 → 只做结构;强校验 → 才还原算法
  • 边界捕获优于逐指令还原:在协议关键点(编码入口 / 密钥使用点)一次性插桩捕获输入/码表/输出, 往往一个钩子抵多轮操作码分析 —— 动手前先问"最少几个边界采样能定契约"
  • 解码先于破解,码表先于差分:拿到输出先试解码看明文再谈加密; 乱序码表下用标准解码会产生"看似恒定区"的确定性乱码假象 —— 差分结论必须建立在已验证的码表之上
  • 已完成的深挖成果归档为储备资产,不删除

本节只给"识别特征 → 标准解法";工具化、校验和定位、偏移陷阱、验证边界、自身 bug 清单见 §4、§5 与附录 A。


§4 工具链建设

工具先于结论。 凡涉及"找代码边界 / 找调用点 / 找赋值点",一律上 AST;正则只用于在已定位的小片段里粗筛。

4.1 五件套(控制流平坦化场景的最小工具集)

# 工具 输入 → 输出 解决什么
① 状态机清点 全文件 → 机器清单(起始/状态变量/掩码/判别式/case 区间) 先知道"有多少台机器"
② 状态提取 单机器 → (case, 子状态) → {约束, 代码, 后继} 把机器拆成可查的状态表
③ 运行时追踪 运行一次 → 状态序列(环形缓冲) 排除未执行分支、验证静态提取
④ 执行序渲染 状态表 + 轨迹 → 按序排列的代码叙事 得到"算法正文"
⑤ 查询 CLI 正则/寄存器名 → 命中的状态与所属机器 秒级回答"谁写了它"

4.2 工具验收标准

  • 能回答三个问题:「这个字段谁写的」「这段代码在哪」「这台机器有多少状态」
  • 答案必须与运行时观测一致(否则工具在骗你)
  • 可复用:一次投入,后续所有分析都变成查询

4.3 配套的运行时捕获工具

捕获对象 用途
累加器最终值 在输出里搜其大端/小端编码 → 反推字段偏移与整块布局(§5.2)
中间缓冲区内容 判断校验和的真实求和对象(常不是最终载荷)
子程序返回值 定位"这块数据由哪个子程序产出"
环境属性读取点 得到有限的有效输入特征集

探针可复现性清单(不满足则实验不可复现,会浪费时间追不存在的"算法差异"):

  • 固定时钟(Date / performance / timeOrigin)
  • 固定 PRNG(Math.random / crypto.getRandomValues / randomUUID)
  • 明确"每进程只调用一次"或显式复位累积状态
  • 明确区分初始化时刻与调用时刻(见 §5.4)

§5 实战经验(检查表)

每条都来自"踩过坑再总结",附识别信号 → 标准动作。

5.1 工具先于结论

  • 正则处理代码 = 技术债;代码结构一律 AST
  • 先内联再搜索:混淆常量必须先批量内联成字面量,搜出的代码才有语义
  • 工具要可复用、可查询;一次投入,后续全是查询

5.2 完整性字段(校验和/长度)的通用识别法

  • 布局模式:二进制载荷常是 [2字节校验和][块内容] 顺序拼接(多块)
  • 求和形式:校验和 = Σ(块内容 & MASK),且块内嵌套的其它校验和字段在计算时按 0 计 (信号:中间缓冲区对应位置是 0,最终载荷那里已被后续写入覆盖)
  • MASK 是常量池里的数:同一源码常有多个同类累加器、掩码各不相同 → 要列全,别找到一个就以为找全
  • 杀手锏:运行时捕获累加器最终值 → 在输出里搜索该值的大端/小端编码 → 命中位置即字段偏移;多个字段一起做即可反推整块布局(比盲试公式高效一个数量级)
  • 不要只试通用哈希族(CRC/Adler/FNV/Murmur/区间和): 自研校验和多以"逐字节掩码求和"出现 —— 先按上一条定位字段,再回源码读公式

5.3 绝不做位置假设(最大的坑)

  • 块长会随环境数据变化 → 后续所有偏移漂移。 单画像验证通过的"固定偏移"(如"校验和一定在 [78:80]")换画像立刻失效
  • 正确做法:用「内容特征 + 数学约束」识别结构,而不是用偏移
    • 内容特征:块头魔数(如块起始固定是 ff 7f 75 ...)
    • 数学约束:Σ(块 & MASK) == 该块前面的 2 字节
    • 两者同时满足 → 唯一确定块的位置与长度
  • 任何偏移结论都要跨样本 / 跨画像复验,否则不要写进代码

5.4 把"时间"和"随机"当作独立实验维度

  • 时间层次解耦实验:区分「模块初始化时刻」与「请求/调用时刻」——两种变化各跑一次
    • 若"固定 init、变化 call"导致载荷零差异 ⇒ 所有时间字段都来自 init
    • 这一次实验就定下整套回填策略(把所有时间字段按同一目标 init 时刻统一回填)
  • 累积状态:同进程重复调用常使输出变长/变化(内部记账)。 确定性目标应定义为"模块初始化后的首次调用"
  • 随机源不固定时,不要下任何"算法差异"结论(先按 §4.3 清单排查)

5.5 逐字节对拍的适用边界(认清不可达目标)

  • 逐字节对拍只在受控随机源下才有意义。 真实环境每次调用含随机区,同会话相隔数秒的两个真实样本可有上千字节不同 —— 这不是"还没做到",而是不可达目标

  • 验收标准要分层:

    层级 内容
    ① 受控环境逐字节一致(证明算法还原正确)
    ② 真实样本上验证不变量与公式(版本、校验和、时间字段关系、结构可自动定位)
    ③ 运行时插桩值 == 输出字段(三方对照)
  • 三重验证法:多样本(≥10,多维扰动)+ 真实样本 + 运行时捕获对照。 只靠单一样本"公式对上"极易是巧合(弱校验和尤其容易假命中)

5.6 自身工具 bug 的高频陷阱(先怀疑自己)

动手改结论前逐条排查,详见 附录 A。

5.7 记录被推翻的假设(负结果也是资产)

  • 文档里除"已知/未知"外,还要有**"已排除的假设"**:试过什么、怎么否定的、证据是什么
  • 负结论要写清适用条件,否则未来会被误用
  • 示例(本项目实战中被推翻的判断):早期"31% 常量指纹"、"某字段 = 某累加器"、"真机块结构与沙箱不同" —— 全部靠负结果登记避免了重复投入

5.8 标定式交付(算法难还原但输入维度小)

  • 当某部分算法难完整还原,但其输入维度很小(时间、设备画像)时,交付 「模板 + 一次探针标定 + 定点回填」:

    环节 做法
    模板 一次真实采样(含全部画像数据)
    标定 再用一次不同取值的采样,反解变换类型(异或/加法)与常数
    回填 按目标取值重算受影响字段(含重新计算受影响的校验和,注意 §5.6 的依赖顺序)
  • 该方案自动适配画像差异(块长 / 偏移 / 常数),比硬编码偏移健壮得多

  • 标定结果可持久化(JSON),生成时无需再次采样

5.9 用回归测试固化每一条已确证的性质

  • 一旦确证某个公式/不变量,立刻写成断言测试并把样本打包成 fixtures
  • 覆盖范围:编解码往返、捕获样本复现、公式(多样本 + 真实样本)、 结构自动发现、跨画像生成、标定流程
  • 交付一个 selftest 入口,让别人一条命令复验全部结论

5.10 分层交付的落地形态

  • 纯算 / 半自动 / 桥接 = 同一接口的三个实现,调用方无感
  • 服务端一旦收紧、或某画像标定失败,可立刻降级而不改调用方

§6 工作节奏与验收

6.1 每轮节奏

① 读进度文档的「下一步」 → ② 选区分度最大的实验 → ③ 执行并记录证据
→ ④ 更新「已知/未知/下一步」 → ⑤ 提交(中文 commit)→ ⑥ 下一轮
  • 实验设计优先做区分度最大的那个("不变性实验""容忍度实验"),它能直接决定路线分叉
  • 每轮结束必须有可验证的产物(工具/文档/测试/结论),不要只留"分析过程"

6.2 交付前验收清单

  • 服务端容忍度实验做了吗?不校验的部分是否已剔除出工作范围?
  • 结论是否带证据来源?(实验 / 抓包 / 源码位置)
  • 是否做了三重验证(多样本 + 真实样本 + 运行时对照)?
  • 是否做了跨样本/跨画像复验(尤其任何偏移结论)?
  • 被推翻的旧结论是否已在文档中显式修正?
  • 是否先怀疑过自己的工具(附录 A 全过一遍)?
  • 产物是否可复现、可 diff、有一键复验入口?
  • 分层交付是否已就位(最低成本方案先验证)?
  • 是不是把"不可达目标"(如真实环境逐字节对拍)误当成了"待办"?

6.3 卡住时的升级路径

症状 升级动作
同一细节卡住 > 几轮 回 §2.1 第 0 问:服务端到底在不在乎这个细节
静态读不懂 改为动态采集语义(探针/追踪),不要硬读
公式猜不出来 改为运行时捕获 + 定位偏移(§5.2),或改走标定式交付(§5.8)
插桩就崩/卡死 先做 no-op 对照实验排除自身工具副作用,再判断是否反调试
样本之间结论矛盾 检查是否编码层/码表轮换或块长漂移(§5.3、附录 A 第 5/6 条)

附录 A 自身 bug 排查清单

推翻任何结论之前,先逐条排查以下陷阱——它们在本项目实战中全部真实发生过。

# 陷阱 典型症状 正确做法
1 运算符优先级 Python X & M == v 被解析成 X & (M == v) 一律加括号:(X & M) == v
2 多样本扫描用统一上界 各样本长度不同(如 1074~1171)导致长样本被截断 用各自长度
3 依赖顺序 校验和 A 的覆盖区间包含校验和 B 必须先算被包含者,再算包含者
4 括号配平不考虑字面量 字符串/正则里的 { } 破坏配平 用 AST 取精确区间
5 编码层陷阱 参数在 body 而非 header/cookie/query;urlencoded 把 + 变空格(而自定义码表恰含 +) 先确认数据位置;解码前还原 +
6 码表/版本轮换 用一套码表解另一版本,得到"看似合理的乱码" 先确认版本,每版本单独取码表
7 工具解析失败被忽略 提取结果与运行时不一致却继续用 提取结果必须与运行时轨迹逐条对齐
8 静默异常 except: pass 掩盖了真实错误 断言 + 显式失败

附录 B 信号到动作速查

观察到的信号 最可能的性质 下一步动作
for(;;){switch(x)} + 状态字 控制流平坦化 §3.2
巨大 switch + 数字数组 + Uint8Array 字节码 VM §3.3
字符串数组 + 下标取值 字符串混淆 §3.1 常量内联
输出 = 版本前缀 + 定长乱码 编码类 §3.4 第 2 步(试魔改码表)
同环境多次输出大范围变化 加密 / 含 nonce §3.4 第 2 步(差分定位)
输出定长 + 输入微改即全变 哈希 / 签名 §3.4(试常见族 + 拼接组合)
载荷中 [小值][定长块] 交替 [校验和][块内容] 布局 §5.2
常量字节 + 变化字节混合 指纹 + nonce/时间戳 §3.4 第 3 步(逐字节差分)
单画像能对上、换画像就错 偏移漂移 §5.3
同一实验两次结果不同 随机源未固定 §4.3 探针可复现性清单