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

2020-12-01 08:08 菜鸟学Python编程


本文分析的是某国外的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 试试:


  function _0x33d434(_0x62c6cd) {    var _0x2f2a79;    var _0x2cecf5 = _0x288ca0();    var _0x3dacec = new _0x2ca1a3["Array"](_0x2cecf5["length"]);    for (var _0x59635d = 0x0; _0x59635d < _0x2cecf5["length"]; _0x59635d++) {      _0x3dacec[_0x59635d] = _0x1efa58(_0x62c6cd + _0x2cecf5[_0x59635d]);    }    _0x50089f();    var _0x518d86 = "\x94\xF6|V\xF2\x87\xDA~";    var _0x5c5427 = _0x518d86["substr"](0x00x5);    var _0x4116fd = _0x518d86["substr"](0x5);    var _0x39af56 = "\xEF\xF7q\x05\xE2\x12\xBE@";    var _0xeda3f4 = 0x3;    while (--_0xeda3f4) {      _0x39af56 = _0x39af56["substr"](0x1) + _0x39af56[0x0];    }    var _0xfd1c9f = _0x39af56;    var _0x3cc1e3 = _0x39af56["length"] - 0x1;    while (--_0x3cc1e3) {      _0xfd1c9f = _0xfd1c9f["substr"](0x1) + _0xfd1c9f[0x0];    }    var _0xf84116 = _0x27b9("0x65", _0x5c5427 + _0x4116fd);    var _0xca7a76 = _0x3dacec["join"]();    var _0x5bc98e = "";    for (var _0x59635d = 0x0; _0x59635d < _0xf84116["length"]; _0x59635d++) {      _0x5bc98e += (_0xf84116["charCodeAt"](_0x59635d) + _0xca7a76["charCodeAt"](_0x59635d % _0xca7a76["length"]))["toString"](0x10);    }    _0x50089f();    _0x7b92["push"](btoa(_0x62c6cd));    _0x2f2a79 = btoa(_0x27b9(_0x7b92["length"] - 0x1, _0xf84116["substr"](0x00x5)) + ",digest=" + _0xca7a76 + ",s=" + _0x5bc98e);    _0x7b92["pop"]();    _0x28e982("___utmvc", _0x2f2a79, 0x14);  }


原来调用了 _0x28e982 这个函数:


  function _0x28e982(_0x491bc3, _0x4bb0d1, _0x5dfe4c) {    var _0x35bb3c = "";    if (_0x5dfe4c) {      var _0x4a870b = new _0x2ca1a3["Date"]();      _0x4a870b["setTime"](_0x4a870b["getTime"]() + _0x5dfe4c * 0x3e8);      var _0x35bb3c = "; expires=" + _0x4a870b["toGMTString"]();    }    _0x240c09["cookie"] = _0x491bc3 + "=" + _0x4bb0d1 + _0x35bb3c + "; path=/";  }


逻辑很清晰,就是一个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:


   function _0x33d434(_0x62c6cd) {    var _0x2f2a79;
var _0x2cecf5 = _0x288ca0();
var _0x3dacec = new _0x2ca1a3["Array"](_0x2cecf5["length"]);
for (var _0x59635d = 0x0; _0x59635d < _0x2cecf5["length"]; _0x59635d++) { _0x3dacec[_0x59635d] = _0x1efa58(_0x62c6cd + _0x2cecf5[_0x59635d]); }


首先看  _0x288ca0 这个函数:


  function _0x288ca0() {    var _0x1b9363 = new _0x2ca1a3["Array"]();    var _0x5f24c8 = new _0x2ca1a3["RegExp"]("^\\s?incap_ses_");    var _0x30d99e = _0x240c09["cookie"]["split"](";");    for (var _0x59e2f0 = 0x0; _0x59e2f0 < _0x30d99e["length"]; _0x59e2f0++) {      var _0x5c56a9 = _0x30d99e[_0x59e2f0]["substr"](0x0, _0x30d99e[_0x59e2f0]["indexOf"]("="));      var _0x727f65 = _0x30d99e[_0x59e2f0]["substr"](_0x30d99e[_0x59e2f0]["indexOf"]("=") + 0x1, _0x30d99e[_0x59e2f0]["length"]);      if (_0x5f24c8["test"](_0x5c56a9)) {        _0x1b9363[_0x1b9363["length"]] = _0x727f65;      }    }    _0x50089f();    return _0x1b9363;  }


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



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



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



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



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


  function _0x288ca0() {    var _0x1b9363 = new _0x2ca1a3["Array"]();    var _0x5f24c8 = new _0x2ca1a3["RegExp"]("^\\s?incap_ses_");    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==" ];    for (var _0x59e2f0 = 0x0; _0x59e2f0 < _0x30d99e["length"]; _0x59e2f0++) {      var _0x5c56a9 = _0x30d99e[_0x59e2f0]["substr"](0x0, _0x30d99e[_0x59e2f0]["indexOf"]("="));
var _0x727f65 = _0x30d99e[_0x59e2f0]["substr"](_0x30d99e[_0x59e2f0]["indexOf"]("=") + 0x1, _0x30d99e[_0x59e2f0]["length"]);
if (_0x5f24c8["test"](_0x5c56a9)) { _0x1b9363[_0x1b9363["length"]] = _0x727f65; }    }    _0x50089f(); return _0x1b9363; }


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



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


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


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


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


运行报错,


_0x1efa58 is not defined


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


 function _0x1efa58(_0x94e2f4{    var _0x22b242 = 0x0;    for (var _0x1856e7 = 0x0; _0x1856e7 < _0x94e2f4["length"]; _0x1856e7++) {      _0x22b242 += _0x94e2f4["charCodeAt"](_0x1856e7);    }    //_0x50089f();    return _0x22b242;  }


再次运行后,继续报错,


_0x62c6cd is not defined

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




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


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"]];_0x466892["push"](["'v4aead2a1deb1b76130ffbb35218fe2b608e5e059756393a0b06378a23b50336a'.toString()", "value"]);


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

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



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


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


再次运行,结果如下:



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


继续将整个值计算出来:




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


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

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"]];_0x466892["push"](["'v4aead2a1deb1b76130ffbb35218fe2b608e5e059756393a0b06378a23b50336a'.toString()", "value"]);


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


_0x3b88bc(_0x466892)

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


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

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



因此,你需要这样去补:


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


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

本文章转载自公众号:IPyhon

首页 - Python 相关的更多文章: