1420 字
4 分钟
利用ast处理花指令,控制流
JS逆向实战:利用AST处理花指令、嵌套if-else与三元表达式
完整技术指南(含区间夹逼算法详解)
一、前言
在JavaScript逆向工程中,我们经常会遇到经过混淆的代码,其中最典型的就是基于操作码的虚拟机(VM)保护。这种保护将原本清晰的代码逻辑转换为复杂的if-else链或三元表达式,增加了逆向分析的难度。
本文将通过实际案例,详细讲解如何利用Babel AST工具链处理这类混淆代码,成功提取所有操作码映射。
二、项目背景与挑战分析
2.1 目标代码结构
某平台的VM解释器函数d()使用了基于操作码t的分支结构:
function d() { for (;;) { var t = o[a++]; if (t < 38) { if (t < 19) { if (t < 9) { if (t < 4) { if (t < 2) { if (0 === t) { // 操作码0 } else { // 操作码1 } } else if (2 === t) { // 操作码2 } else { // 操作码3 } } else if (t < 6) { // 操作码4 } else { // 操作码5 } } else if (t < 14) { // 操作码6-8 } else { // 操作码9 } } else if (t < 28) { // 操作码10-18 } else if (t < 33) { // 操作码19-27 } else if (t < 35) { // 三元表达式:v[++p] = 33 === t ? void 0 : c; } else if (t < 36) { // 操作码35 } else { // 三元表达式:v[p] = 36 === t ? +v[p] : ~v[p]; } } else if (t < 57) { // 操作码38-56 } // ... 更多分支 else { if (75 !== t) return f = 2, void (l = v[p--]); v[++p] = null; // 操作码75 } }}2.2 四大挑战
┌─────────────────────────────────────────────────────────────┐│ JS逆向混淆四大挑战 │├─────────────────────────────────────────────────────────────┤│ ││ 1️⃣ 嵌套if-else链 ││ ├── 通过多个 <、<=、>、>= 操作符逐步夹逼 ││ └── 操作码范围从 [0, +∞) 逐步缩小到单个整数 ││ ││ 2️⃣ 三元表达式 ││ ├── 独立三元:v[p] ? a += U : --p ││ └── 嵌套三元:v[p] = 36 === t ? +v[p] : ~v[p] ││ ││ 3️⃣ 隐式else分支 ││ └── if (75 !== t) return; v[++p] = null; ││ ││ 4️⃣ 操作码缺失 ││ └── 部分操作码通过特殊结构定义(33、34、53、75等) ││ │└─────────────────────────────────────────────────────────────┘三、核心概念:区间夹逼算法
3.1 什么是区间夹逼?
区间夹逼是提取操作码的核心技术。通过分析嵌套的if-else条件,逐步缩小t的取值范围,最终确定每个操作码对应的代码。
生活化类比
猜数字游戏(区间夹逼):
我心里想一个0-100的数字,你来猜:1. 你猜50 → 我说"小了" → 范围变成[50, 101)2. 你猜75 → 我说"大了" → 范围变成[50, 75)3. 你猜62 → 我说"小了" → 范围变成[62, 75)4. 你猜68 → 我说"大了" → 范围变成[62, 68)5. 你猜65 → 我说"对了" → 范围变成[65, 66),即操作码653.2 左闭右开区间表示法
我们使用左闭右开区间 [min, max) 来表示操作码范围:
// 左闭右开区间 [min, max)// 含义:min <= x < max
// 示例[0, 19) → 表示整数: 0, 1, 2, ..., 18[19, 28) → 表示整数: 19, 20, ..., 27[74, 75) → 表示整数: 74(恰好一个整数)3.3 操作符与区间映射
| 操作符 | 真分支区间 | 假分支区间 | 示例 |
|---|---|---|---|
t < num | [-∞, num) | [num, +∞) | t < 19 → 真: [0,19), 假: [19,+∞) |
t <= num | [-∞, num+1) | [num+1, +∞) | t <= 19 → 真: [0,20), 假: [20,+∞) |
t > num | (num, +∞] | [-∞, num] | t > 19 → 真: [20,+∞), 假: [-∞,20) |
t >= num | [num, +∞] | [-∞, num) | t >= 19 → 真: [19,+∞), 假: [-∞,19) |
t === num | [num, num+1) | (-∞, num) ∪ (num+1, +∞) | t === 19 → 真: [19,20) |
3.4 区间夹逼流程图
┌─────────────────────────────────┐ │ 初始区间 [0, +∞) │ └──────────────┬────────────────────┘ │ ┌──────────────▼────────────────────┐ │ if (t < 38) ? │ └──────────────┬────────────────────┘ │ ┌───────────────────┴───────────────────┐ │ 真分支: [0, 38) 假分支: [38, +∞) │ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ │ if (t < 19) ? │ │ 其他操作码 │ └────────┬─────────┘ └──────────────────┘ │ ┌───────┴───────┐ │真: [0, 19) │ 假: [19, 38) ▼ ▼┌────────┐ ┌────────┐│if(t<9)?│ │继续夹逼 │└───┬────┘ │到[19,38)│ │ └────────┘┌───┴───┐│真:[0,9)│ 假:[9,19)▼ ▼... ...3.5 完整夹逼示例
原始代码片段
if (t < 38) { if (t < 19) { if (t < 9) { if (t < 4) { if (t < 2) { if (0 === t) { // 操作码0 } else { // 操作码1 } } else if (2 === t) { // 操作码2 } else { // 操作码3 } } else if (t < 6) { // 操作码4 } else { // 操作码5 } } else if (t < 14) { // 操作码6-8 } else { // 操作码9 } } else if (t < 28) { // 操作码10-18 } else { // 操作码19-27 }}夹逼过程图解
第1层: if (t < 38)┌─────────────────────────────────────────────────────────┐│ 初始区间: [0, +∞) ││ ↓ t < 38 ││ ┌─────────────────────────┴─────────────────────────┐ ││ 真分支: [0, 38) 假分支: [38, +∞) │ │└─────────────────────────────────────────────────────────┘
第2层: if (t < 19)┌─────────────────────────────────────────────────────────┐│ 当前区间: [0, 38) ││ ↓ t < 19 ││ ┌─────────────────────────┴─────────────────────────┐ ││ 真分支: [0, 19) 假分支: [19, 38) │ │└─────────────────────────────────────────────────────────┘
第3层: if (t < 9)┌─────────────────────────────────────────────────────────┐│ 当前区间: [0, 19) ││ ↓ t < 9 ││ ┌─────────────────────────┴─────────────────────────┐ ││ 真分支: [0, 9) 假分支: [9, 19) │ │└─────────────────────────────────────────────────────────┘
第4层: if (t < 4)┌─────────────────────────────────────────────────────────┐│ 当前区间: [0, 9) ││ ↓ t < 4 ││ ┌─────────────────────────┴─────────────────────────┐ ││ 真分支: [0, 4) 假分支: [4, 9) │ │└─────────────────────────────────────────────────────────┘
第5层: if (t < 2)┌─────────────────────────────────────────────────────────┐│ 当前区间: [0, 4) ││ ↓ t < 2 ││ ┌─────────────────────────┴─────────────────────────┐ ││ 真分支: [0, 2) 假分支: [2, 4) │ │└─────────────────────────────────────────────────────────┘
第6层: if (0 === t)┌─────────────────────────────────────────────────────────┐│ 当前区间: [0, 2) ││ ↓ 0 === t ││ ┌─────────────────────────┴─────────────────────────┐ ││ 真分支: [0, 1) 假分支: [1, 2) │ ││ ┌────────┐ ┌────────┐ ││ │操作码0 │ │操作码1 │ ││ │[0,1) │ │[1,2) │ ││ └────────┘ └────────┘ │└─────────────────────────────────────────────────────────┘四、区间工具函数详解
4.1 intersect函数 - 区间交集计算
位置:decode.js 第79-81行
功能:计算两个区间的交集,是夹逼算法的核心
function intersect(r1, r2) { return { min: Math.max(r1.min, r2.min), max: Math.min(r1.max, r2.max) };}工作原理
┌──────────────────────────────────────────────────────────┐│ 交集计算示意图 │├──────────────────────────────────────────────────────────┤│ ││ 区间r1: [0, 38) ││ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ││ 0 19 38 ││ ││ 区间r2: [0, 19) ││ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ││ 0 19 │ ││ ↓ ││ 交集: min = max(0, 0) = 0 ││ max = min(38, 19) = 19 ││ 结果: [0, 19) ││ │└──────────────────────────────────────────────────────────┘代码示例
// 场景:计算 if (t < 38) { if (t < 19) {...} } 的真分支区间const currentRange = { min: 0, max: 38 }; // t < 38 的真分支const range = { min: -Infinity, max: 19 }; // t < 19 的条件区间
const branchRange = intersect(currentRange, range);// 结果:{ min: 0, max: 19 }// 即 [0, 19),表示 t < 19 时满足所有条件4.2 isSingleInt函数 - 单整数判断
位置:decode.js 第84-86行
功能:判断区间是否恰好包含一个整数
function isSingleInt(range) { return range.max - range.min === 1;}判断逻辑
┌──────────────────────────────────────────────────────────┐│ 单整数判断示意 │├──────────────────────────────────────────────────────────┤│ ││ 区间[19, 20) ││ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ││ 19 20 ││ ││ max - min = 20 - 19 = 1 ✓ ││ 结论:恰好包含一个整数(19) ││ ││ ───────────────────────────────────────────────────── ││ ││ 区间[0, 38) ││ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ││ 0 38 ││ ││ max - min = 38 - 0 = 38 ✗ ││ 结论:包含多个整数,需要继续夹逼 ││ │└──────────────────────────────────────────────────────────┘4.3 storeOpcode函数 - 操作码存储
位置:decode.js 第88-93行
功能:存储操作码及其对应的代码
function storeOpcode(range, node) { // 只有区间恰好包含一个整数时才存储 if (isSingleInt(range)) { const op = range.min; // 取出唯一的操作码 if (!stream[op]) { // 避免重复覆盖 stream[op] = node; // 存储操作码对应的代码 } }}存储流程
┌──────────────────────────────────────────────────────────┐│ 操作码存储流程 │├──────────────────────────────────────────────────────────┤│ ││ 输入: ││ range = { min: 19, max: 20 } // [19, 20) ││ node = "v[++p] = void 0" // 操作码19的代码 ││ ││ 处理步骤: ││ 1. isSingleInt({19, 20})? ││ → 20 - 19 = 1 ✓ ││ ││ 2. op = range.min = 19 ││ ││ 3. stream[19] 已存在? → 否 ││ ││ 4. stream[19] = node ││ ││ 结果: ││ stream = { ││ 19: "v[++p] = void 0", // 操作码19 ││ ... ││ } ││ │└──────────────────────────────────────────────────────────┘五、parseRange函数 - 区间解析器(核心函数)
5.1 函数功能
位置:decode.js 第99-130行
功能:将条件表达式解析为区间,是区间夹逼的起点
5.2 完整代码
function parseRange(testPath) { if (!testPath || testPath.type !== 'BinaryExpression') return null;
const left = testPath.get('left'); const right = testPath.get('right'); const op = testPath.get('operator').node;
// 情况1: t < num if (left.type === 'Identifier' && left.node.name === 't') { if (right.type === 'NumericLiteral') { const num = right.node.value; switch (op) { case '<': return { min: -Infinity, max: num }; case '<=': return { min: -Infinity, max: num + 1 }; case '>': return { min: num + 1, max: Infinity }; case '>=': return { min: num, max: Infinity }; case '===': case '==': return { min: num, max: num + 1 }; } } } // 情况2: num > t (反向比较) else if (right.type === 'Identifier' && right.node.name === 't') { if (left.type === 'NumericLiteral') { const num = left.node.value; switch (op) { case '<': return { min: num + 1, max: Infinity }; case '<=': return { min: num, max: Infinity }; case '>': return { min: -Infinity, max: num }; case '>=': return { min: -Infinity, max: num + 1 }; case '===': case '==': return { min: num, max: num + 1 }; } } } return null;}5.3 为什么 t < 19 对应 [-∞, 19)?
┌──────────────────────────────────────────────────────────┐│ t < 19 的区间转换过程 │├──────────────────────────────────────────────────────────┤│ ││ 1. 条件含义 ││ t < 19 表示:t可以是 0, 1, 2, ..., 18 ││ ││ 2. 用左闭右开区间表示 ││ 最小值:-∞(或0,因为操作码不会是负数) ││ 最大值:19(不包含19) ││ 结果:[0, 19) 或 [-∞, 19) ││ ││ 3. 验证 ││ [0, 19) = { x | 0 ≤ x < 19 } ││ = { 0, 1, 2, ..., 18 } ✓ ││ │└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐│ t <= 19 的区间转换过程 │├──────────────────────────────────────────────────────────┤│ ││ 1. 条件含义 ││ t <= 19 表示:t可以是 0, 1, 2, ..., 19 ││ ││ 2. 技巧:使用 num + 1 ││ 如果用 [-∞, 19),只能表示 0-18 ││ 如果用 [-∞, 20),可以表示 0-19 ││ 所以 max = num + 1 = 19 + 1 = 20 ││ 结果:[-∞, 20) ││ ││ 3. 验证 ││ [-∞, 20) = { x | -∞ ≤ x < 20 } ││ = { 0, 1, 2, ..., 19 } ✓ ││ │└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐│ t === 19 的区间转换过程 │├──────────────────────────────────────────────────────────┤│ ││ 1. 条件含义 ││ t === 19 表示:t只能是 19(唯一值) ││ ││ 2. 技巧:使用 max = min + 1 ││ 要表示单个整数,需要 max - min = 1 ││ min = 19, max = 19 + 1 = 20 ││ 结果:[19, 20) ││ ││ 3. 验证 ││ [19, 20) = { x | 19 ≤ x < 20 } ││ = { 19 } ✓ ││ │└──────────────────────────────────────────────────────────┘5.4 操作符转换对照表
┌──────────────────────────────────────────────────────────────────┐│ 区间转换对照表 │├──────────────────────────────────────────────────────────────────┤│ ││ ┌─────────────┬────────────────────────┬────────────────────────┐ ││ │ 操作符 │ 真分支区间 │ 假分支区间 │ ││ ├─────────────┼────────────────────────┼────────────────────────┤ ││ │ t < 19 │ [-∞, 19) │ [19, +∞) │ ││ │ t <= 19 │ [-∞, 20) │ [20, +∞) │ ││ │ t > 19 │ [20, +∞) │ [-∞, 20) │ ││ │ t >= 19 │ [19, +∞) │ [-∞, 19) │ ││ │ t === 19 │ [19, 20) │ (-∞,19) ∪ (20,+∞) │ ││ ├─────────────┼────────────────────────┼────────────────────────┤ ││ │ 19 < t │ [20, +∞) │ [-∞, 20) │ ││ │ 19 <= t │ [19, +∞) │ [-∞, 19) │ ││ │ 19 > t │ [-∞, 19) │ [19, +∞) │ ││ │ 19 >= t │ [-∞, 20) │ [20, +∞) │ ││ │ 19 === t │ [19, 20) │ (-∞,19) ∪ (20,+∞) │ ││ └─────────────┴────────────────────────┴────────────────────────┘ ││ ││ 注意:反向比较 (num > t) 等价于 (t < num) ││ │└──────────────────────────────────────────────────────────────────┘六、squeezeRange函数 - 区间夹逼主函数
6.1 函数功能
位置:decode.js 第140-310行
功能:递归处理if-else链,通过区间夹逼收集所有操作码
6.2 完整代码结构
function squeezeRange(path, currentRange) { // ==================== 第1步:解析当前if条件 ==================== const testPath = path.get('test'); const range = parseRange(testPath); if (!range) return;
// ==================== 第2步:计算真分支区间 ==================== const branchRange = intersect(currentRange, range);
// ==================== 第3步:处理consequent(条件为真) ==================== const consequent = path.get('consequent');
if (consequent.isBlockStatement()) { // BlockStatement处理(包含特殊模式识别) handleBlockStatement(consequent, branchRange); } else if (consequent.isIfStatement()) { // 嵌套if递归处理 squeezeRange(consequent, branchRange); } else { // 单个语句直接存储 storeOpcode(branchRange, consequent.node); }
// ==================== 第4步:处理alternate(条件为假) ==================== const alternate = path.get('alternate'); if (alternate.node) { // 计算假分支区间 let elseRangeRaw = calculateElseRange(testPath, range); const elseRange = intersect(currentRange, elseRangeRaw);
// 递归或直接存储 if (alternate.isIfStatement()) { squeezeRange(alternate, elseRange); } else { storeOpcode(elseRange, alternate.node); } }}6.3 处理流程图
┌─────────────────────────────────────────────────────────────┐│ squeezeRange 主流程 │├─────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 1. 解析条件表达式 │ ││ │ testPath = path.get('test') │ ││ │ range = parseRange(testPath) │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 2. 计算真分支区间 │ ││ │ branchRange = intersect(currentRange, range) │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 3. 处理consequent(条件为真) │ ││ │ │ ││ │ ┌──────────┬──────────┬──────────┐ │ ││ │ │BlockStmt │ IfStmt │ Other │ │ ││ │ └────┬─────┴────┬─────┴────┬─────┘ │ ││ │ │ │ │ │ ││ │ ▼ ▼ ▼ │ ││ │ 递归处理 递归处理 直接存储 │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 4. 处理alternate(条件为假) │ ││ │ │ ││ │ ┌──────────┬──────────┐ │ ││ │ │ IfStmt │ Other │ │ ││ │ └────┬─────┴────┬─────┘ │ ││ │ │ │ │ ││ │ ▼ ▼ │ ││ │ 递归处理 区间交集+存储 │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌──────────────┐ ││ │ 处理完成 │ ││ └──────────────┘ ││ │└─────────────────────────────────────────────────────────────┘6.4 特殊处理:BlockStatement中的隐式else
问题发现
// 原始代码} else if (t < 54) { if (52 === t) return U = o[a++], f = 1, void (l = a + U); U = o[a++], a += U; // ← 这个代码没有明确的if对应,是隐式else分支}AST结构分析
else if (t < 54) { BlockStatement ├─ IfStatement (52 === t) │ ├─ test: 52 === t │ ├─ consequent: return ... │ └─ alternate: null ← 没有else! │ └─ ExpressionStatement (U = o[a++], a += U) ← 隐式的else分支}识别与处理
// 识别条件if (body.length === 2 && body[0].isIfStatement() && !body[0].node.alternate) { // body[0] 是if语句,没有else分支 // body[1] 是隐式的else分支
const ifStmt = body[0]; const elseStmt = body[1];
// 处理if语句 squeezeRange(ifStmt, branchRange);
// 处理隐式else分支 const ifRange = parseRange(ifStmt.get('test')); // if (52 === t) 的真分支: [52, 53) // 隐式else分支: [53, 54) const elseRange = { min: 53, max: 54 }; storeOpcode(elseRange, elseStmt.node);}流程图
┌─────────────────────────────────────────────────────────────┐│ 隐式else分支处理流程 │├─────────────────────────────────────────────────────────────┤│ ││ else if (t < 54) { ││ ┌─────────────────────────────────────────────────┐ ││ │ if (52 === t) { │ ││ │ return ...; // 真分支 │ ││ │ } // 没有else! │ ││ │ │ ││ │ U = o[a++], a += U; // 隐式的else分支 │ ││ └─────────────────────────────────────────────────┘ ││ } ││ ││ 处理步骤: ││ ┌─────────────────────────────────────────────────────┐ ││ │ 1. 识别模式: │ ││ │ body.length === 2 ✓ │ ││ │ body[0].isIfStatement() ✓ │ ││ │ !body[0].node.alternate ✓ │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 2. 计算区间: │ ││ │ if (52 === t) → 真分支: [52, 53) │ ││ │ 隐式else → [53, 54) ← t < 54 但 t !== 52 │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 3. 存储操作码: │ ││ │ 操作码52: return ... │ ││ │ 操作码53: U = o[a++], a += U │ ││ └─────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘6.5 特殊处理:if (num !== t) return
问题发现
// 原始代码} else { if (75 !== t) return f = 2, void (l = v[p--]); v[++p] = null; // ← 只有 t === 75 时才会执行到这里}识别逻辑
// 识别条件if (body[0].isIfStatement() && body[0].get('consequent').isReturnStatement()) { const ifTestPath = body[0].get('test');
// 检查条件是否是 !== 操作符 if (ifTestPath.isBinaryExpression() && ifTestPath.get('operator').node === '!==') { // if (num !== t) return 表示只有 t === num 时才继续执行
const left = ifTestPath.get('left'); const right = ifTestPath.get('right'); let num = null;
if (left.isNumericLiteral()) num = left.node.value; else if (right.isNumericLiteral()) num = right.node.value;
if (num !== null) { // 后面的语句对应操作码 num const numRange = { min: num, max: num + 1 }; storeOpcode(numRange, body[1].node); } }}流程图
┌─────────────────────────────────────────────────────────────┐│ if (num !== t) return 处理流程 │├─────────────────────────────────────────────────────────────┤│ ││ else { ││ ┌─────────────────────────────────────────────────┐ ││ │ if (75 !== t) { │ ││ │ return f = 2, void (l = v[p--]); │ ││ │ } │ ││ │ │ ││ │ v[++p] = null; // 只有 t === 75 时执行 │ ││ └─────────────────────────────────────────────────┘ ││ } ││ ││ 逻辑推理: ││ ┌─────────────────────────────────────────────────────┐ ││ │ if (75 !== t) return │ ││ │ │ ││ │ 当 t = 75 时:75 !== 75 为 false,不执行return │ ││ │ 当 t ≠ 75 时:75 !== t 为 true,执行return │ ││ │ │ ││ │ 结论:只有 t === 75 时才会继续执行后面的代码 │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 操作码75: v[++p] = null │ ││ └─────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘七、handleNestedConditional函数 - 嵌套三元表达式处理
7.1 函数功能
位置:decode.js 第312-380行
功能:处理嵌套在赋值语句中的三元表达式
7.2 问题分析
两种三元表达式
// 类型1:独立三元表达式v[p] ? a += U : --p// 可以直接转换为 if-else
// 类型2:嵌套三元表达式(赋值语句中)v[p] = 36 === t ? +v[p] : ~v[p]// 不能直接转换,需要特殊处理AST结构对比
┌─────────────────────────────────────────────────────────────┐│ 独立三元表达式 AST结构 │├─────────────────────────────────────────────────────────────┤│ ││ ExpressionStatement ││ └─ ConditionalExpression ││ ├─ test: v[p] ││ ├─ consequent: a += U ││ └─ alternate: --p ││ ││ 处理方式:直接替换为 if-else ││ │└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐│ 嵌套三元表达式 AST结构 │├─────────────────────────────────────────────────────────────┤│ ││ ExpressionStatement ││ └─ AssignmentExpression (v[p] = ?) ││ ├─ left: v[p] ││ └─ right: ConditionalExpression ││ ├─ test: 36 === t ││ ├─ consequent: +v[p] ││ └─ alternate: ~v[p] ││ ││ 处理方式:提取===条件,分区存储操作码 ││ │└─────────────────────────────────────────────────────────────┘7.3 完整代码
function handleNestedConditional(path, currentRange) { // ==================== 第1步:检查节点类型 ==================== if (!path.isExpressionStatement()) return;
const expr = path.node.expression; if (!types.isAssignmentExpression(expr)) return;
// ==================== 第2步:检查右侧是否是三元表达式 ==================== const right = expr.right; if (!types.isConditionalExpression(right)) return;
console.log("[发现嵌套三元表达式]", path.toString());
// ==================== 第3步:解析三元表达式的条件 ==================== const test = right.test; if (!types.isBinaryExpression(test)) return;
const op = test.operator; let num = null;
// 提取数字 if (types.isNumericLiteral(test.left)) { num = test.left.value; } else if (types.isNumericLiteral(test.right)) { num = test.right.value; } else { return; }
// ==================== 第4步:根据操作符处理 ==================== if (op === '===') { // 真分支区间:精确匹配 const trueRange = { min: num, max: num + 1 };
// 假分支区间:当前区间去掉num,分割成两部分 const falseRange1 = { min: currentRange.min, max: num }; const falseRange2 = { min: num + 1, max: currentRange.max };
// ==================== 第5步:构建代码并存储 ==================== const trueCode = types.expressionStatement( types.assignmentExpression(expr.operator, expr.left, right.consequent) ); const falseCode = types.expressionStatement( types.assignmentExpression(expr.operator, expr.left, right.alternate) );
// 存储操作码 storeOpcode(intersect(currentRange, trueRange), trueCode); // 操作码num storeOpcode(intersect(currentRange, falseRange1), falseCode); // 假分支前半 storeOpcode(intersect(currentRange, falseRange2), falseCode); // 假分支后半 }}7.4 详细示例
原始代码
v[p] = 36 === t ? +v[p] : ~v[p]处理过程图解
┌─────────────────────────────────────────────────────────────┐│ 嵌套三元表达式处理流程 │├─────────────────────────────────────────────────────────────┤│ ││ 输入: ││ 代码: v[p] = 36 === t ? +v[p] : ~v[p] ││ 区间: [36, 38) ││ ││ ───────────────────────────────────────────────────────── ││ ││ 第1步:提取信息 ││ ┌─────────────────────────────────────────────────────┐ ││ │ 条件: 36 === t │ ││ │ num: 36 │ ││ │ op: === │ ││ │ 真分支: +v[p] │ ││ │ 假分支: ~v[p] │ ││ └─────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ 第2步:计算区间 ││ ┌─────────────────────────────────────────────────────┐ ││ │ 真分支区间: [36, 37) ← 操作码36 │ ││ │ 假分支区间: [37, 38) ← 操作码37 │ ││ │ │ ││ │ 为什么分割? │ ││ │ [36, 38) 去掉 36 后变成 [36, 38) │ ││ │ 分成两部分: [36, 36) + [37, 38) │ ││ │ [36, 36) 是空区间,丢弃 │ ││ │ [37, 38) 对应操作码37 │ ││ └─────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ 第3步:构建代码 ││ ┌─────────────────────────────────────────────────────┐ ││ │ 操作码36: v[p] = +v[p] │ ││ │ 操作码37: v[p] = ~v[p] │ ││ └─────────────────────────────────────────────────────┘ ││ │ ││ ▼ ││ 第4步:存储结果 ││ ┌─────────────────────────────────────────────────────┐ ││ │ stream[36] = v[p] = +v[p] │ ││ │ stream[37] = v[p] = ~v[p] │ ││ └─────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘7.5 更多示例
示例1:操作码33和34
// 原始代码v[++p] = 33 === t ? void 0 : c┌─────────────────────────────────────────────────────────────┐│ 代码: v[++p] = 33 === t ? void 0 : c ││ 区间: [33, 35) │├─────────────────────────────────────────────────────────────┤│ ││ 条件: 33 === t ││ num: 33 ││ ││ 真分支区间: [33, 34) ← 操作码33 → v[++p] = void 0 ││ 假分支区间: [34, 35) ← 操作码34 → v[++p] = c ││ ││ 原理: ││ - 当 t=33 时:33===33为真,v[++p]=void 0 ││ - 当 t=34 时:33===34为假,v[++p]=c ││ │└─────────────────────────────────────────────────────────────┘示例2:多级三元表达式
// 原始代码t < 35 ? v[++p] = 33 === t ? void 0 : c : t < 36 ? (w = v[p--], v[p] = v[p] >> w) : v[p] = 36 === t ? +v[p] : ~v[p]┌─────────────────────────────────────────────────────────────┐│ 多级三元表达式结构 │├─────────────────────────────────────────────────────────────┤│ ││ t < 35 ? ││ ├─ 真: v[++p] = 33 === t ? void 0 : c ││ │ ├─ 真: 操作码33 → v[++p] = void 0 ││ │ └─ 假: 操作码34 → v[++p] = c ││ │ ││ └─ 假: t < 36 ? ││ ├─ 真: 操作码35 → w = v[p--], v[p] = v[p] >> w ││ └─ 假: v[p] = 36 === t ? +v[p] : ~v[p] ││ ├─ 真: 操作码36 → v[p] = +v[p] ││ └─ 假: 操作码37 → v[p] = ~v[p] ││ ││ 完整映射: ││ [33, 34) → 操作码33 ││ [34, 35) → 操作码34 ││ [35, 36) → 操作码35 ││ [36, 37) → 操作码36 ││ [37, 38) → 操作码37 ││ │└─────────────────────────────────────────────────────────────┘八、get_identity函数 - ===条件处理
8.1 函数功能
位置:decode.js 第366-395行
功能:收集===条件的操作码及其t+1的操作码
8.2 完整代码
function get_identity(path) { const testPath = path.get('test'); if (!testPath || testPath.type !== 'BinaryExpression') return;
const op = testPath.get('operator').node; if (op !== '===') return;
const left = testPath.get('left'); const right = testPath.get('right');
let opr = null; if (left.isNumericLiteral()) opr = left.node.value; else if (right.isNumericLiteral()) opr = right.node.value; else return;
// 存储===条件的真分支 const code = path.node.consequent; stream[opr] = code;
// 收集t+1的操作码 if (path.node.alternate) { stream[opr + 1] = path.node.alternate; }}8.3 为什么收集t+1?
示例代码
if (72 === t) { // 操作码72的代码} else { // 操作码73的代码}工作原理
┌─────────────────────────────────────────────────────────────┐│ t+1 技巧解释 │├─────────────────────────────────────────────────────────────┤│ ││ if (72 === t) { ││ // ... 操作码72 ││ } else { ││ // ... 操作码73 ││ } ││ ││ 当 t = 72 时: ││ - 满足 72 === t → 执行if分支 → 操作码72 ✓ ││ ││ 当 t = 73 时: ││ - 不满足 72 === 73 → 执行else分支 → 操作码73 ✓ ││ ││ 结论: ││ - 72 === t 的真分支 = 操作码72 ││ - 72 === t 的假分支 = 操作码73 = 操作码72 + 1 ││ │└─────────────────────────────────────────────────────────────┘8.4 完整示例
原始代码
} else if (t < 74) { if (72 === t) { x = o[a++]; var W = Z[x]; W in globalThis || (globalThis[W] = void 0); } else { x = o[a++], v[++p] = Z[x]; }}处理过程
┌─────────────────────────────────────────────────────────────┐│ get_identity 处理流程 │├─────────────────────────────────────────────────────────────┤│ ││ 输入: ││ if (72 === t) {...} else {...} ││ ││ ───────────────────────────────────────────────────────── ││ ││ 1. 解析 === 条件 ││ num = 72 ││ ││ 2. 存储操作码72 ││ stream[72] = /* if (72 === t) {...} */ ││ ││ 3. 检查alternate ││ path.node.alternate 存在 ✓ ││ ││ 4. 存储操作码73 ││ stream[73] = /* else {...} */ ││ opr + 1 = 72 + 1 = 73 ││ ││ ───────────────────────────────────────────────────────── ││ ││ 结果: ││ stream[72] = if (72 === t) {...} // 操作码72 ││ stream[73] = else {...} // 操作码73 ││ │└─────────────────────────────────────────────────────────────┘九、processRangeSqueeze函数 - 入口控制
9.1 函数功能
位置:decode.js 第410-430行
功能:控制区间夹逼的入口,只处理最外层if
9.2 完整代码
function processRangeSqueeze(path) { // ==================== 第1步:检查是否在函数d内部 ==================== const pa = path.findParent( p => p.isFunctionDeclaration() && p.node.id.name === 'd' ) if (!pa) return;
// ==================== 第2步:检查是否是最外层if ==================== const parent = path.parentPath; if (!parent.isBlockStatement()) { // 如果父节点不是BlockStatement,说明是嵌套的if // 这些由squeezeRange递归处理,不需要单独调用 return; }
// ==================== 第3步:从整个区间开始夹逼 ==================== const testPath = path.get('test'); const range = parseRange(testPath); if (range) { const fullRange = { min: -Infinity, max: Infinity }; squeezeRange(path, fullRange); }}9.3 为什么只处理最外层if?
问题分析
if (t < 38) { // ← 最外层if(父节点是BlockStatement) if (t < 19) { // ← 嵌套if(父节点是if的consequent) if (t < 9) { // ← 更深的嵌套 // ... } }}如果每个if都单独处理
┌─────────────────────────────────────────────────────────────┐│ 错误做法:每个if都单独处理 │├─────────────────────────────────────────────────────────────┤│ ││ 1. 处理 if (t < 38) ││ currentRange = [-∞, +∞) ││ branchRange = [0, 38) ││ ││ 2. 处理 if (t < 19) ← 错误:重复处理! ││ currentRange = [-∞, +∞) ← 应该是 [0, 38) ││ branchRange = [0, 19) ← 区间不正确 ││ ││ 3. 处理 if (t < 9) ← 错误:重复处理! ││ currentRange = [-∞, +∞) ← 应该是 [0, 19) ││ branchRange = [0, 9) ← 区间严重不正确 ││ ││ 结果: ││ - 同一个区间被多次计算 ││ - 操作码收集混乱 ││ │└─────────────────────────────────────────────────────────────┘正确做法
┌─────────────────────────────────────────────────────────────┐│ 正确做法:只处理最外层,递归处理嵌套 │├─────────────────────────────────────────────────────────────┤│ ││ 1. 处理最外层 if (t < 38) ││ currentRange = [-∞, +∞) ││ branchRange = [0, 38) ││ 递归处理嵌套 ││ ││ 2. squeezeRange 处理 if (t < 19) ││ currentRange = [0, 38) ← 正确!来自父节点 ││ branchRange = [0, 19) ││ 递归处理嵌套 ││ ││ 3. squeezeRange 处理 if (t < 9) ││ currentRange = [0, 19) ← 正确!来自父节点 ││ branchRange = [0, 9) ││ ││ 结果: ││ - 区间逐层夹逼,越来越精确 ││ - 每个操作码只被收集一次 ││ │└─────────────────────────────────────────────────────────────┘9.4 父节点检查逻辑
// 判断是否是最外层ifconst parent = path.parentPath;
if (parent.isBlockStatement()) { // 父节点是BlockStatement → 最外层if ✓ // 例如:函数体的第一个if语句} else if (parent.isIfStatement()) { // 父节点是IfStatement → 这是 else if ✗ // 例如:if (...) { if (...) {...} }} else if (parent.isConditionalExpression()) { // 父节点是三元表达式 → 嵌套在三元中 ✗}十、完整执行流程图
10.1 整体架构
┌─────────────────────────────────────────────────────────────┐│ decode.js 整体架构 │├─────────────────────────────────────────────────────────────┤│ ││ ┌─────────────────────────────────────────────────────┐ ││ │ visitor1 │ ││ │ 三元表达式处理 │ ││ │ │ ││ │ ConditionalExpression: { │ ││ │ exit(path) { │ ││ │ // 处理独立三元表达式 │ ││ │ // 嵌套三元由visitor2处理 │ ││ │ } │ ││ │ } │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ visitor2 │ ││ │ 操作码收集 + 区间夹逼 │ ││ │ │ ││ │ IfStatement: { │ ││ │ get_identity() // === 条件处理 │ ││ │ processRangeSqueeze() // 区间夹逼入口 │ ││ │ } │ ││ │ │ ││ │ 核心函数: │ ││ │ - squeezeRange() // 区间夹逼主函数 │ ││ │ - handleNestedConditional() // 嵌套三元处理 │ ││ │ - storeOpcode() // 操作码存储 │ ││ └────────────────────────┬────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 输出结果 │ ││ │ │ ││ │ stream = { │ ││ │ 0: 操作码0代码, │ ││ │ 1: 操作码1代码, │ ││ │ ... │ ││ │ 75: 操作码75代码 │ ││ │ } │ ││ │ │ ││ │ 操作码数量: 76 (0-75) │ ││ └─────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘10.2 visitor1 处理流程
┌─────────────────────────────────────────────────────────────┐│ visitor1 处理流程 │├─────────────────────────────────────────────────────────────┤│ ││ 遍历所有 ConditionalExpression 节点 ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 1. 检查是否在函数d内部 │ ││ │ path.findParent(FunctionDeclaration d) │ ││ │ │ │ ││ │ ┌────────────┴────────────┐ │ ││ │ │ 不在函数d | 在函数d内 │ │ ││ │ │ return ↓继续 │ │ ││ │ └────────────┬────────────┘ │ ││ └───────────────────────────┼───────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 2. 检查父节点类型 │ ││ │ │ ││ │ parentPath.isExpressionStatement()? │ ││ │ │ │ ││ │ ┌────┴────┐ │ ││ │ │ 是 │ 否(其他位置) │ ││ │ ▼ ▼ │ ││ │ 替换为 跳过(由visitor2处理) │ ││ │ if-else │ ││ └─────────────────────────────────────────────────────┘ ││ │└─────────────────────────────────────────────────────────────┘10.3 visitor2 处理流程
┌─────────────────────────────────────────────────────────────┐│ visitor2 处理流程 │├─────────────────────────────────────────────────────────────┤│ ││ 遍历所有 IfStatement 节点 ││ │ ││ ▼ ││ ┌─────────────────────────────────────────────────────┐ ││ │ 1. 检查是否在函数d内部 │ ││ │ path.findParent(FunctionDeclaration d) │ ││ │ │ │ ││ │ ┌────┴────┐ │ ││ │ │ 不在函数d | 在函数d内 │ ││ │ │ return ↓继续 │ ││ │ └─────────┼─────────────────────────────────┘ │ ││ └──────────────┼───────────────────────────────────────┘ ││ │ ││ ┌───────┴───────┐ ││ ▼ ▼ ││ ┌──────────────┐ ┌──────────────┐ ││ │ get_identity()│ │processRange │ ││ │ │ │Squeeze() │ ││ └──────┬───────┘ └──────┬───────┘ ││ │ │ ││ ▼ ▼ ││ ┌──────────────┐ ┌──────────────┐ ││ │===条件处理 │ │区间夹逼主函数 │ ││ │ │ │ │ ││ │stream[opr] │ │squeezeRange │ ││ │stream[opr+1] │ │ (递归) │ ││ └──────────────┘ └──────────────┘ ││ │└─────────────────────────────────────────────────────────────┘十一、运行结果
11.1 执行命令
node decode.js11.2 输出结果
收集到的操作码数量: 76操作码列表: [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75']11.3 操作码映射示例
stream = { 0: "var r = o[a++]; p -= r; var e = v.slice(p + 1, p + r + 1), ...", 1: "var w = v[p--]; v[p] = v[p] <= w", 2: "var w = v[p--]; v[p] = v[p] > w", 3: "var x = o[a++], S = v[p--], P = []; for (var j in S) P.push(j); s[x] = [P, S]", 4: "x = o[a++]; var O = v[p--], R = v[p--]; ...", 5: "x = o[a++]; var A = Z[x], E = b(A, i); v[++p] = E, v[++p] = A", 6: "var k = v[p--]; v[p] = v[p][k]", 7: "v[++p] = {}", 8: "var k = v[p--]; v[p] = v[p][k]", 9: "v[++p] = !0", 10: "v[p] = void 0", // ... 更多操作码 33: "v[++p] = void 0", // 来自嵌套三元表达式 34: "v[++p] = c", // 来自嵌套三元表达式 35: "w = v[p--], v[p] = v[p] >> w", 36: "v[p] = +v[p]", // 来自嵌套三元表达式 37: "v[p] = ~v[p]", // 来自嵌套三元表达式 // ... 更多操作码 53: "U = o[a++], a += U", // 来自隐式else分支 // ... 更多操作码 75: "v[++p] = null" // 来自 if (75 !== t) return};十二、关键要点总结
12.1 区间夹逼核心要点
- 区间夹逼:通过嵌套if条件逐步缩小区间
- 左闭右开:
[min, max)表示min <= x < max - 区间交集:每层条件都取交集,确保区间正确
- 单整数判断:
max - min === 1时存储操作码
12.2 特殊模式识别
- 隐式else分支:BlockStatement中”if+语句”的模式
- 嵌套三元表达式:赋值语句右侧的三元表达式
- ===条件处理:配合t+1收集相邻操作码
- !== return模式:隐式条件分支
12.3 递归处理策略
- 只处理最外层if:避免重复计算
- 递归处理嵌套:通过squeezeRange递归处理
- 区间逐层夹逼:从[-∞, +∞)逐步缩小到单个操作码
12.4 AST遍历技巧
- visitor1:处理独立三元表达式
- visitor2:收集操作码和区间夹逼
- 遍历顺序:使用exit确保子节点先处理
- 父节点检查:根据父节点类型决定处理策略
十三、扩展应用
此方法可应用于:
- 虚拟机(VM)保护分析:提取操作码映射
- 代码混淆逆向:还原控制流
- 控制流平坦化还原:处理switch-case结构
- 恶意软件分析:识别恶意代码逻辑
- 代码审计:理解复杂的条件逻辑
十四、参考资料
部分信息可能已经过时









