hcaptcha算法分析

完整流程

[开始hCaptcha验证] –> [接收JWT令牌] –>[分析关键参数] –>[发送请求]

n值数据生成

n值是由浏览器的环境和一些指纹生成的下面我将来分析下这个算法

算法部分

hcaptcha这个部分核心的算法是在wasm里面和vmp里面的

通过解混淆我们知道这个是一个AES的加密算法但它的key和iv分别在wasm里

提取key

我们可以直接引用wasm里生成好的key

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
def _generate_n_key(self):
seed = self.key_factors["seed"]
key_factor1 = self.key_factors["key_factor1"]
key_factor2 = self.key_factors["key_factor2"]


key_bytes = list(self.key_factors["key_seed"].to_bytes(4, byteorder="little"))[
:2
]


for step in range(30):

if step != 0:
seed = (seed * self.LCG_MULTIPLIER) & 0xFFFFFFFFFFFFFFFF


if self.key_factors["operator"] == "+":
seed = (seed + key_factor1) & 0xFFFFFFFFFFFFFFFF
else:
seed = (seed - key_factor1) & 0xFFFFFFFFFFFFFFFF


base_index = self.key_factors["memory"] + step


memory_position = base_index + key_factor2

segment_address = (
((memory_position // 320) << 3) + memory_position + 1032 - 1075552
)

# get the mask address using modulo 96 and an offset of 8
mask_address = (memory_position % 96) + 8

# read a 32 bit value from memory at the segment address
segment_address %= len(self._memory)
if segment_address + 4 <= len(self._memory):
segment_bytes = self._memory[segment_address : segment_address + 4]
else:
wrap = segment_address + 4 - len(self._memory)
segment_bytes = self._memory[segment_address:] + self._memory[:wrap]
segment_value = int.from_bytes(segment_bytes, byteorder="little")

mask_address %= len(self._memory)
if mask_address + 8 <= len(self._memory):
mask_bytes = self._memory[mask_address : mask_address + 8]
else:
wrap = mask_address + 8 - len(self._memory)
mask_bytes = self._memory[mask_address:] + self._memory[:wrap]
mask_value = int.from_bytes(mask_bytes, byteorder="little")

# calculate a hash value by xoring the segment value with the lower 32 bits of the mask value
hash_value = (segment_value ^ (mask_value & 0xFFFFFFFF)) & 0xFF

# extract specific bit positions from the state seed
# these are at positions 45, 27, and 59 in the 64 bit seed
bit45 = (seed >> 45) & 0xFFFFFFFF
bit27 = (seed >> 27) & 0xFFFFFFFF
bit59 = (seed >> 59) & 0xFFFFFFFF

if bit45 & 0x80000000:
bit45 = bit45 - 0x100000000
if bit27 & 0x80000000:
bit27 = bit27 - 0x100000000
if bit59 & 0x80000000:
bit59 = bit59 - 0x100000000

# xor bit45 and bit27
combined = bit45 ^ bit27

shift = bit59 % 32
combined &= 0xFFFFFFFF
rotated = ((combined >> shift) | (combined << (32 - shift))) & 0xFFFFFFFF

if rotated & 0x80000000:
rotated = rotated - 0x100000000

key_byte = (hash_value ^ rotated) & 0xFF

key_bytes.append(key_byte)
self.key = bytes(key_bytes).hex()

剩下的就简单了直接算法还原