1. 项目背景与核心挑战
盼之代售平台作为国内知名的二手交易服务商,其Web端采用了典型的JavaScript混淆+动态加密方案来保护核心接口。在分析其商品列表API时,发现请求参数中存在两个关键字段:decode__1174和sign,这正是本次逆向工程需要攻破的核心目标。
通过Chrome开发者工具的Network面板抓包观察,所有商品查询请求都包含以下特征:
- 请求头带有动态生成的
x-sign字段 - 查询参数中包含经过编码的
decode__1174字符串 - 每次请求这两个值都会变化,但变化规律不明显
2. 逆向分析环境搭建
2.1 工具链准备
工欲善其事必先利其器,以下是经过实战验证的工具组合:
- Chrome DevTools:内置的调试器和性能分析工具
- Fiddler Everywhere:抓包和请求重放(比Charles更轻量)
- AST Explorer:用于解析混淆后的JS语法树
- WebStorm:带智能提示的JS调试环境
- Node.js 18+:本地执行解密函数
重要提示:务必使用无痕模式访问目标网站,避免缓存干扰。建议每次分析前清除:
- Application → Storage → Clear site data
- Network → Disable cache勾选
2.2 关键请求定位技巧
在数百个JS文件中快速定位目标函数有三个诀窍:
- 在Network面板搜索
sign=或decode__1174=找到初始请求 - 对包含关键词的JS文件使用Pretty Print美化({}按钮)
- 在Sources面板对疑似文件使用
Ctrl+Shift+F全局搜索
3. 核心加密逻辑拆解
3.1 sign生成算法逆向
通过XHR断点追踪,发现sign值由以下流程生成:
function generateSign(params) { // 步骤1:参数标准化处理 const sortedParams = Object.keys(params) .sort() .map(k => `${k}=${params[k]}`) .join('&'); // 步骤2:混合时间戳盐值 const timestamp = Math.floor(Date.now() / 1000); const salt = window.__salt__ || '7s#d9f!k'; // 步骤3:HMAC-SHA256加密 const hash = CryptoJS.HmacSHA256( `${sortedParams}_${timestamp}`, salt ).toString(); return hash.slice(0, 16) + timestamp.toString(16); }关键发现:
- 盐值
__salt__在页面加载时通过另一个接口动态获取 - 最终sign值前16位是哈希值,后8位是时间戳的16进制表示
- 服务端会校验时间戳有效性(±5分钟)
3.2 decode__1174编码逆向
这个参数实际上是AES加密后的JSON字符串,逆向过程更复杂:
- 密钥定位:在
vendor.1a2b3c4d.js中找到如下代码片段:
const _0x123abc = ['encrypt', 'decodeURI', '...']; function getKey() { return localStorage.getItem('__aes_key__') || (_0x123abc[5] + 'p8*^d' + _0x123abc[2]); }加密模式分析:使用CryptoJS的AES-128-CBC模式,需要提取:
- 密钥:拼接后的字符串经MD5处理取前16位
- IV向量:固定值
panzhi_iv_2023
完整解密函数:
function decrypt1174(encoded) { const key = CryptoJS.MD5(getKey()).toString().slice(0, 16); const iv = 'panzhi_iv_2023'; return CryptoJS.AES.decrypt( { ciphertext: CryptoJS.enc.Base64.parse(encoded) }, CryptoJS.enc.Utf8.parse(key), { iv: CryptoJS.enc.Utf8.parse(iv) } ).toString(CryptoJS.enc.Utf8); }4. 补环境实战技巧
4.1 浏览器环境检测绕过
目标网站使用了多种反爬手段:
// 检测开发者工具 if(window.Firebug && window.Firebug.chrome && window.Firebug.chrome.isInitialized) { location.reload(); } // 检测调试模式 const debugger = new Function('debugger'); setInterval(() => { if(console.log.toString().includes('native')) debugger(); }, 5000);解决方案:
- 使用
Object.defineProperty重写相关检测属性 - 通过Proxy代理console方法
- 推荐使用
puppeteer-extra-plugin-stealth插件
4.2 本地Node.js复现方案
在Node环境实现加密需要特别注意:
- 补全浏览器特有对象:
global.window = { localStorage: { getItem: () => null }, __salt__: 'default_salt' };- 处理环境差异:
// 浏览器端CryptoJS会自动加载,Node端需要显式引入 const CryptoJS = require('crypto-js'); require('crypto-js/aes'); require('crypto-js/hmac-sha256');5. 完整请求模拟流程
5.1 参数生成步骤
- 准备原始参数:
const rawParams = { page: 1, size: 20, category: 'digital', sort: 'price_asc' };- 生成sign:
const sign = generateSign(rawParams);- 加密请求体:
const encodedBody = CryptoJS.AES.encrypt( JSON.stringify(rawParams), key, { iv } ).toString();- 最终请求示例:
curl 'https://api.panzhi.com/list' \ -H 'x-sign: 3a7d8f1b2c4e590a' \ --data-raw 'decode__1174=U2FsdGVkX1+3v4JYq7Hj...'5.2 频率控制策略
为避免触发风控,需要:
- 随机延迟:
Math.random() * 3000 + 1000 - 模拟鼠标移动轨迹
- 轮换User-Agent池
- 使用住宅代理IP(注意合规性)
6. 常见问题排查指南
6.1 签名无效(403)
可能原因:
- 时间戳超出服务端允许范围
- 参数排序规则不一致
- 盐值未正确更新
解决方案:
- 同步服务器时间(NTP协议)
- 检查参数是否经过URL编码
- 重新获取
salt值
6.2 解密失败
典型错误:
Error: Malformed UTF-8 data处理步骤:
- 确认密钥生成逻辑一致
- 检查IV向量是否匹配
- 验证Base64解码是否正确
6.3 请求被阻断
现象:返回418 I'm a teapot应对方案:
- 降低请求频率
- 添加合理的请求头:
headers: { 'Accept-Language': 'zh-CN,zh;q=0.9', 'Referer': 'https://www.panzhi.com/', 'X-Requested-With': 'XMLHttpRequest' }7. 进阶优化方向
7.1 自动化脚本设计
推荐架构:
├── core/ │ ├── encrypt.js # 加密模块 │ └── decrypt.js # 解密模块 ├── utils/ │ ├── proxy.js # 代理管理 │ └── anti.js # 反检测措施 └── main.js # 主流程控制7.2 性能优化技巧
- 预计算高频参数
- 使用WebWorker处理加密
- 缓存已解析的JS环境
7.3 风控对抗策略
最新监测手段包括:
- WebGL指纹检测
- AudioContext指纹验证
- 请求时序分析
应对方案:
- 使用
headless-gl模拟WebGL - 重写
AudioBuffer.prototype.getChannelData - 添加随机网络抖动