使用Ruby逆向解析极验四代滑块验证码

一、极验请求分析
1. 滑块测试网站入口
极验验证码测试入口

2. 滑块验证过程抓包
与极验三代滑块验证码相比,极验四代简化了验证过程,加密参数w的生成也变简单了。和极验三代验证码一样的分析流程,继续撸就完了。

3. 请求详解
3.1. adaptive-captcha-demo
请求介绍
极验第四代验证码测试主页,主要是获取下个请求中的URL(这个URL是动态变化的,所以这个步骤必须要)。

请求响应
响应为HTML文档,通过正则匹配下个步骤的请求URL
正则表达式:href="(.*?adaptive-captcha-demo\.js)"

3.2. adaptive-captcha-demo.js
请求介绍
获取w参数加密需要的参数captchaId

请求参数
实际破解滑块过程中,此请求可忽略。

请求响应
响应为JS文件,通过正则匹配captchaId参数的值
正则表达式:captchaId:"([0-9a-z]+)"

3.3. load
请求介绍
获取验证码信息,包括:验证码类型、验证码背景图、验证码滑块图、lot_number参数、静态文件URL等。

请求参数

ruby

{
  "captcha_id": "24f56dc13c40dc4a02fd0318567caef5",    # 上个请求中获取
  "challenge": "f8beca82-84a4-4b32-a01d-dae1697f1236",  # 由JS代码生成,下面会详细讲解生成过程
  "client_type": "web",                                 # 固定值
  "risk_type": "slide",                                 # 验证码类型
  "lang": "zh",                                         # 固定值
  "callback": "geetest_1641878914316"                   # 当前时间戳
}
请求响应
响应中的c、s在后续无感验证生成w参数时需要使用,其中c为定值,s为变化值。

json

{
  "status": "success",
  "data": {
    "lot_number": "c574cd8c30a541b28597fb4582542c61",
    "captcha_type": "slide",
    "js": "/js/gcaptcha4.js",
    "css": "/css/gcaptcha4.css",
    "static_path": "/v4/static/v1.4.4",
    "slice": "pictures/v4_pic/slide_2021_07_14/Group81/slide/019d7acaf9aa4f488a332b6baff7176b.png",
    "bg": "pictures/v4_pic/slide_2021_07_14/Group81/bg/019d7acaf9aa4f488a332b6baff7176b.png",
    "ypos": 116,
    "gct_path": "/v4/gct/gct4.5258a91d0f5f0bb73c65d4d18d48d93f.js",
    "arrow": "arrow_1",
    "show_voice": false,
    "feedback": "https://www.geetest.com/Helper",
    "logo": true,
    "pt": "1",
    "captcha_mode": "risk_manage",
    "language": "zh",
    "custom_theme": {
      "_style": "stereoscopic",
      "_color": "hsla(224,98%,66%,1)",
      "_gradient": "linear-gradient(180deg, hsla(224,98%,71%,1) 0%, hsla(224,98%,66%,1) 100%)",
      "_hover": "linear-gradient(180deg, hsla(224,98%,66%,1) 0%, hsla(224,98%,71%,1) 100%)",
      "_brightness": "system",
      "_radius": "4px"
    }
  }
}
3.4. verify
请求介绍
该请求是极验验证请求,gcaptcha4.js收集滑动轨迹,与上个请求中的lot_number参数,加密生成w参数。

请求参数

ruby

{
  "captcha_id": "24f56dc13c40dc4a02fd0318567caef5",      # 与上个请求中的captcha_id参数相同
  "challenge": "e29f82f7-78db-42de-913f-fb1b01d3e30b",   # 与上个请求中的challenge参数相同
  "client_type": "web",                                  # 固定值
  "lot_number": "c574cd8c30a541b28597
fb4582542c61", # 上个请求的响应中lot_number参数值
"risk_type": "slide", # 验证码类型
"pt": 1, # 上个请求的响应中pt参数值
"w": "c742e66584e3b20ad523c2ddff...", # gcaptcha4.js收集滑动轨迹加密生成
"callback": "geetest_1641878916958" # 当前时间戳
}

swift
 

**请求响应**  
验证成功,拿到`seccode`。
```json
{
  "status": "success",
  "data": {
    "lot_number": "5b79a07bfb1640c1955ef28fbe28bef0",
    "result": "success",
    "fail_count": 0,
    "seccode": {
      "lot_number": "5b79a07bfb1640c1955ef28fbe28bef0",
      "pass_token": "f5b3b3d7664e032bc06730d56f83433046af98878bfa796d5e4b5b5f48904e40",
      "gen_time": "1641880037",
      "captcha_output": "2W2T6RrNJ8qVlCuIQxrHVp0imaZt_LrywRPCvYEbTHwQyoZwHIYvpYM5zF0-qSl8LQF_m8ggUDGiA0b8IDdrjji1YjjbEERRWAP9SxWj-G090QRaou4m8NnZL0NVmBie"
    },
    "score": "1"
  }
}
二、JS破解前准备工作
1. gcaptcha4.js反混淆
通过AST语法树将混淆的gcaptcha4.js文件还原,具体还原方式请看 极验第四代滑块验证码破解(一):AST还原混淆JS。

2. 找到w参数
三、load请求中challenge参数破解
经过测试,下面有两种方式生成challenge。

1. 通过Python的uuid库代码直接生成
python

import uuid
challenge = uuid.uuid1().__str__()
2. 通过构造challenge的函数生成
第一步:找到构造challenge的函数入口。

第二步:刷新网页,进入断点分析,找到构造challenge的函数入口。

第三步:进入uuid函数,这个函数特别简单,直接改成Python。

python

import random

def uuid():
    def __random(c):
        r = int(random.random() * 16)
        v = r if c == 'x' else (r & 0x3 | 0x8)
        return hex(v)[2:]

    string = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
    ret = ''
    for i in string:
        if i in 'xy':
            i = __random(i)
        ret += i
    return ret
四、w参数破解
1. w参数分析
滑动滑块,触发debugger,从w生成位置开始往上一步一步分析,看看生成w参数需要哪些关键函数。

2. e 对象参数详情
通过对w参数的分析,可以得出:w参数=加密函数(序列化函数(e对象))。

下面是e对象中各参数的详解:

json

{
  "setLeft": 99,                         # 滑块滑动距离
  "track": [[38, 15, 0], ... ],          # 滑块滑动轨迹
  "passtime": 146,                       # 滑块滑动过程时长
  "userresponse": 98.41476022585691,     # 通过滑块滑动距离计算得到
  "lot_number": "4b4ef3e583444e0fb...",  # load响应中的lot_number参数
  "device_id": "A59C",                   # 可为定值,可省略此参数
  "geetest": "captcha",                  # 可为定值,可省略此参数
  "lang": "zh",                          # 可为定值,可省略此参数
  "ep": "123",                           # 可为定值,可省略此参数
  "nz8c": "255401529",                   # 可为定值,可省略此参数
  "em": {"ph": 0, ...}                   # 可为定值,可省略此参数
}
3. 序列化函数:d["default"]["stringify"]
断点进入d["default"]["stringify"]函数,发现此函数中有外部函数调用,所以将外部函数整个抠出来。

4. 加密函数:h["default"]
断点进入h["default"]函数,这里需要注意:此函数中对第二个入参中的值进行了很多判断,实际没有进行运算,这里我们修改下函数,直接绕过判断。

5. e 对象参数破解
e对象作为入参,因此需要通过调用栈,跳转到上层函数调用,分析e对象中每个参数的构造函数。

5.1. 参数破解:track
滑块轨迹构造请参考:极验第四代滑块验证码破解(三):滑块轨迹构造。

5.2. 参数破解:passtime
滑动消耗时长直接从滑动轨迹中累加。

ruby

def get_passtime(track)
  passtime = 0
  track.each { |t| passtime += t[2] }
  passtime
end
5.3. 参数破解:setLeft
滑动距离从滑动轨迹中累加。

ruby

def get_setLeft(track)
  setLeft = 0
  track[1..-1].each { |t| setLeft += t[0] }
  setLeft
end
5.4. 参数破解:userresponse
从上文知道 userresponse = setLeft / t["_BGBb"],那就分析一下t["_BGBb"]函数。

ruby

def get_userresponse(setLeft, captcha_width)
  e = 340
  i = 0.8876 * e / captcha_width
  setLeft / i
end
五、w参数破解完整代码
感谢小伙伴们看了这么久,直接上干货吧。w参数破解的全量代码奉上,其中只有3个函数需要大家抠出来(原封不动地抠出来就行,太长了没法粘贴进去)。

ruby

require 'json'

class GeetestCracker
  def initialize
    @navigator = { "appName" => "Netscape" }
  end

  def randoms(min, max)
    rand(min..max)
  end

  def getRandomValues(buf)
    min = 0
    max = 255
    raise 'QuotaExceededError' if buf.size > 65536

    buf.each_with_index do |_, index|
      buf[index] = randoms(min, max)
    end
    buf
  end

  def get_passtime(track)
    passtime = 0
    track.each { |t| passtime += t[2] }
    passtime
  end

  def get_setLeft(track)
    setLeft = 0
    track[1..-1].each { |t| setLeft += t[0] }
    setLeft
  end

  def get_userresponse(setLeft, captcha_width)
    e = 340
    i = 0.8876 * e / captcha_width
    setLeft / i
  end

  def get_w(track, captcha_width, lot_number)
    d = { "default" => method(:stringify) }

    h = { "default" => method(:encrypt) }

    passtime = get_passtime(track)
    setLeft = get_setLeft(track)
    userresponse = get_userresponse(setLeft, captcha_width)

    e = {
      "setLeft" => setLeft,
      "track" => track,
      "passtime" => passtime,
      "userresponse" => userresponse,
      "device_id" => "A59C",
      "lot_number" => lot_number,
      "geetest" => "captcha",
      "lang" => "zh",
      "ep" => "123",
      "nz8c" => "255401529",
      "em" => { "ph" => 0 }
    }

    h[:default].call(d[:default].call(e))
  end

  def stringify(e)
    JSON.generate(e)
  end

  def encrypt(e, t = nil)
    # 请自行抠出实际加密算法内容
  end
end

# 测试区
cracker = GeetestCracker.new
track = [[47, 0, 0], [44, 1, 10], [44, 1, 18], [44, 1, 23], [43, 1, 25], [43, 1, 28], [43, 1, 29]]
captcha_width = 100
lot_number = "4b4ef3e583444e0fb...”

w = cracker.get_w(track, captcha_width, lot_number)
puts w

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值