0x1、基础部分


    AST:Abstract Syntax Tree(抽象语法树),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

    我知道,看完定义的你八成是一脸懵逼,什么叫做源代码语法结构的抽象表示?不用害怕,我在这里简单使用一句话为例来帮助大家理解:

    今天你喝水了吗?

    大家都知道这句话的意思就是问今天你喝水了吗,但你知道这句话是由哪些成分组成的吗?

    AST并没有想象中那么神秘 - 图1

    QQ截图20210708015835.jpg (3.71 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:39 上传

    以前上学的时候语文课肯定都学过,句子里有主语,谓语,宾语,定语等等,句子中的词又分为名词、动词、介词….各种词,当这些词按照某种规律形式组合以后,就形成了上面的那句话,

    而现在正在看文章的你肯定知道并理解上面那句话的意思,但是,你不一定知道这句话中每个词的词性,不知道哪个是主语,哪个是谓语,这就跟你知道 JavaScript,却不了解AST是一回事。

    所以:

    不会AST这件事本身不会对你学习使用JavaScript有任何影响,但如果你理解并掌握了AST,那么JavaScript 可能在你眼里就有些不太一样了,从某种程度上来说,了解掌握AST可以帮助你真正吃透 JavaScript 的语言精髓。

    既然如此,那怎样才能快速的了解AST?

    答案很简单,随写写句JS拿去AST分析一下,然后对着看看,相信聪明的你很快就能有所了解。

    在线 AST语法结构解析网站:https://astexplorer.net/

    下面来看看最最简单的JS

    1. var a=1;

    代码对应的AST语法树结构:

    AST并没有想象中那么神秘 - 图2

    Snipaste_2021-07-08_18-59-46.png (16.15 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:33 上传

    从上往下看,有Program、declarations、VariableDeclarator、Identifier、Literal,这些都AST的结构类型,除了这些还有很多,列表如下:

    AST并没有想象中那么神秘 - 图3

    AST.jpg (175.56 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:34 上传

    知道了这些有什么用呢?除了上面说的可以加深对JS的深层理解外,最大的用处就是对 JavaScript 代码进行混淆以及还原了。

    了解JavaScript 的小伙伴应该都知道,JS有非常非常多的语法糖,而对JS的加密混淆,其实就是在对这些语法糖的充分利用,当你既熟练掌握JavaScript ,又理解并掌握了AST以后,你就可以从AST语法树结构的视角,去对混淆后的JS代码进行某种程度的还原,有点类似于降维打击,因为JS加密混淆都是在JS代码的外在表现形式上做的手脚,而AST的视角则更偏向底层。

    0x2、混淆JS还原演示


    PS:环境问题请自行百度解决。

    测试代码:

    1. function MyFun(a,b){
    2. var c=a+b;
    3. var d=a*b+c;
    4. return c+d;
    5. }
    6. console.log(MyFun(10,20))

    加密混淆后的代码:

    1. var _0x305a = ['B8OBMcKcUQ==', 'w4Y2wrrCl8OT', 'wpHCpMKLRsOu', 'wpgfwo7DgMO4', 'wpVywozCqwHCucKV', 'HMK1VsOS', 'wrfCiSgfw7c0NGHCoAXCuH7CnyzDlMOdfXxHLDorfsOCwrzCnFNPw6I+cwQ=', 'SsO0PUdW', 'PcKRVQ==', 'UHbDj3xcw7ZY', 'UsO6OA==', 'wrjChmcfw70=', 'W8OnLURB', 'w6/CrATDncKcw6lF', 'IcOxw4DDiMOx', 'wr3Dp07DtUUfwrI=', 'N8KOesOsw58=', 'FR7DgS0p', 'AgPDjj0jcUI=', 'AsOYGMKFdg==', 'bcK0wrHCiMOt', 'w7HClAPDo8KVSsKe', 'wrbDhGR1', 'ABzDkCI1', 'Fg1fRsOE', 'w4XCocOJBGg=', 'w60AwpHCv8OJ', 'NcKLwpBPHW01', 'w4QGIWLDiw==', 'wqXCmmAT', 'FT9dMg==', 'LF7Du2sqwrrCqsKDRw==', 'wr/DsknDs3s=', 'wrXDhHR3BQ==', 'P8KKwphT', 'RHjDk2E=', 'w7FCY21yw7BV', 'w7tDa3E=', 'w77ClAo=', 'AcOWJMKKRsOCwpbDsRLDlHkTw5g/wqfCgDzCusOC', 'w6fDpAHDiRlpWQ==', 'wpgGwo3Dtg==', 'w4c+wqHDh2E=', 'fXjDpUspw5kAH1jDvcOGAMKhfiHCiFvCkcOLGMOqw5HDtsKTw5jDom7DmMOaYMKdwrs=', 'QhFqYQg=', 'wqHCsm3Ds0DCpsKB', 'w4cdwo4=', 'w75Cag==', 'w4YiEcO9wq1aIw==', 'w48XwovCvcOY', 'e1vDnHbCg8Kkdkp5', 'cEHDtUhR', 'asKwwonCjMOw', 'TsOaw4QMGsKswp4=', 'wqrDqULDqk8=', 'w7DCjHcsw4A=', 'w4N6worDksOo', 'WMKywpDDonI=', 'wo0GwqkKwrA=', 'DjRPKMKlwpROa8OCV8KxY8KYWGEuwobDgcO2', 'CcOZw5jDlcOO', 'bsK4wpLClsOT', 'w6YJw5APwoo=', 'w4EoHcO7wqU=', 'w4lHTTnCpcKkw6jCgUc=', 'wpNlwoHCvR7CocKZwoMe', 'w6XCt8O1I1A=', 'w7dff3Fv', 'FMKrQMORw6M=', 'w7fCgw7DtcKKUsKSLyA=', 'DMOyw6vDucO6', 'L1/Dt1nDuMOlwpQ=', 'w5MbLnLDgcKlwps=', 'wrXCvHHDrg==', 'w5EsHcOiwqc=', 'N8KUwo5QCw==', 'w6sdwo3Co8OUw7xB', 'VABoYw==', 'wrF7LhFwwo5O', 'Rj9O']; (function(_0x1f4b85, _0x305a4c) {
    2. var _0x796962 = function(_0x4b72d1) {
    3. while (--_0x4b72d1) {
    4. _0x1f4b85['push'](_0x1f4b85['shift']());
    5. }
    6. };
    7. _0x796962(++_0x305a4c);
    8. } (_0x305a, 0xe7));
    9. var _0x7969 = function(_0x1f4b85, _0x305a4c) {
    10. _0x1f4b85 = _0x1f4b85 - 0x0;
    11. var _0x796962 = _0x305a[_0x1f4b85];
    12. if (_0x7969['UkyMFX'] === undefined) { (function() {
    13. var _0x344906;
    14. try {
    15. var _0x1d345f = Function('return\x20(function()\x20' + '{}.constructor(\x22return\x20this\x22)(\x20)' + ');');
    16. _0x344906 = _0x1d345f();
    17. } catch(_0x1d3b2a) {
    18. _0x344906 = window;
    19. }
    20. var _0x14f0f1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
    21. _0x344906['atob'] || (_0x344906['atob'] = function(_0x3128ab) {
    22. var _0x23f475 = String(_0x3128ab)['replace'](/=+$/, '');
    23. var _0x32bdd4 = '';
    24. for (var _0x3da026 = 0x0,
    25. _0x372bba, _0x104d49, _0x5eb7a8 = 0x0; _0x104d49 = _0x23f475['charAt'](_0x5eb7a8++);~_0x104d49 && (_0x372bba = _0x3da026 % 0x4 ? _0x372bba * 0x40 + _0x104d49: _0x104d49, _0x3da026++%0x4) ? _0x32bdd4 += String['fromCharCode'](0xff & _0x372bba >> ( - 0x2 * _0x3da026 & 0x6)) : 0x0) {
    26. _0x104d49 = _0x14f0f1['indexOf'](_0x104d49);
    27. }
    28. return _0x32bdd4;
    29. });
    30. } ());
    31. var _0x570ba5 = function(_0x257551, _0x1f613d) {
    32. var _0x3e9282 = [],
    33. _0x434903 = 0x0,
    34. _0x33fc11,
    35. _0x249601 = '',
    36. _0x2f07c3 = '';
    37. _0x257551 = atob(_0x257551);
    38. for (var _0x464f7e = 0x0,
    39. _0x4b3e63 = _0x257551['length']; _0x464f7e < _0x4b3e63; _0x464f7e++) {
    40. _0x2f07c3 += '%' + ('00' + _0x257551['charCodeAt'](_0x464f7e)['toString'](0x10))['slice']( - 0x2);
    41. }
    42. _0x257551 = decodeURIComponent(_0x2f07c3);
    43. var _0x94001b;
    44. for (_0x94001b = 0x0; _0x94001b < 0x100; _0x94001b++) {
    45. _0x3e9282[_0x94001b] = _0x94001b;
    46. }
    47. for (_0x94001b = 0x0; _0x94001b < 0x100; _0x94001b++) {
    48. _0x434903 = (_0x434903 + _0x3e9282[_0x94001b] + _0x1f613d['charCodeAt'](_0x94001b % _0x1f613d['length'])) % 0x100;
    49. _0x33fc11 = _0x3e9282[_0x94001b];
    50. _0x3e9282[_0x94001b] = _0x3e9282[_0x434903];
    51. _0x3e9282[_0x434903] = _0x33fc11;
    52. }
    53. _0x94001b = 0x0;
    54. _0x434903 = 0x0;
    55. for (var _0x27a73e = 0x0; _0x27a73e < _0x257551['length']; _0x27a73e++) {
    56. _0x94001b = (_0x94001b + 0x1) % 0x100;
    57. _0x434903 = (_0x434903 + _0x3e9282[_0x94001b]) % 0x100;
    58. _0x33fc11 = _0x3e9282[_0x94001b];
    59. _0x3e9282[_0x94001b] = _0x3e9282[_0x434903];
    60. _0x3e9282[_0x434903] = _0x33fc11;
    61. _0x249601 += String['fromCharCode'](_0x257551['charCodeAt'](_0x27a73e) ^ _0x3e9282[(_0x3e9282[_0x94001b] + _0x3e9282[_0x434903]) % 0x100]);
    62. }
    63. return _0x249601;
    64. };
    65. _0x7969['tMJLOX'] = _0x570ba5;
    66. _0x7969['vDRtQv'] = {};
    67. _0x7969['UkyMFX'] = !![];
    68. }
    69. var _0x4b72d1 = _0x7969['vDRtQv'][_0x1f4b85];
    70. if (_0x4b72d1 === undefined) {
    71. if (_0x7969['SqRviR'] === undefined) {
    72. _0x7969['SqRviR'] = !![];
    73. }
    74. _0x796962 = _0x7969['tMJLOX'](_0x796962, _0x305a4c);
    75. _0x7969['vDRtQv'][_0x1f4b85] = _0x796962;
    76. } else {
    77. _0x796962 = _0x4b72d1;
    78. }
    79. return _0x796962;
    80. };
    81. function _0x556652(_0x4a2332, _0x2634dc) {
    82. var _0x94d946 = function() {
    83. if (_0x7969('0xb', 'X*5E') !== _0x7969('0xc', 'WhTf')) {
    84. if (fn) {
    85. var _0x33221e = fn[_0x7969('0x35', 'hsTm')](context, arguments);
    86. fn = null;
    87. return _0x33221e;
    88. }
    89. } else {
    90. var _0x43616c = !![];
    91. return function(_0x550c05, _0x13d9c2) {
    92. if (_0x7969('0xa', '9uv1') !== _0x7969('0x1d', 'wdO$')) {
    93. var _0x296878 = _0x43616c ?
    94. function() {
    95. if (_0x7969('0x22', 'NGkH') !== _0x7969('0x40', 't$pJ')) {
    96. if (_0x13d9c2) {
    97. if (_0x7969('0x21', '9kDW') === _0x7969('0x17', 'k(YQ')) {
    98. that[_0x7969('0x16', 'r8p)')] = function(_0x1c2524) {
    99. var _0x2972cc = {};
    100. _0x2972cc[_0x7969('0x11', 'k3XH')] = _0x1c2524;
    101. _0x2972cc[_0x7969('0x1f', 'Cu4J')] = _0x1c2524;
    102. _0x2972cc[_0x7969('0x3a', 'R*hj')] = _0x1c2524;
    103. _0x2972cc[_0x7969('0x26', 'V6wH')] = _0x1c2524;
    104. _0x2972cc[_0x7969('0x41', 'Bt5v')] = _0x1c2524;
    105. _0x2972cc[_0x7969('0x4e', 'kamb')] = _0x1c2524;
    106. _0x2972cc[_0x7969('0x10', 'a4uQ')] = _0x1c2524;
    107. _0x2972cc[_0x7969('0x9', 'uP&h')] = _0x1c2524;
    108. return _0x2972cc;
    109. } (func);
    110. } else {
    111. var _0x34a69a = _0x13d9c2[_0x7969('0x20', 'sT$v')](_0x550c05, arguments);
    112. _0x13d9c2 = null;
    113. return _0x34a69a;
    114. }
    115. }
    116. } else {
    117. that = window;
    118. }
    119. }: function() {};
    120. _0x43616c = ![];
    121. return _0x296878;
    122. } else {
    123. var _0x2c3174 = {};
    124. _0x2c3174[_0x7969('0x8', 'pE[N')] = func;
    125. _0x2c3174[_0x7969('0x32', 'WhTf')] = func;
    126. _0x2c3174[_0x7969('0x47', 'sDkG')] = func;
    127. _0x2c3174[_0x7969('0x27', 'YgD$')] = func;
    128. _0x2c3174[_0x7969('0x23', '9uv1')] = func;
    129. _0x2c3174[_0x7969('0x4a', '(^8)')] = func;
    130. _0x2c3174[_0x7969('0x2a', 'Cu4J')] = func;
    131. _0x2c3174[_0x7969('0x25', 'AWI1')] = func;
    132. return _0x2c3174;
    133. }
    134. };
    135. }
    136. } ();
    137. var _0x23c1f6 = _0x94d946(this,
    138. function() {
    139. var _0x6e83c1 = function() {};
    140. var _0x3f3777;
    141. try {
    142. if (_0x7969('0x33', 'JKNM') !== _0x7969('0x45', 'k(YQ')) {
    143. var _0x584a01 = Function(_0x7969('0x30', 'uP&h') + _0x7969('0xf', 'V6wH') + ');');
    144. _0x3f3777 = _0x584a01();
    145. } else {
    146. var _0x30c130 = Function(_0x7969('0x44', 'YgD$') + _0x7969('0x34', 'Jler') + ');');
    147. _0x3f3777 = _0x30c130();
    148. }
    149. } catch(_0x5365b4) {
    150. if (_0x7969('0x19', 'D3T3') !== _0x7969('0x1c', 'uP&h')) {
    151. _0x3f3777 = window;
    152. } else {
    153. _0x3f3777[_0x7969('0x1', 'AWI1')][_0x7969('0x38', 'sJv5')] = _0x6e83c1;
    154. _0x3f3777[_0x7969('0x12', 'O(f6')][_0x7969('0x2c', 'O(f6')] = _0x6e83c1;
    155. _0x3f3777[_0x7969('0x1e', 'kamb')][_0x7969('0x48', 'aCn%')] = _0x6e83c1;
    156. _0x3f3777[_0x7969('0x3e', ']zyO')][_0x7969('0xe', 'D3T3')] = _0x6e83c1;
    157. _0x3f3777[_0x7969('0x31', 'jA(H')][_0x7969('0x4c', 'sJv5')] = _0x6e83c1;
    158. _0x3f3777[_0x7969('0x24', '*20m')][_0x7969('0x3b', 'G1i2')] = _0x6e83c1;
    159. _0x3f3777[_0x7969('0x7', '1MEH')][_0x7969('0x2a', 'Cu4J')] = _0x6e83c1;
    160. _0x3f3777[_0x7969('0x0', 'QGrn')][_0x7969('0x14', 'V6wH')] = _0x6e83c1;
    161. }
    162. }
    163. if (!_0x3f3777[_0x7969('0x39', 'aCn%')]) {
    164. if (_0x7969('0x4b', 'NGkH') !== _0x7969('0x3c', 'O(f6')) {
    165. _0x3f3777[_0x7969('0x36', 'WU*x')] = function(_0x4bdab5) {
    166. if (_0x7969('0x29', 'zEsY') === _0x7969('0x46', 'wdO$')) {
    167. var _0x5f33bc = {};
    168. _0x5f33bc[_0x7969('0x37', 'R*hj')] = _0x4bdab5;
    169. _0x5f33bc[_0x7969('0x6', 'hsTm')] = _0x4bdab5;
    170. _0x5f33bc[_0x7969('0x43', 'JQ7t')] = _0x4bdab5;
    171. _0x5f33bc[_0x7969('0x2e', 'sJv5')] = _0x4bdab5;
    172. _0x5f33bc[_0x7969('0x3d', 'wdO$')] = _0x4bdab5;
    173. _0x5f33bc[_0x7969('0x49', 'U(TC')] = _0x4bdab5;
    174. _0x5f33bc[_0x7969('0x3f', 'zEsY')] = _0x4bdab5;
    175. _0x5f33bc[_0x7969('0x1a', 'sT$v')] = _0x4bdab5;
    176. return _0x5f33bc;
    177. } else {
    178. var _0x1b640b = firstCall ?
    179. function() {
    180. if (fn) {
    181. var _0x3dd5c2 = fn[_0x7969('0x4d', 'D3T3')](context, arguments);
    182. fn = null;
    183. return _0x3dd5c2;
    184. }
    185. }: function() {};
    186. firstCall = ![];
    187. return _0x1b640b;
    188. }
    189. } (_0x6e83c1);
    190. } else {
    191. var _0x345d2c = fn[_0x7969('0x4', '*20m')](context, arguments);
    192. fn = null;
    193. return _0x345d2c;
    194. }
    195. } else {
    196. _0x3f3777[_0x7969('0x36', 'WU*x')][_0x7969('0x2f', 'kamb')] = _0x6e83c1;
    197. _0x3f3777[_0x7969('0x5', '9uv1')][_0x7969('0x2', 'WU*x')] = _0x6e83c1;
    198. _0x3f3777[_0x7969('0x12', 'O(f6')][_0x7969('0x42', 'vJ8P')] = _0x6e83c1;
    199. _0x3f3777[_0x7969('0x2d', 'sJv5')][_0x7969('0x2b', '*20m')] = _0x6e83c1;
    200. _0x3f3777[_0x7969('0xd', '(^8)')][_0x7969('0x15', 'a4uQ')] = _0x6e83c1;
    201. _0x3f3777[_0x7969('0x1b', 'sT$v')][_0x7969('0x28', 'ijQK')] = _0x6e83c1;
    202. _0x3f3777[_0x7969('0x1b', 'sT$v')][_0x7969('0x3', 'aCn%')] = _0x6e83c1;
    203. _0x3f3777[_0x7969('0x18', 'zEsY')][_0x7969('0x4f', 'k(YQ')] = _0x6e83c1;
    204. }
    205. });
    206. _0x23c1f6();
    207. var _0x245e10 = _0x4a2332 + _0x2634dc;
    208. var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    209. return _0x245e10 + _0x5d196d;
    210. }
    211. console[_0x7969('0x13', 'a4uQ')](_0x556652(0xa, 0x14));

    检查混淆代码是否可以正常执行:

    AST并没有想象中那么神秘 - 图4

    00.png (80.82 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:34 上传

    混淆后的代码可以正常执行,

    对混淆后的代码进行初步分析

    AST并没有想象中那么神秘 - 图5

    03.png (99.6 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:34 上传

    上图中可以很清晰的看到,标注红色框的函数 _0x7969 在整个被混淆的JS中大量出现,并且,都是以 _0x7969[‘xxx’] 或 _0x7969(‘xxx’,’xxx’) 的形式出现,其中的xxx则全部都是乱七八糟的字符串,由此可以得出结论:

    _0x7969 这个函数就是这个混淆JS 加密字符串的解密函数

    使用支持JS格式的文本编辑器,对混淆后的代码进行收缩,如下

    AST并没有想象中那么神秘 - 图6

    01.png (68.44 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:35 上传

    我给标注出了解密函数跟执行函数(或者叫原始功能函数,叫啥无所谓,明白意思就行)

    混淆JS的大体结构我们清楚了,下面第一步,就是对执行函数的加密字符串进行解密

    我们先将混沌JS中的解密函数复制出来保存到de.js,将执行函数部分复制出来保存为en.js,再新建一个obTest.js,用于编写AST还原代码
    AST并没有想象中那么神秘 - 图7

    04.png (22.74 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:35 上传

    de.js 需要添加一行,导出函数,名称就是上面的 _0x7969,如下:

    AST并没有想象中那么神秘 - 图8

    05.png (140.74 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:35 上传

    打开obTest.js,开始进入AST 搬砖环节

    1. const fs = require("fs");
    2. const esprima = require('esprima'); //ECMAScript(JavaScript) 解析架构,主要用于多用途分析。
    3. const estraverse = require('estraverse'); //语法树遍历辅助库(提供了两个静态方法,estraverse.traverse 和 estraverse.replace。前者单纯遍历 AST 的节点,通过返回值控制是否继续遍历到叶子节点;而 replace 方法则可以在遍历的过程中直接修改 AST,实现代码重构功能。)
    4. const escodegen = require('escodegen');//AST的 ECMAScript (也称为JavaScript)代码生成器
    5. const iconv = require("iconv-lite");
    6. const de = require("./de");
    7. //读取加密混淆的执行函数Js
    8. var content = fs.readFileSync('./en.js',{encoding:'binary'});
    9. var buf = new Buffer.from(content,'binary');
    10. var code = iconv.decode(buf,'utf-8');
    11. //将混淆后的执行函数Js转换为AST
    12. var ast = esprima.parse(code);
    1. //字符串解密
    2. var ast = esprima.parse(code);
    3. ast = estraverse.replace(ast, {
    4. enter: function (node) {
    5. if (node.type == 'CallExpression' && //标注1
    6. node.callee.type == 'Identifier' && //标注2
    7. node.callee.name == "_0x7969" && //解密函数名
    8. node.arguments.length == 2 &&
    9. node.arguments[0].type == 'Literal' && //标注3
    10. node.arguments[1].type == 'Literal') //标注4
    11. {
    12. var val = de._0x7969(node.arguments[0].value,node.arguments[1].value); //标注5
    13. return {
    14. type: esprima.Syntax.Literal,
    15. value: val,
    16. raw: val
    17. }
    18. }
    19. }
    20. });
    21. code = escodegen.generate(ast) //将AST转换为JS
    22. console.log(code)

    上面代码看不懂?没关系,一张图搞定

    AST并没有想象中那么神秘 - 图9

    06.png (51.5 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:37 上传

    左边为混淆代码,右边为AST语法树结构,上面我们讲过了,所有加密的字符串都是以 _0x7969(‘xxx’,’xxx’) 这样的结构出现,所以,我们需要对结构进行筛查判断,找到所有这一类的节点

    上面代码中的 if …. && …&& 一大串就是干这个事的,拿一行为例说明:

    if (node.type == ‘CallExpression’ && //标注1
    判断 node.type(当前节点类型)是否为 CallExpression(对应看上面的图),是不是马上就清楚了

    执行代码看一下效果(加密字符串解密后)

    1. function _0x556652(_0x4a2332, _0x2634dc) {
    2. var _0x94d946 = function () {
    3. if ('wxqXe' !== 'wxqXe') {
    4. if (fn) {
    5. var _0x33221e = fn['apply'](context, arguments);
    6. fn = null;
    7. return _0x33221e;
    8. }
    9. } else {
    10. var _0x43616c = !![];
    11. return function (_0x550c05, _0x13d9c2) {
    12. if ('NDYGh' !== 'bvJko') {
    13. var _0x296878 = _0x43616c ? function () {
    14. if ('RzWPN' !== 'fJYYY') {
    15. if (_0x13d9c2) {
    16. if ('CONdw' === 'YqJRn') {
    17. that['console'] = function (_0x1c2524) {
    18. var _0x2972cc = {};
    19. _0x2972cc['log'] = _0x1c2524;
    20. _0x2972cc['warn'] = _0x1c2524;
    21. _0x2972cc['debug'] = _0x1c2524;
    22. _0x2972cc['info'] = _0x1c2524;
    23. _0x2972cc['error'] = _0x1c2524;
    24. _0x2972cc['exception'] = _0x1c2524;
    25. _0x2972cc['table'] = _0x1c2524;
    26. _0x2972cc['trace'] = _0x1c2524;
    27. return _0x2972cc;
    28. }(func);
    29. } else {
    30. var _0x34a69a = _0x13d9c2['apply'](_0x550c05, arguments);
    31. _0x13d9c2 = null;
    32. return _0x34a69a;
    33. }
    34. }
    35. } else {
    36. that = window;
    37. }
    38. } : function () {
    39. };
    40. _0x43616c = ![];
    41. return _0x296878;
    42. } else {
    43. var _0x2c3174 = {};
    44. _0x2c3174['log'] = func;
    45. _0x2c3174['warn'] = func;
    46. _0x2c3174['debug'] = func;
    47. _0x2c3174['info'] = func;
    48. _0x2c3174['error'] = func;
    49. _0x2c3174['exception'] = func;
    50. _0x2c3174['table'] = func;
    51. _0x2c3174['trace'] = func;
    52. return _0x2c3174;
    53. }
    54. };
    55. }
    56. }();
    57. var _0x23c1f6 = _0x94d946(this, function () {
    58. var _0x6e83c1 = function () {
    59. };
    60. var _0x3f3777;
    61. try {
    62. if ('LbvcK' !== 'qYROQ') {
    63. var _0x584a01 = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
    64. _0x3f3777 = _0x584a01();
    65. } else {
    66. var _0x30c130 = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
    67. _0x3f3777 = _0x30c130();
    68. }
    69. } catch (_0x5365b4) {
    70. if ('BUJQE' !== 'qkHzB') {
    71. _0x3f3777 = window;
    72. } else {
    73. _0x3f3777['console']['log'] = _0x6e83c1;
    74. _0x3f3777['console']['warn'] = _0x6e83c1;
    75. _0x3f3777['console']['debug'] = _0x6e83c1;
    76. _0x3f3777['console']['info'] = _0x6e83c1;
    77. _0x3f3777['console']['error'] = _0x6e83c1;
    78. _0x3f3777['console']['exception'] = _0x6e83c1;
    79. _0x3f3777['console']['table'] = _0x6e83c1;
    80. _0x3f3777['console']['trace'] = _0x6e83c1;
    81. }
    82. }
    83. if (!_0x3f3777['console']) {
    84. if ('rlkwv' !== 'CXTGb') {
    85. _0x3f3777['console'] = function (_0x4bdab5) {
    86. if ('aziuQ' === 'aziuQ') {
    87. var _0x5f33bc = {};
    88. _0x5f33bc['log'] = _0x4bdab5;
    89. _0x5f33bc['warn'] = _0x4bdab5;
    90. _0x5f33bc['debug'] = _0x4bdab5;
    91. _0x5f33bc['info'] = _0x4bdab5;
    92. _0x5f33bc['error'] = _0x4bdab5;
    93. _0x5f33bc['exception'] = _0x4bdab5;
    94. _0x5f33bc['table'] = _0x4bdab5;
    95. _0x5f33bc['trace'] = _0x4bdab5;
    96. return _0x5f33bc;
    97. } else {
    98. var _0x1b640b = firstCall ? function () {
    99. if (fn) {
    100. var _0x3dd5c2 = fn['apply'](context, arguments);
    101. fn = null;
    102. return _0x3dd5c2;
    103. }
    104. } : function () {
    105. };
    106. firstCall = ![];
    107. return _0x1b640b;
    108. }
    109. }(_0x6e83c1);
    110. } else {
    111. var _0x345d2c = fn['apply'](context, arguments);
    112. fn = null;
    113. return _0x345d2c;
    114. }
    115. } else {
    116. _0x3f3777['console']['log'] = _0x6e83c1;
    117. _0x3f3777['console']['warn'] = _0x6e83c1;
    118. _0x3f3777['console']['debug'] = _0x6e83c1;
    119. _0x3f3777['console']['info'] = _0x6e83c1;
    120. _0x3f3777['console']['error'] = _0x6e83c1;
    121. _0x3f3777['console']['exception'] = _0x6e83c1;
    122. _0x3f3777['console']['table'] = _0x6e83c1;
    123. _0x3f3777['console']['trace'] = _0x6e83c1;
    124. }
    125. });
    126. _0x23c1f6();
    127. var _0x245e10 = _0x4a2332 + _0x2634dc;
    128. var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    129. return _0x245e10 + _0x5d196d;
    130. }
    131. console['log'](_0x556652(10, 20));

    可以看到,被混淆加密的字符串已经解密完成,一般情况下,这种程度的代码已经可以进行调试分析了,但我们的追求可以更高,各位同学可以翻到前面看看,测试加密混淆的原始JS才几行,虽然现在加密字符串解密了,但代码里依然存在大量的垃圾指令,还等什么,继续盘

    分析一下第一步解密字符串完成后的代码,如下图

    AST并没有想象中那么神秘 - 图10

    08.png (38.77 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:36 上传

    代码里有很多 if (‘xxx’=’xxx’) 、if (‘xxx’!’xxx’)、if (‘xxx’ === ‘yyy’)、if (‘xxx’ !== ‘yyy)

    会点js的一眼就看出来了,这不就是垃圾代码吗,明明一样还搞个判断分支,所以,把所以这类的 if 处理掉,可以将代码量直接砍掉一半,动手

    1. // 处理if('xx'==='xx')
    2. var ast = esprima.parse(code);
    3. ast = estraverse.replace(ast, {
    4. enter: function (node,parent) {
    5. if (node.type == 'IfStatement' &&
    6. node.test.type == 'BinaryExpression')
    7. {
    8. if(node.test.left.value == node.test.right.value) { //if('aaa'==='aaa'){}
    9. switch (node.test.operator) {
    10. case '!==' : //if('aaa'!=='aaa'){}
    11. for (var idx = 0; idx < node.consequent.body.length; idx++) {
    12. parent.body.splice(parent.body.indexOf(node), 0, node.consequent.body[idx]);
    13. }
    14. parent.body.splice(parent.body.indexOf(node), 1);
    15. break
    16. case '===' : //if('aaa'==='aaa'){}
    17. for (var idx = 0; idx < node.alternate.body.length; idx++) {
    18. parent.body.splice(parent.body.indexOf(node), 0, node.alternate.body[idx]);
    19. }
    20. parent.body.splice(parent.body.indexOf(node), 1);
    21. break
    22. }
    23. } else { //if('aaa'==='bbb'){}
    24. switch (node.test.operator) {
    25. case '!==' : //if('aaa'!=='bbb'){}
    26. for (var idx = 0; idx < node.consequent.body.length; idx++) {
    27. parent.body.splice(parent.body.indexOf(node), 0, node.consequent.body[idx]);
    28. }
    29. parent.body.splice(parent.body.indexOf(node), 1);
    30. break
    31. case '===' : //if('aaa'==='bbb'){}
    32. for (var idx = 0; idx < node.alternate.body.length; idx++) {
    33. parent.body.splice(parent.body.indexOf(node), 0, node.alternate.body[idx]);
    34. }
    35. parent.body.splice(parent.body.indexOf(node), 1);
    36. break
    37. }
    38. }
    39. }
    40. }
    41. });

    跟第一步字符串解密的代码相比差别不算很大,前面也是一大堆的类型判断,确定遍历到的节点就是我们需要找的if …. 这类的垃圾节点,找到后,将正常分支的内容插入到父节点,然后删除当前节点,为什么是这样的操作,同样上图说明

    AST并没有想象中那么神秘 - 图11

    09.png (68.16 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:35 上传

    左侧是我们需要找的 if …. 这样的节点,绿色部分代表会执行部分,黑色部分代表不会执行的部分

    AST并没有想象中那么神秘 - 图12

    10.png (35.71 KB, 下载次数: 0)

    下载附件

    2021-7-9 13:37 上传

    而我们需要做的就是,将绿色部分会执行的代码 consequent 节点插入到它的父节点,也就是TryStatement节点下,然后再把整个的 IfStatement 节点删除,这样就完成了对这类垃圾if…语句的处理

    看下效果(处理垃圾if语句结果)

    1. function _0x556652(_0x4a2332, _0x2634dc) {
    2. var _0x94d946 = function () {
    3. if (fn) {
    4. var _0x33221e = fn['apply'](context, arguments);
    5. fn = null;
    6. return _0x33221e;
    7. }
    8. }();
    9. var _0x23c1f6 = _0x94d946(this, function () {
    10. var _0x6e83c1 = function () {
    11. };
    12. var _0x3f3777;
    13. try {
    14. var _0x584a01 = Function('return (function() ' + '{}.constructor("return this")( )' + ');');
    15. _0x3f3777 = _0x584a01();
    16. } catch (_0x5365b4) {
    17. _0x3f3777 = window;
    18. }
    19. if (!_0x3f3777['console']) {
    20. _0x3f3777['console'] = function (_0x4bdab5) {
    21. var _0x1b640b = firstCall ? function () {
    22. if (fn) {
    23. var _0x3dd5c2 = fn['apply'](context, arguments);
    24. fn = null;
    25. return _0x3dd5c2;
    26. }
    27. } : function () {
    28. };
    29. firstCall = ![];
    30. return _0x1b640b;
    31. }(_0x6e83c1);
    32. } else {
    33. _0x3f3777['console']['log'] = _0x6e83c1;
    34. _0x3f3777['console']['warn'] = _0x6e83c1;
    35. _0x3f3777['console']['debug'] = _0x6e83c1;
    36. _0x3f3777['console']['info'] = _0x6e83c1;
    37. _0x3f3777['console']['error'] = _0x6e83c1;
    38. _0x3f3777['console']['exception'] = _0x6e83c1;
    39. _0x3f3777['console']['table'] = _0x6e83c1;
    40. _0x3f3777['console']['trace'] = _0x6e83c1;
    41. }
    42. });
    43. _0x23c1f6();
    44. var _0x245e10 = _0x4a2332 + _0x2634dc;
    45. var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    46. return _0x245e10 + _0x5d196d;
    47. }
    48. console['log'](_0x556652(10, 20));

    是不是清爽多了,当然,还有可以优化的空间,无非就是制定添加规则,这就留给各位当课后作业吧。

    最后附上这个混淆JS通过AST还原的最终结果:

    1. function _0x556652(_0x4a2332, _0x2634dc) {
    2. var _0x245e10 = _0x4a2332 + _0x2634dc;
    3. var _0x5d196d = _0x4a2332 * _0x2634dc + _0x245e10;
    4. return _0x245e10 + _0x5d196d;
    5. }
    6. console['log'](_0x556652(10, 20));

    对比一下测试加密源代码:

    1. function MyFun(a,b){
    2. var c=a+b;
    3. var d=a*b+c;
    4. return c+d;
    5. }
    6. console.log(MyFun(10,20))

    视频演示:https://www.bilibili.com/video/BV1E64y147Q6/