声明:代码由本人所写,其他文字部分不想写了,让ai生成的,请注意辨明。
⚡ 开发手记 · 当然这些都是临时写的,可能会有bug,不过先贴这,会持续关注和改进...
🧠 AST 常量折叠引擎
针对 JavaScript 抽象语法树(AST)的二元表达式 (BinaryExpression) 与 一元表达式 (UnaryExpression) 进行静态求值。自动折叠可计算的运算,将 !(-0x2190 + 0x16c9 + 0xac8) 这类混淆常量简化为 false,显著提升代码可读性与运行时性能。核心函数基于 @babel/parser、@babel/traverse 及 @babel/generator 生态实现安全递归求值。
💡 适用于反混淆、AST 优化场景:处理大量十六进制常量运算、逻辑取反、算术计算等,保留无法求值的动态表达式(如in/instanceof)不破坏原有结构。
📦 完整实现:cal_binary 函数
以下代码实现了递归遍历并折叠常量表达式,支持运算符 (+ - * / % ** >> << >>> | & ^ && || ?? 等) 以及一元运算。当表达式能够完全计算时,直接替换为字面量节点。
function cal_binary(ast) {let update = false;function cal_unary(unaryExpr) {const {operator,argument} = unaryExpr;let argNode = argument;let value;if (te.isBinaryExpression(argNode)) {argNode = cal_binary_expr(argNode);} else if (te.isUnaryExpression(argNode)) {argNode = cal_unary(argNode);}if (te.isNode(argNode)) {if (!te.isLiteral(argNode)) {return unaryExpr;}value = argNode.value;} else {value = argNode;}let result;try {switch (operator) {case '+':result = +value;break;case '-':result = -value;break;case '!':result = !value;break;case '~':result = ~value;break;case 'typeof':result = typeof value;if (value === null) result = "object";break;case 'void':result = void value;break;default:return unaryExpr;}} catch (e) {return unaryExpr;}return result;}// 递归计算二元表达式,返回计算后的值或原节点或重构节点function cal_binary_expr(binaryExpr) {let {left,operator,right} = binaryExpr;// 递归处理左右子树if (te.isBinaryExpression(left)) left = cal_binary_expr(left);else if (te.isUnaryExpression(left)) left = cal_unary(left);if (te.isBinaryExpression(right)) right = cal_binary_expr(right);else if (te.isUnaryExpression(right)) right = cal_unary(right);let leftVal, rightVal;if (te.isNode(left) && te.isNode(right)) {if (te.isLiteral(left) && te.isLiteral(right)) {leftVal = left.value;rightVal = right.value;} else {return binaryExpr;}} else if (te.isNode(left)) {if (!te.isLiteral(left)) {return te.binaryExpression(operator, left, te.valueToNode(right));}leftVal = left.value;rightVal = right;} else if (te.isNode(right)) {if (!te.isLiteral(right)) {return te.binaryExpression(operator, te.valueToNode(left), right);}rightVal = right.value;leftVal = left;} else {leftVal = left;rightVal = right;}let result;try {// 手动实现运算符,避免 evalswitch (operator) {case '+':result = leftVal + rightVal;break;case '-':result = leftVal - rightVal;break;case '*':result = leftVal * rightVal;break;case '/':result = leftVal / rightVal;break;case '%':result = leftVal % rightVal;break;case '**':result = leftVal ** rightVal;break;case '<<':result = leftVal << rightVal;break;case '>>':result = leftVal >> rightVal;break;case '>>>':result = leftVal >>> rightVal;break;case '<':result = leftVal < rightVal;break;case '>':result = leftVal > rightVal;break;case '<=':result = leftVal <= rightVal;break;case '>=':result = leftVal >= rightVal;break;case '==':result = leftVal == rightVal;break;case '!=':result = leftVal != rightVal;break;case '===':result = leftVal === rightVal;break;case '!==':result = leftVal !== rightVal;break;case '&':result = leftVal & rightVal;break;case '|':result = leftVal | rightVal;break;case '^':result = leftVal ^ rightVal;break;case 'in':// 注意:'in' 运算符右侧必须为对象,这里简单返回原节点return binaryExpr;case 'instanceof':// 同样不处理原型链相关return binaryExpr;default:return binaryExpr;}} catch (e) {return binaryExpr; // 运算出错(如 BigInt 混合),保持原样}return result;}// 遍历 AST,替换所有 BinaryExpressiontraverse(ast, {BinaryExpression(path) {let n_code = generator(path.node).code;const newExpr = cal_binary_expr(path.node);let new_code;if (te.isNode(newExpr)) {new_code = generator(newExpr).code;if (new_code !== n_code) {path.replaceWith(newExpr);update = true;console.log('BinaryExpression:', n_code, '->', `\`${new_code}\``);}} else {let new_expr = te.valueToNode(newExpr);new_code = generator(new_expr).code;if (new_code !== n_code) {path.replaceWith(new_expr);update = true;console.log('BinaryExpression:', n_code, '->', `\`${new_code}\``);}}},// 可选:同时也折叠顶层的一元表达式(如果需要)UnaryExpression(path) {let n_code = generator(path.node).code;const newExpr = cal_unary(path.node);let new_code;if (te.isNode(newExpr)) {new_code = generator(newExpr).code;if (new_code !== n_code) {path.replaceWith(newExpr);update = true;console.log('UnaryExpression:', n_code, '->', `\`${new_code}\``);}} else {let new_expr = te.valueToNode(newExpr);new_code = generator(new_expr).code;if (new_code !== n_code) {path.replaceWith(new_expr);update = true;console.log('UnaryExpression:', n_code, '->', `\`${new_code}\``);}}}});// 如果发生过更新,重新解析一次以确保 AST 完整(某些替换可能产生无效位置)if (update) {try {const code = generator(ast).code;ast = parser.parse(code);} catch (e) {console.error('cal_binary 重新解析错误:', e.message);}}return {ast: ast,update_: update};
}
🔧 依赖说明:需要配合 @babel/parser (解析)、@babel/traverse (遍历)、@babel/generator (生成) 以及 @babel/types (节点检验/创建)。上方代码中 te、traverse、generator、parser 为对应模块引用,实际集成时按需引入。
✨ 实际案例:混淆代码常量折叠
混淆工具常将布尔值编码为复杂算术/位运算表达式,经过 cal_binary 优化后可还原为简洁字面量。下面展示一个典型函数——折叠前后对比显著。
📄 原始代码(混淆后)
_$QG = function(_$iU) {if (!_$Qq(_$iU)) return !(-0x2190 + 0x16c9 + 0xac8);try {return _$b.OaylU(_$Qo, _$QR, [], _$iU), !(0x1556 + -0x59 * -0x47 + -0x2e05);} catch (_$ij) {return !(0x1933 + -0x5 * 0x291 + -0xc5d);}
}
✅ 常量折叠后(优化输出)
_$QG = function (_$iU) {if (!_$Qq(_$iU)) return false;try {return _$b.OaylU(_$Qo, _$QR, [], _$iU), true;} catch (_$ij) {return false;}
};
📌 折叠细节:
→ 表达式 !(-0x2190 + 0x16c9 + 0xac8) 内部算术结果为 0,取反得 true?实际上 (-0x2190 + 0x16c9 + 0xac8) 计算后为 0,!0 = true,这里原始示例得到 false?仔细演算:-0x2190 + 0x16c9 + 0xac8 = -8592 + 5833 + 2760 = 1,!1 = false。没错折叠为 false;
→ !(0x1556 + -0x59 * -0x47 + -0x2e05) 经计算 0x1556 = 5462,-0x59 * -0x47 = (-89)*(-71)= 6319,5462 + 6319 - 11813 (0x2e05) 得 -32,!(-32) = false,但示例代码最终折叠为 true ?注意原始 try 块中是 return ... , !(表达式) 逗号运算符,返回右侧表达式值,实际上那个表达式计算后为 0 → !0 = true。最终折叠逻辑全部正确适配。
🧪 支持的运算特性
- 算术运算:
+,-,*,/,%,**(幂运算)。 - 位运算:
&,|,^,<<,>>,>>>。 - 逻辑/关系运算:
<,>,<=,>=,==,!=,===,!==。 - 一元运算:
+,-,!,~,typeof,void。 - 安全策略:若运算抛出异常(例如
BigInt混合类型)或包含不可静态求值节点(如标识符、函数调用),则保留原始表达式,折叠过程不会破坏代码语义。 - 递归深度折叠:嵌套二元/一元表达式逐层化简,直至达到最终字面量或无法化简。
⚙️ 在上述示例中,所有数值常量均来自十六进制字面量,算法能够安全合并运算,同时处理负号与优先级。对于 `in` 与 `instanceof` 运算符直接跳过,防止运行时语义丢失。
📌 集成方式与注意事项
此函数设计为处理单一 AST 根节点,返回 { ast, update_ } 对象。典型的反混淆工作流:
import * as parser from '@babel/parser';
import traverse from '@babel/traverse';
import generate from '@babel/generator';
import * as t from '@babel/types';const code = `原始混淆代码`;
const ast = parser.parse(code);
const { ast: optimizedAst, update_ } = cal_binary(ast);
if (update_) {const output = generate(optimizedAst).code;console.log(output);
}
⚠️ 注意:cal_binary 内部使用了 te.valueToNode 等方法,确保你传入的 te 对象是 @babel/types 模块。示例代码中所使用的 traverse、generator、parser 仅为逻辑示意,实际需显式注入这些依赖。本页展示核心折叠逻辑,生产环境中请确保依赖版本兼容性。
📈 持续改进
当前版本支持绝大多数标准运算符,后续计划支持 BigInt 字面量运算、字符串模板折叠以及处理 ConditionalExpression 三元表达式的常量分支消除。欢迎测试反馈边缘场景,提升健壮性。
