AST还原技术专题:浅谈去控制流平坦化的思路及方法

举报
悦来客栈的老板 发表于 2021/03/29 22:45:02 2021/03/29
【摘要】 一. while-switch结构的控制流 这类平坦化代码很简单,常见于经过obfuscator在线工具混淆后的控制流平坦化。一般代码段不会很长,常见的 switch-case 基本都在10个分支以内,因此是否还原,并不影响阅读。 代码举例: var _0x42b38e = "5|4|3|1|2|0"["split"]('|'), _0x435210 = 0; ...

一. while-switch结构的控制流

这类平坦化代码很简单,常见于经过obfuscator在线工具混淆后的控制流平坦化。一般代码段不会很长,常见的 switch-case 基本都在10个分支以内,因此是否还原,并不影响阅读。

代码举例:


   
  1. var _0x42b38e = "5|4|3|1|2|0"["split"]('|'), _0x435210 = 0;
  2. while (true) {
  3. switch (_0x42b38e[_0x435210++]) {
  4. case '0':
  5. _0x352bac[_0x4447b2] = _0x38b230;
  6. continue;
  7. case '1':
  8. _0x38b230["__proto__"] = _0x529196["bind"](_0x529196);
  9. continue;
  10. case '2':
  11. _0x38b230["toString"] = _0x1bd819["toString"]["bind"](_0x1bd819);
  12. continue;
  13. case '3':
  14. var _0x1bd819 = _0x352bac[_0x4447b2] || _0x38b230;
  15. continue;
  16. case '4':
  17. var _0x4447b2 = _0x124cae[_0x31cdb9];
  18. continue;
  19. case '5':
  20. var _0x38b230 = _0x529196["constructor"]['prototype']["bind"](_0x529196);
  21. continue;
  22. }
  23. break;
  24. }

变量_0x42b38e 充当着分发器的角色,_0x435210则是索引,每次索引自增匹配case,从case语句中取相关的代码,直到全部取完。这类代码结构固定,case语句中无更改索引值的代码,因此,取出来的代码顺序是固定的。使用AST编写插件很容易,并能达到一劳永逸的结果。

二.  while-if结构的控制流

这种控制流与第一种比较类似,也是在while循环外面直接定义一个分发器(数组元素全部是常量),只不过switch-case语句变成了if-else 结构,还有一点就是索引值(变量_0x435210)可能会在 代码中改变,不再是自增。其实如果仔细观察,这类代码可以直接改写成 switch-case结构。

代码举例:


   
  1. switch(dispatch++)
  2. {
  3. ......
  4. if (dispatch == 5)
  5. {
  6. if (somecode)
  7. {
  8. dispatch += 2;//在这里更改了dispatch的值,干扰了下一条执行的语句,会走向不同的分支
  9. }
  10. ......
  11. }
  12. else if (dispatch == 6)
  13. {
  14. ......
  15. }
  16. ......
  17. }

这类代码其实也很好处理,可以先还原成switch-case结构的代码,再进行处理。

唯一需要注意的是 索引值在代码中可能变化,这时需要考虑代码还原后的样子,可能是 while循环,也可能是 if-else 结构。

三.  while-switch-switch...结构的控制流

这类代码其实与上面的代码区别不大,只不过不再有分发器,而是给定 一个初始值,通过这个初始值分别计算出另外多个值来定位 switch-case-switch-case...节点,复杂度增加了。

switch语句嵌套了switch语句,甚至有多重嵌套,看起来确实无从下手。

其实也有技巧,既然代码变复杂了,那就想办法简化它。

对于 while-switch-switch 这种结构,如:


   
  1. while(dispatch)
  2. {
  3. .....//通过dispath计算出switch中的a和b
  4. switch(a)
  5. {
  6. ......
  7. case 11:
  8. switch(b)
  9. {
  10. ......
  11. case 11:
  12. {
  13. ......
  14. dispatch = 5;
  15. break;
  16. }
  17. ......
  18. }
  19. ......
  20. }
  21. }

经过分析,所有有用的代码基本都在第二个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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。