本文仅供学习交流使用,请勿用于商业用途或不正当行为
如果侵犯到贵公司的隐私或权益,请联系我立即删除
搞爬虫的没有不知道极验的吧,看下官网的介绍
卧槽,牛逼!
嗯,这就是读书少的缘故吧,除了卧槽牛逼,其他也不会讲了…
不说了,直接看干货吧
极验的关键JS文件有两个:fullpage.8.9.5.js和slide.7.7.2.js(貌似这是最新的版本?)
前一篇文章JS逆向:AST还原极验混淆JS实战使用AST还原了fullpage.8.9.5.js,另外的slide.7.7.2.js文件还原方法与fullpage.8.9.5.js一样的,还原代码基本上是通用的,这里就不多说了。
极验的验证码背景图片分两张,一张带滑块缺口的图片,一张不带滑块的完整图片。
两张都被分割成52分,上下两部分各26分,然后乱序排列,通过css将乱序的图片重组起来,显示在网页里
插播一下:我写文章一般是边分析边记录,本来是准备搞B站的,结果搞一半,B站给换了极验的文字点选验证码,雷佳音版:我尼玛!!!
刚开始还以为是B站是分时间段展示不同的验证码,结果两天过去了,发现的确给换了文字点选,小伙伴给了个其他使用极验的站,所以接下来的截图会跟之前的不一样,但都是极验的将就看吧。
因为验证码图片是乱序的,所以首选需找到图片还原的算法,对乱序图片还原后才能进行缺口位置识别
通过观察能够看到,图片是用canvas画出来的,如下
canvas绘图前肯定需要先对图片还原再进行绘图,这时候可以使用油候脚本在canvas绘图前HOOK住,定位到对应的canvas代码位置,再进行分析,脚本如下:
// hook canvas
(function() {
'use strict';
let create_element = document.createElement.bind(doument);
document.createElement = function (_element) {
console.log("create_element:",_element);
if (_element === "canvas") {
debugger;
}
return create_element(_element);
}
})();
添加HOOK脚本后,点击【获取验证码】按钮触发极验滑块验证码,脚本就会在canvas调用时暂停。
由于网页内除了验证码图片外还有其他图片也使用了canvas绘制,所以脚本会Hook住每一个canvas调用,可以使用F8跳过其他图片,一直跳过到页面快要显示出验证码图片时
这里大概跳过了11个拦截的canvas吧,开始单步进入到以下位置
图片还原的算法就这里了,for循环里的常量SEQUENCE在这里
按照上述算法编写python代码,然后就可以拿到还原后的图片了
这种识别比较简单,思路是按列遍历两张图片的每个像素点位置,比较两张图片素点的RGB值,对比出来的第一个不同的像素点即为滑块位置
def is_px_equal(self, img1, img2, x, y):
"""
判断两个像素是否相同
:param img1: 图片1
:param img2:图片2
:param x:位置1
:param y:位置2
:return:像素是否相同
"""
pix1 = img1.load()[x, y]
pix2 = img2.load()[x, y]
threshold = 60
if abs(pix1[0] - pix2[0]) < threshold and abs(pix1[1] -pix2[1]) < threshold and abs(pix1[2] - pix2[2]) < threshold:
return True
else:
return False
def get_gap(self, img1, img2):
"""
获取缺口偏移量
:param img1: 不带缺口图片
:param img2: 带缺口图片
:return:
"""
left = 0
for row in range(left, img1.size[0]):
for col in range(img1.size[1]):
if not self.is_px_equal(img1, img2, row, col):
left = row
return left
return left
load() 方法可以获取[x,y]位置的RGB值size() 方法的返回值为图片宽度和高度值
在提交验证的请求里跟踪调用栈,在$_DADP找到加密的关键位置
var i = {
"lang": o["lang"] || "zh-cn",
"userresponse": $_CFn(t, o["challenge"]),
"passtime": n,
"imgload": r["$_CJFt"],
"aa": e,
"ep": r["$_DAEP"]()
};
t的值是传进来的,o[“challenge”]是获取图片信息请求返回的challenge
函数$_CFn(),没啥好说的直接扣
passtime的值是n,n是传进来的
aa的值为e,e也是传进来的
ep的值是$_DAEP的返回结果
te的值为false,me的值为true,可以是固定的
$_FHx()函数经过AST还原后为空函数
$_CCDO调用的是window.performance.timing里的属性值
返回结果为
i["rp"] = $_DDF(o["gt"] + o["challenge"]["slice"](0, 32)+ i["passtime"]);
这里传入了前面的几个值到$_DDF里进行了计算
$_DDF也是整个直接扣
到现在还是不清楚传进来的t、e,、n分别是什么,必须先搞清楚才能进行加密
跟踪到上一步,$_DADP的调用处,看下传入的参数都是什么
注:为了便于调试同时能看起来清晰点,所以改成了以下这样
t1 = n["$_DDFQ"]["$_BCBz"](),
t2 = n["$_IFM"]["c"],
t3 = n["$_IFM"]["s"],
l = n["$_DDFQ"]["$_BHBs"](t1, t2, t3);
c是拖动滑块时在x轴的最后一个点,也就是滑块缺口的距离n["$_CGJL"] 其实是拖动滑块的总耗时
接下来看t1,先看一下n["$_DDFQ"]
跟进$_BCBz
这个$_BCJN就是滑动轨迹数据,最后通过
r["join"]("") + "!!" + o["join"]("") + "!!" + i["join"("");
分隔拼接后返回结果
t2和t3是请求图片信息时返回的c和s值
接着将t1、t2、t3传入$_BHBs进行处理得到l的值
搞清楚了传入的t、e,、n后,接下看这一段
var s = r["$_DAFB"](),
a = AES["encrypt"](gjson["stringify"](i), r["$_DAGa"()),
_ = Base64["$_BCCY"](a),
u = {
"gt": o["gt"],
"challenge": o["challenge"],
"lang": i["lang"],
"pt": r["$_CDHf"],
"w": _ + s
};
先看:
s = r["$_DAFB"]()
$_DAFB里面是进行了RSA加密
这是RSA的秘钥
代码太长了显示不全
this['setPublic']('00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81','10001');
RSA传入的值为$_DAGa的返回值
看下$_DJj
简单说就是通过$_DJj生成一个随机字符作为RSA的key,然后进行RSA加密,得到的值给s
再分析:
a = AES["encrypt"](gjson["stringify"](i), r["$_DAGa"]())
stringify是将对象转换为字符串AES[“encrypt”] 传入了两个值:i 和 $_DAGa的返回值(刚才分析过)AES的秘钥为"0000000000000000"
_ = Base64["$_BCCY"](a)
最后将a的值进行base64编码,这里不多说也是整体扣下来
至此极验滑块的加密已经分析完了,只需将生成的轨迹数据用上述加密方法进行加密,然后发送给服务端校验,至于生成轨迹的思路,网上一搜一大把。
最后测试了一下貌似有80%-90%的成功率,感觉还行~
极验滑块的破解难度相对还是稍微高一些,需要的就是耐心。
这一篇写的没有之前那么全面,但写了一些主要关键点。由于极验的复杂度稍高,写起来也要花一些时间,最近忙着找工作,精力有限,就这样吧。
另外,本文使用的代码为个人学习成果,暂不对外公开,请小伙伴们理解
再次声明,本文仅为个人学习记录分享,请勿用于商业用途或者不正当行为。