某验3代分析
字数 1241 2025-08-18 11:35:59
某验3代滑块验证码逆向分析教程
声明
本教程所有内容仅供学习交流使用,严禁用于商业用途和非法用途!文中涉及的所有敏感信息均已做脱敏处理。
前言
某验3代滑块验证码近期加强了校验机制,现在需要逆向三个相互关联的w参数才能通过验证。本文将详细分析每个w参数的生成逻辑。
验证流程分析
接口调用顺序
- register-slide接口:返回challenge和gt值
- get.php接口:返回c和s参数(新版w参数不能为空)
- ajax.php接口:返回验证码类型(必须请求且w不能为空)
- 第二次get.php接口:返回滑块图片和底图
- 滑动完成后生成validate参数,作为后续登录令牌
第一个w参数逆向分析
定位方法
搜索"\u0077"可以定位到w的生成位置:w=i+r
关键代码
var r = t[$_CEFDY(1196)]()
o = $_BEH()[$_CEFCV(1127)](fe[$_CEFDY(431)](t[$_CEFCV(370)]), t[$_CEFDY(1143)]())
i = R[$_CEFDY(1197)](o)
r值分析
this[$_CGHDO(1143)](e)生成16位随机字符串new G()[$_CGHDO(93)]是RSA加密函数- 需要扣下G函数或获取RSA公钥和模值
- r值为RSA加密后的16位随机字符串
o值分析
- 加密方法:
$_BEH()[$_CEFCV(1127)] - 参数1:
t[$_CEFDY(1143)]()(RSA加密的key) - 参数2:
fe[$_CEFDY(431)](t[$_CEFCV(370)]) - 实际是AES加密,初始向量为
0000000000000000
AES加密实现
function Aes_encrypt(text, key_value) {
var key = CryptoJS.enc.Utf8.parse(key_value);
var iv = CryptoJS.enc.Utf8.parse("0000000000000000");
var srcs = CryptoJS.enc.Utf8.parse(text);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
for (var r = encrypted, o = r.ciphertext.words, i = r.ciphertext.sigBytes, s = [], a = 0; a < i; a++) {
var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
s.push(c);
}
return s;
}
i值分析
- i值为两个value相加
- 传入参数为AES加密后的值
- 需要扣下m模块的函数
明文参数
- gt和challenge:来自/register-slider接口
- offline和static_servers:固定值
第二个w参数逆向分析
定位方法
无法通过搜索"\u0077"定位,需要通过跟栈方式找到生成位置
生成方式
w=R["encrypt"](r,key),与第一个w中的i生成方式相同
nr参数结构
{
"lang": "zh-cn",
"type": "fullpage",
"tt": "M3*8Pjp8Pj9HbUp8PN9U),,:A(,(5(,(m-BJBFB:bgfA9/1O6*:I:JkNjRj31RkK**2KDRjE1S0OMM9*)-2.k6h)).E-:-)-9-:(:5b9-:1Mal2UK1RjY1I****)*:F3)pM0/JBBBA(((((,((iB9(((((,(5bn)BBBo95(,(qcjc*)R)fM2*QWU3cUA.N9?-G5N(:(?-N6,B1-2OUS_M9b?M:(A-)19d_cUS/BTF@AfC*Mf5?M95U-)1E1*OE(mj@.NQJ2@(g5@Acb?T)0N5u9khbE6,:CX)*E/B5-*Mb*)ME-((((M(((((((Lqqqp(Df((((((bb55,55(5((,((n-.(--88e(qR@).?2WE-Q(c19M9-)M919/)MM/)(P-U-(/)M*/.M*-)4)M@-N9d5Y-,-d(?b9/,M1AB9*nF)2(J*Df*M9/)MfN9*)(UU(0)(N1I-*b9/)(0qqM)qqp(-n",
"s": "c7c3e21112fe4f741921cb3e4ff9f7cb",
"h": "321f9af1e098233dbd03f250fd2b5e21",
"hh": "39bd9cad9e425c3a8f51610fd506e3b3",
"hi": "09eb21b3ae9542a9bc1e8b63b3d9a467",
"vip_order": -1,
"ct": -1,
"ep": {...},
"passtime": 5365,
"rp": "0d51406b2c658811294a91e9ea533bed",
"captcha_token": "541381339",
"gdyf": "kqy8o0w7"
}
rp参数生成
const CryptoJS = require("crypto-js");
let gtt = '019924a82c70bb123aae90d483087f94';
let challenge = '7d59427b8c64734df3d8aa8585311fac';
let rp = CryptoJS.MD5(gtt + challenge + 1986).toString();
第三个w参数逆向分析
生成方式
与第一个w类似:w = h + u
关键参数
- userresponse:滑动距离 + challenge的值
- passtime:滑块滑动时间
- imgload:图片加载时间
- aa:轨迹加密
- h9s9:每天变化的key/value(可固定)
- rp:gt + 32位challenge + passtime的MD5加密
rp参数生成
var CryptoJS = require("crypto-js");
gt = '019924a82c70bb123aae90d483087f94'
challenge = '7d59427b8c64734df3d8aa8585311fac'
var rp = CryptoJS.MD5(gtt + challenge + 476).toString()
userresponse生成
// t为滑动距离,i[$_CAHJS(134)]为最新的challenge
var userresponse = H(t, i[$_CAHJS(134)])
aa参数生成
- 由轨迹数组加密生成
- 需要扣下轨迹加密函数
- 关键参数:
- 轨迹加密结果
- c值
- s值
轨迹加密函数示例
function ct(t) {
// 省略具体实现
W['prototype'] = {
"\u0024\u005f\u0046\u0044\u004c": function (trace) {
// 轨迹加密逻辑
}
}
aa = W['prototype']['\u0024\u005f\u0042\u0042\u0045\u0049'](W['prototype']['\u0024\u005f\u0046\u0044\u004c'](trace), C, S);
return aa;
}
辅助功能实现
底图还原
def restore_picture():
img_list = ["./乱序缺口背景图.png", "./乱序背景图.png"]
for index, img in enumerate(img_list):
image = Image.open(img)
s = Image.new("RGBA", (260, 160))
ut = [39, 38, 48, 49, 41, 40, 46, 47, 35, 34, 50, 51, 33, 32, 28, 29, 27, 26, 36, 37, 31, 30, 44, 45, 43, 42, 12, 13, 23, 22, 14, 15, 21, 20, 8, 9, 25, 24, 6, 7, 3, 2, 0, 1, 11, 10, 4, 5, 19, 18, 16, 17]
height_half = 80
for inx in range(52):
c = ut[inx] % 26 * 12 + 1
u = height_half if ut[inx] > 25 else 0
l_ = image.crop(box=(c, u, c + 10, u + 80))
s.paste(l_, box=(inx % 26 * 10, 80 if inx > 25 else 0))
if index == 0:
s.save("./缺口背景图片.png")
else:
s.save("./背景图片.png")
缺口识别(方案A)
import io
from PIL import Image
import cv2
import numpy as np
def pilImgToCv2(img: Image.Image, flag=cv2.COLOR_RGB2BGR):
return cv2.cvtColor(np.asarray(img), flag)
def getDistance(img: Image.Image, slice: Image.Image):
grayImg = pilImgToCv2(img, cv2.COLOR_BGR2GRAY)
graySlice = pilImgToCv2(slice, cv2.COLOR_BGR2GRAY)
grayImg = cv2.Canny(grayImg, 255, 255)
graySlice = cv2.Canny(graySlice, 255, 255)
result = cv2.matchTemplate(grayImg, graySlice, cv2.TM_CCOEFF_NORMED)
maxLoc = cv2.minMaxLoc(result)[3]
distance = maxLoc[0]
return distance
缺口识别(方案B)
import ddddocr
slide = ddddocr.DdddOcr(det=False, ocr=False)
with open('bg.jpg', 'rb') as f:
target_bytes = f.read()
with open('fullpage.jpg', 'rb') as f:
background_bytes = f.read()
res = slide.slide_comparison(target_bytes, background_bytes)
print(res)
轨迹模拟
import random
def __ease_out_expo(sep):
if sep == 1:
return 1
else:
return 1 - pow(2, -10 * sep)
def get_slide_track(distance):
slide_track = [
[random.randint(-50, -10), random.randint(-50, -10), 0],
[0, 0, 0],
]
count = 40 + int(distance / 2)
t = random.randint(50, 100)
_x = 0
_y = 0
for i in range(count):
x = round(__ease_out_expo(i / count) * distance)
t += random.randint(10, 50)
if x == _x:
continue
slide_track.append([x, _y, t])
_x = x
slide_track.append(slide_track[-1])
return slide_track
总结
- 三个w参数相互关联,缺一不可
- 第一个w:RSA+AES加密组合
- 第二个w:与无感验证类似,需要生成复杂的nr参数
- 第三个w:包含轨迹加密和多种参数组合
- 需要配合底图还原、缺口识别和轨迹模拟等辅助功能
通过完整实现上述所有环节,即可成功逆向某验3代滑块验证码。