mirror of
https://github.com/yuaotian/go-cursor-help.git
synced 2026-03-07 22:33:39 +00:00
fix(cursor-hook): 修复Cursor ID修改器的正则匹配安全问题 - 修改PowerShell脚本中的正则表达式,从泛匹配改为精确匹配b6函数名, 避免跨函数误替换导致main.js语法破坏 - 添加匹配长度检查,当匹配结果超过5000字符时跳过替换以防止文件损坏 - 更新警告信息,明确标注未找到目标函数时的行为 - 调整JavaScript Hook文件为纯CommonJS写法,避免ESM语法导致Cursor启动失败 ```
530 lines
19 KiB
JavaScript
530 lines
19 KiB
JavaScript
/**
|
||
* Cursor 设备标识符 Hook 模块
|
||
*
|
||
* 🎯 功能:从底层拦截所有设备标识符的生成,实现一劳永逸的机器码修改
|
||
*
|
||
* 🔧 Hook 点:
|
||
* 1. child_process.execSync - 拦截 REG.exe 查询 MachineGuid
|
||
* 2. crypto.createHash - 拦截 SHA256 哈希计算
|
||
* 3. @vscode/deviceid - 拦截 devDeviceId 获取
|
||
* 4. @vscode/windows-registry - 拦截注册表读取
|
||
* 5. os.networkInterfaces - 拦截 MAC 地址获取
|
||
*
|
||
* 📦 使用方式:
|
||
* 将此代码注入到 main.js 文件顶部(Sentry 初始化之后)
|
||
*
|
||
* ⚙️ 配置方式:
|
||
* 1. 环境变量:CURSOR_MACHINE_ID, CURSOR_MAC_MACHINE_ID, CURSOR_DEV_DEVICE_ID, CURSOR_SQM_ID
|
||
* 2. 配置文件:~/.cursor_ids.json
|
||
* 3. 自动生成:如果没有配置,则自动生成并持久化
|
||
*/
|
||
|
||
// ==================== 配置区域 ====================
|
||
// 使用 var 确保在 ES Module 环境中也能正常工作
|
||
var __cursor_hook_config__ = {
|
||
// 是否启用 Hook(设置为 false 可临时禁用)
|
||
enabled: true,
|
||
// 是否输出调试日志(设置为 true 可查看详细日志)
|
||
debug: false,
|
||
// 配置文件路径(相对于用户目录)
|
||
configFileName: '.cursor_ids.json',
|
||
// 标记:防止重复注入
|
||
injected: false
|
||
};
|
||
|
||
// ==================== Hook 实现 ====================
|
||
// 使用 IIFE 确保代码立即执行
|
||
(function() {
|
||
'use strict';
|
||
|
||
// 防止重复注入
|
||
if (globalThis.__cursor_patched__ || __cursor_hook_config__.injected) {
|
||
return;
|
||
}
|
||
globalThis.__cursor_patched__ = true;
|
||
__cursor_hook_config__.injected = true;
|
||
|
||
// 调试日志函数
|
||
const log = (...args) => {
|
||
if (__cursor_hook_config__.debug) {
|
||
console.log('[CursorHook]', ...args);
|
||
}
|
||
};
|
||
|
||
// ==================== ID 生成和管理 ====================
|
||
|
||
// 生成 UUID v4
|
||
const generateUUID = () => {
|
||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||
const r = Math.random() * 16 | 0;
|
||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||
return v.toString(16);
|
||
});
|
||
};
|
||
|
||
// 生成 64 位十六进制字符串(用于 machineId)
|
||
const generateHex64 = () => {
|
||
let hex = '';
|
||
for (let i = 0; i < 64; i++) {
|
||
hex += Math.floor(Math.random() * 16).toString(16);
|
||
}
|
||
return hex;
|
||
};
|
||
|
||
// 生成 MAC 地址格式的字符串
|
||
const generateMacAddress = () => {
|
||
const hex = '0123456789ABCDEF';
|
||
let mac = '';
|
||
for (let i = 0; i < 6; i++) {
|
||
if (i > 0) mac += ':';
|
||
mac += hex[Math.floor(Math.random() * 16)];
|
||
mac += hex[Math.floor(Math.random() * 16)];
|
||
}
|
||
return mac;
|
||
};
|
||
|
||
// 加载或生成 ID 配置
|
||
// 注意:该 Hook 由脚本注入的 Loader 通过 CommonJS(require) 方式加载,
|
||
// 为避免出现 import.meta 等仅 ESM 支持的语法导致 Cursor 启动期解析失败,这里保持纯 CommonJS 写法。
|
||
const loadOrGenerateIds = () => {
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const os = require('os');
|
||
|
||
const configPath = path.join(os.homedir(), __cursor_hook_config__.configFileName);
|
||
|
||
let ids = null;
|
||
|
||
// 尝试从环境变量读取
|
||
if (process.env.CURSOR_MACHINE_ID) {
|
||
ids = {
|
||
machineId: process.env.CURSOR_MACHINE_ID,
|
||
// machineGuid 用于模拟注册表 MachineGuid/IOPlatformUUID
|
||
machineGuid: process.env.CURSOR_MACHINE_GUID || generateUUID(),
|
||
macMachineId: process.env.CURSOR_MAC_MACHINE_ID || generateHex64(),
|
||
devDeviceId: process.env.CURSOR_DEV_DEVICE_ID || generateUUID(),
|
||
sqmId: process.env.CURSOR_SQM_ID || `{${generateUUID().toUpperCase()}}`,
|
||
macAddress: process.env.CURSOR_MAC_ADDRESS || generateMacAddress(),
|
||
sessionId: process.env.CURSOR_SESSION_ID || generateUUID(),
|
||
firstSessionDate: process.env.CURSOR_FIRST_SESSION_DATE || new Date().toISOString()
|
||
};
|
||
log('从环境变量加载 ID 配置');
|
||
return ids;
|
||
}
|
||
|
||
// 尝试从配置文件读取
|
||
try {
|
||
if (fs.existsSync(configPath)) {
|
||
const content = fs.readFileSync(configPath, 'utf8');
|
||
ids = JSON.parse(content);
|
||
// 补全缺失字段,保持向后兼容
|
||
let updated = false;
|
||
if (!ids.machineGuid) {
|
||
ids.machineGuid = generateUUID();
|
||
updated = true;
|
||
}
|
||
if (!ids.macAddress) {
|
||
ids.macAddress = generateMacAddress();
|
||
updated = true;
|
||
}
|
||
if (!ids.sessionId) {
|
||
ids.sessionId = generateUUID();
|
||
updated = true;
|
||
}
|
||
if (!ids.firstSessionDate) {
|
||
ids.firstSessionDate = new Date().toISOString();
|
||
updated = true;
|
||
}
|
||
if (updated) {
|
||
try {
|
||
fs.writeFileSync(configPath, JSON.stringify(ids, null, 2), 'utf8');
|
||
log('已补全并更新 ID 配置:', configPath);
|
||
} catch (e) {
|
||
log('补全配置文件失败:', e.message);
|
||
}
|
||
}
|
||
log('从配置文件加载 ID 配置:', configPath);
|
||
return ids;
|
||
}
|
||
} catch (e) {
|
||
log('读取配置文件失败:', e.message);
|
||
}
|
||
|
||
// 生成新的 ID
|
||
ids = {
|
||
machineId: generateHex64(),
|
||
machineGuid: generateUUID(),
|
||
macMachineId: generateHex64(),
|
||
devDeviceId: generateUUID(),
|
||
sqmId: `{${generateUUID().toUpperCase()}}`,
|
||
macAddress: generateMacAddress(),
|
||
sessionId: generateUUID(),
|
||
firstSessionDate: new Date().toISOString(),
|
||
createdAt: new Date().toISOString()
|
||
};
|
||
|
||
// 保存到配置文件
|
||
try {
|
||
fs.writeFileSync(configPath, JSON.stringify(ids, null, 2), 'utf8');
|
||
log('已生成并保存新的 ID 配置:', configPath);
|
||
} catch (e) {
|
||
log('保存配置文件失败:', e.message);
|
||
}
|
||
|
||
return ids;
|
||
};
|
||
|
||
// 加载 ID 配置
|
||
const __cursor_ids__ = loadOrGenerateIds();
|
||
// 统一获取 MachineGuid,缺失时回退到 machineId 的前 36 位
|
||
const getMachineGuid = () => __cursor_ids__.machineGuid || __cursor_ids__.machineId.substring(0, 36);
|
||
log('当前 ID 配置:', __cursor_ids__);
|
||
|
||
// ==================== Module Hook ====================
|
||
|
||
const Module = require('module');
|
||
const originalRequire = Module.prototype.require;
|
||
|
||
// 缓存已 Hook 的模块
|
||
const hookedModules = new Map();
|
||
|
||
Module.prototype.require = function(id) {
|
||
// 兼容 node: 前缀
|
||
const normalizedId = (typeof id === 'string' && id.startsWith('node:')) ? id.slice(5) : id;
|
||
const result = originalRequire.apply(this, arguments);
|
||
|
||
// 如果已经 Hook 过,直接返回缓存
|
||
if (hookedModules.has(normalizedId)) {
|
||
return hookedModules.get(normalizedId);
|
||
}
|
||
|
||
let hooked = result;
|
||
|
||
// Hook child_process 模块
|
||
if (normalizedId === 'child_process') {
|
||
hooked = hookChildProcess(result);
|
||
}
|
||
// Hook os 模块
|
||
else if (normalizedId === 'os') {
|
||
hooked = hookOs(result);
|
||
}
|
||
// Hook crypto 模块
|
||
else if (normalizedId === 'crypto') {
|
||
hooked = hookCrypto(result);
|
||
}
|
||
// Hook @vscode/deviceid 模块
|
||
else if (normalizedId === '@vscode/deviceid') {
|
||
hooked = hookDeviceId(result);
|
||
}
|
||
// Hook @vscode/windows-registry 模块
|
||
else if (normalizedId === '@vscode/windows-registry') {
|
||
hooked = hookWindowsRegistry(result);
|
||
}
|
||
|
||
// 缓存 Hook 结果
|
||
if (hooked !== result) {
|
||
hookedModules.set(normalizedId, hooked);
|
||
log(`已 Hook 模块: ${normalizedId}`);
|
||
}
|
||
|
||
return hooked;
|
||
};
|
||
|
||
// ==================== child_process Hook ====================
|
||
|
||
function hookChildProcess(cp) {
|
||
const originalExecSync = cp.execSync;
|
||
const originalExecFileSync = cp.execFileSync;
|
||
|
||
cp.execSync = function(command, options) {
|
||
const cmdStr = String(command).toLowerCase();
|
||
|
||
// 拦截 MachineGuid 查询
|
||
if (cmdStr.includes('reg') && cmdStr.includes('machineguid')) {
|
||
log('拦截 MachineGuid 查询');
|
||
// 返回格式化的注册表输出
|
||
return Buffer.from(`\r\n MachineGuid REG_SZ ${getMachineGuid()}\r\n`);
|
||
}
|
||
|
||
// 拦截 ioreg 命令 (macOS)
|
||
if (cmdStr.includes('ioreg') && cmdStr.includes('ioplatformexpertdevice')) {
|
||
log('拦截 IOPlatformUUID 查询');
|
||
return Buffer.from(`"IOPlatformUUID" = "${getMachineGuid().toUpperCase()}"`);
|
||
}
|
||
|
||
// 拦截 machine-id 读取 (Linux)
|
||
if (cmdStr.includes('machine-id') || cmdStr.includes('hostname')) {
|
||
log('拦截 machine-id 查询');
|
||
return Buffer.from(__cursor_ids__.machineId.substring(0, 32));
|
||
}
|
||
|
||
return originalExecSync.apply(this, arguments);
|
||
};
|
||
|
||
// 兼容 execFileSync(部分版本会直接调用可执行文件)
|
||
if (typeof originalExecFileSync === 'function') {
|
||
cp.execFileSync = function(file, args, options) {
|
||
const cmdStr = [file].concat(args || []).join(' ').toLowerCase();
|
||
|
||
if (cmdStr.includes('reg') && cmdStr.includes('machineguid')) {
|
||
log('拦截 MachineGuid 查询(execFileSync)');
|
||
return Buffer.from(`\r\n MachineGuid REG_SZ ${getMachineGuid()}\r\n`);
|
||
}
|
||
|
||
if (cmdStr.includes('ioreg') && cmdStr.includes('ioplatformexpertdevice')) {
|
||
log('拦截 IOPlatformUUID 查询(execFileSync)');
|
||
return Buffer.from(`"IOPlatformUUID" = "${getMachineGuid().toUpperCase()}"`);
|
||
}
|
||
|
||
if (cmdStr.includes('machine-id') || cmdStr.includes('hostname')) {
|
||
log('拦截 machine-id 查询(execFileSync)');
|
||
return Buffer.from(__cursor_ids__.machineId.substring(0, 32));
|
||
}
|
||
|
||
return originalExecFileSync.apply(this, arguments);
|
||
};
|
||
}
|
||
|
||
return cp;
|
||
}
|
||
|
||
// ==================== os Hook ====================
|
||
|
||
function hookOs(os) {
|
||
const originalNetworkInterfaces = os.networkInterfaces;
|
||
|
||
os.networkInterfaces = function() {
|
||
log('拦截 networkInterfaces 调用');
|
||
// 返回虚拟的网络接口,使用固定的 MAC 地址
|
||
return {
|
||
'Ethernet': [{
|
||
address: '192.168.1.100',
|
||
netmask: '255.255.255.0',
|
||
family: 'IPv4',
|
||
mac: __cursor_ids__.macAddress || '00:00:00:00:00:00',
|
||
internal: false
|
||
}]
|
||
};
|
||
};
|
||
|
||
return os;
|
||
}
|
||
|
||
// ==================== crypto Hook ====================
|
||
|
||
function hookCrypto(crypto) {
|
||
const originalCreateHash = crypto.createHash;
|
||
const originalRandomUUID = crypto.randomUUID;
|
||
|
||
// Hook createHash - 用于拦截 machineId 的 SHA256 计算
|
||
crypto.createHash = function(algorithm) {
|
||
const hash = originalCreateHash.apply(this, arguments);
|
||
|
||
if (algorithm.toLowerCase() === 'sha256') {
|
||
const originalUpdate = hash.update.bind(hash);
|
||
const originalDigest = hash.digest.bind(hash);
|
||
|
||
let inputData = '';
|
||
|
||
hash.update = function(data, encoding) {
|
||
inputData += String(data);
|
||
return originalUpdate(data, encoding);
|
||
};
|
||
|
||
hash.digest = function(encoding) {
|
||
// 检查是否是 machineId 相关的哈希计算
|
||
if (inputData.includes('MachineGuid') ||
|
||
inputData.includes('IOPlatformUUID') ||
|
||
inputData.length === 32 ||
|
||
inputData.length === 36) {
|
||
log('拦截 SHA256 哈希计算,返回固定 machineId');
|
||
if (encoding === 'hex') {
|
||
return __cursor_ids__.machineId;
|
||
}
|
||
return Buffer.from(__cursor_ids__.machineId, 'hex');
|
||
}
|
||
return originalDigest(encoding);
|
||
};
|
||
}
|
||
|
||
return hash;
|
||
};
|
||
|
||
// Hook randomUUID - 用于拦截 devDeviceId 生成
|
||
if (originalRandomUUID) {
|
||
let uuidCallCount = 0;
|
||
crypto.randomUUID = function() {
|
||
uuidCallCount++;
|
||
// 第一次调用返回固定的 devDeviceId
|
||
if (uuidCallCount <= 2) {
|
||
log('拦截 randomUUID 调用,返回固定 devDeviceId');
|
||
return __cursor_ids__.devDeviceId;
|
||
}
|
||
return originalRandomUUID.apply(this, arguments);
|
||
};
|
||
}
|
||
|
||
return crypto;
|
||
}
|
||
|
||
// ==================== @vscode/deviceid Hook ====================
|
||
|
||
function hookDeviceId(deviceIdModule) {
|
||
log('Hook @vscode/deviceid 模块');
|
||
|
||
return {
|
||
...deviceIdModule,
|
||
getDeviceId: async function() {
|
||
log('拦截 getDeviceId 调用');
|
||
return __cursor_ids__.devDeviceId;
|
||
}
|
||
};
|
||
}
|
||
|
||
// ==================== @vscode/windows-registry Hook ====================
|
||
|
||
function hookWindowsRegistry(registryModule) {
|
||
log('Hook @vscode/windows-registry 模块');
|
||
|
||
const originalGetStringRegKey = registryModule.GetStringRegKey;
|
||
|
||
return {
|
||
...registryModule,
|
||
GetStringRegKey: function(hive, path, name) {
|
||
// 拦截 MachineId 读取
|
||
if (name === 'MachineId' || path.includes('SQMClient')) {
|
||
log('拦截注册表 MachineId/SQMClient 读取');
|
||
return __cursor_ids__.sqmId;
|
||
}
|
||
// 拦截 MachineGuid 读取
|
||
if (name === 'MachineGuid' || path.includes('Cryptography')) {
|
||
log('拦截注册表 MachineGuid 读取');
|
||
return getMachineGuid();
|
||
}
|
||
return originalGetStringRegKey?.apply(this, arguments) || '';
|
||
}
|
||
};
|
||
}
|
||
|
||
// ==================== 动态 import Hook ====================
|
||
|
||
// Cursor 使用动态 import() 加载模块,我们需要 Hook 这些模块
|
||
// 由于 ES Module 的限制,我们通过 Hook 全局对象来实现
|
||
|
||
// 存储已 Hook 的动态导入模块
|
||
const hookedDynamicModules = new Map();
|
||
|
||
// Hook crypto 模块的动态导入
|
||
const hookDynamicCrypto = (cryptoModule) => {
|
||
if (hookedDynamicModules.has('crypto')) {
|
||
return hookedDynamicModules.get('crypto');
|
||
}
|
||
|
||
const hooked = { ...cryptoModule };
|
||
|
||
// Hook createHash
|
||
if (cryptoModule.createHash) {
|
||
const originalCreateHash = cryptoModule.createHash;
|
||
hooked.createHash = function(algorithm) {
|
||
const hash = originalCreateHash.apply(this, arguments);
|
||
|
||
if (algorithm.toLowerCase() === 'sha256') {
|
||
const originalDigest = hash.digest.bind(hash);
|
||
let inputData = '';
|
||
|
||
const originalUpdate = hash.update.bind(hash);
|
||
hash.update = function(data, encoding) {
|
||
inputData += String(data);
|
||
return originalUpdate(data, encoding);
|
||
};
|
||
|
||
hash.digest = function(encoding) {
|
||
// 检测 machineId 相关的哈希
|
||
if (inputData.includes('MachineGuid') ||
|
||
inputData.includes('IOPlatformUUID') ||
|
||
(inputData.length >= 32 && inputData.length <= 40)) {
|
||
log('动态导入: 拦截 SHA256 哈希');
|
||
return encoding === 'hex' ? __cursor_ids__.machineId : Buffer.from(__cursor_ids__.machineId, 'hex');
|
||
}
|
||
return originalDigest(encoding);
|
||
};
|
||
}
|
||
return hash;
|
||
};
|
||
}
|
||
|
||
hookedDynamicModules.set('crypto', hooked);
|
||
return hooked;
|
||
};
|
||
|
||
// Hook @vscode/deviceid 模块的动态导入
|
||
const hookDynamicDeviceId = (deviceIdModule) => {
|
||
if (hookedDynamicModules.has('@vscode/deviceid')) {
|
||
return hookedDynamicModules.get('@vscode/deviceid');
|
||
}
|
||
|
||
const hooked = {
|
||
...deviceIdModule,
|
||
getDeviceId: async () => {
|
||
log('动态导入: 拦截 getDeviceId');
|
||
return __cursor_ids__.devDeviceId;
|
||
}
|
||
};
|
||
|
||
hookedDynamicModules.set('@vscode/deviceid', hooked);
|
||
return hooked;
|
||
};
|
||
|
||
// Hook @vscode/windows-registry 模块的动态导入
|
||
const hookDynamicWindowsRegistry = (registryModule) => {
|
||
if (hookedDynamicModules.has('@vscode/windows-registry')) {
|
||
return hookedDynamicModules.get('@vscode/windows-registry');
|
||
}
|
||
|
||
const originalGetStringRegKey = registryModule.GetStringRegKey;
|
||
const hooked = {
|
||
...registryModule,
|
||
GetStringRegKey: function(hive, path, name) {
|
||
if (name === 'MachineId' || path?.includes('SQMClient')) {
|
||
log('动态导入: 拦截 SQMClient');
|
||
return __cursor_ids__.sqmId;
|
||
}
|
||
if (name === 'MachineGuid' || path?.includes('Cryptography')) {
|
||
log('动态导入: 拦截 MachineGuid');
|
||
return getMachineGuid();
|
||
}
|
||
return originalGetStringRegKey?.apply(this, arguments) || '';
|
||
}
|
||
};
|
||
|
||
hookedDynamicModules.set('@vscode/windows-registry', hooked);
|
||
return hooked;
|
||
};
|
||
|
||
// 将 Hook 函数暴露到全局,供后续使用
|
||
globalThis.__cursor_hook_dynamic__ = {
|
||
crypto: hookDynamicCrypto,
|
||
deviceId: hookDynamicDeviceId,
|
||
windowsRegistry: hookDynamicWindowsRegistry,
|
||
ids: __cursor_ids__
|
||
};
|
||
|
||
log('Cursor Hook 初始化完成');
|
||
log('machineId:', __cursor_ids__.machineId.substring(0, 16) + '...');
|
||
log('machineGuid:', getMachineGuid().substring(0, 16) + '...');
|
||
log('devDeviceId:', __cursor_ids__.devDeviceId);
|
||
log('sqmId:', __cursor_ids__.sqmId);
|
||
|
||
})();
|
||
|
||
// ==================== 导出配置(供外部使用) ====================
|
||
if (typeof module !== 'undefined' && module.exports) {
|
||
module.exports = { __cursor_hook_config__ };
|
||
}
|
||
|
||
// ==================== ES Module 兼容性 ====================
|
||
// 如果在 ES Module 环境中,也暴露配置
|
||
if (typeof globalThis !== 'undefined') {
|
||
globalThis.__cursor_hook_config__ = __cursor_hook_config__;
|
||
}
|