UNPKG

js-confuser

Version:

JavaScript Obfuscation Tool.

187 lines (159 loc) 5.85 kB
import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "../plugin"; import { Order } from "../../order"; import * as t from "@babel/types"; import { getMemberExpressionPropertyAsString, getObjectPropertyAsString, getParentFunctionOrProgram, } from "../../utils/ast-utils"; export default ({ Plugin }: PluginArg): PluginObject => { const me = Plugin(Order.ObjectExtraction, { changeData: { objects: 0, }, }); return { visitor: { Program: { enter(path) { path.scope.crawl(); }, }, VariableDeclaration(varDecPath) { if (varDecPath.node.declarations.length !== 1) return; const declaration = varDecPath.get( "declarations.0" ) as NodePath<t.VariableDeclarator>; // Must be simple variable declaration (No destructuring) const identifier = declaration.get("id"); if (!identifier.isIdentifier()) return; // Must be an object expression const objectExpression = declaration.get("init"); if (!objectExpression.isObjectExpression()) return; // Not allowed to reassign the object const binding = varDecPath.scope.getBinding(identifier.node.name); if (!binding || binding.constantViolations.length > 0) return; var pendingReplacements: { path: NodePath<t.MemberExpression>; replaceWith: t.Expression; }[] = []; const newObjectName = me.getPlaceholder() + "_" + identifier.node.name; const newPropertyMappings = new Map<string, string>(); // Create new property names from the original object properties var newDeclarations: t.VariableDeclarator[] = []; for (var property of objectExpression.get("properties")) { if (!property.isObjectProperty()) return; const propertyKey = getObjectPropertyAsString(property.node); if (!propertyKey) { // Property key is not a static string, not allowed return; } let newPropertyName = newPropertyMappings.get(propertyKey); if (newPropertyName) { // Duplicate property, not allowed return; } else { newPropertyName = newObjectName + "_" + (t.isValidIdentifier(propertyKey) ? propertyKey : me.getPlaceholder()); newPropertyMappings.set(propertyKey, newPropertyName); } // Check function for referencing 'this' const value = property.get("value"); if (value.isFunction()) { var referencesThis = false; value.traverse({ ThisExpression(thisPath) { referencesThis = true; }, }); if (referencesThis) { // Function references 'this', not allowed // When extracted, this will not refer to the original object return; } } newDeclarations.push( t.variableDeclarator( t.identifier(newPropertyName), value.node as t.Expression ) ); } var isObjectSafe = true; getParentFunctionOrProgram(varDecPath).traverse({ Identifier: { exit(idPath) { if (idPath.node.name !== identifier.node.name) return; if (idPath === identifier) return; // Skip the original declaration const memberExpression = idPath.parentPath; if (!memberExpression || !memberExpression.isMemberExpression()) { isObjectSafe = false; return; } const property = getMemberExpressionPropertyAsString( memberExpression.node ); if (!property) { isObjectSafe = false; return; } // Delete expression check if ( memberExpression.parentPath.isUnaryExpression({ operator: "delete", }) ) { // Deleting object properties is not allowed isObjectSafe = false; return; } let newPropertyName = newPropertyMappings.get(property); if (!newPropertyName) { // Property added later on, not allowed isObjectSafe = false; return; } const extractedIdentifier = t.identifier(newPropertyName); pendingReplacements.push({ path: memberExpression, replaceWith: extractedIdentifier, }); }, }, }); // Object references are too complex to safely extract if (!isObjectSafe) return; if ( !me.computeProbabilityMap( me.options.objectExtraction, identifier.node.name ) ) return; const newDeclarationKind = varDecPath.node.kind === "const" ? "let" : varDecPath.node.kind; varDecPath .replaceWithMultiple( newDeclarations.map((declaration) => t.variableDeclaration(newDeclarationKind, [declaration]) ) ) .forEach((path) => { // Make sure to register the new declarations path.scope.registerDeclaration(path); }); // Replace all references to new singular identifiers for (const { path, replaceWith } of pendingReplacements) { path.replaceWith(replaceWith); } me.log("Extracted object", identifier.node.name); me.changeData.objects++; }, }, }; };