diff --git a/scripts/hook/cursor_hook.js b/scripts/hook/cursor_hook.js new file mode 100644 index 0000000..bdfcdb8 --- /dev/null +++ b/scripts/hook/cursor_hook.js @@ -0,0 +1,477 @@ +/** + * 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 配置 + // 注意:使用 createRequire 来支持 ES Module 环境 + const loadOrGenerateIds = () => { + // 在 ES Module 环境中,需要使用 createRequire 来加载 CommonJS 模块 + let fs, path, os; + try { + // 尝试使用 Node.js 内置模块 + const { createRequire } = require('module'); + const require2 = createRequire(import.meta?.url || __filename); + fs = require2('fs'); + path = require2('path'); + os = require2('os'); + } catch (e) { + // 回退到直接 require + fs = require('fs'); + path = require('path'); + 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, + macMachineId: process.env.CURSOR_MAC_MACHINE_ID || generateHex64(), + devDeviceId: process.env.CURSOR_DEV_DEVICE_ID || generateUUID(), + sqmId: process.env.CURSOR_SQM_ID || `{${generateUUID().toUpperCase()}}` + }; + log('从环境变量加载 ID 配置'); + return ids; + } + + // 尝试从配置文件读取 + try { + if (fs.existsSync(configPath)) { + const content = fs.readFileSync(configPath, 'utf8'); + ids = JSON.parse(content); + log('从配置文件加载 ID 配置:', configPath); + return ids; + } + } catch (e) { + log('读取配置文件失败:', e.message); + } + + // 生成新的 ID + ids = { + machineId: generateHex64(), + macMachineId: generateHex64(), + devDeviceId: generateUUID(), + sqmId: `{${generateUUID().toUpperCase()}}`, + macAddress: generateMacAddress(), + 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(); + 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) { + const result = originalRequire.apply(this, arguments); + + // 如果已经 Hook 过,直接返回缓存 + if (hookedModules.has(id)) { + return hookedModules.get(id); + } + + let hooked = result; + + // Hook child_process 模块 + if (id === 'child_process') { + hooked = hookChildProcess(result); + } + // Hook os 模块 + else if (id === 'os') { + hooked = hookOs(result); + } + // Hook crypto 模块 + else if (id === 'crypto') { + hooked = hookCrypto(result); + } + // Hook @vscode/deviceid 模块 + else if (id === '@vscode/deviceid') { + hooked = hookDeviceId(result); + } + // Hook @vscode/windows-registry 模块 + else if (id === '@vscode/windows-registry') { + hooked = hookWindowsRegistry(result); + } + + // 缓存 Hook 结果 + if (hooked !== result) { + hookedModules.set(id, hooked); + log(`已 Hook 模块: ${id}`); + } + + return hooked; + }; + + // ==================== child_process Hook ==================== + + function hookChildProcess(cp) { + const originalExecSync = cp.execSync; + + 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 ${__cursor_ids__.machineId.substring(0, 36)}\r\n`); + } + + // 拦截 ioreg 命令 (macOS) + if (cmdStr.includes('ioreg') && cmdStr.includes('ioplatformexpertdevice')) { + log('拦截 IOPlatformUUID 查询'); + return Buffer.from(`"IOPlatformUUID" = "${__cursor_ids__.machineId.substring(0, 36).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); + }; + + 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 __cursor_ids__.machineId.substring(0, 36); + } + 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 __cursor_ids__.machineId.substring(0, 36); + } + 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('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__; +} + diff --git a/scripts/hook/inject_hook_unix.sh b/scripts/hook/inject_hook_unix.sh new file mode 100644 index 0000000..86ebf05 --- /dev/null +++ b/scripts/hook/inject_hook_unix.sh @@ -0,0 +1,255 @@ +#!/bin/bash + +# ======================================== +# Cursor Hook 注入脚本 (macOS/Linux) +# ======================================== +# +# 🎯 功能:将 cursor_hook.js 注入到 Cursor 的 main.js 文件顶部 +# +# 📦 使用方式: +# chmod +x inject_hook_unix.sh +# ./inject_hook_unix.sh +# +# 参数: +# --rollback 回滚到原始版本 +# --force 强制重新注入 +# --debug 启用调试模式 +# +# ======================================== + +set -e + +# 颜色定义 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +# 参数解析 +ROLLBACK=false +FORCE=false +DEBUG=false + +for arg in "$@"; do + case $arg in + --rollback) ROLLBACK=true ;; + --force) FORCE=true ;; + --debug) DEBUG=true ;; + esac +done + +# 日志函数 +log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +log_error() { echo -e "${RED}[ERROR]${NC} $1"; } +log_debug() { if $DEBUG; then echo -e "${BLUE}[DEBUG]${NC} $1"; fi; } + +# 获取脚本所在目录 +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HOOK_SCRIPT="$SCRIPT_DIR/cursor_hook.js" + +# 获取 Cursor main.js 路径 +get_cursor_path() { + local paths=() + + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + paths=( + "/Applications/Cursor.app/Contents/Resources/app/out/main.js" + "$HOME/Applications/Cursor.app/Contents/Resources/app/out/main.js" + ) + else + # Linux + paths=( + "/opt/Cursor/resources/app/out/main.js" + "/usr/share/cursor/resources/app/out/main.js" + "$HOME/.local/share/cursor/resources/app/out/main.js" + "/snap/cursor/current/resources/app/out/main.js" + ) + fi + + for path in "${paths[@]}"; do + if [[ -f "$path" ]]; then + echo "$path" + return 0 + fi + done + + return 1 +} + +# 检查是否已注入 +check_already_injected() { + local main_js="$1" + grep -q "__cursor_patched__" "$main_js" 2>/dev/null +} + +# 备份原始文件 +backup_main_js() { + local main_js="$1" + local backup_dir="$(dirname "$main_js")/backups" + + mkdir -p "$backup_dir" + + local timestamp=$(date +%Y%m%d_%H%M%S) + local backup_path="$backup_dir/main.js.backup_$timestamp" + local original_backup="$backup_dir/main.js.original" + + # 创建原始备份(如果不存在) + if [[ ! -f "$original_backup" ]]; then + cp "$main_js" "$original_backup" + log_info "已创建原始备份: $original_backup" + fi + + cp "$main_js" "$backup_path" + log_info "已创建时间戳备份: $backup_path" + + echo "$original_backup" +} + +# 回滚到原始版本 +restore_main_js() { + local main_js="$1" + local backup_dir="$(dirname "$main_js")/backups" + local original_backup="$backup_dir/main.js.original" + + if [[ -f "$original_backup" ]]; then + cp "$original_backup" "$main_js" + log_info "已回滚到原始版本" + return 0 + else + log_error "未找到原始备份文件" + return 1 + fi +} + +# 关闭 Cursor 进程 +stop_cursor_process() { + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + pkill -x "Cursor" 2>/dev/null || true + pkill -x "Cursor Helper" 2>/dev/null || true + else + # Linux + pkill -f "cursor" 2>/dev/null || true + fi + + sleep 2 + log_info "Cursor 进程已关闭" +} + +# 注入 Hook 代码 +inject_hook() { + local main_js="$1" + local hook_script="$2" + + # 读取 Hook 脚本内容 + local hook_content=$(cat "$hook_script") + + # 创建临时文件 + local temp_file=$(mktemp) + + # 读取 main.js 并注入 Hook + # 在版权声明之后注入 + awk -v hook="$hook_content" ' + /^\*\// && !injected { + print + print "" + print "// ========== Cursor Hook 注入开始 ==========" + print hook + print "// ========== Cursor Hook 注入结束 ==========" + print "" + injected = 1 + next + } + { print } + ' "$main_js" > "$temp_file" + + # 替换原文件 + mv "$temp_file" "$main_js" + + return 0 +} + +# 主函数 +main() { + echo "" + echo -e "${BLUE}========================================${NC}" + echo -e "${BLUE} Cursor Hook 注入工具 (Unix) ${NC}" + echo -e "${BLUE}========================================${NC}" + echo "" + + # 获取 Cursor main.js 路径 + local main_js + main_js=$(get_cursor_path) || { + log_error "未找到 Cursor 安装路径" + log_error "请确保 Cursor 已正确安装" + exit 1 + } + log_info "找到 Cursor main.js: $main_js" + + # 回滚模式 + if $ROLLBACK; then + log_info "执行回滚操作..." + stop_cursor_process + if restore_main_js "$main_js"; then + log_info "回滚成功!" + else + log_error "回滚失败!" + exit 1 + fi + exit 0 + fi + + # 检查是否已注入 + if check_already_injected "$main_js" && ! $FORCE; then + log_warn "Hook 已经注入,无需重复操作" + log_info "如需强制重新注入,请使用 --force 参数" + exit 0 + fi + + # 检查 Hook 脚本是否存在 + if [[ ! -f "$HOOK_SCRIPT" ]]; then + log_error "未找到 cursor_hook.js 文件" + log_error "请确保 cursor_hook.js 与此脚本在同一目录" + exit 1 + fi + log_info "找到 Hook 脚本: $HOOK_SCRIPT" + + # 关闭 Cursor 进程 + stop_cursor_process + + # 备份原始文件 + log_info "正在备份原始文件..." + backup_main_js "$main_js" + + # 注入 Hook 代码 + log_info "正在注入 Hook 代码..." + if inject_hook "$main_js" "$HOOK_SCRIPT"; then + log_info "Hook 注入成功!" + else + log_error "Hook 注入失败!" + log_warn "正在回滚..." + restore_main_js "$main_js" + exit 1 + fi + + echo "" + echo -e "${GREEN}========================================${NC}" + echo -e "${GREEN} ✅ Hook 注入完成! ${NC}" + echo -e "${GREEN}========================================${NC}" + echo "" + log_info "现在可以启动 Cursor 了" + log_info "ID 配置文件位置: ~/.cursor_ids.json" + echo "" + echo -e "${YELLOW}提示:${NC}" + echo " - 如需回滚,请运行: ./inject_hook_unix.sh --rollback" + echo " - 如需强制重新注入,请运行: ./inject_hook_unix.sh --force" + echo " - 如需启用调试日志,请运行: ./inject_hook_unix.sh --debug" + echo "" +} + +# 执行主函数 +main + diff --git a/scripts/hook/inject_hook_win.ps1 b/scripts/hook/inject_hook_win.ps1 new file mode 100644 index 0000000..4fc8220 --- /dev/null +++ b/scripts/hook/inject_hook_win.ps1 @@ -0,0 +1,266 @@ +# ======================================== +# Cursor Hook 注入脚本 (Windows) +# ======================================== +# +# 🎯 功能:将 cursor_hook.js 注入到 Cursor 的 main.js 文件顶部 +# +# 📦 使用方式: +# 1. 以管理员权限运行 PowerShell +# 2. 执行: .\inject_hook_win.ps1 +# +# ⚠️ 注意事项: +# - 会自动备份原始 main.js 文件 +# - 支持回滚到原始版本 +# - Cursor 更新后需要重新注入 +# +# ======================================== + +param( + [switch]$Rollback, # 回滚到原始版本 + [switch]$Force, # 强制重新注入 + [switch]$Debug # 启用调试模式 +) + +# 颜色定义 +$RED = "`e[31m" +$GREEN = "`e[32m" +$YELLOW = "`e[33m" +$BLUE = "`e[34m" +$NC = "`e[0m" + +# 日志函数 +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + switch ($Level) { + "INFO" { Write-Host "$GREEN[INFO]$NC $Message" } + "WARN" { Write-Host "$YELLOW[WARN]$NC $Message" } + "ERROR" { Write-Host "$RED[ERROR]$NC $Message" } + "DEBUG" { if ($Debug) { Write-Host "$BLUE[DEBUG]$NC $Message" } } + } +} + +# 获取 Cursor 安装路径 +function Get-CursorPath { + $possiblePaths = @( + "$env:LOCALAPPDATA\Programs\cursor\resources\app\out\main.js", + "$env:LOCALAPPDATA\Programs\Cursor\resources\app\out\main.js", + "C:\Program Files\Cursor\resources\app\out\main.js", + "C:\Program Files (x86)\Cursor\resources\app\out\main.js" + ) + + foreach ($path in $possiblePaths) { + if (Test-Path $path) { + return $path + } + } + + return $null +} + +# 获取 Hook 脚本路径 +function Get-HookScriptPath { + $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + $hookPath = Join-Path $scriptDir "cursor_hook.js" + + if (Test-Path $hookPath) { + return $hookPath + } + + # 尝试从当前目录查找 + $currentDir = Get-Location + $hookPath = Join-Path $currentDir "cursor_hook.js" + + if (Test-Path $hookPath) { + return $hookPath + } + + return $null +} + +# 检查是否已注入 +function Test-AlreadyInjected { + param([string]$MainJsPath) + + $content = Get-Content $MainJsPath -Raw -Encoding UTF8 + return $content -match "__cursor_patched__" +} + +# 备份原始文件 +function Backup-MainJs { + param([string]$MainJsPath) + + $backupDir = Join-Path (Split-Path -Parent $MainJsPath) "backups" + if (-not (Test-Path $backupDir)) { + New-Item -ItemType Directory -Path $backupDir -Force | Out-Null + } + + $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" + $backupPath = Join-Path $backupDir "main.js.backup_$timestamp" + + # 检查是否有原始备份 + $originalBackup = Join-Path $backupDir "main.js.original" + if (-not (Test-Path $originalBackup)) { + Copy-Item $MainJsPath $originalBackup -Force + Write-Log "已创建原始备份: $originalBackup" + } + + Copy-Item $MainJsPath $backupPath -Force + Write-Log "已创建时间戳备份: $backupPath" + + return $originalBackup +} + +# 回滚到原始版本 +function Restore-MainJs { + param([string]$MainJsPath) + + $backupDir = Join-Path (Split-Path -Parent $MainJsPath) "backups" + $originalBackup = Join-Path $backupDir "main.js.original" + + if (Test-Path $originalBackup) { + Copy-Item $originalBackup $MainJsPath -Force + Write-Log "已回滚到原始版本" "INFO" + return $true + } else { + Write-Log "未找到原始备份文件" "ERROR" + return $false + } +} + +# 注入 Hook 代码 +function Inject-Hook { + param( + [string]$MainJsPath, + [string]$HookScriptPath + ) + + # 读取 Hook 脚本内容 + $hookContent = Get-Content $HookScriptPath -Raw -Encoding UTF8 + + # 读取 main.js 内容 + $mainContent = Get-Content $MainJsPath -Raw -Encoding UTF8 + + # 查找注入点:在 Sentry 初始化代码之后 + # Sentry 初始化代码特征: _sentryDebugIds + $sentryPattern = '(?<=\}\(\);)\s*(?=var\s+\w+\s*=\s*function)' + + if ($mainContent -match $sentryPattern) { + # 在 Sentry 初始化之后注入 + $injectionPoint = $mainContent.IndexOf('}();') + 4 + $newContent = $mainContent.Substring(0, $injectionPoint) + "`n`n// ========== Cursor Hook 注入开始 ==========`n" + $hookContent + "`n// ========== Cursor Hook 注入结束 ==========`n`n" + $mainContent.Substring($injectionPoint) + } else { + # 如果找不到 Sentry,直接在文件开头注入(在版权声明之后) + $copyrightEnd = $mainContent.IndexOf('*/') + 2 + if ($copyrightEnd -gt 2) { + $newContent = $mainContent.Substring(0, $copyrightEnd) + "`n`n// ========== Cursor Hook 注入开始 ==========`n" + $hookContent + "`n// ========== Cursor Hook 注入结束 ==========`n`n" + $mainContent.Substring($copyrightEnd) + } else { + $newContent = "// ========== Cursor Hook 注入开始 ==========`n" + $hookContent + "`n// ========== Cursor Hook 注入结束 ==========`n`n" + $mainContent + } + } + + # 写入修改后的内容 + Set-Content -Path $MainJsPath -Value $newContent -Encoding UTF8 -NoNewline + + return $true +} + +# 关闭 Cursor 进程 +function Stop-CursorProcess { + $cursorProcesses = Get-Process -Name "Cursor*" -ErrorAction SilentlyContinue + + if ($cursorProcesses) { + Write-Log "发现 Cursor 进程正在运行,正在关闭..." + $cursorProcesses | Stop-Process -Force + Start-Sleep -Seconds 2 + Write-Log "Cursor 进程已关闭" + } +} + +# 主函数 +function Main { + Write-Host "" + Write-Host "$BLUE========================================$NC" + Write-Host "$BLUE Cursor Hook 注入工具 (Windows) $NC" + Write-Host "$BLUE========================================$NC" + Write-Host "" + + # 获取 Cursor main.js 路径 + $mainJsPath = Get-CursorPath + if (-not $mainJsPath) { + Write-Log "未找到 Cursor 安装路径" "ERROR" + Write-Log "请确保 Cursor 已正确安装" "ERROR" + exit 1 + } + Write-Log "找到 Cursor main.js: $mainJsPath" + + # 回滚模式 + if ($Rollback) { + Write-Log "执行回滚操作..." + Stop-CursorProcess + if (Restore-MainJs -MainJsPath $mainJsPath) { + Write-Log "回滚成功!" "INFO" + } else { + Write-Log "回滚失败!" "ERROR" + exit 1 + } + exit 0 + } + + # 检查是否已注入 + if ((Test-AlreadyInjected -MainJsPath $mainJsPath) -and -not $Force) { + Write-Log "Hook 已经注入,无需重复操作" "WARN" + Write-Log "如需强制重新注入,请使用 -Force 参数" "INFO" + exit 0 + } + + # 获取 Hook 脚本路径 + $hookScriptPath = Get-HookScriptPath + if (-not $hookScriptPath) { + Write-Log "未找到 cursor_hook.js 文件" "ERROR" + Write-Log "请确保 cursor_hook.js 与此脚本在同一目录" "ERROR" + exit 1 + } + Write-Log "找到 Hook 脚本: $hookScriptPath" + + # 关闭 Cursor 进程 + Stop-CursorProcess + + # 备份原始文件 + Write-Log "正在备份原始文件..." + $backupPath = Backup-MainJs -MainJsPath $mainJsPath + + # 注入 Hook 代码 + Write-Log "正在注入 Hook 代码..." + try { + if (Inject-Hook -MainJsPath $mainJsPath -HookScriptPath $hookScriptPath) { + Write-Log "Hook 注入成功!" "INFO" + } else { + Write-Log "Hook 注入失败!" "ERROR" + exit 1 + } + } catch { + Write-Log "注入过程中发生错误: $_" "ERROR" + Write-Log "正在回滚..." "WARN" + Restore-MainJs -MainJsPath $mainJsPath + exit 1 + } + + Write-Host "" + Write-Host "$GREEN========================================$NC" + Write-Host "$GREEN ✅ Hook 注入完成! $NC" + Write-Host "$GREEN========================================$NC" + Write-Host "" + Write-Log "现在可以启动 Cursor 了" + Write-Log "ID 配置文件位置: $env:USERPROFILE\.cursor_ids.json" + Write-Host "" + Write-Host "$YELLOW提示:$NC" + Write-Host " - 如需回滚,请运行: .\inject_hook_win.ps1 -Rollback" + Write-Host " - 如需强制重新注入,请运行: .\inject_hook_win.ps1 -Force" + Write-Host " - 如需启用调试日志,请运行: .\inject_hook_win.ps1 -Debug" + Write-Host "" +} + +# 执行主函数 +Main + diff --git a/scripts/run/cursor_linux_id_modifier.sh b/scripts/run/cursor_linux_id_modifier.sh index 00db3e4..1ef210e 100755 --- a/scripts/run/cursor_linux_id_modifier.sh +++ b/scripts/run/cursor_linux_id_modifier.sh @@ -718,12 +718,13 @@ find_cursor_js_files() { } # 修改Cursor的JS文件 -# 🔧 修改Cursor内核JS文件实现设备识别绕过(A+B混合方案 - IIFE + someValue替换) +# 🔧 修改Cursor内核JS文件实现设备识别绕过(增强版 Hook 方案) # 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名 -# 方案B: IIFE运行时劫持 - 劫持crypto.randomUUID从源头拦截 +# 方案B: 深度 Hook 注入 - 从底层拦截所有设备标识符生成 +# 方案C: Module.prototype.require 劫持 - 拦截 child_process, crypto, os 等模块 modify_cursor_js_files() { log_info "🔧 [内核修改] 开始修改Cursor内核JS文件实现设备识别绕过..." - log_info "💡 [方案] 使用A+B混合方案:someValue占位符替换 + IIFE运行时劫持" + log_info "💡 [方案] 使用增强版 Hook 方案:深度模块劫持 + someValue替换" # 先查找需要修改的JS文件 if ! find_cursor_js_files; then @@ -740,15 +741,31 @@ modify_cursor_js_files() { local machine_id=$(openssl rand -hex 32) local device_id=$(generate_uuid) local mac_machine_id=$(openssl rand -hex 32) - local sqm_id=$(generate_uuid) + local sqm_id="{$(generate_uuid | tr '[:lower:]' '[:upper:]')}" local session_id=$(generate_uuid) - # 🔧 新增: 生成 firstSessionDate 用于替换 someValue.firstSessionDate local first_session_date=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z") + local mac_address="00:11:22:33:44:55" log_info "🔑 [生成] 已生成新的设备标识符" log_info " machineId: ${machine_id:0:16}..." log_info " deviceId: ${device_id:0:16}..." log_info " macMachineId: ${mac_machine_id:0:16}..." + log_info " sqmId: $sqm_id" + + # 保存 ID 配置到用户目录(供 Hook 读取) + local ids_config_path="$HOME/.cursor_ids.json" + cat > "$ids_config_path" << EOF +{ + "machineId": "$machine_id", + "macMachineId": "$mac_machine_id", + "devDeviceId": "$device_id", + "sqmId": "$sqm_id", + "macAddress": "$mac_address", + "createdAt": "$first_session_date" +} +EOF + chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$ids_config_path" 2>/dev/null || true + log_info "💾 [保存] ID 配置已保存到: $ids_config_path" local modified_count=0 local file_modification_status=() @@ -762,7 +779,6 @@ modify_cursor_js_files() { continue fi - # 检查是否已经被修改过(使用统一标记 __cursor_patched__) if grep -q "__cursor_patched__" "$file" 2>/dev/null; then log_info "✅ [已修改] 文件已修改: $(basename "$file")" else @@ -785,7 +801,6 @@ modify_cursor_js_files() { continue fi - # 检查是否已经修改过 if grep -q "__cursor_patched__" "$file"; then log_info "✅ [跳过] 文件已经被修改过" ((modified_count++)) @@ -793,8 +808,21 @@ modify_cursor_js_files() { continue fi - # 创建文件备份 - local backup_file="${file}.backup_$(date +%Y%m%d_%H%M%S)" + # 创建备份目录 + local backup_dir="$(dirname "$file")/backups" + mkdir -p "$backup_dir" 2>/dev/null || true + + # 创建原始备份(如果不存在) + local original_backup="$backup_dir/$(basename "$file").original" + if [ ! -f "$original_backup" ]; then + cp "$file" "$original_backup" + chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$original_backup" 2>/dev/null || true + chmod 444 "$original_backup" 2>/dev/null || true + log_info "✅ [备份] 原始备份创建成功" + fi + + # 创建时间戳备份 + local backup_file="$backup_dir/$(basename "$file").backup_$(date +%Y%m%d_%H%M%S)" if ! cp "$file" "$backup_file"; then log_error "无法创建文件备份: $file" file_modification_status+=("'$(basename "$file")': Backup Failed") @@ -803,99 +831,177 @@ modify_cursor_js_files() { chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$backup_file" 2>/dev/null || true chmod 444 "$backup_file" 2>/dev/null || true - # 确保文件对当前执行用户可写 chmod u+w "$file" || { log_error "无法修改文件权限(写): $file" file_modification_status+=("'$(basename "$file")': Permission Error") - cp "$backup_file" "$file" 2>/dev/null || true continue } local replaced=false # ========== 方法A: someValue占位符替换(稳定锚点) ========== - # 这些字符串是固定的占位符,不会被混淆器修改,跨版本稳定 - - # 替换 someValue.machineId if grep -q 'someValue\.machineId' "$file"; then - sed -i "s/someValue\.machineId/${machine_id}/g" "$file" + sed -i "s/someValue\.machineId/\"${machine_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.machineId" replaced=true fi - # 替换 someValue.macMachineId if grep -q 'someValue\.macMachineId' "$file"; then - sed -i "s/someValue\.macMachineId/${mac_machine_id}/g" "$file" + sed -i "s/someValue\.macMachineId/\"${mac_machine_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.macMachineId" replaced=true fi - # 替换 someValue.devDeviceId if grep -q 'someValue\.devDeviceId' "$file"; then - sed -i "s/someValue\.devDeviceId/${device_id}/g" "$file" + sed -i "s/someValue\.devDeviceId/\"${device_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.devDeviceId" replaced=true fi - # 替换 someValue.sqmId if grep -q 'someValue\.sqmId' "$file"; then - sed -i "s/someValue\.sqmId/${sqm_id}/g" "$file" + sed -i "s/someValue\.sqmId/\"${sqm_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.sqmId" replaced=true fi - # 替换 someValue.sessionId(新增锚点) if grep -q 'someValue\.sessionId' "$file"; then - sed -i "s/someValue\.sessionId/${session_id}/g" "$file" + sed -i "s/someValue\.sessionId/\"${session_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.sessionId" replaced=true fi - # 🔧 新增: 替换 someValue.firstSessionDate(首次会话日期) if grep -q 'someValue\.firstSessionDate' "$file"; then - sed -i "s/someValue\.firstSessionDate/${first_session_date}/g" "$file" + sed -i "s/someValue\.firstSessionDate/\"${first_session_date}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.firstSessionDate" replaced=true fi - # ========== 方法B: IIFE运行时劫持(crypto.randomUUID) ========== - # 使用IIFE包装,兼容webpack打包的bundle文件 - # 在支持 require 的环境中劫持 crypto.randomUUID;在 ESM 环境中安全降级为 no-op,避免 require 抛错 - local inject_code=";(function(){/*__cursor_patched__*/var _cr=null,_os=null;if(typeof require!=='undefined'){try{_cr=require('crypto');_os=require('os');}catch(e){}}if(_cr&&_cr.randomUUID){var _orig=_cr.randomUUID;_cr.randomUUID=function(){return'${new_uuid}';};}if(typeof globalThis!=='undefined'){globalThis.__cursor_machine_id='${machine_id}';globalThis.__cursor_mac_machine_id='${mac_machine_id}';globalThis.__cursor_dev_device_id='${device_id}';globalThis.__cursor_sqm_id='${sqm_id}';}if(_os&&_os.networkInterfaces){try{var _origNI=_os.networkInterfaces;_os.networkInterfaces=function(){var r=_origNI.call(_os);for(var k in r){if(r[k]){for(var i=0;i=32&&inputData.length<=40)){ + return enc==="hex"?__ids__.machineId:Buffer.from(__ids__.machineId,"hex"); + } + return _origDigest(enc); + }; + } + return hash; + }; + if(_origRandomUUID){ + var uuidCount=0; + result.randomUUID=function(){ + uuidCount++; + if(uuidCount<=2)return __ids__.devDeviceId; + return _origRandomUUID.apply(this,arguments); + }; + } + hooked=result; + } + else if(id==="@vscode/deviceid"){ + hooked={...result,getDeviceId:async function(){return __ids__.devDeviceId;}}; + } - # 注入代码到文件开头 + if(hooked!==result)_hooked.set(id,hooked); + return hooked; +}; + +console.log("[Cursor ID Modifier] 增强版 Hook 已激活 - 煎饼果子(86) 公众号【煎饼果子卷AI】"); +})(); +// ========== Cursor Hook 注入结束 ========== + +' + + # 在版权声明后注入代码 local temp_file=$(mktemp) - echo "$inject_code" > "$temp_file" - cat "$file" >> "$temp_file" + if grep -q '\*/' "$file"; then + awk -v inject="$inject_code" ' + /\*\// && !injected { + print + print "" + print inject + injected = 1 + next + } + { print } + ' "$file" > "$temp_file" + log_info " ✓ [方案B] 增强版 Hook 代码已注入(版权声明后)" + else + echo "$inject_code" > "$temp_file" + cat "$file" >> "$temp_file" + log_info " ✓ [方案B] 增强版 Hook 代码已注入(文件开头)" + fi if mv "$temp_file" "$file"; then - log_info " ✓ [方案B] IIFE运行时劫持代码已注入" - if [ "$replaced" = true ]; then - log_info "✅ [成功] A+B混合方案修改成功(someValue替换 + IIFE劫持)" + log_info "✅ [成功] 增强版混合方案修改成功(someValue替换 + 深度Hook)" else - log_info "✅ [成功] 方案B修改成功(IIFE劫持)" + log_info "✅ [成功] 增强版 Hook 修改成功" fi ((modified_count++)) file_modification_status+=("'$(basename "$file")': Success") - # 恢复文件权限为只读 chmod u-w,go-w "$file" 2>/dev/null || true chown "$CURRENT_USER":"$(id -g -n "$CURRENT_USER")" "$file" 2>/dev/null || true else - log_error "IIFE注入失败 (无法移动临时文件)" + log_error "Hook注入失败 (无法移动临时文件)" rm -f "$temp_file" file_modification_status+=("'$(basename "$file")': Inject Failed") - # 恢复备份 - cp "$backup_file" "$file" 2>/dev/null || true + cp "$original_backup" "$file" 2>/dev/null || true fi - # 清理备份文件(可选保留) - # rm -f "$backup_file" - - done # 文件循环结束 + done - # 报告每个文件的状态 log_info "📊 [统计] JS 文件处理状态汇总:" for status in "${file_modification_status[@]}"; do log_info " - $status" @@ -907,9 +1013,10 @@ modify_cursor_js_files() { fi log_info "🎉 [完成] 成功修改 $modified_count 个JS文件" - log_info "💡 [说明] 使用A+B混合方案:" + log_info "💡 [说明] 使用增强版 Hook 方案:" log_info " • 方案A: someValue占位符替换(稳定锚点,跨版本兼容)" - log_info " • 方案B: IIFE运行时劫持(crypto.randomUUID + os.networkInterfaces)" + log_info " • 方案B: 深度模块劫持(child_process, crypto, os, @vscode/*)" + log_info "📁 [配置] ID 配置文件: $ids_config_path" return 0 } diff --git a/scripts/run/cursor_mac_id_modifier.sh b/scripts/run/cursor_mac_id_modifier.sh index aec14d6..f22939c 100644 --- a/scripts/run/cursor_mac_id_modifier.sh +++ b/scripts/run/cursor_mac_id_modifier.sh @@ -1510,12 +1510,13 @@ backup_config() { fi } -# 🔧 修改Cursor内核JS文件实现设备识别绕过(A+B混合方案 - IIFE + someValue替换) +# 🔧 修改Cursor内核JS文件实现设备识别绕过(增强版 Hook 方案) # 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名 -# 方案B: IIFE运行时劫持 - 劫持crypto.randomUUID从源头拦截 +# 方案B: 深度 Hook 注入 - 从底层拦截所有设备标识符生成 +# 方案C: Module.prototype.require 劫持 - 拦截 child_process, crypto, os 等模块 modify_cursor_js_files() { log_info "🔧 [内核修改] 开始修改Cursor内核JS文件实现设备识别绕过..." - log_info "💡 [方案] 使用A+B混合方案:someValue占位符替换 + IIFE运行时劫持" + log_info "💡 [方案] 使用增强版 Hook 方案:深度模块劫持 + someValue替换" echo # 检查Cursor应用是否存在 @@ -1529,22 +1530,34 @@ modify_cursor_js_files() { local machine_id=$(openssl rand -hex 32) local device_id=$(uuidgen | tr '[:upper:]' '[:lower:]') local mac_machine_id=$(openssl rand -hex 32) - local sqm_id=$(uuidgen | tr '[:upper:]' '[:lower:]') - # 生成一个固定的session_id用于替换someValue.sessionId + local sqm_id="{$(uuidgen | tr '[:lower:]' '[:upper:]')}" local session_id=$(uuidgen | tr '[:upper:]' '[:lower:]') - # 🔧 新增: 生成 firstSessionDate 用于替换 someValue.firstSessionDate local first_session_date=$(date -u +"%Y-%m-%dT%H:%M:%S.000Z") + local mac_address="00:11:22:33:44:55" log_info "🔑 [生成] 已生成新的设备标识符" log_info " machineId: ${machine_id:0:16}..." log_info " deviceId: ${device_id:0:16}..." log_info " macMachineId: ${mac_machine_id:0:16}..." + log_info " sqmId: $sqm_id" + + # 保存 ID 配置到用户目录(供 Hook 读取) + local ids_config_path="$HOME/.cursor_ids.json" + cat > "$ids_config_path" << EOF +{ + "machineId": "$machine_id", + "macMachineId": "$mac_machine_id", + "devDeviceId": "$device_id", + "sqmId": "$sqm_id", + "macAddress": "$mac_address", + "createdAt": "$first_session_date" +} +EOF + log_info "💾 [保存] ID 配置已保存到: $ids_config_path" - # 目标JS文件列表(按优先级排序) + # 目标JS文件列表(只修改 main.js) local js_files=( - "$CURSOR_APP_PATH/Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js" "$CURSOR_APP_PATH/Contents/Resources/app/out/main.js" - "$CURSOR_APP_PATH/Contents/Resources/app/out/vs/code/node/cliProcessMain.js" ) local modified_count=0 @@ -1558,7 +1571,6 @@ modify_cursor_js_files() { continue fi - # 检查是否已经被修改过(使用统一标记 __cursor_patched__) if grep -q "__cursor_patched__" "$file" 2>/dev/null; then log_info "✅ [已修改] 文件已修改: ${file/$CURSOR_APP_PATH\//}" else @@ -1578,16 +1590,29 @@ modify_cursor_js_files() { # 创建备份 local timestamp=$(date +%Y%m%d_%H%M%S) - local backup_dir="/tmp/Cursor_JS_Backup_${timestamp}" + local backup_dir="$CURSOR_APP_PATH/Contents/Resources/app/out/backups" log_info "💾 [备份] 创建JS文件备份..." mkdir -p "$backup_dir" + + # 创建原始备份(如果不存在) + local original_backup="$backup_dir/main.js.original" + if [ ! -f "$original_backup" ]; then + for file in "${js_files[@]}"; do + if [ -f "$file" ]; then + cp "$file" "$backup_dir/$(basename "$file").original" + fi + done + log_info "✅ [备份] 原始备份创建成功" + fi + + # 创建时间戳备份 for file in "${js_files[@]}"; do if [ -f "$file" ]; then - cp "$file" "$backup_dir/$(basename "$file")" + cp "$file" "$backup_dir/$(basename "$file").backup_$timestamp" fi done - log_info "✅ [备份] 备份创建成功: $backup_dir" + log_info "✅ [备份] 时间戳备份创建成功: $backup_dir" # 修改JS文件 log_info "🔧 [修改] 开始修改JS文件..." @@ -1608,70 +1633,164 @@ modify_cursor_js_files() { fi # ========== 方法A: someValue占位符替换(稳定锚点) ========== - # 这些字符串是固定的占位符,不会被混淆器修改,跨版本稳定 local replaced=false - # 替换 someValue.machineId if grep -q 'someValue\.machineId' "$file"; then - sed -i.tmp "s/someValue\.machineId/${machine_id}/g" "$file" + sed -i.tmp "s/someValue\.machineId/\"${machine_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.machineId" replaced=true fi - # 替换 someValue.macMachineId if grep -q 'someValue\.macMachineId' "$file"; then - sed -i.tmp "s/someValue\.macMachineId/${mac_machine_id}/g" "$file" + sed -i.tmp "s/someValue\.macMachineId/\"${mac_machine_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.macMachineId" replaced=true fi - # 替换 someValue.devDeviceId if grep -q 'someValue\.devDeviceId' "$file"; then - sed -i.tmp "s/someValue\.devDeviceId/${device_id}/g" "$file" + sed -i.tmp "s/someValue\.devDeviceId/\"${device_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.devDeviceId" replaced=true fi - # 替换 someValue.sqmId if grep -q 'someValue\.sqmId' "$file"; then - sed -i.tmp "s/someValue\.sqmId/${sqm_id}/g" "$file" + sed -i.tmp "s/someValue\.sqmId/\"${sqm_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.sqmId" replaced=true fi - # 替换 someValue.sessionId(新增锚点) if grep -q 'someValue\.sessionId' "$file"; then - sed -i.tmp "s/someValue\.sessionId/${session_id}/g" "$file" + sed -i.tmp "s/someValue\.sessionId/\"${session_id}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.sessionId" replaced=true fi - # 🔧 新增: 替换 someValue.firstSessionDate(首次会话日期) if grep -q 'someValue\.firstSessionDate' "$file"; then - sed -i.tmp "s/someValue\.firstSessionDate/${first_session_date}/g" "$file" + sed -i.tmp "s/someValue\.firstSessionDate/\"${first_session_date}\"/g" "$file" log_info " ✓ [方案A] 替换 someValue.firstSessionDate" replaced=true fi - # ========== 方法B: IIFE运行时劫持(crypto.randomUUID) ========== - # 使用IIFE包装,兼容webpack打包的bundle文件 - # 在支持 require 的环境中劫持 crypto.randomUUID;在 ESM 环境中安全降级为 no-op,避免 require 抛错 - local inject_code=";(function(){/*__cursor_patched__*/var _cr=null,_os=null;if(typeof require!=='undefined'){try{_cr=require('crypto');_os=require('os');}catch(e){}}if(_cr&&_cr.randomUUID){var _orig=_cr.randomUUID;_cr.randomUUID=function(){return'${new_uuid}';};}if(typeof globalThis!=='undefined'){globalThis.__cursor_machine_id='${machine_id}';globalThis.__cursor_mac_machine_id='${mac_machine_id}';globalThis.__cursor_dev_device_id='${device_id}';globalThis.__cursor_sqm_id='${sqm_id}';}if(_os&&_os.networkInterfaces){try{var _origNI=_os.networkInterfaces;_os.networkInterfaces=function(){var r=_origNI.call(_os);for(var k in r){if(r[k]){for(var i=0;i "${file}.new" - cat "$file" >> "${file}.new" - mv "${file}.new" "$file" +Module.prototype.require=function(id){ + var result=_origReq.apply(this,arguments); + if(_hooked.has(id))return _hooked.get(id); + var hooked=result; - log_info " ✓ [方案B] IIFE运行时劫持代码已注入" + if(id==="child_process"){ + var _origExecSync=result.execSync; + result.execSync=function(cmd,opts){ + var cmdStr=String(cmd).toLowerCase(); + if(cmdStr.includes("ioreg")&&cmdStr.includes("ioplatformexpertdevice")){ + return Buffer.from("\"IOPlatformUUID\" = \""+__ids__.machineId.substring(0,36).toUpperCase()+"\""); + } + if(cmdStr.includes("machine-id")||cmdStr.includes("hostname")){ + return Buffer.from(__ids__.machineId.substring(0,32)); + } + return _origExecSync.apply(this,arguments); + }; + hooked=result; + } + else if(id==="os"){ + result.networkInterfaces=function(){ + return{"en0":[{address:"192.168.1.100",netmask:"255.255.255.0",family:"IPv4",mac:__ids__.macAddress,internal:false}]}; + }; + hooked=result; + } + else if(id==="crypto"){ + var _origCreateHash=result.createHash; + var _origRandomUUID=result.randomUUID; + result.createHash=function(algo){ + var hash=_origCreateHash.apply(this,arguments); + if(algo.toLowerCase()==="sha256"){ + var _origDigest=hash.digest.bind(hash); + var _origUpdate=hash.update.bind(hash); + var inputData=""; + hash.update=function(data,enc){inputData+=String(data);return _origUpdate(data,enc);}; + hash.digest=function(enc){ + if(inputData.includes("IOPlatformUUID")||(inputData.length>=32&&inputData.length<=40)){ + return enc==="hex"?__ids__.machineId:Buffer.from(__ids__.machineId,"hex"); + } + return _origDigest(enc); + }; + } + return hash; + }; + if(_origRandomUUID){ + var uuidCount=0; + result.randomUUID=function(){ + uuidCount++; + if(uuidCount<=2)return __ids__.devDeviceId; + return _origRandomUUID.apply(this,arguments); + }; + } + hooked=result; + } + else if(id==="@vscode/deviceid"){ + hooked={...result,getDeviceId:async function(){return __ids__.devDeviceId;}}; + } + + if(hooked!==result)_hooked.set(id,hooked); + return hooked; +}; + +console.log("[Cursor ID Modifier] 增强版 Hook 已激活 - 煎饼果子(86) 公众号【煎饼果子卷AI】"); +})(); +// ========== Cursor Hook 注入结束 ========== + +' + + # 在版权声明后注入代码 + if grep -q '\*/' "$file"; then + # 使用 awk 在版权声明后注入 + awk -v inject="$inject_code" ' + /\*\// && !injected { + print + print "" + print inject + injected = 1 + next + } + { print } + ' "$file" > "${file}.new" + mv "${file}.new" "$file" + log_info " ✓ [方案B] 增强版 Hook 代码已注入(版权声明后)" + else + # 注入到文件开头 + echo "$inject_code" > "${file}.new" + cat "$file" >> "${file}.new" + mv "${file}.new" "$file" + log_info " ✓ [方案B] 增强版 Hook 代码已注入(文件开头)" + fi # 清理临时文件 rm -f "${file}.tmp" if [ "$replaced" = true ]; then - log_info "✅ [成功] A+B混合方案修改成功(someValue替换 + IIFE劫持)" + log_info "✅ [成功] 增强版混合方案修改成功(someValue替换 + 深度Hook)" else - log_info "✅ [成功] 方案B修改成功(IIFE劫持)" + log_info "✅ [成功] 增强版 Hook 修改成功" fi ((modified_count++)) done @@ -1679,9 +1798,10 @@ modify_cursor_js_files() { if [ $modified_count -gt 0 ]; then log_info "🎉 [完成] 成功修改 $modified_count 个JS文件" log_info "💾 [备份] 原始文件备份位置: $backup_dir" - log_info "💡 [说明] 使用A+B混合方案:" + log_info "💡 [说明] 使用增强版 Hook 方案:" log_info " • 方案A: someValue占位符替换(稳定锚点,跨版本兼容)" - log_info " • 方案B: IIFE运行时劫持(crypto.randomUUID + os.networkInterfaces)" + log_info " • 方案B: 深度模块劫持(child_process, crypto, os, @vscode/*)" + log_info "📁 [配置] ID 配置文件: $ids_config_path" return 0 else log_error "❌ [失败] 没有成功修改任何文件" diff --git a/scripts/run/cursor_win_id_modifier.ps1 b/scripts/run/cursor_win_id_modifier.ps1 index 2531f04..32a158a 100644 --- a/scripts/run/cursor_win_id_modifier.ps1 +++ b/scripts/run/cursor_win_id_modifier.ps1 @@ -24,13 +24,14 @@ function Generate-RandomString { return $result } -# 🔧 修改Cursor内核JS文件实现设备识别绕过(A+B混合方案 - IIFE + someValue替换) +# 🔧 修改Cursor内核JS文件实现设备识别绕过(增强版 Hook 方案) # 方案A: someValue占位符替换 - 稳定锚点,不依赖混淆后的函数名 -# 方案B: IIFE运行时劫持 - 劫持crypto.randomUUID从源头拦截 +# 方案B: 深度 Hook 注入 - 从底层拦截所有设备标识符生成 +# 方案C: Module.prototype.require 劫持 - 拦截 child_process, crypto, os 等模块 function Modify-CursorJSFiles { Write-Host "" Write-Host "$BLUE🔧 [内核修改]$NC 开始修改Cursor内核JS文件实现设备识别绕过..." - Write-Host "$BLUE💡 [方案]$NC 使用A+B混合方案:someValue占位符替换 + IIFE运行时劫持" + Write-Host "$BLUE💡 [方案]$NC 使用增强版 Hook 方案:深度模块劫持 + someValue替换" Write-Host "" # Windows版Cursor应用路径 @@ -72,19 +73,37 @@ function Modify-CursorJSFiles { $rng2.GetBytes($randomBytes2) $macMachineId = [System.BitConverter]::ToString($randomBytes2) -replace '-','' $rng2.Dispose() - $sqmId = [System.Guid]::NewGuid().ToString().ToLower() + $sqmId = "{" + [System.Guid]::NewGuid().ToString().ToUpper() + "}" $sessionId = [System.Guid]::NewGuid().ToString().ToLower() + $macAddress = "00:11:22:33:44:55" Write-Host "$GREEN🔑 [生成]$NC 已生成新的设备标识符" Write-Host " machineId: $($machineId.Substring(0,16))..." Write-Host " deviceId: $($deviceId.Substring(0,16))..." Write-Host " macMachineId: $($macMachineId.Substring(0,16))..." + Write-Host " sqmId: $sqmId" + + # 保存 ID 配置到用户目录(供 Hook 读取) + # 每次执行都删除旧配置并重新生成,确保获得新的设备标识符 + $idsConfigPath = "$env:USERPROFILE\.cursor_ids.json" + if (Test-Path $idsConfigPath) { + Remove-Item -Path $idsConfigPath -Force + Write-Host "$YELLOW🗑️ [清理]$NC 已删除旧的 ID 配置文件" + } + $idsConfig = @{ + machineId = $machineId + macMachineId = $macMachineId + devDeviceId = $deviceId + sqmId = $sqmId + macAddress = $macAddress + createdAt = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + } + $idsConfig | ConvertTo-Json | Set-Content -Path $idsConfigPath -Encoding UTF8 + Write-Host "$GREEN💾 [保存]$NC 新的 ID 配置已保存到: $idsConfigPath" # 目标JS文件列表(Windows路径,按优先级排序) $jsFiles = @( - "$cursorAppPath\resources\app\out\vs\workbench\api\node\extensionHostProcess.js", - "$cursorAppPath\resources\app\out\main.js", - "$cursorAppPath\resources\app\out\vs\code\node\cliProcessMain.js" + "$cursorAppPath\resources\app\out\main.js" ) $modifiedCount = 0 @@ -118,18 +137,32 @@ function Modify-CursorJSFiles { # 创建备份 $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" - $backupPath = "$env:TEMP\Cursor_JS_Backup_$timestamp" + $backupPath = "$cursorAppPath\resources\app\out\backups" Write-Host "$BLUE💾 [备份]$NC 创建Cursor JS文件备份..." try { New-Item -ItemType Directory -Path $backupPath -Force | Out-Null + + # 创建原始备份(如果不存在) + $originalBackup = "$backupPath\main.js.original" + if (-not (Test-Path $originalBackup)) { + foreach ($file in $jsFiles) { + if (Test-Path $file) { + $fileName = Split-Path $file -Leaf + Copy-Item $file "$backupPath\$fileName.original" -Force + } + } + Write-Host "$GREEN✅ [备份]$NC 原始备份创建成功" + } + + # 创建时间戳备份 foreach ($file in $jsFiles) { if (Test-Path $file) { $fileName = Split-Path $file -Leaf - Copy-Item $file "$backupPath\$fileName" -Force + Copy-Item $file "$backupPath\$fileName.backup_$timestamp" -Force } } - Write-Host "$GREEN✅ [备份]$NC 备份创建成功: $backupPath" + Write-Host "$GREEN✅ [备份]$NC 时间戳备份创建成功: $backupPath" } catch { Write-Host "$RED❌ [错误]$NC 创建备份失败: $($_.Exception.Message)" return $false @@ -163,35 +196,35 @@ function Modify-CursorJSFiles { # 替换 someValue.machineId if ($content -match 'someValue\.machineId') { - $content = $content -replace 'someValue\.machineId', $machineId + $content = $content -replace 'someValue\.machineId', "`"$machineId`"" Write-Host " $GREEN✓$NC [方案A] 替换 someValue.machineId" $replaced = $true } # 替换 someValue.macMachineId if ($content -match 'someValue\.macMachineId') { - $content = $content -replace 'someValue\.macMachineId', $macMachineId + $content = $content -replace 'someValue\.macMachineId', "`"$macMachineId`"" Write-Host " $GREEN✓$NC [方案A] 替换 someValue.macMachineId" $replaced = $true } # 替换 someValue.devDeviceId if ($content -match 'someValue\.devDeviceId') { - $content = $content -replace 'someValue\.devDeviceId', $deviceId + $content = $content -replace 'someValue\.devDeviceId', "`"$deviceId`"" Write-Host " $GREEN✓$NC [方案A] 替换 someValue.devDeviceId" $replaced = $true } # 替换 someValue.sqmId if ($content -match 'someValue\.sqmId') { - $content = $content -replace 'someValue\.sqmId', $sqmId + $content = $content -replace 'someValue\.sqmId', "`"$sqmId`"" Write-Host " $GREEN✓$NC [方案A] 替换 someValue.sqmId" $replaced = $true } # 替换 someValue.sessionId(新增锚点) if ($content -match 'someValue\.sessionId') { - $content = $content -replace 'someValue\.sessionId', $sessionId + $content = $content -replace 'someValue\.sessionId', "`"$sessionId`"" Write-Host " $GREEN✓$NC [方案A] 替换 someValue.sessionId" $replaced = $true } @@ -204,23 +237,139 @@ function Modify-CursorJSFiles { $replaced = $true } - # ========== 方法B: IIFE运行时劫持(crypto.randomUUID) ========== - # 使用IIFE包装,兼容webpack打包的bundle文件 - # 在支持 require 的环境中劫持 crypto.randomUUID;在 ESM 环境中安全降级为 no-op,避免 require 抛错 - $injectCode = ";(function(){/*__cursor_patched__*/var _cr=null,_os=null;if(typeof require!=='undefined'){try{_cr=require('crypto');_os=require('os');}catch(e){}}if(_cr&&_cr.randomUUID){var _orig=_cr.randomUUID;_cr.randomUUID=function(){return'$newUuid';};}if(typeof globalThis!=='undefined'){globalThis.__cursor_machine_id='$machineId';globalThis.__cursor_mac_machine_id='$macMachineId';globalThis.__cursor_dev_device_id='$deviceId';globalThis.__cursor_sqm_id='$sqmId';}if(_os&&_os.networkInterfaces){try{var _origNI=_os.networkInterfaces;_os.networkInterfaces=function(){var r=_origNI.call(_os);for(var k in r){if(r[k]){for(var i=0;i=32&&inputData.length<=40)){ + return enc==='hex'?__ids__.machineId:Buffer.from(__ids__.machineId,'hex'); + } + return _origDigest(enc); + }; + } + return hash; + }; + if(_origRandomUUID){ + var uuidCount=0; + result.randomUUID=function(){ + uuidCount++; + if(uuidCount<=2)return __ids__.devDeviceId; + return _origRandomUUID.apply(this,arguments); + }; + } + hooked=result; + } + // Hook @vscode/deviceid + else if(id==='@vscode/deviceid'){ + hooked={...result,getDeviceId:async function(){return __ids__.devDeviceId;}}; + } + // Hook @vscode/windows-registry + else if(id==='@vscode/windows-registry'){ + var _origGetReg=result.GetStringRegKey; + hooked={...result,GetStringRegKey:function(hive,path,name){ + if(name==='MachineId'||path.includes('SQMClient'))return __ids__.sqmId; + if(name==='MachineGuid'||path.includes('Cryptography'))return __ids__.machineId.substring(0,36); + return _origGetReg?_origGetReg.apply(this,arguments):''; + }}; + } + + if(hooked!==result)_hooked.set(id,hooked); + return hooked; +}; + +console.log('[Cursor ID Modifier] 增强版 Hook 已激活 - 煎饼果子(86) 公众号【煎饼果子卷AI】'); +})(); +// ========== Cursor Hook 注入结束 ========== - # 注入代码到文件开头 - $content = $injectCode + "`n" + $content +"@ - Write-Host " $GREEN✓$NC [方案B] IIFE运行时劫持代码已注入" + # 找到版权声明结束位置并在其后注入 + if ($content -match '(\*/\s*\n)') { + $content = $content -replace '(\*/\s*\n)', "`$1$injectCode" + Write-Host " $GREEN✓$NC [方案B] 增强版 Hook 代码已注入(版权声明后)" + } else { + # 如果没有找到版权声明,则注入到文件开头 + $content = $injectCode + $content + Write-Host " $GREEN✓$NC [方案B] 增强版 Hook 代码已注入(文件开头)" + } # 写入修改后的内容 Set-Content -Path $file -Value $content -Encoding UTF8 -NoNewline if ($replaced) { - Write-Host "$GREEN✅ [成功]$NC A+B混合方案修改成功(someValue替换 + IIFE劫持)" + Write-Host "$GREEN✅ [成功]$NC 增强版混合方案修改成功(someValue替换 + 深度Hook)" } else { - Write-Host "$GREEN✅ [成功]$NC 方案B修改成功(IIFE劫持)" + Write-Host "$GREEN✅ [成功]$NC 增强版 Hook 修改成功" } $modifiedCount++ @@ -228,7 +377,7 @@ function Modify-CursorJSFiles { Write-Host "$RED❌ [错误]$NC 修改文件失败: $($_.Exception.Message)" # 尝试从备份恢复 $fileName = Split-Path $file -Leaf - $backupFile = "$backupPath\$fileName" + $backupFile = "$backupPath\$fileName.original" if (Test-Path $backupFile) { Copy-Item $backupFile $file -Force Write-Host "$YELLOW🔄 [恢复]$NC 已从备份恢复文件" @@ -240,9 +389,10 @@ function Modify-CursorJSFiles { Write-Host "" Write-Host "$GREEN🎉 [完成]$NC 成功修改 $modifiedCount 个JS文件" Write-Host "$BLUE💾 [备份]$NC 原始文件备份位置: $backupPath" - Write-Host "$BLUE💡 [说明]$NC 使用A+B混合方案:" + Write-Host "$BLUE💡 [说明]$NC 使用增强版 Hook 方案:" Write-Host " • 方案A: someValue占位符替换(稳定锚点,跨版本兼容)" - Write-Host " • 方案B: IIFE运行时劫持(crypto.randomUUID + os.networkInterfaces)" + Write-Host " • 方案B: 深度模块劫持(child_process, crypto, os, @vscode/*)" + Write-Host "$BLUE📁 [配置]$NC ID 配置文件: $idsConfigPath" return $true } else { Write-Host "$RED❌ [失败]$NC 没有成功修改任何文件"