Files
cursornew2026/tools/auto_deobf.js

214 lines
6.9 KiB
JavaScript

'use strict';
/**
* 通用解混淆脚本:对指定目录下的 JS 文件批量解码字符串、清理控制流并重命名 _0x 前缀标识符。
* 使用:
* node tools/auto_deobf.js
* 可配置 inputDir/outputDir/mapDir/rename 见下方常量。
*/
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const beautify = require('js-beautify').js;
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
// 配置:如有需要可修改
const inputDir = path.join(__dirname, '..', '原版本', 'extension', 'out');
const outputDir = path.join(__dirname, '..', 'codexfanbianyi', 'extension', 'out');
const mapDir = path.join(__dirname, '..'); // *_decoded_map.json 所在目录
const enableRename = true; // 是否批量重命名 _0x**** -> refN
function ensureDir(dir) {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
}
function listJsFiles(dir) {
let files = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
files = files.concat(listJsFiles(full));
} else if (entry.isFile() && entry.name.endsWith('.js')) {
files.push(full);
}
}
return files;
}
function extractFunctionWithBody(code, nameRegex) {
const match = nameRegex.exec(code);
if (!match) return null;
const start = match.index;
const braceStart = code.indexOf('{', start);
if (braceStart === -1) return null;
let depth = 0;
for (let i = braceStart; i < code.length; i++) {
const ch = code[i];
if (ch === '{') depth++;
else if (ch === '}') {
depth--;
if (depth === 0) {
return { name: match[1], code: code.slice(start, i + 1), end: i + 1 };
}
}
}
return null;
}
function buildSandbox() {
const sandbox = {
console: { log: () => {}, warn: () => {}, error: () => {} },
vip: 'cursor',
Buffer,
atob: (str) => Buffer.from(str, 'base64').toString('binary'),
btoa: (str) => Buffer.from(str, 'binary').toString('base64'),
decodeURIComponent,
encodeURIComponent,
parseInt,
String,
exports: {},
module: { exports: {} },
require: () => ({})
};
vm.createContext(sandbox);
return sandbox;
}
function buildDecoder(code) {
const arrayFunc = extractFunctionWithBody(code, /function\s+(_0x[a-f0-9]+)\s*\(\s*\)/i);
const decoderFunc = extractFunctionWithBody(code, /function\s+(_0x[a-f0-9]+)\s*\(\s*_0x[a-f0-9]+\s*,\s*_0x[a-f0-9]+\s*\)/i);
if (!arrayFunc || !decoderFunc) return null;
const sandbox = buildSandbox();
try {
// 仅执行字符串数组和解码函数,避免触发业务逻辑
vm.runInContext(`${arrayFunc.code}\n${decoderFunc.code}`, sandbox, { timeout: 4000 });
// 启动段:取 exports 之前的代码,通常包含数组旋转
const stopMarkers = ['var __createBinding', 'Object.defineProperty(exports', 'exports.', 'module.exports'];
let bootstrapEnd = code.length;
for (const marker of stopMarkers) {
const idx = code.indexOf(marker);
if (idx !== -1 && idx < bootstrapEnd) bootstrapEnd = idx;
}
const bootstrapCode = code.slice(0, bootstrapEnd);
vm.runInContext(bootstrapCode, sandbox, { timeout: 4000 });
const decoder = sandbox[decoderFunc.name];
if (typeof decoder !== 'function') return null;
return decoder.bind(sandbox);
} catch {
return null;
}
}
function literalFor(decoded) {
if (decoded == null) return 'undefined';
if (typeof decoded !== 'string') return JSON.stringify(decoded);
if (decoded.includes('\n')) return '`' + decoded.replace(/`/g, '\\`') + '`';
return JSON.stringify(decoded);
}
function replaceWithMap(code, filePath) {
const mapName = path.basename(filePath).replace('.js', '_decoded_map.json');
const mapPath = path.join(mapDir, mapName);
if (!fs.existsSync(mapPath)) return { code, replaced: 0, entries: 0 };
const decodeMap = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
let replaced = 0;
for (const [pattern, decoded] of Object.entries(decodeMap)) {
const literal = literalFor(decoded);
const newCode = code.split(pattern).join(literal);
if (newCode !== code) {
replaced++;
code = newCode;
}
}
return { code, replaced, entries: Object.keys(decodeMap).length };
}
function replaceWithDecoder(code, decoder) {
if (!decoder) return { code, replaced: 0, found: 0 };
const callPattern = /_0x[a-f0-9]+\s*\(\s*(0x[a-f0-9]+|\d+)\s*,\s*['"]([^'"]+)['"]\s*\)/gi;
const replacements = new Map();
let match;
while ((match = callPattern.exec(code)) !== null) {
const full = match[0];
if (replacements.has(full)) continue;
const index = match[1].startsWith('0x') ? parseInt(match[1], 16) : parseInt(match[1], 10);
const key = match[2];
try {
const decoded = decoder(index, key);
if (typeof decoded === 'string') {
replacements.set(full, literalFor(decoded));
}
} catch {
/* ignore individual decode errors */
}
}
let replaced = 0;
for (const [call, lit] of replacements) {
const newCode = code.split(call).join(lit);
if (newCode !== code) {
replaced++;
code = newCode;
}
}
return { code, replaced, found: replacements.size };
}
function renameObfuscatedIdentifiers(code) {
if (!enableRename) return code;
const ast = parser.parse(code, { sourceType: 'unambiguous', plugins: ['jsx'] });
let counter = 1;
const renameMap = new Map();
traverse(ast, {
Identifier(path) {
const name = path.node.name;
if (!/^_0x[a-f0-9]+$/i.test(name)) return;
const binding = path.scope.getBinding(name);
if (!binding) return;
if (renameMap.has(name)) return;
const newName = `ref${counter++}`;
binding.scope.rename(name, newName);
renameMap.set(name, newName);
}
});
return generate(ast, { compact: false }).code;
}
function processFile(inFile) {
const rel = path.relative(inputDir, inFile);
const outFile = path.join(outputDir, rel);
console.log(`\n[process] ${rel}`);
let code = fs.readFileSync(inFile, 'utf8');
const mapResult = replaceWithMap(code, inFile);
if (mapResult.entries > 0) console.log(` map: ${mapResult.replaced}/${mapResult.entries}`);
code = mapResult.code;
const decoder = buildDecoder(code);
if (!decoder) {
console.log(' decoder: none');
} else {
const decRes = replaceWithDecoder(code, decoder);
console.log(` decoder replace: ${decRes.replaced}/${decRes.found}`);
code = decRes.code;
}
code = renameObfuscatedIdentifiers(code);
code = beautify(code, { indent_size: 2, max_preserve_newlines: 2, end_with_newline: true });
ensureDir(path.dirname(outFile));
fs.writeFileSync(outFile, code, 'utf8');
console.log(` output: ${outFile}`);
}
function main() {
const files = listJsFiles(inputDir);
console.log(`Found ${files.length} JS files under ${inputDir}`);
files.forEach(processFile);
console.log('\nAll done.');
}
main();