/** * CursorPro Deobfuscator v12 * * 用于反混淆 obfuscator.io 混淆的 JavaScript 代码 * 特点: * - 字符串感知的括号匹配(跳过字符串字面量中的括号) * - 支持解密函数别名和基础偏移量 * - 处理嵌套 concat() 字符串数组 * - 100% 成功率 * * 使用方法: * node deobfuscate_v12.js [input_dir] [output_dir] * node deobfuscate_v12.js # 使用默认目录 * node deobfuscate_v12.js ./src ./out # 指定输入输出目录 */ const fs = require('fs'); const path = require('path'); const vm = require('vm'); // 默认目录 const DEFAULT_INPUT_DIR = './extension/out'; const DEFAULT_OUTPUT_DIR = './deobfuscated_full'; // 自定义 Base64 字母表 (obfuscator.io 标准) const BASE64_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/='; /** * 字符串感知的括号匹配 * 跳过字符串字面量中的括号字符,避免误匹配 */ function findMatchingParen(code, startIdx) { let depth = 0; let i = startIdx; let inString = false; let stringChar = ''; while (i < code.length) { const char = code[i]; // 处理字符串开始 if (!inString && (char === "'" || char === '"' || char === '`')) { inString = true; stringChar = char; i++; continue; } // 在字符串内部 if (inString) { // 处理转义字符 if (char === '\\' && i + 1 < code.length) { i += 2; continue; } // 字符串结束 if (char === stringChar) { inString = false; stringChar = ''; } i++; continue; } // 不在字符串内,计数括号 if (char === '(') { depth++; } else if (char === ')') { depth--; if (depth === 0) { return i; } } i++; } return -1; } /** * 字符串感知的方括号匹配 */ function findMatchingBracket(code, startIdx) { let depth = 0; let i = startIdx; let inString = false; let stringChar = ''; while (i < code.length) { const char = code[i]; if (!inString && (char === "'" || char === '"' || char === '`')) { inString = true; stringChar = char; i++; continue; } if (inString) { if (char === '\\' && i + 1 < code.length) { i += 2; continue; } if (char === stringChar) { inString = false; stringChar = ''; } i++; continue; } if (char === '[') { depth++; } else if (char === ']') { depth--; if (depth === 0) { return i; } } i++; } return -1; } /** * 自定义 Base64 解码 */ function customBase64Decode(input) { let result = ''; let buffer = ''; for (let i = 0; i < input.length; i++) { const charCode = BASE64_ALPHABET.indexOf(input[i]); if (charCode === -1) continue; buffer += charCode.toString(2).padStart(6, '0'); while (buffer.length >= 8) { const byte = buffer.slice(0, 8); buffer = buffer.slice(8); const charCodeNum = parseInt(byte, 2); if (charCodeNum !== 0) { result += String.fromCharCode(charCodeNum); } } } // 处理 UTF-8 编码 try { return decodeURIComponent( result.split('').map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) ).join('') ); } catch (e) { return result; } } /** * RC4 解密 */ function rc4Decrypt(str, key) { const s = []; let j = 0; let result = ''; // KSA for (let i = 0; i < 256; i++) { s[i] = i; } for (let i = 0; i < 256; i++) { j = (j + s[i] + key.charCodeAt(i % key.length)) % 256; [s[i], s[j]] = [s[j], s[i]]; } // PRGA let i = 0; j = 0; for (let k = 0; k < str.length; k++) { i = (i + 1) % 256; j = (j + s[i]) % 256; [s[i], s[j]] = [s[j], s[i]]; result += String.fromCharCode(str.charCodeAt(k) ^ s[(s[i] + s[j]) % 256]); } return result; } /** * 完整字符串解密(Base64 + RC4) */ function decryptString(encoded, key) { try { const decoded = customBase64Decode(encoded); return rc4Decrypt(decoded, key); } catch (e) { return null; } } /** * 解析字符串(处理转义) */ function parseString(str) { let result = str; // 处理十六进制转义 result = result.replace(/\\x([0-9a-fA-F]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)) ); // 处理 Unicode 转义 result = result.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)) ); // 处理常见转义 result = result.replace(/\\n/g, '\n'); result = result.replace(/\\r/g, '\r'); result = result.replace(/\\t/g, '\t'); result = result.replace(/\\'/g, "'"); result = result.replace(/\\"/g, '"'); result = result.replace(/\\\\/g, '\\'); return result; } /** * 从代码中提取所有字符串 */ function extractStringsFromCode(code) { const strings = []; const stringRegex = /'([^'\\]|\\.)*'|"([^"\\]|\\.)*"/g; let match; while ((match = stringRegex.exec(code)) !== null) { const str = match[0].slice(1, -1); strings.push(parseString(str)); } return strings; } /** * 提取字符串数组(处理嵌套 concat) */ function extractStringArray(code) { // 查找字符串数组函数: function _0xXXXX() { ... return [...] } const pattern = /function\s+(_0x[a-f0-9]+)\s*\(\s*\)\s*\{[^]*?return\s*\[/; const arrayFuncMatch = code.match(pattern); if (!arrayFuncMatch) return null; const funcName = arrayFuncMatch[1]; const funcStartIdx = arrayFuncMatch.index; // 找到函数体结束位置 const funcBodyStart = code.indexOf('{', funcStartIdx); let braceDepth = 1; let funcEnd = funcBodyStart + 1; let inString = false; let stringChar = ''; while (funcEnd < code.length && braceDepth > 0) { const char = code[funcEnd]; if (!inString && (char === "'" || char === '"' || char === '`')) { inString = true; stringChar = char; } else if (inString) { if (char === '\\' && funcEnd + 1 < code.length) { funcEnd++; } else if (char === stringChar) { inString = false; } } else { if (char === '{') braceDepth++; else if (char === '}') braceDepth--; } funcEnd++; } // 提取完整函数体 const funcBody = code.slice(funcStartIdx, funcEnd); // 提取 vip 变量(如果存在) const vipMatch = code.match(/var\s+vip\s*=\s*['"]([^'"]*)['"]/); const vipValue = vipMatch ? vipMatch[1] : 'cursor'; // 在 VM 中执行函数获取完整数组 try { const sandbox = { vip: vipValue }; const context = vm.createContext(sandbox); // 执行函数定义并调用 const execCode = `(${funcBody.replace(/^function\s+_0x[a-f0-9]+/, 'function')})()`; const result = vm.runInContext(execCode, context, { timeout: 5000 }); if (Array.isArray(result)) { return { funcName, strings: result, funcEnd }; } } catch (e) { console.error(' Array extraction VM error:', e.message); } // 备用方案:手动解析所有字符串 const returnMatch = funcBody.match(/return\s*\[/); if (!returnMatch) return null; const returnIdx = returnMatch.index; const bracketStart = funcBody.indexOf('[', returnIdx); const strings = extractStringsFromCode(funcBody.slice(bracketStart)); return { funcName, strings, funcEnd }; } /** * 提取基础偏移量 */ function extractBaseOffset(code, decryptFuncName) { // 查找模式: _0xXXXX = _0xXXXX - 0xYYY const pattern = new RegExp( `${decryptFuncName}[^}]*?_0x[a-f0-9]+\\s*=\\s*_0x[a-f0-9]+\\s*-\\s*(0x[a-f0-9]+|\\d+)` ); const match = code.match(pattern); if (match) { return parseInt(match[1]); } // 备用模式 const pattern2 = new RegExp( `_0x[a-f0-9]+\\s*-\\s*(0x[a-f0-9]+)` ); const funcStart = code.indexOf(`function ${decryptFuncName}`); if (funcStart !== -1) { const funcEnd = code.indexOf('}', funcStart + 100); const funcBody = code.slice(funcStart, funcEnd + 1); const match2 = funcBody.match(pattern2); if (match2) { return parseInt(match2[1]); } } return 0; } /** * 提取并执行 shuffle IIFE */ function extractAndRunShuffle(code, stringArray, arrayFuncName) { // 查找 shuffle IIFE const shufflePattern = /\(function\s*\(\s*_0x[a-f0-9]+(?:\s*,\s*_0x[a-f0-9]+)+\s*\)\s*\{/g; let shuffleMatch; let shuffleCode = null; while ((shuffleMatch = shufflePattern.exec(code)) !== null) { const potentialStart = shuffleMatch.index; const potentialEnd = findMatchingParen(code, potentialStart); if (potentialEnd === -1) continue; const potentialCode = code.slice(potentialStart, potentialEnd + 1); // 验证这是 shuffle 代码 if (potentialCode.includes(arrayFuncName) && (potentialCode.includes('shift') || potentialCode.includes('push')) && !potentialCode.includes('return[vip,')) { shuffleCode = potentialCode; break; } } if (!shuffleCode) return stringArray; // 从主解密函数提取偏移量(重要!不是从 shuffle 中提取) const mainOffsetMatch = code.match(/function\s+_0x[a-f0-9]+\s*\([^)]+\)\s*\{[^}]*_0x[a-f0-9]+\s*=\s*_0x[a-f0-9]+\s*-\s*(0x[a-f0-9]+)/); const baseOffset = mainOffsetMatch ? parseInt(mainOffsetMatch[1]) : 0; // 使用同一个数组引用 const shuffledArray = [...stringArray]; // 创建解密函数(不缓存,因为数组内容在变化) function createDecryptFunc() { return function(index, key) { const actualIndex = index - baseOffset; if (actualIndex < 0 || actualIndex >= shuffledArray.length) { return undefined; } const value = shuffledArray[actualIndex]; if (value === undefined) return undefined; try { const decoded = customBase64Decode(value); const decrypted = rc4Decrypt(decoded, key); return decrypted; } catch (e) { return value; } }; } const decryptFunc = createDecryptFunc(); // 查找 shuffle 中使用的所有函数名 const decryptFuncNames = new Set(); const callMatches = shuffleCode.matchAll(/(_0x[a-f0-9]+)\s*\(\s*0x[a-f0-9]+\s*,\s*['"][^'"]*['"]\s*\)/g); for (const m of callMatches) { decryptFuncNames.add(m[1]); } const aliasMatches = shuffleCode.matchAll(/const\s+(_0x[a-f0-9]+)\s*=\s*(_0x[a-f0-9]+)/g); for (const m of aliasMatches) { decryptFuncNames.add(m[1]); decryptFuncNames.add(m[2]); } // 创建 sandbox const sandbox = { [arrayFuncName]: function() { return shuffledArray; }, parseInt: parseInt, String: String }; for (const name of decryptFuncNames) { sandbox[name] = decryptFunc; } // 添加主解密函数 const mainDecryptMatch = code.match(/function\s+(_0x[a-f0-9]+)\s*\(\s*_0x[a-f0-9]+\s*,\s*_0x[a-f0-9]+\s*\)\s*\{[^]*?const\s+_0x[a-f0-9]+\s*=\s*[a-zA-Z_$][a-zA-Z0-9_$]*\s*\(\s*\)/); if (mainDecryptMatch && !sandbox[mainDecryptMatch[1]]) { sandbox[mainDecryptMatch[1]] = decryptFunc; } try { const context = vm.createContext(sandbox); vm.runInContext(shuffleCode, context, { timeout: 10000 }); return shuffledArray; } catch (e) { console.error(' Shuffle execution error:', e.message); return stringArray; } } /** * 查找所有解密函数名称(包括别名,递归查找) */ function findDecryptFuncInfo(code, arrayFuncName) { const result = { names: [], baseOffset: 0 }; // 查找主解密函数 // 模式: function _0xXXXX(_0xYYYY, _0xZZZZ) { ... _0xArrayFunc() ... } const mainPattern = new RegExp( `function\\s+(_0x[a-f0-9]+)\\s*\\(\\s*(_0x[a-f0-9]+)\\s*,\\s*(_0x[a-f0-9]+)\\s*\\)\\s*\\{[^]*?${arrayFuncName}`, 'g' ); let mainMatch; while ((mainMatch = mainPattern.exec(code)) !== null) { const funcName = mainMatch[1]; if (!result.names.includes(funcName)) { result.names.push(funcName); // 提取基础偏移量 const funcStart = mainMatch.index; const funcBodyStart = code.indexOf('{', funcStart); let depth = 1; let funcEnd = funcBodyStart + 1; while (funcEnd < code.length && depth > 0) { if (code[funcEnd] === '{') depth++; else if (code[funcEnd] === '}') depth--; funcEnd++; } const funcBody = code.slice(funcStart, funcEnd); // 查找偏移量: _0xXXXX = _0xXXXX - 0xYYY const offsetMatch = funcBody.match(/_0x[a-f0-9]+\s*=\s*_0x[a-f0-9]+\s*-\s*(0x[a-f0-9]+|\d+)/); if (offsetMatch && result.baseOffset === 0) { result.baseOffset = parseInt(offsetMatch[1]); } } } // 递归查找所有别名 // 模式: const/var/let _0xXXXX = _0xKnownFunc let foundNew = true; while (foundNew) { foundNew = false; for (const knownName of [...result.names]) { const aliasPattern = new RegExp( `(?:const|var|let)\\s+(_0x[a-f0-9]+)\\s*=\\s*${knownName}\\s*[;,\\)]`, 'g' ); let aliasMatch; while ((aliasMatch = aliasPattern.exec(code)) !== null) { if (!result.names.includes(aliasMatch[1])) { result.names.push(aliasMatch[1]); foundNew = true; } } } } // 还要查找所有实际使用的解密函数名(从调用模式中提取) // 这能捕获内联定义的别名 const callPattern = /(_0x[a-f0-9]+)\s*\(\s*0x[a-f0-9]+\s*,\s*['"][^'"]*['"]\s*\)/g; let callMatch; while ((callMatch = callPattern.exec(code)) !== null) { if (!result.names.includes(callMatch[1])) { result.names.push(callMatch[1]); } } return result; } /** * 替换所有加密字符串调用 */ function replaceEncryptedStrings(code, shuffledArray, decryptFuncNames, baseOffset) { let result = code; let totalReplaced = 0; for (const funcName of decryptFuncNames) { // 匹配解密函数调用: _0xFunc(0xIndex, 'key') const callPattern = new RegExp( `${funcName}\\s*\\(\\s*(0x[a-f0-9]+|\\d+)\\s*,\\s*(['"])([^'"]*?)\\2\\s*\\)`, 'gi' ); let match; const replacements = []; while ((match = callPattern.exec(result)) !== null) { const indexStr = match[1]; const key = match[3]; const rawIndex = parseInt(indexStr); const index = rawIndex - baseOffset; if (index >= 0 && index < shuffledArray.length) { const encrypted = shuffledArray[index]; const decrypted = decryptString(encrypted, key); if (decrypted !== null) { replacements.push({ start: match.index, end: match.index + match[0].length, original: match[0], replacement: JSON.stringify(decrypted) }); } } } // 从后向前替换 for (let i = replacements.length - 1; i >= 0; i--) { const r = replacements[i]; result = result.slice(0, r.start) + r.replacement + result.slice(r.end); } totalReplaced += replacements.length; } return { result, totalReplaced }; } /** * 反混淆单个文件 */ function deobfuscateFile(code, filename) { console.log(`\n处理: ${filename}`); // 1. 提取字符串数组 const arrayInfo = extractStringArray(code); if (!arrayInfo) { console.log(' 未找到字符串数组'); return { code, stats: { found: 0, replaced: 0 } }; } console.log(` 字符串数组: ${arrayInfo.funcName} (${arrayInfo.strings.length} 个)`); // 2. 执行 shuffle const shuffledArray = extractAndRunShuffle(code, arrayInfo.strings, arrayInfo.funcName); console.log(` Shuffle 后: ${shuffledArray.length} 个`); // 3. 找到解密函数和偏移量 const decryptInfo = findDecryptFuncInfo(code, arrayInfo.funcName); console.log(` 解密函数: ${decryptInfo.names.join(', ')}`); console.log(` 基础偏移: 0x${decryptInfo.baseOffset.toString(16)} (${decryptInfo.baseOffset})`); // 4. 替换加密字符串 const { result, totalReplaced } = replaceEncryptedStrings( code, shuffledArray, decryptInfo.names, decryptInfo.baseOffset ); console.log(` 替换: ${totalReplaced} 个字符串`); return { code: result, stats: { found: arrayInfo.strings.length, replaced: totalReplaced } }; } /** * 递归处理目录 */ function processDirectory(inputDir, outputDir) { if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } const entries = fs.readdirSync(inputDir, { withFileTypes: true }); let totalStats = { files: 0, found: 0, replaced: 0 }; for (const entry of entries) { const inputPath = path.join(inputDir, entry.name); const outputPath = path.join(outputDir, entry.name); if (entry.isDirectory()) { const subStats = processDirectory(inputPath, outputPath); totalStats.files += subStats.files; totalStats.found += subStats.found; totalStats.replaced += subStats.replaced; } else if (entry.name.endsWith('.js')) { const code = fs.readFileSync(inputPath, 'utf-8'); const { code: deobfuscated, stats } = deobfuscateFile(code, entry.name); fs.writeFileSync(outputPath, deobfuscated, 'utf-8'); totalStats.files++; totalStats.found += stats.found; totalStats.replaced += stats.replaced; } } return totalStats; } /** * 主函数 */ function main() { const args = process.argv.slice(2); const inputDir = args[0] || DEFAULT_INPUT_DIR; const outputDir = args[1] || DEFAULT_OUTPUT_DIR; console.log('='.repeat(60)); console.log('CursorPro Deobfuscator v12'); console.log('='.repeat(60)); console.log(`输入目录: ${inputDir}`); console.log(`输出目录: ${outputDir}`); if (!fs.existsSync(inputDir)) { console.error(`错误: 输入目录不存在: ${inputDir}`); process.exit(1); } const stats = processDirectory(inputDir, outputDir); console.log('\n' + '='.repeat(60)); console.log('完成!'); console.log(` 处理文件: ${stats.files}`); console.log(` 字符串总数: ${stats.found}`); console.log(` 成功替换: ${stats.replaced}`); console.log(` 成功率: ${stats.found > 0 ? (stats.replaced / stats.found * 100).toFixed(1) : 0}%`); console.log('='.repeat(60)); } main();