网址:滑动模式 (geetest.com)

验证码运行流程

  • 浏览器向极验服务器索要各项数据,极验服务器返回各种数据完成验证码加载
  • 用户滑动后,各项滑动的参数也会发送给极验服务器,返回成功或者失败的状态
  • 然后客户端带着成功或者失败的状态发给商家的服务器,然后商家的服务器会去找极验验证是否是正常

抓包分析

刷新页面,第一个html文件不用管

image-20230527110525991

gt.js, 因为涉及到跨域的问题,该文件就是用来解决跨域问题

image-20230526154505161

第一个包,发送第一个请求,浏览器向极验进行请求注册一个验证码,返回的两个参数将贯穿始终,因为这是用来验证是哪次请求,哪个验证码

image-20230526154640690

image-20230526154726592

第二个包 该请求携带上述返回的两个参数,用于获取验证码类型,返回的数据用于加载浏览器中的验证码

image-20230526154835093

image-20230526154906554

第三个包 接下来该请求携带第一个加密的w,返回的数据没有意义,但是同样用于加载验证码

image-20230526155044753

image-20230526155145308

第四个包 点击验证码后,加载了一大堆东西,该接口同样携带w,有点类似上面的get,该接口返回的数据看上去没有任何意义

image-20230526155407506

image-20230526155509500

第五个包 接下来又有一个请求,该请求携带的参数似乎未加密,该接口返回的数据包含三张图,一张完整背景,一张滑块,一张带缺口的背景图

image-20230526155607145

image-20230526155638531

通过测试,需要发送五次请求才能获得验证码图片,滑动滑块,

第六个包 最后一个请求,验证验证码是否正确,该参数也是最后一个w,该接口验证成功后会返回一个值,用于确定是否验证正确

image-20230526155857858

image-20230526160021683

第一个w获取流程

以上就是极验验证码 从加载到验证的全部过程,接下来开始进行代码获取

1.发送第一个请求,注册验证码,返回gt和challenge

接口地址:https://www.geetest.com/demo/gt/register-slide

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time
import requests

def get_time():
return int(time.time() * 1000)

session = requests.session()
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50',
'x-requested-with': 'XMLHttpRequest',
}

params = {
't': get_time(),
}
# 第一个请求,用于注册验证码,获取贯穿始终的gt
response = session.get('https://www.geetest.com/demo/gt/register-slide', params=params, headers=headers).json()
challenge = response['challenge']
gt = response['gt']

2.第二个请求,用于返回加载验证码的js

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

1
2
3
4
5
6
7
# 第二个请求,用于返回加载验证码的js
params = {
'gt': gt,
'callback': 'geetest_{}'.format(get_time()),
}
load_res = session.get('https://apiv6.geetest.com/gettype.php', params=params, headers=headers)

3.第三个请求,携带解密后的w参数

1
2
3
4
5
6
params = first_js.call('cul_first_w', gt, challenge)

load_res2 = requests.get(
'https://apiv6.geetest.com/get.php', headers=headers, params=params
)
print(load_res2.text)

第一个w解密流程

第一个w是不参与验证码获取和验证流程的,所以第一个w可以不用解析,但这里还是做了解析

加密入口

查看js代码,发现所有的js代码,都是进行了Unicode编码,所以,我们搜索”\u0077”,正常只能搜到一个,搜到其他的是因为代码进行了格式化,所以搜到了好多条

image-20230526161535812

image-20230526161653804

点击进去后搜索得到三个w, 对所有w进行打上断点

image-20230526161913334

image-20230526162012696

image-20230526162037113

刷新页面,断在了7907行,我们将该行代码扣下来解析一下,w是i + r构成

image-20230526162204617

1
2
3
4
5
6
7
8
9
10
11
var r = t["$_CCHF"]()
, o = $_BFZ()["encrypt1"](ge["stringify"](t['$_EJi']), t['$_CCIl']())
, i = p['$_HEf'](o),
s = {
'gt': t[$_CEIHW(321)][$_CEIHW(300)],
'challenge': t[$_CEIIU(321)][$_CEIIU(377)],
'lang': n[$_CEIHW(250)],
'pt': t[$_CEIHW(1391)],
'client_type': t[$_CEIIU(1352)],
'w': i + r
};

我们往上看一下,这三个参数都是数组,其功能有点类似于数组混淆,第一个参数等于第三个参数,然后第四行代码,将第一个参数去除,然后把第一个参数赋值给第五行,前五行的四个参数全部相同

image-20230526163240201

所以 我们在扣取这一段代码的时候,为了方便,可以在控制台将其中一个赋值给window下的一个变量,这样可以快速复制,快速替换

image-20230526164153081

这一段处理后最终的结果, 大致的意思是,把一段类似于浏览器指纹一样的数据加密后得到一个数组,然后在进行加密得到最终的w参数,这个跟浏览器指纹一样的东西,其实可以固定写死,只要更改时间戳就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var t = this
, n = t['$_EIk'];
// if (!n['gt'] || !n['challenge']) // 这一段可以注释掉,因为一定有gt
// return U(j('config_lack', t));
var e = t['$_BJCr']['$_BIBF'](); // 检测浏览器数据,类似于浏览器指纹
t['$_CCGM'] = e,
t['$_EJi']['cc'] = n['cc'],
t['$_EJi']['ww'] = n['supportWorker'],
t['$_EJi']['i'] = e;
var r = t["$_CCHF"]()
// gt是json.stringify , o的结果是一个数组
// , o = $_BFZ()["encrypt1"](ge["stringify"](t['$_EJi']), t['$_CCIl']())
, o = $_BFZ()["encrypt1"](JSON["stringify"](t['$_EJi']), t['$_CCIl']())
, i = p['$_HEf'](o),
s = {
'gt': t['$_EJi']['gt'],
'challenge': t['$_EJi']['challenge'],
'lang': n['lang'],
'pt': t['$_BJGr'],
'client_type': t['$_BJH_'],
'w': i + r
};

image-20230526170259562

获取浏览器指纹

解决第一个 e = t['$_BJCr']['$_BIBF'](),整体扣下来大概是这个样子的,接下来看ce的代码,ce的代码很坑,我们需要先理清逻辑在扣代码,看上去像是一个循环,但实际上经过判断后,只走一次,所以我们可以把循环体拿出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function $_BIBF() {
var n = this
, r = {
"internalip": -1
};
var BJAm = [
]
r['timestamp'] = new Date()['getTime'](),
r['touchEvent'] = -1,
r['performanceTiming'] = -1,
r['internalip'] = -1;
var o = [];
// 需要先扣取ce方法,然后还要扣取ce的$_EAz方法
return new ce(BJAm)['$_EAz'](function(e) {
var t = r[e];
o['push'](n['$_BIHL'](t) ? n['$_BIDi'] : t);
}),
o['join']('!!');
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function ce(e) {
// var $_DEBCK = VIPVz.$_Ds()[0][14];
// for (; $_DEBCK !== VIPVz.$_Ds()[0][13]; ) { // 相当于for (True)
// switch ($_DEBCK) {
// case VIPVz.$_Ds()[0][14]: // 这里一定相等
// this[$_DAIZ(602)] = e || [];
// $_DEBCK = VIPVz.$_Ds()[4][13]; //将这个值复制给上面的循环判断,所以下一个循环一定是false,所以只走一步
// break;
// }
// }
// this[$_DAIZ(602)] = e || [];
this['$_BAEJ'] = e || [];
}

接下来我们看一下 new ce是个什么东西,这就是我们要找的eaz, 我们在往上面拉, 发现ce实际上是一个构造器,ce的原型方法上,创建了一大堆方法,所以我们可以把这些方法全部都扣下来

image-20230526175056174

image-20230526175138519

image-20230526175426271

通过删减和替换,其实也就这些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
ce["prototype"] = {
"\u0024\u005f\u0048\u004a\u006d": function(e) {
return this['$_BAEJ'][e];
},

"\u0024\u005f\u0042\u0041\u0047\u0078": function() {

return this['$_BAEJ']['length'];
},
"\u0024\u005f\u0044\u004a\u0064": function(e, t) {

return new ce(K(t) ? this['$_BAEJ']['slice'](e, t) : this['$_BAEJ']['slice'](e));
},
"\u0024\u005f\u0042\u0041\u0048\u0055": function(e) {

return this['$_BAEJ']['push'](e),
this;
},
"\u0024\u005f\u0042\u0041\u0049\u0054": function(e, t) {

return this['$_BAEJ']['splice'](e, t || 1);
},
"\u0024\u005f\u0045\u0042\u0078": function(e) {

return this['$_BAEJ']['join'](e);
},
"\u0024\u005f\u0042\u0041\u004a\u0051": function(e) {

return new ce(this['$_BAEJ']['concat'](e));
},
"\u0024\u005f\u0045\u0041\u007a": function(e) {

var t = this['$_BAEJ'];
if (t['map'])
return new ce(t['map'](e));
for (var n = [], r = 0, o = t['length']; r < o; r += 1)
n[r] = e(t[r], r, this);
return new ce(n);
},
"\u0024\u005f\u0042\u0042\u0041\u0068": function(e) {

var t = this['$_BAEJ'];
if (t['filter'])
return new ce(t['filter'](e));
for (var n = [], r = 0, o = t['length']; r < o; r += 1)
e(t[r], r, this) && n['push'](t[r]);
return new ce(n);
},
"\u0024\u005f\u0045\u0048\u0063": function(e) {

var t = this['$_BAEJ'];
if (t['indexOf'])
return t['indexOf'](e);
for (var n = 0, r = t['length']; n < r; n += 1)
if (t[n] === e)
return n;
return -1;
},
"\u0024\u005f\u0042\u0042\u0042\u0072": function(e) {

var t = this['$_BAEJ'];
if (!t['forEach'])
for (var n = arguments[1], r = 0; r < t['length']; r++)
r in t && e['call'](n, t[r], r, this);
return t['forEach'](e);
}
}

然后我们回到上面,看 n['$_BIHL'](t) 我们看一下 n['$_BIHL'] 是一个什么方法, 这一段的代码其实就是判断e是否为空,所以那一段代码就相当于 o['push']((void 0 === t) ? -1 : t);

image-20230526180835347

这一段的最终代码就是这个样子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function $_BIBF() {
var n = this
, r = {
18,
"touchEvent": -1,
"performanceTiming": -1,
"internalip": -1
};
var BJAm = [
"textLength",

]
r['timestamp'] = new Date()['getTime'](),
r['touchEvent'] = -1,
r['performanceTiming'] = -1,
r['internalip'] = -1;
var o = [];
// 需要先扣取ce方法,然后还要扣取ce的$_EAz方法
return new ce(BJAm)['$_EAz'](function(e) {
var t = r[e];
// o['push'](n['$_BIHL'](t) ? -1 : t);
o['push']((void 0 === t) ? -1 : t);
}),

最终我们就获取到了浏览器指纹

image-20230526181216516

做过修改的RSA加密

接下来 我们把断点打到最开始的地方,看是莱真正获取 r o i 三个参数,先看 t[$_CEIIU(1300)] 方法, 替换以下

image-20230526181536692

1
2
3
4
5
6
function $_CCHF(e) {
var t = new X()['encrypt'](this['$_CCIl'](e));
while (!t || 256 !== t['length'])
t = new X()['encrypt'](this['$_CCIl'](!0));
return t;
}

然后我们看一下$_CCIl 是个什么东西,就在$_CCHF上面, 这里的逻辑就是如果有 this['$_EIk']['aeskey'] 那就返回它,没有就从 te()方法创建一个,我们直接看te方法

image-20230526182132609

1
2
3
4
function $_CCIl(e) {
return this['$_EIk']['aeskey'] && !e || (this['$_EIk']['aeskey'] = te()), // 如果aeskey 有 并且 e没有 返回 this['$_EIk']['aeskey']
this['$_EIk']['aeskey']; // 如果上述是否 那就重新创建一个aeskey,从te()方法创建
}

e方法 其实就是返回return处,这里的控制流和上面的其实是一样的,所以我们就可以这样进行改造,其实就是返回的随机字母和数字

image-20230526182723030

1
2
3
4
5
6
7
function te(){
return e() + e() + e() + e();
}

function e(){
return (65536 * (1 + Math['random']()) | 0)['toString'](16)['substring'](1);
}

接下来我们来看 X() 方法

image-20230526202353130

整理一下,去除不必要的逻辑,发现是一个RSA加密,上面的 new X() 应该就是一个 new JSEncrypt,如果要扣代码解决这个RSA加密的话,会非常麻烦

1
2
3
4
5
6
7
8
9
10
11
12
function X() {
this['n'] = null,
this['e'] = 0,
this['d'] = null,
this['p'] = null,
this['q'] = null,
this['dmp1'] = null,
this['dmq1'] = null,
this['coeff'] = null;
var public_key = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"
this['setPublic'](public_key, '10001');
}

我们找一份 JSEncryot 的源码,发现极验的RSA加密,就是用的第三方库,JSEncrypt,但是极验这里稍微做了一点修改,还有一点就是JSEncrypt这个第三方库,他的导出当中,没有导出RSAKey方法,所以我们要修改源码,其实这里有三种解决方法,一种是扣极验的RSA加密代码,一种是修改JSEncrypt的源码,再一种是用python的RSA加密库进行加密

image-20230526204240294

JSEncrypt源码

image-20230526204738139

修改完成后

image-20230526204904818

这样 我们就不用深究 X()方法了, 然后魔改之后的$_CCHF方法就变成了这样,如果使用python的话,会方便非常多

1
2
3
4
5
6
7
8
9
10
11
12
var {RSAKey} = require('node-jsencrypt')

function $_CCHF(e) {
var rsa = new RSAKey(); // 因为源码中,没有下面这一步,所以我们得手动补上设置公钥的步骤
var public_key = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"
rsa.setPublic(public_key, '10001')

var t = rsa['encrypt']($_CCIl(e)); // 对aeskey进行加密
while (!t || 256 !== t['length']) // 如果t的长度不等于256就一直加密
t = rsa['encrypt']($_CCIl(!0));
return t;
}

使用python实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# -*- coding: utf-8 -*-
# @Time : 2023/5/26 21:28

# @Author : LLi
# @Email : 1984145803@qq.com
# @File : 11.RSA-3.py
import rsa

m = '00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81'
e = '10001'

message = 'a7cbc52e0e73919c'


class Encrypt(object):
def __init__(self, e, m):
self.e = e
self.m = m

def encrypt(self, message):
mm = int(self.m, 16)
ee = int(self.e, 16)
rsa_pubkey = rsa.PublicKey(mm, ee)
crypto = self._encrypt(message.encode(), rsa_pubkey)
return crypto.hex()

def _pad_for_encryption(self, message, target_length):
message = message[::-1]
max_msglength = target_length - 11
msglength = len(message)

padding = b''
padding_length = target_length - msglength - 3

for i in range(padding_length):
padding += b'\x00'

return b''.join([b'\x00\x00', padding, b'\x00', message])

def _encrypt(self, message, pub_key):
keylength = rsa.common.byte_size(pub_key.n)
padded = self._pad_for_encryption(message, keylength)

payload = rsa.transform.bytes2int(padded)
encrypted = rsa.core.encrypt_int(payload, pub_key.e, pub_key.n)
block = rsa.transform.int2bytes(encrypted, keylength)

return block


en = Encrypt(e, m)
print(en.encrypt(message))

做过修改的AES加密

r 参数获取到后,我们接下来看参数o,猜测这里应该是一个AES加密,接下来看 $_BFZ()

1
2
o = $_BFZ()["encrypt1"](JSON["stringify"](t['$_EJi']), $_CCIl())  // 这里相当于是 encrypt(浏览器指纹,aeskey)

image-20230526213544652

encrypt1方法是这个,我们开始扣这个代码

image-20230526213708304

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function encrypt1(e, t, n) {
t = u['parse'](t); // 如果是AES加密 那应该是 CryptoJS.enc.utf8.parse(t)
// 这一段其实就是给iv初始化,然后赋值给n
// n && n['iv'] || ((n = n || {})['iv'] = u['parse']('0000000000000000'));
iv = u['parse']('0000000000000000')
// 开始还原这个for循环,将逗号拆开
// for (var r = m['encrypt'](_, e, t, n), o = r['ciphertext']['words'], i = r['ciphertext']['sigBytes'], s = [], a = 0; a < i; a++) {
var r = m['encrypt'](_, e, t, n); // 可能是CryptoJS.AES.encrypt(初始化,数据,key,iv)
var o = r['ciphertext']['words'];
var i = r['ciphertext']['sigBytes'];
var s = [];
var a = 0;
for (var s = 0; s < i; s++){
var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
s['push'](c);
}
return s;
}

我们看他的 m['encrypt'](_, e, t, n) 我们打印一下,我们用CryptoJS库尝试同样进行加密后,是不是一个结果,可以看到, m['encrypt'] 的密文结果就是我们用crypto-js的AES加密后的结果

image-20230526220945520

image-20230526220928861

所以 encrpyt1方法 我们就可以改成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function encrypt1(e, t, n) {
// t = u['parse'](t); // 如果是AES加密 那应该是 CryptoJS.enc.utf8.parse(t)
// 这一段其实就是给iv初始化,然后赋值给n
// n && n['iv'] || ((n = n || {})['iv'] = u['parse']('0000000000000000'));
// iv = u['parse']('0000000000000000')
// 开始还原这个for循环,将逗号拆开
// for (var r = m['encrypt'](_, e, t, n), o = r['ciphertext']['words'], i = r['ciphertext']['sigBytes'], s = [], a = 0; a < i; a++) {
// var r = m['encrypt'](_, e, t, n); // 可能是CryptoJS.AES.encrypt(初始化,数据,key,iv)
var key = t;
var iv = "0000000000000000";

key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
var r = CryptoJS.AES.encrypt(e,key,{
iv : iv,
mode:CryptoJS.mode.CBC
});

var o = r['ciphertext']['words'];
var i = r['ciphertext']['sigBytes'];
var s = [];
var a = 0;
for (var s = 0; s < i; s++){
var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
s['push'](c);
}
return s;
}

全局变量与代码补齐

最后就差 i 参数的 p['$_HEf'](o) 了, p['$_HEf']()方法是这样的,我们整理一下

image-20230526221857312

1
2
3
4
5
function $_HEf(e) {
var t = this['$_HCh'](e);
return t['res'] + t['end'];

}

我们再看 this['$_HCh']方法, 扣代码的时候需要把断点打进去,这里涉及到 o和i 是this

image-20230526222002457

全部抠出来的代码是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function $_HEf(e) {
var t = $_HCh(e);
return t['res'] + t['end'];

}

function $_HCh(e, o) {
var i = this;
o || (o = i);
for (var t = function (e, t) {
for (var n = 0, r = 24 - 1; 0 <= r; r -= 1)
1 === $_HBw(t, r) && (n = (n << 1) + $_HBw(e, r));
return n;
}, n = '', r = '', s = e['length'], a = 0; a < s; a += 3) {
var c;
if (a + 2 < s)
c = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2],
n += $_GJz(t(c, 7274496)) + $_GJz(t(c, 9483264)) + $_GJz(t(c, 19220)) + $_GJz(t(c, 235));
else {
var _ = s % 3;
2 == _ ? (c = (e[a] << 16) + (e[a + 1] << 8),
n += $_GJz(t(c, 7274496)) + $_GJz(t(c, 9483264)) + $_GJz(t(c, 19220)),
r = '.') : 1 == _ && (c = e[a] << 16,
n += $_GJz(t(c, 7274496)) + $_GJz(t(c, 9483264)),
r = '.' + '.');
}
}
return {
"\u0072\u0065\u0073": n,
"\u0065\u006e\u0064": r
};
}

function $_HBw(e, t) {
return e >> t & 1;
}

function $_GJz(e) {
var t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()';
return e < 0 || e >= t['length'] ? '.' : t['charAt'](e);
}

再然后 我们回到最开始加密的起始位置

1
o = encrypt1(JSON["stringify"](t['$_EJi']), $_CCIl())

t['$_EJi'] 在最开始是有内容的,在进行添加参数前打上断点,然后copy出来

image-20230526224004408

我们设置一个全局变量,然后把 t['$_EJi']放进去,并在函数体里面将 gt和challenge加进去,把需要的t给补齐来

image-20230526225922672

image-20230526225959989

完整代码如下:实际上第一个w可以省略,不需要也行,不过后续的两个w都跟这里多多少少有些关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
var {RSAKey} = require('node-jsencrypt')
var CryptoJS = require("crypto-js")

var quanjv = {
$_EJi :{
// "gt": "019924a82c70bb123aae90d483087f94",
// "challenge": "e6ff4a60ff6c3efe3a363821b193f764",
"offline": false,
"new_captcha": true,
"product": "popup",
"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
}
},
$_EIk:{}
}

function cul_first_w(gt,challenge) {
// var t = this
var t = quanjv
t['$_EJi']['gt'] = gt;
t['$_EJi']['challenge'] = challenge
// if (!n['gt'] || !n['challenge']) // 这一段可以注释掉,因为一定有gt
// return U(j('config_lack', t));
// var e = t['$_BJCr']['$_BIBF'](); // 检测浏览器数据,类似于浏览器指纹
var e = $_BIBF(); // 检测浏览器数据,类似于浏览器指纹
// t['$_CCGM'] = e,
t['$_EJi']['cc'] = 16,
t['$_EJi']['ww'] = true,
t['$_EJi']['i'] = e;
var r = $_CCHF() // 生成一个aes可以,然后进行rsa加密,然后拿到加密后的密文
// gt是json.stringify , o的结果是一个数组
// , o = $_BFZ()["encrypt1"](ge["stringify"](t['$_EJi']), t['$_CCIl']())
// 猜测一下 这里是AES加密
, o = encrypt1(JSON["stringify"](t['$_EJi']), $_CCIl()) // 这里相当于是 encrypt(浏览器指纹,aeskey)
, i = $_HEf(o),
s = {
'gt': t['$_EJi']['gt'],
'challenge': t['$_EJi']['challenge'],
'lang': "zh-cn",
'pt': 0,
'client_type': 'web',
'w': i + r
};
return s;
}

function $_HEf(e) {
var t = $_HCh(e);
return t['res'] + t['end'];

}

function $_HCh(e, o) {
var i = this;
o || (o = i);
for (var t = function (e, t) {
for (var n = 0, r = 24 - 1; 0 <= r; r -= 1)
1 === $_HBw(t, r) && (n = (n << 1) + $_HBw(e, r));
return n;
}, n = '', r = '', s = e['length'], a = 0; a < s; a += 3) {
var c;
if (a + 2 < s)
c = (e[a] << 16) + (e[a + 1] << 8) + e[a + 2],
n += $_GJz(t(c, 7274496)) + $_GJz(t(c, 9483264)) + $_GJz(t(c, 19220)) + $_GJz(t(c, 235));
else {
var _ = s % 3;
2 == _ ? (c = (e[a] << 16) + (e[a + 1] << 8),
n += $_GJz(t(c, 7274496)) + $_GJz(t(c, 9483264)) + $_GJz(t(c, 19220)),
r = '.') : 1 == _ && (c = e[a] << 16,
n += $_GJz(t(c, 7274496)) + $_GJz(t(c, 9483264)),
r = '.' + '.');
}
}
return {
"\u0072\u0065\u0073": n,
"\u0065\u006e\u0064": r
};
}

function $_HBw(e, t) {
return e >> t & 1;
}

function $_GJz(e) {
var t = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789()';
return e < 0 || e >= t['length'] ? '.' : t['charAt'](e);
}

function encrypt1(e, t, n) {
// t = u['parse'](t); // 如果是AES加密 那应该是 CryptoJS.enc.utf8.parse(t)
// 这一段其实就是给iv初始化,然后赋值给n
// n && n['iv'] || ((n = n || {})['iv'] = u['parse']('0000000000000000'));
// iv = u['parse']('0000000000000000')
// 开始还原这个for循环,将逗号拆开
// for (var r = m['encrypt'](_, e, t, n), o = r['ciphertext']['words'], i = r['ciphertext']['sigBytes'], s = [], a = 0; a < i; a++) {
// var r = m['encrypt'](_, e, t, n); // 可能是CryptoJS.AES.encrypt(初始化,数据,key,iv)
var key = t;
var iv = "0000000000000000";

key = CryptoJS.enc.Utf8.parse(key);
iv = CryptoJS.enc.Utf8.parse(iv);
var r = CryptoJS.AES.encrypt(e, key, {
iv: iv,
mode: CryptoJS.mode.CBC
});

var o = r['ciphertext']['words'];
var i = r['ciphertext']['sigBytes'];
var s = [];
for (var a = 0; a < i; a++) {
var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;
s['push'](c);
}
return s;
}

function $_CCHF(e) {
var rsa = new RSAKey(); // 因为源码中,没有下面这一步,所以我们得手动补上设置公钥的步骤
var public_key = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"
rsa.setPublic(public_key, '10001')

var t = rsa['encrypt']($_CCIl(e)); // 对aeskey进行加密
while (!t || 256 !== t['length']) // 如果t的长度不等于256就一直加密
t = rsa['encrypt']($_CCIl(!0));
return t;
}

// 这个X方法就不需要了
function X() {
this['n'] = null,
this['e'] = 0,
this['d'] = null,
this['p'] = null,
this['q'] = null,
this['dmp1'] = null,
this['dmq1'] = null,
this['coeff'] = null;
var public_key = "00C1E3934D1614465B33053E7F48EE4EC87B14B95EF88947713D25EECBFF7E74C7977D02DC1D9451F79DD5D1C10C29ACB6A9B4D6FB7D0A0279B6719E1772565F09AF627715919221AEF91899CAE08C0D686D748B20A3603BE2318CA6BC2B59706592A9219D0BF05C9F65023A21D2330807252AE0066D59CEEFA5F2748EA80BAB81"
this['setPublic'](public_key, '10001');
}




function $_CCIl(e) {
return quanjv['$_EIk']['aeskey'] && !e || (quanjv['$_EIk']['aeskey'] = te()), // 如果aeskey 有 并且 e没有 返回 this['$_EIk']['aeskey']
quanjv['$_EIk']['aeskey']; // 如果上述是否 那就重新创建一个aeskey,从te()方法创建
}

function te() {
return e() + e() + e() + e();
}

function e() {
return (65536 * (1 + Math['random']()) | 0)['toString'](16)['substring'](1);
}

// 解决 e = t['$_BJCr']['$_BIBF']()
function $_BIBF() {
var n = this
, r = {
"STYLE": 2,
"SCRIPT": 6,
"A": 1,
"DIV": 12,
"LABEL": 3,
"INPUT": 2,
"textLength": 7263,
"HTMLLength": 8771,
"documentMode": "CSS1Compat",
"browserLanguage": "zh-CN",
"browserLanguages": "zh-CN,en,en-GB,en-US",
"devicePixelRatio": 1,
"colorDepth": 24,
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.50",
"cookieEnabled": 1,
"netEnabled": 1,
"innerWidth": 2300,
"innerHeight": 646,
"outerWidth": 2316,
"outerHeight": 1400,
"screenWidth": 3440,
"screenHeight": 1440,
"screenAvailWidth": 3440,
"screenAvailHeight": 1400,
"screenLeft": 341,
"screenTop": 0,
"screenAvailLeft": 0,
"screenAvailTop": 0,
"localStorageEnabled": 1,
"sessionStorageEnabled": 1,
"indexedDBEnabled": 1,
"platform": "Win32",
"doNotTrack": 0,
"timezone": -8,
"canvas2DFP": "1ae727458b55454f3bf0563c00f6d443",
"canvas3DFP": 0,
"plugins": "internal-pdf-viewer,mhjfbmdgcfjbbpaeojofohoefgiehjai,internal-nacl-plugin",
"maxTouchPoints": 0,
"flashEnabled": -1,
"javaEnabled": 0,
"hardwareConcurrency": 16,
"jsFonts": "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",
"mediaDevices": -1,
"timestamp": 1685093132118,
"touchEvent": -1,
"performanceTiming": -1,
"internalip": -1
};
var BJAm = [
"textLength",
"HTMLLength",
"documentMode",
"A",
"ARTICLE",
"ASIDE",
"AUDIO",
"BASE",
"BUTTON",
"CANVAS",
"CODE",
"IFRAME",
"IMG",
"INPUT",
"LABEL",
"LINK",
"NAV",
"OBJECT",
"OL",
"PICTURE",
"PRE",
"SECTION",
"SELECT",
"SOURCE",
"SPAN",
"STYLE",
"TABLE",
"TEXTAREA",
"VIDEO",
"screenLeft",
"screenTop",
"screenAvailLeft",
"screenAvailTop",
"innerWidth",
"innerHeight",
"outerWidth",
"outerHeight",
"browserLanguage",
"browserLanguages",
"systemLanguage",
"devicePixelRatio",
"colorDepth",
"userAgent",
"cookieEnabled",
"netEnabled",
"screenWidth",
"screenHeight",
"screenAvailWidth",
"screenAvailHeight",
"localStorageEnabled",
"sessionStorageEnabled",
"indexedDBEnabled",
"CPUClass",
"platform",
"doNotTrack",
"timezone",
"canvas2DFP",
"canvas3DFP",
"plugins",
"maxTouchPoints",
"flashEnabled",
"javaEnabled",
"hardwareConcurrency",
"jsFonts",
"timestamp",
"performanceTiming",
"internalip",
"mediaDevices",
"DIV",
"P",
"UL",
"LI",
"SCRIPT",
"touchEvent"
]
r['timestamp'] = new Date()['getTime'](),
r['touchEvent'] = -1,
r['performanceTiming'] = -1,
r['internalip'] = -1;
var o = [];
// 需要先扣取ce方法,然后还要扣取ce的$_EAz方法
return new ce(BJAm)['$_EAz'](function (e) {
var t = r[e];
// o['push'](n['$_BIHL'](t) ? -1 : t);
o['push']((void 0 === t) ? -1 : t);
}),
o['join']('!!');
}

function ce(e) {
// var $_DEBCK = VIPVz.$_Ds()[0][14];
// for (; $_DEBCK !== VIPVz.$_Ds()[0][13]; ) { // 相当于for (True)
// switch ($_DEBCK) {
// case VIPVz.$_Ds()[0][14]: // 这里一定相等
// this[$_DAIZ(602)] = e || [];
// $_DEBCK = VIPVz.$_Ds()[4][13]; //将这个值复制给上面的循环判断,所以下一个循环一定是false,所以只走一步
// break;
// }
// }
// this[$_DAIZ(602)] = e || [];
this['$_BAEJ'] = e || [];
}

ce["prototype"] = {
"\u0024\u005f\u0048\u004a\u006d": function (e) {
return this['$_BAEJ'][e];
},

"\u0024\u005f\u0042\u0041\u0047\u0078": function () {

return this['$_BAEJ']['length'];
},
"\u0024\u005f\u0044\u004a\u0064": function (e, t) {

return new ce(K(t) ? this['$_BAEJ']['slice'](e, t) : this['$_BAEJ']['slice'](e));
},
"\u0024\u005f\u0042\u0041\u0048\u0055": function (e) {

return this['$_BAEJ']['push'](e),
this;
},
"\u0024\u005f\u0042\u0041\u0049\u0054": function (e, t) {

return this['$_BAEJ']['splice'](e, t || 1);
},
"\u0024\u005f\u0045\u0042\u0078": function (e) {

return this['$_BAEJ']['join'](e);
},
"\u0024\u005f\u0042\u0041\u004a\u0051": function (e) {

return new ce(this['$_BAEJ']['concat'](e));
},
"\u0024\u005f\u0045\u0041\u007a": function (e) {

var t = this['$_BAEJ'];
if (t['map'])
return new ce(t['map'](e));
for (var n = [], r = 0, o = t['length']; r < o; r += 1)
n[r] = e(t[r], r, this);
return new ce(n);
},
"\u0024\u005f\u0042\u0042\u0041\u0068": function (e) {

var t = this['$_BAEJ'];
if (t['filter'])
return new ce(t['filter'](e));
for (var n = [], r = 0, o = t['length']; r < o; r += 1)
e(t[r], r, this) && n['push'](t[r]);
return new ce(n);
},
"\u0024\u005f\u0045\u0048\u0063": function (e) {

var t = this['$_BAEJ'];
if (t['indexOf'])
return t['indexOf'](e);
for (var n = 0, r = t['length']; n < r; n += 1)
if (t[n] === e)
return n;
return -1;
},
"\u0024\u005f\u0042\u0042\u0042\u0072": function (e) {

var t = this['$_BAEJ'];
if (!t['forEach'])
for (var n = arguments[1], r = 0; r < t['length']; r++)
r in t && e['call'](n, t[r], r, this);
return t['forEach'](e);
}
}

gt = "019924a82c70bb123aae90d483087f94"
challenge = "e6ff4a60ff6c3efe3a363821b193f764"
console.log(cul_first_w(gt,challenge))