js逆向-js调用和扣代码
前言
路上见识世界,途中认清自己
⚠️声明:本文所涉及的爬虫技术及代码仅用于学习、交流与技术研究目的,禁止用于任何商业用途或违反相关法律法规的行为。若因不当使用造成法律责任,概与作者无关。请尊重目标网站的
robots.txt协议及相关服务条款,共同维护良好的网络环境。
1.PyExecJS
1.1简介
git地址:https://github.com/doloopwhile/PyExecJS
pyexecjs 是一个 Python 库,用于执行 JavaScript 代码。它是 PyExecJS 的一个封装,可以让 Python 调用 JavaScript 引擎,执行 JavaScript 脚本,并获取结果。它通过在后台使用不同的 JavaScript 引擎(如 Node.js、JScript、PhantomJS 等)来实现这一功能。
使用 PyExecJS,你可以在 Python 中执行 JavaScript 代码,而无需启动一个完整的 JavaScript 解释器。它的目标是提供一种简便的方式,使得在 Python 中嵌入 JavaScript 代码成为可能,特别是对于需要与 JavaScript 交互的项目而言。
1.2安装
安装命令
pip install PyExecJS

1.3使用
import execjs
# 生成环境
# 需要安装好node 才能使用 Node.js(v8) 引擎
node = execjs.get()
print(node)
js_code = ""
# 读取js文件
with open('2.demo.js', encoding='utf-8') as f:
js_code = f.read()
# 也可以使用代码方式
# js_code = """
# function add_data(a, b, c) {
# return a[0] + b['b'] + c
# }
# """
# 创建js环境
# 加载js代码到js环境内部
js = execjs.compile(js_code)
# 调用方法: call eval
res = js.call('add_data', ['123'], {'b': 456}, 789)
print(res)
res = js.eval("add_data(['123'], {'b': 234}, 789)")
print(res)

2.Express
2.1简介
Express 的中文官网:https://www.expressjs.com.cn/
Express 是一个广泛使用的、基于 Node.js 的 Web 应用框架,用于构建 Web 应用和 API。它简化了 HTTP 请求和响应的处理,并提供了丰富的中间件支持,能够快速开发服务器端的应用。
主要特性:
- 路由系统:提供强大的路由功能,支持各种 HTTP 方法(如 GET、POST、PUT、DELETE 等)并能根据路径和请求方式处理请求。
- 中间件支持:中间件是 Express 的核心,能够在请求和响应的生命周期中插入自定义逻辑,如验证、日志记录、文件处理等。
- 模板引擎支持:可以与各种模板引擎(如 EJS、Pug、Handlebars 等)一起使用,方便动态渲染页面。
- 易于扩展:通过第三方中间件或插件,Express 能够扩展更多功能,如身份验证、数据库集成等。
常用的中间件:
- body-parser:用于解析请求体,特别是 POST 请求。
- morgan:用于 HTTP 请求日志。
- cookie-parser:用于解析 Cookie。
- express-session:用于管理会话。
与传统的前端框架(如 React 或 Angular)不同,Express 更侧重于后端 API 和 Web 服务的开发。它提供了一个极其灵活和轻量级的框架,可以快速构建 Web 应用或 RESTful API。
2.2安装
安装 express 包并将其添加到 dependencies 部分。
-S 或 --save:在早期版本的 npm 中,使用这个选项会将包添加到 dependencies 中。但从 npm 5 开始,--save 已经是默认行为,所以可以省略。
npm install express -S

2.3使用
代码
// 导入 express 模块
var express = require('express');
// 创建 Express 应用实例
var app = express();
// 模拟加密
function encrypt(a) {
return a + 'abcdef';
}
// 根路径 get请求
app.get('/', function (req, res) {
res.send('这个是根路径');
});
// /user 路由 get请求
app.get('/user', function (req, res) {
// 模拟加密后返回
var result = encrypt(req.query.param); // 显式声明 result 变量
res.send(result);
});
app.use(express.json()); // 使用中间件来解析 JSON 格式的请求体
// /api/user/add 路由 post请求
app.post('/api/user/add', function (req, res) {
console.log(req.body); // 打印请求体
res.send('用户添加成功');
});
// 启动服务
app.listen(8080, function () {
console.log('服务已经启动,访问地址是:http://127.0.0.1:8080');
});
启动服务,并测试发送请求
http://127.0.0.1:8080/
http://127.0.0.1:8080/user

测试发送Post请求
import requests
res = res = requests.post('http://127.0.0.1:8080/api/user/add', json={'username': 'peng', 'pwd': '123456'})
print(res.text)

3.js调试技巧
3.1dom断点定位
根据元素的事件定位

3.2xhr断点定位
找到对应的请求,并在XHR/提取断点添加需要的定位的请求

3.3关键字搜索
地址:http://www.birdreport.cn/home/activity/page.html

然后断点一个个尝试,如果是加密的代码,会进入到代码

关键字定位:
找这个数据的加密位置:Requestid
Requestid = shjkdfghjkhsdf
Requestid : sdkhfjksd
setRequestHead('Requestid', 'asdkfhhjksd')
搜索关键字的好处: 离加密位置更加近 有运气成分
坏处: 搜索文件会特别的多 需要下断点来进行测试
可能会搜索不出来
1.代码做了混淆
2.关键字拼接 'an' + 'al' + 'ysis'
代码执行权重
i[jt] 实际上就是方法的引用
i[jt](i[qt](a, d))
3.4hook拦截
创建hook脚本,并在代码中判断需要定位的请求,记得ctrl + s保存后ctrl + enter启用

3.5启动器
根据启动器去找调用关系(分析启动器是比较麻烦的,运气成分比较多)

4.七麦数据定位加密
4.1逗号表达式
逗号表达式 (0, i[Jt]):逗号表达式的作用是执行 0(没有实际意义,可能是为了执行某些副作用),然后返回 i[Jt] 的值。
i[Jt] 和 i[Qt]:这些看起来是从数组或对象 i 中动态访问的值,可能是一些函数或属性。
(0, i[Qt])(a, d):逗号表达式 (0, i[Qt]) 先执行 0,然后返回 i[Qt],并调用它,传入参数 a 和 d。
e = (0, i[Jt])((0, i[Qt])(a, d))
# 等于 0没有实际意义 算是反爬
i[Jt](i[Qt](a, d))
代码
function abc(){
console.log(123)
}
function def(){
console.log(456)
}
(0,abc(),def())
// abc()

4.2js扣代码
xhr断点到发送请求前

单步调试,可能会先找到响应拦截器,因为请求拦截器和响应拦截器是一起的
然后搜索Wt,找到请求拦截器,在请求拦截器上打上断点

我们发现这行代码的时候t已经进行了加密
e = (0,
i[Jt])((0,
i[Qt])(a, d)),
-B == t[qt][O](v) && (t[qt] += (-B != t[qt][O](Bn) ? Nn : Bn) + v + B5 + R[V5](e)),
t

逐行分析代码,我这里分析的有问题
// a = a[jt]()[I5](N),
// a = (0,
// i[Jt])(a),
// a = (a += p + t[qt][T](t[Tt], N)) + (p + r) + (p + 3),
// e = (0,
// i[Jt])((0,
// i[Qt])(a, d)),
// -B == t[qt][O](v) && (t[qt] += (-B != t[qt][O](Bn) ? Nn : Bn) + v + B5 + R[V5](e)),
// t
function o(e, t) {
return function () {
for (var n = new Array(arguments.length), i = 0; i < n.length; i++)
n[i] = arguments[i];
return e.apply(t, n)
}
}
function p(t) {
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
return o("0x" + t)
});
try {
return btoa(t)
} catch (n) {
return Buffer.from(t).toString("base64")
}
}
function h(n, t) {
t = t || u();
for (var e = (n = n.split("")).length, r = t.length, a = "charCodeAt", i = 0; i < e; i++)
n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
return n.join("")
}
// s = c[x][k][Rt] = -(0, i[Zt])(l) || +new R[K] - r2 * n);
// var e, r = +new R[K] - (s || H) - 1661224081041, a = [];
var e, r = new Date() - (1226 || 0) - 1661224081041, a = []
// a = a[jt]()[I5](N)
a = a.sort().join(""),
// a = (0, i[Jt])(a),
a = p(a),
// a = (a += p + t[qt][T](t[Tt], N)) + (p + r) + (p + 3)
a = a += "@#" + "/indexV2/getIndexRank".replace("https://api.qimai.cn", "") + ("@#" + 79511618594) + ("@#" + 3),
// e = (0, i[Jt])((0, i[Qt])(a, d)),
// e = i[Jt](i[Qt](a,d)),
d = 'xyz517cda96efgh'
e = p(h(a, d))
console.log(e)
// -B == t[qt][O](v) && (t[qt] += (-B != t[qt][O](Bn) ? Nn : Bn) + v + B5 + R[V5](e)),
5.七麦数据网站定位数据加密
5.1调试过程
首先我们根据榜单的接口定位发送请求前,因为我们要找数据加密的过程,肯定在发送请求前进行的数据加密

找到请求拦截器,在该方法打上断点,进行单步调试

我们发现a其实是请求的参数

经过逐步调试,我们发现a在以下代码开始加密(自己单步调试的过程需要查看变量是否变化)
a = a[jt]()[I5](N),
a = (0,
i[Jt])(a),
a = (a += p + t[qt][T](t[Tt], N)) + (p + r) + (p + 3),
e = (0,
i[Jt])((0,
i[Qt])(a, d)),

首先查看a = a[jt]()[I5](N),,然后根据调试把他修改为正常代码
a = a[jt]()[I5](N),
a = a.sort().join(''),

然后再查看 a = (0,i[Jt])(a),,发现这个方法调用的是自己定义的方法,选中后跳到实现
a = (0,i[Jt])(a),

没有好的办法,只有逐字查看了
function p(t) {
t = R[V5](t)[T](/%([0-9A-F]{2})/g, function(n, t) {
return o(Y5 + t)
});
try {
return R[Q5](t)
} catch (n) {
return R[W5][K5](t)[U5](Z5)
}
}
把该方法修改为
function p(t) {
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
// return o(Y5 + t)
return o("0x" + t)
});
return btoa(t)
try {
return btoa(t)
} catch (n) {
return Buffer.from(t).toString('base64')
}
}

我们发现o也是自定义函数,所以接着定位
我们需要在 return o(Y5 + t)打上断点,定位到实现

跳转到o方法的实现
ctrl + shift + f打开控制台,把[c2, f2, s2, d2, m2, l2, p2, f2, m2, s2, v2, h2]放入控制台查看数据,方便编写
然后查看剩下需要改写的js函数
// 原方法
function o(n) {
t = N,
[c2, f2, s2, d2, m2, l2, p2, f2, m2, s2, v2, h2][M](function(n) {
t += R[y2](g2 + n)
});
var t, e = t;
return R[w2][e](n)
}
// 修改后
function o(n) {
t = "",
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65']['forEach'](function (n) {
t += unescape('%u00' + n)
});
var t, e = t;
return String[e](n)
}

修改完成上面方法后,可以跳过当前方法了
接着调试a = (a += p + t[qt][T](t[Tt], N)) + (p + r) + (p + 3),
这个方法大部分都是固定的,但是r好像有点问题

往下查找r的声明和赋值,然后对应调试尽心修改
// 原方法
r = +new R[K] - (s || H) - 1661224081041,
// 修改后
r = new Date() - (1350 || 0) - 1661224081041,

接着调试e = (0,i[Jt])((0,i[Qt])(a, d)),
i[Jt]我们已经实现了,所以我们还需要实现,跳转到该方法的视线
e = (0,i[Jt])((0,i[Qt])(a, d)),

h方法稍微简单点,都是js方法,没有自定义实现,我们修改一下该方法
// 原方法
function h(n, t) {
t = t || u();
for (var e = (n = n[$5](N))[z], r = t[z], a = q5, i = H; i < e; i++)
n[i] = o(n[i][a](H) ^ t[(i + 10) % r][a](H));
return n[I5](N)
}
// 修改后
function h(n, t) {
t = t || u();
for (var e = (n = n['split'](''))['length'], r = t['length'], a = 'charCodeAt', i = 0; i < e; i++)
n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
return n['join']('')
}

5.2代码
function p(t) {
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
// return o(Y5 + t)
return o("0x" + t)
});
return btoa(t)
try {
return btoa(t)
} catch (n) {
return Buffer.from(t).toString('base64')
}
}
function h(n, t) {
t = t || u();
for (var e = (n = n['split'](''))['length'], r = t['length'], a = 'charCodeAt', i = 0; i < e; i++)
n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
return n['join']('')
}
function o(n) {
t = "",
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65']['forEach'](function (n) {
t += unescape('%u00' + n)
});
var t, e = t;
return String[e](n)
}
// r = +new R[K] - (s || H) - 1661224081041,
// r = new Date() - (1350 || 0) - 1661224081041,
r = 79531803050
// a = a[jt]()[I5](N),
a = [0, '5000']
a = a.sort().join(''),
// a = (0, i[Jt])(a),
a = p(a),
// a = (a += p + t[qt][T](t[Tt], N)) + (p + r) + (p + 3),
a = (a += "@#" + "/indexV2/getIndexRank".replace("https://api.qimai.cn", "")) + ("@#" + r) + ("@#" + 3),
// e = (0, i[Jt])((0,i[Qt])(a, d)),
e = p(h(a, "xyz517cda96efgh"))
console.log(e)
5.3测试
测试的我们需要修改a和r参数

点击总榜,在 a = a[jt]()[I5](N),打上断点,控制台查看a和r的参数
a
(2) [0, '36']
r
79534181823

然后再最后一行t断点,控制台查看t.url
t.url
'/indexV2/getIndexRank?analysis=eyErVShbVhNbVVIbMlMWUQASLgYcHAJnUFkIJEIOD1BVU1lASEIHAndAVw%3D%3D'

然后我们在代码里固定a和r的参数,运行代码,查看是否和浏览器输出的一致
一致就没什么问题了

5.4请求网站
修改1.七麦数据.js,需要添加一个方法调用我们自己解析的代码
get_encrypt_data方法接收一个数组参数,例如[0, '36']
function p(t) {
t = encodeURIComponent(t).replace(/%([0-9A-F]{2})/g, function (n, t) {
// return o(Y5 + t)
return o("0x" + t)
});
return btoa(t)
try {
return btoa(t)
} catch (n) {
return Buffer.from(t).toString('base64')
}
}
function h(n, t) {
t = t || u();
for (var e = (n = n['split'](''))['length'], r = t['length'], a = 'charCodeAt', i = 0; i < e; i++)
n[i] = o(n[i][a](0) ^ t[(i + 10) % r][a](0));
return n['join']('')
}
function o(n) {
t = "",
['66', '72', '6f', '6d', '43', '68', '61', '72', '43', '6f', '64', '65']['forEach'](function (n) {
t += unescape('%u00' + n)
});
var t, e = t;
return String[e](n)
}
function get_encrypt_data(a) {
a = a.sort().join(''),
a = p(a),
r = new Date() - (1350 || 0) - 1661224081041,
// r = 79534181823,
a = (a += "@#" + "/indexV2/getIndexRank".replace("https://api.qimai.cn", "")) + ("@#" + r) + ("@#" + 3)
d = 'xyz517cda96efgh'
return p(h(a, d))
}
使用python脚本调用
import requests
import execjs
url = 'https://api.qimai.cn/indexV2/getIndexRank'
headers = {
'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'
}
params = {
# 'analysis': 'eyEzECU8OEd1EhgKCgVcTjNUSA8dDTNbVVIbNgBXXSVFUFFNSk4EBgJVVlJ5FVY',
'setting': 0,
'genre': 5000
}
# 获取对象的值转化为list
res = list(params.values())
# 读取js代码
f = open('1.七麦数据.js', encoding='utf-8')
js_code = f.read()
# 将js代码放进js环境中
js = execjs.compile(js_code)
# 调用js代码中的get_encrypt_data方法
analysis = js.call('get_encrypt_data', res)
# 请求参数添加params
params['analysis'] = analysis
# 发送请求
r = requests.get(url, headers=headers, params=params)
print(r.json())
# 测试数据
# a = [0, '36']
# r = 79534181823,
# eyErVShbVhNbVVIbMlMWUQASLgYcHAJnUFkIJEIOD1BVU1lASEIHAndAVw==
# /indexV2/getIndexRank?analysis=eyErVShbVhNbVVIbMlMWUQASLgYcHAJnUFkIJEIOD1BVU1lASEIHAndAVw%3D%3D
# print(analysis)

📌 创作不易,感谢支持!
每一篇内容都凝聚了心血与热情,如果我的内容对您有帮助,欢迎请我喝杯咖啡☕,您的支持是我持续分享的最大动力!

浙公网安备 33010602011771号