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

本文分析的是某国外的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"](0x0, 0x5);
    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"](0x0, 0x5)) + ",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 = "";

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

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
js逆向AST混淆是一种通过析和修改JavaScript的抽象语法树(AST)来还原混淆代码的过程。首先,我们需要获取到混淆代码AST表示形式。然后,根据特定的反混淆算法,对AST进行遍历和修改,以还原原始代码的结构和逻辑。在这个过程中,我们可以使用不同的技术和工具来帮助我们完成反混淆任务。 在提供的引用中,涉及了一些对AST进行遍历和修改的代码片段。例如,在引用中,使用了traverse函数来遍历AST,然后通过修改AST节点来进行替换和替换。在引用中,通过迭代和遍历AST,找到量名和取值方法名,然后将它们替换或删除。在引用中,使用了traverse函数和eval函数来移除赋值表达式和成员表达式。 以上是一些常见的技术和方法,用于js逆向AST混淆。具体的反混淆过程可能因代码结构和混淆方式而有所不同。为了成功反混淆代码,可能需要更多的详细信息和专业知识。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [【JavaScript 逆向AST混淆](https://blog.csdn.net/pyzzd/article/details/130613135)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值