UNPKG

js-confuser

Version:

JavaScript Obfuscation Tool.

176 lines (152 loc) 5.58 kB
import * as t from "@babel/types"; import { NodePath } from "@babel/traverse"; import { PluginArg, PluginObject } from "./plugin"; import { Order } from "../order"; import { NameGen } from "../utils/NameGen"; import { ok } from "assert"; const LABEL = Symbol("label"); interface LabelInterface { label?: string; renamed?: string; removed: boolean; required: boolean; paths: NodePath<t.BreakStatement | t.ContinueStatement>[]; } interface NodeLabel { [LABEL]?: LabelInterface; } export default function ({ Plugin }: PluginArg): PluginObject { const me = Plugin(Order.RenameLabels, { changeData: { labelsRenamed: 0, labelsRemoved: 0, }, }); return { visitor: { Program(path) { const allLabelInterfaces: LabelInterface[] = []; // First pass: Collect all label usages path.traverse({ LabeledStatement(labelPath) { const labelInterface = { label: labelPath.node.label.name, removed: false, required: false, paths: [], }; allLabelInterfaces.push(labelInterface); (labelPath.node as NodeLabel)[LABEL] = labelInterface; }, "BreakStatement|ContinueStatement"(_path) { const path = _path as NodePath< t.BreakStatement | t.ContinueStatement >; if (path.node.label) { const labelName = path.node.label.name; let targets: NodePath< t.For | t.While | t.BlockStatement | t.SwitchStatement >[] = []; let onlySearchLoops = path.isContinueStatement(); let currentPath: NodePath = path; while (currentPath) { if ( currentPath.isFor() || currentPath.isWhile() || currentPath.isSwitchStatement() ) { targets.push(currentPath); } if ( currentPath.isBlockStatement() && currentPath.parentPath.isLabeledStatement() ) { targets.push(currentPath); } currentPath = currentPath.parentPath; } const target = targets.find( (label) => label.parentPath && label.parentPath.isLabeledStatement() && label.parentPath.node.label.name === labelName ); if (onlySearchLoops) { // Remove BlockStatements and SwitchStatements from the list of targets // a continue statement only target loops // This helps remove unnecessary labels when a continue is nested with a block statement // ex: for-loop with if-statement continue targets = targets.filter( (target) => !target.isBlockStatement() && !target.isSwitchStatement() ); } ok(target); const isRequired = target.isBlockStatement() || targets[0] !== target; const labelInterface = (target.parentPath.node as NodeLabel)[ LABEL ]; if (isRequired) { labelInterface.required = true; } else { // Label is not required here, remove it for this particular break/continue statement path.node.label = null; } if (!labelInterface.paths) { labelInterface.paths = []; } labelInterface.paths.push(path); } }, }); const nameGen = new NameGen(me.options.identifierGenerator); for (var labelInterface of allLabelInterfaces) { const isRequired = labelInterface.required; if (isRequired) { var newName = labelInterface.label; if ( me.computeProbabilityMap( me.options.renameLabels, labelInterface.label ) ) { newName = nameGen.generate(); } labelInterface.renamed = newName; me.changeData.labelsRenamed++; } else { labelInterface.removed = true; me.changeData.labelsRemoved++; } } // Second pass: Rename labels and remove unused ones path.traverse({ LabeledStatement(labelPath) { const labelInterface = (labelPath.node as NodeLabel)[LABEL]; if (labelInterface) { // Remove label but replace it with its body if (labelInterface.removed) { labelPath.replaceWith(labelPath.node.body); } // Else keep the label but rename it if (typeof labelInterface.renamed === "string") { labelPath.node.label.name = labelInterface.renamed; } // Update all break/continue statements for (var breakPath of labelInterface.paths) { // Remove label from break/continue statement if (labelInterface.removed) { breakPath.node.label = null; } else { // Update label name breakPath.node.label = t.identifier(labelInterface.renamed); } } } }, }); }, }, }; }