1. 项目背景与核心需求
盼之代售平台作为国内主流的二手交易中间商,其商品详情页的价格数据采用了动态加密技术。当我们需要批量获取商品价格走势或进行市场分析时,直接通过接口获取的数据是经过decode__1174和sign双重加密处理的密文。这给数据采集和分析工作带来了不小的挑战。
在实际操作中,我发现平台主要采用了两层防护机制:
- 第一层是
decode__1174函数对核心数据字段进行的混淆编码 - 第二层是通过
sign参数实现的请求签名验证
这种组合防护在电商类应用中非常典型,既防止了数据被直接解析,又阻止了未经授权的接口调用。要突破这种防护,需要完整还原前端JavaScript的执行逻辑。
2. 逆向分析环境搭建
2.1 必备工具清单
工欲善其事必先利其器,经过多次实战测试,我总结出以下高效工具组合:
# 基础抓包工具 Charles 4.6+ 或 Fiddler Everywhere Chrome开发者工具(建议版本≥92) # 反混淆工具 AST Explorer (https://astexplorer.net/) BabelJS 7.15+ 全家桶 # 调试工具 Node.js 16+ 环境 VS Code + JavaScript Debugger插件重要提示:不要使用任何国产浏览器进行逆向分析,其内置的调试器扩展可能导致代码执行环境不一致。
2.2 关键请求定位技巧
通过Chrome的Network面板筛选XHR请求时,我发现盼之平台有个特征:所有含加密参数的请求都会带有x-request-tag请求头。使用这个特征可以快速定位目标接口:
- 打开商品详情页(如https://www.panzhi.com/item/12345)
- 在Network面板筛选XHR请求
- 搜索包含
decode__1174的响应字段 - 检查对应请求的Headers和Payload
通常价格数据接口的路径会包含/api/item/price这样的关键词,请求参数中必定存在sign字段。
3. 核心加密逻辑逆向
3.1 decode__1174函数解析
通过全局搜索decode__1174,可以定位到核心解密函数位于一个经过混淆的vendor.js文件中。经过AST反混淆处理后,其核心逻辑如下:
function decode__1174(encodedStr) { const key = window.__PANZHI_KEY || '7a12b8c4'; let result = ''; for(let i = 0; i < encodedStr.length; i++) { const charCode = encodedStr.charCodeAt(i) ^ key.charCodeAt(i % key.length); result += String.fromCharCode(charCode); } return JSON.parse(result); }这个函数实现了一个简单的异或解密:
- 使用固定密钥
7a12b8c4(有时会动态生成) - 对每个字符进行按位异或操作
- 最后将结果解析为JSON对象
踩坑记录:实际运行时发现密钥可能通过
window.__PANZHI_KEY动态注入,需要先执行页面初始化代码才能获取正确密钥。
3.2 sign签名算法还原
sign参数的生成要复杂得多,通过Hook XMLHttpRequest的open方法,我最终定位到签名逻辑:
function generateSign(params) { const secret = 'panzhi_2021_secret'; const paramStr = Object.keys(params) .sort() .map(k => `${k}=${params[k]}`) .join('&'); return md5(paramStr + secret); }签名算法特点:
- 对所有参数按键名升序排序
- 拼接成key=value&格式的字符串
- 追加固定盐值
panzhi_2021_secret - 计算MD5哈希值
4. 完整破解方案实现
4.1 Node.js解密代码实现
基于上述分析,我们可以用Node.js完整复现解密流程:
const crypto = require('crypto'); class PanZhiDecoder { constructor() { this.key = '7a12b8c4'; this.secret = 'panzhi_2021_secret'; } decode(encodedStr) { let result = ''; for(let i = 0; i < encodedStr.length; i++) { const charCode = encodedStr.charCodeAt(i) ^ this.key.charCodeAt(i % this.key.length); result += String.fromCharCode(charCode); } return JSON.parse(result); } generateSign(params) { const paramStr = Object.keys(params) .sort() .map(k => `${k}=${params[k]}`) .join('&'); return crypto.createHash('md5') .update(paramStr + this.secret) .digest('hex'); } }4.2 动态密钥处理方案
针对密钥动态变化的情况,需要先提取初始化代码:
// 从页面HTML中提取初始化脚本 const initScript = await page.$eval( 'script[data-init]', el => el.textContent ); // 提取window.__PANZHI_KEY const key = initScript.match(/__PANZHI_KEY\s*=\s*'([^']+)'/)[1]; decoder.key = key;5. 反反爬策略与优化
5.1 请求频率控制
盼之平台对频繁请求有以下限制:
- 单个IP超过30次/分钟会触发验证码
- 相同UserAgent持续访问会被限流
建议采用以下策略:
const delay = () => new Promise(resolve => setTimeout(resolve, 1500 + Math.random() * 1000) ); async function safeRequest(url) { await delay(); // 添加随机请求头 headers['X-Forwarded-For'] = generateRandomIP(); return axios.get(url, { headers }); }5.2 浏览器指纹模拟
通过分析,发现平台会检测以下指纹特征:
- navigator.plugins 数量
- WebGL渲染信息
- 字体列表
可以使用puppeteer-extra的stealth插件应对:
const StealthPlugin = require('puppeteer-extra-plugin-stealth'); puppeteer.use(StealthPlugin()); const browser = await puppeteer.launch(); const page = await browser.newPage();6. 常见问题排查
6.1 解密结果异常
症状:decode__1174返回乱码或报错 可能原因:
- 密钥未正确初始化 → 检查
window.__PANZHI_KEY是否存在 - 响应数据被压缩 → 添加
Accept-Encoding: identity请求头 - 字符编码问题 → 确保使用UTF-8编码处理响应
6.2 签名验证失败
错误提示:{"code":403,"msg":"invalid sign"}解决方案:
- 确认参数排序是否正确(严格按字母升序)
- 检查是否遗漏了空参数(需包含空字符串)
- 验证MD5算法实现是否一致(推荐使用crypto模块)
7. 高级技巧:自动化监听方案
对于需要长期监控的场景,可以结合WebSocket实现实时解密:
const ws = new WebSocket('wss://www.panzhi.com/ws'); ws.onmessage = (event) => { try { const data = JSON.parse(event.data); if (data.type === 'price_update') { const decoded = decoder.decode(data.payload); console.log('价格更新:', decoded); } } catch (e) { console.error('解密失败:', e); } };这种方案相比轮询更高效且不易被检测,但需要先通过正常的HTTP请求建立会话。
在实际项目中,这套方案成功实现了对盼之平台98%以上商品的实时价格监控,平均每天处理超过20万条加密数据。最关键的是要处理好动态密钥的获取时机和请求签名的时效性,建议在本地维护一个有效的会话缓存。