爬虫逆向--Day20Day21--JS逆向案例之Webpack逆向
一、JS逆向案例之Webpack逆向
1.1、Webpack逆向概念介绍
Webpack是一个现代的静态模块打包工具,它主要用于前端开发中的模块化打包和构建。通过Webpack,开发者可以将多个模块(包括JavaScript、CSS、图片等)进行打包,生成优化后的静态资源文件,以供在浏览器中加载和运行。
Webpack的主要功能和特点包括:
模块化支持:Webpack将应用程序拆分为多个模块,通过模块化的方式管理和加载依赖关系。它支持CommonJS、ES module、AMD等多种模块化规范,并且能够将这些模块打包成最终的静态资源文件。
打包和压缩:Webpack可以将多个模块打包成一个或多个最终的静态资源文件。它支持对JavaScript、CSS、图片等资源进行压缩、合并和优化,以减小文件大小,提升加载速度和性能。
资源加载管理:Webpack可以处理各种类型的资源文件,例如JavaScript、CSS、图片、字体等。通过加载器(Loader)的配置,Webpack可以对这些资源文件进行转换和处理,使其能够被应用程序正确地引用和加载。
/* // 数组格式 !function(形参){加载器}([模块1,模块2,...])// 字典格式 !function(形参){加载器}({"k1":"模块1","k2":"模块2"}) 整体看上去就是一个自执行函数 */
// 数组格式 window = global; !function (e) {var t = {};function n(r) {if (t[r])return t[r].exports;var o = t[r] = {i: r,l: !1,exports: {}};e[r].call(o.exports, o, o.exports, n);return o.exports.exports; }window.loader = n;// n("1002"); }([ function () {console.log("foo");this.exports = 100; },function () {console.log("bar");this.exports = 200;}] );console.log(window.loader(0)); console.log(window.loader(1));
// 字典对象格式 window = global; !function (e) {var t = {};function n(r) {if (t[r])return t[r].exports;var o = t[r] = {i: r,l: !1,exports: {}};e[r].call(o.exports, o, o.exports, n);return o.exports.exports; // 返回 o.exports.exports,而不是整个 o.exports 对象}window.loader = n;// n("1002"); }({"1001": function () {console.log("foo");this.exports = 100; // 直接修改 exports 变量},"1002": function () {console.log("bar");this.exports = 200;} });console.log(window.loader("1001"));
1.2、Webpack理解
备注:
var t = {}; // t就是一个缓冲池 把每次调用过的都放到缓存池
function n(r) {} // 这个n函数就是【加载器函数】调用模块的一种方式,是固定格式的
【这个方法一定要认识,可能有些变量不一样,但结构一定是这样的】
if (t[r]) // 先判断缓冲池中是否有,有的话,从缓冲池中执行return t[r].exports;
1.3、Webpack关键点
1. 流程
(1) 全局变量配置:在整个项目都可以调用这个加载器函数
(2) 记录调用模块的日志
2. 初始化调用模块的注释问题
1.3.1、全局变量配置
加载器函数n只能在该作用域使用,如果想在整个项目都可以调用这个加载器函数n,就需要配一个全局变量
// 字典对象格式 window = {} // 或 window = global 声明一个全局作用域 !function (e) {var t = {};function n(r) {if (t[r])return t[r].exports;var o = t[r] = {i: r,l: !1,exports: {}};e[r].call(o.exports, o, o.exports, n);return o.exports.exports; // 返回 o.exports.exports,而不是整个 o.exports 对象}// n("1002"); // barconsole.log(n("1002")) // bar 200// 在加载器下面加一个属性,属性名可以任意,这里我们叫loader// 把加载器函数写入到了loader中,方便在外面直接调用window.loader = n}({"1001": function () {console.log("foo");this.exports = 100;},"1002": function () {console.log("bar");this.exports = 200;} });// 在外部的任何地方都可以使用该方式进行调用,只要能拿到window就可以 window.loader("1002")
1.3.2、记录调用模块日志
补充:单文件webpack和多文件的webpack
所以说明当函数在调用"1002"的时候,因为找不到报错误了,然后我们就需要想办法去源代码中去搜索"1002"这个function,然后把这个代码加进来就好了
1.3.3、初始化调用模块的注释问题
1.3.4、最终代码
// 字典对象格式 window = {} // 或 window = global 声明一个全局作用域 document = {} navigator = {} !function (e) {var t = {};function n(r) {if (t[r]) return t[r].exports;var o = t[r] = {i: r, l: !1, exports: {}};console.log("调用模块名:::",r)e[r].call(o.exports, o, o.exports, n);// 返回 o.exports.exports,而不是整个 o.exports 对象return o.exports.exports;}window.loader = n// 在实际的开发中,往往n会加很多的属性,有很多属性赋值的,前面的这个对象n,对应的函数就是加载器函数// n.o = function (){}// n.a = function (){}// n.b = function (){}// 初始化// console.log("1001")}({"1001": function () {console.log("test1001");window.loader("1002")console.log(document.cookie)console.log(navigator.userAgent)this.exports = 100;},"1002": function () {console.log("test1002");window.loader("1003")this.exports = 200;},"1003": function () {console.log("test1003");this.exports = 300;}, });// 在外部的任何地方都可以使用该方式进行调用 window.loader("1002")
1.4、案例【33搜帧】
案例地址链接:https://fse.agilestudio.cn/search?keyword=%E7%81%AB%E8%BD%A6%E5%91%BC%E5%95%B8%E8%80%8C%E8%BF%87
案例爬取链接:https://fse-api.agilestudio.cn/api/search?keyword=%E7%81%AB%E8%BD%A6%E5%91%BC%E5%95%B8%E8%80%8C%E8%BF%87&page=1&limit=12&_platform=web&_versioin=0.2.5&_ts=1756434489229
1.4.1、入口定位
我们拿到网站首先还是先看那些是需要我们进行破解的对象
先看响应数据--响应数据是明文的,无需关注
再看载荷数据--是查询字符串参数,params,就一个时间戳_ts有点可疑
其次再看请求头--有一个x-signature: 是加密数据,需要特别关注
我们找到我们需要爬取目标地址链接,复制URL,用【https://curlconverter.com/ 】生成基础爬虫代码
我们通过key关键字【X-Signature】进行搜索确定入口,就搜索到了一个匹配项,点击进入就是我们的入口,在搜索的地方打上断点,卡在该处,并且通过url和参数确定该处就是我们要找的入口位置
1.4.2、代码分析
确定入口以后,点击进入函数的原始位置
1.4.3、扣JS
1.4.4、补充【】
正常操作,就是报什么找什么,我们就是搜索什么然后打断点定在该处,看找不到的是function还是对象还是列表,不管是什么直接把对应的代码拷走,但是这次你会发现c是一个对象是什么不重要,重要的是往上看c的格式有点特殊,当看到这样的格式时,咱们要找的某个变量比如c来自于n(数字/key)比如:c=n("b85c") 这样的格式时就是典型的webpack,n是加载器,加载了某个key模块,c是模块对象,从这个模块对象中取出这个a,这个a可能是一个函数或者一个值
遇到这样的情况,我们就需要先找到这个n加载器函数,所以我们首先在报找不到的地方比如报c找不到,那就在c这里打上断点,一般这样的事件都是需要刷新页面触发,所以我们刷新页面让断点卡在该处,点击进入找到n加载器函数
点击进入加载器函数
很多网站都是把这个自执行函数放在js文件中,该案例是放在了html中,我们只需要扣走这个自执行函数即可 扣出来以后,单独建一个文件loader.js文件存放扣出来的全部代码 ,然后就开始思考这是一个单文件还是一个多文件 一定是多文件的 如果我们调用的模块都在下面的列表中,我们只需要拿走对应的列表或者模块就行 但是我们要用到的模块可以说是没有,或者不够,这就说明还有一些模块在别的位置,这就涉及到多文件查询,一般多文件查询多一点
1. 流程(1) 全局变量配置:在整个项目都可以调用这个加载器函数 加上分隔符 !(2) 记录调用模块的日志 运行该文件,不报任何错误,证明扣的该自执行函数没问题
在第一次创建的js文件,测试中,引入扣出来的自执行函数,由于是多文件webpack所以就会报找不到的模块
我们在根据缺失的模块进行全局搜索
require("./loader") // 引入加载器函数所在的文件 console.log(window.loader("b85c"))
把模块所在的文件,全部拷贝走 Ctrl+A Ctrl+C,因为可能会有多个模块文件,所以可以创建文件名为mod01.js
同样在第一次创建的js文件,测试中,引入扣的模块文件
require("./mod01") // 引入拷贝的模块文件
这个时候所有的依赖全部补充完成,我们就可以替换了
1.4.5、代码文件
1.4.5.1、Python文件:33搜帧.py
import requests import execjsheaders = {'Accept': 'application/json, text/plain, */*','Accept-Language': 'zh-CN,zh;q=0.9','Connection': 'keep-alive','Origin': 'https://fse.agilestudio.cn','Referer': 'https://fse.agilestudio.cn/','Sec-Fetch-Dest': 'empty','Sec-Fetch-Mode': 'cors','Sec-Fetch-Site': 'same-site','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',# 'X-Signature': 'ec61eb8eefc752b6d0d5eee0657958e0', # 可以直接删除了'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"','sec-ch-ua-mobile': '?0','sec-ch-ua-platform': '"Windows"', } params = {'keyword': '火车呼啸而过','page': '2','limit': '12','_platform': 'web','_versioin': '0.2.5',# '_ts': '1756434489229', }obj = execjs.compile(open("33搜帧.js", encoding="utf-8").read()).call("get_sign", params)headers["X-Signature"] = obj["Signature"] params["_ts"] = obj["ts"]url = 'https://fse-api.agilestudio.cn/api/search' response = requests.get(url=url, params=params, headers=headers) print(response.text)
1.4.5.2、JS文件:33搜帧.js
require("./loader") // 引入加载器函数所在的文件 require("./mod01") // 引入拷贝的模块文件 // console.log(window.loader("b85c"))n = window.loader c = n("b85c")u = n("6821") l = n.n(u)d = function (e) {e._ts = (new Date).getTime() - 9999;var t, n = Object.keys(e), i = "", o = Object(c["a"])(n.sort());try {for (o.s(); !(t = o.n()).done;) {var a = t.value;void 0 !== e[a] && null !== e[a] && (i += "".concat(a, "=").concat(e[a], ","))}} catch (r) {o.e(r)} finally {o.f()}return {Signature: l()(i),ts: e._ts}}// 测试 function get_sign(data) {return d(data) }// let data = { // "keyword": "火车呼啸而过", // "page": 1, // "limit": 12, // "_platform": "web", // "_versioin": "0.2.5" // } // console.log(get_sign(data))