某条反爬JS分析(1):准备工作


本文仅用于技术学习、交流,切莫用于非法用途,读者的一切行为后果自行承担。

背景

最近研究了一下某条的反爬。某条针对文章使用的是 Cookie 中的 _ac_nonce__ac_signature,针对文章列表之类的 API 则是在 GET 请求的 URL 参数中带上签名参数 _signature。Cookie 中的签名和 URL 参数中的签名实际上都是使用 acrawler.js 来计算的。最新版的 acrawler.js 做了运行环境检测,使用各种 JS 执行器或者 node.js 都是无法模拟的(早先的版本在引入 jsdom 后是可以的),Selenium 也行不通。

搞了几天,总算是搞了出来,写几篇博文稍作记(zhuāng)录(bī)。

预先分析

首先抓下包,看看哪些参数需要分析,哪些可以在响应中获得。

首先是 Cookie 中的几个必要的参数,tt_webidcsrftoken 可以通过访问任意页面得到,而 s_v_web_id__tasessionId 则需要通过 JS 来生成。其它 Cookie 都是非必须的。

另有一个比较奇怪的 Cookie tt_scid,需要通过接口 https://xxbg.snssdk.com/websdk/v1/getInfo?q=xxxxx&callback=xxx 来得到。而对这个接口的访问,似乎是无规律的,有时候会在第一次下拉时访问,有时候连续下拉几十次都不会访问。当这个 Cookie 存在时,请求签名会变长许多,并且根据初步观察,应该是把 tt_scid 处理后直接拼在请求签名的后面。初步猜测,访问 API 可以不带这个 Cookie,但是带上的时候,请求签名也要做相应的改变。

当同时带有上述 5 个 Cookie 时,访问文章列表 API 的 URL 如下:

'https://www.toutiao.com/c/user/article/'
+ '?page_type=1'
+ '&user_id=4377795668'
+ '&max_behot_time=1592405348'
+ '&count=20'
+ '&as=A1852E5E2A34233'
+ '&cp=5EEAA4E243238E1'
+ '&_signature=_02B4Z6wo00901Zk3vyAAAIBDh4Gslr-BX8WZMruAADirAYtjy4FBx.kn-WjOpZFHL7kk.k5jhE7G.0RtZrEs0QfJzqyDPSFbsF7ajiJFnci7Ch8.3PhAeIg8kBkzGyNM4xK17m35a9pOji2p10'

其响应如下:

{
    "login_status":false,
    "has_more":true,
    "next":{
        "max_behot_time":1592402062
    },
    "page_type":1,
    "message":"success",
    "data":[
        {
            "image_url":"//p3.pstatp.com/list/190x124/pgc-image/S2BFDBP4Fu7oy4",
            "single_mode":true,
            "abstract":"英国卫生部16日宣布,即日起政府正式批准本国医疗体系使用地塞米松来治疗医院中那些需要吸氧以及使用呼吸机的新冠病患。地塞米松是一种人工合成的皮质类固醇,已被广泛用于治疗多种症状,如严重过敏、哮喘等,并且价格低廉。",
            "image_list":[
                {
                    "url":"//p3.pstatp.com/list/pgc-image/S2BFDBP4Fu7oy4",
                    "width":900,
                    "url_list":[
                        {
                            "url":"http://p3.pstatp.com/list/pgc-image/S2BFDBP4Fu7oy4"
                        },
                        {
                            "url":"http://pb9.pstatp.com/list/pgc-image/S2BFDBP4Fu7oy4"
                        },
                        {
                            "url":"http://pb1.pstatp.com/list/pgc-image/S2BFDBP4Fu7oy4"
                        }
                    ],
                    "uri":"list/pgc-image/S2BFDBP4Fu7oy4",
                    "height":600
                },
                {
                    "url":"//p1.pstatp.com/list/pgc-image/S2BFDLTGFTHEbY",
                    "width":900,
                    "url_list":[
                        {
                            "url":"http://p1.pstatp.com/list/pgc-image/S2BFDLTGFTHEbY"
                        },
                        {
                            "url":"http://pb3.pstatp.com/list/pgc-image/S2BFDLTGFTHEbY"
                        },
                        {
                            "url":"http://pb9.pstatp.com/list/pgc-image/S2BFDLTGFTHEbY"
                        }
                    ],
                    "uri":"list/pgc-image/S2BFDLTGFTHEbY",
                    "height":600
                },
                {
                    "url":"//p1.pstatp.com/list/pgc-image/S2BFDUT98KtRRn",
                    "width":900,
                    "url_list":[
                        {
                            "url":"http://p1.pstatp.com/list/pgc-image/S2BFDUT98KtRRn"
                        },
                        {
                            "url":"http://pb3.pstatp.com/list/pgc-image/S2BFDUT98KtRRn"
                        },
                        {
                            "url":"http://pb9.pstatp.com/list/pgc-image/S2BFDUT98KtRRn"
                        }
                    ],
                    "uri":"list/pgc-image/S2BFDUT98KtRRn",
                    "height":600
                }
            ],
            "more_mode":true,
            "tag":"news_health",
            "tag_url":"news_health",
            "title":"英国批准地塞米松用于治疗部分新冠病患",
            "has_video":false,
            "chinese_tag":"健康",
            "source":"新华网客户端",
            "group_source":2,
            "comments_count":"1",
            "composition":8,
            "media_url":"/m4377795668/",
            "go_detail_count":"97",
            "middle_mode":false,
            "gallary_image_count":0,
            "detail_play_effective_count":"0",
            "visibility":3,
            "source_url":"/item/6839327530185392648/",
            "item_id":"6839327530185392648",
            "article_genre":"article",
            "display_url":"//www.xinhuanet.com/photo/2020-06/17/c_1126127492.htm",
            "behot_time":"1天内",
            "has_gallery":false,
            "group_id":"6839327530185392648"
        }
        // 省略19],
    "is_self":false
}

分析 URL 中的参数,page_type 是固定的,user_id 是文章发布者的 id,max_behot_time 则在上一次请求的响应中得到(首次取 0),count 可以固定取 20,ascp_signature 都需要通过 JS 生成。

于是,需要逆向分析的一共有 s_v_web_id__tasessionIdascp_signature 这 5 个变量,显然 _signature 会是最难搞的。

经过简单的分析(过程略),可以发现 s_v_web_id__tasessionIdascp 这几个变量都是通过 index_xxx.js 或者 lib_xxx.js 来生成的,这两个文件只做了简单的压缩,而没有做任何混淆和其它防护。它们的生成算法都比较简单,这里就不写了。

代码初步处理

扒掉外衣

首先把 acrawler.js 下载下来,本地写一个 html 引入它,用浏览器打开,然后把 JS 格式化:

原始JS格式化

整串代码的形式为 Function(code)(),也就是执行代码 code。这里 code 的形式为:

function (t) {
  return '...这里是一个很长的加密字符串...'.replace(/[\u0010-\u001f]/g, function (m) {
    return t[m.charCodeAt(0) & 15]
  })
}('var \u0010function \u0010()\u0010.length\u0010++\u0010return \u0010))\u0010;break;case \u0010;else{'.split('\u0010'))

显然,是通过正则替换得到代码。

拿到控制台去跑一下:

控制台执行扒掉第一层外衣

这样就拿到了 Function(code)() 里面的代码。

再格式化一下,得到这样的一段代码:

var w = function () {
  function S(S, K) {
    if (!a[S]) {
      a[S] = {
      };
      for (var y = 0; y < S.length; y++) a[S][S.charAt(y)] = y
    }
    return a[S][K]
  }
  var K = String.fromCharCode,
  a = {
  },
  y = {
    x: function (K) {
      return null == K ? '' : '' == K ? null : y.y(K.length, 32, function (a) {
        return S('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', K.charAt(a))
      })
    },
    y: function (S, a, y) {
      var p,
      m,
      o,
      T,
      l,
      r,
      k,
      i = [],
      J = 4,
      q = 4,
      j = 3,
      I = '',
      b = [],
      z = {
        val: y(0),
        position: a,
        index: 1
      };
      for (p = 0; p < 3; p += 1) i[p] = p;
      for (o = 0, l = Math.pow(2, 2), r = 1; r != l; ) T = z.val & z.position,
      z.position >>= 1,
      0 == z.position && (z.position = a, z.val = y(z.index++)),
      o |= (T > 0 ? 1 : 0) * r,
      r <<= 1;
      switch (o) {
        case 0:
          for (o = 0, l = Math.pow(2, 8), r = 1; r != l; ) T = z.val & z.position,
          z.position >>= 1,
          0 == z.position && (z.position = a, z.val = y(z.index++)),
          o |= (T > 0 ? 1 : 0) * r,
          r <<= 1;
          k = K(o);
          break;
        case 1:
          for (o = 0, l = Math.pow(2, 16), r = 1; r != l; ) T = z.val & z.position,
          z.position >>= 1,
          0 == z.position && (z.position = a, z.val = y(z.index++)),
          o |= (T > 0 ? 1 : 0) * r,
          r <<= 1;
          k = K(o);
          break;
        case 2:
          return ''
      }
      for (i[3] = k, m = k, b.push(k); ; ) {
        if (z.index > S) return '';
        for (o = 0, l = Math.pow(2, j), r = 1; r != l; ) T = z.val & z.position,
        z.position >>= 1,
        0 == z.position && (z.position = a, z.val = y(z.index++)),
        o |= (T > 0 ? 1 : 0) * r,
        r <<= 1;
        switch (k = o) {
          case 0:
            for (o = 0, l = Math.pow(2, 8), r = 1; r != l; ) T = z.val & z.position,
            z.position >>= 1,
            0 == z.position && (z.position = a, z.val = y(z.index++)),
            o |= (T > 0 ? 1 : 0) * r,
            r <<= 1;
            i[q++] = K(o),
            k = q - 1,
            J--;
            break;
          case 1:
            for (o = 0, l = Math.pow(2, 16), r = 1; r != l; ) T = z.val & z.position,
            z.position >>= 1,
            0 == z.position && (z.position = a, z.val = y(z.index++)),
            o |= (T > 0 ? 1 : 0) * r,
            r <<= 1;
            i[q++] = K(o),
            k = q - 1,
            J--;
            break;
          case 2:
            return b.join('')
        }
        if (0 == J && (J = Math.pow(2, j), j++), i[k]) I = i[k];
         else {
          if (k !== q) return null;
          I = m + m.charAt(0)
      }
      b.push(I),
      i[q++] = m + I.charAt(0),
      m = I,
      0 == --J && (J = Math.pow(2, j), j++)
    }
}
};
return y
}();
'function ' == typeof define && define.amd ? define(function () {
return w
})  : 'undefined' != typeof module && null != module ? module.exports = w : 'undefined' != typeof angular && null != angular && angular.module('w', [
]).factory('w', function () {
return w
}),
eval(w.x('...这里是一个很长的加密字符串...'));

扫一眼就知道,这里是首先定义了一个对象 w,然后用函数 w.x 解密一个字符串,并通过 eval 执行。

接下来当然是把 w.x('......') 拿到控制台执行:

再次控制台执行扒掉第二层外衣

再次格式化后得到了这样的代码:

var _typeof = 'function' == typeof Symbol && 'symbol' == typeof Symbol.iterator ? function (f) {
  return typeof f
}
 : function (f) {
  return f && 'function' == typeof Symbol && f.constructor === Symbol && f !== Symbol.prototype ? 'symbol' : typeof f
};
TAC = function () {
  function f(f, a, b, d, c, r) {
    null == r && (r = this);
    var n,
    i,
    o = {
    },
    l = o.d = c ? c.d + 1 : 0;
    for (o['$' + l] = o, i = 0; i < l; i++) o[n = '$' + i] = c[n];
    for (i = 0, l = o.length = d.length; i < l; i++) o[i] = d[i];
    return e(f, a, b, o, r) [1]
  }
  function e(r, o, l, t, v, y) {
    function h(f) {
      S[++A] = f
    }
    function k() {
      return S[A--]
    }
    function m(f, e) {
      for (var a = b, d = '', c = 0; c < f.length; c++) {
        var r = f.charCodeAt(c);
        d += String.fromCharCode(a ^ r),
        a = (a << 1) + c + e + 1 + (a >> 1) & 255
      }
      return d
    }
    null == v && (v = this);
    var g,
    C,
    x,
    I,
    S = [
    ],
    A = 0;
    y && (g = y);
    for (var w = o + 2 * l; o < w; ) {
      var z = 13 * i(r, o) % 241;
      if (o += 2, 0 == (3 & z)) if (0 == (3 & (z >>= 2))) {
        if (0 == (z >>= 2)) return [1,
        S[A--]];
        if (2 == z) oprand = n(r, o),
        o += 2 * oprand[0],
        I = oprand[1],
        S[++A] = + I;
         else if (4 == z) g = S[A--],
        S[A] = S[A] * g;
         else if (6 == z) g = S[A--],
        S[A] = S[A] != g;
         else if (13 == z) C = S[A--],
        x = S[A--],
        (I = S[A--]).x === e ? S[++A] = f(r, I.pc, I.len, C, I.z, x)  : S[++A] = I.apply(x, C);
         else {
          if (15 != z) break;
          oprand = n(r, o),
          I = oprand[1],
          S[A] = function (a, b) {
            var d = function e() {
              var a = arguments;
              return f(r, e.pc, e.len, a, e.z, this)
            };
            return d.pc = a,
            d.len = b,
            d.x = e,
            d.z = t,
            d
          }(o + 6, I - 4),
          o += 2 * I - 2
        }
      } else if (1 == (3 & z)) if (3 == (z >>= 2)) g = S[--A],
      S[A] = g(S[A + 1]);
       else if (5 == z) S[A -= 1] = S[A][S[A + 1]];
       else if (7 == z) S[A] = --S[A];
       else {
        if (9 != z) break;
        g = S[A--],
        S[A] = typeof g
      } else if (2 == (3 & z)) if (6 == (z >>= 2)) S[A] = u(S[A]);
       else if (8 == z) g = S[A--],
      oprand = n(r, o),
      o += 2 * oprand[0],
      S[A--][m(a[oprand[1]], oprand[1])] = g;
       else {
        if (10 != z) {
          if (12 == z) throw S[A--];
          break
        }
        S[A] = ~S[A]
      } else if (0 == (z >>= 2)) S[++A] = null;
       else if (2 == z) g = S[A--],
      S[A] = S[A] >= g;
       else if (9 == z) g = k(),
      C = k(),
      t[0] = 65599 * t[0] + t[g].charCodeAt(C) >>> 0;
       else if (11 == z) S[++A] = void 0;
       else {
        if (13 != z) break;
        g = S[A--],
        S[A] = S[A] && g
      } else if (1 == (3 & z)) if (0 == (3 & (z >>= 2))) {
        if (4 == (z >>= 2)) {
          oprand = n(r, o),
          I = oprand[1];
          try {
            if (d[c][2] = 1, 1 == (g = e(r, o + 6, I - 4, t, v)) [0]) return g
          } catch (y) {
            if (d[c] && d[c][1] && 1 == (g = e(r, d[c][1][0], d[c][1][1], t, v, y)) [0]) return g
          } finally {
            if (d[c] && d[c][0] && 1 == (g = e(r, d[c][0][0], d[c][0][1], t, v)) [0]) return g;
            d[c] = 0,
            c--
          }
          o += 2 * I - 2
        } else if (6 == z) oprand = n(r, o),
        o += 2 * oprand[0],
        I = oprand[1],
        S[A -= I] = p('x,y', 'return new x[y](' + Array(I + 1).join(',x[++y]').substr(1) + ')') (S, A);
         else if (8 == z) g = S[A--],
        S[A] = S[A] & g;
         else if (10 != z) break
      } else if (1 == (3 & z)) if (0 == (z >>= 2)) S[A] = !S[A];
       else if (7 == z) C = S[A--],
      g = delete S[A--][C];
       else if (9 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      S[A] = S[A][m(a[oprand[1]], oprand[1])];
       else {
        if (11 != z) break;
        g = S[A--],
        S[A] = S[A] << g
      } else if (2 == (3 & z)) if (1 == (z >>= 2)) S[++A] = g;
       else if (3 == z) g = S[A--],
      S[A] = S[A] <= g;
       else if (10 == z) g = S[A -= 2][S[A + 1]] = S[A + 2],
      A--;
       else if (12 == z) g = S[A],
      S[++A] = g;
       else {
        if (14 != z) break;
        g = S[A--],
        S[A] = S[A] || g
      } else if (0 == (z >>= 2)) S[A] = !S[A];
       else if (2 == z) oprand = n(r, o),
      o += 2 * (I = oprand[1]) - 2;
       else if (4 == z) g = S[A--],
      S[A] = S[A] / g;
       else if (6 == z) g = S[A--],
      S[A] = S[A] !== g;
       else {
        if (13 != z) break;
        S[++A] = v
      } else if (2 == (3 & z)) if (0 == (3 & (z >>= 2))) if (1 == (z >>= 2)) g = S[A--],
      S[A] = S[A] > g;
       else if (8 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      C = A + 1,
      S[A -= I - 1] = I ? S.slice(A, C)  : [
      ];
       else if (10 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      g = S[A--],
      t[I] = g;
       else {
        if (12 != z) break;
        g = S[A--],
        S[A] = S[A] >> g
      } else if (1 == (3 & z)) if (0 == (z >>= 2)) S[++A] = s;
       else if (2 == z) g = S[A--],
      S[A] = S[A] + g;
       else if (4 == z) g = S[A--],
      S[A] = S[A] == g;
       else if (11 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      S[--A] = p('x,y', 'return x ' + m(a[I], I) + ' y') (S[A], S[A + 1]);
       else {
        if (13 != z) break;
        g = S[A - 1],
        C = S[A],
        S[++A] = g,
        S[++A] = C
      } else if (2 == (3 & z)) if (1 == (z >>= 2)) oprand = n(r, o),
      o += 2 * oprand[0],
      S[++A] = m(a[oprand[1]], oprand[1]);
       else if (3 == z) S[A--] ? o += 6 : (oprand = n(r, o), o += 2 * (I = oprand[1]) - 2);
       else if (5 == z) g = S[A--],
      S[A] = S[A] % g;
       else if (7 == z) g = S[A--],
      S[A] = S[A] instanceof g;
       else {
        if (14 != z) break;
        S[++A] = !1
      } else if (4 == (z >>= 2)) oprand = n(r, o),
      I = oprand[1],
      d[c][0] && !d[c][2] ? d[c][1] = [
        o + 6,
        I - 4
      ] : d[c++] = [
        0,
        [
          o + 6,
          I - 4
        ],
        0
      ],
      o += 2 * I - 2;
       else if (6 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      S[++A] = t['$' + I];
       else {
        if (8 != z) break;
        g = S[A--],
        S[A] = S[A] | g
      } else if (0 == (3 & (z >>= 2))) if (1 == (z >>= 2)) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      S[++A] = + m(a[I], I);
       else if (3 == z) g = S[A--],
      S[A] = S[A] - g;
       else if (5 == z) g = S[A--],
      S[A] = S[A] === g;
       else if (12 == z) C = S[A--],
      x = S[A--],
      (I = S[A--]).x === e ? S[++A] = f(r, I.pc, I.len, C, I.z, x)  : S[++A] = I.apply(x, C);
       else {
        if (14 != z) break;
        g = S[A],
        S[A] = S[A - 1],
        S[A - 1] = g
      } else if (1 == (3 & z)) if (2 == (z >>= 2)) h(function (f) {
        var e = 0,
        a = f.length;
        return function () {
          var b = e < a;
          b && h(f[e++]),
          h(b)
        }
      }(S[A]));
       else if (4 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      g = t[I],
      S[++A] = g;
       else if (6 == z) S[A] = ++S[A];
       else {
        if (8 != z) break;
        g = S[A--],
        S[A] = S[A] in g
      } else if (2 == (3 & z)) if (5 == (z >>= 2));
       else if (7 == z) g = S[A--];
       else if (9 == z) g = S[A--],
      S[A] = S[A] ^ g;
       else {
        if (11 != z) break;
        oprand = n(r, o),
        I = oprand[1],
        d[++c] = [
          [o + 6,
          I - 4],
          0,
          0
        ],
        o += 2 * I - 2
      } else if (1 == (z >>= 2)) g = S[A--],
      S[A] = S[A] < g;
       else if (8 == z) oprand = n(r, o),
      o += 2 * oprand[0],
      I = oprand[1],
      S[A] = S[A][I];
       else if (10 == z) S[++A] = !0;
       else {
        if (12 != z) break;
        g = S[A--],
        S[A] = S[A] >>> g
      }
    }
    return [0,
    null]
  }
  var a = [
  ],
  b = 0,
  d = [
  ],
  c = 0,
  r = function (f, e) {
    var a = '' + f[e++] + f[e];
    return parseInt(a, 16)
  },
  n = function (f, e) {
    var a = f[e++],
    b = f[e],
    d = parseInt('' + a + b, 16);
    if (d >> 7 == 0) return d >> 6 != 0 && (d = - 64 | 63 & d),
    [
      1,
      d
    ];
    if (d >> 6 == 2) {
      var c = parseInt('' + f[++e] + f[++e], 16);
      return 0 != (32 & d) ? d = - 32 | 31 & d : d &= 31,
      d <<= 8,
      c = d + c,
      [
        2,
        c
      ]
    }
    if (d >> 6 == 3) {
      var r = parseInt('' + f[++e] + f[++e], 16),
      n = parseInt('' + f[++e] + f[++e], 16);
      return 0 != (32 & d) ? d = - 32 | 31 & d : d &= 31,
      d <<= 16,
      r <<= 8,
      n = d + r + n,
      [
        3,
        n
      ]
    }
  },
  i = function (f, e) {
    var a = f[e++],
    b = f[e];
    return parseInt('' + a + b, 16)
  },
  o = function (f, e) {
    var a = '' + f[e++] + f[e];
    return a = parseInt(a, 16),
    String.fromCharCode(a)
  },
  l = function (f, e, a) {
    for (var b = '', d = 0; d < a; d++) b += o(f, e),
    e += 2;
    return b
  },
  t = function (f, e, b) {
    for (var d = 0; d < b; d++) {
      var c = n(f, e);
      e += 2 * c[0];
      var r = l(f, e, c[1]);
      a.push(r),
      e += 2 * c[1]
    }
  },
  s = this,
  p = s.Function,
  u = Object.keys || function (f) {
    var e = {
    },
    a = 0;
    for (var b in f) e[a++] = b;
    return e.length = a,
    e
  };
  return function (e) {
    e.length;
    for (var d = 0, c = '', i = d; i < d + 16; ) c += o(e, i),
    i += 2;
    if ('HNOJ@?RC' != c) throw new Error('error magic number ' + c);
    n(e, d += 16);
    d += 8,
    b = 0;
    for (var l = 0; l < 4; l++) {
      var s = r(e, d + 2 * l);
      b += (3 & s) << 2 * l
    }
    d += 16;
    var p = n(e, d += 16),
    u = p[1],
    v = d += 2 * p[0];
    d += p[1];
    var y = n(e, d);
    y[1];
    d += 2 * y[0],
    a = [
    ],
    t(e, d, y[1]),
    f(e, v, u, [
    ])
  }
}(),
TAC('...这里是一个很长的加密字符串...', []);

控制台尝试执行 TAC('asdasdf',[]) 会报 error magic number,并且中间 SgI 等变量的代码有明显的控制流平坦化特征,所以外衣已经被全部扒掉了,这一段就是内层的混淆代码。

现在还需要验证一下这段代码能不能用于正常计算请求签名,也就是检查是否有反格式化之类的防止代码被修改的措施。利用 Fiddler 的 AutoResponder 功能,用上面这段代码将线上的 acrawler.js 替换。再次访问某条的页面,发现替换文件后可以正常获取数据:

用代理替换JS后仍可正常获取数据

可以确认,扒掉两层外衣后的代码是可用的。

神器之威

现在实际上已经可以进入下一步了,但如果还能进一步处理,获取更多有利信息,自然是更好的。是时候祭出神器 prepack 了。

使用 prepack 处理代码,具体过程不赘述。运行后会将三百多行的代码变成两千多行,并且成功拆出大量的函数,prepack 会给拆出来的匿名函数命名,命名是随机的,因此多次运行 prepack 会得到不一样的结果。

拆得许多具名函数,其中大部分具有相同的形式

部分拆得的常量

字符串密文数组,根据我的经验,这些字符串解密后应该是函数名和字面常量参数

你该减肥了

prepack 处理后的文件大小达到了 5.4MB,主要原因是加密字符串在几十个函数之中重复出现。

重复出现的加密字符串

搞一个常量 tagStr 来存放这个长得要死的字符串,然后把函数中的字符串全部替换成 tacStr,即可让文件大小缩减到 122KB。

接下来处理 _$0_$1 之类的常量,这些常量是 prepack 搞的鬼。全部替换掉然后删除无用代码即可。

prepack画蛇添足搞出来的东西

继续观察代码,可以发现大量形如 $$0.value = "e", Object.defineProperty(_TN, "name", $$0); 的语句,它们会把所有函数的 name 属性都换成 e,这显然是不利于逆向的,用正则表达式 \$\$0\.value = "e", Object\.defineProperty\(_.., "name", \$\$0\);\n 把这些语句全部删除。

函数名替换,常见的混淆手法

之后就可以把 $$0 删掉了,再用同样地方法处理掉 $$1_typeof 等冗余变量和函数。

继续看代码,可以发现大量对 this.oprand 的连续赋值。显然只需要保留最后一次赋值即可,其它全部删掉。

无意义的连续赋值

代码中还存在着超过 100 行的 _XX.z = _Sj,并且不难发现这些 _XX.z 都只出现了一次,按理说可以把它们全部替换成 _Sj 然后删除掉。然而,尝试之后发现会报错,应该是控制流平坦化的部分核心代码中也有通过 _XX.z 来获取 _Sj。类似的还有 _XX.x = _TM

这些赋值不能删

至此,prepack 处理后的两千多行、5.4MB 的代码缩减到了 1400 行、100KB 左右。准备工作到此为止,接下来可以开始进行逆向了。


文章作者: yuanbug
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yuanbug !
评论
  目录