Skip to content

极验三代一键通过

更新: 2025/2/24 字数: 0 字 时长: 0 分钟

作为一位js逆向爱好者,写本篇文章在于纯技术分析。无任何不良商业目的。旨在提高大家的网络安全意识,共同维护网络安全环境!请不要做任何有损国家或其他集体或个人的事情, 否者后果自负!本文如有任何侵权行为,请马上联系作者,立马删除。

本章简介

极验–全球交互安全创领者!拥有32万家使用客户!我们的这次分析选用官方给出的demo地址作为分析案例。极验提供的防御方式大概分一键通过、滑块拼图、文字点选三种,下面介绍一键通过。

难度:困难

地址:https://www.geetest.com/demo/

详细地址:https://www.geetest.com/demo/fullpage.html

20220628170149

?> 提示:先回看前面的极验三代滑块拼图,再看本章节会轻松不少。

流程分析

首先开启Chrome无痕模式进行抓包,重要的请求标注如下,我们可以把重要的请求分为两个部分:

  • 第一部分,生成按钮时发送的请求;
  • 第二部分,点击按钮时发送的请求;

20230724210453

?> 提示:这些请求都是GET请求,不存在POST请求。

!> 注意:在下面的分析中,可能某些请求的值发生改变,这是因为整个分析流程会比较长,期间会多次调试,因此这里的重点不是请求的值发生改变,而是如何逆向这些值的生成逻辑。

第一部分

地址:https://www.geetest.com/demo/gt/register-fullpage

作用:请求服务器获取验证参数和流水号(爬虫必须)。

请求参数:有一个 t 时间戳参数,请求时必须带上。

返回结果:返回了 challenge 参数、gt 参数。

20230724211048

地址:https://apiv6.geetest.com/gettype.php

作用:返回需要使用的JS资源,用于下一次请求(爬虫可省略)。

请求参数:gt 参数来源于上次请求的结果,callback 参数是 geetest_时间戳 固定格式。

返回结果:JSON格式的JS资源。

20230724211254

地址:https://static.geetest.com/static/js/fullpage.9.1.4.js

作用:请求需要使用的JS资源(爬虫可省略)。

请求参数:无重要参数。

返回结果:用于加密参数的JS代码。

20230724211924

地址:https://apiv6.geetest.com/get.php

作用:环境校验(爬虫必须)。

请求参数:gtchallenge 参数来源于第一次请求的结果,langptclient_typecallback 参数为固定格式,w 加密参数(不可省略,当前请求会验证该参数)。

返回结果:返回一些提示信息。

20230724212304

20230724212424

第二部分

地址:https://api.geetest.com/ajax.php

作用:点击验证成功(爬虫必须)。

请求参数:gtchallenge 参数来源于第二部分中的请求的结果,w 加密参数(不可省略,当前请求会验证该参数),其他参数为固定格式。

返回结果:返回验证成功信息。

20230724212937

20230724213047

分析总结

到这里我们就把一键通过完整的请求验证流程都走完了,可以看到相比于滑块的流程还是少了很多。**与滑块流程不同的是,这里验证了两次不同的w加密参数,而且这两次的w加密参数生成逻辑是不同的。**这里我们先把前期代码写出来:

python
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 加密值的生成逻辑:

20230724214642

刷新网页后,断点断住,我们往前追溯,在 $_BGGH 函数里面定位到了 w 值的生成逻辑,看起来是不是和滑块的 w 值生成逻辑很像,同样由两部分的值组成,代码如下:

20230724215226

javascript
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 值开始分析,将代码中的混淆变量进行还原:

javascript
r = t["$_CCHF"]()

现在进入赋值的 r["$_CCDf"] 函数当中,发现这和我们在滑块中最追溯的代码十分相近,通过深入的追溯,发现两者代码除了混淆变量名不同之外,其功能都是相同的,于是我们可以使用前面的代码还原:

20230724220136

python
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值,可以看到这里也和我们之前逆向的滑块代码很像,这里我们直接在控制台输出:

javascript
o = $_BFZ()[$_CEIHW(1372)](ge[$_CEIHW(417)](t[$_CEIIU(321)]), t[$_CEIHW(1361)]())

首先,看 t[$_CEIHW(1361)]() 函数的返回值,就是上面代码中返回的 message 值。

20230724222130

接着,看 ge[$_CEIHW(417)](t[$_CEIIU(321)]) 函数的返回值,经过测试,发现该参数只有 gt 参数和 challenge 参数会被验证,其他参数都可以为固定值:

20230724222259

'{"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)] 函数,进入后发现和滑块中的加密函数代码相似,经过测试这里可以直接复用滑块中的加密函数代码:

20230724223247

由于该函数过长这里就不展示代码了,反正扣下来后,能就返回加密值了,也就是 o 的值。

逆向i值

继续逆向i值,可以看到形式和滑块的代码一样,预测逻辑也可能一样:

javascript
i = p[$_CEIIU(1303)](o)

进入 p[$_CEIIU(1303)] 函数中,我们能看到这里和逆向滑块的过程一样,也是函数内部调用外部函数,整个就是一个大的对象,经过测试,我们这里同样可以复用之前扣的对象代码:

20230724223841

对象代码太长了,这里就不展示了。到此,我们将生成的i值和生成r值结合,就能得到我们需要的第一个w值了。

第二个w值

首先,我们先逆向第二部分请求中所使用到的 w 加密值。我们选择 Initiator 选项卡,从第一个请求开始往前追溯 w 加密值的生成逻辑:

20230724224638

经过追溯,我们在 $_CEBe 函数中定位到了 w 值的生成,其来源于 t[$_CFICl(1481)] 中的值,继续往上追溯,发现在执行 t[$_CFICl(1488)]() 函数后,其 t[$_CFICl(1481)] 才会被赋值:

20230724224948

我们进入到 t[$_CFICl(1488)]() 函数中,可以看到是一个大段的函数:

20230724225628

我们直接看该函数的末尾,发现函数里面有一个自执行函数,那我们看自执行函数的最后一行,发现其结果就是我们需要的 w 值,其中 i[$_CGBDY(1361)]() 函数的返回值上面代码中返回的 message 值,然后看 r 值是个一大段字符串,但经过测试,屁都不验证,因此这里可以直接给一个 '{}' 空的对象字符串:

javascript
i[$_CGBCi(1481)] = p[$_CGBDY(1303)](_[$_CGBDY(12)](r, i[$_CGBDY(1361)]()));

20230724230626

继续跟进 _[$_CGBDY(12)]() 函数,进入后发现和上面一样是加密函数,效果功能都一样,可以直接复用之前的代码:

20230724231442

最后看 p[$_CGBDY(1303)]() 函数,进入后发现这就是上面逆向i值的代码,所以直接复用代码就可以了:

20230724223841

到此,我们逆向完了第二个w值了。

效果展示

20230724233416