最近朋友有个业务里多了个滑块,于是找到我想把原本的自动化改改,那就绕不开这个极验了,说到极验,这可能是我的心魔,半年前啥也不会的时候头铁花了5天把极验的协议复现出来了,但是就是不给通过,那段时间有阴影了,后面又尝试了一次,还是以失败告终,以至于后来看到这玩意就绕道,宁愿去分析ali都不愿意去看极验(我个人的感觉是顶象数美易盾啥的比这简单多了,可能真是有阴影了哈哈哈😂),不过这次我没退缩,花了几个小时硬着头皮给他过了(也算是打破心魔了),我这篇博客主要记录我面对这次心魔的一些坑点
目标网址
看标题自己找了
流程分析
先触发整个流程看看效果
箭头指出来的都是和这个登录有关的请求,每一步都要完善。虽然半年前就知道了极验的w好像只有最后一个轨迹的会校验其他的置空写死都可以,但由于是面对心魔,所以我秉着每一步都排查仔细,参数全都要生成的原则来写这个流程。
gee_test/get
第一步没啥好说的,直接模拟拿到结果 challenge,gt,xq_geetest
gettype.php
第二步也要模拟,别看他不返回有用的东西就不做,也很简单
第一个get.php
这第一个php的 负载里面的gt和challenge用前面返回的,这个w值我也模拟了,先定位到加密值,过程就不说了,直接看内容
w值由 i + r 组成的,把代码全部拿下来,然后补上环境,我是一点一点补的,没有用jsdom,补了20多分钟,很头铁。然后把r的生成函数导出和i生成有关的函数都导出,可以拿到第一个w值,如图
然后迎来第一个坑点了,r的生成函数里面依赖了一个随机数,o的生成也依赖了一个随机数,这两个随机数一定要相同的,如果不相同,那这个接口返回就一直是 param error, 这边直接写死
无论是r的生成还是o的生成,随机数都是一致的生成的w值才是正确的,然后返回的c值和s值都存下来,后面有用
第一个ajax.php
首先这个接口是必须要的,因为你别看他只返回了一个slide,看起来对你后面没什么帮助,但是这个接口你不做的话,后面的会拿不到结果。那这个w要做吗? 很坑,这是第二个坑点,我一开始是直接写死,能返回结果,但是最后一步验证可就别想过去了,排查半天只能给他完善。所以这个第二个w值是一定要做的,一样的,直接看内容,怎么跟的就不赘述了,太繁杂了
可以看到是对一个r和一个随机数加密,这个随机数依旧写死,和前面一样,来看看这个r
{
"lang": "zh-cn",
"type": "fullpage",
"tt": "Mm(*(f(H1((YM((",
"light": -1,
"s": "c7c3e21112fe4f741921cb3e4ff9f7cb",
"h": "321f9af1e098233dbd03f250fd2b5e21",
"hh": "39bd9cad9e425c3a8f51610fd506e3b3",
"hi": "09eb21b3ae9542a9bc1e8b63b3d9a467",
"vip_order": -1,
"ct": -1,
"ep": {
"v": "9.1.9-cyhomb",
"$_Ei": false,
"me": true,
"ven": "Google Inc. (NVIDIA)",
"ren": "ANGLE (NVIDIA, NVIDIA T1000 8GB (0x00001FF0) Direct3D11 vs_5_0 ps_5_0, D3D11)",
"fp": null,
"lp": null,
"em": {
"ph": 0,
"cp": 0,
"ek": "11",
"wd": 1,
"nt": 0,
"si": 0,
"sc": 0
},
"tm": {
"a": 1745744494557,
"b": 0,
"c": 0,
"d": 0,
"e": 0,
"f": 1745744494566,
"g": 1745744494584,
"h": 1745744494584,
"i": 1745744494584,
"j": 1745744494653,
"k": 1745744494614,
"l": 1745744494653,
"m": 1745744494831,
"n": 1745744494901,
"o": 1745744494835,
"p": 1745744495717,
"q": 1745744495717,
"r": 1745744495720,
"s": 1745744755658,
"t": 1745744755658,
"u": 1745744755659
},
"dnf": "dnf",
"by": 2
},
"passtime": 1068463,
"rp": "6506528540e6617c70194a710295e16f",
"captcha_token": "1938544055",
"ydue": "hfoj0glb"
}
这里只有rp需要模拟生成,其他的都可以定值,看看rp怎么生成的,藏的好深,我找了有一会才找到
rp就藏在这个a数组里面,直接定义的,拉老长了
就像这样的一个定义,可以看到rp是用了 G(gt + challenge + pastime ),经过验证这个函数是标准的md5加密,那么这第二个w也可以得出了
这个rp我是在外面生成的,passtime就被我写死了
第二个get.php
ok,这第二个get.php并没有什么加密的值,直接拿到结果,把图片都下载下来,并且更新一下challenge和gt和c和s的值,拿到的图片自己还原一下,这里就不讲了
第二个ajax.php
到最后一个验证的w值了,步骤都是一样,全拿下来,导出该导出的函数,然后调用,这里资料大把,就不说还原了,讲讲坑点
拿下来都可以直接用的,唯一需要注意的还是那个随机值,又是这玩意,直接写死,别给他机会
h个u的生成都会依赖这个随机值,一定要一样,然后还有传入gt,challenge的时候也要传对来,是更新后的才行。
login
验证通过后拿到一个validate,然后会发现加密了账号密码等值
全局一搜,断住,发现是个sm4加密,我直接扣下来用了
window = global
function xx(op) {
const n = [214, 144, 233, 254, 204, 225, 61, 183, 22, 182, 20, 194, 40, 251, 44, 5, 43, 103, 154, 118, 42, 190, 4, 195, 170, 68, 19, 38, 73, 134, 6, 153, 156, 66, 80, 244, 145, 239, 152, 122, 51, 84, 11, 67, 237, 207, 172, 98, 228, 179, 28, 169, 201, 8, 232, 149, 128, 223, 148, 250, 117, 143, 63, 166, 71, 7, 167, 252, 243, 115, 23, 186, 131, 89, 60, 25, 230, 133, 79, 168, 104, 107, 129, 178, 113, 100, 218, 139, 248, 235, 15, 75, 112, 86, 157, 53, 30, 36, 14, 94, 99, 88, 209, 162, 37, 34, 124, 59, 1, 33, 120, 135, 212, 0, 70, 87, 159, 211, 39, 82, 76, 54, 2, 231, 160, 196, 200, 158, 234, 191, 138, 210, 64, 199, 56, 181, 163, 247, 242, 206, 249, 97, 21, 161, 224, 174, 93, 164, 155, 52, 26, 85, 173, 147, 50, 48, 245, 140, 177, 227, 29, 246, 226, 46, 130, 102, 202, 96, 192, 41, 35, 171, 13, 83, 78, 111, 213, 219, 55, 69, 222, 253, 142, 47, 3, 255, 106, 114, 109, 108, 91, 81, 141, 27, 175, 146, 187, 221, 188, 127, 17, 217, 92, 65, 31, 16, 90, 216, 10, 193, 49, 136, 165, 205, 123, 189, 45, 116, 208, 18, 184, 229, 180, 176, 137, 105, 151, 74, 12, 150, 119, 126, 101, 185, 241, 9, 197, 110, 198, 132, 24, 240, 125, 236, 58, 220, 77, 32, 121, 238, 95, 62, 215, 203, 57, 72]
,
r = [462357, 472066609, 943670861, 1415275113, 1886879365, 2358483617, 2830087869, 3301692121, 3773296373, 4228057617, 404694573, 876298825, 1347903077, 1819507329, 2291111581, 2762715833, 3234320085, 3705924337, 4177462797, 337322537, 808926789, 1280531041, 1752135293, 2223739545, 2695343797, 3166948049, 3638552301, 4110090761, 269950501, 741554753, 1213159005, 1684763257];
function o(e) {
const t = [];
for (let n = 0, r = e.length; n < r; n += 2)
t.push(parseInt(e.substr(n, 2), 16));
return t
}
function i(e, t) {
const n = 31 & t;
return e << n | e >>> 32 - n
}
function a(e) {
return (255 & n[e >>> 24 & 255]) << 24 | (255 & n[e >>> 16 & 255]) << 16 | (255 & n[e >>> 8 & 255]) << 8 | 255 & n[255 & e]
}
function s(e) {
return e ^ i(e, 2) ^ i(e, 10) ^ i(e, 18) ^ i(e, 24)
}
function u(e) {
return e ^ i(e, 13) ^ i(e, 23)
}
function c(e, t, n) {
const r = new Array(4)
, o = new Array(4);
for (let t = 0; t < 4; t++)
o[0] = 255 & e[4 * t],
o[1] = 255 & e[4 * t + 1],
o[2] = 255 & e[4 * t + 2],
o[3] = 255 & e[4 * t + 3],
r[t] = o[0] << 24 | o[1] << 16 | o[2] << 8 | o[3];
for (let e, t = 0; t < 32; t += 4)
e = r[1] ^ r[2] ^ r[3] ^ n[t + 0],
r[0] ^= s(a(e)),
e = r[2] ^ r[3] ^ r[0] ^ n[t + 1],
r[1] ^= s(a(e)),
e = r[3] ^ r[0] ^ r[1] ^ n[t + 2],
r[2] ^= s(a(e)),
e = r[0] ^ r[1] ^ r[2] ^ n[t + 3],
r[3] ^= s(a(e));
for (let e = 0; e < 16; e += 4)
t[e] = r[3 - e / 4] >>> 24 & 255,
t[e + 1] = r[3 - e / 4] >>> 16 & 255,
t[e + 2] = r[3 - e / 4] >>> 8 & 255,
t[e + 3] = 255 & r[3 - e / 4]
}
function l(e, t, n, {padding: i = "pkcs#7", mode: s = "cbc", iv: l, output: f = "string"} = {}) {
if ("cbc" === s && (void 0 === l && (l = t),
"string" == typeof l && (l = o(l)),
16 !== l.length))
throw new Error("iv is invalid");
if ("string" == typeof t && (t = o(t)),
16 !== t.length)
throw new Error("key is invalid");
if (e = "string" == typeof e ? 0 !== n ? function (e) {
const t = [];
for (let n = 0, r = e.length; n < r; n++) {
const r = e.codePointAt(n);
if (r <= 127)
t.push(r);
else if (r <= 2047)
t.push(192 | r >>> 6),
t.push(128 | 63 & r);
else if (r <= 55295 || r >= 57344 && r <= 65535)
t.push(224 | r >>> 12),
t.push(128 | r >>> 6 & 63),
t.push(128 | 63 & r);
else {
if (!(r >= 65536 && r <= 1114111))
throw t.push(r),
new Error("input is not supported");
n++,
t.push(240 | r >>> 18 & 28),
t.push(128 | r >>> 12 & 63),
t.push(128 | r >>> 6 & 63),
t.push(128 | 63 & r)
}
}
return t
}(e) : o(e) : [...e],
("pkcs#5" === i || "pkcs#7" === i) && 0 !== n) {
const t = 16 - e.length % 16;
for (let n = 0; n < t; n++)
e.push(t)
}
const d = new Array(32);
!function (e, t, n) {
const o = new Array(4)
, i = new Array(4);
for (let t = 0; t < 4; t++)
i[0] = 255 & e[0 + 4 * t],
i[1] = 255 & e[1 + 4 * t],
i[2] = 255 & e[2 + 4 * t],
i[3] = 255 & e[3 + 4 * t],
o[t] = i[0] << 24 | i[1] << 16 | i[2] << 8 | i[3];
o[0] ^= 2746333894,
o[1] ^= 1453994832,
o[2] ^= 1736282519,
o[3] ^= 2993693404;
for (let e, n = 0; n < 32; n += 4)
e = o[1] ^ o[2] ^ o[3] ^ r[n + 0],
t[n + 0] = o[0] ^= u(a(e)),
e = o[2] ^ o[3] ^ o[0] ^ r[n + 1],
t[n + 1] = o[1] ^= u(a(e)),
e = o[3] ^ o[0] ^ o[1] ^ r[n + 2],
t[n + 2] = o[2] ^= u(a(e)),
e = o[0] ^ o[1] ^ o[2] ^ r[n + 3],
t[n + 3] = o[3] ^= u(a(e));
if (0 === n)
for (let e, n = 0; n < 16; n++)
e = t[n],
t[n] = t[31 - n],
t[31 - n] = e
}(t, d, n);
const p = [];
let h = l
, m = e.length
, v = 0;
for (; m >= 16;) {
const t = e.slice(v, v + 16)
, r = new Array(16);
if ("cbc" === s)
for (let e = 0; e < 16; e++)
0 !== n && (t[e] ^= h[e]);
c(t, r, d);
for (let e = 0; e < 16; e++)
"cbc" === s && 0 === n && (r[e] ^= h[e]),
p[v + e] = r[e];
"cbc" === s && (h = 0 !== n ? r : t),
m -= 16,
v += 16
}
if (("pkcs#5" === i || "pkcs#7" === i) && 0 === n) {
const e = p.length
, t = p[e - 1];
for (let n = 1; n <= t; n++)
if (p[e - n] !== t)
throw new Error("padding is invalid");
p.splice(e - t, t)
}
return "array" !== f ? 0 !== n ? p.map(e => 1 === (e = e.toString(16)).length ? "0" + e : e).join("") : function (e) {
const t = [];
for (let n = 0, r = e.length; n < r; n++)
e[n] >= 240 && e[n] <= 247 ? (t.push(String.fromCodePoint(((7 & e[n]) << 18) + ((63 & e[n + 1]) << 12) + ((63 & e[n + 2]) << 6) + (63 & e[n + 3]))),
n += 3) : e[n] >= 224 && e[n] <= 239 ? (t.push(String.fromCodePoint(((15 & e[n]) << 12) + ((63 & e[n + 1]) << 6) + (63 & e[n + 2]))),
n += 2) : e[n] >= 192 && e[n] <= 223 ? (t.push(String.fromCodePoint(((31 & e[n]) << 6) + (63 & e[n + 1]))),
n++) : t.push(String.fromCodePoint(e[n]));
return t.join("")
}(p) : p
}
return op.exports = {
encrypt: (e, t, n) => l(e, t, 1, n),
decrypt: (e, t, n) => l(e, t, 0, n),
defaultKey: "0123456789abcdeffedcba9876543210"
}
}
function getEncryptData(xq_geetest,username,pwd,challenge,validate) {
rn = new xx(window)
T = "44366db6541f579612624aa1ad23ba08"
I = "web7cc8ec28d50946f7a68816438fa465c5"
k = "0123456789abcdeffedcba9876543210"
data = {
"remember_me": true,
"xq_geetest": xq_geetest,
"username": username,
"password": pwd,
"captcha": "",
"geetest_challenge": challenge,
"geetest_validate": validate,
"geetest_seccode": validate + "|jordan",
"sid": "68ec71b2723e5fbfef6cf6cdb7db4bde",
"secret": T,
"ee2eDid": I
}
ans = rn.encrypt(JSON.stringify(data), k)
return ans
}
注意
请求别太快,太快极验会给你forbidden。
代码结构大体如下
看看流程
至此,这个登录的全过程就是搞好了。心魔也拿下了,修为更进一步
免责声明
本技术分享仅供学习交流使用,本人不承担因使用这些信息而直接或间接导致的任何责任!!!我鼓励所有参与者遵守法律法规,并在合法合规的前提下使用相关技术。任何逆向工程活动都应在获得充分授权的情况下进行。本人不对分享内容的准确性、可靠性或完整性提供任何明示或暗示的保证。参与者应自行承担风险,并在实施任何技术操作前进行适当的法律咨询