feat(kg): 账密登录与 20028 验证码处理——/v9/login_by_pwd、ssa-code 挑战链、行为指纹模拟、dfid 正式注册
This commit is contained in:
1 parent
e796e622f2
commit
3b40ba4c8f
3 files changed
+366
-3
No files matched your search
@@ -0,0 +1,116 @@
|
||||
// 酷狗行为指纹模拟(sid/edt 生成)——移植 MakcRe/KuGouMusicApi util/generate_simulate.js
|
||||
// 用途:20028 二次验证时替代浏览器 WASM 上报行为数据。
|
||||
// 协议:明文 = mid=..;userid=..;dfid=..;webgl=..;webdriver=0;ts=..;data=<事件流>
|
||||
// edt = AES-128-CBC(明文, key=md5(rand16)[0:16], iv='kugousecurity123') → Base64
|
||||
// sid = RSA-OAEP-SHA256(key) → Base64(公钥取自酷狗 WASM 二进制)
|
||||
// 事件流:贝塞尔曲线鼠标轨迹(type 3) + 滚动(type 5) + 窗口(type 6),条目间以 : 分隔,
|
||||
// 每条事件后跟一条哨兵记录(时间差字段替换为接近 0xFFFFFFFF 的随机值)。
|
||||
|
||||
import crypto from 'node:crypto'
|
||||
|
||||
const md5 = (s: string) => crypto.createHash('md5').update(s).digest('hex')
|
||||
|
||||
const SIM_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoW2+Ylo8ALePSQTP0xBF
|
||||
lFmEOHvBD9tS+s7DBlfKEu3RzzvZTaX1JtYbX4+AVUqj6ARz8IM+CKByqGFvbHN/
|
||||
W64XxNI+q7z36ajCL3VTJ2W5G9MCJitc6oGbire4NQfhaEq0nC+hxBWQvCbIFflA
|
||||
2ItrLUbSU7z1bHA/a+jlQm4OWvY+IKnTryOJTPuT1yNOVjbJ8wBLKy2DgQr9pPqW
|
||||
PmEQtGpR5IM9V8Kao6PaSdKYOWGbX3i2+RzIKhvZUxxtJwdVbqPlDPlW9h4/xIBc
|
||||
56Lgvr4aIl8nFtwbj4UJVUTFuGrs0tY9H/tXvZ22dUCKuGxW/gW7ZF+gXz6vHtYa
|
||||
rQIDAQAB
|
||||
-----END PUBLIC KEY-----`
|
||||
const SIM_IV = 'kugousecurity123'
|
||||
|
||||
const ri = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min
|
||||
const randomString = (n: number) => {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return Array.from({ length: n }, () => chars[Math.floor(Math.random() * chars.length)]).join('')
|
||||
}
|
||||
|
||||
let sentinel = 0xffffffff - ri(0, 20)
|
||||
|
||||
const f3 = (t: number | string, i: number, x: number, y: number) => `3,${t},${i},${x},${y}`
|
||||
const f5 = (t: number | string, i: number) => `5,${t},${i}`
|
||||
const f6 = (t: number | string, i: number, x: number, y: number) => `6,${t},${i},${x},${y}`
|
||||
|
||||
// 三阶贝塞尔鼠标轨迹:随机控制点 + 起步抖动大、后程收敛
|
||||
const bezierPath = (sx: number, sy: number, ex: number, ey: number, n: number) => {
|
||||
const c1x = sx + (ex - sx) * 0.3 + ri(-80, 80)
|
||||
const c1y = sy + (ey - sy) * 0.2 + ri(-60, 60)
|
||||
const c2x = sx + (ex - sx) * 0.7 + ri(-60, 60)
|
||||
const c2y = sy + (ey - sy) * 0.8 + ri(-40, 40)
|
||||
const pts: Array<{ x: number, y: number }> = []
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const t = i / n
|
||||
const u = 1 - t
|
||||
const x = u * u * u * sx + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ex
|
||||
const y = u * u * u * sy + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ey
|
||||
const jitter = Math.max(0.5, 3 - t * 2.5)
|
||||
pts.push({ x: x + (Math.random() - 0.5) * jitter, y: y + (Math.random() - 0.5) * jitter })
|
||||
}
|
||||
return pts
|
||||
}
|
||||
|
||||
const generateEdtData = ({ startX, startY, endX, endY, mousePoints }: {
|
||||
startX: number, startY: number, endX: number, endY: number, mousePoints: number
|
||||
}) => {
|
||||
const entries: string[] = []
|
||||
let ts = 0
|
||||
let ei = 0
|
||||
|
||||
entries.push(f5(0, 0), f5(sentinel, 0), f5(0, 0), f5(sentinel, 0))
|
||||
|
||||
ts += ri(5, 20)
|
||||
entries.push(f6(ts, ei, 750, 500), f6(sentinel, ei, 750, 500))
|
||||
ei++
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
ts += ri(80, 600)
|
||||
entries.push(f5(ts, ei), f5(sentinel, ei))
|
||||
ei++
|
||||
}
|
||||
|
||||
const path = bezierPath(startX, startY, endX, endY, mousePoints)
|
||||
let si = 0
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const { x, y } = path[i]
|
||||
ts += ri(8, 50)
|
||||
const rx = Math.round(x)
|
||||
const ry = Math.round(y)
|
||||
entries.push(f3(ts, si, rx, ry), f3(sentinel, si, rx, ry))
|
||||
if (i > 0 && i % 12 === 0) {
|
||||
ts += ri(20, 60)
|
||||
entries.push(f5(ts, ei), f5(sentinel, ei))
|
||||
ei++
|
||||
}
|
||||
si = (si + 1) % 2
|
||||
}
|
||||
|
||||
ts += ri(5, 30)
|
||||
entries.push(f3(ts, 1, Math.round(endX + ri(-5, 5)), Math.round(endY + ri(-5, 5))), f3(sentinel, 1, Math.round(endX), Math.round(endY)))
|
||||
|
||||
return entries.join(':')
|
||||
}
|
||||
|
||||
/** 生成模拟行为指纹(sid/edt),验证接口 /v4/verify_user_info 原样上送 */
|
||||
export const generateSimulate = (mid: string | number, userid: string | number, dfid: string, webglHash?: string) => {
|
||||
sentinel = 0xffffffff - ri(0, 20)
|
||||
const key = md5(randomString(16)).slice(0, 16)
|
||||
const points = ri(30, 60)
|
||||
const startX = ri(200, 600)
|
||||
const startY = ri(200, 500)
|
||||
const endX = ri(500, 700)
|
||||
const endY = ri(80, 150)
|
||||
const webgl = webglHash || md5(randomString(32))
|
||||
const plaintext = `mid=${mid || 0};userid=${userid || 0};dfid=${dfid || 0};webgl=${webgl};webdriver=0;ts=${Date.now()};data=${generateEdtData({ startX, startY, endX, endY, mousePoints: points })}`
|
||||
|
||||
const cipher = crypto.createCipheriv('aes-128-cbc', Buffer.from(key, 'utf8'), Buffer.from(SIM_IV, 'utf8'))
|
||||
const edt = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]).toString('base64')
|
||||
const sid = crypto.publicEncrypt(
|
||||
{ key: SIM_PUBLIC_KEY, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, oaepHash: 'sha256' },
|
||||
Buffer.from(key, 'utf8'),
|
||||
).toString('base64')
|
||||
return { edt, sid }
|
||||
}
|
||||
|
||||
export default { generateSimulate }
|
||||
Reference in new issue
Block a user