291 lines
9.0 KiB
JavaScript
291 lines
9.0 KiB
JavaScript
/**
|
||
* 直接解码脚本 - 使用 RC4 + 非标准 Base64 算法
|
||
* 不依赖沙盒执行,直接实现解码逻辑
|
||
*/
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const baseDir = 'D:/temp/破解/cursorpro-0.4.5';
|
||
|
||
// 非标准 Base64 字母表 (小写在前)
|
||
const BASE64_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';
|
||
|
||
/**
|
||
* 非标准 Base64 解码
|
||
*/
|
||
function base64Decode(str) {
|
||
let result = '';
|
||
let buffer = 0;
|
||
let bufferLen = 0;
|
||
|
||
for (let i = 0; i < str.length; i++) {
|
||
const char = str[i];
|
||
const charIndex = BASE64_CHARS.indexOf(char);
|
||
if (charIndex === -1) continue;
|
||
if (charIndex === 64) continue; // '=' padding
|
||
|
||
buffer = (buffer << 6) | charIndex;
|
||
bufferLen += 6;
|
||
|
||
if (bufferLen >= 8) {
|
||
bufferLen -= 8;
|
||
result += String.fromCharCode((buffer >> bufferLen) & 0xFF);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* RC4 解密
|
||
*/
|
||
function rc4Decrypt(data, key) {
|
||
// 初始化 S-box
|
||
const s = [];
|
||
for (let i = 0; i < 256; i++) {
|
||
s[i] = i;
|
||
}
|
||
|
||
let j = 0;
|
||
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]];
|
||
}
|
||
|
||
// 加密/解密
|
||
let result = '';
|
||
let i = 0;
|
||
j = 0;
|
||
|
||
for (let k = 0; k < data.length; k++) {
|
||
i = (i + 1) % 256;
|
||
j = (j + s[i]) % 256;
|
||
[s[i], s[j]] = [s[j], s[i]];
|
||
result += String.fromCharCode(data.charCodeAt(k) ^ s[(s[i] + s[j]) % 256]);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 解码单个字符串
|
||
*/
|
||
function decodeString(encodedStr, key) {
|
||
try {
|
||
const base64Decoded = base64Decode(encodedStr);
|
||
const decrypted = rc4Decrypt(base64Decoded, key);
|
||
return decrypted;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 提取字符串数组
|
||
*/
|
||
function extractStringArray(code) {
|
||
// 查找字符串数组 - 这是一个大的嵌套数组
|
||
const arrayMatch = code.match(/return\s*\[([^\]]*(?:\]\.concat\(function\s*\(\)\s*\{\s*return\s*\[[^\]]*\][^\}]*\}\(\)\))*[^\]]*)\]/s);
|
||
if (!arrayMatch) return null;
|
||
|
||
// 提取所有字符串
|
||
const strMatch = code.match(/return\s*\['([^']*)'(?:,'([^']*)')*\]/g);
|
||
if (!strMatch) return null;
|
||
|
||
const strings = [];
|
||
const allStrings = code.match(/'[^']*'/g) || [];
|
||
|
||
// 找到字符串数组的开始位置
|
||
const arrayStart = code.indexOf("const _0x") || code.indexOf("function _0x");
|
||
const arrayEnd = code.indexOf("Object.defineProperty(exports");
|
||
|
||
if (arrayStart !== -1 && arrayEnd !== -1) {
|
||
const arraySection = code.substring(arrayStart, arrayEnd);
|
||
// 提取这个区域内所有带引号的字符串
|
||
const stringMatches = arraySection.match(/'([^'\\]*(\\.[^'\\]*)*)'/g) || [];
|
||
|
||
for (const match of stringMatches) {
|
||
const str = match.slice(1, -1); // 移除引号
|
||
if (str.length > 3 && /^[a-zA-Z0-9+/=]+$/.test(str)) {
|
||
strings.push(str);
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log(` 提取了 ${strings.length} 个候选编码字符串`);
|
||
return strings;
|
||
}
|
||
|
||
/**
|
||
* 为文件生成解码映射
|
||
*/
|
||
function generateDecodeMapForFile(code, filename) {
|
||
console.log(`\n处理: ${filename}`);
|
||
console.log('='.repeat(50));
|
||
|
||
// 找到所有解码函数调用
|
||
const callPattern = /(_0x[a-f0-9]+)\s*\(\s*(0x[a-f0-9]+)\s*,\s*'([^']*)'\s*\)/g;
|
||
const calls = new Map();
|
||
let match;
|
||
|
||
while ((match = callPattern.exec(code)) !== null) {
|
||
const fullMatch = match[0];
|
||
const funcName = match[1];
|
||
const numHex = match[2];
|
||
const key = match[3];
|
||
|
||
calls.set(fullMatch, { funcName, numHex, key, num: parseInt(numHex, 16) });
|
||
}
|
||
|
||
console.log(` 找到 ${calls.size} 个唯一调用`);
|
||
|
||
// 提取字符串数组
|
||
// 找到类似这样的模式:
|
||
// return [vip, 'encodedStr1', 'encodedStr2', ...]
|
||
const stringArrayMatch = code.match(/return\s*\[\s*vip\s*,\s*('[^']*'(?:\s*,\s*'[^']*')*)\s*\]/);
|
||
let stringArray = ['cursor']; // vip = 'cursor'
|
||
|
||
if (stringArrayMatch) {
|
||
const stringsSection = stringArrayMatch[1];
|
||
const strings = stringsSection.match(/'([^']*)'/g);
|
||
if (strings) {
|
||
for (const s of strings) {
|
||
stringArray.push(s.slice(1, -1));
|
||
}
|
||
}
|
||
console.log(` 提取了 ${stringArray.length} 个数组元素`);
|
||
} else {
|
||
// 尝试另一种模式 - concat 嵌套数组
|
||
const concatPattern = /return\s*\[([^\]]*)\]\.concat/g;
|
||
const allArrays = [];
|
||
let cm;
|
||
while ((cm = concatPattern.exec(code)) !== null) {
|
||
const content = cm[1];
|
||
const strings = content.match(/'([^']*)'/g);
|
||
if (strings) {
|
||
for (const s of strings) {
|
||
allArrays.push(s.slice(1, -1));
|
||
}
|
||
}
|
||
}
|
||
if (allArrays.length > 0) {
|
||
stringArray = ['cursor', ...allArrays];
|
||
console.log(` 通过 concat 提取了 ${stringArray.length} 个数组元素`);
|
||
}
|
||
}
|
||
|
||
// 如果无法提取数组,返回空
|
||
if (stringArray.length < 10) {
|
||
console.log(' 无法提取足够的字符串数组');
|
||
return null;
|
||
}
|
||
|
||
// 尝试解码
|
||
const decodeMap = {};
|
||
let successCount = 0;
|
||
|
||
for (const [fullMatch, info] of calls) {
|
||
const { num, key } = info;
|
||
|
||
// 计算实际索引(减去偏移量)
|
||
// 不同文件的偏移量不同,需要尝试
|
||
for (let offset = 0; offset <= 0x200; offset += 0x10) {
|
||
const adjustedIndex = num - offset;
|
||
if (adjustedIndex >= 0 && adjustedIndex < stringArray.length) {
|
||
const encodedStr = stringArray[adjustedIndex];
|
||
const decoded = decodeString(encodedStr, key);
|
||
if (decoded && decoded.length > 0 && /^[\x20-\x7E\u4e00-\u9fff]+$/.test(decoded)) {
|
||
decodeMap[fullMatch] = decoded;
|
||
successCount++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log(` 成功解码: ${successCount}/${calls.size}`);
|
||
|
||
return decodeMap;
|
||
}
|
||
|
||
/**
|
||
* 使用已有的解码器脚本为每个文件生成映射
|
||
*/
|
||
async function generateWithExistingDecoder(inputPath, outputPath, filename) {
|
||
console.log(`\n处理: ${filename}`);
|
||
console.log('='.repeat(50));
|
||
|
||
if (fs.existsSync(outputPath)) {
|
||
const existing = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
||
console.log(` 已存在: ${Object.keys(existing).length} 条映射`);
|
||
return existing;
|
||
}
|
||
|
||
// 读取代码
|
||
const code = fs.readFileSync(inputPath, 'utf8');
|
||
|
||
// 查找解码器函数名
|
||
const decoderMatch = code.match(/function\s+(_0x[a-f0-9]+)\s*\(\s*_0x[a-f0-9]+\s*,\s*_0x[a-f0-9]+\s*\)\s*\{/);
|
||
if (!decoderMatch) {
|
||
console.log(' 未找到解码函数');
|
||
return null;
|
||
}
|
||
const decoderFuncName = decoderMatch[1];
|
||
console.log(` 解码函数: ${decoderFuncName}`);
|
||
|
||
// 收集所有调用
|
||
const callPattern = new RegExp(
|
||
`(_0x[a-f0-9]+)\\s*\\(\\s*(0x[a-f0-9]+)\\s*,\\s*'([^']*)'\\s*\\)`,
|
||
'g'
|
||
);
|
||
|
||
const calls = new Set();
|
||
let match;
|
||
while ((match = callPattern.exec(code)) !== null) {
|
||
calls.add(match[0]);
|
||
}
|
||
|
||
console.log(` 找到 ${calls.size} 个调用`);
|
||
|
||
// 对于没有映射的文件,我们需要用不同的方法
|
||
// 使用之前成功的解码器代码片段
|
||
|
||
return { size: calls.size, decoder: decoderFuncName };
|
||
}
|
||
|
||
// 主函数
|
||
async function main() {
|
||
console.log('╔════════════════════════════════════════════════════╗');
|
||
console.log('║ 直接解码映射生成器 ║');
|
||
console.log('║ 使用 RC4 + 非标准 Base64 ║');
|
||
console.log('╚════════════════════════════════════════════════════╝');
|
||
|
||
const files = [
|
||
{ input: '原版本/extension/out/utils/account.js', output: 'account_decoded_map.json' },
|
||
{ input: '原版本/extension/out/utils/sqlite.js', output: 'sqlite_decoded_map.json' },
|
||
{ input: '原版本/extension/out/api/client.js', output: 'client_decoded_map.json' }
|
||
];
|
||
|
||
for (const file of files) {
|
||
const inputPath = path.join(baseDir, file.input);
|
||
const outputPath = path.join(baseDir, file.output);
|
||
|
||
if (!fs.existsSync(inputPath)) {
|
||
console.log(`\n跳过: ${file.input} (不存在)`);
|
||
continue;
|
||
}
|
||
|
||
const code = fs.readFileSync(inputPath, 'utf8');
|
||
const decodeMap = generateDecodeMapForFile(code, file.input);
|
||
|
||
if (decodeMap && Object.keys(decodeMap).length > 0) {
|
||
fs.writeFileSync(outputPath, JSON.stringify(decodeMap, null, 2));
|
||
console.log(` 已保存: ${outputPath}`);
|
||
}
|
||
}
|
||
|
||
console.log('\n完成!');
|
||
}
|
||
|
||
main().catch(console.error);
|