JS逆向:拨云见日,AST解混淆让代码分析变得如此简单

举报
悦来客栈的老板 发表于 2020/12/28 23:33:32 2020/12/28
【摘要】 本文分析的是某国外的cdn,如果国内有使用该cdn的站点,请与本人联系立即删除,谢谢。 遵纪守法是每个公民应尽的义务和责任。 要分析的网站如下: "aHR0cHM6Ly9ib29raW5nLnZvbG90ZWEuY29tLw==" 需要着重分析的是类似下面的url: "aHR0cHM6Ly9ib29raW5nLnZvbG90ZWEuY29tL19JbmNhc...

本文分析的是某国外的cdn,如果国内有使用该cdn的站点,请与本人联系立即删除,谢谢。

遵纪守法是每个公民应尽的义务和责任。

要分析的网站如下:

"aHR0cHM6Ly9ib29raW5nLnZvbG90ZWEuY29tLw=="

  

需要着重分析的是类似下面的url:

"aHR0cHM6Ly9ib29raW5nLnZvbG90ZWEuY29tL19JbmNhcHN1bGFfUmVzb3VyY2U/U1dKSVlMV0E9NzE5ZDM0ZDMxYzhlM2E2ZTZmZmZkNDI1ZjdlMDMyZjMmbnM9MSZjYj0xNjc2OTgyNzkx"

  

它返回的是一段JavaScript代码:

由于代码太长,我就放个截图吧,它是一个自执行的函数,首先定义了很长的一串字符串,然后再进行 String.fromCharCode操作,最后eval运行,会生成一个  ___utmvc 字段的cookie,你可以在这个页面看到它:

https://booking.volotea.com/_Incapsula_Resource?SWKMTFSR=1&e=0.2468114615986583

  

在这里将 此时的cookie值贴出来吧,方便后续比对:

/oA8Rt/lbDWHcnO+WZ9if+CgW5rbtdD3irqpowAoj5MJToS4myQS2GcV9HW6xuOedImC3ad9KdD+i/yBIlqIYg70N55pPqfjMGZxG14QELhFn6o/KiGkX6A2452xoqbG5wvQxvkygVIoNV/mGJNTPgTQquXxlJ7cyjRmTHxteDWrFTf6A+y5sOeARNLKCMSlhDTYIpYcvI8wajq8Z4h4Xbtkoz+JUehzO+oO+z7UmIihfVig/d/Pz0PHOLDyRHVBwWG/GCkBf7oYGVPJF2xCncm1KA88e5Et6YAhusA2NJq0gA4Yrg+Vqky3roJp2lbPZVyaOSQ34QFf32mxTkgIwWC4esVHANil2vwuIckM/L6HA+xqZQTyB9Br8fGdD/21oY7g/U6iAaxU+sJGnvIPRqz7nDTPOdgyDF+HA1FqJ8O0fDtsi9JEHb+ZJ6waVGYGg9U8Mq2dyj3iPgqevNOK8X/mHojutQlRIkWlzsvANvq7KI03YaBsKEKPdzM5o5//Y7VI0t7lOSHpCep5cIMyM7LbT/8CHumKzPfDpXiLbeV/Q7YvhXOuDX61VZ25MVxnC2YGVj4AIiTLL1qRwsmMqIZHcLu0X3/svtrWNuFvjlP7ia2NffWHZegGDfz++oodPVOIqP9HVnKvYtXSt5MtDtAMb/cSr5TLmIamo5LdXqokFZBVwur6kg27YXAI9Xf5nD6CdScdczAVVwJs1zfc7xZfOv82gxU1fn5Aby8eqr3TEc+JNLD+n7vPHdJSB30P12aEYciw/ngKV4J+yJqKRHFxmpO2GN3GotHq986P260rCmVxv0AuIVptdr9fXEa0mTXkGGsP/smC5zle3AqP/lHHvEsGrjqGsaInrC3S3rWEHgFsZOKyo6g3LTWGeIGwV/1WGsSzV3GvXlpZQNSqxmniqKNuJIXUQGm/HVFV6zg8ZJbOR0nTll5fYm2aGI/+2+52PMaK6wvO4FCTJme4/h2nd0byxGLIQz+3V7Lc7LWPVSuRwhVrtBGvP5X5lwqolwZADItxGjPkNfARgO25T5oeh2BxRvHpWF2Ufr4dJdiTy95KfKnx9fOfc3FATOK/ROBpyjDXFgcr+qAOl3/XNlqV5oXv1trw156LKN5dfps7tArFug7jPT9/lrRnLxwXAzKVWdV6RCmV3l2DjYcsw4qMlUlh6Tbcwvun7nBKS1xyVyFTaUeb7Yw0ZiOEV9BwlkJFRMNf8CiPxUcbhojPFk+YoDnhzxGKj9TaQvNZeQpQHBTJSNrWiaXCFyb9Cr3Gheg4JE111jRZyOmGEQuJLcoEULMxRWA84iE3AIu9h9n1UlCF/K64LyMuQemHwwksSMffux/LPrCs+2V3XwKq8uNjejkvZ2oKR0E8nwSh/4eVZ2oqYbv54V8wl2Rn19gTRkf5s/ADqkIyyQd6XMigAlBO9CYA7wA+TpZmXDd+kN/PyPDscDmeW0Ade1J++e3jjw+6XJZCBpaG88rJevJx64F+VmclgZ4ahRzEEw10vhUjKVf1Lf9gxM8QEtuyS6tgPjYTQmPjLt2kR3MfDtc0meMDuWQS4HJcWLAmlM8A/iEo/0+kISFCOWba6XgCS43N+HUuqCa9yFtUfXTIqtumYAvvtCxkaWdlc3Q9MTEyNzk0LHM9ODQ4OTdhYWU5ZjgyYTg4MjdhOGE4OTc5ODE5ZTgyNjc4ZDlhNzU3M2E1NzBhZTZiNjVhMzc2NzlhNDg4NjBhMzdiYWRhZDZiN2Y3MjZmNzQ=

  

如图:

将这段源代码复制到浏览器控制台,把eval 换成 console.log,回车,再把结果美化下,如图:

很多很多这样的十六进制字符串,用之前介绍的方法,将其转换为 人能识别的字符串,还原后成这样:

又变成了我们非常熟悉的ob混淆了,这种代码之前有介绍过怎么处理,通过操作AST将加密的函数调用解密成字符串:

这比之前的代码又要清爽很多,我们注意到有几个这样变量:

var _0x1df825 = {.....}

  

这种也是可以进行处理的,论坛上有相关的处理方法,请进行参考。处理后的部分代码:

同一位置 的截图如上,而这种 while--- switch语句也是可以通过操作AST来进行(请参考论坛上的文章)处理的,处理后的部分代码截图:

大致还原成这样就差不多了。我们在代码中直接搜索 ___utmvc 试试:


   
  1. function _0x33d434(_0x62c6cd) {
  2.     var _0x2f2a79;
  3.     var _0x2cecf5 = _0x288ca0();
  4.     var _0x3dacec = new _0x2ca1a3["Array"](_0x2cecf5["length"]);
  5. for (var _0x59635d = 0x0; _0x59635d < _0x2cecf5["length"]; _0x59635d++) {
  6. _0x3dacec[_0x59635d] = _0x1efa58(_0x62c6cd + _0x2cecf5[_0x59635d]);
  7.     }
  8.     _0x50089f();
  9.     var _0x518d86 = "\x94\xF6|V\xF2\x87\xDA~";
  10.     var _0x5c5427 = _0x518d86["substr"](0x00x5);
  11.     var _0x4116fd = _0x518d86["substr"](0x5);
  12. var _0x39af56 = "\xEF\xF7q\x05\xE2\x12\xBE@";
  13.     var _0xeda3f4 = 0x3;
  14. while (--_0xeda3f4) {
  15. _0x39af56 = _0x39af56["substr"](0x1) + _0x39af56[0x0];
  16.     }
  17.     var _0xfd1c9f = _0x39af56;
  18.     var _0x3cc1e3 = _0x39af56["length"] - 0x1;
  19. while (--_0x3cc1e3) {
  20. _0xfd1c9f = _0xfd1c9f["substr"](0x1) + _0xfd1c9f[0x0];
  21.     }
  22.     var _0xf84116 = _0x27b9("0x65", _0x5c5427 + _0x4116fd);
  23.     var _0xca7a76 = _0x3dacec["join"]();
  24.     var _0x5bc98e = "";
  25. for (var _0x59635d = 0x0; _0x59635d < _0xf84116["length"]; _0x59635d++) {
  26. _0x5bc98e += (_0xf84116["charCodeAt"](_0x59635d) + _0xca7a76["charCodeAt"](_0x59635d % _0xca7a76["length"]))["toString"](0x10);
  27.     }
  28.     _0x50089f();
  29.     _0x7b92["push"](btoa(_0x62c6cd));
  30.     _0x2f2a79 = btoa(_0x27b9(_0x7b92["length"] - 0x1, _0xf84116["substr"](0x00x5)) + ",digest=" + _0xca7a76 + ",s=" + _0x5bc98e);
  31.     _0x7b92["pop"]();
  32. _0x28e982("___utmvc", _0x2f2a79, 0x14);
  33. }

原来调用了 _0x28e982 这个函数:


   
  1. function _0x28e982(_0x491bc3, _0x4bb0d1, _0x5dfe4c) {
  2.     var _0x35bb3c = "";
  3. if (_0x5dfe4c) {
  4.       var _0x4a870b = new _0x2ca1a3["Date"]();
  5.       _0x4a870b["setTime"](_0x4a870b["getTime"]() + _0x5dfe4c * 0x3e8);
  6. var _0x35bb3c = "; expires=" + _0x4a870b["toGMTString"]();
  7.     }
  8. _0x240c09["cookie"] = _0x491bc3 + "=" + _0x4bb0d1 + _0x35bb3c + "; path=/";
  9. }

逻辑很清晰,就是一个cookie赋值的功能,并且这个值是通过 参数传递进来的,也就是上面代码中的 _0x2f2a79,它是在这里赋值的:

 _0x2f2a79 = btoa(_0x27b9(_0x7b92["length"] - 0x1, _0xf84116["substr"](0x0, 0x5)) + ",digest=" + _0xca7a76 + ",s=" + _0x5bc98e)

  

原来值是经过 base64编码的,我们将之前保存的cookie值解码看看:

以及:

解码后看到了 digest=112794, 以及

s=84897aae9f82a8827a8a8979819e82678d9a7573a570ae6b65a37679a48860a37badad6b7f726f74;

  

 再一次佐证了生成的位置。前面是一段看不懂的字符串,我们从这个 digest 作为爆破点,它的值是  _0xca7a76,最后的生成位置在这里:

var _0xca7a76 = _0x3dacec["join"]();

  

继续往上追 _0x3dacec:


   
  1. function _0x33d434(_0x62c6cd) {
  2. var _0x2f2a79;
  3. var _0x2cecf5 = _0x288ca0();
  4. var _0x3dacec = new _0x2ca1a3["Array"](_0x2cecf5["length"]);
  5. for (var _0x59635d = 0x0; _0x59635d < _0x2cecf5["length"]; _0x59635d++) {
  6. _0x3dacec[_0x59635d] = _0x1efa58(_0x62c6cd + _0x2cecf5[_0x59635d]);
  7. }

首先看  _0x288ca0 这个函数:


   
  1. function _0x288ca0() {
  2.     var _0x1b9363 = new _0x2ca1a3["Array"]();
  3.     var _0x5f24c8 = new _0x2ca1a3["RegExp"]("^\\s?incap_ses_");
  4.     var _0x30d99e = _0x240c09["cookie"]["split"](";");
  5. for (var _0x59e2f0 = 0x0; _0x59e2f0 < _0x30d99e["length"]; _0x59e2f0++) {
  6.       var _0x5c56a9 = _0x30d99e[_0x59e2f0]["substr"](0x0, _0x30d99e[_0x59e2f0]["indexOf"]("="));
  7.       var _0x727f65 = _0x30d99e[_0x59e2f0]["substr"](_0x30d99e[_0x59e2f0]["indexOf"]("=") + 0x1, _0x30d99e[_0x59e2f0]["length"]);
  8. if (_0x5f24c8["test"](_0x5c56a9)) {
  9. _0x1b9363[_0x1b9363["length"]] = _0x727f65;
  10. }
  11.     }
  12.     _0x50089f();
  13. return _0x1b9363;
  14.   }

是一段对 cookie操作的代码,这段代码拿到浏览器肯定是无法运行的,而且cookie的值早已改变,因此需要将这个响应是的cookie代码复制下来进行同样的操作:

我这里用的是火狐浏览器,因为我在谷歌浏览器上安装了插件,后面的代码有检测这个插件信息,会有干扰,处理起来不是很方便,因此我直接上一个没有插件的浏览器:

又因为直接在浏览器上进行cookie赋值,然后进行操作,会有错误:

这里只 split 出了一个字符串,肯定是不对的,我们直接搞一个普通的字符串split试试:

将这段代码替换进去即可:


   
  1. function _0x288ca0() {
  2.     var _0x1b9363 = new _0x2ca1a3["Array"]();
  3.     var _0x5f24c8 = new _0x2ca1a3["RegExp"]("^\\s?incap_ses_");
  4.     var _0x30d99e = [ "dtSa=-"" dtCookie=1$E7FB92CBB7FBB1FC5FF8E30DD2DCE492|ea7c4b59f27d43eb|1"" ASP.NET_SessionId=4q44krz2ez3elmj0yzj33a3y"" geoInfo=%7b%22geoIp%22%3a2742941454%2c%22ip%22%3a%22163.125.247.14%22%2c%22continent%22%3a%22AS%22%2c%22country%22%3a%22CN%22%2c%22city%22%3a%22Shenzhen%22%2c%22currencyCode%22%3a%22CNY%22%2c%22displayCurrency%22%3a%22USD%22%2c%22latitude%22%3a22.5333%2c%22longitude%22%3a114.1333%2c%22nearestStation%22%3a%22ARK%22%2c%22selectedCurrency%22%3a%22USD%22%2c%22proposedCurrency%22%3a%22USD%22%2c%22multicurrency%22%3a%22true%22%7d"" tokenInfo=YTc2M2I2NmMtYWY5ZS00NTQ5LWI5ZTEtMjAyMDA1MTAxMDQzNDAtNHE0NGtyejJlejNlbG1qMHl6ajMzYTN5"" skysales=!Kq7SB+PO/C+ceuc8ac0SZ4cwGUzSo+xRgVJyWqQ7MsPUeBrSLwP47Kc3G60M05gvdg3Mzc6esR915ac="" ak_bmsc=ED4CEB49C8564F5CFA51B7676AB804647D38DA39853B0000DCDAB75E9FE4491C~plWLTGb0mCa7xbnSj4xNL/P8qL4FEjWlb3wclYRkPpR6hTbMOBK+LLao1UcYIXYlNmX69xEtt8PfNm+Dz5i8+Yi6JXhzq1TME/Z3HFD4VhJQfJl7Qe7mRr6Sg3bVkFrGX3v07Ha7ZEkRSvVQ7RhvfL/kB8LcsZ26cri5aFVytSQFZqrhlaMA3zea2Awp1wToZM/2ROY/7wcfE+NcF5hOb1EWcZ8Fikj3XWfNH2yJLYmDo="" visid_incap_1895301=Tt1zbPCESTK/r3VzH4f95trat14AAAAAQUIPAAAAAABVDXqy6Jps1PJU7XM/v/8h"" nlbi_1895301=BNMkF3yZniMb8R43DWaj5AAAAAD/pJINRErKpKVBTRoo1P4k"" incap_ses_895_1895301=2/MZXcUNA3/iTzGHy65rDNzat14AAAAAi5FxnRPDlgGDDChRiyp6WA==" ];
  5. for (var _0x59e2f0 = 0x0; _0x59e2f0 < _0x30d99e["length"]; _0x59e2f0++) {
  6. var _0x5c56a9 = _0x30d99e[_0x59e2f0]["substr"](0x0, _0x30d99e[_0x59e2f0]["indexOf"]("="));
  7. var _0x727f65 = _0x30d99e[_0x59e2f0]["substr"](_0x30d99e[_0x59e2f0]["indexOf"]("=") + 0x1, _0x30d99e[_0x59e2f0]["length"]);
  8. if (_0x5f24c8["test"](_0x5c56a9)) {
  9. _0x1b9363[_0x1b9363["length"]] = _0x727f65;
  10. }
  11.     }
  12.     _0x50089f();
  13. return _0x1b9363;
  14. }

最后,新开一个标签页,将代码复制进去,然后缺啥补啥,让这个函数得以运行起来,注意,_0x50089f 这个函数没有什么用,可以直接删除,我得到的结果是这样的:

这样,这三行代码得以解决:


   
  1.    var _0x2f2a79;
  2.    var _0x2cecf5 = _0x288ca0();
  3.    var _0x3dacec = new _0x2ca1a3["Array"](_0x2cecf5["length"]);

直接在浏览器上运行即可,再看下面的这个for循环:


   
  1. for (var _0x59635d = 0x0; _0x59635d < _0x2cecf5["length"]; _0x59635d++) {
  2. _0x3dacec[_0x59635d] = _0x1efa58(_0x62c6cd + _0x2cecf5[_0x59635d]);
  3. }

运行报错,

_0x1efa58 is not defined

  

这是一个函数,直接复制到控制台运行:


   
  1.  function _0x1efa58(_0x94e2f4{
  2.     var _0x22b242 = 0x0;
  3. for (var _0x1856e7 = 0x0; _0x1856e7 < _0x94e2f4["length"]; _0x1856e7++) {
  4. _0x22b242 += _0x94e2f4["charCodeAt"](_0x1856e7);
  5.     }
  6.     //_0x50089f();
  7. return _0x22b242;
  8. }

再次运行后,继续报错,

_0x62c6cd is not defined

  

看代码  function _0x33d434(_0x62c6cd) { 这个原来是参数,我们需要找到它的实参,即看 _0x33d434 这个函数是在哪里调用的:

又追到了这里,实参是 _0x3b88bc(_0x466892),这时我们得计算出它的结果,先看 _0x466892 ,有定义和更新:


   
  1. var _0x466892 = [["navigator", "exists"], ["navigator.vendor", "value"], ["navigator.appName", "value"], ["navigator.plugins.length==0", "value"], ["navigator.platform", "value"], ["navigator.webdriver", "value"], ["platform", "plugin_extentions"], ["ActiveXObject", "exists"], ["webkitURL", "exists"], ["_phantom", "exists"], ["callPhantom", "exists"], ["chrome", "exists"], ["yandex", "exists"], ["opera", "exists"], ["opr", "exists"], ["safari", "exists"], ["awesomium", "exists"], ["puffinDevice", "exists"], ["__nightmare", "exists"], ["domAutomation", "exists"], ["domAutomationController", "exists"], ["_Selenium_IDE_Recorder", "exists"], ["document.__webdriver_script_fn", "exists"], ["document.$cdc_asdjflasutopfhvcZLmcfl_", "exists"], ["process.version", "exists"], ["global.require", "exists"], ["global.process", "exists"], ["WebAssembly", "exists"], ["window.toString()", "value"], ["navigator.cpuClass", "exists"], ["navigator.oscpu", "exists"], ["navigator.connection", "exists"], ["navigator.language=='C'", "value"], ["window.outerWidth==0", "value"], ["window.outerHeight==0", "value"], ["window.WebGLRenderingContext", "exists"], ["window.constructor.toString()", "value"], ["document.documentMode", "value"], ["eval.toString().length", "value"]];
  2. _0x466892["push"](["'v4aead2a1deb1b76130ffbb35218fe2b608e5e059756393a0b06378a23b50336a'.toString()", "value"]);

看这个值,都是与浏览器相关的特征,也拷贝到控制台运行,

再将 _0x3b88bc 这个函数 同样拷贝到浏览器运行,然后再赋值给 _0x62c6cd

得到了这么一串与浏览器特征相关的数据,值出来了,再运行这个for循环吧:


   
  1. for (var _0x59635d = 0x0; _0x59635d < _0x2cecf5["length"]; _0x59635d++) {
  2. _0x3dacec[_0x59635d] = _0x1efa58(_0x62c6cd + _0x2cecf5[_0x59635d]);
  3. }

再次运行,结果如下:

结果出来了,与之前的 值是一样的,我们再来看s的值:

继续将整个值计算出来:

只此,在浏览器上面已正确调试出结果。那如果要在node下面运行成功,要该如何呢?

可以看到,与node唯一的差异只是浏览器特征的检测,也就是这个object:


   
  1. var _0x466892 = [["navigator", "exists"], ["navigator.vendor", "value"], ["navigator.appName", "value"], ["navigator.plugins.length==0", "value"], ["navigator.platform", "value"], ["navigator.webdriver", "value"], ["platform", "plugin_extentions"], ["ActiveXObject", "exists"], ["webkitURL", "exists"], ["_phantom", "exists"], ["callPhantom", "exists"], ["chrome", "exists"], ["yandex", "exists"], ["opera", "exists"], ["opr", "exists"], ["safari", "exists"], ["awesomium", "exists"], ["puffinDevice", "exists"], ["__nightmare", "exists"], ["domAutomation", "exists"], ["domAutomationController", "exists"], ["_Selenium_IDE_Recorder", "exists"], ["document.__webdriver_script_fn", "exists"], ["document.$cdc_asdjflasutopfhvcZLmcfl_", "exists"], ["process.version", "exists"], ["global.require", "exists"], ["global.process", "exists"], ["WebAssembly", "exists"], ["window.toString()", "value"], ["navigator.cpuClass", "exists"], ["navigator.oscpu", "exists"], ["navigator.connection", "exists"], ["navigator.language=='C'", "value"], ["window.outerWidth==0", "value"], ["window.outerHeight==0", "value"], ["window.WebGLRenderingContext", "exists"], ["window.constructor.toString()", "value"], ["document.documentMode", "value"], ["eval.toString().length", "value"]];
  2. _0x466892["push"](["'v4aead2a1deb1b76130ffbb35218fe2b608e5e059756393a0b06378a23b50336a'.toString()", "value"]);

以及这个函数检测的结果:

_0x3b88bc(_0x466892)

  

因此在node环境下面只需要将这些特征补上即可,以

["navigator", "exists"], ["navigator.vendor", "value"]

  

这两个为例,一个是判断是否存在,一个是计算出它的值,

因此,你需要这样去补:


   
  1. navigator = {};
  2. navigator.vendor = "";

补特征是个细心的过程,做的多了,也就很容易了。

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

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

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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