← 返回文章列表

深度解析:极验4消消乐验证码逆向笔记

前言本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!逆向地址网址:aHR0cHM6Ly9ndDQuZ2VldGVzdC5jb

前言

本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

逆向地址

网址

aHR0cHM6Ly9ndDQuZ2VldGVzdC5jb20v

接口

captcha_id

接口:

https://gt4.geetest.com/assets/index.xxxxxxxx.js

请求验证码接口(load接口):

https://gcaptcha4.geetest.com/load

验证验证码接口(verify接口):

https://gcaptcha4.geetest.com/verify

逆向分析

1.三个接口

首先分析一下这三个接口,看看请求和响应的参数是否动态

captcha_id

接口

https://gt4.geetest.com/assets/index.604047e9.js

这个接口返回的是一个js文件,文件里有我们需要的

caprcha_id

,这个参数需要传递给下面两个接口(请求验证码接口和验证验证码接口)当作请求参数,如下图所示:

请求验证码接口(load接口)

https://gcaptcha4.geetest.com/load

请求参数

callback

:geetest_13位的时间戳

captcha_id

:由

captcha_id

接口返回

challenge

:uuid格式的数据

client_type

:web

risk_type

:match,消消乐验证码

lang

:zho

响应数据(JSONP格式),有以下几个参数需要注意:

ques

:消消乐矩阵(3x3),每个数字代表一种图片,将三个相同数字换到同一列或同一行即可通过

pow_detail

:里面的4个参数均作为参数用于生成加密参数

w

version

bits

hashfunc

datetime

lot_number

:传递给下一个接口当请求参数,并且传参用于加密参数

w

payload

:传递给下一个接口当请求参数

process_token

:传递给下一个接口当请求参数

验证验证码接口(verify接口)

https://gcaptcha4.geetest.com/verify

请求参数

callback

:geetest_13位的时间戳

captcha_id

:由

captcha_id

接口返回

client_type

:web

risk_type

:match,消消乐验证码

lot_number

:由请求验证码接口返回

payload

:由请求验证码接口返回

process_token

:由请求验证码接口返回

payload_protocol

:1

pt

:1,作为参数用于生成加密参数

w

w

:加密参数(1504位),接下去要分析的重点

2.加密参数w

加密参数w就不分析了,和上一篇文章的一样,只不过传入的参数从移动的距离

setLeft

变成了点击两块拼图的坐标

userresponse

点击传送到上一篇文章

3.加密参数userresponse

1.验证码规则

原始数据是一个

3 行 3 列的矩阵

,我们的目标:

找到

唯一一对需要交换的元素

,交换后让一行 / 一列全部相同。

2.核心规律

所有消消乐题目,都满足一个固定特征:

存在一行 / 一列:2 个数字相同,1 个数字不同

我们只需要找到这组数据,再判断:

不同数字的

上方 / 下方

(列场景)

不同数字的

左侧 / 右侧

(行场景)

是否是目标数字,即可确定交换坐标。

3.代码

# 处理消消乐验证码,返回拼图前后的数据

def

solve_match_puzzle

(

ques

)

:

# 1. 转置矩阵:处理【原始列】(极验核心逻辑)

grid

=

[

list

(

item

)

for

item

in

zip

(

*

ques

)

]

# ========== 第一部分:遍历转置后的行 = 原始数据的列 ==========

for

r

,

row

in

enumerate

(

grid

)

:

# 过滤无重复的行

same_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

2

]

if

not

same_nums

:

continue

target

=

same_nums

[

0

]

# 找到唯一不同元素的列索引

diff_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

1

]

if

not

diff_nums

:

continue

c

=

row

.

index

(

diff_nums

[

0

]

)

coord1

=

[

r

,

c

]

coord2

=

None

# 判断上下交换

if

r

==

0

and

grid

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

elif

r

==

2

and

grid

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

r

==

1

:

if

grid

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

grid

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

# 找到答案,转换为原始坐标并返回

if

coord2

:

original_coord1

=

[

coord1

[

1

]

,

coord1

[

0

]

]

original_coord2

=

[

coord2

[

1

]

,

coord2

[

0

]

]

return

[

original_coord1

,

original_coord2

]

# ========== 第二部分:遍历原始数据的行(完整补充) ==========

for

r

,

row

in

enumerate

(

ques

)

:

same_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

2

]

if

not

same_nums

:

continue

target

=

same_nums

[

0

]

diff_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

1

]

if

not

diff_nums

:

continue

c

=

row

.

index

(

diff_nums

[

0

]

)

coord1

=

[

r

,

c

]

coord2

=

None

if

r

==

0

and

ques

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

elif

r

==

2

and

ques

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

r

==

1

:

if

ques

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

ques

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

if

coord2

:

return

[

coord1

,

coord2

]

# 兜底

return

None

结果演示

最终代码

JS代码

const

CryptoJs

=

require

(

"crypto-js"

)

;

// ========================= 工具函数 =========================

dtavm

=

{

}

;

dtavm

.

log

=

console

.

log

;

function

proxy

(

obj

,

objname

,

type

)

{

function

getMethodHandler

(

WatchName

,

target_obj

)

{

let

methodhandler

=

{

apply

(

target

,

thisArg

,

argArray

)

{

if

(

this

.

target_obj

)

{

thisArg

=

this

.

target_obj

;

}

let

result

=

Reflect

.

apply

(

target

,

thisArg

,

argArray

)

;

if

(

target

.

name

!==

"toString"

)

{

if

(

target

.

name

===

"addEventListener"

)

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 函数名 => [

${

target

.

name

}

], 传参 => [

${

argArray

[

0

]

}

], 结果 => [

${

result

}

].

`

,

)

;

}

else

if

(

WatchName

===

"window.console"

)

{

}

else

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 函数名 => [

${

target

.

name

}

], 传参 => [

${

argArray

}

], 结果 => [

${

result

}

].

`

,

)

;

}

}

else

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 函数名 => [

${

target

.

name

}

], 传参 => [

${

argArray

}

], 结果 => [

${

result

}

].

`

,

)

;

}

return

result

;

}

,

construct

(

target

,

argArray

,

newTarget

)

{

var

result

=

Reflect

.

construct

(

target

,

argArray

,

newTarget

)

;

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 构造函数名 => [

${

target

.

name

}

], 传参 => [

${

argArray

}

], 结果 => [

${

result

}

].

`

,

)

;

return

result

;

}

,

}

;

methodhandler

.

target_obj

=

target_obj

;

return

methodhandler

;

}

function

getObjhandler

(

WatchName

)

{

let

handler

=

{

get

(

target

,

propKey

,

receiver

)

{

let

result

=

target

[

propKey

]

;

if

(

result

instanceof

Object

)

{

if

(

typeof

result

===

"function"

)

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 获取属性名 => [

${

propKey

}

] , 是个函数

`

,

)

;

return

new

Proxy

(

result

,

getMethodHandler

(

WatchName

,

target

)

)

;

}

else

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 获取属性名 => [

${

propKey

}

], 结果 => [

${

result

}

]

`

,

)

;

}

return

new

Proxy

(

result

,

getObjhandler

(

`

${

WatchName

}

.

${

propKey

}

`

)

)

;

}

if

(

typeof

propKey

!==

"symbol"

)

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 获取属性名 => [

${

propKey

?.

description

??

propKey

}

], 结果 => [

${

result

}

]

`

,

)

;

}

return

result

;

}

,

set

(

target

,

propKey

,

value

,

receiver

)

{

if

(

value

instanceof

Object

)

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 设置属性名 => [

${

propKey

}

], 值为 => [

${

value

}

]

`

,

)

;

}

else

{

dtavm

.

log

(

`

调用者 => [

${

WatchName

}

] 设置属性名 => [

${

propKey

}

], 值为 => [

${

value

}

]

`

,

)

;

}

return

Reflect

.

set

(

target

,

propKey

,

value

,

receiver

)

;

}

,

has

(

target

,

propKey

)

{

var

result

=

Reflect

.

has

(

target

,

propKey

)

;

dtavm

.

log

(

`

针对in操作符的代理has=> [

${

WatchName

}

] 有无属性名 => [

${

propKey

}

], 结果 => [

${

result

}

]

`

,

)

;

return

result

;

}

,

deleteProperty

(

target

,

propKey

)

{

var

result

=

Reflect

.

deleteProperty

(

target

,

propKey

)

;

dtavm

.

log

(

`

拦截属性delete => [

${

WatchName

}

] 删除属性名 => [

${

propKey

}

], 结果 => [

${

result

}

]

`

,

)

;

return

result

;

}

,

defineProperty

(

target

,

propKey

,

attributes

)

{

var

result

=

Reflect

.

defineProperty

(

target

,

propKey

,

attributes

)

;

dtavm

.

log

(

`

拦截对象define操作 => [

${

WatchName

}

] 待检索属性名 => [

${

propKey

.

toString

(

)

}

] 属性描述 => [

${

attributes

}

], 结果 => [

${

result

}

]

`

,

)

;

// debugger

return

result

;

}

,

getPrototypeOf

(

target

)

{

var

result

=

Reflect

.

getPrototypeOf

(

target

)

;

dtavm

.

log

(

`

被代理的目标对象 => [

${

WatchName

}

] 代理结果 => [

${

result

}

]

`

)

;

return

result

;

}

,

setPrototypeOf

(

target

,

proto

)

{

dtavm

.

log

(

`

被拦截的目标对象 => [

${

WatchName

}

] 对象新原型==> [

${

proto

}

]

`

,

)

;

return

Reflect

.

setPrototypeOf

(

target

,

proto

)

;

}

,

preventExtensions

(

target

)

{

dtavm

.

log

(

`

方法用于设置preventExtensions => [

${

WatchName

}

] 防止扩展

`

)

;

return

Reflect

.

preventExtensions

(

target

)

;

}

,

isExtensible

(

target

)

{

var

result

=

Reflect

.

isExtensible

(

target

)

;

dtavm

.

log

(

`

拦截对对象的isExtensible() => [

${

WatchName

}

] isExtensible, 返回值==> [

${

result

}

]

`

,

)

;

return

result

;

}

,

}

;

return

handler

;

}

if

(

type

===

"method"

)

{

return

new

Proxy

(

obj

,

getMethodHandler

(

objname

,

obj

)

)

;

}

return

new

Proxy

(

obj

,

getObjhandler

(

objname

)

)

;

}

// 【全局防 toString 检测 · 完整版】

(

(

)

=>

{

"use strict"

;

// 1. 保存原生的 Function.toString 方法

const

$toString

=

Function

.

toString

;

// 2. 创建唯一隐形标记(Symbol)

const

myFunction_toString_symbol

=

Symbol

(

"_"

+

Math

.

random

(

)

.

toString

(

36

)

.

slice

(

2

)

,

)

;

// 3. 自定义 toString 逻辑(检票员)

const

myToString

=

function

(

)

{

return

(

(

typeof

this

===

"function"

&&

this

[

myFunction_toString_symbol

]

)

||

$toString

.

call

(

this

)

)

;

}

;

// 4. 隐形贴标工具(给函数挂隐藏属性)

function

set_native

(

func

,

key

,

value

)

{

Object

.

defineProperty

(

func

,

key

,

{

enumerable

:

false

,

// 设置为【不可枚举】= 隐形

configurable

:

true

,

writable

:

true

,

value

:

value

,

// 要伪装的内容:原生函数字符串{[native code]}

}

)

;

}

// 5. 替换全局的 toString(接管所有函数)

delete

Function

.

prototype

.

toString

;

// 删除系统原生的toString

set_native

(

Function

.

prototype

,

"toString"

,

myToString

)

;

// Function.prototype:安装目标 → 函数总原型

// 'toString':安装的属性名 → 覆盖 toString 方法

// myToString:我们自己写的自定义 toString 逻辑(有标记就伪装原生,没标记就正常输出)

// 6. 给全局挂载一键伪装工具

globalThis

.

safefunction

=

(

func

)

=>

{

set_native

(

func

,

// 要伪装的函数

myFunction_toString_symbol

,

// 要伪装的属性名

`

function

${

func

.

name

||

""

}

() { [native code] }

`

,

// 要伪装的内容

)

;

}

;

// 7. 自我保护:让我们的伪装工具也看起来像原生

set_native

(

Function

.

prototype

.

toString

,

myFunction_toString_symbol

,

"function toString() { [native code] }"

,

)

;

}

)

(

)

;

function

obj_toString

(

obj

,

tag

)

{

// 标准原生属性配置(完全模拟浏览器原生行为,防检测)

Object

.

defineProperty

(

obj

,

Symbol

.

toStringTag

,

{

value

:

tag

,

enumerable

:

false

,

// 不可枚举(原生对象默认行为)

configurable

:

true

,

// 可重新配置(防止重复定义报错)

writable

:

false

,

// 不可修改(更贴近原生,防二次检测)

}

)

;

}

// ========================= 补环境代码 =========================

// EventTarget

EventTarget

=

function

EventTarget

(

)

{

}

;

safefunction

(

EventTarget

)

;

EventTarget

.

prototype

.

addEventListener

=

function

(

)

{

}

;

safefunction

(

EventTarget

.

prototype

.

addEventListener

)

;

// Node

Node

=

function

Node

(

)

{

}

;

safefunction

(

Node

)

;

Object

.

setPrototypeOf

(

Node

.

prototype

,

EventTarget

.

prototype

)

;

// window

Window

=

function

Window

(

)

{

}

;

safefunction

(

Window

)

;

Object

.

setPrototypeOf

(

Window

.

prototype

,

EventTarget

.

prototype

)

;

window

=

global

;

self

=

top

=

globalThis

=

parent

=

window

;

Object

.

setPrototypeOf

(

window

,

Window

.

prototype

)

;

obj_toString

(

window

,

"Window"

)

;

// window = proxy(window, "window");

// HTMLHtmlElement

HTMLHtmlElement

=

function

HTMLHtmlElement

(

)

{

}

;

safefunction

(

HTMLHtmlElement

)

;

// Object.setPrototypeOf(HTMLHtmlElement.prototype, HTMLElement.prototype);

html

=

new

HTMLHtmlElement

(

)

;

obj_toString

(

html

,

"HTMLHtmlElement"

)

;

// html = proxy(html, "html");

// HTMLHeadElement

HTMLHeadElement

=

function

HTMLHeadElement

(

)

{

}

;

safefunction

(

HTMLHeadElement

)

;

// Object.setPrototypeOf(HTMLHeadElement.prototype, HTMLElement.prototype);

head

=

new

HTMLHeadElement

(

)

;

obj_toString

(

head

,

"HTMLHeadElement"

)

;

// head = proxy(head, "head");

// HTMLBodyElement

HTMLBodyElement

=

function

HTMLBodyElement

(

)

{

}

;

safefunction

(

HTMLBodyElement

)

;

// Object.setPrototypeOf(HTMLBodyElement.prototype, HTMLElement.prototype);

HTMLBodyElement

.

prototype

.

removeChild

=

function

(

args

)

{

console

.

log

(

"对象:HTMLHtmlElement.removeChild"

,

args

)

;

}

;

body

=

new

HTMLBodyElement

(

)

;

obj_toString

(

body

,

"HTMLBodyElement"

)

;

// body = proxy(body, "body");

// document

Document

=

function

Document

(

)

{

}

;

safefunction

(

Document

)

;

Object

.

setPrototypeOf

(

Document

.

prototype

,

Node

.

prototype

)

;

Document

.

prototype

.

head

=

head

;

Document

.

prototype

.

body

=

body

;

Document

.

prototype

.

documentElement

=

html

;

Document

.

prototype

.

createElement

=

function

createElement

(

tag

)

{

console

.

log

(

`

特殊检测: createElement 被调用 (tag:

${

tag

}

)

`

)

;

}

;

safefunction

(

Document

.

prototype

.

createElement

)

;

HTMLDocument

=

function

HTMLDocument

(

)

{

}

;

safefunction

(

HTMLDocument

)

;

Object

.

setPrototypeOf

(

HTMLDocument

.

prototype

,

Document

.

prototype

)

;

document

=

new

HTMLDocument

(

)

;

obj_toString

(

document

,

"HTMLDocument"

)

;

// document = proxy(document, "document");

// navigator

Navigator

=

function

Navigator

(

)

{

}

;

safefunction

(

Navigator

)

;

Navigator

.

prototype

=

{

userAgent

:

'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36'

,

platform

:

"Win32"

,

hardwareConcurrency

:

16

,

webdriver

:

false

,

languages

:

[

"zh-CN"

,

"zh"

]

,

appName

:

"Netscape"

,

appVersion

:

'5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36'

,

plugins

:

{

0

:

{

0

:

{

}

,

1

:

{

}

,

}

,

1

:

{

0

:

{

}

,

1

:

{

}

,

}

,

2

:

{

0

:

{

}

,

1

:

{

}

,

}

,

3

:

{

0

:

{

}

,

1

:

{

}

,

}

,

4

:

{

0

:

{

}

,

1

:

{

}

,

}

,

length

:

5

,

}

,

}

;

navigator

=

new

Navigator

(

)

;

obj_toString

(

navigator

,

"Navigator"

)

;

// navigator = proxy(navigator, "navigator");

// location

Location

=

function

Location

(

)

{

}

;

safefunction

(

Location

)

;

location

=

new

Location

(

)

;

location

=

{

ancestorOrigins

:

{

}

,

href

:

"https://gt4.geetest.com/"

,

origin

:

"https://gt4.geetest.com"

,

protocol

:

"https:"

,

host

:

"gt4.geetest.com"

,

hostname

:

"gt4.geetest.com"

,

port

:

""

,

pathname

:

"/"

,

search

:

""

,

hash

:

""

,

}

;

obj_toString

(

location

,

"Location"

)

;

// location = proxy(location, "location");

// Screen

Screen

=

function

Screen

(

)

{

}

;

safefunction

(

Screen

)

;

screen

=

new

Screen

(

)

;

obj_toString

(

screen

,

"Screen"

)

;

// screen = proxy(screen, "screen");

// localStorage

Storage

=

function

Storage

(

)

{

}

;

safefunction

(

Storage

)

;

localStorage

=

new

Storage

(

)

;

obj_toString

(

localStorage

,

"Storage"

)

;

// localStorage = proxy(localStorage, "localStorage");

// crypto

Crypto

=

function

Crypto

(

)

{

}

;

safefunction

(

Crypto

)

;

Crypto

.

prototype

.

getRandomValues

=

function

getRandomValues

(

typedArray

)

{

for

(

let

i

=

0

;

i

<

typedArray

.

length

;

i

++

)

{

// 生成对应类型的随机数

if

(

typedArray

instanceof

Uint8Array

)

{

typedArray

[

i

]

=

Math

.

floor

(

Math

.

random

(

)

*

256

)

;

// 0-255

}

else

if

(

typedArray

instanceof

Uint16Array

)

{

typedArray

[

i

]

=

Math

.

floor

(

Math

.

random

(

)

*

65536

)

;

// 0-65535

}

else

if

(

typedArray

instanceof

Uint32Array

)

{

typedArray

[

i

]

=

Math

.

floor

(

Math

.

random

(

)

*

4294967296

)

;

// 0-4294967295

}

}

// ✅ 关键:必须返回传入的数组(原生规范)

return

typedArray

;

}

;

safefunction

(

Crypto

.

prototype

.

getRandomValues

)

;

crypto

=

new

Crypto

(

)

;

obj_toString

(

crypto

,

"Crypto"

)

;

// crypto = proxy(crypto, "crypto");

// ========================= 源码 =========================

require

(

"./source.js"

)

;

// ========================= 测试 =========================

function

get_w

(

userresponse

,

hashfunc

,

lot_number

,

datetime

,

captcha_id

)

{

let

key

;

let

pow_msg

;

let

pow_sign

;

// 🔥 核心:循环生成 key,直到 pow_sign 以 00 开头

do

{

// 每次循环重新生成 key

key

=

window

.

key

(

)

;

// 拼接 pow_msg

pow_msg

=

`

1|8|

${

hashfunc

}

|

${

datetime

}

|

${

captcha_id

}

|

${

lot_number

}

||

${

key

}

`

;

// 计算 sha256

if

(

hashfunc

===

'md5'

)

{

pow_sign

=

CryptoJs

.

MD5

(

pow_msg

)

.

toString

(

)

;

}

else

if

(

hashfunc

===

'sha1'

)

{

pow_sign

=

CryptoJs

.

SHA1

(

pow_msg

)

.

toString

(

)

;

}

else

if

(

hashfunc

===

'sha256'

)

{

pow_sign

=

CryptoJs

.

SHA256

(

pow_msg

)

.

toString

(

)

;

}

// 调试:打印看看是否满足条件

// console.log("重试中...pow_sign:", pow_sign);

}

while

(

pow_sign

.

indexOf

(

'00'

)

!==

0

)

;

// 不满足就一直循环

console

.

log

(

"✅ 找到符合条件的pow_sign:"

,

pow_sign

)

;

console

.

log

(

"✅ 对应key:"

,

key

)

;

str1

=

lot_number

.

substr

(

19

,

6

)

// 索引19开始截取6位

str2

=

lot_number

.

substr

(

23

,

8

)

// 索引23开始截取8位

str3

=

lot_number

.

substr

(

5

,

8

)

// 索引5开始截取8位

str4

=

lot_number

.

substr

(

14

,

6

)

// 索引14开始截取6位

const

passtime

=

Math

.

floor

(

Math

.

random

(

)

*

300

)

+

500

;

// 300-800ms 真人耗时

aaa

=

{

"passtime"

:

passtime

,

// 滑动耗时

"userresponse"

:

userresponse

,

// [[第1个点击的坐标],[第2个点击的坐标]]

"device_id"

:

""

,

"lot_number"

:

lot_number

,

// load接口返回

// hashfunc、datetime 由load接口返回, captch_id 由captcha_id接口返回

"pow_msg"

:

pow_msg

,

// 由pow_msg 加密得来

"pow_sign"

:

pow_sign

,

"geetest"

:

"captcha"

,

// 固定的

"lang"

:

"zh"

,

// 固定的

"ep"

:

"123"

,

// 固定的

"biht"

:

"1426265548"

,

// 固定的

"gee_guard"

:

{

"roe"

:

{

"aup"

:

"3"

,

"sep"

:

"3"

,

"egp"

:

"3"

,

"auh"

:

"3"

,

"rew"

:

"3"

,

"snh"

:

"3"

,

"res"

:

"3"

,

"cdc"

:

"3"

}

}

,

"4MTT"

:

"0Qh0"

,

// 动态的

// 3个值都来自load接口,截取lot_number中的部分值

[

str1

]

:

{

[

str2

]

:

{

[

str3

]

:

str4

}

}

,

"em"

:

{

"ph"

:

0

,

"cp"

:

0

,

"ek"

:

"11"

,

"wd"

:

1

,

"nt"

:

0

,

"si"

:

0

,

"sc"

:

0

}

// 固定的

}

;

console

.

log

(

aaa

)

arr

=

{

options

:

{

'pt'

:

'1'

}

}

result

=

window

.

iii

(

JSON

.

stringify

(

aaa

)

,

arr

)

;

return

result

;

}

console

.

log

(

get_w

(

[

[

2

,

2

]

,

[

1

,

2

]

]

,

'sha256'

,

"dbbcf007750e48bea942e4bb7d350bc9"

,

'2026-04-03T13:29:07.115836+08:00'

,

'54088bb07d2df3c46b79f80300b0abbe'

)

)

;

Python代码

import

requests

import

execjs

import

time

import

re

import

random

import

json

jsocde

=

open

(

'消消乐验证码.js'

,

'r'

,

encoding

=

'utf-8'

)

.

read

(

)

jscompile

=

execjs

.

compile

(

jsocde

)

session

=

requests

.

Session

(

)

headers

=

{

"Accept"

:

"*/*"

,

"Accept-Language"

:

"zh-CN,zh;q=0.9"

,

"Cache-Control"

:

"no-cache"

,

"Connection"

:

"keep-alive"

,

"Pragma"

:

"no-cache"

,

"Referer"

:

"https://gt4.geetest.com/"

,

"Sec-Fetch-Dest"

:

"script"

,

"Sec-Fetch-Mode"

:

"no-cors"

,

"Sec-Fetch-Site"

:

"same-site"

,

"User-Agent"

:

"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36"

,

"sec-ch-ua"

:

"\"Chromium\";v=\"146\", \"Not-A.Brand\";v=\"24\", \"Google Chrome\";v=\"146\""

,

"sec-ch-ua-mobile"

:

"?0"

,

"sec-ch-ua-platform"

:

"\"Windows\""

}

cookies

=

{

"captcha_v4_user"

:

""

,

"Hm_lvt_25b04a5e7a64668b9b88e2711fb5f0c4"

:

""

,

"sensorsdata2015jssdkcross"

:

""

}

session

.

headers

.

clear

(

)

session

.

headers

.

update

(

headers

)

session

.

cookies

.

update

(

cookies

)

# 动态生成challenge,uuid格式

def

generate_challenge

(

)

:

def

replace

(

c

)

:

r

=

random

.

randint

(

0

,

15

)

v

=

r

if

c

==

'x'

else

(

r

&

0x3

|

0x8

)

return

hex

(

v

)

[

2

:

]

uuid_str

=

'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'

result

=

''

for

char

in

uuid_str

:

result

+=

replace

(

char

)

if

char

in

(

'x'

,

'y'

)

else

char

return

result

# 解析JSONP响应

def

parse_jsonp

(

response_text

)

:

"""把 geetest_xxx({...}) 转为 纯JSON"""

left

=

response_text

.

find

(

'('

)

+

1

right

=

response_text

.

rfind

(

')'

)

json_str

=

response_text

[

left

:

right

]

return

json

.

loads

(

json_str

)

# 处理消消乐验证码,返回拼图前后的数据

def

solve_match_puzzle

(

ques

)

:

# 1. 转置矩阵:处理【原始列】(极验核心逻辑)

grid

=

[

list

(

item

)

for

item

in

zip

(

*

ques

)

]

# ========== 第一部分:遍历转置后的行 = 原始数据的列 ==========

for

r

,

row

in

enumerate

(

grid

)

:

# 过滤无重复的行

same_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

2

]

if

not

same_nums

:

continue

target

=

same_nums

[

0

]

# 找到唯一不同元素的列索引

diff_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

1

]

if

not

diff_nums

:

continue

c

=

row

.

index

(

diff_nums

[

0

]

)

coord1

=

[

r

,

c

]

coord2

=

None

# 判断上下交换

if

r

==

0

and

grid

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

elif

r

==

2

and

grid

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

r

==

1

:

if

grid

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

grid

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

# 找到答案,转换为原始坐标并返回

if

coord2

:

original_coord1

=

[

coord1

[

1

]

,

coord1

[

0

]

]

original_coord2

=

[

coord2

[

1

]

,

coord2

[

0

]

]

return

[

original_coord1

,

original_coord2

]

# ========== 第二部分:遍历原始数据的行(完整补充) ==========

for

r

,

row

in

enumerate

(

ques

)

:

same_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

2

]

if

not

same_nums

:

continue

target

=

same_nums

[

0

]

diff_nums

=

[

n

for

n

in

row

if

row

.

count

(

n

)

==

1

]

if

not

diff_nums

:

continue

c

=

row

.

index

(

diff_nums

[

0

]

)

coord1

=

[

r

,

c

]

coord2

=

None

if

r

==

0

and

ques

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

elif

r

==

2

and

ques

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

r

==

1

:

if

ques

[

r

-

1

]

[

c

]

==

target

:

coord2

=

[

r

-

1

,

c

]

elif

ques

[

r

+

1

]

[

c

]

==

target

:

coord2

=

[

r

+

1

,

c

]

if

coord2

:

return

[

coord1

,

coord2

]

# 兜底

return

None

# 请求 captcha_id 接口

def

get_captcha_id

(

)

:

# 请求的js文件名是会变化的,有效时间大概6-8个小时

url

=

"https://gt4.geetest.com/assets/index.1395bf87.js"

response

=

session

.

get

(

url

)

pattern

=

r'captchaId:"([a-f0-9]{32})"'

match

=

re

.

search

(

pattern

,

response

.

text

)

captcha_id

=

match

.

group

(

1

)

return

captcha_id

# 请求验证码接口(load接口)

def

get_captcha_load

(

captcha_id

)

:

# 动态生成callback(时间戳,极验要求)

callback

=

f"geetest_

{

int

(

time

.

time

(

)

*

1000

)

}

"

# 动态生成 challenge

challenge

=

generate_challenge

(

)

params

=

{

"callback"

:

callback

,

"captcha_id"

:

captcha_id

,

"challenge"

:

challenge

,

"client_type"

:

"web"

,

"risk_type"

:

"match"

,

"lang"

:

"zho"

}

url

=

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

response

=

session

.

get

(

url

,

params

=

params

)

result

=

parse_jsonp

(

response

.

text

)

return

result

# 请求验证接口(verify接口)

def

get_captcha_verify

(

captcha_id

,

load_response

,

w

)

:

# 动态生成callback(时间戳,极验要求)

callback

=

f"geetest_

{

int

(

time

.

time

(

)

*

1000

)

}

"

# 从load结果中拿【最新、未过期】的参数

lot_number

=

load_response

[

"data"

]

[

"lot_number"

]

payload

=

load_response

[

"data"

]

[

"payload"

]

process_token

=

load_response

[

"data"

]

[

"process_token"

]

params

=

{

# 时间戳,动态

"callback"

:

callback

,

"captcha_id"

:

captcha_id

,

# 验证码id

"client_type"

:

"web"

,

# 动态,由load接口返回

"lot_number"

:

lot_number

,

"risk_type"

:

"slide"

,

# 动态,由load接口返回

"payload"

:

payload

,

# 动态,由load接口返回

"process_token"

:

process_token

,

"payload_protocol"

:

"1"

,

"pt"

:

"1"

,

# 加密参数,1504位

"w"

:

w

,

}

# print("verify接口请求参数>>>>",params)

url

=

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

response

=

session

.

get

(

url

,

params

=

params

)

result

=

parse_jsonp

(

response

.

text

)

return

result

if

__name__

==

'__main__'

:

captcha_id

=

get_captcha_id

(

)

# print("captcha_id>>>>", captcha_id)

# 首次获取Load数据

load_response

=

get_captcha_load

(

captcha_id

)

print

(

"load接口响应数据>>>>"

,

load_response

)

lot_number

=

load_response

[

"data"

]

[

"lot_number"

]

hashfunc

=

load_response

[

"data"

]

[

"pow_detail"

]

[

"hashfunc"

]

datetime

=

load_response

[

"data"

]

[

"pow_detail"

]

[

"datetime"

]

# 处理消消乐验证码,返回拼图前后的数据

ques

=

load_response

[

"data"

]

[

"ques"

]

userresponse

=

solve_match_puzzle

(

ques

)

# userresponse = input("请输入拼图前/后数据:")

print

(

"拼图前/后数据>>>>"

,

userresponse

)

w

=

jscompile

.

call

(

"get_w"

,

userresponse

,

hashfunc

,

lot_number

,

datetime

,

captcha_id

)

print

(

"加密参数w>>>>"

,

w

)

# 执行验证流程

verify_response

=

get_captcha_verify

(

captcha_id

,

load_response

,

w

)

print

(

"验证结果>>>>"

,

verify_response

)