JavaScript 混淆解密原理分析

215 阅读3分钟

WX20231204-144436@2x.png 1. 代码混淆的目的

混淆代码的主要目的是保护源代码,防止未经授权的复制、篡改或逆向工程。通过对变量名、字符串和控制流的修改,混淆代码看似毫无逻辑,但本质功能未变。混淆技术常用于商业应用和恶意软件中。

2. 混淆的常见模式

在上述代码中,可以观察到以下典型的混淆手段:

  1. 动态计算与防调试逻辑
if (143752 === -parseInt(u(646)) / 1 * ... ) break;

这段代码通过大量的数学运算试图让分析人员迷惑,同时可能触发防调试逻辑以阻止代码正常运行。

  1. 混淆的变量名与动态查找
function t(r, n) {
    var u = q();
    return (t = function (n, v) {
        var q = u[n -= 479];
        ...
    })(r, n);
}

这里的 t 函数是一个核心的解密函数,利用了动态查找表 u 来返回字符串。

  1. Base64 解码
var z = function (r) {
    for (var n, u, v = "", t = "", q = 0, z = 0; u = r.charAt(z++); ...) {
        ...
    }
    return decodeURIComponent(t);
};

Base64 解码是一种常用的加密字符串技术,结合 decodeURIComponent 进一步解码 URL 编码的字符串。

  1. 动态插入模块
var n = require(r(582));

使用 require 动态加载模块,进一步隐藏依赖关系。

3. 解密过程

为了分析这段代码,我们需要依次完成以下步骤:

(1)提取核心解密函数

核心函数 t 和 q 是解密的关键。通过分析 t 的动态行为,可以逐步将加密的字符串解密。

(2)定位并清理防调试逻辑

try...catch 块和多重数学运算可能包含防调试逻辑,删除或屏蔽相关逻辑可以使代码更容易执行。

(3)重建动态查找表

q() 返回一个字符串数组,配合 n -= 479 这样的索引偏移,我们可以重建一个静态表,用于替换动态调用。

4. 解密实例

以下是解密的基本流程:

(1)重建查找表

从 q() 提取静态数组,并根据偏移量解析出对应的字符串:

const lookupTable = [
    "DxjS", "CNuVofberxfeEa", "zKrQstz6v2rpBW", ...
];

(2)简化动态函数

将动态索引替换为静态索引,从而消除 t 函数的复杂性:

function t(index) {
    return lookupTable[index - 479];
}

(3)删除防调试代码

屏蔽或直接注释掉防调试逻辑:

// 删除多余的数学计算和 try-catch

(4)解析 Base64 加密字符串

提取 Base64 解码部分,直接输出结果:

const base64String = "aGVsbG8gd29ybGQ="; // 示例字符串
const decoded = atob(base64String);
console.log(decoded); // 输出 "hello world"

5. 防护与对策

尽管混淆代码能有效防止普通用户分析,但无法抵挡专业解密人员的逆向分析。以下是改进建议:

  1. 代码加壳

使用加壳工具将代码与运行时环境绑定,提升解密难度。

  1. 运行时校验

通过动态校验代码完整性,发现被篡改时自动退出。

  1. 多层加密

结合动态加密和多种混淆方式,使解密时间成本加倍。

结论

JavaScript 混淆是一把双刃剑,虽然可以保护知识产权,但对代码维护和调试带来极大不便。解密的过程并非简单,而是需要深入理解代码的逻辑及目的。掌握混淆和解密的原理,能更好地平衡安全性与可维护性。