webpack逆向通用流程

2024-09-13

如有不当可联系本人删除!

本文以x乎的请求为例,发现加密字段在请求头中。下面分析请求头中的加密字段96

请求头

加密函数的定位

  • 复制携带加密参数的请求路径。在 Source 中下XHR断点,然后右键清除缓存并刷新页面

  • 在调用堆栈中回溯,可查看到加密参数生成过程。tT中包含了加密参数。tT是通过函数ed生成的,因此ed为加密函数 (补充:t0,即er函数是取cookie中d_c0的值)

    定位加密函数

  • 通过函数所在文件的第一行,发现该函数在webpack中

    (self.webpackChunkheifetz = self.webpackChunkheifetz || []).push([[2636], {
        ...
    

webpack流程

以下内容为webpack内函数的快速调用办法。如果涉及到复杂的环境检测,需要补环境或者依旧使用扣逻辑的方式去解密目标函数

明确分发编号

  • 该js文件为webpack,所在编号为 61763 。编号可能是字符串或者此处的整数
  • 若是非webpack文件,调用了webpack文件内的函数,需要手动明确编号
  • webpack文件特征,搜索||[]).push([,类似 (window.webpackJsonp = window.webpackJsonp || []).push([。如本js中的 (self.webpackChunkheifetz = self.webpackChunkheifetz || []).push([[2636], {

分发器(加载器)定位

  • 特征,通常在 runtime*.js 内,多用一元运算符引导的自执行函数写法

    !function(e) {  // `!`引导的自执行函数 参数数量不一定
        ...
        function c(a) {
            if (f[a])
                return f[a].exports; // 特征,返回 export
            var d = f[a] = {  // 特征对象
                i: a,  // 字段1为索引
                l: !1,  // 字段2为!1
                exports: {} // 字段3为exports
            };
            // 特征,返回*.call(*.exports, *, *.exports, 函数本身)
            return e[a].call(d.exports, d, d.exports, c),  
            d.l = !0,
            d.exports
        } // 此时函数c为加载器
        ...
    }
    
  • 定位方法

    • 方法一:在目标函数加断点,例如本例中的ed函数。右键刷新后单步调试。发现执行到 runtime.app.*.js 。代码有分发器特征,确定了分发器位置

      !function() {
          "use strict";
          var e, a, c, f, d, b, t, r, o, n, i, s, l, u = {}, m = {};
          function p(e) {
              var a = m[e];
              if (void 0 !== a)
                  return a.exports;
              var c = m[e] = {
                  id: e,
                  loaded: !1,
                  exports: {}
              };
              return u[e].call(c.exports, c, c.exports, p),
              c.loaded = !0,
              c.exports
          }
          p.m = u,
          p.c = m,
          p.amdD = function() {
              throw Error("define cannot be used indirect")
          }
          ,
          ...
      
    • 方法二:找到目标函数所在编号函数的声明处,在首行添加断点。由于三个参数固定为__unused_webpack_module, exports, __webpack_require__ ,第三个参数为该编号函数内代码的依赖,由分发器生成,因此第三个参数的位置就是分发器所在位置

      定位分发器位置

处理加密函数

  • 方法1: 将分发器与目标函数全局化。使js文件可以webpack内目标函数的依赖构建与调用

    • 分发器全局化。方便初始化目标函数所需的依赖

      • 将分发器的整个自执行函数代码扣出来,在分发器内的函数前,添加 window.wp_require=分发器函数名 使分发器可被外部调用(取消代码格式化防止内部含有格式化检测)

        分发器全局化1

      • 如果上述方法失败可以模仿编号函数,使用webpack执行全局化代码。查看编号函数处的webpack代码,类似 (window.webpackJsonp = window.webpackJsonp || []).push([

        (self.webpackChunkheifetz = self.webpackChunkheifetz || []).push([[2636], {
            54616: function(tt, te, tr) {
                "use strict";
                tr.d(te, {
                    Z: function() {
                        return ec
                    }
                });
                ...
                ...
        

        在分发器中也使用该方式执行我们需要执行的代码,即分发器全局化代码(取消代码格式化防止内部含有格式化检测)

        (self.webpackChunkheifetz = self.webpackChunkheifetz || []).push([  // 参考项目内的webpack代码
            [123456789],  // 随意传一个整形数组
            {},  // 不自定义编号函数,传空
            function(e) {  // 此处传递分发器
            window.wp_require=e  // 分发器全局化代码
        }])
        

        分发器全局化2

    • 目标函数全局化。使我们可直接在最外层调用目标函数。在目标函数定义完成后,新增行 window.target_fn=目标函数名; 使其全局化

    • 调用目标函数

      // 构建目标函数的依赖
      wp_require(61763)  // 将目标函数所在的编号传入分发器,即第一步的明确编号
      target_fn(xxx, ...)  // 传入对应参数调用目标函数
      
  • 方法2: 目标编号块与分发器组合

    • 确认分发器的参数。查看分发器所在的自执行函数是否包含参数

      !function() {  // 自执行没有传入参数
      !function(c) {  // 自执行函数有1个参数
      !function(o, pls) {  // 自执行函数有2个参数
      
    • 将分发器代码扣出后,将目标代码段作为分发器自执行函数的参数传入或内部赋值进去。注意分发器的返回值,调用call的对象就是需要构造成 {编号: 代码块} 的对象

      • 没有参数的情况
        !function() {
            "use strict";
            var e, a, c, d, f, b, t, r, o, n, i, s, l, u = {}, m = {};
        
            // 2.通过分发器的返回值明确需要构造的变量
            u = {
                61763: function(tt, te, tr) {
                    "use strict";
                    tr.d(te, {
                        DH: function() {
                            return tq
                        },
                        ...
            }
        
            function p(e) {
                var a = m[e];
                if (void 0 !== a)
                    return a.exports;
                var c = m[e] = {
                    id: e,
                    loaded: !1,
                    exports: {}
                };
                return u[e].call(c.exports, c, c.exports, p),  // 1.u为需要构造的对象
                c.loaded = !0,
                c.exports
            }
            ...
        }();
        
      • 一个参数
        !function(c) {
            "use strict";
            ...
            function f(n) {
                if (h[n])
                    return h[n].exports;
                var u = h[n] = {
                    i: n,
                    l: !1,
                    exports: {}
                };
                // 1.c为需要构造的对象,且c是自执行函数的参数
                return c[n].call(u.exports, u, u.exports, f),
                u.l = !0,
                u.exports
            }
            ...
        }(  // 此行之前是分发器的全部原始代码。
            // 2.将编码和代码块构造成对象 传入
            {
                61763: function(tt, te, tr) {
                    "use strict";
                    tr.d(te, {
                        DH: function() {
                            return tq
                        },
                        ...
            }
        );
        
      • 两个参数
        !function(o, p) {
            ...
            function t(e) {
                if (n[e])
                    return n[e].exports;
                var d = n[e] = {
                    i: e,
                    l: !1,
                    exports: {}
                };
                return o[e].call(d.exports, d, d.exports, t),
                d.l = !0,
                d.exports
            }
            ...
        }([], [[], {
            61763: function(tt, te, tr) {
                "use strict";
                tr.d(te, {
                    DH: function() {
                        return tq
                    },
                    ...
        }])
        
    • 将分发器全局化并将加密函数的参数与返回值绑定到window中,同时将自执行函数套一个函数壳改为普通的调用执行函数,并返回绑定在window中的结果。这样我们在调用时在window中绑定传入参数,加密函数才正常读取到我们传入的参数,并返回我们需要的结果

      
      var llll;  // 分发器全局化变量
      
      // 可直接在外边套一个函数保证其不会自执行。函数返回结果
      function ppp() {
      
          !function(c) {
              "use strict";
              ...
              function f(n) {
                  if (h[n])
                      return h[n].exports;
                  var u = h[n] = {
                      i: n,
                      l: !1,
                      exports: {}
                  };
                  return c[n].call(u.exports, u, u.exports, f),
                  u.l = !0,
                  u.exports
              }
              ...
      
              llll = f;  // 将分发器全局化
          }( 
              {
                  61763: function(tt, te, tr) {
                      "use strict";
                      ...
                      function ed(tt, te, tr, ti) {
      
                          // 传入参数改从window中获取                          
                          tt = window.tt
                          te = window.te
                          tr = window.tr
                          ti = window.ti
      
                          var ta = tr.zse93
                          , tu = tr.dc0
                          , tc = tr.xZst81
                          , tf = t3(tt)
                          , td = t6(te)
                          , tp = [ta, tf, tu, t8(td) && td, tc].filter(Boolean).join("+");
      
                          // 运行结果全局化
                          result = {
                              source: tp,
                              signature: (0,
                              tJ(ti).encrypt)(ty()(tp))
                          }
                          window.result = result;
      
                          return result
                      }
                      ...
                  }
              }
          );
      
          llll[61763];  // 加载依赖
          return window.result  // 返回绑定在window上的结果
      }
      
    • 生成目标结果

      window.tt = 1
      window.te = 2
      window.tr = 3
      window.ti = 4
      res = ppp()  // 调用套壳函数,内部会自执行分发器,加载依赖,并返回结果
      console.log(res)  
      

解密

 

补充说明: 如果传入参数固定,每次生成内容不一致的,可能是加密过程中使用了随机数或者时间戳。可在重载对应方法(在加密函数调用前重载即可),看看生成内容是否不变。明确后在浏览器console中,也重载对应方法后调用,查看生成结果是否与解密一致

Math,random = function () {return 0.5}  // 重载随机数方法