AST还原技术专题:浅谈去控制流平坦化的思路及方法
一. while-switch结构的控制流
这类平坦化代码很简单,常见于经过obfuscator在线工具混淆后的控制流平坦化。一般代码段不会很长,常见的 switch-case 基本都在10个分支以内,因此是否还原,并不影响阅读。
代码举例:
-
var _0x42b38e = "5|4|3|1|2|0"["split"]('|'), _0x435210 = 0;
-
-
-
while (true) {
-
-
-
switch (_0x42b38e[_0x435210++]) {
-
-
-
case '0':
-
-
-
_0x352bac[_0x4447b2] = _0x38b230;
-
-
-
continue;
-
-
-
-
-
case '1':
-
-
-
_0x38b230["__proto__"] = _0x529196["bind"](_0x529196);
-
-
-
continue;
-
-
-
-
-
case '2':
-
-
-
_0x38b230["toString"] = _0x1bd819["toString"]["bind"](_0x1bd819);
-
-
-
continue;
-
-
-
-
-
case '3':
-
-
-
var _0x1bd819 = _0x352bac[_0x4447b2] || _0x38b230;
-
-
-
-
-
continue;
-
-
-
-
-
case '4':
-
-
-
var _0x4447b2 = _0x124cae[_0x31cdb9];
-
-
-
continue;
-
-
-
-
-
case '5':
-
-
-
var _0x38b230 = _0x529196["constructor"]['prototype']["bind"](_0x529196);
-
-
-
-
-
continue;
-
-
-
}
-
-
-
-
-
break;
-
-
-
}
变量_0x42b38e 充当着分发器的角色,_0x435210则是索引,每次索引自增匹配case,从case语句中取相关的代码,直到全部取完。这类代码结构固定,case语句中无更改索引值的代码,因此,取出来的代码顺序是固定的。使用AST编写插件很容易,并能达到一劳永逸的结果。
二. while-if结构的控制流
这种控制流与第一种比较类似,也是在while循环外面直接定义一个分发器(数组元素全部是常量),只不过switch-case语句变成了if-else 结构,还有一点就是索引值(变量_0x435210)可能会在 代码中改变,不再是自增。其实如果仔细观察,这类代码可以直接改写成 switch-case结构。
代码举例:
-
switch(dispatch++)
-
{
-
-
-
......
-
-
-
if (dispatch == 5)
-
-
-
{
-
-
-
if (somecode)
-
-
-
{
-
-
-
dispatch += 2;//在这里更改了dispatch的值,干扰了下一条执行的语句,会走向不同的分支
-
-
-
}
-
-
-
......
-
-
-
}
-
else if (dispatch == 6)
-
{
-
......
-
}
-
......
-
-
-
}
这类代码其实也很好处理,可以先还原成switch-case结构的代码,再进行处理。
唯一需要注意的是 索引值在代码中可能变化,这时需要考虑代码还原后的样子,可能是 while循环,也可能是 if-else 结构。
三. while-switch-switch...结构的控制流
这类代码其实与上面的代码区别不大,只不过不再有分发器,而是给定 一个初始值,通过这个初始值分别计算出另外多个值来定位 switch-case-switch-case...节点,复杂度增加了。
switch语句嵌套了switch语句,甚至有多重嵌套,看起来确实无从下手。
其实也有技巧,既然代码变复杂了,那就想办法简化它。
对于 while-switch-switch 这种结构,如:
-
while(dispatch)
-
-
-
{
-
-
-
.....//通过dispath计算出switch中的a和b
-
-
-
switch(a)
-
-
-
{
-
-
-
......
-
-
-
case 11:
-
-
-
switch(b)
-
-
-
{
-
-
-
......
-
-
-
case 11:
-
-
-
{
-
-
-
......
-
-
-
dispatch = 5;
-
-
-
break;
-
-
-
}
-
-
-
......
-
-
-
}
-
-
-
......
-
-
-
}
-
-
-
}
经过分析,所有有用的代码基本都在第二个switch中,当然有些核心代码也可能在第一个switch-case结构中,但是第一个switch大部分都充当了一个继续分发的角色。
如何简化代码,很简单,在此不表。总之,还原前可以做些预处理,不再受垃圾代码的干扰即可。
而对于 while-switch-switch-switch 结构,也可以先简化,再还原,和while-switch-switch 结构没啥区别。
总结:
复杂的代码简单化,也就是我常说的看看能不能转变成熟悉的代码
如何判断什么时候是while循环,什么时候是if-else语句。这个没有什么好的技巧,有时候需要手动还原去找规律找条件。
使用递归语句容易死循环,因此建议使用非递归代码。
备注:所有内容首发于公众号,之后会更新AST反混淆实战、Javascript基础知识、APP逆向、C++、数据结构与算法等等一系列教程,也会更新一些自己的学习心得等,欢迎大家关注。
文章来源: blog.csdn.net,作者:悦来客栈的老板,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/qq523176585/article/details/115302509
- 点赞
- 收藏
- 关注作者
评论(0)