javascript-控制流平坦化

本文最后更新于:2020年9月27日 晚上

有用的链接

在线开源混淆工具:https://obfuscator.io
在线AST解析:https://astexplorer.net

概述

通过引入状态机与循环,破坏代码上下文之间的阅读连续性和代码块之间的关联性
将若干个分散的小整体整合成一个巨大的循环体

  • 无法还原成原来具体的函数
  • 无法以函数为单位的调试方法,大幅度增加调试难度
  • 降低代码运行效率,提高爬虫运行时执行js的资源成本
  • 可根据js运行时检测到的某些因素自由跳转到蜜罐或跳出代码执行

描述过程的简单例子

例如图中的方式就是 将代码运行流程交给一个变量next来控制
每次执行完一小段代码后,都修改next的值,使得下次判断会执行另外的代码块

常见实现方式

  1. 多维数组
  2. 数值比较

总体

小规模控制流平坦化处理

准备实验代码

首先写一段js源码

1
2
3
4
5
6
7
8
9
10
function test(){
var i = [101, 102, 103, 104, 105, 106, 107];
i[1] = i[1] + 1;
output_str = [];
for(var ind=0; ind < i.length; ind++){
output_str.push(String.fromCharCode(i[ind]))
}
return output_str
}
console.log(test().join(''))

拿去在线混淆网站混淆 https://obfuscator.io/

输入原代码以后在下方设置混淆选项
然后点混淆拿到混淆后的代码,重新格式化以后拿到

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
function test() {
var _0xf74e59 = {
'IvmKa': '1|3|2|0|4',
'Ndyet': function (_0x419593, _0x22a11f) {
return _0x419593 < _0x22a11f;
},
'STiJR': function (_0x2d5759, _0x3449ae) {
return _0x2d5759 + _0x3449ae;
}
};
var _0x42bf99 = _0xf74e59['IvmKa']['split']('|');
var _0x3c36ca = 0x0;
while (!![]) {
switch (_0x42bf99[_0x3c36ca++]) {
case '0':
for (var _0x27bf7e = 0x0; _0xf74e59['Ndyet'](_0x27bf7e, _0x18a535['length']); _0x27bf7e++) {
output_str['push'](String['fromCharCode'](_0x18a535[_0x27bf7e]));
}
continue;
case '1':
var _0x18a535 = [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b];
continue;
case '2':
output_str = [];
continue;
case '3':
_0x18a535[0x1] = _0xf74e59['STiJR'](_0x18a535[0x1], 0x1);
continue;
case '4':
return output_str;
}
break;
}
}
console['log'](test()['join'](''));

代码特点:有一个循环来控制代码运行流程

处理思路

  1. 全局观察
    大致观察每一个代码结构
  • 是否有类似于DOM操作的代码
    Node.js环境下课没有DOM这种东西,运行会报错

  • 是否为纯计算型的循环体
    如果只是纯粹的计算,那么拿到结果就好,过程并不重要
    比如说上面的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var _0xf74e59 = {
    'IvmKa': '1|3|2|0|4',
    'Ndyet': function (_0x419593, _0x22a11f) {
    return _0x419593 < _0x22a11f;
    },
    'STiJR': function (_0x2d5759, _0x3449ae) {
    return _0x2d5759 + _0x3449ae;
    }
    };

    这种赋值,切割字符之类的,无二义性的东西直接记录一下结果就过了,不用管过程

  • 是否有try-catch异常捕获结构
    比如说用try-catch包围一个DOM操作,Node.js执行的时候报错,捕获到错误以后,相当于代码识别出你不是浏览器了,之后就可以跳转到死机代码或直接跳出,达到防御目的

  1. 整体分析与载入
    推荐断点位置:
  • 断点定于while开头部分
  • 断点定于try代码体第一行

while循环体整体取出构造原始函数

  1. 构造函数
    查缺补漏,在运行过程中通过不断地运行报错,补充确实的函数或数据

处理

对于小型的代码混淆,也许你还能看得出入口和出口大概是在什么地方
但对于那种动则几千行的大型混淆来说,几乎就不能看出什么东西来了

大型代码混淆指不定会有蜜罐
比如说发现你没有DOM,给你吐脏数据
比如说上下两步操作实践超过时长,判定你打了断点,让你死机

思路

由于这个函数没有传递参数,是相对固定的执行条件,那么可以通过输出每次执行的代码来得到代码的执行顺序
添加输出的这个操作可以通过AST来实现

过程

将代码丢进 https://astexplorer.net/ 进行AST分析


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!