JS 逆向:PEDATA 资讯接口加密解密与 zlib.gunzipSync 应用
1. 逆向目标
- 目标系统:某投资领域 SaaS 系统 PEDATA MAX 资讯模块
- 请求地址:
https://max.pedata.cn/api/q4x/newsflash/list
- 返回特征:响应数据中的
data 字段为经过多层加密的字符串,包含 exor 异或密钥和 ts 时间戳。
- 核心难点:数据经过 Base64、XOR 异或及 Gzip 压缩处理,需还原明文 JSON。
2. 抓包分析
在页面加载时,通过开发者工具 XHR 面板筛选出 newsflash/list 请求。观察请求参数与响应结构:
2.1 请求参数
Payload 主要包含翻页信息(如页码、每页数量),无明显加密特征。重点在于请求头:
HTTP-X-TOKEN:用于标识用户身份,替代 Cookie 进行鉴权。
Cookie:部分广告统计参数(如 Hm_lvt_xxx)可忽略。
2.2 响应结构
{
"data": "L+o+YmIyNDE...",
"exor": "01",
"ts": 1708632000
}
其中 data 为加密串,exor 为后续解密所需的异或密钥。
3. 加密逻辑逆向
3.1 定位解密函数
在浏览器调试器中搜索键名 exor,定位到唯一引用处。该处代码调用了对象方法 Object(p["y"])(e.data.data, e.data.exor)。跟进该方法发现其内部调用了名为 M 的函数。
3.2 关键函数解析
核心解密逻辑位于 M(t, n) 函数中:
function M(t, n) {
var a = L(Object(s["a"])(), n);
var r = Y(B(t), a);
var c = o.a.gunzipSync(e.from(r)).toString("utf-8");
return JSON.parse(c);
}
各步骤含义如下:
- 获取 Token:
Object(s["a"])() 获取登录凭证(即 HTTP-X-TOKEN)。
- 生成异或数组:
L(token, exor) 根据 token 和 exor 生成异或密钥数组。
- Base64 解码:
B(t) 将加密字符串转为普通字符串。
- XOR 异或:
Y(decoded_str, key_array) 执行异或运算得到二进制流。
- 解压数据:
gunzipSync 对二进制流进行 Gunzip 解压。
- 解析 JSON:最后将解压后的 UTF-8 字符串解析为 JSON 对象。
3.3 zlib.gunzipSync 说明
zlib.gunzipSync 是 Node.js zlib 模块提供的同步解压方法。它支持 Buffer、TypedArray 等类型输入。在浏览器环境中通常无法直接使用,但在爬虫脚本(Node.js/Python via execjs)中可直接调用。
4. 完整实现方案
4.1 JavaScript 核心算法
将逆向得到的 JS 函数提取为独立模块,便于在其他语言中复现。
var zlib = require('zlib');
function L(e, t) {
if ("1" == t) return [7, 65, 75, 31, 71, 101, 57, 0];
for (var n = [], a = 0, r = t.length; a < r; a += 2)
n.push(e.substr(1 * t.substr(a, 2), 1).charCodeAt());
return n;
}
function Y(e, t) {
for (var n, a = new Uint8Array(e.length), r = 0, c = e.length; r < c; r++)
n = t[r % t.length], a[r] = e[r].charCodeAt() ^ n;
return a;
}
function B(e) {
var t, n, a, r, c, u, i, o = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", s = "", f = 0;
e = e.replace(, );
(f < e.)
r = o.(e.(f++)), c = o.(e.(f++)),
u = o.(e.(f++)), i = o.(e.(f++)),
t = r << | c >> , n = ( & c) << | u >> ,
a = ( & u) << | i, s += .(t),
!= u && (s += .(n)), != i && (s += .(a));
s;
}
() {
a = (loginToken, exor);
r = ((encryptedData), a);
decryptedData = zlib.(r).();
decryptedData;
}
4.2 Python 调用示例
使用 execjs 调用上述 JS 逻辑,结合 requests 发起请求。
import execjs
import requests
import json
NEWS_URL = "https://max.pedata.cn/api/q4x/newsflash/list"
LOGIN_TOKEN = "YOUR_LOGIN_TOKEN_HERE"
headers = {
"Accept": "application/json, text/plain, */*",
"Content-Type": "application/json",
"Host": "max.pedata.cn",
"HTTP-X-TOKEN": LOGIN_TOKEN,
"Origin": "https://max.pedata.cn",
"Referer": "https://max.pedata.cn/client/news/newsflash",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
def load_js_context():
with open('pedata_decrypt.js', 'r', encoding='utf-8') as f:
return execjs.compile(f.read())
def decrypt_data(encrypted_data, exor, ctx):
return ctx.call('getDecryptedData', encrypted_data, exor, LOGIN_TOKEN)
def fetch_news():
payload = {
"type": "",
"module": "LP",
"page": {"currentPage": 1, "pageSize": 10}
}
resp = requests.post(NEWS_URL, headers=headers, json=payload)
resp.raise_for_status()
data = resp.json()
encrypted_data = data.get()
exor = data.get()
encrypted_data exor:
ValueError()
ctx = load_js_context()
decrypted_json = decrypt_data(encrypted_data, exor, ctx)
json.loads(decrypted_json)
__name__ == :
:
news_list = fetch_news()
(json.dumps(news_list, ensure_ascii=, indent=))
Exception e:
()
5. 注意事项
- Token 有效期:
HTTP-X-TOKEN 可能有时效性,若请求失败请重新获取。
- 环境依赖:Python 端需安装
requests 和 execjs 库。
- Buffer 兼容性:Node.js
zlib 处理 Uint8Array 时需确保版本 >= v8.0.0。
- 合规性:本方案仅供技术学习,请勿用于非法爬取或商业用途。