备份: 完整开发状态(含反混淆脚本和临时文件)
This commit is contained in:
230
run_codex_deobf.js
Normal file
230
run_codex_deobf.js
Normal file
@@ -0,0 +1,230 @@
|
||||
/**
|
||||
* 反混淆并美化原版 extension/out 下的核心文件,输出到 codexfanbianyi/extension/out。
|
||||
* 策略:动态执行字符串数组 + 解码函数,解出所有形如 _0x****(0x123,'key') 的调用并替换为明文,再用 js-beautify 排版。
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const vm = require('vm');
|
||||
const beautify = require('js-beautify').js;
|
||||
|
||||
const baseDir = __dirname;
|
||||
const inputBase = path.join(baseDir, '原版本', 'extension', 'out');
|
||||
const outputBase = path.join(baseDir, 'codexfanbianyi', 'extension', 'out');
|
||||
|
||||
const targets = [
|
||||
'webview/provider.js',
|
||||
'extension.js',
|
||||
'utils/account.js',
|
||||
'utils/sqlite.js',
|
||||
'api/client.js'
|
||||
];
|
||||
|
||||
const mapPaths = {
|
||||
'webview/provider.js': path.join(baseDir, 'provider_decoded_map.json'),
|
||||
'extension.js': path.join(baseDir, 'extension_decoded_map.json'),
|
||||
'utils/account.js': path.join(baseDir, 'account_decoded_map.json'),
|
||||
'utils/sqlite.js': path.join(baseDir, 'sqlite_decoded_map.json'),
|
||||
'api/client.js': path.join(baseDir, 'client_decoded_map.json')
|
||||
};
|
||||
|
||||
function ensureDir(dir) {
|
||||
if (!fs.existsSync(dir)) {
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
setTimeout,
|
||||
clearTimeout,
|
||||
setInterval,
|
||||
clearInterval,
|
||||
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 {
|
||||
// 1) 先只注入字符串数组函数 + 解码函数(避免执行到 exports/require 等顶层逻辑)
|
||||
vm.runInContext(`${arrayFunc.code}\n${decoderFunc.code}`, sandbox, { timeout: 4000 });
|
||||
|
||||
// 2) 执行“数组旋转/IIFE”等启动代码(通常位于文件开头,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') {
|
||||
console.warn(` 解码器 ${decoderFunc.name} 不是函数`);
|
||||
return null;
|
||||
}
|
||||
return decoder.bind(sandbox);
|
||||
} catch (e) {
|
||||
console.warn(` 解码器初始化失败: ${e.message}`);
|
||||
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 replaceCalls(code, decoder) {
|
||||
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 replacedCount = 0;
|
||||
for (const [call, literal] of replacements) {
|
||||
const newCode = code.split(call).join(literal);
|
||||
if (newCode !== code) {
|
||||
replacedCount++;
|
||||
code = newCode;
|
||||
}
|
||||
}
|
||||
return { code, replacedCount, found: replacements.size };
|
||||
}
|
||||
|
||||
function replaceWithMap(code, relPath) {
|
||||
const mapPath = mapPaths[relPath];
|
||||
if (!mapPath || !fs.existsSync(mapPath)) {
|
||||
return { code, replacedCount: 0, entries: 0 };
|
||||
}
|
||||
const decodeMap = JSON.parse(fs.readFileSync(mapPath, 'utf8'));
|
||||
let replacedCount = 0;
|
||||
for (const [pattern, decoded] of Object.entries(decodeMap)) {
|
||||
const literal = literalFor(decoded);
|
||||
const newCode = code.split(pattern).join(literal);
|
||||
if (newCode !== code) {
|
||||
replacedCount++;
|
||||
code = newCode;
|
||||
}
|
||||
}
|
||||
return { code, replacedCount, entries: Object.keys(decodeMap).length };
|
||||
}
|
||||
|
||||
function cleanDecoderBlocks(code) {
|
||||
// 保留原始函数,避免因匹配不准破坏结构
|
||||
return code;
|
||||
}
|
||||
|
||||
function processFile(relPath) {
|
||||
const inputPath = path.join(inputBase, relPath);
|
||||
const outputPath = path.join(outputBase, relPath);
|
||||
|
||||
if (!fs.existsSync(inputPath)) {
|
||||
console.warn(`跳过缺失文件: ${relPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let code = fs.readFileSync(inputPath, 'utf8');
|
||||
console.log(`\n==== 处理 ${relPath} ====`);
|
||||
|
||||
const mapResult = replaceWithMap(code, relPath);
|
||||
if (mapResult.entries > 0) {
|
||||
console.log(` 预置映射 ${mapResult.entries} 条,替换 ${mapResult.replacedCount} 处`);
|
||||
code = mapResult.code;
|
||||
}
|
||||
|
||||
const decoder = buildDecoder(code);
|
||||
if (!decoder) {
|
||||
console.warn(' 未找到可用的解码器,直接美化原始代码');
|
||||
} else {
|
||||
const result = replaceCalls(code, decoder);
|
||||
console.log(` 找到 ${result.found} 个唯一调用,成功替换 ${result.replacedCount} 处`);
|
||||
code = result.code;
|
||||
}
|
||||
|
||||
code = cleanDecoderBlocks(code);
|
||||
code = beautify(code, { indent_size: 4, max_preserve_newlines: 2, end_with_newline: true });
|
||||
|
||||
ensureDir(path.dirname(outputPath));
|
||||
fs.writeFileSync(outputPath, code, 'utf8');
|
||||
console.log(` 输出: ${outputPath}`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
targets.forEach(processFile);
|
||||
console.log('\n全部处理完成。');
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user