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

image-20250228164912074

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)

image-20250228165937318

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

image-20250228170602219

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

image-20250228171830177

测试发送Post请求

import requests
res = res = requests.post('http://127.0.0.1:8080/api/user/add', json={'username': 'peng', 'pwd': '123456'})
print(res.text)

image-20250228171540851

3.js调试技巧

3.1dom断点定位

根据元素的事件定位

image-20250228172736488

3.2xhr断点定位

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

image-20250228173056882

3.3关键字搜索

地址:http://www.birdreport.cn/home/activity/page.html

image-20250228180708700

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

image-20250228180958374


关键字定位:
    找这个数据的加密位置: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启用

image-20250228173238942

3.5启动器

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

image-20250228173446939

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],并调用它,传入参数 ad

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()

image-20250228180006735

4.2js扣代码

地址:https://www.qimai.cn/

xhr断点到发送请求前

image-20250228173616222

单步调试,可能会先找到响应拦截器,因为请求拦截器和响应拦截器是一起的

然后搜索Wt,找到请求拦截器,在请求拦截器上打上断点

image-20250228173931128

我们发现这行代码的时候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

image-20250228174230954

逐行分析代码,我这里分析的有问题

//                     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.七麦数据网站定位数据加密

地址:https://www.qimai.cn/

5.1调试过程

首先我们根据榜单的接口定位发送请求前,因为我们要找数据加密的过程,肯定在发送请求前进行的数据加密

image-20250228225750478

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

image-20250228230050004

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

image-20250228230538942

经过逐步调试,我们发现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)),

image-20250228230242828

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

a = a[jt]()[I5](N),

a = a.sort().join(''),

image-20250228230727126

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

 a = (0,i[Jt])(a),

image-20250228231001937

没有好的办法,只有逐字查看了

 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')
    }
}

image-20250228231509174

我们发现o也是自定义函数,所以接着定位

我们需要在 return o(Y5 + t)打上断点,定位到实现

image-20250228232242715

跳转到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)
}

image-20250228232726560

修改完成上面方法后,可以跳过当前方法了

接着调试a = (a += p + t[qt][T](t[Tt], N)) + (p + r) + (p + 3),

这个方法大部分都是固定的,但是r好像有点问题

image-20250228233514031

往下查找r的声明和赋值,然后对应调试尽心修改

 // 原方法
 r = +new R[K] - (s || H) - 1661224081041,
 // 修改后
 r = new Date() - (1350 || 0) - 1661224081041,

image-20250228234017082

接着调试e = (0,i[Jt])((0,i[Qt])(a, d)),

i[Jt]我们已经实现了,所以我们还需要实现,跳转到该方法的视线

e = (0,i[Jt])((0,i[Qt])(a, d)),

image-20250228234616094 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']('')
}

image-20250228235229392

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测试

测试的我们需要修改ar参数

image-20250228235605972

点击总榜,在 a = a[jt]()[I5](N),打上断点,控制台查看ar的参数

a
(2) [0, '36']
r
79534181823

image-20250228235941054

然后再最后一行t断点,控制台查看t.url

t.url
'/indexV2/getIndexRank?analysis=eyErVShbVhNbVVIbMlMWUQASLgYcHAJnUFkIJEIOD1BVU1lASEIHAndAVw%3D%3D'

image-20250301000101933

然后我们在代码里固定ar的参数,运行代码,查看是否和浏览器输出的一致

一致就没什么问题了

image-20250301000307719

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)

image-20250301002328666

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

wxzf
posted @ 2025-04-09 16:06  peng_boke  阅读(194)  评论(0)    收藏  举报