Skip to content

Webpack打包

更新: 2025/2/24 字数: 0 字 时长: 0 分钟

Webpack 是基于 Javascript 应用程序的一个模块化的打包(构建)工具,它可以把开发中的所有资源(图片、JS 文件、CSS 文件)都打包成模块,通过 loader 加载器和 plugins 插件对资源进行处理,打包成符合生产环境部署的前端资源,所有的资源都是通过 Javascript 渲染出来的。

20230611015754

简单介绍

打包形式

任何的 Webpack 打包都会有一个自执行函数,里面都会有三个参数分别是:形参、加载器、模块。

20230611191914

展开后大体形式如下:

20230611192156

如果模块比较多,可能会定义一个全局变量 window["webpackJsonp"] = [],它的作用是存储需要动态导入的模块,然后重写 window["webpackJsonp"] 数组的 push() 方法为 webpackJsonpCallback() 也就是说 window["webpackJsonp"].push() 其实执行的是 webpackJsonpCallback(),而 window["webpackJsonp"].push() 接收三个参数,第一个参数是模块的 ID,第二个参数是一个数组或者对象,里面定义大量的函数,第三个参数是要调用的函数(可选)。

20230611193012

调用方式

加载器调度 Webpack 打包的函数模块有两种方式:

  1. 通过数组下标来定位调度模块,例如上面图中的 n(0) 写法;
  2. 通过对象的 key 来定位调度模块,例如 n('MpQL') 写法;

三步战略

前面说了 Webpack 打包后是一个自执行函数,也就是说,函数的调用都是在内部通过加载器进行调用的,这种我们如何在外部进行调度呢?很简单,只需要三步:

  • 首先,在外部定义一个全局变量,例如 var cz;

  • 然后,在函数中将 n 加载器赋值给 cz 全局变量,即 cz = n;

  • 最后,我们在外部通过全局变量进行调度 cz(0) 相当于内部 n(0) 调度;

警告

由于 Webpack 所有的代码模块都加载到了“加载器”中,因此在调试“加载器”的过程当中,一定要刷新页面,如果你不刷新的话,或者只是加载分页,“加载器”是不会去重新加载模块的,只有在页面刷新重载时,“加载器”才会去进行模块的加载。

网站实践

猿人学第16题

难度:简单

访问网址获取任务,在 Network 里面的 Fetch/XHR 选项中定位到网页的数据接口:

20211015111419

访问前面 3 页,分析前 3 页请求头参数后,得出初步接结论:page 参数就是页码,m 参数是加密参数,t 是时间戳。

20211015111614

现在我们需要定位到,哪一行的代码发送了当前的请求,点击左侧的 Initiator 选项,它主要是标记请求是由哪个对象或进程发起的(请求源),重点关注里面的 request 请求,显示从一个名称为 webpack 的文件的第 2 行代码发送了当前请求,点击后面的地址进行跳转:

20211015111836

跳转到了该文件的第2行,点击左下方的 {} 按钮,将文件里面的代码格式化:

20211015112118

格式代码后定位到 9475 行,看上下几行代码就,传递的 data 等于 i 等于字典 r,可以看到很熟悉的参数 r.m 和参数 r.t,其中 r.t 等于 p_s 等于 Date[e(496)](new Date)[e(517)]() 这串代码和时间相关,估计 r[e(532)] 就是 page 参数:

20211015115818

**虽然我们不清楚参数 r.m 和参数 r.t 他们具体的生成方式,因为所有的关键词都被换算成了 e(数字) 形式,这就是典型的 Webpack 打包,这里我们不必太过关注里面的实现逻辑,现在只管抠取和变量 r 相关的代码。**注意抠代码的过程中,可能会出现变量污染,例如下图明明 n 是一个函数,t = n 却报错 t 不是一个函数:原因在于,在最开始我们扣代码时,已经定义了一次 n = t,然而 t = {},因此这里只需要将 n 替换为 t,即可解决变量污染。

20211015143846

20211015144056

36氪

难度:简单

网址:https://www.36kr.com

36 氪创办于 2010 年 12 月 ,是一家以媒体为旗舰的新经济服务集团,以长期报道、服务和陪伴中国新经济参与者,提高中国商业效率为使命,让一部分人先看到未来。36 氪始终秉持"媒体的内核永远不变,媒体的边界永在拓展"的发展理念,为初创企业、互联网巨头、传统企业、投资机构、地方政府及个人用户六大社群提供价值对接的纽带。首先我们进行登录,进行抓包,发现账户和密码都进行了加密,通过全局搜索 mobileNo 加密字段,找到一处位置有,我们点击进行跳转:

20230611181849

跳转后,发现这里就有我们要找的两个加密字段,我们打算断点,再次点击登录,断点断住,通过控制台的输出可以看到,在这里对账户和密码进行了加密,而加密的函数就是 Object(i.b),选中它,点击地址进行跳转:

20230611182535

跳转过来可以看到,有一个内容很长的 i 变量,在下面 r.setPublicKey(i);i 变量设置了为 r 公钥(这里可以联想到前面学习的 RSA 加密,其实 i 变量就是 Base64 编码的公钥字符串),因此基本可以确定这里使用了 RSA 非对称加密算法,通过 r 公钥对账户和密码都进行加密,返回 a 加密内容,因此传输的都是加密后的密文信息。在这里还有一个重点就是,下面的很多变量都是通过 n(数字) 进行赋值的,其实这就是 Webpack 打包所生成的,它其实就是把 JS 封装为了一些模块,通过 n 加载器和 数字 对函数进行调用:

20230611190648

现在我们移动到最上面,可以看到定义了一个全局的方式,还有一个 push 方法将函数模块推入 n 加载器中:

20230611232039

回到之前的位置,我们选中 n 加载器,可以看到里面的 m 属性有 2739 个模块:

20230612012124

选中 n 加载器,点击里面的地址进行跳转:

20230611225250

**跳转后,进行格式化,移动到最下面,可以看到这里有一个 [] 空数组,这里就是接收上面 push 方法推送过来的函数的。**如下图:

20230611232944

现在我们新建一个文件夹,在浏览器的 Overrides 中将其添加进来,在添加文件夹的时候,需要同意浏览器的对该文件夹的完整访问权限,在添加进来后,选择该文件右键选择 Save for overrides 保存并覆盖:

20230612010644

20230612010252

建议

这里必须先将文件夹添加进来才会有 Save for overrides 选项。

现在我们就可以对该文件进行修改了,按照最上面的三步策略。首先,我们定义一个 var xl; 全局变量:

20230612011058

然后,由于这里使用的加载器变量名称为 n,在尾部将 n 加载器赋值给 xl 全局变量,即 xl = n;

20230612011339

现在我们 ctrl+s 保存一下,再重新刷新一下网页,在控制台输出 xl 可以看到这就是之前的 n 加载器:

20230612011641

这里我们新建一个 JS 文件,将文件全部的内容复制进来,因为它就是一个完整的加载器,复制完成后我们,将空数组改成空字典,以后就在空字典里面放我们所需要的模块:

20230612015200

回看之前的调用了 n(769) 模块对参数进行了加密,我们在控制台通过现在的 xl 加载器中 m 这个属性的 769 下标来输出函数,将其转化为字符串进行拷贝:

20230612014453

拷贝进我们的 JS 文件中,键名和上面的数字 769 一样:

20230612015318

警告

由于是拷贝的字符串,JS里面的 \ 符号会变为 \\ 字符串形式,因此拷贝后需要将 \\ 替换为 \ 才行,否则会报错。

现在我们就可以来模拟整个的加密了,定义一个 crypto 函数,将上面函数中加密的部分拷贝过来,将 n 加载器换成 xl 加载器,运行后抱一个 window 未定义的错:

20230612021921

报错不用慌,我们在最上面补一个 window = global; 的环境,再次运行,还会报一个 navigator 未定义的错,我们点击下面第一个地址进行跳转:

20230612022050

跳转到这位置,原来是 navigator.appName 这个属性没有,那我们就去控制台打印,可以看到 navigator 是一个浏览器环境对象,而 appName 属性就是 'Netscape' 值:

20230612022546

于是我们在最上面,补上一个 navigator 浏览器环境对象,以及对象的 appName 属性,再次运行就能得到加密后的密文信息了:

20230612023031

中国五矿

难度:简单

网址:https://ec.minmetals.com.cn/open/home/purchase-info/?tabIndex=1

中国五矿集团有限公司(China Minmetals Corporation),简称“中国五矿”或“五矿集团”,成立于 1950 年,是由两个世界 500 强企业(原中国五矿和中冶集团)战略重组形成的中国最大、国际化程度最高的金属矿业企业集团,是全球最大最强的冶金建设运营服务商 ,由中央直接管理的国有重要骨干企业,国有资本投资公司试点企业。首先我们进行登录,进行抓包,在 XHR 中找到了数据接口的来源,请求中包含 param 加密参数:

20230708232312

20230708232736

这里比较特殊的是,每次翻页都会有一个地址结尾为 public 的请求,根据单词的含义大概率是 RSA 非对称加密,而 public 的请求返回的估计就是加密所需要的 Base64 编码的公钥字符串:

20230708232816

我们全局搜索发送域名后面的地址,发现有一处使用到了,点击进行跳转:

20230708233018

在跳转后的位置打上断点,我们发现关键词 setPublicKey 这里也验证了我们上面的 RSA 非对称加密猜测,在下面返回的位置打上断点进行翻页调试。在发现 A 对象中发现了 public 请求的响应值以及请求的数据的 param 加密参数,而 A 对象来源于上面的 s 对象,又来源于上面几行代码,简化代码如下:

20230708233935

javascript
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 打包:

20230709001052

我们搜索 webpack 关键字,发现果然存在,下面就是通过键值对的形式将函数打包进加载器中:

20230709001445

现在已经确定网页使用了 Webpack 打包,而且所有的函数都已经加载进了加载器中,现在我们要找出生成加载器的函数,很简单,我们在调用加载器那里打上一个断点,例如上面的 v = t("9816") 地方,当中的 t 就是加载器,再一次刷新网页从新生成加载器,发现加载器是一个名称为 g 的函数,点击下方的函数地址进行跳转:

20230709002547

可以看到 g 生成加载器函数虽然代码不多,但使用到了的一些外部属性,那么我们将包含 g 函数的外部函数整个扣下来,就得到了一个空的加载器:

20230709003300

20230709004157

回到之前的断点,我们选中 t("9816") 出现 9816t 加载器中对应的 e 函数地址,点击进行跳转:

20230709004525

跳转后进入到一个新的 JS 文件当中,全局搜索一下 9816 函数键,拷贝这部分函数,到我们的代码中加载函数的部分:

20230709010005

首先我们要知道,外部是不能直接调用加载器内部函数的,但是我们可以定义个全局变量来接收加载器,从而在外部调用加载器中的函数。这里我们定义一个 var cz; 全局变量,将 g 加载器生成函数赋值给 cz 全局变量,最后输出一下,发现 window 未定义:

20230709010624

我们在最上面定义一个 window = global; 后,再次运行就可以正常输出了:

20230709011012

现在我们输出一下 cz('9816') 这个函数,发现报错 call 未定义,这个错误的原因就是缺少模块,因为在 Webpack 中,模块之间的耦合度是很高的,也就是说在模块之间的关联程度是很高的:

20230709012150

我们在报错位置的上面输出 e 模块的键,发现在调用第二个 a524 模块没有,那么我们在调试代码中找到 a524 模块也给扣进去,再次运行就能正常输出了:

20230709012752

20230709013349

现在我们就来全局来运行一下,发现 b 函数未定义,那我们就断点到 b 函数,直接去 b 函数的位置,发现 b 函数在一个VM临时文件中,将 b 函数全部扣下来:

20230709013841

20230709014207

20230709014449

接下我们继续调试,还会发现 d 函数、 m 函数未定义,它们刚好就在 b 函数的上下位置,只需要扣过来补全即可:

20230709020716

我们再次运行,发现 e 变量未定义,回看调试代码 e 变量就是请求的明文参数,将其拷贝进代码即可:

20230709014904

接下我们继续调试,出现 f()(JSON.stringify(e)) 代码,先看里面就是把 e 变量进行了JSON序列化,再通过 f() 得到了32位字符,根据长度猜测是 MD5 哈希,经过比对是标准的 MD5 哈希:

20230709015542

继续调试,发现 t.encryptLong 函数未定义,我们在浏览器选中 t.encryptLong 函数点击地址进行跳转,将函数扣入我们的代码当中,注意由于函数是通过 v 加载器进行调用,因此要放在 v 加载器定义后的下面:

20230709021441

20230709022012

继续调试,发现 w 函数未定义,我们在浏览器选中 w 函数点击地址进行跳转,将函数扣入我们的代码当中,最后再次运行就可以输出加密的请求参数啦:

20230709022847

20230709023237