JavaScript 反混淆的一般套路和技巧: 承

反混淆的本质, 是提升代码的可读性, 反混淆的过程中, 你所做的大多数事情, 都是在保证代码运行结果不变的情况下, 提升代码的可读性, 人工进行反混淆这一行为本身, 就是对自己阅读代码能力的一种锻炼.

本篇文章将讨论一些常见的混淆方式和反混淆的方法, 以及怎样组织各个流程的顺序, 为了方便叙述, 我们以反混淆的几个主要步骤来展开讲解:

  1. 解密被加密的代码, 将代码结构尽可能还原至最接近原始代码的状态.
  2. 去除可能存在的最外尾的IIFE的参数, 将参数转化为函数顶部的变量定义.
  3. 将只使用一次的变量转化为值或表达式.
  4. 计算所有可直接计算的表达式.
  5. 还原判断语句的短路逻辑简写.
  6. 还原变量类型转换简写.
  7. 去除专门用于妨碍阅读的代码.
  8. 基于主函数的运行顺序整理.
  9. 猜解变量名和函数名.

解密被加密的代码, 将代码结构尽可能还原至最接近原始代码的状态

对于一些加密过多次的混淆代码, 必须先将代码解密, 由于JavaScript是脚本语言, 脚本解释器最终执行代码的效果必然和原始代码的运行效果一致, 所以理论上不存在无法解密的代码.

大部分加密工具最后都会通过eval函数执行被加密的代码, 很多时候我们只要把eval换成输出用的函数就可以得到加密前的代码.

少部分加密工具会将原始代码拆分后分别加密, 这时可能需要先对加密后的代码进行反混淆, 找出解密函数所在的位置, 逐一得到加密前的代码, 人工进行拼接.

去除可能存在的最外围的IIFE的参数, 将参数转化为函数顶部的变量定义

IIFE的传参位置在整段代码的尾部, 人类的正常阅读习惯是从上至下的阅读, 如果保留IIFE的参数, 会影响我们对代码的理解, 绝大多数情况下, 我们都应该去除IIFE的参数, 将参数转变为变量定义.

反混淆前:

(function(a){
...
})('Hello World!')

反混淆后:

(function(){
    var a = 'Hello World!'
    ...
})()

将只使用一次的变量转化为值或表达式

有一些变量在代码中只被使用到了一次, 把这些变量直接用变量的值或表达式替代, 会有助于提升代码的可读性.

反混淆前:

var a = 'Hello World',  
    b = a.split(' ')

反混淆后:

var b = 'Hello World'.split(' ')  

需要注意的是, 一些变量定义被写在代码中的位置可能与代码在运行时的时间有关, 这类变量不应该简单的被去除, 在还未知晓具体用处的时候, 应当保留.

例如:

var a = new Date  

计算所有可直接计算的表达式

对于可直接计算出结果的表达式, 建议只保留代码运行的产生的结果.

反混淆前:

'Hello World!'.split(' ')  

反混淆后:

['Hello', 'World']

这个例子的转换是可能存在风险的, 只有在测试了split函数没有被修改的情况下, 才能进行化简, 一定要做足测试.

另外, 使用new RegExp(pattern, attributes)创建的正则表达式, 也应该被简写成直接量/pattern/attributes, new Array和new Object也需要视具体情况进行简写.

还原判断语句的短路逻辑简写

JavaScript的判断语句可以用短路逻辑简写, 简写后的代码在一定程度上降低了代码的可读性, 通常我们会将其还原.

反混淆前:

a === 'Hello World!' && b()  

反混淆后:

if(a === 'Hello World!'){  
  d()
}

还原变量类型转换简写

JavaScript的变量类型转换也可以被简写, 部分简写在JavaScript中被视为陷阱, 所以尽量将简写还原成等价代码或不影响运行效果的近似代码.

反混淆前:

function a(){  
    return '0x5f3759df'
}
var b = +a()  

反混淆后:

function a(){  
    return '0x5f3759df'
}
var b = Number(a())  

去除专门用于妨碍阅读的代码

一些混淆器会往表达式中添加一些无关紧要的表达式或变量赋值, 例如:

var a = Math.random(),  
    b = Math.round(a * 9),
    c = ''.split()[0],
    d = [1, 1, 1, 1, 1, 1, 1, 1, 1 ,1][b > 9 ? c : b]

当你发现一些数量或调用方式明显异常的代码, 就要好好检查一下是否是例子中的这种情况了, 类似的情况在反混淆中可能会遇到很多, 有时你甚至得重写函数.

流程顺序的安排

除”解密被加密的代码, 将代码结构尽可能还原至最接近原始代码的状态”、”猜解变量名和函数名”, 其他流程是可以交叉进行的, 但个人建议每次只处理1~2种情况, 以免发生混乱. 优先级方面, 先解决数量最多的混淆, 再解决最影响可读性的混淆, 其他的依次处理即可.

在反混淆过程中, 务必频繁进行测试, 以保证代码执行效果与混淆代码一致, 越早发现问题, 也越容易解决问题.

由于篇幅所限, “基于主函数的运行顺序整理”和”猜解变量名和函数名”将放在下一章《JavaScript 反混淆的一般套路和技巧: 转》中讲解.