完成网络音乐、工具页面与安装器体验升级
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
/**
|
||||
* @fileoverview 酷狗音乐 API 行为指纹模拟生成器(Node.js 版)
|
||||
*
|
||||
* 本模块用于在服务端生成模拟的用户行为指纹数据,替代浏览器端 WASM 的功能。
|
||||
* 主要用于自动化请求场景(如批量登录、接口调用等),绕过酷狗的行为检测机制。
|
||||
*
|
||||
* 核心功能:
|
||||
* 1. 生成模拟的鼠标移动轨迹(贝塞尔曲线 + 随机抖动)
|
||||
* 2. 生成模拟的页面交互事件(滚动、窗口 resize 等)
|
||||
* 3. 使用 AES-128-CBC 加密行为数据得到 EDT(Encrypted Data Token)
|
||||
* 4. 使用 RSA-OAEP SHA-256 加密 AES 密钥得到 SID(Session ID)
|
||||
* 5. 服务端用 RSA 私钥解密 SID 得到 AES 密钥,再用 AES 密钥解密 EDT 还原行为数据
|
||||
*
|
||||
* 加密方案:
|
||||
* 明文(行为数据)→ AES-128-CBC 加密 → EDT(Base64)
|
||||
* AES 密钥 → RSA-OAEP SHA-256 加密 → SID(Base64)
|
||||
*
|
||||
* @module generate_simulate
|
||||
* @requires crypto-js - AES 加密库
|
||||
* @requires node-forge - RSA 加密库
|
||||
* @requires ./util - 工具函数(randomString)
|
||||
*/
|
||||
|
||||
const { randomString } = require('./util');
|
||||
|
||||
const CryptoJS = require('crypto-js');
|
||||
const forge = require('node-forge');
|
||||
|
||||
/**
|
||||
* RSA 公钥(PEM 格式)
|
||||
* 从酷狗 WASM 二进制中提取的 SPKI 公钥,用于 RSA-OAEP SHA-256 加密 AES 密钥
|
||||
* 算法: RSA-2048,公钥指数 65537 (0x10001)
|
||||
*
|
||||
* 服务端持有对应的私钥,用于解密 SID 获取 AES 密钥
|
||||
*/
|
||||
const publicKey = `-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoW2+Ylo8ALePSQTP0xBF\nlFmEOHvBD9tS+s7DBlfKEu3RzzvZTaX1JtYbX4+AVUqj6ARz8IM+CKByqGFvbHN/\nW64XxNI+q7z36ajCL3VTJ2W5G9MCJitc6oGbire4NQfhaEq0nC+hxBWQvCbIFflA\n2ItrLUbSU7z1bHA/a+jlQm4OWvY+IKnTryOJTPuT1yNOVjbJ8wBLKy2DgQr9pPqW\nPmEQtGpR5IM9V8Kao6PaSdKYOWGbX3i2+RzIKhvZUxxtJwdVbqPlDPlW9h4/xIBc\n56Lgvr4aIl8nFtwbj4UJVUTFuGrs0tY9H/tXvZ22dUCKuGxW/gW7ZF+gXz6vHtYa\nrQIDAQAB\n-----END PUBLIC KEY-----`;
|
||||
|
||||
/**
|
||||
* AES 初始化向量(固定值)
|
||||
* ASCII 解码为 "kugousecurity123"
|
||||
* 与浏览器端 WASM 中硬编码的 IV 一致,每次加密都使用相同的 IV
|
||||
* @type {string}
|
||||
*/
|
||||
const iv = 'kugousecurity123';
|
||||
|
||||
/**
|
||||
* 哨兵值(接近 0xFFFFFFFF 的随机值)
|
||||
* WASM 中每条事件记录后都会跟一条哨兵记录,用于标记事件结束或表示"无数据"
|
||||
* 每次调用 generateSimulate 时会重新生成,增加指纹随机性
|
||||
* @type {number}
|
||||
*/
|
||||
let SENTINEL = 0xffffffff - Math.floor(Math.random() * 20);
|
||||
|
||||
/**
|
||||
* 生成 EDT 中的 data 字段(用户行为指纹数据)
|
||||
*
|
||||
* 模拟真实用户在页面上的交互行为,包括:
|
||||
* - 窗口加载/resize 事件(type 6)
|
||||
* - 页面滚动事件(type 5)
|
||||
* - 鼠标移动轨迹(type 3,贝塞尔曲线生成)
|
||||
*
|
||||
* 事件编码格式:各条目用冒号 `:` 分隔,每个条目的字段用逗号 `,` 分隔
|
||||
* - type-3(鼠标移动): "3,时间差,子索引,X,Y"
|
||||
* - type-5(滚动/计时): "5,时间差,事件索引"
|
||||
* - type-6(窗口事件): "6,时间差,事件索引,宽,高"
|
||||
* - 哨兵记录: 时间差字段替换为 SENTINEL 值
|
||||
*
|
||||
* @param {Object} opts - 配置项
|
||||
* @param {number} opts.startX - 鼠标起点 X 坐标
|
||||
* @param {number} opts.startY - 鼠标起点 Y 坐标
|
||||
* @param {number} opts.endX - 鼠标终点 X 坐标
|
||||
* @param {number} opts.endY - 鼠标终点 Y 坐标
|
||||
* @param {number} opts.mousePoints - 鼠标轨迹采样点数
|
||||
* @returns {string} 编码后的 data 字段字符串,各事件条目用 `:` 分隔
|
||||
*/
|
||||
function generateEDTData(opts) {
|
||||
const { startX, startY, endX, endY, mousePoints } = opts;
|
||||
const entries = []; // 所有事件条目
|
||||
let ts = 0; // 累计时间戳(毫秒),从 0 开始递增
|
||||
let ei = 0; // 全局事件索引(用于 type-5/6 事件的标识)
|
||||
|
||||
// --- 初始化: 两个 type-5 零事件 ---
|
||||
// 模拟 WASM 启动时记录的初始事件,每个事件后跟一条哨兵记录
|
||||
entries.push(f5(0, 0));
|
||||
entries.push(fs5(0));
|
||||
entries.push(f5(0, 0));
|
||||
entries.push(fs5(0));
|
||||
|
||||
// --- 窗口事件 (type 6) ---
|
||||
// 模拟窗口加载/resize 事件,窗口尺寸设为 750x500(模拟移动端页面)
|
||||
ts += ri(5, 20); // 随机延迟 5-20ms(模拟页面加载耗时)
|
||||
entries.push(f6(ts, ei, 750, 500)); // 窗口事件记录
|
||||
entries.push(fs6(ei, 750, 500)); // 对应的哨兵记录
|
||||
ei++;
|
||||
|
||||
// --- 滚动事件 (type 5) ---
|
||||
// 模拟用户滚动页面的行为(3 次滚动,间隔不均匀)
|
||||
for (let i = 0; i < 3; i++) {
|
||||
ts += ri(80, 600); // 滚动间隔 80-600ms(模拟不均匀的滚动节奏)
|
||||
entries.push(f5(ts, ei));
|
||||
entries.push(fs5(ei));
|
||||
ei++;
|
||||
}
|
||||
|
||||
// --- 鼠标轨迹 (type 3) ---
|
||||
// 用三阶贝塞尔曲线生成平滑的鼠标移动路径
|
||||
const path = bezierPath(startX, startY, endX, endY, mousePoints);
|
||||
let si = 0; // 子索引(0 或 1,交替变化)
|
||||
for (let i = 0; i < path.length; i++) {
|
||||
const { x, y } = path[i];
|
||||
ts += ri(8, 50); // 鼠标移动间隔 8-50ms
|
||||
entries.push(f3(ts, si, Math.round(x), Math.round(y)));
|
||||
entries.push(fs3(si, Math.round(x), Math.round(y)));
|
||||
|
||||
// 每隔 12 帧插入一个滚动事件,模拟边滚动边移动鼠标的真实行为
|
||||
if (i > 0 && i % 12 === 0) {
|
||||
ts += ri(20, 60);
|
||||
entries.push(f5(ts, ei));
|
||||
entries.push(fs5(ei));
|
||||
ei++;
|
||||
}
|
||||
si = (si + 1) % 2; // 子索引在 0 和 1 之间交替
|
||||
}
|
||||
|
||||
// --- 结束事件 ---
|
||||
// 最后一个鼠标位置,带微小随机偏移(模拟点击前的微调)
|
||||
ts += ri(5, 30);
|
||||
entries.push(f3(ts, 1, Math.round(endX + ri(-5, 5)), Math.round(endY + ri(-5, 5))));
|
||||
entries.push(fs3(1, Math.round(endX), Math.round(endY)));
|
||||
|
||||
return entries.join(':');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 贝塞尔曲线鼠标路径生成
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 用三阶贝塞尔曲线生成模拟真人的鼠标移动路径
|
||||
*
|
||||
* 真人鼠标轨迹特点:
|
||||
* - 不是直线,有弧度和加速减速
|
||||
* - 有微小抖动(手抖),起步时抖动大,移动后趋于稳定
|
||||
* - 起步慢、中间快、结束减速(由贝塞尔曲线的参数 t 均匀采样自然实现)
|
||||
*
|
||||
* 三阶贝塞尔公式: B(t) = (1-t)³·P0 + 3(1-t)²t·P1 + 3(1-t)t²·P2 + t³·P3
|
||||
* 其中 P0=起点, P3=终点, P1/P2=两个随机控制点
|
||||
*
|
||||
* @param {number} sx - 起点 X
|
||||
* @param {number} sy - 起点 Y
|
||||
* @param {number} ex - 终点 X
|
||||
* @param {number} ey - 终点 Y
|
||||
* @param {number} n - 采样点数(越多轨迹越平滑)
|
||||
* @returns {Array<{x:number, y:number}>} 路径点数组,长度为 n+1
|
||||
*/
|
||||
function bezierPath(sx, sy, ex, ey, n) {
|
||||
// 生成两个随机控制点,使路径不是直线而是有弧度的曲线
|
||||
// 控制点在起点到终点的连线附近随机偏移
|
||||
const c1x = sx + (ex - sx) * 0.3 + ri(-80, 80); // 第一个控制点 X(起点 30% 处 + 随机偏移)
|
||||
const c1y = sy + (ey - sy) * 0.2 + ri(-60, 60); // 第一个控制点 Y(起点 20% 处 + 随机偏移)
|
||||
const c2x = sx + (ex - sx) * 0.7 + ri(-60, 60); // 第二个控制点 X(起点 70% 处 + 随机偏移)
|
||||
const c2y = sy + (ey - sy) * 0.8 + ri(-40, 40); // 第二个控制点 Y(起点 80% 处 + 随机偏移)
|
||||
|
||||
const pts = [];
|
||||
for (let i = 0; i <= n; i++) {
|
||||
const t = i / n; // 参数 t 从 0 到 1,均匀采样
|
||||
const u = 1 - t; // 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;
|
||||
|
||||
// 抖动幅度: 起点大(3px),逐渐减小到 0.5px(模拟起步时手抖,移动后趋于稳定)
|
||||
const jitter = Math.max(0.5, 3 - t * 2.5);
|
||||
pts.push({
|
||||
x: x + (Math.random() - 0.5) * jitter, // X 方向随机抖动
|
||||
y: y + (Math.random() - 0.5) * jitter, // Y 方向随机抖动
|
||||
});
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 事件记录格式化函数
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 格式化 type-3 事件(鼠标/触摸移动)
|
||||
* @param {number} t - 时间差(毫秒)
|
||||
* @param {number} i - 子索引(0 或 1,交替变化)
|
||||
* @param {number} x - 鼠标 X 坐标
|
||||
* @param {number} y - 鼠标 Y 坐标
|
||||
* @returns {string} 格式: "3,时间差,子索引,X,Y"
|
||||
*/
|
||||
function f3(t, i, x, y) {
|
||||
return `3,${t},${i},${x},${y}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 type-5 事件(滚动/计时)
|
||||
* @param {number} t - 时间差(毫秒)
|
||||
* @param {number} i - 事件索引
|
||||
* @returns {string} 格式: "5,时间差,事件索引"
|
||||
*/
|
||||
function f5(t, i) {
|
||||
return `5,${t},${i}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 type-6 事件(窗口事件)
|
||||
* @param {number} t - 时间差(毫秒)
|
||||
* @param {number} i - 事件索引
|
||||
* @param {number} x - 窗口宽度
|
||||
* @param {number} y - 窗口高度
|
||||
* @returns {string} 格式: "6,时间差,事件索引,宽,高"
|
||||
*/
|
||||
function f6(t, i, x, y) {
|
||||
return `6,${t},${i},${x},${y}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 type-3 哨兵记录(鼠标事件结束标记)
|
||||
* 哨兵记录的时间差字段使用 SENTINEL 值,标记该类型事件序列的结束
|
||||
* @param {number} i - 子索引
|
||||
* @param {number} x - X 坐标
|
||||
* @param {number} y - Y 坐标
|
||||
* @returns {string} 格式: "3,SENTINEL,子索引,X,Y"
|
||||
*/
|
||||
function fs3(i, x, y) {
|
||||
return `3,${SENTINEL},${i},${x},${y}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 type-5 哨兵记录(滚动事件结束标记)
|
||||
* @param {number} i - 事件索引
|
||||
* @returns {string} 格式: "5,SENTINEL,事件索引"
|
||||
*/
|
||||
function fs5(i) {
|
||||
return `5,${SENTINEL},${i}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化 type-6 哨兵记录(窗口事件结束标记)
|
||||
* @param {number} i - 事件索引
|
||||
* @param {number} x - 窗口宽度
|
||||
* @param {number} y - 窗口高度
|
||||
* @returns {string} 格式: "6,SENTINEL,事件索引,宽,高"
|
||||
*/
|
||||
function fs6(i, x, y) {
|
||||
return `6,${SENTINEL},${i},${x},${y}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 [min, max] 范围内的随机整数(包含两端)
|
||||
* @param {number} min - 最小值
|
||||
* @param {number} max - 最大值
|
||||
* @returns {number} 随机整数
|
||||
*/
|
||||
function ri(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 核心导出函数
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 生成模拟的 sid 和 edt 加密数据
|
||||
*
|
||||
* 完整流程:
|
||||
* 1. 生成随机 AES-128 密钥(16 字节,取 MD5 哈希的前 16 字符)
|
||||
* 2. 随机化鼠标轨迹参数(起点、终点、采样点数)
|
||||
* 3. 生成模拟行为数据(鼠标轨迹 + 滚动 + 窗口事件)
|
||||
* 4. 拼接完整明文: mid=xxx;userid=xxx;dfid=xxx;webgl=xxx;webdriver=0;ts=xxx;data=xxx
|
||||
* 5. AES-128-CBC 加密明文 → EDT(Base64)
|
||||
* 6. RSA-OAEP SHA-256 加密 AES 密钥 → SID(Base64)
|
||||
*
|
||||
* @param {string|number} mid - 设备 MID 标识,不存在时默认 0
|
||||
* @param {string|number} userid - 用户 ID,不存在时默认 0
|
||||
* @param {string|number} dfid - 设备指纹 ID(由 register_dev 接口返回),不存在时默认 0
|
||||
* @param {string} [webglHash] - WebGL 指纹哈希,不传时自动生成
|
||||
* @returns {{ edt: string, sid: string }} 加密后的数据对象
|
||||
* - edt: AES-128-CBC 加密后的行为数据(Base64)
|
||||
* - sid: RSA-OAEP 加密后的 AES 密钥(Base64)
|
||||
*/
|
||||
const generateSimulate = (mid, userid, dfid, webglHash) => {
|
||||
// 每次调用重新生成哨兵值,增加指纹随机性
|
||||
SENTINEL = 0xffffffff - Math.floor(Math.random() * 20);
|
||||
|
||||
// 生成随机 AES-128 密钥:先生成 16 字节随机字符串,取其 MD5 哈希的前 16 字符
|
||||
const key = CryptoJS.MD5(randomString(16)).toString(CryptoJS.enc.Hex).substring(0, 16);
|
||||
|
||||
// 随机化鼠标轨迹参数,使每次请求的行为指纹不同
|
||||
const points = ri(30, 60); // 鼠标轨迹采样点数(30~60 个点)
|
||||
const startX = ri(200, 600); // 鼠标起点 X(页面中部区域)
|
||||
const startY = ri(200, 500); // 鼠标起点 Y
|
||||
const endX = ri(500, 700); // 鼠标终点 X(登录按钮附近)
|
||||
const endY = ri(80, 150); // 鼠标终点 Y
|
||||
|
||||
// 参数默认值处理
|
||||
mid = mid || 0;
|
||||
userid = userid || 0;
|
||||
dfid = dfid || 0;
|
||||
webglHash = webglHash || generateWebGLHash();
|
||||
const ts = Date.now(); // 当前时间戳
|
||||
|
||||
// 生成行为数据(鼠标轨迹 + 滚动 + 窗口事件)
|
||||
const data = generateEDTData({ startX, startY, endX, endY, mousePoints: points });
|
||||
|
||||
// 拼接完整明文
|
||||
// 格式: mid=xxx;userid=xxx;dfid=xxx;webgl=xxx;webdriver=0;ts=xxx;data=xxx
|
||||
// - mid: 设备标识
|
||||
// - userid: 用户 ID
|
||||
// - dfid: 设备指纹 ID
|
||||
// - webgl: WebGL 渲染器指纹哈希
|
||||
// - webdriver: 是否为自动化驱动(0 表示否)
|
||||
// - ts: 时间戳
|
||||
// - data: 行为事件数据
|
||||
const sidPlaintext = `mid=${mid};userid=${userid};dfid=${dfid};webgl=${webglHash};webdriver=0;ts=${ts};data=${data}`;
|
||||
|
||||
console.log(sidPlaintext);
|
||||
|
||||
// 第1步: AES-128-CBC 加密行为指纹明文 → EDT
|
||||
// - 密钥: 随机生成的 16 字符字符串
|
||||
// - IV: 固定值 "kugousecurity123"
|
||||
// - 填充: PKCS7
|
||||
// - 输出: Base64 编码的密文
|
||||
const edtData = CryptoJS.AES.encrypt(sidPlaintext, CryptoJS.enc.Utf8.parse(key), {
|
||||
iv: CryptoJS.enc.Utf8.parse(iv),
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7,
|
||||
}).toString();
|
||||
|
||||
// 第2步: RSA-OAEP SHA-256 加密 AES 密钥 → SID
|
||||
// - 使用酷狗服务器的 RSA 公钥加密 AES 密钥
|
||||
// - 哈希算法: SHA-256
|
||||
// - MGF1 哈希: SHA-256(与主哈希一致)
|
||||
// - 输出: Base64 编码的密文
|
||||
const rsaKey = forge.pki.publicKeyFromPem(publicKey);
|
||||
|
||||
const encrypted = rsaKey.encrypt(key, 'RSA-OAEP', {
|
||||
md: forge.md.sha256.create(), // 主哈希算法
|
||||
mgf1: { md: forge.md.sha256.create() }, // MGF1 掩码生成函数的哈希算法
|
||||
});
|
||||
const ciphertext = forge.util.encode64(encrypted); // Base64 编码
|
||||
|
||||
// 返回 EDT(加密的行为数据)和 SID(加密的 AES 密钥)
|
||||
return { edt: edtData, sid: ciphertext };
|
||||
};
|
||||
|
||||
module.exports = { generateSimulate };
|
||||
Reference in New Issue
Block a user