Files
MusicServer/sdk/auth/kgSimulate.ts
T

117 lines
4.8 KiB
TypeScript

// 酷狗行为指纹模拟(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 }