本文将详细介绍如何通过分析和重现极验滑动验证码中的关键参数,最终破解 w 参数的过程。我们会逐步讲解每个步骤及其背后的原理,提供完整的代码实现。
请求概览
在破解极验滑动验证码的过程中,我们需要关注以下几个请求及其参数:
gettype.php:获取核心 JS 文件链接。
get.php:收集浏览器信息并上报。
ajax.php:执行无感验证。
slide.x.x.x.js:加载相关 JS 文件。
get.php:获取滑动验证的一些基本数据。
ajax.php:执行滑动验证,成功后返回 validate。
关键参数
gt:固定值,是和极验申请的 ID。
challenge:行为的 ID,每次操作会返回一个新的 challenge。
w:加密数据,是校验通过与否的核心参数。
破解 w 参数
我们的目标是破解滑动验证码中的 w 参数。首先,我们需要观察 w 参数的生成过程,并找到其构造逻辑。
观察 w 参数的生成
通过在代码中搜索 w 参数,并设置断点进行调试,我们可以发现 w 参数由两个部分组成。每个部分都有自己的构造逻辑。接下来,我们将分别破解这两个部分。
破解 u 参数
u 参数的生成涉及一个随机值和一个加密过程。以下是生成 u 参数的代码:
typescript
function getRandomTextHelper() {
const helper = () => ((65536 * (1 + Math.random())) | 0).toString(16).substring(1);
return helper() + helper() + helper() + helper();
}
var oldRandomText = getRandomTextHelper();
function getRandomText(needRefresh) {
if (needRefresh) {
oldRandomText = getRandomTextHelper();
}
return oldRandomText;
}
function getU() {
var e = new U().encrypt(getRandomText());
while (!e || 256 !== e.length) e = new U().encrypt(getRandomText(true));
return e;
}
var U = ...; // U 对象的定义
破解 h 参数
h 参数的生成相对复杂,需要结合多个步骤来完成。
l 参数
l 参数是 h 参数的一部分,它的生成涉及加密和序列化操作:
typescript
function getL({ c, s, trace, challenge }) {
const o = {
lang: 'zh-cn',
userresponse: H(trace.at(-1)[0], challenge),
passtime: trace.at(-1)[2],
imgload: Math.floor(Math.random() * 50 + 30),
aa: getAA(encodeTrace(trace), c, s),
ep: {
v: '7.8.8',
$_BIQ: false,
me: true,
tm: -1,
td: -1,
},
};
return V.encrypt(stringify(o), getRandomText());
}
function getRandomText() {
const helper = () => ((65536 * (1 + Math.random())) | 0).toString(16).substring(1);
return helper() + helper() + helper() + helper();
}
var V = ...; // V 对象的定义
o 参数
o 参数包含了滑动验证的各个细节信息:
lang:语言,可以设为 'zh-cn'。
userresponse:滑动距离的加密结果。
passtime:滑动验证的耗时。
imgload:验证码图片的加载时间。
aa:滑动轨迹的加密结果。
ep:额外参数。
构造滑动轨迹
滑动轨迹是一个包含多个记录的小数组的数组,每个小数组记录一次滑动的 x 坐标、y 坐标和时间:
typescript
function generateSlideTrace(distance: number) {
const trace = [
[random(-50, -10), random(-50, -10), 0],
[0, 0, 0],
];
let t = random(50, 100);
let lastX = 0;
let lastY = 0;
let lastYCount = 0;
const count = 30 + Math.floor(distance / 2);
for (let i = 0; i < count; i++) {
const x = Math.round(i === count ? 1 : (1 - Math.pow(2, (-10 * i) / count)) * distance);
t += random(10, 20);
if (x === lastX) continue;
lastYCount += 1;
if (lastYCount > random(5, 10)) {
lastYCount = 0;
lastY = random(-2, 2);
}
lastX = x;
trace.push([x, lastY, t]);
}
return trace;
}
合并 u 和 h
最终,我们需要将两个部分合并生成 w 参数。以下是完整的代码实现:
typescript
export function getSlideW(props) {
return getSlideLeft(props) + getRight();
}
function getSlideLeft(props) {
return encodeLeft(getL(props));
}
function getL({ c, s, trace, challenge }) {
const o = {
lang: 'zh-cn',
userresponse: H(trace.at(-1)[0], challenge),
passtime: trace.at(-1)[2],
imgload: Math.floor(Math.random() * 50 + 30),
aa: getAA(encodeTrace(trace), c, s),
ep: {
v: '7.8.8',
$_BIQ: false,
me: true,
tm: -1,
td: -1,
},
};
return V.encrypt(stringify(o), getRandomText());
}
function getRight() {
var e = new U().encrypt(getRandomText());
while (!e || 256 !== e.length) e = new U().encrypt(getRandomText(true));
return e;
}
function getRandomTextHelper() {
const helper = () => ((65536 * (1 + Math.random())) | 0).toString(16).substring(1);
return helper() + helper() + helper() + helper();
}
var oldRandomText = getRandomTextHelper();
function getRandomText(needRefresh) {
if (needRefresh) {
oldRandomText = getRandomTextHelper();
}
return oldRandomText;
}
// 下面的是从 slide 文件里抠出来的逻辑
function stringify(t, e, n) { ... }
function H(t, e) { ... }
function getAA(t, e, n) { ... }
function encodeTrace(arr) { ... }
function encodeLeft(l) { ... }
var U = ...;
var V = ...;