js-confuser
Version:
JavaScript Obfuscation Tool.
202 lines (175 loc) • 6.09 kB
text/typescript
import * as t from "@babel/types";
import Obfuscator from "../obfuscator";
import Template from "../templates/template";
import {
isDefiningIdentifier,
isModifiedIdentifier,
isVariableIdentifier,
} from "../utils/ast-utils";
import {
GEN_NODE,
NodeSymbol,
reservedIdentifiers,
variableFunctionName,
WITH_STATEMENT,
} from "../constants";
import { PluginArg, PluginObject } from "./plugin";
import { Order } from "../order";
export default function pack({ Plugin }: PluginArg): PluginObject {
const me = Plugin(Order.Pack, {
changeData: {
globals: 0,
},
});
const objectName = me.obfuscator.nameGen.generate();
const mappings = new Map<string, string>();
const setterPropsNeeded = new Set<string>();
const typeofMappings = new Map<string, string>();
const prependNodes: t.Statement[] = [];
return {
// Transform identifiers, preserve import statements
visitor: {
ImportDeclaration(path) {
prependNodes.push(path.node);
path.remove();
// Ensure bindings are removed -> variable becomes a global -> added to mappings object
path.scope.crawl();
},
Program(path) {
path.scope.crawl();
},
Identifier: {
exit(path) {
if (!isVariableIdentifier(path)) return;
if (isDefiningIdentifier(path)) return;
if ((path.node as NodeSymbol)[GEN_NODE]) return;
if ((path.node as NodeSymbol)[WITH_STATEMENT]) return;
const identifierName = path.node.name;
if (reservedIdentifiers.has(identifierName)) return;
if (me.obfuscator.options.globalVariables.has(identifierName)) return;
if (identifierName === variableFunctionName) return;
if (identifierName === objectName) return;
if (!path.scope.hasGlobal(identifierName)) return;
if (path.scope.hasBinding(identifierName)) return;
// Check user's custom implementation
if (!me.computeProbabilityMap(me.options.pack, identifierName))
return;
if (
path.key === "argument" &&
path.parentPath.isUnaryExpression({ operator: "typeof" })
) {
const unaryExpression = path.parentPath;
let propertyName = typeofMappings.get(identifierName);
if (!propertyName) {
propertyName = me.obfuscator.nameGen.generate();
typeofMappings.set(identifierName, propertyName);
}
unaryExpression.replaceWith(
t.memberExpression(
t.identifier(objectName),
t.stringLiteral(propertyName),
true
)
);
return;
}
let propertyName = mappings.get(identifierName);
if (!propertyName) {
propertyName = me.obfuscator.nameGen.generate();
mappings.set(identifierName, propertyName);
}
// Only add setter if the identifier is modified
if (isModifiedIdentifier(path)) {
setterPropsNeeded.add(identifierName);
}
path.replaceWith(
t.memberExpression(
t.identifier(objectName),
t.stringLiteral(propertyName),
true
)
);
},
},
},
// Final AST handler
// Very last step in the obfuscation process
finalASTHandler(ast) {
// Create object expression
// Very similar to flatten, maybe refactor to use the same code
const objectProperties: t.ObjectMethod[] = [];
me.changeData.globals = mappings.size;
for (const [identifierName, propertyName] of mappings) {
// get identifier() { return identifier; }
objectProperties.push(
t.objectMethod(
"get",
t.stringLiteral(propertyName),
[],
t.blockStatement([t.returnStatement(t.identifier(identifierName))])
)
);
// Only add setter if the identifier is modified
if (setterPropsNeeded.has(identifierName)) {
// set identifier(value) { return identifier = value; }
objectProperties.push(
t.objectMethod(
"set",
t.stringLiteral(propertyName),
[t.identifier(objectName)],
t.blockStatement([
t.returnStatement(
t.assignmentExpression(
"=",
t.identifier(identifierName),
t.identifier(objectName)
)
),
])
)
);
}
}
// Add typeof mappings
for (const [identifierName, propertyName] of typeofMappings) {
// get typeof identifier() { return typeof identifier; }
objectProperties.push(
t.objectMethod(
"get",
t.stringLiteral(propertyName),
[],
t.blockStatement([
t.returnStatement(
t.unaryExpression("typeof", t.identifier(identifierName))
),
])
)
);
}
const objectExpression = t.objectExpression(objectProperties);
// Convert last expression to return statement
// This preserves the last expression in the packed code
var lastStatement = ast.program.body.at(-1);
if (lastStatement && t.isExpressionStatement(lastStatement)) {
Object.assign(
lastStatement,
t.returnStatement(lastStatement.expression)
);
}
const outputCode = Obfuscator.generateCode(ast, {
...me.obfuscator.options,
compact: true,
});
var newAST = new Template(`
{prependNodes}
Function({objectName}, {outputCode})({objectExpression});
`).file({
objectName: () => t.stringLiteral(objectName),
outputCode: () => t.stringLiteral(outputCode),
objectExpression: objectExpression,
prependNodes: prependNodes,
});
return newAST;
},
};
}