极验滑动验证最新破解实践(18年1月底)
零、最新更新
最近,有知友问道滑动验证最新效果,我于是又把一个月前的代码试着运行了一下,发现代码失效不可用了。为了不辜负各位老铁的支持,我又尝试着破解,最终效果还好。
本次极验针对前端的升级在于canvas图片合并生成了,canvas_bg与canvas_fullbg合并在一起显示了,造成难以简单快速计算滑块的目的位置了。这里使用OpenCV的图像模板匹配能识别滑块在canvas背景的准确位置。
为了避免滑块的耳朵对匹配结果的影响,我们对滑块裁剪,仅保留最中心小片区域,已经足够我们的匹配结果了。
该部分代码如下:
def pictures_recover(self):
#img1 = Image.open('snapshot1.png')
#img2 = Image.open('snapshot2.png')
# xpos = self.judge(img1, img2)
imsrc = cv2.imread('snapshot1.png')
imobj = cv2.imread('snapshot2.png')
xpos = -1
ypos = -1
for xrow in range(0,160):
for ycol in range(0,260):
point_rgb = imobj[xrow][ycol]
if(point_rgb[0] < 110 and point_rgb[1] < 110 and point_rgb[2] < 110):
ypos = xrow
break
if ypos != -1:
break
for ycol in range(0, 260):
for xrow in range(0, 160):
point_rgb = imobj[xrow][ycol]
if (point_rgb[0] < 110 and point_rgb[1] < 110 and point_rgb[2] < 110):
xpos = ycol
break
if xpos != -1:
break
img2 = Image.open('snapshot2.png')
# 为避免耳朵的识别影响,均切割去除,仅保留中心位置区域
region = img2.crop((xpos + 14, ypos + 14, xpos + 28, ypos + 26))
region.save("./snapshot2_cut.png")
# find the match position
imobj_cut = cv2.imread('snapshot2_cut.png')
result = cv2.matchTemplate(imsrc, imobj_cut, cv2.TM_CCORR_NORMED)
(minVal, maxVal, minLoc, maxLoc) = cv2.minMaxLoc(result)
xtarget_pos = maxLoc[0] - 12
return self.darbra_track(xtarget_pos)
匹配获得了图片位置后,一切就和从前一样了哦。不过,尝试了下,识别率较以前下降了一些,看了极验的轨迹识别又有了提升,但该破解基本不影响我们学习研究和正常使用了。
一、概览
极验验证是一款优秀的验证工具,在知乎上已经有多个关于极验滑动验证破解的文章介绍破解的思路和实践。主要有两种思路:一是追踪前端请求参数,再逆向工程分析参数的js计算过程,模拟轨迹及其计算过程获取请求参数,已此实现破解;二是直接利用selenium调用浏览器或phantomjs,模拟鼠标轨迹过程,实现破解。第一种技术难度更高,破解后访问效率更高,但受极验js代码频繁升级的影响很大;第二种实现难度相对低些,访问效率相对低些,但一旦破解,如果极验不升级轨迹的智能检测程度,则基本可长期使用。
极验的更新升级速度很快,请求参数又再次加密封装在仅仅一个w值中,加密函数又多层混淆迭代,很多多层嵌套函数也难以使用python改编,即使使用PyV8也很难将纯粹的加密算法剥离,破解难度相对5.x和6.0大了很多;页面拼图也以修改为canvas绘图,难以从乱排图片中直接还原。目前,知乎上的破解文章已基本失效。本文结合前面的破解思路,实践了一种基于selenium调用浏览器或phantomjs实现的破解。
选取极验官方示例页面作为实践网址,
http://www.geetest.com/demo/slide-bind.html。
二、验证图片获取
由于极验采用canvas绘图,难以通过乱排原始图片还原回界面绘图。考虑使用selenium的网页截图功能和js加载功能,通过调整fullbg、slice和bg的display属性,分别截取网页,然后再将验证图片区域截取,保存即可直接得到还原的验证图片。接下来的图片计算过程与之前的文章破解思路一致。
def capture_pic(self):
# 截图
self.br.execute_script(
'var x = document.getElementsByClassName("geetest_canvas_slice");x[0].style.display="none";var y = document.getElementsByClassName("geetest_canvas_fullbg");y[0].style.display="none";')
time.sleep(0.8)
self.br.get_screenshot_as_file(self.filename1)
left = 387
top = 155
right = left + 260
bottom = top + 160
im = Image.open(self.filename1)
im = im.crop((left, top, right, bottom))
im.save(self.filename1)
self.br.execute_script(
'var x = document.getElementsByClassName("geetest_canvas_bg");x[0].style.display="none";var y = document.getElementsByClassName("geetest_canvas_fullbg");y[0].style.display="block";')
time.sleep(0.8)
self.br.get_screenshot_as_file(self.filename2)
im = Image.open(self.filename2)
im = im.crop((left, top, right, bottom))
im.save(self.filename2)
self.br.execute_script(
'var x = document.getElementsByClassName("geetest_canvas_bg");x[0].style.display="block";var y = document.getElementsByClassName("geetest_canvas_slice");y[0].style.display="block";')
time.sleep(0.8)
三、模拟鼠标轨迹
鼠标轨迹用一组数组表示,数组里每个小数组代表每一个微小滑动的数据:[滑动距离, 上下偏移量, 累计时间]
滑动距离采用了一个sigmoid函数,并做了优化。一般初段(<5像素点)在鼠标左键按下后70-140ms内至少拖出1个像素点;中段7-9ms采样一次,每次3-5像素点;尾端3-5像素点时每次移动1-2像素点耗时16ms以上;最后到达位置后会停留200-400ms再释放。滑动距离一般小于200(长度260减去slice宽度44),经过计算,以下系数能得到较好的距离值,track为slice位置值(50-200之间)
self.magic_x = [(10/20.0)*i for i in range(-20,21)]
self.magic_y = [int(track / (1 + math.exp(-0.4 * i))) for i in self.magic_x]
以100为例,
>>> magic_y
[1, 2, 2, 3, 3, 4, 5, 6, 8, 9, 11, 14, 16, 19, 23, 26, 31, 35, 40, 45, 50, 54, 59, 64, 68, 73, 76, 80, 83, 85, 88, 90, 91, 93, 94, 95, 96, 96, 97, 97, 98]
累计时间比对了大量手动轨迹的特征,在滑动距离sigmoid基础上再叠加随机值;
上下偏移量在[-3,+3]区间少量一致性连续偏移,即从0到-1,保持-1或再到-2;而不会从0到-1,再从-1到0,再从0到-1;尽可能考虑人手鼠标偏移趋势。
x = 0
y = 0
deltay = 0
t = 0
flag = True
count = 0
while flag and count < len(magic_y):
x = magic_y[count]
count = count + 1
if x <= 3:
y = 0
delta = random.randint(60, 140)
t = t + delta
elif x < track - 6:
change = random.randint(0, 100)
deltay = 0
if change > 90:
if abs(y) < 2:
if flag > 0:
deltay = -1
y = y + deltay
else:
deltay = 1
y = y + deltay
delta = random.randint(7, 9)
t = t + delta
elif x < track:
delta = random.randint(14, 16)
t = t + delta
else:
y = y
delta = random.randint(200, 400)
t = t + delta
flag = False
print str(x) + ', ' + str(y) + ', ' + str(t) + '.'
鼠标移动代码,
element = self.br.find_element_by_class_name("geetest_slider_button")
elementStatic = self.br.find_element_by_class_name("geetest_canvas_img")
ActionChains(self.br).click_and_hold(on_element=element).perform()
while flag and count < len(magic_y):
ActionChains(self.br).move_to_element_with_offset(
to_element=elementStatic,
xoffset=x + 22,
yoffset=deltay + 195).perform()
#ActionChains(self.br).click_and_hold(on_element=element).perform()
time.sleep(delta/1000.0)
ActionChains(self.br).release(on_element=element).perform()
四、实践结果
经过尝试,20次成功19次,仅1次吃掉,普遍成功率在90%以上。关键思路仍然是参考之前已有文章,再此基础上优化完善。本文主要是用于学习研究,不用做任何商业用途,完整源码也就不贴了。
五、参考文章
1、极验滑动验证码6.0破解复盘 (https://zhuanlan.zhihu.com/p/30878884)
2、极验验证可以被破解吗?
(https://www.zhihu.com/question/28833985/answer/146830451)
3、极验验证码破解—超详细教程(https://zhuanlan.zhihu.com/p/28492887)
4、自动查企业工商登记信息(企业信用信息公示系统、极验Geetest与Python爬虫)