一、JS逆向案例之Webpack逆向
1.1、Webpack逆向概念介绍
Webpack是一个现代的静态模块打包工具,它主要用于前端开发中的模块化打包和构建。通过Webpack,开发者可以将多个模块(包括JavaScript、CSS、图片等)进行打包,生成优化后的静态资源文件,以供在浏览器中加载和运行。
Webpack的主要功能和特点包括:
-
模块化支持:Webpack将应用程序拆分为多个模块,通过模块化的方式管理和加载依赖关系。它支持***monJS、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"); // bar
console.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.***/search?keyword=%E7%81%AB%E8%BD%A6%E5%91%BC%E5%95%B8%E8%80%8C%E8%BF%87
案例爬取链接:https://fse-api.agilestudio.***/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.***/ 】生成基础爬虫代码
我们通过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 execjs
headers = {
'A***ept': 'application/json, text/plain, */*',
'A***ept-Language': 'zh-***,zh;q=0.9',
'Connection': 'keep-alive',
'Origin': 'https://fse.agilestudio.***',
'Referer': 'https://fse.agilestudio.***/',
'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.***pile(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.***/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))