const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;
const vm = require('vm');
// Helper to check if a node is a literal
function isLiteral(node) {
return t.isStringLiteral(node) || t.isNumericLiteral(node) || t.isBooleanLiteral(node) || t.isNullLiteral(node);
}
async function main() {
const inputFile = path.join(__dirname, 'example-1.js');
if (!fs.existsSync(inputFile)) {
console.error(`File ${inputFile} not found.`);
return;
}
const code = fs.readFileSync(inputFile, 'utf8');
console.log(`Processing ${inputFile} (${code.length} bytes)...`);
const ast = parser.parse(code, {
sourceType: 'script',
plugins: ['bigInt']
});
// ---------------------------------------------------------
// Step 1: Prepare Execution Environment (Prelude & Full Execution)
// ---------------------------------------------------------
let mainIIFEPath = null;
let mainBlock = null;
let iifeScope = null;
traverse(ast, {
CallExpression(path) {
if (t.isFunctionExpression(path.node.callee) && !mainIIFEPath) {
const body = path.node.callee.body.body;
if (body.length > 50) {
mainIIFEPath = path;
mainBlock = body;
iifeScope = path.get('callee').scope;
path.stop();
}
}
}
});
if (!mainIIFEPath) {
console.error("Could not find main IIFE.");
return;
}
console.log("Found main IIFE.");
// Identify IIFE variables and functions
const iifeVars = new Set();
mainBlock.forEach(node => {
if (t.isVariableDeclaration(node)) {
node.declarations.forEach(dec => {
if (t.isIdentifier(dec.id)) {
iifeVars.add(dec.id.name);
}
});
} else if (t.isFunctionDeclaration(node)) {
if (node.id && t.isIdentifier(node.id)) {
iifeVars.add(node.id.name);
}
}
});
// Add potentially missing critical variables
['tC', 'Rl', 'Wg', 'MS', 'Um', 'kg', 'hW', 'JQ', 'qp', 'KC', 'lQ', 'l2', 'b6', 'hO', 'An', 'H8', 'Cc', 'bL', 'Y8', 'Un', 'ZO', 'DH', 'd7', 'cY', 'XO', 'A7', 'QS', 'Mp', 'TO', 'Vg', 'k6', 'Vx', 'OC', 'kH', 'N8', 'pH', 'vm', 'Qc', 'IO', 'Qm', 'nm', 'CX', 'wc', 'XB', 'sS', 'Up', 'Bf', 'Fb', 'zw', 'lC', 'IX', 'Zc', 'Xl', 'jl', 'cH', 'T4', 'pv', 'tY', 'A8', 'rL', 'lF', 'B8', 'YO', 'Mq', 'K6', 'XKN', 'AQ', 'SL', 'zH', 'hd', 'n7', 'QQ', 'Hr', 'Ih', 'fm', 'pB', 'md', 'm4', 'ptN', 'cQ', 'ZhN', 'cw', 'krN', 'Vp', 'Dc', 'kt', 'ZS', 'pp', 'X8', 'El'].forEach(v => iifeVars.add(v));
// Setup VM Sandbox with enhanced mocks
const sandbox = {
window: {
screen: { colorDepth: 24, pixelDepth: 24, availWidth: 1920, availHeight: 1080 },
navigator: {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
plugins: [],
maxTouchPoints: 0,
languages: ['en-US', 'en'],
hardwareConcurrency: 4,
deviceMemory: 8,
webdriver: false,
onLine: true,
cookieEnabled: true,
javaEnabled: () => false,
doNotTrack: null,
platform: 'Win32',
appName: 'Netscape',
appCodeName: 'Mozilla',
product: 'Gecko',
vendor: 'Google Inc.'
},
document: {
createElement: () => ({ style: {}, appendChild: () => {}, remove: () => {}, getAttribute: () => null, setAttribute: () => {} }),
location: { protocol: 'https:', hostname: 'example.com', href: 'https://example.com/' },
addEventListener: () => {},
attachEvent: () => {},
documentElement: { style: {} },
head: { appendChild: () => {} },
body: { appendChild: () => {} },
cookie: '',
referrer: '',
getElementById: () => null,
getElementsByTagName: () => []
},
chrome: { runtime: {} },
performance: { memory: { jsHeapSizeLimit: 2147483648, totalJSHeapSize: 10000000, usedJSHeapSize: 5000000 }, now: () => Date.now() },
localStorage: { getItem: () => null, setItem: () => {}, removeItem: () => {} },
sessionStorage: { getItem: () => null, setItem: () => {}, removeItem: () => {} },
indexedDB: {},
openDatabase: () => {},
DeviceOrientationEvent: {},
DeviceMotionEvent: {},
TouchEvent: {},
history: {},
location: { protocol: 'https:', hostname: 'example.com', href: 'https://example.com/' },
getComputedStyle: () => ({}),
frames: { ServiceWorkerRegistration: {} },
ServiceWorker: {},
ServiceWorkerContainer: {},
innerWidth: 1920,
innerHeight: 1080,
outerWidth: 1920,
outerHeight: 1080,
screenX: 0,
screenY: 0,
pageXOffset: 0,
pageYOffset: 0,
top: {},
parent: {},
self: {}
},
document: {
createElement: () => ({ style: {}, appendChild: () => {}, remove: () => {}, getAttribute: () => null, setAttribute: () => {} }),
location: { protocol: 'https:', hostname: 'example.com' },
documentElement: { style: {} },
head: { appendChild: () => {} },
body: { appendChild: () => {} },
referrer: ''
},
navigator: { userAgent: 'Mozilla/5.0' },
console: console,
Array: Array,
String: String,
Object: Object,
Math: Math,
parseInt: parseInt,
parseFloat: parseFloat,
RegExp: RegExp,
Date: Date,
Function: Function,
JSON: JSON,
Error: Error,
TypeError: TypeError,
setTimeout: (fn) => fn(),
setInterval: () => {},
clearTimeout: () => {},
clearInterval: () => {},
encodeURIComponent: encodeURIComponent,
decodeURIComponent: decodeURIComponent,
unescape: unescape,
escape: escape
};
// Circular references and polyfills
sandbox.window.window = sandbox.window;
sandbox.window.self = sandbox.window;
sandbox.window.global = sandbox.window;
sandbox.window.top = sandbox.window;
sandbox.window.parent = sandbox.window;
// Copy standard globals to window
['Array', 'String', 'Object', 'Math', 'parseInt', 'parseFloat', 'RegExp', 'Date', 'Function', 'JSON', 'Error', 'TypeError', 'encodeURIComponent', 'decodeURIComponent', 'unescape', 'escape', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'].forEach(prop => {
sandbox.window[prop] = sandbox[prop];
});
sandbox.self = sandbox.window;
sandbox.global = sandbox.window;
const vmCtx = vm.createContext(sandbox);
// Instrument IIFE to export variables at the end using Try-Catch-Finally
// Split into functions (export early) and variables (export late)
const funcNames = new Set();
const varNames = new Set();
mainBlock.forEach(node => {
if (t.isFunctionDeclaration(node) && node.id && t.isIdentifier(node.id)) {
funcNames.add(node.id.name);
} else if (t.isVariableDeclaration(node)) {
node.declarations.forEach(dec => {
if (t.isIdentifier(dec.id)) {
varNames.add(dec.id.name);
}
});
}
});
// Manual list classification (best effort)
const manualFuncs = ['ZhN', 'cw', 'krN', 'Vp', 'Dc', 'kt', 'ZS', 'pp', 'X8', 'El', 'fw', 'wf', 'j8', 'v3N', 'k3N', 'LM', 'FPN', 'P6', 'Zc', 'l2', 'G0N'];
const manualVars = ['tC', 'Rl', 'Wg', 'MS', 'Um', 'kg', 'hW', 'JQ', 'qp', 'KC', 'lQ', 'b6', 'hO', 'An', 'H8', 'Cc', 'bL', 'Y8', 'Un', 'ZO', 'DH', 'd7', 'cY', 'XO', 'A7', 'QS', 'Mp', 'TO', 'Vg', 'k6', 'Vx', 'OC', 'kH', 'N8', 'pH', 'vm', 'Qc', 'IO', 'Qm', 'nm', 'CX', 'wc', 'XB', 'sS', 'Up', 'Bf', 'Fb', 'zw', 'lC', 'IX', 'Xl', 'jl', 'cH', 'T4', 'pv', 'tY', 'A8', 'rL', 'lF', 'B8', 'YO', 'Mq', 'K6', 'XKN', 'AQ', 'SL', 'zH', 'hd', 'n7', 'QQ', 'Hr', 'Ih', 'fm', 'pB', 'md', 'm4', 'ptN', 'cQ'];
manualFuncs.forEach(f => funcNames.add(f));
manualVars.forEach(v => varNames.add(v));
const funcExportCode = Array.from(funcNames).map(v => `try { window['${v}'] = ${v}; } catch(e) {}`).join('\n');
const varExportCode = Array.from(varNames).map(v => `try { window['${v}'] = ${v}; } catch(e) {}`).join('\n');
const originalBody = [...mainBlock];
mainBlock.length = 0; // Clear
// Prepend function exports to try block
const funcExportNodes = parser.parse(funcExportCode).program.body;
const tryBodyNodes = [...funcExportNodes, ...originalBody];
const tryBlock = t.blockStatement(tryBodyNodes);
const catchBlock = t.catchClause(t.identifier('e'), t.blockStatement([
t.expressionStatement(t.callExpression(
t.memberExpression(t.identifier('console'), t.identifier('log')),
[t.stringLiteral("Error in IIFE execution:"), t.memberExpression(t.identifier('e'), t.identifier('message'))]
))
]));
const finalizerBlock = t.blockStatement(parser.parse(varExportCode).program.body);
const tryStmt = t.tryStatement(tryBlock, catchBlock, finalizerBlock);
mainBlock.push(tryStmt);
// Re-generate the code from AST
const instrumentedCode = generator(ast).code;
console.log("Executing instrumented IIFE in VM...");
try {
vm.runInContext(instrumentedCode, vmCtx, { timeout: 10000 });
console.log("IIFE execution completed (possibly with caught errors).");
} catch (e) {
console.error("Error executing IIFE wrapper:", e.message);
}
function evaluateInVM(codeSnippet) {
try {
return vm.runInContext(codeSnippet, vmCtx);
} catch (e) {
return undefined;
}
}
// ---------------------------------------------------------
// Step 2: Constant Propagation (Aggressive & Assignment Aware)
// ---------------------------------------------------------
console.log("Starting constant propagation...");
let constantCount = 0;
// 1. Track assignments to all top-level variables
const assignmentCounts = new Map();
const invalidCandidates = new Set();
traverse(ast, {
AssignmentExpression(path) {
if (t.isIdentifier(path.node.left)) {
const name = path.node.left.name;
assignmentCounts.set(name, (assignmentCounts.get(name) || 0) + 1);
}
},
UpdateExpression(path) {
if (t.isIdentifier(path.node.argument)) {
const name = path.node.argument.name;
invalidCandidates.add(name);
}
},
VariableDeclarator(path) {
if (t.isIdentifier(path.node.id) && path.node.init) {
const name = path.node.id.name;
assignmentCounts.set(name, (assignmentCounts.get(name) || 0) + 1);
}
}
});
// 2. Identify constants from VM state
const constantBindings = new Map(); // binding -> value
for (const key of iifeVars) {
if (invalidCandidates.has(key)) continue;
const count = assignmentCounts.get(key) || 0;
// Allow 1 assignment (init) or 0 (if implicit/VM magic, though unlikely)
if (count <= 1) {
try {
const val = sandbox.window[key];
if (val !== undefined && (typeof val === 'number' || typeof val === 'boolean' || typeof val === 'string')) {
const binding = iifeScope.getBinding(key);
if (binding) {
constantBindings.set(binding, val);
}
}
} catch (e) {}
}
}
console.log(`Identified ${constantBindings.size} global constants (single-assignment).`);
// 3. Propagate
traverse(ast, {
Identifier(path) {
// Don't replace declarations or assignments
if (path.key === 'id' || path.key === 'params' || path.key === 'property' && !path.parent.computed) return;
if (t.isAssignmentExpression(path.parent) && path.parent.left === path.node) return;
if (t.isUpdateExpression(path.parent)) return;
const binding = path.scope.getBinding(path.node.name);
if (binding && constantBindings.has(binding)) {
path.replaceWith(t.valueToNode(constantBindings.get(binding)));
constantCount++;
}
}
});
console.log(`Propagated ${constantCount} constants.`);
// ---------------------------------------------------------
// Step 3: Proxy Replacement & String Decoding
// ---------------------------------------------------------
console.log("Starting proxy replacement and string decoding...");
let replacedCount = 0;
// Helper to statically extract array from function
function extractArrayFromFunction(funcName, funcNode) {
// Case 1: function f() { return ['a', 'b']; }
if (funcNode.body.body.length === 1 && t.isReturnStatement(funcNode.body.body[0])) {
const arg = funcNode.body.body[0].argument;
if (t.isArrayExpression(arg)) {
return arg.elements.map(e => t.isLiteral(e) ? e.value : null);
}
}
// Case 2: function f() { var x = ['a']; f = function() { return x; }; return x; }
// Akamai style:
// function ZhN() {
// var wXN = ['XA', ...];
// ZhN = function() { return wXN; };
// return wXN;
// }
const body = funcNode.body.body;
for (const stmt of body) {
if (t.isVariableDeclaration(stmt)) {
for (const dec of stmt.declarations) {
if (t.isArrayExpression(dec.init)) {
return dec.init.elements.map(e => t.isLiteral(e) ? e.value : null);
}
}
}
}
return null;
}
// Detect array-based string decoders
const stringArrays = new Map();
// Debug: Check if ZhN is available
try {
console.log("Type of ZhN in VM:", evaluateInVM("typeof ZhN"));
console.log("Type of cw in VM:", evaluateInVM("typeof cw"));
} catch(e) { console.log("Debug check failed:", e.message); }
mainBlock.forEach(node => {
if (t.isFunctionDeclaration(node) && node.id) {
const funcName = node.id.name;
let result = null;
// Try static extraction first (safer/faster)
const staticArray = extractArrayFromFunction(funcName, node);
if (staticArray && staticArray.length > 10 && typeof staticArray[0] === 'string') {
console.log(`Statically detected string array function: ${funcName} (${staticArray.length} strings)`);
result = staticArray;
}
// Try VM if static failed
if (!result) {
try {
const vmResult = evaluateInVM(`${funcName}()`);
if (Array.isArray(vmResult) && vmResult.length > 10 && typeof vmResult[0] === 'string') {
console.log(`VM detected string array function: ${funcName} (${vmResult.length} strings)`);
result = vmResult;
}
} catch (e) {
// console.log(`Failed to evaluate ${funcName}: ${e.message}`);
}
}
if (result) {
stringArrays.set(funcName, result);
}
}
});
const stringHelpers = new Map();
mainBlock.forEach(node => {
if (t.isFunctionDeclaration(node) && node.id) {
const body = node.body.body;
if (body.length === 1 && t.isReturnStatement(body[0])) {
const arg = body[0].argument;
if (t.isMemberExpression(arg) && t.isCallExpression(arg.object) && t.isIdentifier(arg.object.callee)) {
const arrayFuncName = arg.object.callee.name;
if (stringArrays.has(arrayFuncName)) {
console.log(`Detected string helper: ${node.id.name} -> ${arrayFuncName}`);
stringHelpers.set(node.id.name, {
arrayFunc: arrayFuncName,
array: stringArrays.get(arrayFuncName)
});
}
}
}
}
});
traverse(ast, {
CallExpression(path) {
if (isLiteral(path.node)) return;
if (t.isIdentifier(path.node.callee) && stringHelpers.has(path.node.callee.name)) {
const helper = stringHelpers.get(path.node.callee.name);
if (path.node.arguments.length === 1 && isLiteral(path.node.arguments[0])) {
let index = -1;
if (t.isNumericLiteral(path.node.arguments[0])) index = path.node.arguments[0].value;
if (index >= 0 && index < helper.array.length) {
path.replaceWith(t.valueToNode(helper.array[index]));
replacedCount++;
return;
}
}
}
// General VM evaluation for other proxies (now that constants are propagated)
try {
const codeSnippet = generator(path.node).code;
if (codeSnippet.length > 100) return;
const allSimple = path.node.arguments.every(a => isLiteral(a) || t.isIdentifier(a));
if (!allSimple) return;
const ids = codeSnippet.match(/[a-zA-Z_$][a-zA-Z0-9_$]*/g) || [];
const allGlobal = ids.every(id => sandbox.window.hasOwnProperty(id) || sandbox.hasOwnProperty(id) || global[id] !== undefined);
if (allGlobal) {
const result = evaluateInVM(codeSnippet);
if (result !== undefined && (typeof result === 'string' || typeof result === 'number' || typeof result === 'boolean')) {
path.replaceWith(t.valueToNode(result));
replacedCount++;
}
}
} catch (e) {}
},
MemberExpression(path) {
if (t.isCallExpression(path.node.object) && t.isIdentifier(path.node.object.callee)) {
const funcName = path.node.object.callee.name;
if (stringArrays.has(funcName)) {
const arr = stringArrays.get(funcName);
let index = -1;
if (t.isNumericLiteral(path.node.property)) {
index = path.node.property.value;
} else if (path.node.computed) {
// Now that constants are propagated, this might resolve
try {
const propCode = generator(path.node.property).code;
const val = evaluateInVM(propCode);
if (typeof val === 'number') index = val;
} catch(e) {}
}
if (index >= 0 && index < arr.length) {
path.replaceWith(t.valueToNode(arr[index]));
replacedCount++;
}
}
}
}
});
console.log(`Replaced ${replacedCount} proxy calls/strings.`);
// ---------------------------------------------------------
// Step 4: Control Flow Unflattening
// ---------------------------------------------------------
let unflattenedCount = 0;
const flattenedFunctions = new Map();
traverse(ast, {
Function(path) {
if (path.isFunctionDeclaration() || path.isFunctionExpression()) {
const info = analyzeFlattenedFunction(path);
if (info) {
if (path.node.id) flattenedFunctions.set(path.node.id.name, info);
if (path.parentPath.isVariableDeclarator()) {
flattenedFunctions.set(path.parentPath.node.id.name, info);
}
}
}
}
});
function analyzeFlattenedFunction(path) {
const body = path.get('body');
if (!body.isBlockStatement()) return null;
const statements = body.get('body');
let loopStmt = statements.find(p => p.isWhileStatement() || p.isForStatement());
if (!loopStmt) return null;
let loopBody = loopStmt.get('body');
if (!loopBody.isBlockStatement()) return null;
let switchStmt = loopBody.get('body').find(p => p.isSwitchStatement());
if (!switchStmt) return null;
const discriminant = switchStmt.node.discriminant;
if (!t.isIdentifier(discriminant)) return null;
const stateVarName = discriminant.name;
const cases = new Map();
switchStmt.get('cases').forEach(casePath => {
const test = casePath.node.test;
if (test) {
if (t.isLiteral(test)) {
cases.set(test.value, casePath);
} else {
try {
const val = evaluateInVM(generator(test).code);
if (val !== undefined) cases.set(val, casePath);
} catch(e) {}
}
}
});
const params = path.node.params;
let entryArgIndex = -1;
params.forEach((param, index) => {
if (t.isIdentifier(param) && param.name === stateVarName) entryArgIndex = index;
});
return { path, stateVarName, cases, entryArgIndex };
}
traverse(ast, {
CallExpression(path) {
const callee = path.node.callee;
let funcName = null;
if (t.isIdentifier(callee)) funcName = callee.name;
if (funcName && flattenedFunctions.has(funcName)) {
const info = flattenedFunctions.get(funcName);
if (info.entryArgIndex === -1) return;
const startStateArg = path.node.arguments[info.entryArgIndex];
if (!startStateArg) return;
let startStateVal;
if (isLiteral(startStateArg)) startStateVal = startStateArg.value;
else {
try { startStateVal = evaluateInVM(generator(startStateArg).code); } catch(e) {}
}
if (startStateVal !== undefined) {
console.log(`Unflattening call to ${funcName} with state ${startStateVal}`);
const newBody = reconstructPath(info, startStateVal, path.node.arguments);
if (newBody && newBody.length > 0) {
const iife = t.callExpression(
t.functionExpression(null, [], t.blockStatement(newBody)),
[]
);
t.inheritsComments(iife, path.node);
path.replaceWith(iife);
unflattenedCount++;
}
}
}
}
});
function reconstructPath(info, startState, callArgs) {
const { stateVarName, cases } = info;
let currentState = startState;
const nodes = [];
const visited = new Set();
let iterations = 0;
const MAX_ITERATIONS = 5000;
while (iterations < MAX_ITERATIONS) {
if (!cases.has(currentState)) break;
if (visited.has(currentState)) break;
visited.add(currentState);
const casePath = cases.get(currentState);
const caseBody = casePath.node.consequent;
let nextState = null;
let hasReturn = false;
for (const stmt of caseBody) {
if (t.isBreakStatement(stmt)) continue;
if (t.isExpressionStatement(stmt) && t.isAssignmentExpression(stmt.expression)) {
const left = stmt.expression.left;
if (t.isIdentifier(left) && left.name === stateVarName) {
try {
const operator = stmt.expression.operator;
const rightCode = generator(stmt.expression.right).code;
const rightVal = evaluateInVM(rightCode);
if (rightVal !== undefined) {
if (operator === '=') nextState = rightVal;
else if (operator === '+=') nextState = currentState + rightVal;
else if (operator === '-=') nextState = currentState - rightVal;
} else {
nextState = null;
}
} catch (e) {}
continue;
}
}
if (t.isReturnStatement(stmt)) hasReturn = true;
nodes.push(t.cloneNode(stmt));
}
if (hasReturn) break;
if (nextState === null) break;
currentState = nextState;
iterations++;
}
return nodes;
}
console.log(`Unflattened ${unflattenedCount} calls.`);
// ---------------------------------------------------------
// Step 5: Function Inlining
// ---------------------------------------------------------
console.log("Starting function inlining...");
let inlinedCount = 0;
const inlineableFunctions = new Map();
traverse(ast, {
VariableDeclarator(path) {
if (t.isIdentifier(path.node.id) && t.isFunctionExpression(path.node.init)) {
if (isInlineable(path.node.init)) inlineableFunctions.set(path.node.id.name, path.node.init);
}
},
FunctionDeclaration(path) {
if (t.isIdentifier(path.node.id)) {
if (isInlineable(path.node)) inlineableFunctions.set(path.node.id.name, path.node);
}
}
});
function isInlineable(node) {
const body = node.body.body;
if (body.length !== 1) return false;
return t.isReturnStatement(body[0]);
}
traverse(ast, {
CallExpression(path) {
const callee = path.node.callee;
if (t.isIdentifier(callee) && inlineableFunctions.has(callee.name)) {
const funcNode = inlineableFunctions.get(callee.name);
const returnArg = funcNode.body.body[0].argument;
const params = funcNode.params;
const args = path.node.arguments;
if (args.length >= params.length) {
const substitutions = new Map();
params.forEach((param, index) => {
if (t.isIdentifier(param)) substitutions.set(param.name, args[index]);
});
const safeReplace = (node) => {
if (!node) return node;
if (t.isIdentifier(node) && substitutions.has(node.name)) return t.cloneNode(substitutions.get(node.name));
if (t.isBinaryExpression(node) || t.isLogicalExpression(node)) {
node.left = safeReplace(node.left);
node.right = safeReplace(node.right);
return node;
}
if (t.isUnaryExpression(node)) {
node.argument = safeReplace(node.argument);
return node;
}
if (t.isCallExpression(node)) {
node.callee = safeReplace(node.callee);
node.arguments = node.arguments.map(safeReplace);
return node;
}
if (t.isMemberExpression(node)) {
node.object = safeReplace(node.object);
if (node.computed) node.property = safeReplace(node.property);
return node;
}
return node;
};
path.replaceWith(safeReplace(t.cloneNode(returnArg)));
inlinedCount++;
}
}
}
});
console.log(`Inlined ${inlinedCount} function calls.`);
// ---------------------------------------------------------
// Step 6: Constant Folding
// ---------------------------------------------------------
console.log("Starting constant folding...");
let foldedCount = 0;
traverse(ast, {
BinaryExpression(path) {
if (isLiteral(path.node.left) && isLiteral(path.node.right)) {
try {
const val = evaluateInVM(generator(path.node).code);
if (val !== undefined && (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean')) {
path.replaceWith(t.valueToNode(val));
foldedCount++;
}
} catch(e) {}
}
},
ConditionalExpression(path) {
if (isLiteral(path.node.test)) {
if (path.node.test.value) path.replaceWith(path.node.consequent);
else path.replaceWith(path.node.alternate);
foldedCount++;
}
}
});
console.log(`Folded ${foldedCount} expressions.`);
// ---------------------------------------------------------
// Step 7: Dead Code Elimination
// ---------------------------------------------------------
console.log("Starting dead code elimination...");
let deadCount = 0;
traverse(ast, {
Program(path) { path.scope.crawl(); }
});
traverse(ast, {
VariableDeclarator(path) {
const binding = path.scope.getBinding(path.node.id.name);
if (binding && !binding.referenced && binding.constant) {
if (isPure(path.node.init)) {
path.remove();
deadCount++;
}
}
}
});
function isPure(node) {
if (!node) return true;
if (isLiteral(node) || t.isIdentifier(node) || t.isFunction(node)) return true;
return false;
}
console.log(`Removed ${deadCount} unused variables.`);
// Output
const outputOpts = { compact: false, comments: true };
const output = generator(ast, outputOpts).code;
fs.writeFileSync(path.join(__dirname, 'example-1-deobfuscated.js'), output);
console.log("Saved to example-1-deobfuscated.js");
}
main().catch(err => console.error(err));
what next ?
最新发布