js逆向-webpack
前言
钟表可以回到起点,但已不是昨天
⚠️声明:本文所涉及的爬虫技术及代码仅用于学习、交流与技术研究目的,禁止用于任何商业用途或违反相关法律法规的行为。若因不当使用造成法律责任,概与作者无关。请尊重目标网站的
robots.txt协议及相关服务条款,共同维护良好的网络环境。
1.webpack打包
1.1概念
Webpack 是一个现代 JavaScript 应用的打包工具,它可以将模块化的代码转换成浏览器可执行的 JavaScript 文件。Webpack 主要提供以下功能:
- 模块打包:支持 ES Modules、CommonJS、AMD 等模块格式。
- 代码拆分(Code Splitting):按需加载代码,减少首屏加载时间。
- 加载资源(Loaders):处理 CSS、图片、字体、TypeScript 等文件。
- 插件扩展(Plugins):优化打包过程,如压缩代码、提取 CSS、生成 HTML 文件等。
- 开发服务器(Webpack DevServer):提供热更新(HMR),加快开发效率。
如果一个页面大部分是script标签构成,80%以上是 webpack 打包

1.2webpack
e :其实是一个数组,包含了所有模块的定义
加载器 (n(t)):
n(2)被调用,意味着执行模块2。n(2)检查c[2]是否已加载(最开始是undefined)。加载直接返回return c[2].exports;c[2]还未加载,因此创建c[2],然后执行e[2].call(a.exports, a, a.exports, n)a.l = !0表示该模块加载完成。- 返回
a.exports(但这里没有exports,所以undefined)。
总结:
- 这个代码实现了一个简化版的
Webpack模块加载器。 - 其中
n(t)负责按需加载模块,并用c缓存已加载模块。 - 入口
n(2)直接执行了console.log('模块3')
!function (e) {
var c = {} // 用于存储已加载的模块
function n(t) {
if (c[t]) // 如果模块已加载,则直接返回
return c[t].exports;
var a = c[t] = { // 创建新模块对象
i: t, // 模块 ID
l: !1, // 是否加载完成
exports: {} // 模块导出对象
};
e[t].call(a.exports, a, a.exports, n), // 执行模块函数
a.l = !0; // 标记模块已加载
return a.exports;
}
n.m = e; // 存储所有模块
// 入口执行模块 2
n(2);
}
([
function () {
console.log('模块1')
},
function () {
console.log('模块2')
},
function () {
console.log('模块3')
}
]);
1.3模块加载器
Webpack 的 模块加载器 是 Webpack 运行时(Runtime)的核心部分,它的主要功能是 按需加载模块,并确保模块之间的依赖关系得到正确解析和执行。
Webpack 在打包代码时,会将模块转换为 对象或数组的形式,并将其存储在 Webpack 运行时所管理的模块列表中。在运行时,模块加载器 负责:
- 管理已加载的模块,防止重复执行。
- 按需加载模块,提高执行效率。
- 解析依赖,确保模块之间的引用正确执行。
- 提供
require/import机制,让模块可以相互调用。
总结
- Webpack 的模块加载器 负责动态加载模块,管理缓存,并解析依赖。
- 执行入口模块(
n(2);)是核心,它检查缓存、执行模块,并返回exports。 - 优化点:
- 代码分割(Code Splitting) 提高加载效率
- 按需加载(Dynamic Import) 只加载必要模块
- 热更新(HMR) 替换模块而不刷新页面
(function (e) { ... })([ ... ]) 存储所有模块:
e是一个数组,每个元素都是一个模块函数。- 这些模块函数是 Webpack 打包后的代码。
var c = {}:模块缓存。这个对象用来存储已经加载的模块,防止重复加载。
function n(t) {}:这是 Webpack 动态加载模块的关键函数,它根据 t 来加载相应的模块。
if (c[t]) return c[t].exports:检查模块是否已经加载。如果模块已经加载,直接返回 exports,避免重复执行。
var a = c[t] = { i: t, l: !1, exports: {} };:
- 创建一个新的模块对象,并存储到
c里。 exports用于存储模块的导出内容(类似于module.exports)。
e[t].call(a.exports, a, a.exports, n):执行模块函数,传入:
a:当前模块对象a.exports:导出对象n:模块加载函数
a.l = !0;:标记模块已加载。执行完模块代码后,将 l 设置为 true,避免重复加载。
return a.exports;:返回模块的 exports,供其他模块使用。
n.m = e;:存储所有模块
n(2);:执行入口模块。这里调用 n(2),即 加载并执行 ID 为 2 的模块。
!function (e) {
var c = {} // 用于存储已加载的模块
function n(t) {
if (c[t]) // 如果模块已加载,则直接返回
return c[t].exports;
var a = c[t] = { // 创建新模块对象
i: t, // 模块 ID
l: !1, // 是否加载完成
exports: {} // 模块导出对象
};
e[t].call(a.exports, a, a.exports, n), // 执行模块函数
a.l = !0; // 标记模块已加载
return a.exports;
}
n.m = e; // 存储所有模块
// 入口执行模块 2
n(2);
}
([
function () {
console.log('模块1')
},
function () {
console.log('模块2')
},
function () {
console.log('模块3')
}
]);
1.4webpack数组形式
在 Webpack 打包后,所有的模块会被存储在一个数组或对象中,数组索引(或对象的 key)就是模块的 ID,对应的值是模块的代码(通常是一个函数)。这种形式有助于 Webpack 高效管理模块的加载。
Webpack 采用数组存储模块的原因:
- 索引访问更快
- 直接通过
n(id)即可访问e[id],效率高。
- 直接通过
- 代码结构紧凑
- 使用数组存储函数,能减少代码体积。
- 避免命名冲突
- 通过索引管理模块,避免不同模块使用相同的变量名。
模块数组 e,传入 e 是一个数组,其中:
e[0]:function () { console.log('模块1') }e[1]:function () { console.log('模块2') }e[2]:function () { console.log('模块3') }
入口模块执行n(2);:
n(2)表示执行e[2](即console.log('模块3'),索引从0开始的)。
!function (e) {
var c = {} // 用于存储已加载的模块
function n(t) {
if (c[t]) // 如果模块已加载,则直接返回
return c[t].exports;
var a = c[t] = { // 创建新模块对象
i: t, // 模块 ID
l: !1, // 是否加载完成
exports: {} // 模块导出对象
};
e[t].call(a.exports, a, a.exports, n), // 执行模块函数
a.l = !0; // 标记模块已加载
return a.exports;
}
n.m = e; // 存储所有模块
// 入口执行模块 2
n(2);
}
([
function () {
console.log('模块1')
},
function () {
console.log('模块2')
},
function () {
console.log('模块3')
}
]);

1.5webpack对象形式
Webpack 采用 键值对 存储模块,每个模块的 key 是模块 ID,value 是模块的代码(通常是一个函数)。
Webpack 将所有模块封装在一个对象 e 中:
- 对象的
key(即0,1,2)是模块 ID。 - 对象的
value是模块对应的函数,模块代码在其中执行。
这种方式允许 Webpack 按需加载特定模块,而不像数组形式那样需要按索引顺序组织。
!function (e) {
var c = {} // 用于存储已加载的模块
function n(t) {
if (c[t]) // 如果模块已加载,则直接返回
return c[t].exports;
var a = c[t] = { // 创建新模块对象
i: t, // 模块 ID
l: !1, // 是否加载完成
exports: {} // 模块导出对象
};
e[t].call(a.exports, a, a.exports, n), // 执行模块函数
a.l = !0; // 标记模块已加载
return a.exports;
}
n.m = e; // 存储所有模块
// 入口执行模块 2
n(2);
}
({
0: function () {
console.log('模块1')
},
1: function () {
console.log('模块2')
},
2: function () {
console.log('模块3')
}
});

1.6多js文件打包
当 Webpack 处理多个 js 文件时,它会将模块拆分并动态加载,使用全局变量 window["webpackJsonp"] 来存储和管理动态导入的模块。
当 Webpack 进行代码拆分(Code Splitting),或者使用 import() 进行按需加载(Lazy Loading)时,它会:
- 定义全局变量
window["webpackJsonp"] = [](存储动态加载的模块) - 重写
window["webpackJsonp"].push()方法,替换为webpackJsonpCallback()来处理新加载的模块 - 动态加载的
js文件 会调用window["webpackJsonp"].push(),注册新的模块
Webpack 动态加载模块 时,会调用 window["webpackJsonp"].push(),通常接受 3 个参数:
- 参数 1:模块 ID(
chunkId) - 参数 2:模块定义(
modules对象) - 参数 3:运行时的
webpackJsonpCallback()(可选)
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([
[1], // 模块 ID(chunkId)
{
"./src/moduleA.js": function(module, exports) {
console.log("模块 A 加载成功");
},
"./src/moduleB.js": function(module, exports) {
console.log("模块 B 加载成功");
}
},
[[1]] // 可选的执行入口(runtime callback)
]);
Webpack 在 runtime 代码中会重写 window["webpackJsonp"].push(),执行 webpackJsonpCallback()。
function webpackJsonpCallback(data) {
var chunkId = data[0]; // 模块 ID
var modules = data[1]; // 模块代码
var runtime = data[2]; // 运行时回调(可选)
// 将新加载的模块加入 Webpack 内部模块管理
for (var moduleId in modules) {
if (Object.prototype.hasOwnProperty.call(modules, moduleId)) {
__webpack_modules__[moduleId] = modules[moduleId];
}
}
// 执行回调(如果存在)
if (runtime) runtime();
}
// 重写 `window["webpackJsonp"].push()` 为 `webpackJsonpCallback`
window["webpackJsonp"].push = webpackJsonpCallback;
运行时流程
-
页面初始化时
-
window["webpackJsonp"] = [] -
window["webpackJsonp"].push = webpackJsonpCallback
-
-
按需加载模块
-
Webpack 生成
chunk.js -
该
chunk.js执行window["webpackJsonp"].push([...]) -
触发
webpackJsonpCallback()
-
-
webpackJsonpCallback()解析chunk.js并执行代码-
注册模块 到 Webpack 模块缓存
-
调用回调,执行动态加载模块
-
Webpack 代码拆分的核心机制
- 存储动态模块:
window["webpackJsonp"] = [] - 拦截
push():Webpack 通过webpackJsonpCallback()处理新加载模块 - 按需加载:动态
import()触发 Webpack 生成chunk.js,并执行window["webpackJsonp"].push() - 模块管理:Webpack 将模块存入
__webpack_modules__并执行回调
2.36kr逆向
接口地址:https://gateway.36kr.com/api/mus/login/byMobilePassword

2.1关键字搜索
关键字推断:
password加密,就搜索password- 也可以搜索
encrypt
这里搜索到了很多,这个是在发送/login/byMobilePassword请求时处理了password,感觉很像就打个断点调试一下

可以看到这里Object(i.b)就是加密的逻辑
m("/login/byMobilePassword", {
countryCode: o.a.get(t, "phoneItc.code"),
mobileNo: Object(i.b)(o.a.get(t, "mobileNo")),
password: Object(i.b)(o.a.get(t, "password"))
});

2.2xhr断点
添加/api/mus/login/byMobilePassword的xhr断点
/api/mus/login/byMobilePassword

在调用堆栈的异步方法之前都没有找到加密位置,所以只能在后面异步方法上打断点调试

异步方法可能被调用多次,看参数和自己想找的不相关的话就跳过

2.3hook拦截
因为加密肯定是在发送请求前,所以使用hook拦截了xhr
注意检查自己的地址
/api/mus/login/byMobilePassword,然后ctrl + enter运行脚本
(function () {
var open = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, url, async) {
if (url.indexOf("/api/mus/login/byMobilePassword") != -1) {
debugger;
}
return open.apply(this, arguments);
};
})();

然后就一步步调试,依然可以找到加密的地方

2.4逆向-加载全部模块
找到加密的位置后就可以跳转到加密的实现进行具体的逆向
m("/login/byMobilePassword", {
countryCode: o.a.get(t, "phoneItc.code"),
mobileNo: Object(i.b)(o.a.get(t, "mobileNo")),
password: Object(i.b)(o.a.get(t, "password"))
});

进入 Object(i.b)后,发现使用setPublicKey设置了一个密钥,所以这应该是非对称加密(rsa)加密,具体的实现还是r.encrypt(e),这里可以不着急跳转到r.encrypt(e),可以先查看r的实现r = n(16),所以我们跳到n的实现
var r = n(16)
, o = n(4)
, i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB"
, a = function() {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
if (!Object(o.isNodeEnv)()) {
var t = n(774)
, r = new t.JSEncrypt;
r.setPublicKey(i);
var a = r.encrypt(e);
return a
}
}

跳转到n的实现发现n是一个模块加载器,所以这是一个webpack打包的脚本
-
n(t):根据t来加载相应的模块 -
n.m = e;:存储所有模块
function n(t) {
if (c[t])
return c[t].exports;
var a = c[t] = {
i: t,
l: !1,
exports: {}
};
return e[t].call(a.exports, a, a.exports, n),
a.l = !0,
a.exports
}
n.e = function(e) {}
,
n.m = e,

接下来我们复制整个脚本到编辑器

声明变量moudle,在webpack内部获取到n,外部传入下标5执行一下
报错:window is not defined
var module;
!function(e) {
...
module = n
}([]);
console.log(module(5))

之前我们遇到过,window 是浏览器对象,pycharm执行没有浏览器环境,给个默认值即可
window = global;
但是报错 Cannot read properties of undefined (reading 'call'),其实就是没有找到执行的模块

回到浏览器,使用控制台输出n(16),找到n(16)的调用

查看n(16)的方法

在查看整个加载代码,因为浏览器折叠不方便,我们全部复制到pycharm编辑器上

我们将代码全部折叠,发现有985个模块,复制这个数组

我们将985个模块全部复制到webpack中自执行的方法参数中

声明一个数组存储执行过模块的索引,再次执行
var mouduleArr = [];
mouduleArr.push(t)

成功执行,解密代码去浏览器复制,我这里只写重要的代码
代码
window = global;
var module;
var moduleArr = [];
!function (e) {
......
function n(t) {
if (c[t])
return c[t].exports;
var a = c[t] = {
i: t,
l: !1,
exports: {}
};
// 输出索引 方便定位缺失的模块
// console.log(t)
moduleArr.push(t)
return e[t].call(a.exports, a, a.exports, n),
a.l = !0,
a.exports
}
}([...放入所有模块,985个元素...]);
console.log(module(5))
console.log('已执行模块:' + moduleArr.join(','))

2.5逆向-按需加载模块
我们把webpack所有需要的模块参数删除,然后在上面输出添加代码console.log(t)打印出执行模块的下标

在浏览器中看到var r = n(16),说明从下标为16的模块开始执行的,所以我们测试也从console.log(module(16))开始
var r = n(16)
, o = n(4)
, i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB"
, a = function() {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
if (!Object(o.isNodeEnv)()) {
var t = n(774)
, r = new t.JSEncrypt;
r.setPublicKey(i);
var a = r.encrypt(e);
return a
}
}

在浏览器的控制台输出n.m[16],找到n.m[16]模块,然后复制,粘贴到webpack的参数中
n.m[16]

浏览器是根据模块的下标的,n.m[16]代表第17个模块,但是粘贴到逆向代码中就变成0下标的模块,所以我们可以使用webpack对象形式,把这个模块设置键为16,这样就好不影响调用了

再次执行,缺少第42个模块

再次在浏览器的控制台输出n.m[42],找到n.m[42]模块,然后复制,粘贴到webpack的参数中
n.m[42]

再次执行,缺少第57个模块

再次在浏览器的控制台输出n.m[57],找到n.m[57]模块,然后复制,粘贴到webpack的参数中
n.m[57]

再次执行总算成功了

接下来需要重写加密方法
// 原代码
var r = n(16)
, o = n(4)
, i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB"
, a = function() {
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
if (!Object(o.isNodeEnv)()) {
var t = n(774)
, r = new t.JSEncrypt;
r.setPublicKey(i);
var a = r.encrypt(e);
return a
}
}
// 修改后
function get_encrypt_data(userinfo) {
var r = module(16),
o = module(4),
i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB";
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
if (!Object(o.isNodeEnv)()) {
var t = module(774)
, r = new t.JSEncrypt;
r.setPublicKey(i);
var a = r.encrypt(e);
return a
}
}

这里需要注意o = module(4),刚才我们找到了r = module(16)的所有模块,所以这里还需要找到o = module(4)需要的模块,具体使用浏览器的调试的流程我这里就不写了,跟上面一样。具体需要以下模块,找到后复制到webpack参数下面即可:

2.6js逆向代码
测试完成后,需要修改js逆向的代码给python调用
我就写个具体修改的地方,算做个总结
window = global;
navigator = {
appName: 'Netscape'
}
var module;
var moduleArr = [];
!function (e) {
// 这里有代码...
function n(t) {
if (c[t])
return c[t].exports;
var a = c[t] = {
i: t,
l: !1,
exports: {}
};
// 输出索引 方便定位缺失的模块
console.log(t)
moduleArr.push(t)
return e[t].call(a.exports, a, a.exports, n),
a.l = !0,
a.exports
}
// 这里有代码...
// a()
module = n
}({
1: function (e, t, n) {},
4: function (e, t, n) {},
16: function (e, t, n) {},
// 其他模块代码
...
})
// 测试代码
// console.log(module(16))
// console.log(module(4))
// console.log(moduleArr.join(','))
// console.log('已执行模块:' + moduleArr.join(','))
// console.log(get_encrypt_data('15566668888'))
// python调用代码
function get_encrypt_data(userinfo) {
var r = module(16),
o = module(4),
i = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeiLxP4ZavN8qhI+x+whAiFpGWpY9y1AHSQC86qEMBVnmqC8vdZAfxxuQWeQaeMWG07lXhXegTjZ5wn9pHnjg15wbjRGSTfwuZxSFW6sS3GYlrg40ckqAagzIjkE+5OLPsdjVYQyhLfKxj/79oOfjl/lV3rQnk/SSczHW0PEyUbQIDAQAB";
var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : "";
if (!Object(o.isNodeEnv)()) {
var t = module(774)
, r = new t.JSEncrypt;
r.setPublicKey(i);
var a = r.encrypt(e);
return a
}
}
2.7python调用
代码
大部分问题之前有遇到过,主要是确认header没问题,post请求发送的json数据
# 第一种
import subprocess
import time
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import requests
headers = {
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/json",
"Origin": "https://36kr.com",
"Pragma": "no-cache",
"Referer": "https://36kr.com/",
"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/132.0.0.0 Safari/537.36",
"sec-ch-ua": "Not A(Brand;v=8, Chromium;v=132, Google Chrome;v=132",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "Windows"
}
cookies = {
"userId": "",
"tfstk": "gNLZ_1VR8V3a7mYZn6b4Yj3Yrk_OlND5_E6fiIAc1OXGIsDVm9JACG_MuHU2wILs6ZGOWCQAoYMSPKR96ZIxXLF67J4haBf0IrmC-6iiwsDSP4OgLB0uDYT6J93NCsbcsiXGxX5PNRbcnrcFKs58mRvcoXlFi_PGssfm-WfV_ZXDnZcet9CqZWWcFy5PjjS68CXQcvIOECXUozPRGGmDtoa8yefNbEAGL11681SNEMe_RvYMdIY6WMn3YZdB4K-DEvZ1I35yLgtZUPJyBsAVidu8a9YwgeS9A7ayTiWwxFjUirABttvW0dkaXOx6LgChxSuROL6H6FxEMJAMFO7GtMgoaBjDAFIJ5v4Nr3d15nvodl6M4sjyn-C3Gr8vb-qVj6CFFXlezaGzfPk-jkrYDGKdTTGt6oEAj6CFFXlUDoIOy6WS6fC.."
}
url = "https://gateway.36kr.com/api/mus/login/byMobilePassword"
data = {
"krtoken": "",
"partner_id": "web",
"timestamp": int(round(time.time() * 1000)),
"param": {
"countryCode": "86",
"mobileNo": "",
"password": ""
}
}
js = execjs.compile(open('5.6kr按需加载模块.js',encoding='utf-8').read())
encrypt_mobileNo = js.call("get_encrypt_data",'15566669999')
encrypt_password = js.call("get_encrypt_data",'123qweasd')
# data = json.dumps(data, separators=(',', ':'))
data['param']['mobileNo']= encrypt_mobileNo
data['param']['password']= encrypt_mobileNo
print(data)
response = requests.post(url, headers=headers, cookies=cookies, json=data)
print(response.text)

3.拼多多跨境卖家中心登录逆向
地址:https://seller.kuajingmaihuo.com/login/
接口:https://seller.kuajingmaihuo.com/bg/quiet/api/mms/login

2.1关键字搜索定位加密
请求参数encryptPassword进行了加密,我们直接搜索

然后打断点确认,M.password是密码,N.current是公钥,Object(P.a)是加密方法
var i = {
loginName: Object(W.a)({
phoneNo: M.mobile,
areaCode: M.areaCode
}),
encryptPassword: Object(P.a)(M.password, N.current),
keyVersion: N.current.version,
verifyCode: p ? M.verifyCode : void 0
};

2.2逆向
跳转到Object(P.a)的实现,发现这里就是加密的实现
var g = o(1503)可以大致确定为webpack打包
var g = o(1503),
B = function(A, e) {
try {
var o = new g.a;
return o.setPublicKey(e.publicKey),
o.encrypt(A)
} catch (t) {}

在var g = o(1503)打上断点,刷新页面,然后就可以跳转到o的实现了

发现 c(t)是一个模块加载器,说明这是webpack打包的js脚本
function c(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, c),
r.l = !0,
r.exports
}

我们复制整个webpack脚本到编辑器,然后就要开始定位需要执行的模块了

声明一个window变量用于模拟浏览器环境
声明外部变量module,在webpack内部的最后进行赋值,赋值给模块加载器
因为我们外部代码需要测试调用

我们还需要输出模块加载器的下标或者键,方便确认代码的调用流程

然后在浏览器调试获取公钥e.publicKey

最后重写加密函数,执行代码,根据错误输出我们需要找到键为1503或者下标为1503的模块
console.log(get_encrypt_data('123qweasd'))
function get_encrypt_data(text) {
var g = module(1503)
var e = {
publicKey: 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJpaLPONg3qWfi2kdwauqOT3L7AEK6lrMZqKYZv0ikWPZtzTqnVLMeolsE7x3Sc4UxS6ews+WS0ijwIEU8GAbhr2aJh0F+o6bpYQgWosHBts4lMW/IdtKg45DSGgBWg8gWDTK7K+T+zWfTjEQGwEgNHsZ2NbQBtTEZ35CDWUP7iQIDAQAB',
version: '1'
}
var o = new g.a;
return o.setPublicKey(e.publicKey),
o.encrypt(text)
}

刷新页面,再次调试定位到var g = o(1503),我们在浏览器控制台输出o.m[1503]

跳转到o.m[1503]实现,发现这是一个对象形式的模块脚本
这里浏览器不方便折叠代码,我们把模块所有代码复制到编辑器

发现只有3个模块,模块不多,其实可以全部复制到webpack的参数里
这里为了学习还是一个一个模块的调试,我么先复制1503模块

以对象的形式复制到webpack参数里,然后再次执行代码,发现缺少1732

在模块代码里找到1732模块,再次复制

再次执行代码,发现密钥成功生成
这次模块不多,复制全部模块和复制执行的模块没多少差距,就不多做赘述了

2.3js逆向代码
测试完成后,需要修改js逆向的代码给python调用
我就写个具体修改的地方,算做个总结
window = global
var module;
!function (e) {
// ...其它代码
function c(t) {
if (n[t])
return n[t].exports;
var r = n[t] = {
i: t,
l: !1,
exports: {}
};
// 输出执行模块的下标或者键
console.log(t)
return e[t].call(r.exports, r, r.exports, c),
r.l = !0,
r.exports
}
,
// 存储了所有模块
c.m = e,
// ...其它代码
// r()
module = c
}({
// 所有模块的代码 模块的具体实现
1503: function (t, e, r){},
1732: function (t, e, r){}
// 其他模块代码
...
})
// // 测试
// console.log(get_encrypt_data('123qweasd'))
function get_encrypt_data(text) {
var g = module(1503)
var e = {
publicKey: 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJpaLPONg3qWfi2kdwauqOT3L7AEK6lrMZqKYZv0ikWPZtzTqnVLMeolsE7x3Sc4UxS6ews+WS0ijwIEU8GAbhr2aJh0F+o6bpYQgWosHBts4lMW/IdtKg45DSGgBWg8gWDTK7K+T+zWfTjEQGwEgNHsZ2NbQBtTEZ35CDWUP7iQIDAQAB',
version: '1'
}
var o = new g.a;
return o.setPublicKey(e.publicKey),
o.encrypt(text)
}

2.4python调用
代码
如果调不通,排除js问题后,也记得检查自己的headers、cookies、参数
# 第一种
import subprocess
import time
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import requests
headers = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9",
"anti-content": "0aqWtqlYNcHgjgv2R4kIJVEhS6S_HM4e4KLUNxFkMP1l2eyQGez6vxyPil8n1SMYOnNDl1z29vEGKVB6xUryG42yhGHhO-BPFmpVgVE2uJQXSEEZp0IBC9TFXTb0KAYbTq8Owq0YhYhbxCKkKi-QocLR2mIcz-M0AaghfV8RVj8N2jybYJXc6gljug7oKh7azzN0rE9sEaCiu9M89umb9fanmUjV7v7aWOA--F9LR9u89PCVkf2QoVAC2UWCaxiVBHAus360E9WYLsEb2QN5lHVgMLVAC9lCKK8aEWb5mDLbAiXLQKDqLYUkzw0lf9GDK2CN6TyBoaGpCcVMTVEh0iO7Z4iNoXGnRjVH-wG5FA3oMZ_DZ7bClMuuUYmlkju8WuMCtWSohjbT6TKaXWAaizAmJuFiykszkX5OZezbIK55FAgz_ksz_nzdeoBd-W6n3ASLeP3bNxue5cRq1ijG0E3B4WwplHxtoJ1PZFj54Zaa542VpPTFKK5032ggs9HmeJHdKMTppKSGpp5tbJvIuQgQSMXSX9MNob9BuFzClC5_pSG47B_O8m7X0UfdII_dQ4WYap8Lw-hZwIBrxFEX8dOmgPoqykamcqxApyh-ybDjf3ruJrT5BcQ7R-OyZUzxbbSUIEBvV7DOPWBMccE8zK8iWCStReo4zTXStGv_6vf",
"cache-control": "no-cache",
"content-type": "application/json",
"origin": "https://seller.kuajingmaihuo.com",
"pragma": "no-cache",
"priority": "u=1, i",
"referer": "https://seller.kuajingmaihuo.com/login/",
"sec-ch-ua": "\"Not A(Brand\";v=\"8\", \"Chromium\";v=\"132\", \"Google Chrome\";v=\"132\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36"
}
cookies = {
"api_uid": "CmaweWfHTNKOFABMOMrlAg==",
"_nano_fp": "XpmYXpgan0mxl0TonT_ZXgfAKu4TpVbigK894tJT",
"_bee": "yqf3uKQSTXbdy80DMDehwi20WJtkRam6",
"_f77": "88ebfb0b-3918-44b3-841c-273300b748f3",
"_a42": "d65c2710-c318-428e-8dfd-1e1d0763cc0d",
"rckk": "yqf3uKQSTXbdy80DMDehwi20WJtkRam6",
"ru1k": "88ebfb0b-3918-44b3-841c-273300b748f3",
"ru2k": "d65c2710-c318-428e-8dfd-1e1d0763cc0d"
}
url = "https://seller.kuajingmaihuo.com/bg/quiet/api/mms/login"
data = {
"loginName": "15588889999",
"encryptPassword": "",
"keyVersion": "1"
}
# data = json.dumps(data, separators=(',', ':'))
js = execjs.compile(open('7.拼多多.js', encoding='utf-8').read())
encrypt_encryptPassword = js.call("get_encrypt_data",'123qweasd')
data['encryptPassword']= encrypt_encryptPassword
print(f'参数:{data}')
response = requests.post(url, headers=headers, json=data)
print(response.text)
print(response)

4.33搜帧逆向
地址:https://fse.agilestudio.cn/search?keyword=火车呼啸而过

4.1关键字搜索
请求头中带有x-signature,肯定有加密或赋值的地方,所以我们直接尝试搜索x-signature

然后打断点确认,e.params是请求参数, _是加密方法
return e.params["_platform"] = "web",
e.params["_versioin"] = "0.2.5",
e.headers["X-Signature"] = _(e.params),
e

4.2逆向
跳转到_(e.params)的实现,l()(i)为最后加密方法
var i = n("bc3a")
, o = n.n(i)
, a = n("4360")
, r = n("5f87")
, s = n("eae4")
, 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 console.log("signature:", i),
l()(i)
}

c = n("b85c")这种写法就很像webpack,我们先跳转到n的实现,在n的实现在确定一下

发现o(n) 就是模块加载器,确定就是webpack
function o(n) {
if (t[n])
return t[n].exports;
var c = t[n] = {
i: n,
l: !1,
exports: {}
};
return e[n].call(c.exports, c, c.exports, o),
c.l = !0,
c.exports
}

这个webpack在页面中,我们复制整个代码到编辑器中

声明一个window变量用于模拟浏览器环境
声明外部变量module,在webpack内部的最后进行赋值,赋值给模块加载器
因为我们外部代码需要测试调用
console.log(n);moduleArr.push(n);是为了要执行的模块的下标或者键,方便确认代码的调用流程
console.log(module('bc3a'))是为了测试调用整个webpack代码

执行代码如果只是下面问题的话就说明缺少执行的模块
接下来需要找到执行的模块

在浏览器控制台输出缺少的模块值,然后通过控制台输出跳转到模块实现,根据代码可以确定这是对象形式的webpack
当然我们也可以把所有模块都复制过来
n.m['bc3a']

我们复制整个模块到编辑器

复制这1182个模块

粘贴到webpack代码自执行函数的参数中(这是对象形式,使用{})

然后执行脚本,我们发现变量c都需要84个脚本,虽然代码很多,导致编辑器卡顿,但是不建议按需加载模块,一个模块一个模块的去找,我这里就直接复制全部模块

4.3js逆向代码
测试完成后,需要修改js逆向的代码给python调用
我就写个具体修改的地方,算做个总结
window = global
var module;
!function (e) {
// ...其它代码
function o(n) {
if (t[n])
return t[n].exports;
var c = t[n] = {
i: n,
l: !1,
exports: {}
};
console.log(n)
moduleArr.push(n);
return e[n].call(c.exports, c, c.exports, o),
c.l = !0,
c.exports
}
,
// 存储了所有模块
o.m = e,
// ...其它代码
// c()
module = o;
}({
// 所有模块的代码 模块的具体实现
"0094": function(e, t, n){},
"00ad": function(e, t, n),
// 其他模块代码
...
})
// 测试代码
// console.log(module('b85c'))
// console.log(moduleArr.join(','))
// console.log(moduleArr.length)
var e = {
'keyword': '火车呼啸而过',
'page': '1',
'limit': '12',
'_platform': 'web',
'_versioin': '0.2.5',
'_ts': 1741183716088,
}
function get_encrypt_data(e) {
var c = module("b85c"),
u = module("6821"),
l = module.n(u);
var t, n = Object.keys(e), i = "", o = Object(c["a"])(n.sort());
for (o.s(); !(t = o.n()).done;) {
var a = t.value;
void 0 !== e[a] && null !== e[a] && (i += "".concat(a, "=").concat(e[a], ","))
}
return l()(i)
}
// console.log(get_encrypt_data(e))

4.4python调用
代码
记得检查自己的headers、cookies、参数
# 第一种
import subprocess
import time
from functools import partial
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
import execjs
import requests
headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Origin": "https://fse.agilestudio.cn",
"Pragma": "no-cache",
"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/132.0.0.0 Safari/537.36",
"X-Signature": "",
"sec-ch-ua": "\"Not A(Brand\";v=\"8\", \"Chromium\";v=\"132\", \"Google Chrome\";v=\"132\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\""
}
url = "https://fse-api.agilestudio.cn/api/search"
params = {
"keyword": "火车呼啸而过",
"page": "1",
"limit": "12",
"_platform": "web",
"_versioin": "0.2.5",
"_ts": int(round(time.time() * 1000))
}
js = execjs.compile(open('10.33搜帧.js', encoding='utf-8').read())
sign_encrypt = js.call("get_encrypt_data", params)
headers["X-Signature"] = sign_encrypt
response = requests.get(url, headers=headers, params=params)
print(response.text)
print(response)

5.爱奇艺登录逆向
地址:https://m.iqiyi.com/user.html#baseLogin
接口:https://passport.iqiyi.com/apis/reglogin/login.action
网页版没法测试登录

我看了下移动版可以,反正都是逆向加密,我就看看这个移动版的

5.1逆向webpack
查了2个多小时左右,本来以为webpack很快能搞定,但是代码嵌套太多了
一个webpack嵌套另一个webpack,只是加密的功能,所以我还是决定扣代码

5.2逆向代码
关键字搜索passwd,基本上可以确认rsaUtil.RSAEncryption就是加密方法了
if (opts.param.passwd) {
opts.param.passwd = rsaUtil.RSAEncryption(opts.param.passwd);
}

跳转到RSAEncryption实现 ,这里很简单,只需要找到RSA的生成
//RSA加密
RSAEncryption: function(proclaimedCode) {
var EXPONENT_KEY = '10001';
var MODULUS_KEY = 'ab86b6371b5318aaa1d3c9e612a9f1264f372323c8c0f19875b5fc3b3fd3afcc1e5bec527aa94bfa85bffc157e4245aebda05389a5357b75115ac94f074aefcd';
//参数1为公钥的exponent(hex),参数2请留空,参数3为公钥的modulus(hex)
var RSA_key = RSA.getKeyPair(EXPONENT_KEY, '', MODULUS_KEY);
//要加密的字符必须encode编码,replace是因为如果要加密的字符过长自动分段和后台商定用'-'分割
var result = RSA.encryptedString(RSA_key, encodeURIComponent(proclaimedCode)).replace(/\s/g, '-');
return result;
}

在上面找到RSA
var RSA = __webpack_require__(55);

在这里断点,然后转到实现,看代码可以确定这是一个webpack

我们跳转到__webpack_require__.m[55]的实现

我们复制里面的自执行函数,里面是RSA代码的实现

删除$,我们不是浏览器环境,也没有jquery环境

我们这里优化一下代码,缺什么补什么
需要$w作为参数,我们就在外部声明一下全局,然后传进去
再给RSAUtils默认值
var $w = global || window; // 兼容 Node.js 和浏览器
(function ($w) {
// 其他代码
...
var RSAUtils = {};
if (typeof $w.RSAUtils === 'undefined') {
$w.RSAUtils = RSAUtils; // 将 RSAUtils 挂载到全局
} else {
RSAUtils = $w.RSAUtils; // 复用全局的 RSAUtils
}
// 其他代码
...
})($w);

然后我们把页面代码修改一下,基本就没什么问题了
RSAEncryption = function (proclaimedCode) {
var EXPONENT_KEY = '10001';
var MODULUS_KEY = 'ab86b6371b5318aaa1d3c9e612a9f1264f372323c8c0f19875b5fc3b3fd3afcc1e5bec527aa94bfa85bffc157e4245aebda05389a5357b75115ac94f074aefcd';
//参数1为公钥的exponent(hex),参数2请留空,参数3为公钥的modulus(hex)
var RSA_key = RSAUtils.getKeyPair(EXPONENT_KEY, '', MODULUS_KEY);
//要加密的字符必须encode编码,replace是因为如果要加密的字符过长自动分段和后台商定用'-'分割
var result = RSAUtils.encryptedString(RSA_key, encodeURIComponent(proclaimedCode)).replace(/\s/g, '-');
return result;
}
console.log(RSAEncryption('123qweasd'))

因为登录时还需要验证码,这里暂时先完成加密
📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

浙公网安备 33010602011771号