JavaScript反混淆插件七:如何编写一个专用的插件?

本文缘由

随着反爬的升级,前端的JavaScript代码越来越难以阅读,一个简单的字符串声明竟然可以拆分成多行代码,虽然并不会给动态调试带来困难,但是在静态分析时着实让人难受。明明一行代码可以搞定的事情,偏偏写成了十行甚至百行,代码冗余非常严重。

我们以下面的代码为例,来讲讲如何还原。


   
  1. for (var e = "\u0270\u026D\u0274\u0274\u0277\u0234\u0249\u025B\u025C\u0229", a = "", s = 0; s < e.length; s++) {
  2. var r = e.charCodeAt(s) - 520;
  3. a += String.fromCharCode(r);
  4. }

代码很常见,一个for循环 + 一个String.fromCharCode 完成了代码的还原。在浏览器上运行可以得到结果:

其实运行下来,也就是将a的值设置为 "hello,AST!",如果结合实际的代码,其实可以发现它和下面的代码是等价的:

var a = "hello,AST!";

  

因为其他的变量根本就没有用到过!

也许你有疑问,为什么不转换成赋值语句,而变成声明语句,如果你处理过作用域就知道了。

从源代码:


   
  1. for (var e = "\u0270\u026D\u0274\u0274\u0277\u0234\u0249\u025B\u025C\u0229", a = "", s = 0; s < e.length; s++) {
  2. var r = e.charCodeAt(s) - 520;
  3. a += String.fromCharCode(r);
  4. }

变成目标代码:

var a = "hello,AST!";

  

现在只差一个AST的插件了。

分析

  1. 这是一个for循环,因此我们需要遍历 ForStatement

  2. 第二行源代码是一个声明语句,并且包含charCodeAt函数

  3. 第三行代码是一个表达式语句,并包含String.fromCharCode

根据这些特征,我们就可以写一个专用的插件:


   
  1. const types = require("@babel/types");
  2. const forToString = {
  3. //遍历ForStatement
  4. ForStatement(path) {
  5.         let body = path.get("body.body");
  6. if (!body || body.length !== 2)
  7.         //根据在线解析网站可以看出body的长度为 2
  8. return;
  9. if (!body[0].isVariableDeclaration() || !body[1].isExpressionStatement()) {
  10. //循环体的第一个语句为声明语句,第二个语句为表达式语句
  11. return;
  12. }
  13. let body0_code = body[0].toString();
  14. let body1_code = body[1].toString();
  15. if (body0_code.indexOf("charCodeAt") != -1 && body1_code.indexOf("String.fromCharCode") != -1)
  16.         {//根据上面的分析而来
  17. //dosomething
  18. }
  19. },
  20. }

核心代码的处理

上面下来判断的代码,下面来写核心代码。

  1. 运行for循环,得到 "hello,AST!" 这个值。

  2. 构造一个 VariableDeclaration 节点,并进行替换

如何得到这个for循环的结果呢?我这里借助 new Function ,代码如下:


   
  1. //获取赋值语句左边的 a
  2. let expression = body[1].node.expression;
  3. let name = expression.left.name;
  4. //根据Function函数进行构造
  5. let code = path.toString() + "\nreturn " + name;
  6. //构造并运行,即可得到for循环的结果
  7. let func = new Function("",code);
  8. let value = func();

根据value构造VariableDeclaration节点,代码如下:

let new_node = types.VariableDeclaration("var",[types.VariableDeclarator(types.Identifier(name),types.valueToNode(value))]);

  

然后再进行替换:

path.replaceWith(new_node);

  

代码合并起来是这样的:


   
  1. const for_to_string = {
  2. ForStatement(path) {
  3. let body = path.get("body.body");
  4. if (!body || body.length !== 2)
  5. return;
  6. if (!body[0].isVariableDeclaration() || !body[1].isExpressionStatement()) {
  7. return;
  8. }
  9. let body0_code = body[0].toString();
  10. let body1_code = body[1].toString();
  11. if (body0_code.indexOf("charCodeAt") != -1 && body1_code.indexOf("String.fromCharCode") != -1) {
  12. try {
  13. let expression = body[1].node.expression;
  14. let name = expression.left.name;
  15. let code = path.toString() + "\nreturn " + name;
  16. let func = new Function("",code);
  17. let value = func();
  18. let new_node = types.VariableDeclaration("var", [types.VariableDeclarator(types.Identifier(name), types.valueToNode(value))]);
  19. path.replaceWith(new_node);
  20.             } catch (e) {};
  21. }
  22. }
  23. }

这里我用了try...catch语句是因为在真正遍历的时候,运行func可能会报错,有可能需要某些依赖,因此用 try...catch语句来捕捉异常。

结语

随着多次的还原练习,只要是结构固定的代码,皆可以编写一键还原的工具,再使用reres映射进行动态调试即可,实在是保头发的不二选择。

文章来源: blog.csdn.net,作者:悦来客栈的老板,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq523176585/article/details/111570618

(完)