
Webpack打包
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
Webpack 是基于 Javascript 应用程序的一个模块化的打包(构建)工具,它可以把开发中的所有资源(图片、JS 文件、CSS 文件)都打包成模块,通过 loader
加载器和 plugins
插件对资源进行处理,打包成符合生产环境部署的前端资源,所有的资源都是通过 Javascript 渲染出来的。
简单介绍
打包形式
任何的 Webpack 打包都会有一个自执行函数,里面都会有三个参数分别是:形参、加载器、模块。
展开后大体形式如下:
如果模块比较多,可能会定义一个全局变量 window["webpackJsonp"] = []
,它的作用是存储需要动态导入的模块,然后重写 window["webpackJsonp"]
数组的 push()
方法为 webpackJsonpCallback()
也就是说 window["webpackJsonp"].push()
其实执行的是 webpackJsonpCallback()
,而 window["webpackJsonp"].push()
接收三个参数,第一个参数是模块的 ID,第二个参数是一个数组或者对象,里面定义大量的函数,第三个参数是要调用的函数(可选)。
调用方式
加载器调度 Webpack 打包的函数模块有两种方式:
- 通过数组下标来定位调度模块,例如上面图中的
n(0)
写法; - 通过对象的 key 来定位调度模块,例如
n('MpQL')
写法;
三步战略
前面说了 Webpack 打包后是一个自执行函数,也就是说,函数的调用都是在内部通过加载器进行调用的,这种我们如何在外部进行调度呢?很简单,只需要三步:
首先,在外部定义一个全局变量,例如
var cz;
然后,在函数中将
n
加载器赋值给cz
全局变量,即cz = n;
最后,我们在外部通过全局变量进行调度
cz(0)
相当于内部n(0)
调度;
警告
由于 Webpack 所有的代码模块都加载到了“加载器”中,因此在调试“加载器”的过程当中,一定要刷新页面,如果你不刷新的话,或者只是加载分页,“加载器”是不会去重新加载模块的,只有在页面刷新重载时,“加载器”才会去进行模块的加载。
网站实践
猿人学第16题
难度:简单
访问网址获取任务,在 Network 里面的 Fetch/XHR 选项中定位到网页的数据接口:
访问前面 3 页,分析前 3 页请求头参数后,得出初步接结论:page
参数就是页码,m
参数是加密参数,t
是时间戳。
现在我们需要定位到,哪一行的代码发送了当前的请求,点击左侧的 Initiator 选项,它主要是标记请求是由哪个对象或进程发起的(请求源),重点关注里面的 request 请求,显示从一个名称为 webpack 的文件的第 2 行代码发送了当前请求,点击后面的地址进行跳转:
跳转到了该文件的第2行,点击左下方的 {}
按钮,将文件里面的代码格式化:
格式代码后定位到 9475 行,看上下几行代码就,传递的 data
等于 i
等于字典 r
,可以看到很熟悉的参数 r.m
和参数 r.t
,其中 r.t
等于 p_s
等于 Date[e(496)](new Date)[e(517)]()
这串代码和时间相关,估计 r[e(532)]
就是 page
参数:
**虽然我们不清楚参数 r.m
和参数 r.t
他们具体的生成方式,因为所有的关键词都被换算成了 e(数字)
形式,这就是典型的 Webpack 打包,这里我们不必太过关注里面的实现逻辑,现在只管抠取和变量 r
相关的代码。**注意抠代码的过程中,可能会出现变量污染,例如下图明明 n
是一个函数,t = n
却报错 t
不是一个函数:原因在于,在最开始我们扣代码时,已经定义了一次 n = t
,然而 t = {}
,因此这里只需要将 n
替换为 t
,即可解决变量污染。
36氪
难度:简单
36 氪创办于 2010 年 12 月 ,是一家以媒体为旗舰的新经济服务集团,以长期报道、服务和陪伴中国新经济参与者,提高中国商业效率为使命,让一部分人先看到未来。36 氪始终秉持"媒体的内核永远不变,媒体的边界永在拓展"的发展理念,为初创企业、互联网巨头、传统企业、投资机构、地方政府及个人用户六大社群提供价值对接的纽带。首先我们进行登录,进行抓包,发现账户和密码都进行了加密,通过全局搜索 mobileNo
加密字段,找到一处位置有,我们点击进行跳转:
跳转后,发现这里就有我们要找的两个加密字段,我们打算断点,再次点击登录,断点断住,通过控制台的输出可以看到,在这里对账户和密码进行了加密,而加密的函数就是 Object(i.b)
,选中它,点击地址进行跳转:
跳转过来可以看到,有一个内容很长的 i
变量,在下面 r.setPublicKey(i);
将 i
变量设置了为 r
公钥(这里可以联想到前面学习的 RSA 加密,其实 i
变量就是 Base64 编码的公钥字符串),因此基本可以确定这里使用了 RSA 非对称加密算法,通过 r
公钥对账户和密码都进行加密,返回 a
加密内容,因此传输的都是加密后的密文信息。在这里还有一个重点就是,下面的很多变量都是通过 n(数字)
进行赋值的,其实这就是 Webpack 打包所生成的,它其实就是把 JS 封装为了一些模块,通过 n
加载器和 数字
对函数进行调用:
现在我们移动到最上面,可以看到定义了一个全局的方式,还有一个 push
方法将函数模块推入 n
加载器中:
回到之前的位置,我们选中 n
加载器,可以看到里面的 m
属性有 2739 个模块:
选中 n
加载器,点击里面的地址进行跳转:
**跳转后,进行格式化,移动到最下面,可以看到这里有一个 []
空数组,这里就是接收上面 push
方法推送过来的函数的。**如下图:
现在我们新建一个文件夹,在浏览器的 Overrides
中将其添加进来,在添加文件夹的时候,需要同意浏览器的对该文件夹的完整访问权限,在添加进来后,选择该文件右键选择 Save for overrides
保存并覆盖:
建议
这里必须先将文件夹添加进来才会有 Save for overrides
选项。
现在我们就可以对该文件进行修改了,按照最上面的三步策略。首先,我们定义一个 var xl;
全局变量:
然后,由于这里使用的加载器变量名称为 n
,在尾部将 n
加载器赋值给 xl
全局变量,即 xl = n;
:
现在我们 ctrl+s
保存一下,再重新刷新一下网页,在控制台输出 xl
可以看到这就是之前的 n
加载器:
这里我们新建一个 JS 文件,将文件全部的内容复制进来,因为它就是一个完整的加载器,复制完成后我们,将空数组改成空字典,以后就在空字典里面放我们所需要的模块:
回看之前的调用了 n(769)
模块对参数进行了加密,我们在控制台通过现在的 xl
加载器中 m
这个属性的 769
下标来输出函数,将其转化为字符串进行拷贝:
拷贝进我们的 JS 文件中,键名和上面的数字 769
一样:
警告
由于是拷贝的字符串,JS里面的 \
符号会变为 \\
字符串形式,因此拷贝后需要将 \\
替换为 \
才行,否则会报错。
现在我们就可以来模拟整个的加密了,定义一个 crypto
函数,将上面函数中加密的部分拷贝过来,将 n
加载器换成 xl
加载器,运行后抱一个 window
未定义的错:
报错不用慌,我们在最上面补一个 window = global;
的环境,再次运行,还会报一个 navigator
未定义的错,我们点击下面第一个地址进行跳转:
跳转到这位置,原来是 navigator.appName
这个属性没有,那我们就去控制台打印,可以看到 navigator
是一个浏览器环境对象,而 appName
属性就是 'Netscape'
值:
于是我们在最上面,补上一个 navigator
浏览器环境对象,以及对象的 appName
属性,再次运行就能得到加密后的密文信息了:
中国五矿
难度:简单
网址:https://ec.minmetals.com.cn/open/home/purchase-info/?tabIndex=1
中国五矿集团有限公司(China Minmetals Corporation),简称“中国五矿”或“五矿集团”,成立于 1950 年,是由两个世界 500 强企业(原中国五矿和中冶集团)战略重组形成的中国最大、国际化程度最高的金属矿业企业集团,是全球最大最强的冶金建设运营服务商 ,由中央直接管理的国有重要骨干企业,国有资本投资公司试点企业。首先我们进行登录,进行抓包,在 XHR 中找到了数据接口的来源,请求中包含 param
加密参数:
这里比较特殊的是,每次翻页都会有一个地址结尾为 public
的请求,根据单词的含义大概率是 RSA 非对称加密,而 public
的请求返回的估计就是加密所需要的 Base64 编码的公钥字符串:
我们全局搜索发送域名后面的地址,发现有一处使用到了,点击进行跳转:
在跳转后的位置打上断点,我们发现关键词 setPublicKey
这里也验证了我们上面的 RSA 非对称加密猜测,在下面返回的位置打上断点进行翻页调试。在发现 A
对象中发现了 public
请求的响应值以及请求的数据的 param
加密参数,而 A
对象来源于上面的 s
对象,又来源于上面几行代码,简化代码如下:
t = new v["a"],
// 服务器返回的public响应内容
r = n.data,
t.setPublicKey(r),
// 用于加密的a对象
a = b(b({}, e), {}, {
sign: f()(JSON.stringify(e)),
timeStamp: +new Date
})
// 加密a对象,返回加密参数
s = t.encryptLong(JSON.stringify(a))
现在我们上面开始寻找 new v["a"]
的生成逻辑,我们往上看找了 v = t("9816")
这个关系,而且上面还有一个 u = t("8237")
,一般当看到这种格式的时候,我们就要敏感一些,大胆猜测是 Webpack 打包:
我们搜索 webpack
关键字,发现果然存在,下面就是通过键值对的形式将函数打包进加载器中:
现在已经确定网页使用了 Webpack 打包,而且所有的函数都已经加载进了加载器中,现在我们要找出生成加载器的函数,很简单,我们在调用加载器那里打上一个断点,例如上面的 v = t("9816")
地方,当中的 t
就是加载器,再一次刷新网页从新生成加载器,发现加载器是一个名称为 g
的函数,点击下方的函数地址进行跳转:
可以看到 g
生成加载器函数虽然代码不多,但使用到了的一些外部属性,那么我们将包含 g
函数的外部函数整个扣下来,就得到了一个空的加载器:
回到之前的断点,我们选中 t("9816")
出现 9816
在 t
加载器中对应的 e
函数地址,点击进行跳转:
跳转后进入到一个新的 JS 文件当中,全局搜索一下 9816
函数键,拷贝这部分函数,到我们的代码中加载函数的部分:
首先我们要知道,外部是不能直接调用加载器内部函数的,但是我们可以定义个全局变量来接收加载器,从而在外部调用加载器中的函数。这里我们定义一个 var cz;
全局变量,将 g
加载器生成函数赋值给 cz
全局变量,最后输出一下,发现 window
未定义:
我们在最上面定义一个 window = global;
后,再次运行就可以正常输出了:
现在我们输出一下 cz('9816')
这个函数,发现报错 call
未定义,这个错误的原因就是缺少模块,因为在 Webpack 中,模块之间的耦合度是很高的,也就是说在模块之间的关联程度是很高的:
我们在报错位置的上面输出 e
模块的键,发现在调用第二个 a524
模块没有,那么我们在调试代码中找到 a524
模块也给扣进去,再次运行就能正常输出了:
现在我们就来全局来运行一下,发现 b
函数未定义,那我们就断点到 b
函数,直接去 b
函数的位置,发现 b
函数在一个VM临时文件中,将 b
函数全部扣下来:
接下我们继续调试,还会发现 d
函数、 m
函数未定义,它们刚好就在 b
函数的上下位置,只需要扣过来补全即可:
我们再次运行,发现 e
变量未定义,回看调试代码 e
变量就是请求的明文参数,将其拷贝进代码即可:
接下我们继续调试,出现 f()(JSON.stringify(e))
代码,先看里面就是把 e
变量进行了JSON序列化,再通过 f()
得到了32位字符,根据长度猜测是 MD5 哈希,经过比对是标准的 MD5 哈希:
继续调试,发现 t.encryptLong
函数未定义,我们在浏览器选中 t.encryptLong
函数点击地址进行跳转,将函数扣入我们的代码当中,注意由于函数是通过 v
加载器进行调用,因此要放在 v
加载器定义后的下面:
继续调试,发现 w
函数未定义,我们在浏览器选中 w
函数点击地址进行跳转,将函数扣入我们的代码当中,最后再次运行就可以输出加密的请求参数啦: