mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1420 字
4 分钟
利用ast处理花指令,控制流
2026-06-21

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),即操作码65

3.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 父节点检查逻辑#

// 判断是否是最外层if
const 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.js

11.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 区间夹逼核心要点#

  1. 区间夹逼:通过嵌套if条件逐步缩小区间
  2. 左闭右开[min, max) 表示 min <= x < max
  3. 区间交集:每层条件都取交集,确保区间正确
  4. 单整数判断max - min === 1 时存储操作码

12.2 特殊模式识别#

  1. 隐式else分支:BlockStatement中”if+语句”的模式
  2. 嵌套三元表达式:赋值语句右侧的三元表达式
  3. ===条件处理:配合t+1收集相邻操作码
  4. !== return模式:隐式条件分支

12.3 递归处理策略#

  1. 只处理最外层if:避免重复计算
  2. 递归处理嵌套:通过squeezeRange递归处理
  3. 区间逐层夹逼:从[-∞, +∞)逐步缩小到单个操作码

12.4 AST遍历技巧#

  1. visitor1:处理独立三元表达式
  2. visitor2:收集操作码和区间夹逼
  3. 遍历顺序:使用exit确保子节点先处理
  4. 父节点检查:根据父节点类型决定处理策略

十三、扩展应用#

此方法可应用于:

  1. 虚拟机(VM)保护分析:提取操作码映射
  2. 代码混淆逆向:还原控制流
  3. 控制流平坦化还原:处理switch-case结构
  4. 恶意软件分析:识别恶意代码逻辑
  5. 代码审计:理解复杂的条件逻辑

十四、参考资料#

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

利用ast处理花指令,控制流
https://fatdog.20060113.xyz/posts/ast-if/
作者
神秘大胖狗
发布于
2026-06-21
许可协议
MIT

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00