
极验三代一键通过
更新: 2025/2/24 字数: 0 字 时长: 0 分钟
作为一位js逆向爱好者,写本篇文章在于纯技术分析。无任何不良商业目的。旨在提高大家的网络安全意识,共同维护网络安全环境!请不要做任何有损国家或其他集体或个人的事情, 否者后果自负!本文如有任何侵权行为,请马上联系作者,立马删除。
本章简介
极验–全球交互安全创领者!拥有32万家使用客户!我们的这次分析选用官方给出的demo地址作为分析案例。极验提供的防御方式大概分一键通过、滑块拼图、文字点选三种,下面介绍一键通过。
难度:困难
地址:https://www.geetest.com/demo/
详细地址:https://www.geetest.com/demo/fullpage.html
?> 提示:先回看前面的极验三代滑块拼图,再看本章节会轻松不少。
流程分析
首先开启Chrome无痕模式进行抓包,重要的请求标注如下,我们可以把重要的请求分为两个部分:
- 第一部分,生成按钮时发送的请求;
- 第二部分,点击按钮时发送的请求;
?> 提示:这些请求都是GET请求,不存在POST请求。
!> 注意:在下面的分析中,可能某些请求的值发生改变,这是因为整个分析流程会比较长,期间会多次调试,因此这里的重点不是请求的值发生改变,而是如何逆向这些值的生成逻辑。
第一部分
地址:https://www.geetest.com/demo/gt/register-fullpage
作用:请求服务器获取验证参数和流水号(爬虫必须)。
请求参数:有一个 t
时间戳参数,请求时必须带上。
返回结果:返回了 challenge
参数、gt
参数。
地址:https://apiv6.geetest.com/gettype.php
作用:返回需要使用的JS资源,用于下一次请求(爬虫可省略)。
请求参数:gt
参数来源于上次请求的结果,callback
参数是 geetest_时间戳
固定格式。
返回结果:JSON格式的JS资源。
地址:https://static.geetest.com/static/js/fullpage.9.1.4.js
作用:请求需要使用的JS资源(爬虫可省略)。
请求参数:无重要参数。
返回结果:用于加密参数的JS代码。
地址:https://apiv6.geetest.com/get.php
作用:环境校验(爬虫必须)。
请求参数:gt
、challenge
参数来源于第一次请求的结果,lang
、pt
、client_type
、callback
参数为固定格式,w
加密参数(不可省略,当前请求会验证该参数)。
返回结果:返回一些提示信息。
第二部分
地址:https://api.geetest.com/ajax.php
作用:点击验证成功(爬虫必须)。
请求参数:gt
、challenge
参数来源于第二部分中的请求的结果,w
加密参数(不可省略,当前请求会验证该参数),其他参数为固定格式。
返回结果:返回验证成功信息。
分析总结
到这里我们就把一键通过完整的请求验证流程都走完了,可以看到相比于滑块的流程还是少了很多。**与滑块流程不同的是,这里验证了两次不同的w加密参数,而且这两次的w加密参数生成逻辑是不同的。**这里我们先把前期代码写出来:
import time
import requests
# 建立会话
session = requests.Session()
session.headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36'}
'''
第一部分
'''
# 第一次请求,获取challenge、gt
first_url = f'https://www.geetest.com/demo/gt/register-fullpage?t={int(time.time() * 1000)}'
first_res = session.get(url=first_url).json()
逆向分析
第一个w值
首先,我们先逆向第一部分中的环境校验时,所使用到的 w
加密值。我们选择 Initiator
选项卡,从第一个请求开始往前追溯 w
加密值的生成逻辑:
刷新网页后,断点断住,我们往前追溯,在 $_BGGH
函数里面定位到了 w
值的生成逻辑,看起来是不是和滑块的 w
值生成逻辑很像,同样由两部分的值组成,代码如下:
r = t[$_CEIIU(1300)]()
o = $_BFZ()[$_CEIHW(1372)](ge[$_CEIHW(417)](t[$_CEIIU(321)]), t[$_CEIHW(1361)]())
i = p[$_CEIIU(1303)](o)
w = i + r
逆向r值
根据代码,我们从 r
值开始分析,将代码中的混淆变量进行还原:
r = t["$_CCHF"]()
现在进入赋值的 r["$_CCDf"]
函数当中,发现这和我们在滑块中最追溯的代码十分相近,通过深入的追溯,发现两者代码除了混淆变量名不同之外,其功能都是相同的,于是我们可以使用前面的代码还原:
import binascii
import execjs
import rsa
js_code = '''
function rt() {
var data = '';
for (var i = 0; i < 4; i++) {
data = data + (65536 * (1 + Math['random']()) | 0)['toString'](16)['substring'](1);
}
return data
}
'''
# 逆向r值
def js_r(message):
# 十六进制指数(固定值)
rsaExponent = "10001"
rsaExponent = int(rsaExponent, 16) # 十六进制转十进制
# 十六进制模数(固定值)
rsaModulus = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"
rsaModulus = int(rsaModulus, 16) # 十六进制转十进制
# 依据模数和指数生成公钥
public_key = rsa.PublicKey(rsaModulus, rsaExponent)
# 加密字符串返回二进制流数据
crypto = rsa.encrypt(message.encode('utf8'), public_key)
# 将二进制流数据编码为十六进制字符串
r = binascii.b2a_hex(crypto).decode()
return r
message = execjs.compile(js_code).call('rt')
r_value = js_r(message)
逆向o值
接下来我们逆向o值,可以看到这里也和我们之前逆向的滑块代码很像,这里我们直接在控制台输出:
o = $_BFZ()[$_CEIHW(1372)](ge[$_CEIHW(417)](t[$_CEIIU(321)]), t[$_CEIHW(1361)]())
首先,看 t[$_CEIHW(1361)]()
函数的返回值,就是上面代码中返回的 message
值。
接着,看 ge[$_CEIHW(417)](t[$_CEIIU(321)])
函数的返回值,经过测试,发现该参数只有 gt
参数和 challenge
参数会被验证,其他参数都可以为固定值:
'{"gt":"第一次请求返回的gt参数","challenge":"第一次请求返回的challenge参数","offline":false,"new_captcha":true,"product":"float","width":"300px","https":true,"api_server":"apiv6.geetest.com","protocol":"https://","type":"fullpage","static_servers":["static.geetest.com/","dn-staticdown.qbox.me/"],"beeline":"/static/js/beeline.1.0.1.js","voice":"/static/js/voice.1.2.3.js","click":"/static/js/click.3.0.9.js","fullpage":"/static/js/fullpage.9.1.4.js","slide":"/static/js/slide.7.9.0.js","geetest":"/static/js/geetest.6.0.9.js","aspect_radio":{"slide":103,"click":128,"voice":128,"beeline":50},"cc":8,"ww":true,"i":"6126!!7414!!CSS1Compat!!1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!2!!3!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!-1!!1!!-1!!-1!!-1!!-6!!0!!0!!0!!136!!643!!1421!!1407!!zh-CN!!zh-CN!!-1!!2!!24!!Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36!!1!!1!!2560!!1440!!2560!!1400!!1!!1!!1!!-1!!Win32!!0!!-8!!5240a84972f079f14d4a55c40f5cc113!!0!!internal-pdf-viewer,internal-pdf-viewer,internal-pdf-viewer,internal-pdf-viewer,internal-pdf-viewer!!0!!-1!!0!!8!!Arial,ArialBlack,ArialNarrow,Calibri,Cambria,CambriaMath,ComicSansMS,Consolas,Courier,CourierNew,Georgia,Helvetica,Impact,LucidaConsole,LucidaSansUnicode,MicrosoftSansSerif,MSGothic,MSPGothic,MSSansSerif,MSSerif,PalatinoLinotype,SegoePrint,SegoeScript,SegoeUI,SegoeUILight,SegoeUISemibold,SegoeUISymbol,Tahoma,Times,TimesNewRoman,TrebuchetMS,Verdana,Wingdings!!1690206639235!!-1!!-1!!-1!!12!!-1!!-1!!-1!!5!!-1"}'
最后,我们来看 $_BFZ()[$_CEIHW(1372)]
函数,进入后发现和滑块中的加密函数代码相似,经过测试这里可以直接复用滑块中的加密函数代码:
由于该函数过长这里就不展示代码了,反正扣下来后,能就返回加密值了,也就是 o
的值。
逆向i值
继续逆向i值,可以看到形式和滑块的代码一样,预测逻辑也可能一样:
i = p[$_CEIIU(1303)](o)
进入 p[$_CEIIU(1303)]
函数中,我们能看到这里和逆向滑块的过程一样,也是函数内部调用外部函数,整个就是一个大的对象,经过测试,我们这里同样可以复用之前扣的对象代码:
对象代码太长了,这里就不展示了。到此,我们将生成的i值和生成r值结合,就能得到我们需要的第一个w值了。
第二个w值
首先,我们先逆向第二部分请求中所使用到的 w
加密值。我们选择 Initiator
选项卡,从第一个请求开始往前追溯 w
加密值的生成逻辑:
经过追溯,我们在 $_CEBe
函数中定位到了 w
值的生成,其来源于 t[$_CFICl(1481)]
中的值,继续往上追溯,发现在执行 t[$_CFICl(1488)]()
函数后,其 t[$_CFICl(1481)]
才会被赋值:
我们进入到 t[$_CFICl(1488)]()
函数中,可以看到是一个大段的函数:
我们直接看该函数的末尾,发现函数里面有一个自执行函数,那我们看自执行函数的最后一行,发现其结果就是我们需要的 w
值,其中 i[$_CGBDY(1361)]()
函数的返回值上面代码中返回的 message
值,然后看 r
值是个一大段字符串,但经过测试,屁都不验证,因此这里可以直接给一个 '{}'
空的对象字符串:
i[$_CGBCi(1481)] = p[$_CGBDY(1303)](_[$_CGBDY(12)](r, i[$_CGBDY(1361)]()));
继续跟进 _[$_CGBDY(12)]()
函数,进入后发现和上面一样是加密函数,效果功能都一样,可以直接复用之前的代码:
最后看 p[$_CGBDY(1303)]()
函数,进入后发现这就是上面逆向i值的代码,所以直接复用代码就可以了:
到此,我们逆向完了第二个w值了。