webpack是一个现代 JavaScript 应用程序的静态模块打包器(module bundler),负责分析翻译压缩打包代码。
上面的官网的一张示例图。
首先我们来把两个最简单的js文件合并成一个,第一个JS文件代码如下:
var dt = require('./work.js')
第二个JS文件代码如下:
CryptoJS = require("crypto-js")
document.write('hello')
具体的合并过程我们不关注,各位有兴趣可以自行研究一下。代码很简单,只是引入一下CryptoJS库,我们看看webpack为了引入这个库,会生成什么样的代码。
下面是生成代码的部分粘贴
function (t) {
var e = {};
function r(i) {
if (e[i]) return e[i].exports;
var n = e[i] = {i: i, l: !1, exports: {}};
return t[i].call(n.exports, n, n.exports, r), n.l = !0, n.exports
}
...
}, r.p = "", r(r.s = 10)
}([
function (t, e, r) {},
function (t, e, r) {},
function (t, e, r) {},
function (t, e, r) {},
......
}(r(0), r(3), r(4), r(2), r(1))
}]);
上面的webpack代码,我经过了大量的删减,只保留了框架部分,看懂了这个框架的运行流程,就明白了webpack的原理。
接下来对webpack执行流程进行分析
首先是一个自执行函数,这个函数比较大,是整个webpack的入口。自执行函数传入了一个参数t
这个t是一个函数数组
继续往下走,定义了一个空对象e和一个函数r。
然后接下来的执行流都是一些赋值操作,这个不需要关心
一直到这个位置,前面都是一系列的赋值操作。而这里调用的函数r,传入了参数10。
这个r函数是我们要分析的重点,我们继续跟进
首先会判断当前e[i]的值是否为空,不为空的话就直接返回了。当前我们的e对象是一个空对象
接着往e[i]里面加入了一个对象
这个对象有三个元素,分别是exports,i和l。
接着执行了t[i]函数,并且把this指向改成了n.exports
,这里直接步过这个函数,看看函数执行完成之后e[i]对象的内容。
执行完成之后,我们再来看下e[i]的值,可以看到此时exports里面已经被填充了一堆函数
CryptoJS = require("crypto-js")
document.write('hello')
我们合并的源码实际上是包含了CryptoJS库
这个exports里面也包含了Crypto对象。
那么整个webpack实际上就是一个以加载器为核心的,以加载器的某一个模块为入口(代码里是r(10)),分模块打包,最终返回exports进行调用的一种打包工具
也就是说r函数实际上就是一个加载器,把所有需要的模块全部加载进来,这样就能让后面的代码进行调用。
那么下面这段代码我们就能够理解了
//webpack函数入口 自执行函数
function (t) {
var e = {};
//webpack加载器
function r(i) {
if (e[i]) return e[i].exports;
var n = e[i] = {i: i, l: !1, exports: {}};
return t[i].call(n.exports, n, n.exports, r), n.l = !0, n.exports
}
...
}, r.p = "", r(r.s = 10)
}([
//函数数组
function (t, e, r) {},
function (t, e, r) {},
function (t, e, r) {},
function (t, e, r) {},
......
}(r(0), r(3), r(4), r(2), r(1))
}]);
webpack代码扣取的时候,需要把整个加载器的代码都拿到本地,然后通过调用加载器函数r(10)
得到需要的模块对象。
示例网站是这个
https://open.babytree.com/default#/login
先抓一个登陆的包,这个包里面有两个字段,分别是sign和password,如果想要搞定这个登陆接口的话,就必须对这两个参数进行逆向分析。
通过搜索password关键词,可以定位到这个位置,password和加密后的数值也都符合预期,那么接下来就要进入到handleEncryptValue
函数进行分析
进去以后,这里调用了Object函数,通过控制台可以看到函数原型,继续往里跟
这里关注两个地方
var o = n(409);
t.a = function(e) {
var t = new o.JSEncrypt;
return t.setPublicKey("-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qC67Y3KF6mupPBsnsoIqEM1dfohMkMI4Rxj60Ae3MOT+Ch3vPZwCj4P5vVw+sVuRv0N94MqraNxLBlQfyeIf2Vu1KOdHD+gFfWneSrNM7Cs4b7Cn+ctCf9tJ239IrLilfsasV6iWc7kDHGIwInMJ9XqqTZTBnWP07SCQYf8J3mL/vw/PY1klBknwh8oLuJi8+BfAS1KPgMuK60NxTAMny+9h9Dno1kVGeLa0Osm4TkVWK9Uyx0XbbV0IfrnbpT/0FUxC6X+K+gHsWzmywrC7145+Bgz0lQo2kRTy551RcyMStlT41poc6ASn8mzCMD4u4MyNU+V0srtFBD8fdwZZwIDAQAB-----END PUBLIC KEY-----"),
t.encrypt(e)
}
function(e)
是关键参数的加密函数,而这里是通过o.JSEncrypt
来进行调用的,而这个o是var o = n(409);
这是不是就有点像我们之前分析的加载器函数r10
。这种就是典型的webpack调用模块的方式。特征如下:
r(10)
n(409)
这里的o已经是一个完成了加载过程的对象,可以直接进行调用。接下来就要对webpack代码进行扣取了。
首先我们在这个加载器函数下断,并刷新网页,然后跟进去这个函数里面
可以看到这个代码跟我们之前分析的webpack代码几乎一样
我们需要复制下整个加载器函数,然后构造一个自执行函数,把这个加载器放进自执行函数里面
var Func;
//自执行函数
!function (e)
{
var n={};
function i(t) {
if (n[t])
return n[t].exports;
var r = n[t] = {
i: t,
l: !1,
exports: {}
};
return e[t].call(r.exports, r, r.exports, i),
r.l = !0,
r.exports
}
//提供给外部调用
Func=i;
}({
//这里放需要的模块函数
})
接着我们需要去找到需要加载的模块
执行到下面这个位置,然后输入想要的e[409]
就可以在控制台拿到需要的模块函数,然后右键->Show function definition
如果想要拿到所有的模块,可以在这个位置打一个日志断点或者是Hook,让他打印出所有的t和e[t],这样就可以拿到所有的模块。
就可以跳转到这个函数位置,然后粘贴到代码里面。最近一步,我们来编写调用代码
//补一下缺的环境
var navigator={}
var window=global
//调用
var o = Func(409);
function Crypt(e) {
var t = new o.JSEncrypt;
return t.setPublicKey("-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2qC67Y3KF6mupPBsnsoIqEM1dfohMkMI4Rxj60Ae3MOT+Ch3vPZwCj4P5vVw+sVuRv0N94MqraNxLBlQfyeIf2Vu1KOdHD+gFfWneSrNM7Cs4b7Cn+ctCf9tJ239IrLilfsasV6iWc7kDHGIwInMJ9XqqTZTBnWP07SCQYf8J3mL/vw/PY1klBknwh8oLuJi8+BfAS1KPgMuK60NxTAMny+9h9Dno1kVGeLa0Osm4TkVWK9Uyx0XbbV0IfrnbpT/0FUxC6X+K+gHsWzmywrC7145+Bgz0lQo2kRTy551RcyMStlT41poc6ASn8mzCMD4u4MyNU+V0srtFBD8fdwZZwIDAQAB-----END PUBLIC KEY-----"),
t.encrypt(e)
}
console.log(Crypt('123456'))
然后查看运行结果,测试应该没问题,到这里这个webpack就扣取完成了。
我们现在只扣取了一个模块的代码,假如说如果想要把所有的模块函数全部扣下来,就可以用下面的方法。
先在加载器调用的前后各打一个断点
然后在控制台定义一个变量
进到函数里面,在第一行打一个日志断点
window.result = window.result + '"'+ r + '":'+:e[r] +''+','
就可以通过日志的方式拿到所有的模块。
https://gitcode.net/zjq592767809/webpack_ast
推荐一个可以自动扣取webpack的脚本,支持大部分常见的webpack,业内很有名的一个大佬写的,实战的这个网站也可以用webpack直接扣取。
使用方法:
node webpack_mixer.js -l loader.js -m function.js -o webpack_out.js
脚本运行以后会生成一个JS文件,这个里面的webpack是完整的模块,而不是我们只扣取了一个模块的
用代码打印一下所有的函数列表,可以看到跟我们自己手动扣的完全不一样
然后直接调用,可以看到结果也出来了。只能说一个字,牛逼!
最后,总结一下,扣取webpack的步骤如下: