声明
本文章中全部内容仅供学习沟通运用,不用于其他任何意图,不供给完好代码,抓包内容、灵敏网址、数据接口等均已做脱敏处理,严禁用于商业用处和非法用处,不然由此产生的全部结果均与作者无关!
本文章未经许可制止转载,制止任何修改后二次传播,私行运用本文解说的技能而导致的任何意外,作者均不负责,若有侵权,请在大众号【K哥爬虫】联络作者立即删除!
前语
最近许多粉丝反应,某验三代的滑块一直回来 forbidden,不知道为什么经过不了,尝试了许多方法都不行。其实是因为之前只校验了第三个 w 参数的值,现在官网加强了校验,前面两个 w 参数也需求逆出来,三个 w 参数彼此关联,有一个不对,就无法经过验证。现在只发现官网做了更新,往后其他网站可能也会再上点强度。本文将会对每个 w 参数逐一逆向剖析。
逆向方针
- 方针:最新某验 3 代滑块剖析
- 网址:
aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby8=
抓包剖析
刷新页面,经过抓包发现, register-slide 接口会回来 challenge 和 gt 值,为接口 get.php 的首要恳求参数:
get.php 接口会回来 c 和 s,后面会用到,新版界面此接口里的 w 不能够置空,旧版则能够置空:
点击按键验证,会弹出滑块窗口,一起抓到了一个 ajax.php 接口,这个接口会回来验证码的类型,虽然没用,但是假如不恳求或许恳求不带 w 后面都会报错。旧版的话这个接口也是有必要恳求,但是 w 也是能够置空,且后面不会报错:
紧着着,咱们要又抓到一个 get.php 接口,这个接口仍然给咱们回来 c 和 s,恳求不一样的是,这个接口里边还给咱们回来滑块图片以及底图:
紧接着,咱们滑动完结拼图,得到验证结果 validate 参数,这个参数便是后续登录的令牌,在后续操作的恳求中会用到:
逆向剖析
第一个 w
从 get.php 接口处跟栈,或许直接搜 “u0077” 即可成功定位,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)
很显着,这几个参数关于咱们都不陌生了,咱们进入 l 参数,发现 this[$_CGHDO(1143)](e)
为 16 位随机字符串将 new G()[$_CGHDO(93)]
这个函数扣一下即可,这儿很显着,构造了一个 G 函数,所以咱们只需求把 G 函数扣一下即可:
或许咱们进入 G 的原型链 set_public 中,下断点,将他的 RSA 公钥和模值找到即可。所以 r 为 RSA 加密 16 位随机字符串。至此 r 值现已剖析结束。
接下来是 o 值,加密方法为 $_BEH()[$_CEFCV(1127)]
,传入参数为 t[$_CEFDY(1143)]()
和 fe[$_CEFDY(431)](t[$_CEFCV(370)])
。咱们发现 t[$_CEFDY(1143)]()
为 rsa 加密的 key,t[$_CEFDY(1143)]()
和 fe[$_CEFDY(431)](t[$_CEFCV(370)])
为明文参数。进入加密方法,打断点调试,咱们发现是 AES 加密,一起初始向量是 0000000000000000:
直接扣代码,或许引库复现即可:
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;
}
现在,咱们来剖析一下明文的要害参数,这儿咱们选用 K 哥东西站,来解析一下,要害参数如下:
-
gt,challenge:第一个接口 /register-slider 回来;
-
offline, static_servers : static_servers 其他参数固定即可。
至此咱们 o 就剖析结束,总算只剩下一个 i 值,接下来咱们剖析一下 i 值,咱们进入 R[$_CEFDY(1197)]
函 数,发现 i 值为 t 中的俩个 value 值相加,t 的界说在断点上方:
传入的参数 e 为咱们上一个步骤 aes 加密后的值,跟进到 this[$_IJDN(480)]
中,咱们发现他属于 m 模块下的函数:
将整个 m 扣下来,复现如下:
小结:第一个 w 和曾经扣法基本一致,仅仅明文上有参差之分。
第二个 w
现在,咱们第一个接口现已完结,拿到了回来的 s 和 c,接下来咱们进入 ajax.php 接口,仍是跟栈进入。
咱们依然查找定位参数”u0077″ ,发现并没有搜到,所以这个 w 和咱们曾经的扣法有点不一样!!!不过经过,跟栈的方法,咱们找到正确方位。方位的话在 var n = {}; 这个当地,咱们下断点:
w 值便是 t[$_CFEHG(1160)]
,前面咱们发现 var t=this,然后经过 t[$_CFEHG(1191)]()
,咱们的 w 值就生成了。咱们进入这个函数之中,进入今后发现异常的了解,这不便是无感系列的 w 吗?
当然假如你是第一次扣,也不要心急,咱们找到 w 定位的当地:
i[$_CFHIs(1160)] = R[$_CFHIs(1197)](c[$_CFHIs(93)](r, i[$_CFHIs(1143)]()))
咱们简单修改一下代码,大约如下 w=R["encrypt"](r,key)
,没错这个 R 方法其实与第一个 w 中 i 生成方法一样。所以现在,咱们只需求处理 r 参数就能够完结第二个 w 的逆向。咱们看一下 r 参数包括那些,咱们仍旧运用 K 哥东西站进行解析:
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",
"light": "SPAN_0",
"s": "c7c3e21112fe4f741921cb3e4ff9f7cb",
"h": "321f9af1e098233dbd03f250fd2b5e21",
"hh": "39bd9cad9e425c3a8f51610fd506e3b3",
"hi": "09eb21b3ae9542a9bc1e8b63b3d9a467",
"vip_order": -1,
"ct": -1,
"ep": {
"v": "9.1.8-bfget5",
"$_E_": false,
"me": true,
"ven": "Google Inc. (Intel)",
"ren": "ANGLE (Intel, Intel(R) HD Graphics 520 Direct3D11 vs_5_0 ps_5_0, D3D11)",
"fp": ["move", 483, 149, 1702019849214, "pointermove"],
"lp": ["up", 657, 100, 1702019852230, "pointerup"],
"em": {"ph": 0, "cp": 0, "ek": "11", "wd": 1, "nt": 0, "si": 0, "sc": 0},
"tm": {
"a": 1702019845759,
"b": 1702019845951,
"c": 1702019845951,
"d": 0,
"e": 0,
"f": 1702019845763,
"g": 1702019845785,
"h": 1702019845785,
"i": 1702019845785,
"j": 1702019845845,
"k": 1702019845812,
"l": 1702019845845,
"m": 1702019845942,
"n": 1702019845946,
"o": 1702019845954,
"p": 1702019846282,
"q": 1702019846282,
"r": 1702019846287,
"s": 1702019846288,
"t": 1702019846288,
"u": 1702019846288
},
"dnf": "dnf",
"by": 0
},
"passtime": 5365,
"rp": "0d51406b2c658811294a91e9ea533bed",
"captcha_token": "541381339",
"gdyf": "kqy8o0w7"
}
很显着,这儿面有许多参数,一眼看起来许多参数像 md5 加密,咱们向上调试进行剖析,就能够找到参数界说的当地。这儿的 e,t,n,t 是浏览器环境的计算以及一些鼠标的移动轨道,实测这 4 个值固定或许随机生成即可,再往下走会遇到给变量去赋值的状况,这儿的 V 便是 md5 方法:
这儿首要说一下 rp 参数以及 tt 参数是怎样生成的,其他参数能够固定,向上调查,找到显着的 号当地,咱们发现 rp 参数是这样界说的:
直接引库复现即可:
const CryptoJS = require("crypto-js");
let gtt = '019924a82c70bb123aae90d483087f94';
let challenge = '7d59427b8c64734df3d8aa8585311fac';
let rp = CryptoJS.MD5(gtt challenge 1986).toString();
至此,咱们的 nr 剖析到此结束,第二个 w 复现如下:
nr = {"自行生成"};
# nr 如上,ot 为上一个 w 的 key,保持一致!
w2 = R['$_HDZ'](c['encrypt'](fe['stringify'](nr), Ot));
第三个 w
最终一个 w 大伙都很了解了, 没有研究过的能够阅览下这篇文章 【验证码逆向专栏】某验三代滑块验证码逆向剖析,解说的更为详细,本文简单剖析一下。
走到这儿,咱们现已成功百分之 80 了,咱们仍旧从栈的入口进入,查找要害参数”u0077″ ,果然,峰回路转,又回到了这儿:
显着,这个和咱们第一个 w 生成的方法是一模一样的:
u = r[$_CAHJS(737)]()
l = V[$_CAHJS(392)](gt[$_CAIAK(254)](o), r[$_CAIAK(744)]())
h = m[$_CAIAK(792)](l)
w = h u
能够直接按第一个 w 的方法直接引库,或许直接扣下整个模块即可。
这儿,咱们打印 o 参数,看看 o 参数中哪些是固定不变的,哪些是动态变化的:
- 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 与 aa 参数界说的部分:
// t 为滑动间隔,i[$_CAHJS(134)] 为最新的 challenge
var userresponse = H(t, i[$_CAHJS(134)])
进入 H 函数,将 H 函数扣下来即可:
会提示 mwbxQ 不存在,咱们把代码放入 nodepad 中,折叠代码:
发现,这个函数在开头就界说,咱们把他扣一下即可,即可成功完结 H 的作业,自然这个 userresponse 也就不成问题了。
接下来咱们剖析 aa,咱们看到 aa 参数是由 e 界说的,e 是由上一个函数传过来的,所以咱们跟栈,找到 e 界说的当地:
l = n[$_DAAAU(985)][$_CJJJU(1075)](n[$_CJJJU(985)][$_CJJJU(1073)](), n[$_CJJJU(67)][$_CJJJU(1033)], n[$_DAAAU(67)][$_CJJJU(345)]);
$_CJJJU(1075)](n[$_CJJJU(985)][$_CJJJU(1073)]()
为轨道加密结果,n[$_CJJJU(67)][$_CJJJU(1033)]
为 c 值,n[$_DAAAU(67)][$_CJJJU(345)])
为 s 值。咱们进入 $_CJJJU(1075)](n[$_CJJJU(985)][$_CJJJU(1073)]
中,找到轨道加密的当地。咱们发现 this[$_BEGJO(359)]
为轨道数组:
咱们将整个函数扣下来,将函数改写,将轨道当参数传到里边,改写如下,缺什么补什么,最快速的方法,便是利用 nodepad 折叠,能够直观的看到函数散布状况,将能够看到的有用的放进去,然后运行,缺什么补什么即可。附上部分代码:
function ct(t) {
........................................................................
此处省掉,详细缺什么取扣什么,补进来就行
W['prototype'] = {
"u0024u005fu0046u0044u004c": function (trace) {
var $_BEGJp = _tkts.$_Ch
, $_BEGIy = ['$_BEHCk'].concat($_BEGJp)
, $_BEHAJ = $_BEGIy[1];
$_BEGIy.shift();
var $_BEHBv = $_BEGIy[0];
function n(t) {
var $_DBEA_ = _tkts.$_Dm()[0][10];
for (; $_DBEA_ !== _tkts.$_Dm()[0][9];) {
switch ($_DBEA_) {
case _tkts.$_Dm()[4][10]:
var e = $_BEGJp(454)
, n = e[$_BEGJp(159)]
, r = $_BEHAJ(82)
, i = Math[$_BEHAJ(310)](t)
, o = parseInt(i / n);
n <= o && (o = n - 1),
o && (r = e[$_BEGJp(176)](o));
var s = $_BEGJp(82);
return t < 0 && (s = $_BEGJp(413)),
r && (s = $_BEHAJ(445)),
s r e[$_BEGJp(176)](i %= n);
break;
}
}
}
}
aa = W['prototype']['u0024u005fu0042u0042u0045u0049'](W['prototype']['u0024u005fu0046u0044u004c'](trace), C, S);
console.log(aa)
```
至此,aa 参数剖析结束,至此第三个 w 参数剖析结束,结束了嘛?并没有~
底图还原
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")
restore_picture()
辨认缺口
这儿分为 A 计划和 B 计划:
A 计划
import io
from PIL import Image
import cv2
import numpy as np
# 将 Image 转换为 Mat,经过 flag 能够操控色彩
def pilImgToCv2(img: Image.Image, flag=cv2.COLOR_RGB2BGR):
return cv2.cvtColor(np.asarray(img), flag)
# 弹窗检查图片
def showImg(bg: cv2.Mat, name='test', delay=0):
cv2.imshow(name, bg)
cv2.waitKey(delay)
cv2.destroyAllWindows()
def getDistance(img: Image.Image, slice: Image.Image):
# 经过 pilImgToCv2 将图片置灰
# 背景图和滑块图都需求做相同处理
grayImg = pilImgToCv2(img, cv2.COLOR_BGR2GRAY)
# showImg(grayImg) # 能够经过它来看处理后的图片效果
graySlice = pilImgToCv2(slice, cv2.COLOR_BGR2GRAY)
# 做边缘检测进一步下降搅扰,阈值能够自行调整
grayImg = cv2.Canny(grayImg, 255, 255)
# showImg(grayImg) # 能够经过它来看处理后的图片效果
graySlice = cv2.Canny(graySlice, 255, 255)
# 经过模板匹配两张图片,找出缺口的方位
result = cv2.matchTemplate(grayImg, graySlice, cv2.TM_CCOEFF_NORMED)
maxLoc = cv2.minMaxLoc(result)[3]
# 匹配出来的滑动间隔
distance = maxLoc[0]
# 下面的逻辑是在图片画出一个矩形框来标记匹配到的方位,能够直观的看到匹配结果,去掉也能够的
sliceHeight, sliceWidth = graySlice.shape[:2]
# 左上角
x, y = maxLoc
# 右下角
x2, y2 = x sliceWidth, y sliceHeight
resultBg = pilImgToCv2(img, cv2.COLOR_RGB2BGR)
cv2.rectangle(resultBg, (x, y), (x2, y2), (0, 0, 255), 2)
# showImg(resultBg),能够经过它来看处理后的图片效果
print(distance)
return distance, resultBg
sliceimgpath = './slice.png'
imgpath = './缺口背景图片.png'
getDistance(Image.open(imgpath), Image.open(sliceimgpath))
B 计划
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()
img = cv2.imread("bg.jpg")
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):
"""
依据滑动间隔生成滑动轨道
:param distance: 需求滑动的间隔
:return: 滑动轨道<type 'list'>: [[x,y,t], ...]
x: 已滑动的横向间隔
y: 已滑动的纵向间隔, 除起点外, 均为0
t: 滑动进程消耗的时刻, 单位: 毫秒
"""
if not isinstance(distance, int) or distance < 0:
raise ValueError(f"distance类型有必要是大于等于0的整数: distance: {distance}, type: {type(distance)}")
# 初始化轨道列表
slide_track = [
[random.randint(-50, -10), random.randint(-50, -10), 0],
[0, 0, 0],
]
# 共记录count次滑块方位信息
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)
# y = round(__ease_out_expo(i / count) * 14)
# 滑动进程消耗的时刻
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
结果验证