文章目录
- 1. 写在前面
- 2. 接口分析
- 3. 算法实现
【🏠作者主页】:吴秋霖
【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与开发工作!
【🌟作者推荐】:对爬虫领域以及JS逆向分析感兴趣的朋友可以关注《爬虫JS逆向实战》《深耕爬虫领域》
未来作者会持续更新所用到、学到、看到的技术知识!包括但不限于:各类验证码突防、爬虫APP与JS逆向分析、RPA自动化、分布式爬虫、Python领域等相关文章
作者声明:文章仅供学习交流与参考!严禁用于任何商业与非法用途!否则由此产生的一切后果均与作者无关!如有侵权,请联系作者本人进行删除!
1. 写在前面
x-api-eid-token这个参数它是一个风控值,部分接口是需要用到的,在某些场景下固定虽可用。但是在一定的次数后就会失效出现风控!所以动态去生成这个参数携带请求在一些场景下是必要的,token的值通过其他的接口请求服务端生成给的
2. 接口分析
jsTk.do的这个接口就是生成x-api-eid-token的关键接口。但是请求的时候同样需要两个加密参数,如下所示:
其中a参数如何实现的?它通过一个自定义的b64编码做字符映射,通过charAt方法根据计算的索引值选择对应的字符附加到结果上!
最后,算法将生成一个自定义的Base64编码字符串。并且在末尾,这里有点小细节(参数添加一个斜杠),可以自己看a参数
a参数的加密入参是一个JSON串,主要就是哈希了一个指纹、版本号、接口路径、还有一些固参
然后再来说说d参数,这个参数会稍微复杂一些!在介绍这个参数之前,这里提一个其他的例子,可能接触过的就会熟悉了!某平台的gid参数,这个应该大部分人做过的人都了解吧!它就是设备指纹信息相关的一堆参数加密生成的,那么这里的d其实也是一样的
我们要实现的主要算法,就是包括生成用户设备和浏览器环境的伪指纹,然后通过自定义的b64编码方法对这些信息进行编码
这里的设备及浏览器信息包括的东西会比较多,比如某型号、渲染器信息、Canvas/WebGL指纹、插件信息、屏幕信息、时间信息…等等
总结:两个参数加密入参对象不一样,一个复杂一个简单!密文算法最终的实现都一样
3. 算法实现
接下来,说一下两个参数的算法实现与源码讲解!首先是a参数的算法实现代码,如下所示:
function customBase64Encode(input) {
input = JSON.stringify(input);
input = encodeURIComponent(input);
const base64Chars = "23IL<N01c7KvwZO56RSTAfghiFyzWJqVabGH4PQdopUrsCuX*xeBjkltDEmn89.-";
let encoded = "";
let i = 0;
while (i < input.length) {
const char1 = input.charCodeAt(i++);
const char2 = input.charCodeAt(i++);
const char3 = input.charCodeAt(i++);
const enc1 = char1 >> 2;
const enc2 = ((char1 & 3) << 4) | (char2 >> 4);
const enc3 = ((char2 & 15) << 2) | (char3 >> 6);
const enc4 = char3 & 63;
if (isNaN(char2)) {
enc3 = enc4 = 64;
} else if (isNaN(char3)) {
enc4 = 64;
}
encoded += base64Chars.charAt(enc1) + base64Chars.charAt(enc2) + base64Chars.charAt(enc3) + base64Chars.charAt(enc4);
}
return encoded + "/";
}
function generateEncryptedKey() {
const keyPair = KEYUTIL.generateKeypair("EC", "secp256r1");
const privateKeyObj = keyPair.prvKeyObj;
const publicKeyObj = keyPair.pubKeyObj;
const publicKeyPEM = KEYUTIL.getPEM(publicKeyObj);
const privateKeyPEM = KEYUTIL.getPEM(privateKeyObj, "PKCS8PRV");
return [publicKeyPEM, privateKeyPEM];
}
const keyPair = generateEncryptedKey();
console.log(keyPair);
console.log(customBase64Encode(keyPair[1]));
上面a参数的算法函数实现的主要功能如下:
对象序列化为JSON串,进行URI编码
用自定义的b64编码表将编码后的字符串转换为自定义的Base64字符串
最终返回这个自定义编码字符串,并在末尾附加一个斜杠
接下来说一说d参数的算法实现,这里作者实现了一个随机生成CSS的属性伪造环境算法,目的就是增加设备指纹的一个复杂性,实现算法如下:
function generateCSSAttributes() {
const predefinedColors = [
"rgb(240, 240, 240)", "rgb(109, 109, 109)", "rgb(192, 192, 192)",
"rgb(204, 204, 204)", "rgb(102, 102, 102)", "rgb(247, 247, 247)",
"rgb(127, 127, 127)", "rgb(99, 99, 206)", "rgb(221, 221, 221)",
"rgb(136, 136, 136)", "rgb(107, 107, 107)", "rgb(128, 128, 0)",
"rgb(105, 105, 105)", "rgb(255, 140, 0)", "rgb(112, 128, 144)"
];
const basicColors = ["rgb(0, 0, 0)", "rgb(255, 255, 255)"];
const cssProperties = [
"ActiveBorder", "ActiveCaption", "AppWorkspace", "Background",
"ButtonFace", "ButtonHighlight", "ButtonShadow", "ButtonText",
"CaptionText", "GrayText", "Highlight", "HighlightText",
"InactiveBorder", "InactiveCaption", "InactiveCaptionText",
"InfoBackground", "InfoText", "Menu", "MenuText", "Scrollbar",
"ThreeDDarkShadow", "ThreeDFace", "ThreeDHighlight",
"ThreeDLightShadow", "ThreeDShadow", "Window", "WindowFrame",
"WindowText"
];
const cssAttributes = {};
cssProperties.forEach(property => {
const randomIndex = Math.floor(Math.random() * 30);
if (randomIndex <= 14) {
cssAttributes[property] = predefinedColors[randomIndex];
} else {
cssAttributes[property] = basicColors[Math.floor(Math.random() * 2)];
}
});
return cssAttributes;
}
看代码我们可以看到主要就是预设了多种这个RGB的颜色,这个算法的话不难,我们知道大致的思路就可以。因为开源的也很多可以参考~
下面说说随机指纹的部分,主要就是浏览器跟设备构造的一些信息进行加密。通过哈希的计算对什么Canvas/WebGL的指纹信息进行加密,这里作者放出核心的finger思路,然后Demo实现如下所示:
// 生成一个随机的 UUID 作为指纹信息
function generateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// 生成随机的屏幕信息
function generateScreenInfo() {
var resolutions = [
{ width: 2160, height: 3840 },
{ width: 1440, height: 2560 },
{ width: 1080, height: 1920 },
{ width: 900, height: 1600 },
{ width: 2160, height: 4096 }
];
var randomIndex = Math.floor(Math.random() * resolutions.length);
var screen = resolutions[randomIndex];
return {
width: screen.width,
height: screen.height,
availableHeight: screen.height - 50 // 假设减去了一定高度
};
}
// 创建一个包含指纹信息和屏幕信息的对象
var deviceInfo = {
fingerprint: generateUUID(),
screen: generateScreenInfo()
};
// 输出设备信息
console.log("Device Information:", deviceInfo);
核心就是随机字符去计算哈希,也就是Canvas的指纹信息!然后WebGL同样的操作。把浏览器设备的信息构造出来即可
最后,我们来看一下最终的算法效果,运行JS得到加密参数a、d的值,如下所示:
拿到请求参数的加密值后,就可以开始去构造请求接口的Python代码,把整个流程给串起来!比如说我们要拿价格或者商品详情的数据进行分析,那么不去处理xx-x-eid-token
的这个风控参数,在一段时间跟次数后就会出现问题
作者编写了一下Python的调用算法与爬虫的Demo,实现代码如下所示(小细节,tls检测过一下,用第三方的模块~~):
import time
import json
import execjs
from loguru import logger
from curl_cffi import requests
def get_token(ua):
with open("get_api_eid_token.js", encoding='utf-8') as f:
ctx = execjs.compile(f.read())
res = ctx.call("getTokenParam",ua)
return res
def get_h5st(params, aid, ua):
with open("h5st_4.7.3.js", encoding='utf-8') as f:
ctx = execjs.compile(f.read())
res = ctx.call("web_h5st", params, aid, ua)
return res
headers = {} # 自行获取
cookies = {} # 自行获取
# 你懂的~
token_api = 'https://gia.jd.com/jsTk.do?a={}'
res = json.loads(get_token(headers['user-agent']))
payload = "d={}".format(res["d"])
response = requests.post(token_api.format(res['a']), headers=headers, cookies=cookies, data=payload).json()
logger.info(f'x-api-eid-token接口请求: {response}')
好,测试运行一下上面的Python代码,先尝试获取xx-x-eid-token的数据,如下所示:
解决完这个风控参数,在某些场景下配合提交到请求参数中去动态的使用,效果会更加稳定!最终测试结果如下所示: