[md]import requests
import time
import uuid
import json
import binascii
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import cv2
import subprocess
from functools import partial
import execjs
import os
subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
表情符号和美化输出
EMOJIS = {
"rocket": "🚀",
"camera": "📸",
"key": "🔑",
"check": "✅",
"cross": "❌",
"download": "⬇️",
"upload": "⬆️",
"brain": "🧠",
"lock": "🔒",
"magnifier": "🔍",
"ruler": "📏",
"star": "⭐",
"fire": "🔥",
"success": "🎉",
"fail": "💥",
"gear": "⚙️"
}
def print_header(text, emoji="🚀"):
"""打印标题"""
print("\n" + "="60)
print(f"{emoji} {text}")
print("="60)
def print_step(step, text):
"""打印步骤"""
print(f"\n【步骤 {step}】 {text}")
def print_info(text, emoji="ℹ️"):
"""打印信息"""
print(f" {emoji} {text}")
def print_success(text, emoji="✅"):
"""打印成功"""
print(f" {emoji} {text}")
def print_error(text, emoji="❌"):
"""打印错误"""
print(f" {emoji} {text}")
def print_data(label, value, emoji="📊"):
"""打印数据"""
print(f" {emoji} {label:<20}: {value}")
def get_r(lot_number,x):
print_step(3, "生成加密参数")
print_info("读取JS文件并执行...", "📜")
js_obj = execjs.compile(open('./GT4/demo.js', 'r', encoding='utf-8').read())
js_result = js_obj.call('get_r_and_str',lot_number,x)
_ᖈᕹᕺᖆ = js_result.get('l4')
_obj_str = js_result.get('_obj_str')
print_data("随机种子 l4", f"长度: {len(_ᖈᕹᕺᖆ)}", "🔑")
print_data("轨迹字符串", f"长度: {len(_obj_str)}", "📝")
# 显示预览
if len(_ᖈᕹᕺᖆ) > 50:
print_data("种子预览", _ᖈᕹᕺᖆ[:50] + "...", "👀")
# 1. 配置公钥信息
# 这是你刚刚通过 this.n.toString(16) 拿到的模数
print_info("开始RSA加密...", "🔒")
n_hex = 'c1e3934d1614465b33053e7f48ee4ec87b14b95ef88947713d25eecbff7e74c7977d02dc1d9451f79dd5d1c10c29acb6a9b4d6fb7d0a0279b6719e1772565f09af627715919221aef91899cae08c0d686d748b20a3603be2318ca6bc2b59706592a9219d0bf05c9f65023a21d2330807252ae0066d59ceefa5f2748ea80bab81'.upper()
e_val = 65537 # 指数,通常固定为 65537
# 将 16 进制模数转为整数
n_val = int(n_hex, 16)
# 2. 构造 RSA 公钥对象
pub_key = RSA.construct((n_val, e_val))
# 3. 准备明文
text = _ᖈᕹᕺᖆ
# 确保编码为 bytes
msg = text.encode('utf-8')
# 4. 加密 (PKCS1_v1_5 填充)
cipher = PKCS1_v1_5.new(pub_key)
ciphertext = cipher.encrypt(msg)
# 5. 转为 16 进制字符串 (模拟 JS 的 toString(16))
r = binascii.hexlify(ciphertext).decode()
# 6. 处理长度奇偶性 (完美复刻 JS 的 return 逻辑)
# 0 == (1 & length) ? result : "0" + result
if len(r) % 2 != 0:
r = "0" + r
print_success(f"RSA加密完成!长度: {len(r)}", "✅")
print_data("RSA结果预览", r[:80] + "..." if len(r) > 80 else r, "🔐")
return r, _obj_str, _ᖈᕹᕺᖆ
def get_aes_data(data_str, l4_seed):
"""
AES 加密
【关键修复】 Key 必须使用 l4_seed 的 bytes 形式
"""
print_info("开始AES加密...", "🔒")
# 1. Key 来源于 l4_seed (字符串转bytes)
# 极验4中,AES Key 就是 l4 这个字符串本身的 utf-8 编码
key = l4_seed.encode('utf-8')
# 2. IV 固定为 0000000000000000
iv = b'0000000000000000'
# 3. 加密
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = pad(data_str.encode('utf-8'), 16)
encrypted_bytes = cipher.encrypt(padded_data)
# 4. 返回 Hex
result = encrypted_bytes.hex()
print_success(f"AES加密完成!长度: {len(result)}", "✅")
print_data("AES结果预览", result[:80] + "..." if len(result) > 80 else result, "🔐")
return result
def get_load_api():
print_step(1, "加载验证码")
my_uuid = uuid.uuid4()
headers = {
"Accept": "*/*",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"Connection": "keep-alive",
"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/141.0.0.0 Safari/537.36",
"sec-ch-ua": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\""
}
cookies = {
"captcha_v4_user": "33f671e6e47f4726a6cef1b6c7829a5d"
}
url = "https://gcaptcha4.geetest.com/load"
params = {
"callback": f"geetest_{int(time.time() * 1000)}",
"captcha_id": "54088bb07d2df3c46b79f80300b0abbe",
"challenge": f"{my_uuid}",
"client_type": "web",
"risk_type": "slide",
"lang": "zh"
}
print_info("发送验证码请求...", "🌐")
response = requests.get(url, headers=headers, cookies=cookies, params=params).text
response = json.loads(response[22: -1])
data = response.get("data")
bg_url = 'https://static.geetest.com/' + data.get("bg")
lot_number = data.get("lot_number")
process_token = data.get("process_token")
slice_url = 'https://static.geetest.com/' + data.get("slice")
payload = data.get("payload")
print_success("验证码加载成功!", "✅")
print_data("背景图URL", bg_url, "📸")
print_data("滑块图URL", slice_url, "📸")
print_data("Process Token", process_token[:30] + "..." if len(process_token) > 30 else process_token, "🔑")
print_data("Lot Number", lot_number, "🏷️")
print_data("Payload长度", f"{len(payload)} 字符", "📏")
return bg_url, process_token, slice_url, payload, lot_number
def get_img(url, filename):
try:
filename_base = os.path.basename(filename)
print_info(f"下载图片: {filename_base}", "⬇️")
res = requests.get(url, timeout=10)
if res.status_code == 200:
with open(filename, 'wb') as f:
f.write(res.content)
file_size = len(res.content) / 1024 # KB
print_success(f"图片下载成功: {filename_base} ({file_size:.1f} KB)", "✅")
else:
print_error(f"下载失败,状态码: {res.status_code}", "❌")
except Exception as e:
print_error(f"下载异常: {e}", "❌")
def identify_gap(bg_path, slice_path):
"""
使用 OpenCV 识别滑块缺口距离
"""
print_info("识别滑块位置...", "🔍")
# 1. 读取图片
bg_img = cv2.imread(bg_path)
slice_img = cv2.imread(slice_path, cv2.IMREAD_UNCHANGED) # 保留透明通道
if bg_img is None or slice_img is None:
raise Exception("图片读取失败,请检查路径或下载是否成功")
# 显示图片尺寸
print_data("背景图尺寸", f"{bg_img.shape[1]}x{bg_img.shape[0]}", "📏")
print_data("滑块图尺寸", f"{slice_img.shape[1]}x{slice_img.shape[0]}", "📏")
# 2. 处理滑块图片 (slice)
if slice_img.shape[2] == 4:
slice_gray = cv2.cvtColor(slice_img, cv2.COLOR_BGRA2GRAY)
else:
slice_gray = cv2.cvtColor(slice_img, cv2.COLOR_BGR2GRAY)
# 3. 处理背景图片 (bg)
bg_gray = cv2.cvtColor(bg_img, cv2.COLOR_BGR2GRAY)
# 4. 边缘检测 (Canny)
slice_edge = cv2.Canny(slice_gray, 50, 150)
bg_edge = cv2.Canny(bg_gray, 50, 150)
# 5. 模板匹配
result = cv2.matchTemplate(bg_edge, slice_edge, cv2.TM_CCOEFF_NORMED)
# 6. 寻找最佳匹配位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result)
# max_loc 是 (x, y)
target_x = max_loc[0]
match_percent = max_val * 100
if match_percent > 30:
print_success(f"识别成功!X坐标: {target_x}, 匹配度: {match_percent:.1f}%", "✅")
else:
print_info(f"识别完成!X坐标: {target_x}, 匹配度: {match_percent:.1f}%", "⚠️")
return target_x
def get_x():
try:
print_step(2, "识别滑块距离")
使用 OpenCV 替代 ddddocr
x = identify_gap('./GT4/bg.jpg', './GT4/slice.png')
print_data("识别结果", f"{x} 像素", "🎯")
return x
except Exception as e:
print_error(f"识别出错: {e}", "❌")
return 0
def get_verify_api(process_token, payload, lot_number,w):
print_step(4, "提交验证")
headers = {
"accept": "*/*",
"accept-language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
"referer": "https://gt4.geetest.com/",
"sec-ch-ua": "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"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/141.0.0.0 Safari/537.36"
}
cookies = {
"captcha_v4_user": "33f671e6e47f4726a6cef1b6c7829a5d"
}
url = "https://gcaptcha4.geetest.com/verify"
params = {
"callback": f"geetest_{int(time.time() * 1000)}",
"captcha_id": "54088bb07d2df3c46b79f80300b0abbe",
"client_type": "web",
"lot_number": lot_number,
"risk_type": "slide",
"payload": payload,
"process_token": process_token,
"payload_protocol": "1",
"pt": "1",
"w": w
}
print_info("发送验证请求...", "🌐")
response = requests.get(url, headers=headers, cookies=cookies, params=params)
return response.text
if name == "main":
print_header("极验验证码识别系统", "🚀")
print_info("开始执行验证码识别任务...", "🤖")
# 确保目录存在
os.makedirs('./GT4', exist_ok=True)
try:
# 1. 加载验证码
bg_url, process_token, slice_url, payload, lot_number = get_load_api()
# 2. 下载图片
get_img(bg_url, './GT4/bg.jpg')
get_img(slice_url, './GT4/slice.png')
# 3. 识别距离
x = get_x()
if x == 0:
print_error("滑块识别失败,任务终止!", "💥")
exit()
# 4. 生成加密参数
r, _ᕾᖃᖆᕺ_str, _ᖈᕹᕺᖆ = get_r(lot_number, x)
# 5. AES加密
aes_data = get_aes_data(_ᕾᖃᖆᕺ_str, _ᖈᕹᕺᖆ)
# 6. 合成最终参数
w = aes_data + r
print_info("合成最终参数...", "⚙️")
print_data("RSA长度", f"{len(r)} 字符", "📏")
print_data("AES长度", f"{len(aes_data)} 字符", "📏")
print_data("总长度", f"{len(w)} 字符", "📏")
print_data("参数预览", w[:100] + "..." if len(w) > 100 else w, "👀")
# 7. 提交验证
res = get_verify_api(process_token, payload, lot_number, w)
# 解析响应
json_start = res.find('(') + 1
json_end = res.rfind(')')
pure_json_str = res[json_start:json_end]
result = json.loads(pure_json_str)
status = result.get('status')
print("\n" + "="*60)
if status == 'success':
print_success(f"验证成功✅✅✅!状态: {status}", "🎉")
print_data("返回Token", result.get('result', 'N/A'), "🔑")
elif status == 'fail':
print_error(f"验证失败!状态: {status}", "💥")
print_data("失败原因", result.get('reason', '未知'), "⚠️")
else:
print_info(f"验证结果:✅✅✅ {status}", "ℹ️")
# 显示完整响应
print_info("完整响应:", "📋")
for key, value in result.items():
print_data(key, str(value), "➡️")
except Exception as e:
print_error(f"程序运行出错: {e}", "💥")
import traceback
traceback.print_exc()
print("\n" + "="*60)
print_info("程序执行完毕", "🏁")
print("="*60)[ /md]