好友
阅读权限20
听众
最后登录1970-1-1
|
本帖最后由 epiphanyep 于 2026-3-4 17:54 编辑
大家好,学了web逆向一段时间了,想着用帖子来记录自己的学习过程,逆向过程中的的一些技巧,从而巩固自己的学习的成果(ps:最近AI也太迅猛了,对于我这种小白,AI帮了我的大忙,完成了许多工作)。第一次发帖,如果有哪些不足的地方请多包容,希望能帮助到需要帮助的朋友。
这次是极验4滑块的w值 目标网址aHR0cHM6Ly9ndDQuZ2VldGVzdC5jb20v
点击开始验证 弹出滑块
![]()
打开开发者模式,随便拖动滑块一次
这里有两个主要的接口 load 和verify
![]()
观察load载荷
![]()
load载荷主要就是可以固定的captcha_id 和 callback 是一个’geetest_‘+长度为13位的时间戳和,其他再没有什么特殊的
再看一下load的响应
![]()
比较重要的是:
lot_number : 用于验证接口verify 的请求验证
slice : 滑块图片地址
bg : 背景图片地址
pow_detail中的datetime :时间校验
payload:用于验证接口verify 的请求验证
process_token : 用于验证接口verify 的请求验证
接下来是verify接口的载荷:
![]()
多次拖动滑块会发现 callback lot_number和process_token w都是变化的。
同样是 callback : 是一个’geetest_‘+长度为13位的时间戳和
captcha_id : 也是和load接口一样可以固定的
lot_number:是load接口响应返回的
payload : 是load接口响应返回的
process_token : 是load接口响应返回的
w:最重要的参数 也是本次分析的重点
验证成功就是响应 status 为success!
![]()
现在分析差不多了 开始逆向吧
搜索关键词:”process_token:“
![]()
搜出来四五个 看着像的就是后两个,如果不知道的话可以两个都断上 然后划一下滑块调试,发现第一个就是
或者你可以观察w值就在第一个process_token的下边一点点
看第二个虽然也是process_token 但是没有我们想要的w
![]()
但是这是严重混淆后的文件,没招了,我不会ast,只能硬分析。。。。
点第一个进去比较快的发现w就在眼前,双击变量变量看提示w的生成位置
仔细看其实就是上边红色框里的函数生成的w
![]()
在这里打上断点跟进去函数里面如下图所示
![]()
在这个函数最下边有return ,在return位置上打断点跟过去,发现w值就是画红线的两部分相加得来的
![]()
那么现在思路就很清晰 只需要解决相加的两部分 函数 和变量r 就行
先看r吧
仔细观察 r在上边有定义,打上断点跟上
r 是函数传参的返回值
参数;也是在上边能找到
![]()
参数;也是由一个函数生成的 跟进去
![]()
发现return的是4个l函数相加 我管相加结果叫l4吧 l函数恰巧也在上边
![]()
把l函数还原 再调用4次相加就行了
参数解决完 再看函数
函数跟进去就长这样
![]()
太乱了 我直接丢给AI了整理了
分析后层级是这是两个嵌套的函数 是实现的RSA加密 内函数是最后三目运算的一部分 三目运算判断奇偶 如果加密后长度是奇数,会在前面补一个 "0"。
其中公钥没找到 ai提示我可以通过this 来通过计算得到公钥
这里的 this 就是当前的 RSA 加密对象实例。公钥的所有信息(模数 N 和指数 E)都存储在 this 里面。
然后就可以实现rsa加密,实现原逻辑了!
再把刚刚得到的变量作为参数传进去就可以得到r了!
r解决了 最后剩下的就是前面的函数了
![]()
同样参数传函数
我先把参数解决,参数生成位置在下图断点位置,参数同样也是参数传递函数生成
![]()
这里有两个参数 第一个就是一个json对象变成字符串类型的数据,可以先固定。第二个参就是l4
第一个参数:
![]()
其中第一个字符串类型的参数里面关键的有:
setLeft : 滑动距离
passtime: 滑动消耗时间,这个时间就随便生成了
userresponse : 直接关键词搜索就能搜到产生位置
lot_number : 需要动态替换
pow_msg : 中间的时间和lot_number 我进行了动态替换
其他的可以固定
跟进函数内部发现 也太乱了
![]()
尝试在网页上手动还原,有encrypt字眼,还有一处是iv!
![]()
包括第三个参数也有key iv mode
![]()
我认为AES加密形式就是 AES加密(明文数据, 密钥key, { 初始化向量iv, 加密模式mode, 填充方式padding })
我猜大概率是AES加密
直接丢给AI分析
发现第二个参数确实是key 然后就让AI 还原了算法
最后 前面的函数跟进去
![]()
看见'>>>' 位运算符号 我猜就是位运算和进制转换
丢给AI 分析 发现是用于十六进制字符串转换的 用AI直接还原
然后逆向部分大功告成!
最后还剩滑块部分
滑块识别我最开始用的ddddocr 但是发现识别会出现问题,好多次都会出现识别不到图片,所以换了opencv试一试,效果显著提升!
下面是完整代码
py:
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 编码
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)
js:
// 新增:微秒级时间格式化函数(生成YYYY-MM-DDTHH:mm:ss.ffffff+08:00,匹配原格式)
function getFormattedTime() {
const now = new Date();
// 补零工具函数:单个数字补0(如5→05,9→09,12→12)
const pad = (num, len = 2) => num.toString().padStart(len, '0');
// 提取时间各部分(月份+1,因为JS月份是0-11)
const year = now.getFullYear();
const month = pad(now.getMonth() + 1);
const day = pad(now.getDate());
const hours = pad(now.getHours());
const minutes = pad(now.getMinutes());
const seconds = pad(now.getSeconds());
// 毫秒转6位微秒(原格式要求6位,补3个0凑数,完全匹配极验格式)
const milliseconds = pad(now.getMilliseconds(), 3);
const microseconds = `${milliseconds}000`;
// 拼接最终格式(T分隔日期时间,+08:00固定东八区)
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}.${microseconds}+08:00`;
}
// 你的原有函数:保留所有x/lot_number计算、随机滑动时间、userresponse公式
function get_str(lot_number,x){
let userresponse = x / 1.0059466666666665 + 2
console.log('userresponse:::',userresponse);
// 生成 130 ≤ 随机整数 ≤ 170 滑动时间
const randomInt_time= Math.floor(Math.random() * (170 - 130 + 1)) + 130;
console.log('滑动时间',randomInt_time);
// 关键修改1:调用动态时间函数
const dynamicTime = getFormattedTime();
let obj = {
"setLeft":x,
"passtime":randomInt_time,
"userresponse":userresponse,
"device_id":"",
"lot_number":lot_number,
// 关键修改2:移除多余双引号 + 替换为动态时间 + 保留lot_number动态注入
"pow_msg":`1|8|sha256|${dynamicTime}|54088bb07d2df3c46b79f80300b0abbe|${lot_number}||a4d2b5737d197507`,
"pow_sign":"0067ae27ad567c7ffc37abb9bf15224d96c1ba73bf0940826d6eaf171d592495",
"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"}},
"fwjn":"DJkH",
"c43a":{"43a1f9":"669a03e1"},
"em":{"ph":0,"cp":0,"ek":"11","wd":1,"nt":0,"si":0,"sc":0}
}
return obj
}
// 你的原有函数:完全未修改,保留所有逻辑
function l(){
return (65536 * (1 + Math["random"]()) | 0)["toString"](16)["substring"](1)
}
// 你的原有函数:完全未修改,保留所有逻辑
function get_r_and_str(lot_number,x){
let obj = get_str(lot_number,x)
const _obj_str = JSON.stringify(obj)
let l4 = l() + l() + l() +l()
return {
"l4": l4,
"_obj_str": _obj_str
}
}
最后就是我爱你们 |
免费评分
-
查看全部评分
|