ksjsb/tools/captcha_env.py
2026-07-30 20:25:56 +08:00

186 lines
7.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""合成快手 captcha 的 captchaExtraParam (key1-key39) —— 含拖拽相关传感器。
逆向自 out/captcha_net/js_08_...deobf2.js 的 collectEnvInfo / sensorInstance / traceInstance:
- sensorInstance.cdma(devicemotion): 每事件 push "count, t, ax, ay, az" 到 dma_acc_result,
"count,t,gx,gy,gz" 到 dma_gravity_result, "count,t,ra,rb,rg" 到 dma_rate_result。
- sensorInstance.cdoa(deviceorientation): push "count,t,alpha,beta,gamma" 到 doa_result。
- traceInstance.cta(touchmove): push "teCount,1,t,clientX,clientY" 到 teResult并 resetDoaAndDmaThrottle
—— 所以传感器在拖拽期与触摸同步采样(这是服务端 350014 校验的相关性)。
- 值经 _(e)=parseFloat(e).toFixed(2);时间戳 t = Z()-start_time (ms)。
- 限流: dma_count_lmt=10, doa_count_lmt=10, throttle_lmt=2 (touchmove 重置)。
服务端 350014 = 校验 captchaExtraParam 里的传感器(key18/20/22/24)是否像真人拖拽。
本模块用真人手持滑动的传感器签名(见 memory: 重力 Z:2.96↔12.55/Y≤9.46, 线性加速度±0.5,
陀螺仪±0.5 rad/s)合成与 trajectory 时间相关的样本。
"""
from __future__ import annotations
import json
import math
import random
from typing import Any
def _f(v: float) -> str:
"""复刻 JS 的 _(e): parseFloat(e).toFixed(2),非法→-1。"""
try:
if v is None or (isinstance(v, float) and (math.isnan(v) or math.isinf(v))):
return "-1"
return f"{float(v):.2f}"
except (TypeError, ValueError):
return "-1"
def _build_sensor_series(
duration_ms: float,
n_samples: int,
*,
rnd: random.Random,
) -> dict[str, list[str]]:
"""生成 doa/acc/gravity/rate 四组传感器样本(时间跨 duration_ms, 与拖拽同步)。
关键保真点(对齐 memory 真人签名 + 物理):
- dma_gravity_result = accelerationIncludingGravity = 重力 + 线性加速度。
静态 |.|≈9.8;拖拽期 |.|>9.8(因含 linacc)memory 实测 Z:2.96↔12.55, Y≤9.46。
- dma_acc_result = 线性加速度(去重力):静态 ~0拖拽中段阻尼振荡 ±0.5。
- dma_rate_result = 陀螺仪:静态 ~0拖拽中段 ±0.5 rad/s。
- 噪声用高斯(非均匀),爆发与拖拽速度同相(余弦缓动导数)。
返回 {acc, gravity, rate, doa} 各为 "count,t,x,y,z" 字符串列表。
"""
acc: list[str] = []
grav: list[str] = []
rate: list[str] = []
doa: list[str] = []
total = 0
# 拖拽中手倾斜: theta 在中段大幅摆动(重力向量在 Y/Z 间扫)
tilt_amp = math.radians(55)
for i in range(n_samples):
frac = (i + 0.5) / n_samples
t = round(frac * duration_ms)
# 速度包络: 余弦缓动的导数 ~ sin(pi*frac),中段最大;静态两端 ~0
env = math.sin(math.pi * frac)
# 倾斜角: 中段摆动 + 慢漂
theta = tilt_amp * math.sin(frac * math.pi * 2.0) * (0.5 + 0.5 * env)
# 线性加速度(去重力): 中段阻尼振荡 ±0.5,与速度同相
ax = rnd.gauss(0, 0.12) + 0.45 * env * math.sin(frac * math.pi * 6 + rnd.random())
ay = rnd.gauss(0, 0.12) + 0.50 * env * math.cos(frac * math.pi * 5)
az = rnd.gauss(0, 0.10) + 0.35 * env * math.sin(frac * math.pi * 4)
# 重力向量(纯重力,|.|=9.8): 手机竖持基线 Y≈9.8,倾斜后向 Z 分流
gxg = rnd.gauss(0, 0.15)
gyg = 9.8 * math.cos(theta)
gzg = 9.8 * math.sin(theta)
# accelerationIncludingGravity = 重力 + 线性加速度 (=> 拖拽期 |.|>9.8)
gx = gxg + ax
gy = gyg + ay
gz = gzg + az
# 陀螺仪(角速度)与倾斜变化率同相±0.5 rad/s
ra = rnd.gauss(0, 0.05) + 0.45 * env * math.sin(frac * math.pi * 5)
rb = rnd.gauss(0, 0.05) + 0.40 * env * math.cos(frac * math.pi * 4)
rg = rnd.gauss(0, 0.06) + 0.50 * env * math.sin(frac * math.pi * 6)
# orientationalpha 罗盘稳定beta=倾斜角(度)gamma 随手持微动
alpha = 180 + rnd.gauss(0, 2)
beta = math.degrees(theta) + rnd.gauss(0, 1.5)
gamma = rnd.gauss(0, 2.0) * (0.3 + env)
c = total
acc.append(f"{c},{t},{_f(ax)},{_f(ay)},{_f(az)}")
grav.append(f"{c},{t},{_f(gx)},{_f(gy)},{_f(gz)}")
rate.append(f"{c},{t},{_f(ra)},{_f(rb)},{_f(rg)}")
doa.append(f"{c},{t},{_f(alpha)},{_f(beta)},{_f(gamma)}")
total += 1
return {"acc": acc, "gravity": grav, "rate": rate, "doa": doa}
def _build_touch_series(
start_x: float,
drag: float,
y: float,
duration_ms: float,
n_moves: int,
*,
rnd: random.Random,
) -> list[str]:
"""teResult: "teCount,1,t,clientX,clientY" 列表(最多 10 move), x 从 start_x 到 start_x+drag。"""
out: list[str] = []
te_count = 0
for i in range(n_moves):
frac = (i + 1) / n_moves
ease = 0.5 * (1 - math.cos(math.pi * frac))
x = math.floor(start_x + drag * ease + rnd.uniform(-1.5, 1.5))
yy = math.floor(y + rnd.uniform(-2.0, 2.0))
t = round(frac * duration_ms)
out.append(f"{te_count},1,{t},{x},{yy}")
te_count += 1
return out
# Android WebView 指纹常量(真机 OnePlus PJZ110 / Android 16)
_UA = (
"Mozilla/5.0 (Linux; Android 16; PJZ110 Build/UKQ1.230917.001; wv) "
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.134 "
"Mobile Safari/537.36"
)
def build_captcha_extra_param(
*,
drag_physical: float,
slider_x: float,
slider_y: float,
duration_ms: float = 820.0,
seed: int | None = None,
) -> dict[str, Any]:
"""生成完整 key1-key39 captchaExtraParam, 传感器+触摸与拖拽时间相关。
drag_physical: 滑块按钮物理位移(像素, DPR=3 空间) —— 与 trajectory 终点一致。
slider_x/slider_y: 按钮起始物理坐标(clientX/Y 空间)。
duration_ms: 拖拽总时长(与 build_trajectory 对齐)。
"""
rnd = random.Random(seed)
n_sensor = 10 # dma/doa count_lmt
n_touch = 10 # tmeCountLmt
series = _build_sensor_series(duration_ms, n_sensor, rnd=rnd)
touch = _build_touch_series(slider_x, drag_physical, slider_y, duration_ms, n_touch, rnd=rnd)
# key1=G(version常量, 近似), key2=collect 时间戳, key35=canvas 指纹(近似), key36=U()async(近似)
return {
"key1": "0.2.0",
"key2": 1785061273985, # Z() 收集时刻(近似, 服务端宽容)
"key3": _UA,
"key4": "20030107", # navigator.productSub (Chrome)
"key5": "zh-cn",
"key6": "Gecko",
"key7": 1080, "key8": 2376, "key9": 1080, "key10": 2376,
"key11": 915, "key12": 412, "key13": 2376, "key14": 1080,
"key15": "", # handleXAttri (近似)
"key16": 1, "key17": 1, # DeviceOrientation/DeviceMotion 支持
"key18": series["doa"],
"key19": {},
"key20": series["acc"],
"key21": {},
"key22": series["gravity"],
"key23": {},
"key24": series["rate"],
"key25": {},
"key26": {
"key27": [], # mouseEventInfo
"key28": touch, # touchEventInfo (拖拽轨迹)
"key29": [], # pointerEventInfo
"key30": [], # keyEventInfo
"key31": {}, "key32": {}, "key33": {}, "key34": {},
},
"key35": "", # canvas 指纹 (近似)
"key36": "", # U() async (近似)
"key37": 3, # devicePixelRatio
"key38": False, # webdriver
"key39": 8, # hardwareConcurrency
}
if __name__ == "__main__":
import sys
ep = build_captcha_extra_param(drag_physical=706, slider_x=60, slider_y=840, seed=42)
print(json.dumps(ep, ensure_ascii=False)[:800])
print("...")
print("gravity samples:", ep["key22"][:3])
print("touch samples:", ep["key26"]["key28"][:3])