webpack逆向通用流程
如有不当可联系本人删除!
本文以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=分发器函数名
使分发器可被外部调用(取消代码格式化防止内部含有格式化检测)如果上述方法失败可以模仿编号函数,使用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 // 分发器全局化代码 }])
目标函数全局化。使我们可直接在最外层调用目标函数。在目标函数定义完成后,新增行
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)
解密
- 一般会进行环境检测,需要补环境, 推荐使用 v_jstools
- 使用webpack的流程将目标函数扣出
补充说明: 如果传入参数固定,每次生成内容不一致的,可能是加密过程中使用了随机数或者时间戳。可在重载对应方法(在加密函数调用前重载即可),看看生成内容是否不变。明确后在浏览器console中,也重载对应方法后调用,查看生成结果是否与解密一致
Math,random = function () {return 0.5} // 重载随机数方法