← 返回文章列表

深度解析:滑块验证码前端安全研究:以极验 GT4(第四代)为例

免责声明:本文所有分析均基于公开可访问的前端JS代码及极验官网演示页(https://www.geetest.com/adaptive-captcha),仅用于安全研究、学习与了解验证码防护机制。文中所有敏感参数(RSA公钥模数、captc

免责声明:

本文所有分析均基于公开可访问的前端 JS 代码及极验官网演示页(https://www.geetest.com/adaptive-captcha),仅用于安全研究、学习与了解验证码防护机制。文中所有敏感参数(RSA 公钥模数、captcha_id 等)均来自极验官方公开演示环境,不涉及任何第三方业务系统。请勿将本文技术用于任何未授权的系统,违者后果自负。

一、背景介绍

极验 GT4(第四代行为验证)是国内应用最广泛的人机验证方案之一,支持滑动拼图、文字点选、一点即过、消消乐等多种验证形式。本文以

滑动拼图验证(slide)

为研究对象,分析其前端安全机制。

基本交互逻辑:

前端携带

captcha_id

+ 动态

challenge

请求

load

接口,获取背景图(bg)、滑块图(slice)、会话标识(lot_number)及工作量证明参数(pow_detail)

用计算机视觉识别滑块应移动到的缺口位置,得到

distance

前端将滑动距离、耗时、设备信息等核心字段组装为

w_data

,经

AES-CBC + RSA

双层加密后构造参数

w

携带

w

及会话参数提交

verify

接口,服务端返回

result: success/fail

研究核心难点:

识别缺口距离

(计算机视觉)

还原 w 参数的加密体系

(AES-CBC 对称加密 + RSA 非对称加密密钥)

工作量证明(PoW)参数计算

(动态 bits/hashfunc 适配)

二、整体流程图

本地生成 challenge(UUID v4)+ callback(时间戳)

load 接口(GET /load)

↓ 返回 lot_number、bg、slice、pow_detail、payload、process_token

下载背景图 + 滑块图

ddddocr 识别缺口距离 distance

计算工作量证明 pow_msg / pow_sign

组装 w_data(distance、passtime、userresponse 等字段)

AES-CBC 加密 w_data → aes_data

RSA 加密 AES 密钥 → rsa_data

w = aes_data + rsa_data

verify 接口(GET /verify)→ result: success/fail

三、抓包分析

3.1 load 接口

请求:

GET https://gcaptcha4.geetest.com/load

关键请求参数:

参数

来源

说明

callback

本地生成

'geetest_' + Date.now()

,用于 JSONP 回调

captcha_id

业务方配置

绑定某个具体业务,从前端 JS 中提取,固定值

challenge

本地生成

UUID v4 格式,每次请求动态生成(源码:

config.challenge || uuid()

client_type

固定

web

risk_type

固定

slide

表示滑动拼图,不同验证形式对应不同值

lang

固定

zh

challenge

在 GT4 的

gt4.js

源码中明确写为

config.challenge || uuid()

,即业务方不传时自动生成 UUID,因此可本地用

uuid.uuid4()

替代。

响应示例(JSONP 格式):

geetest_xxxxxxxxxx

(

{

"status"

:

"success"

,

"data"

:

{

"lot_number"

:

"614c7e56806748cfad5d2eeca241293f"

,

"captcha_type"

:

"slide"

,

"bg"

:

"captcha_v4/xxx/bg/xxx.jpg"

,

"slice"

:

"captcha_v4/xxx/slice/xxx.png"

,

"ypos"

:

14

,

"pow_detail"

:

{

"version"

:

"1"

,

"bits"

:

0

,

"datetime"

:

"2026-04-15T21:20:34.369357+08:00"

,

"hashfunc"

:

"md5"

}

,

"payload"

:

"AgFD8g..."

,

"process_token"

:

"ee684e49..."

,

"payload_protocol"

:

1

}

}

)

响应为 JSONP 格式,需动态定位括号位置解析,不能硬编码偏移量(callback 名长度随时间戳变化)。

3.2 verify 接口

请求:

GET https://gcaptcha4.geetest.com/verify

关键请求参数:

参数

来源

说明

captcha_id

同 load

业务方固定值

lot_number

load 响应

本次会话唯一标识

risk_type

固定

slide

payload

load 响应

服务端加密载荷,原样透传

process_token

load 响应

会话凭证,原样透传

payload_protocol

load 响应

协议版本,原样透传

pt

load 响应

原样透传

w

本地加密生成

核心参数,见第四节

四、JS 逆向——w 参数加密体系

w

是 verify 接口的核心参数,其生成逻辑可直接在极验 CDN 分发的

gt4.js

(未混淆)中阅读。

4.1 w 参数结构

w = AES_CBC_Encrypt(JSON.stringify(w_data), aes_key) [hex]

+ RSA_Encrypt(aes_key) [hex]

即:

AES 加密后的数据 hex 字符串

拼接

RSA 加密密钥 hex 字符串

,服务端先用私钥解出 AES 密钥,再解密出

w_data

4.2 w_data 核心字段

字段

含义

说明

setLeft

滑块移动像素

ddddocr 识别结果

passtime

滑动总耗时(ms)

随机范围 1300~2000

userresponse

实际响应距离

distance / 1.0059466... + 2

,固定换算公式

lot_number

会话标识

来自 load 响应

pow_msg

PoW 消息

见第五节

pow_sign

PoW 签名

见第五节

gee_guard

环境检测结果

固定结构,包含多项 bot 检测标志

em

环境检测补充

固定结构

4.3 AES 加密

算法:AES-128-CBC,PKCS7 填充,输出 hex

密钥处理规则(来自

gt4.js

逆向):

key_str

长度 < 16:取其 MD5 digest(16 字节)作为密钥

key_str

长度 ≥ 16:截取前 16 字节

IV 处理规则相同,默认 IV 为

"0000000000000000"

def

AES_Encrypt

(

word

:

str

,

key_str

:

str

,

iv_str

:

str

=

"0000000000000000"

)

-

>

str

:

"""AES-128-CBC 加密,PKCS7 填充,输出 hex"""

key_bytes

=

(

hashlib

.

md5

(

key_str

.

encode

(

)

)

.

digest

(

)

if

len

(

key_str

)

<

16

else

key_str

.

encode

(

)

[

:

16

]

)

iv_bytes

=

(

hashlib

.

md5

(

iv_str

.

encode

(

)

)

.

digest

(

)

if

len

(

iv_str

)

<

16

else

iv_str

.

encode

(

)

[

:

16

]

)

cipher

=

AES

.

new

(

key_bytes

,

AES

.

MODE_CBC

,

iv_bytes

)

return

cipher

.

encrypt

(

pad

(

word

.

encode

(

)

,

AES

.

block_size

)

)

.

hex

(

)

4.4 RSA 加密

算法:RSA PKCS1 v1.5,公钥通过模数(hex)+ 固定指数(65537)构造,输出 hex

RSA 公钥的模数(

modulus

)虽以 hex 字符串形式存在于

gt4.js

中,但实际使用时需通过调试断点或 Hook 加密函数的方式动态确认其与当前 SDK 版本的对应关系,不建议直接静态读取硬编码。加密的内容是随机生成的 AES 密钥字符串,服务端持有私钥还原后用于解密

w_data

def

RSA_Encrypt

(

plaintext

:

str

)

-

>

str

:

"""RSA PKCS1 v1.5 加密,modulus 需通过调试从 gt4.js 中获取,输出 hex"""

modulus

=

int

(

"<调试获取的 modulus hex 字符串>"

,

16

)

key

=

RSA

.

construct

(

(

modulus

,

65537

)

)

return

PKCS1_v1_5

.

new

(

key

)

.

encrypt

(

plaintext

.

encode

(

)

)

.

hex

(

)

4.5 AES 密钥生成

每次请求随机生成 16 字节(4 × 4 位随机 hex),保证每次

w

不重复:

def

get_random_key

(

)

-

>

str

:

"""生成 16 字节随机 hex 字符串,作为 AES 会话密钥"""

return

''

.

join

(

format

(

random

.

getrandbits

(

16

)

,

'04x'

)

for

_

in

range

(

4

)

)

五、工作量证明(PoW)机制

GT4 在

load

响应中下发

pow_detail

,要求客户端提交满足条件的哈希值,用于防刷。

5.1 pow_detail 结构

字段

说明

version

协议版本,固定

"1"

bits

要求哈希结果前导零 bit 数;

bits=0

表示无要求,任意值即可

hashfunc

哈希算法,

md5

sha256

datetime

服务端当前时间戳,计入消息体防重放

5.2 pow_msg 格式

pow_msg = f"1|{bits}|{hashfunc}|{datetime}|{captcha_id}|{lot_number}||{random_key}"

pow_sign = hashfunc(pow_msg)

bits > 0

时,需循环碰撞直到

pow_sign

满足前导零条件:

def

get_pow_info

(

pow_detail

:

dict

,

captcha_id

:

str

,

lot_number

:

str

)

:

"""

动态计算 PoW,适配 bits=0(直接返回)和 bits>0(碰撞循环)两种场景。

"""

bits

=

pow_detail

.

get

(

'bits'

,

0

)

datetime

=

pow_detail

.

get

(

'datetime'

,

''

)

hashfunc

=

pow_detail

.

get

(

'hashfunc'

,

'md5'

)

hash_fn

=

hashlib

.

sha256

if

hashfunc

==

'sha256'

else

hashlib

.

md5

prefix

=

'0'

*

(

bits

//

4

)

# 需要多少个前导零 hex 字符

while

True

:

key

=

get_random_key

(

)

pow_msg

=

f'1|

{

bits

}

|

{

hashfunc

}

|

{

datetime

}

|

{

captcha_id

}

|

{

lot_number

}

||

{

key

}

'

pow_sign

=

hash_fn

(

pow_msg

.

encode

(

)

)

.

hexdigest

(

)

if

bits

==

0

or

pow_sign

.

startswith

(

prefix

)

:

return

pow_msg

,

pow_sign

六、图像识别——计算缺口距离

使用开源库

ddddocr

进行滑块缺口匹配,支持直接传入图片二进制,无需写入磁盘:

from

ddddocr

import

DdddOcr

def

ddddocr_get_distance

(

bg

:

bytes

,

tp

:

bytes

)

-

>

int

:

"""

利用 ddddocr 识别滑块缺口位置,返回滑动距离(像素)。

:param bg: 背景图二进制(带缺口的完整背景)

:param tp: 滑块图二进制(带透明通道的拼图块)

:return: 缺口 x 坐标(像素)

"""

det

=

DdddOcr

(

det

=

False

,

ocr

=

False

,

show_ad

=

False

)

res

=

det

.

slide_match

(

tp

,

bg

,

simple_target

=

True

)

return

int

(

res

[

'target'

]

[

0

]

)

res['target']

返回

[left, top, right, bottom]

left

即缺口的 x 坐标。

极验 GT4 图片为标准分辨率(非 Retina 2x),无需额外

/2

校正,与数美等 SDK 不同。

备选方案:OpenCV 模板匹配

若 ddddocr 识别不准,可改用 Canny 边缘检测 + 模板匹配:

import

cv2

def

opencv_get_distance

(

bg_path

:

str

,

slice_path

:

str

)

-

>

int

:

"""OpenCV Canny 边缘检测 + 模板匹配识别缺口距离"""

bg

=

cv2

.

imread

(

bg_path

)

tp

=

cv2

.

imread

(

slice_path

,

cv2

.

IMREAD_UNCHANGED

)

# 统一灰度化

bg_gray

=

cv2

.

cvtColor

(

bg

,

cv2

.

COLOR_BGR2GRAY

)

tp_gray

=

(

cv2

.

cvtColor

(

tp

,

cv2

.

COLOR_BGRA2GRAY

)

if

tp

.

shape

[

2

]

==

4

else

cv2

.

cvtColor

(

tp

,

cv2

.

COLOR_BGR2GRAY

)

)

# 边缘检测后模板匹配

result

=

cv2

.

matchTemplate

(

cv2

.

Canny

(

bg_gray

,

50

,

150

)

,

cv2

.

Canny

(

tp_gray

,

50

,

150

)

,

cv2

.

TM_CCOEFF_NORMED

)

return

cv2

.

minMaxLoc

(

result

)

[

3

]

[

0

]

# max_loc[0] 即 x

七、JSONP 响应解析

GT4 的 load/verify 接口均返回 JSONP 格式,回调函数名包含毫秒级时间戳,长度不固定,

不能用固定偏移量(如

[22:-1]

)截取

,应动态定位括号:

def

parse_jsonp

(

text

:

str

)

-

>

dict

:

"""动态解析 JSONP 响应,提取 JSON 部分"""

json_str

=

text

[

text

.

index

(

'('

)

+

1

:

text

.

rindex

(

')'

)

]

return

json

.

loads

(

json_str

)

八、完整请求流程概述

步骤

动作

说明

1

生成会话标识

本地生成

challenge

uuid.uuid4()

)和

callback

geetest_

+ 时间戳)

2

调用 load 接口

获取

lot_number

、背景图/滑块图路径、

pow_detail

payload

process_token

3

下载图片

https://static.geetest.com/

下载 bg 和 slice

4

识别缺口距离

ddddocr

slide_match

得到

distance

5

计算 PoW

根据

pow_detail.bits

hashfunc

动态碰撞,得到

pow_msg

/

pow_sign

6

组装 w_data

填入 distance、passtime、userresponse、pow 等字段

7

加密构造 w

AES_CBC(w_data, aes_key)

+

RSA(aes_key)

拼接

8

调用 verify 接口

携带 w 及会话参数,解析返回

result: success/fail

九、关键知识点总结

知识点

详情

challenge 来源

本地

uuid.uuid4()

生成,GT4 源码明确支持(

config.challenge || uuid()

captcha_id

业务方配置的固定值,从前端 JS 提取,不会每次变化

图像识别库

ddddocr

滑块匹配,GT4 无 Retina 缩放,无需

/2

AES 加密

AES-128-CBC,PKCS7 填充,输出 hex;密钥短于 16 字节时取 MD5

RSA 加密

PKCS1 v1.5,公钥参数可从 gt4.js 明文读取,输出 hex

w 结构

AES(w_data) hex

+

RSA(aes_key) hex

直接拼接

PoW 机制

动态适配 bits/hashfunc,bits=0 时直接返回,bits>0 时循环碰撞

JSONP 解析

动态定位

(

)

位置,不能硬编码偏移量

Session 管理

使用

requests.Session

自动携带服务端下发的 Cookie(如

captcha_v4_user

十、与数美 SDK(protocol=206)的横向对比

对比维度

极验 GT4

数美 SDK(新版)

加密算法

AES-CBC + RSA,输出 hex

DES-ECB,输出 Base64

加密参数数量

合并为 1 个 w 参数

分散为 12 个独立字段

密钥来源

本地随机生成 AES Key + 固定公钥

硬编码 Key 逆向 + 动态派生

__key

工作量证明

有 PoW 机制(pow_detail 动态下发)

无 PoW

图片分辨率

标准分辨率,无缩放

Retina 2x,需

/2

校正

序列化

标准

json.dumps

自定义

smStringify

(差异处理 undefined)

JSONP 解析

需动态解析

需动态解析

JS 混淆程度

gt4.js 基本可读

obfuscator.io 重度混淆,需 webcrack 反混淆

十一、依赖安装

pip

install

requests pycryptodome ddddocr opencv-python

本文技术仅供安全研究与学习,切勿用于任何未授权系统,违者后果自负。