第一次遇到PerimeterX PX3防护是在分析某电商网站时,突然发现所有请求都带上了奇怪的加密参数。这种防护不像传统验证码那样明显,而是在后台默默收集用户行为数据,通过机器学习模型判断请求是否来自真人。PX3的核心在于三重防护:动态payload加密、AST混淆代码生成、浏览器指纹校验。
我花了三天时间逆向spirit.com的请求流程,发现每个API调用都需要携带加密的_px3参数。这个参数看似简单的Base64字符串,实际是经过多层变换的复合加密结果。最有趣的是,不同浏览器、不同时间访问时,加密算法会动态调整密钥生成规则,这也是为什么直接复制参数会失效的原因。
通过拦截Chrome开发者工具的网络请求,我捕获到一个典型payload:
json复制{
"do": null,
"ob": "B1lZWQcHWQcUNxgQDA0UW1tYFFsMUA1ZXFBRXFkMXQpdXwsJXF0LXlhdC1FdDl4MUVENCl9aUFlaXVpdCg4NX15aX19RXglcXVxQDQwMWlBcUF9SDREiWAk/WQQLWzoACjApAScCLVslLCkQJSwtWCc8PR8lLAxRFBwaHQ0UW1hYFhYWFllZWQdZBxRZUFpfWVlcWlxbWFFaXFheWF5bWBYWFhZZWVkHB1kUCwsUXlgUPVouHDI+JhgMLz1RPC8uXCcfVVUWFhYWB1lZB1lZBwcUXVxeUQ1QDl5FUFxaX0VZWQ0NRQpfXVhFW1gJXVkMCQ5QClBbFBwaHQ0WFhYWB1kHB1kHFAoNDFoLWVFYRVBcWl5FWVkNDUUJUV1YRQoMWF1QXAsNX14JURRbWV1bXlhYWBQcGh0NFhYWFgdZB1lZWRQLHRYWFhZZWQdZWVkUX1haCglbXwoLUQsJCl0NCloNXFgJDlsJC1leXl0OClpeUVwOUV1aCwpZWV8JXA1YXApcXgxYX1AOXF1cWQwKChYWFhYHWVkHWVkUXVxeUQ1dXQ1FUFxaX0VZWQ0NRQpfXVhFW1gJXVkMCQ5QClBbFhYWFllZWQcHWRQaDhReWBRZFhYWFgdZWVkHBwdZFFlfWFhZWFlcUV1YWF4WFhYWB1lZWQcHBwcUWVpRXxYWFhZZBwcHB1kUCwQJBhkcDwRdXRteWlwcGwcOXA8="
}
经过反复测试发现,ob字段的生成流程是:
用Python实现解密核心代码:
python复制import base64
def decrypt_px3(enc_str):
xor_key = 0x50
b64_decoded = base64.b64decode(enc_str)
return bytes([b ^ xor_key for b in b64_decoded]).decode()
PX3的狡猾之处在于密钥会随版本变化。通过分析main.min.js发现,实际异或值是版本号字符和的模128:
javascript复制// 计算示例:v8.7.2 -> (8+7+2)%128 = 17
const version = "v8.7.2";
const key = version.split('').reduce((a,c)=>!isNaN(c)?a+parseInt(c):a,0)%128;
这就要求我们必须先提取当前脚本版本号。我开发了自动提取工具:
python复制import re
def get_px3_version(js_content):
match = re.search(r'v\d+\.\d+\.\d+', js_content)
return match.group() if match else "v8.7.2" # 默认版本
PX3的客户端脚本采用AST(抽象语法树)混淆,常见手法包括:
以这段典型代码为例:
javascript复制function _0x1a2b() {
var _0x3f2f = ['\x57\x58\x4b\x43', '\x63\x6f\x6f\x6b'];
return function(_0x1a2b, _0x3f2f) {
_0x1a2b = _0x1a2b - 0x0;
var _0x4d8c = _0x3f2f[_0x1a2b];
return _0x4d8c;
};
}
使用Babel工具链反混淆步骤:
bash复制npm install @babel/parser @babel/traverse @babel/generator
javascript复制const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
function deobfuscate(code) {
const ast = parser.parse(code);
traverse(ast, {
StringLiteral(path) {
// 处理十六进制字符串
if(path.node.extra && path.node.extra.raw.includes('\\x')) {
path.node.value = eval(path.node.extra.raw);
}
}
});
return generator(ast).code;
}
通过AST还原出PX3的核心校验函数:
javascript复制function verifyFingerprint(fp) {
const requiredKeys = ['canvas', 'webgl', 'audioContext'];
let score = 0;
requiredKeys.forEach(key => {
if(fp[key] && fp[key].length > 10) {
score += 30;
}
});
if(fp.timezoneOffset === -480) score += 10;
if(fp.languages.includes('en-US')) score += 5;
return score > 60;
}
这个函数验证了浏览器指纹的完整性,包括:
PX3采集的指纹数据超乎想象,包括:
markdown复制- **基础信息**:
- 屏幕分辨率 (PX11843: 1920)
- 色彩深度 (PX12003: 24)
- 时区 (PX12553: "Asia/Shanghai")
- **高级特征**:
- WebGL渲染器 (PX11746: "71f68cf1450d0b31d397705dad72f593")
- 音频采样率哈希 (PX12118: "clanrpe6tl5m24stojmg")
- 字体枚举列表 (PX11881: ["loadTimes","csi","app"])
- **行为特征**:
- 鼠标移动轨迹 (PX12271: "sss")
- 页面停留时间 (PX11431: "1700101605261")
- 滚动行为模式 (PX12550: 1)
经过多次测试,稳定的指纹模拟需要以下配置:
python复制def generate_fingerprint():
return {
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"platform": "MacIntel",
"hardwareConcurrency": 4,
"deviceMemory": 8,
"timezone": "Asia/Shanghai",
"webglVendor": "Intel Inc.",
"canvasHash": "7c5f9724",
"audioContextHash": "65d826e0",
"touchSupport": False,
"languages": ["en-US", "zh-CN"]
}
关键点在于:
最终实现的自动化流程:
python复制from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
javascript复制Object.defineProperty(navigator, 'deviceMemory', {
get: () => 8
});
python复制def generate_px3_param(payload):
version = get_js_version(driver.page_source)
xor_key = calculate_xor_key(version)
encrypted = xor_encrypt(json.dumps(payload), xor_key)
return base64.b64encode(encrypted).decode()
在实际运行中需要注意:
通过这套方案,在持续一周的测试中,PX3的拦截率从最初的78%降到了3%以下。最关键的突破点是发现他们主要依赖WebGL指纹的稳定性,只要保持渲染哈希值恒定,其他参数可以有较小波动。