反混淆"控制流平坦化"
问题背景
业务逻辑非常依赖前端的安全产品,比如极验、顶象-无感验证、瑞数 等都会对前端代码做混淆。其中有一种强度较高的混淆方式,叫”控制流平坦化”。
“控制流平坦化”是将一些顺序执行的代码,变化成阅读难度更大的
while-switch-case
或者for-switch-case
代码。可以见ControlFlow这个项目的例子。
如果能够”反混淆”这种”控制流平坦化”混淆后的代码,就能让代码变得更容易阅读一些。
本文介绍”反控制流平坦化”的思路和实践过程,最终可以反混淆ControlFlow
和https://obfuscator.io/
两个工具的”控制流平坦化”。
反混淆思路:
- 按照”while-switch-case”执行顺序,拼接case语句
实现过程
怎么实现?
分为两步:
1
2* 第一步获取case块的执行顺序
* 第二部根据case块的执行顺序,拼接case块怎么获取”case块的执行顺序”?
思路是在每个case块中”注入代码”,然后在浏览器中执行js代码,执行过程”注入代码”就可以记录执行顺序。我理解这个类似”软件测试”中计算”代码覆盖率”时用到的插桩。
原始代码如下:
1
2
3
4
5
6
7
8while (!![]){
switch (...){
case "0":
...
case "1":
...
}
}插桩后,代码如下
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// 利用"插桩"记录while-switch-case执行顺序
// 集合 = {while id: [case1节点, case2节点,case3节点,case2节点]}
let while_id_stack = [] // 栈
let collection = {}
while (!![]){
while_statement_id = 111
while_id_stack.push(while_statement_id)
collection[while_statement_id] = []
switch (...){
case "0":
case_statement_id = 222
while_id_on_stack_op = while_id_stack[while_id_stack.length-1]
collection[while_id_on_stack_op].push(case_statement_id)
...
case "1":
...
}
while_id_stack.pop()
}
record(collection)浏览器运行后,集合中就存放有case节点的执行顺序。
上面都是伪代码,具体实现见 hook.js
怎么拼接case块?
伪代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18集合 = {while id: [case1节点, case2节点,case3节点,case2节点]}
函数1:
遍历ast,如果遇到while语句 {
得到当前while id
if while id in 集合:
顺序拼接各个case块内容
用"拼接后的顺序语句"替换"while语句"
从集合中删除 while id记录
}
while 集合中还有元素:
调用 函数1具体实现见 simple_decode.js
实现结果
可以将”while-switch-case代码块”变成”顺序执行的代码块”。
比如 PlayWithObfuscator/tests/encoded_js/simple.js 中的混淆后的代码
1 | while (!![]) { |
转换成 阅读性更好一些的代码
1 | if (_0x33g15b = _0x5153cd.charCodeAt(_0xeea791 += .75), _0x33g15b > 255) throw new t("'btoa' failed: The string to be encoded contains characters outside of the Latin1 range."); |
总结
目前属于demo阶段,可以对以下工具的”控制流平坦化”做反混淆:
测试结果在 tests目录 看到