基于一位“传统”掌握C.V大法的CURD编码人员,空余时间对 webpack,js逆向,python爬虫的实践日志,感谢 “Python爬虫案例 | Scrape Center” 作者老师提供的案例,关于 “Scrape | Movie” 逆向,刚开始接触的时候网上的老师都是基于 crypto-js,或者python算法去对 请求 token 做加密操作,对于小白或 传统后端人员很难入门,所以做一个 从扣代码开始的程序员入门讲解。
一:webpack 初探
webpack为js应用的打包工具,将ES Modules、CommonJS、AMD 代码转换成浏览器可执行的 js 文件,使用 webpack 构建的源码项目,打包后会生成
app.e9fbf43f…..js,
chunk-vendors.77daf991.js,
chunk-10192a00.243cb8b7.js
……
js文件。app.x.js 文件为 应用的入口文件/加载器文件。chunk-10192a00.243cb8b7.js,则是为 模块文件
1、app.js、chunk-10192a00.243cb8b7.js 文件结构初探
主要文件 app.js
window=global;
var __webpack_modules__ = {};
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"] = []
window["webpackJsonp"].push = webpackJsonpCallback;
const myModule = require('./webpackJsonp_demo1.js');
!(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; // 存储所有模块
// 入口执行模块 114bfd
n("114bfd");
})({
"114bfd":function () {
console.log('模块 114bfd')
},
"113bfd":function () {
console.log('模块 113bfd')
},
"112bfd":function () {
console.log('模块 112bfd')
}
});
模块文件 webpackJsonp_demo1.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["chunk-10192a00"], {
"5a19": function() {
console.log("chunk-10192a00 5a19")
}
}])
解释 app.js 文件 里面的参数
具体的明确解释,这位老师解释很清楚 js逆向-webpack – peng_boke – 博客园
e :app.js里面定义的模块数组
加载器 (n(t)):
n(114bfd) 被调用,意味着执行模块 114bfd。
n(114bfd) 检查 c[5a19] 是否已加载(最开始是 undefined)。加载直接返回return c[114bfd].exports;
c[114bfd] 还未加载,因此创建 c[114bfd],然后执行e[114bfd].call(a.exports, a, a.exports, n)
a.l = !0 表示该模块加载完成。
返回 a.exports(但这里没有 exports,所以 undefined)。
总结:
这个代码实现了一个简化版的 Webpack 模块加载器。
其中 n(t) 负责按需加载模块,并用 c 缓存已加载模块。
入口 n(114bfd) 直接执行了 console.log('模块 114bfd')
二:浏览器调试分析
以上关于 webpack 的解释 和文件的描述,接下来准备 开始分析 Scrape | Movie 流程浏览器调试,
1、打开浏览器开发者调试工具,请求目的网址,查看请求头加密数据,由截图可知,当前加密数据为 token,

2、借用浏览器搜索工具检索加密参数 token

3、根据调试工具定位到发送网络请求的地方有在处理 token 的加密,添加断点调试加密函数

根据上面对 webpack 调用的研究,当前网络请求在 d504 模块里面,就能得到,上层存在函数使用类似于 n(d504) 这样的方式调用,当前的网络模块去处理,具体的调用可以在 浏览器的函数堆栈里面查看 ,我们需要解决 token 的问题,根据代码分析,得到
this.$store.state.url.index = "/api/movie"
var a = (this.page - 1) * this.limit
var e = token = Object(i["a"])(this.$store.state.url.index, a);
4、根据token的生成,可以得出 i[“a”] 为 一个函数,其中i = e(“7d92”),e为一个模块加载器,得到7d92模块,模块里面有一个 a 函数,调用 a 函数 传入两个参数,最后得到密文

5、上一步 知道 i = e(“7d92”) ,我们可以在这一步添加断点,查看 e的调用,最后定位到,e的调用是 调用了,app.js 里面的,function c(t) {}

6、定位到 7d92 模块里面得到模块 7d92

"7d92": function(t, e, r) {
"use strict";
r("6b54");
var n = r("3452");
//i() 为 token 生成的函数
function i() {
for (var t = Math.round((new Date).getTime() / 1e3).toString(), e = arguments.length, r = new Array(e), i = 0; i < e; i++)
r[i] = arguments[i];
r.push(t);
var o = n.SHA1(r.join(",")).toString(n.enc.Hex)
, c = n.enc.Base64.stringify(n.enc.Utf8.parse([o, t].join(",")));
return c
}
e["a"] = i
},
三:扣 js 代码,还原浏览器运行步骤
根据以上 浏览器分析和webpack模块的调用可以推理得出,如果想要用,e= Object(i[“a”])(this.$store.state.url.index, a) 类似的方法调用 a 函数生成 token,则需要先调用 i 即 i = e(“7d92”)由于 webpack代码都是做了压缩打包相关的操作,显示e调用模块,最终调用的是 function c(t){} 可以得出 i = c(“7d92”) 调用到 app.js 里面去 即可,在调用的时候,只要吧缺少的模块都补充上去,最后就能够 原模原样的调用 i[“a”] 函数生成 token ,本篇文章不对 i = c(“7d92”) 环境做说明(….),本篇文章只会直接 模拟 7d92 模块里面的 ,i 函数做补环境操作。
1、新建app.js 加载器,并且扣app.js 相关的代码
window=global;
// 模块js
const af3e9bb54 = require('./chunk-4136500c.f3e9bb54.js');
// 模块js
const acb8b7 = require('./chunk-10192a00.243cb8b7.js');
// 模块js
//const a77daf991 = require('./chunk-vendors.77daf991.js');
// 补充浏览器需要的环境
navigator = {}
navigator.userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36"
//const { JSDOM } = require('jsdom');
//window.document = new JSDOM('');
const { JSDOM } = require('jsdom');
const dom = new JSDOM();
const document = dom.window.document;
window.document=document;
window.location = {};
var test="https://spa2.scrape.center/";
const event = document.createEvent('Event');
event.initEvent('custom', true, true);
event.timestamp = Date.now();
const element = document.createElement('div');
element.addEventListener('custom', (e) => {});
element.dispatchEvent(event);
const element1 = document.createElement('div');
element1.addEventListener("keydown",(e)=>{})
element1.dispatchEvent(event);
!(function(e) {
function t(t) {
for (var r, o, i = t[0], c = t[1], s = t[2], l = 0, f = []; l < i.length; l++) o = i[l],
Object.prototype.hasOwnProperty.call(a, o) && a[o] && f.push(a[o][0]),
a[o] = 0;
for (r in c) Object.prototype.hasOwnProperty.call(c, r) && (e[r] = c[r]);
d && d(t);
while (f.length) f.shift()();
return u.push.apply(u, s || []),
n()
}
function n() {
for (var e, t = 0; t < u.length; t++) {
for (var n = u[t], r = !0, o = 1; o < n.length; o++) {
var i = n[o];
0 !== a[i] && (r = !1)
}
r && (u.splice(t--, 1), e = c(c.s = n[0]))
}
return e
}
var r = {},
o = {
app: 0
},
a = {
app: 0
},
u = [];
function i(e) {
return c.p + "js/" + ({} [e] || e) + "." + {
"chunk-4136500c": "f3e9bb54",
"chunk-10192a00": "243cb8b7",
"chunk-7502f973": "428355cb"
} [e] + ".js"
}
function c(t) {
if (r[t]) return r[t].exports;
var n = r[t] = {
i: t,
l: !1,
exports: {}
};
// 可以做备注,以防模块加载失败的时候,可以看得到那个模块加载失败了,在去扣对应的模块来补充即可
console.log("当前加载的模块:"+t);
return e[t].call(n.exports, n, n.exports, c),
n.l = !0,
n.exports
}
// 引出内部变量,后续直接 用 window 对象调用内部变量
window.c=c;
})({});
// MD5 相关的加密
function getToken() {
r = {};
var n = window.c("3452");
for (var t = Math.round((new Date).getTime() / 1e3).toString(), e = arguments.length, r = new Array(e), i = 0; i < e; i++)
{
r[i] = arguments[i];
}
r.push(t);
var o = n.SHA1(r.join(",")).toString(n.enc.Hex)
, c = n.enc.Base64.stringify(n.enc.Utf8.parse([o, t].join(",")));
return c
}
//模拟调用
//var token =getToken("/api/movie",0);
//console.log(token)
四:执行相关代码,得到“果子”
limit = 10
offset = self.get_page_number(limit, page)
# 调用 js 里面的 getToken 方法,传入参数,最后生成token,直接通过token使用requests请求库发起请求,即可获得结果
app_e9fbf43f_js = execjs.compile(open('./app.e9fbf43f.js', encoding='utf-8').read())
token = app_e9fbf43f_js.call("getToken", '/api/movie', offset)
params = {
'limit': limit,
'offset': offset,
'token': token
}
response = requests.get('https://spa2.scrape.center/api/movie/', params=params, headers=self.headers)
if response.status_code == 200:
responseData = response.json()
pageCount = responseData["count"]
results = responseData["results"]
当前为第: 1 页, 总共: 10 页
[{'id': 1, 'name': '霸王别姬', 'alias': 'Farewell My Concubine', 'cover': 'https://p0.meituan.net/movie/ce4da3e03e655b5b88ed31b5cd7896cf62472.jpg@464w_644h_1e_1c', 'categories': ['剧情', '爱情'], 'published_at': '1993-07-26', 'minute': 171, 'score': 9.5, 'regions': ['中国内地', '中国香港']}, {'id': 2, 'name': '这个杀手不太冷', 'alias': 'Léon', 'cover': 'https://p1.meituan.net/movie/6bea9af4524dfbd0b668eaa7e187c3df767253.jpg@464w_644h_1e_1c', 'categories': ['剧情', '动作', '犯罪'], 'published_at': '1994-09-14', 'minute': 110, 'score': 9.5, 'regions': ['法国']}, {'id': 3, 'name': '肖申克的救赎', 'alias': 'The Shawshank Redemption', 'cover': 'https://p0.meituan.net/movie/283292171619cdfd5b240c8fd093f1eb255670.jpg@464w_644h_1e_1c', 'categories': ['剧情', '犯罪'], 'published_at': '1994-09-10', 'minute': 142, 'score': 9.5, 'regions': ['美国']}, {'id': 4, 'name': '泰坦尼克号', 'alias': 'Titanic', 'cover': 'https://p1.meituan.net/movie/b607fba7513e7f15eab170aac1e1400d878112.jpg@464w_644h_1e_1c', 'categories': ['剧情', '爱情', '灾难'], 'published_at': '1998-04-03', 'minute': 194, 'score': 9.5, 'regions': ['美国']}, {'id': 5, 'name': '罗马假日', 'alias': 'Roman Holiday', 'cover': 'https://p0.meituan.net/movie/289f98ceaa8a0ae737d3dc01cd05ab052213631.jpg@464w_644h_1e_1c', 'categories': ['剧情', '喜剧', '爱情'], 'published_at': '1953-08-20', 'minute': 118, 'score': 9.5, 'regions': ['美国']}, {'id': 6, 'name': '唐伯虎点秋香', 'alias': 'Flirting Scholar', 'cover': 'https://p0.meituan.net/movie/da64660f82b98cdc1b8a3804e69609e041108.jpg@464w_644h_1e_1c', 'categories': ['喜剧', '爱情', '古装'], 'published_at': '1993-07-01', 'minute': 102, 'score': 9.5, 'regions': ['中国香港']}, {'id': 7, 'name': '乱世佳人', 'alias': 'Gone with the Wind', 'cover': 'https://p0.meituan.net/movie/223c3e186db3ab4ea3bb14508c709400427933.jpg@464w_644h_1e_1c', 'categories': ['剧情', '爱情', '历史', '战争'], 'published_at': '1939-12-15', 'minute': 238, 'score': 9.5, 'regions': ['美国']}, {'id': 8, 'name': '喜剧之王', 'alias': 'The King of Comedy', 'cover': 'https://p0.meituan.net/movie/1f0d671f6a37f9d7b015e4682b8b113e174332.jpg@464w_644h_1e_1c', 'categories': ['剧情', '喜剧', '爱情'], 'published_at': '1999-02-13', 'minute': 85, 'score': 9.5, 'regions': ['中国香港']}, {'id': 9, 'name': '楚门的世界', 'alias': 'The Truman Show', 'cover': 'https://p0.meituan.net/movie/8959888ee0c399b0fe53a714bc8a5a17460048.jpg@464w_644h_1e_1c', 'categories': ['剧情', '科幻'], 'published_at': None, 'minute': 103, 'score': 9.0, 'regions': ['美国']}, {'id': 10, 'name': '狮子王', 'alias': 'The Lion King', 'cover': 'https://p0.meituan.net/movie/27b76fe6cf3903f3d74963f70786001e1438406.jpg@464w_644h_1e_1c', 'categories': ['动画', '歌舞', '冒险'], 'published_at': '1995-07-15', 'minute': 89, 'score': 9.0, 'regions': ['美国']}]
以上文章是本人对于js逆向的探讨,文章写得很不谨慎,关键在于自己实践的心得探讨,如果有同学那里不懂的,可以展开交流学习。
















暂无评论内容