文章目录
- [1. 写在前面](#1. 写在前面)
- [2. 参数分析](#2. 参数分析)
- [3. 纯算还原](#3. 纯算还原)
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
profileData参数的目的则是为了获取gid,早期通过三四十组少量的数据集经过DES加密计算得出,目前数据集增至到了七八十组。从风控的角度来看这个参数在部分场景中影响可大也可小,毕竟它在加密之前的明文数据包含了设备指纹跟环境信息
风控系统的检测并不会对里面的几十个字段去逐一校验,但是从反作弊的场景来分析,则会基于风险权重来划分字段等级,如下:
【高权重字段】: 直接决定是否判定环境或设备异常(必会检测)
【中权重字段】: 用于交叉验证(辅助检测,单一字段异常不触发风控,但多个异常叠加会命中)
【低权重|冗余字段】: 仅作为补充信息(基本不检测,固定或轻微异常不会影响)

2. 参数分析
如上可以看到目前最新的到达了82组,密文的长度也是达到了7000+,这里全局搜索就可以定位跟以前还是一样在VMP内完成加密,如下所示:

可以看到它加密之前的JSON明文是一个Base64,直接解码可以看到,如下所示:


| 核心字段 | 字段描述 |
|---|---|
| x2(webdriver)、x44(时间戳) | 区分 "自动化工具" 和 "真实用户" |
| x22(随机 MD5)、x53(随机 MD5) | 验证设备 / 请求的 "随机性" |
| x7(显卡信息)、x9(分辨率) | 设备硬件核心特征,需符合真实用户分布 |
| x57(Cookies)、x1(UA) | 与请求头信息交叉验证,必须一致 |
如上表格中为加密对象权重较高的一些字段信息,当然还有一些涉及CPU核数|设备内存的字段,长期固定的话肯定也是有一定风险的,可以适当的收集来增加随机真实性
需要注意的是动态生成的字段,如时间戳、随机哈希一般权重高,固定的话相当于自曝异常
然而与真实环境强相关的字段,如显卡、分辨率、CPU需符合用户的分布规律,固定在合理区间可以短期规避
整个风控系统是不会去孤立看待某一个字段的,而是通过规则引擎 + 机器学习模型从多个核维度交叉验证(固定某些值之所以有风险,本质是违背了这些检测逻辑)
最终profileData参数加密算法的核心目标则是:
设备|浏览器信息 → 指纹字典 → 序列化 → Base64编码 → DES加密 → 十六进制编码 → profileData
整个环节拿到明文的大JSON后只需要找到DES的密钥差不多就可以了,密钥的话可以通过日志自吐出来,前缀如下所示:

3. 纯算还原
javascript
const crypto = require('crypto');
/**
* @param {Object} fp - 待加密的字典/对象数据
*/
function encrypt_profileData(fp) {
const fpJsonify = JSON.stringify(fp);
const fpBuffer = Buffer.from(fpJsonify, 'utf8');
const fpBase64Str = fpBuffer.toString('base64');
const fpBase64 = Buffer.from(fpBase64Str, 'utf8');
const blockSize = 8;
const padLen = blockSize - (fpBase64.length % blockSize);
const fpPadded = Buffer.concat([
fpBase64,
Buffer.alloc(padLen, 0x00)
]);
const key = Buffer.from('zbp3....', 'utf8');
const cipher = crypto.createCipheriv('des-ecb', key, null);
cipher.setAutoPadding(false);
let ciphertext = cipher.update(fpPadded);
ciphertext = Buffer.concat([ciphertext, cipher.final()]);
return ciphertext.toString('hex');
}
