UNPKG

js-confuser

Version:

JavaScript Obfuscation Tool.

955 lines (902 loc) 64.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _traverse = _interopRequireWildcard(require("@babel/traverse")); var _order = require("../order"); var _astUtils = require("../utils/ast-utils"); var t = _interopRequireWildcard(require("@babel/types")); var _node = require("../utils/node"); var _template = _interopRequireDefault(require("../templates/template")); var _randomUtils = require("../utils/random-utils"); var _IntGen = require("../utils/IntGen"); var _assert = require("assert"); var _NameGen = require("../utils/NameGen"); var _constants = require("../constants"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } // Function deemed unsafe for CFF var CFF_UNSAFE = Symbol("CFF_UNSAFE"); /** * Breaks functions into DAGs (Directed Acyclic Graphs) * * - 1. Break functions into chunks * - 2. Shuffle chunks but remember their original position * - 3. Create a Switch statement inside a While loop, each case is a chunk, and the while loops exits on the last transition. * * The Switch statement: * * - 1. The state variable controls which case will run next * - 2. At the end of each case, the state variable is updated to the next block of code. * - 3. The while loop continues until the the state variable is the end state. */ var _default = exports["default"] = function _default(_ref) { var Plugin = _ref.Plugin; var me = Plugin(_order.Order.ControlFlowFlattening, { changeData: { functions: 0, blocks: 0, ifStatements: 0, deadCode: 0, variables: 0 } }); // in Debug mode, the output is much easier to read var isDebug = false; var flattenIfStatements = true; // Converts IF-statements into equivalent 'goto style of code' var flattenFunctionDeclarations = true; // Converts Function Declarations into equivalent 'goto style of code' var addRelativeAssignments = true; // state += (NEW_STATE - CURRENT_STATE) var addDeadCode = true; // add fakes chunks of code var addFakeTests = true; // case 100: case 490: case 510: ... var addComplexTests = true; // case s != 49 && s - 10: var addPredicateTests = true; // case scope.A + 10: ... var mangleNumericalLiterals = true; // 50 => state + X var mangleBooleanLiterals = true; // true => state == X var addWithStatement = true; // Disabling not supported yet var addGeneratorFunction = true; // Wrap in generator function? var cffPrefix = me.getPlaceholder(); // Amount of blocks changed by Control Flow Flattening var cffCounter = 0; var functionsModified = []; function flagFunctionToAvoid(path, reason) { var fnOrProgram = (0, _astUtils.getParentFunctionOrProgram)(path); fnOrProgram.node[CFF_UNSAFE] = reason; } return { post: function post() { for (var _i = 0, _functionsModified = functionsModified; _i < _functionsModified.length; _i++) { var node = _functionsModified[_i]; node[_constants.UNSAFE] = true; } }, visitor: { // Unsafe detection ThisExpression: function ThisExpression(path) { flagFunctionToAvoid(path, "this"); }, VariableDeclaration: function VariableDeclaration(path) { if (path.node.declarations.length !== 1) { path.getAncestry().forEach(function (p) { p.node[CFF_UNSAFE] = "multipleDeclarations"; }); } }, Identifier: function Identifier(path) { if (path.node.name === _constants.variableFunctionName || path.node.name === "arguments") { flagFunctionToAvoid(path, "arguments"); } }, "Super|MetaProperty|AwaitExpression|YieldExpression": function SuperMetaPropertyAwaitExpressionYieldExpression(path) { flagFunctionToAvoid(path, "functionSpecific"); }, // Main CFF transformation "Program|Function": { exit: function exit(_path) { var programOrFunctionPath = _path; // Exclude loops if (programOrFunctionPath.find(function (p) { return p.isForStatement() || p.isWhile(); })) return; // Exclude 'CFF_UNSAFE' functions if (programOrFunctionPath.node[CFF_UNSAFE]) return; var programPath = _path.isProgram() ? _path : null; var functionPath = _path.isFunction() ? _path : null; var blockPath; if (programPath) { blockPath = programPath; } else { var fnBlockPath = functionPath.get("body"); if (!fnBlockPath.isBlock()) return; blockPath = fnBlockPath; } // Don't apply to strict mode blocks var strictModeEnforcingBlock = programOrFunctionPath.find(function (path) { return (0, _astUtils.isStrictMode)(path); }); if (strictModeEnforcingBlock) return; // Must be at least 3 statements or more if (blockPath.node.body.length < 3) return; // Check user's threshold setting if (!me.computeProbabilityMap(me.options.controlFlowFlattening)) { return; } if (functionPath) { // Avoid unsafe functions if (functionPath.node[_constants.UNSAFE]) return; if (functionPath.node.async || functionPath.node.generator) return; } programOrFunctionPath.scope.crawl(); var hasIllegalNode = false; var bindingNames = new Set(); blockPath.traverse({ Identifier: function Identifier(path) { if (!path.isBindingIdentifier()) return; var binding = path.scope.getBinding(path.node.name); if (!binding) return; var fnParent = path.getFunctionParent(); if (path.key === "id" && path.parentPath.isFunctionDeclaration()) { fnParent = path.parentPath.getFunctionParent(); } if (fnParent !== functionPath) return; if (!(0, _astUtils.isDefiningIdentifier)(path)) { return; } if (bindingNames.has(path.node.name)) { hasIllegalNode = true; path.stop(); return; } bindingNames.add(path.node.name); } }); if (hasIllegalNode) { return; } me.changeData.blocks++; // Limit how many numbers get entangled var mangledLiteralsCreated = 0; var cffIndex = ++cffCounter; // Start from 1 var prefix = cffPrefix + "_" + cffIndex; var withIdentifier = function withIdentifier(suffix) { var name; if (isDebug) { name = prefix + "_" + suffix; } else { name = me.obfuscator.nameGen.generate(false); } var id = t.identifier(name); id[_constants.NO_RENAME] = cffIndex; return id; }; var mainFnName = withIdentifier("main"); var scopeVar = withIdentifier("scope"); var stateVars = new Array(isDebug ? 1 : (0, _randomUtils.getRandomInteger)(2, 5)).fill("").map(function (_, i) { return withIdentifier("state_".concat(i)); }); var argVar = withIdentifier("_arg"); var usedArgVar = false; var _didReturnVar = withIdentifier("return"); var basicBlocks = new Map(); // Map labels to states var stateIntGen = new _IntGen.IntGen(); var defaultBlockPath = blockPath; var scopeCounter = 0; var scopeNameGen = new _NameGen.NameGen(me.options.identifierGenerator); if (!isDebug) { scopeNameGen = me.obfuscator.nameGen; } // Create 'with' object - Determines which scope gets top-level variable access var withProperty = isDebug ? "with" : scopeNameGen.generate(false); var withMemberExpression = new _template["default"]("".concat(scopeVar.name, "[\"").concat(withProperty, "\"]")).expression(); withMemberExpression.object[_constants.NO_RENAME] = cffIndex; var ScopeManager = /*#__PURE__*/function () { function ScopeManager(scope, initializingBasicBlock) { _classCallCheck(this, ScopeManager); _defineProperty(this, "isNotUsed", true); _defineProperty(this, "requiresInitializing", true); _defineProperty(this, "nameMap", new Map()); _defineProperty(this, "nameGen", addWithStatement ? me.obfuscator.nameGen : new _NameGen.NameGen(me.options.identifierGenerator)); this.scope = scope; this.initializingBasicBlock = initializingBasicBlock; this.propertyName = isDebug ? "_" + cffIndex + "_" + scopeCounter++ : scopeNameGen.generate(); } return _createClass(ScopeManager, [{ key: "findBestWithDiscriminant", value: function findBestWithDiscriminant(basicBlock) { var _this$parent; // This initializing block is forbidden to have a with discriminant // (As no previous code is able to prepare the with discriminant) if (basicBlock !== this.initializingBasicBlock) { // If no variables were defined in this scope, don't use it if (Object.keys(this.scope.bindings).length > 0) return this; } return (_this$parent = this.parent) === null || _this$parent === void 0 ? void 0 : _this$parent.findBestWithDiscriminant(basicBlock); } }, { key: "getNewName", value: function getNewName(name, originalNode) { if (!this.nameMap.has(name)) { var newName = this.nameGen.generate(false); if (isDebug) { newName = "_" + name; } // console.log(name, newName); this.nameMap.set(name, newName); me.changeData.variables++; // console.log( // "Renaming " + // name + // " to " + // newName + // " : " + // this.scope.path.type // ); return newName; } return this.nameMap.get(name); } }, { key: "getScopeObject", value: function getScopeObject() { return t.memberExpression((0, _node.deepClone)(scopeVar), t.stringLiteral(this.propertyName), true); } }, { key: "getInitializingStatement", value: function getInitializingStatement() { return t.expressionStatement(t.assignmentExpression("=", this.getScopeObject(), this.getInitializingObjectExpression())); } }, { key: "getInitializingObjectExpression", value: function getInitializingObjectExpression() { return isDebug ? new _template["default"]("\n ({\n identity: \"".concat(this.propertyName, "\"\n })\n ")).expression() : new _template["default"]("({})").expression(); } }, { key: "getMemberExpression", value: function getMemberExpression(name) { var object = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getScopeObject(); var memberExpression = t.memberExpression(object, t.stringLiteral(name), true); return memberExpression; } }, { key: "parent", get: function get() { return scopeToScopeManager.get(this.scope.parent); } }, { key: "getObjectExpression", value: function getObjectExpression(refreshLabel) { var refreshScope = basicBlocks.get(refreshLabel).scopeManager; var propertyMap = {}; var cursor = this.scope; while (cursor) { var parentScopeManager = scopeToScopeManager.get(cursor); if (parentScopeManager) { propertyMap[parentScopeManager.propertyName] = t.memberExpression((0, _node.deepClone)(scopeVar), t.stringLiteral(parentScopeManager.propertyName), true); } cursor = cursor.parent; } propertyMap[refreshScope.propertyName] = refreshScope.getInitializingObjectExpression(); var properties = []; for (var key in propertyMap) { properties.push(t.objectProperty(t.stringLiteral(key), propertyMap[key], true)); } return t.objectExpression(properties); } }, { key: "hasOwnName", value: function hasOwnName(name) { return this.nameMap.has(name); } }]); }(); var getImpossibleBasicBlocks = function getImpossibleBasicBlocks() { return Array.from(basicBlocks.values()).filter(function (block) { return block.options.impossible; }); }; var scopeToScopeManager = new Map(); /** * A Basic Block is a sequence of instructions with no diversion except at the entry and exit points. */ var BasicBlock = /*#__PURE__*/function () { function BasicBlock(label, parentPath) { var _this = this; var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; _classCallCheck(this, BasicBlock); _defineProperty(this, "allowWithDiscriminant", true); this.label = label; this.parentPath = parentPath; this.options = options; this.createPath(); if (isDebug) { // States in debug mode are just 1, 2, 3, ... this.totalState = basicBlocks.size + 1; } else { this.totalState = stateIntGen.generate(); } // Correct state values // Start with random numbers this.stateValues = stateVars.map(function () { return (0, _randomUtils.getRandomInteger)(-250, 250); }); // Try to re-use old state values to make diffs smaller if (basicBlocks.size > 1) { var lastBlock = _toConsumableArray(basicBlocks.values()).at(-1); this.stateValues = lastBlock.stateValues.map(function (oldValue, i) { return (0, _randomUtils.choice)([oldValue, _this.stateValues[i]]); }); } // Correct one of the values so that the accumulated sum is equal to the state var correctIndex = (0, _randomUtils.getRandomInteger)(0, this.stateValues.length); var getCurrentState = function getCurrentState() { return _this.stateValues.reduce(function (a, b) { return a + b; }, 0); }; // Correct the value this.stateValues[correctIndex] = this.totalState - (getCurrentState() - this.stateValues[correctIndex]); (0, _assert.ok)(getCurrentState() === this.totalState); // Store basic block basicBlocks.set(label, this); // Create a new scope manager if it doesn't exist if (!scopeToScopeManager.has(this.scope)) { scopeToScopeManager.set(this.scope, new ScopeManager(this.scope, this)); } this.initializedScope = this.scopeManager; } return _createClass(BasicBlock, [{ key: "withDiscriminant", get: function get() { if (!this.allowWithDiscriminant) return; return this.bestWithDiscriminant; } }, { key: "createPath", value: function createPath() { var newPath = _traverse.NodePath.get({ hub: this.parentPath.hub, parentPath: this.parentPath, parent: this.parentPath.node, container: this.parentPath.node.body, listKey: "body", // Set the correct list key key: "virtual" // Set the index of the new node }); newPath.scope = this.parentPath.scope; newPath.parentPath = this.parentPath; newPath.node = t.blockStatement([]); this.thisPath = newPath; this.thisNode = newPath.node; } }, { key: "insertAfter", value: function insertAfter(newNode) { this.body.push(newNode); } }, { key: "scope", get: function get() { return this.parentPath.scope; } }, { key: "scopeManager", get: function get() { return scopeToScopeManager.get(this.scope); } }, { key: "body", get: function get() { return this.thisPath.node.body; } }, { key: "createFalsePredicate", value: function createFalsePredicate() { var predicate = this.createPredicate(); if (predicate.value) { // Make predicate false return t.unaryExpression("!", predicate.node); } return predicate.node; } }, { key: "createTruePredicate", value: function createTruePredicate() { var predicate = this.createPredicate(); if (!predicate.value) { // Make predicate true return t.unaryExpression("!", predicate.node); } return predicate.node; } }, { key: "createPredicate", value: function createPredicate() { var stateVarIndex = (0, _randomUtils.getRandomInteger)(0, stateVars.length); var stateValue = this.stateValues[stateVarIndex]; var compareValue = (0, _randomUtils.choice)([stateValue, (0, _randomUtils.getRandomInteger)(-250, 250)]); var operator = (0, _randomUtils.choice)(["==", "!=", "<", ">"]); var compareResult; switch (operator) { case "==": compareResult = stateValue === compareValue; break; case "!=": compareResult = stateValue !== compareValue; break; case "<": compareResult = stateValue < compareValue; break; case ">": compareResult = stateValue > compareValue; break; } return { node: t.binaryExpression(operator, (0, _node.deepClone)(stateVars[stateVarIndex]), (0, _node.numericLiteral)(compareValue)), value: compareResult }; } }, { key: "identifier", value: function identifier(identifierName, scopeManager) { if (this.withDiscriminant && this.withDiscriminant === scopeManager) { var id = t.identifier(identifierName); id[_constants.NO_RENAME] = cffIndex; id[_constants.WITH_STATEMENT] = true; return id; } return scopeManager.getMemberExpression(identifierName); } }]); }(); /** * Stage 1: Flatten the code into Basic Blocks * * This involves transforming the Control Flow / Scopes into blocks with 'goto' statements * * - A block is simply a sequence of statements * - A block can have a 'goto' statement to another block * - A block original scope is preserved * * 'goto' & Scopes are transformed in Stage 2 */ var switchLabel = me.getPlaceholder(); var breakStatement = function breakStatement() { return t.breakStatement(t.identifier(switchLabel)); }; var startLabel = me.getPlaceholder(); var endLabel = me.getPlaceholder(); var currentBasicBlock = new BasicBlock(startLabel, blockPath); currentBasicBlock.allowWithDiscriminant = false; var gotoFunctionName = "GOTO__" + me.getPlaceholder() + "__IF_YOU_CAN_READ_THIS_THERE_IS_A_BUG"; function GotoControlStatement(label) { return new _template["default"]("\n ".concat(gotoFunctionName, "(\"").concat(label, "\");\n ")).single(); } // Ends the current block and starts a new one function endCurrentBasicBlock() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref2$jumpToNext = _ref2.jumpToNext, jumpToNext = _ref2$jumpToNext === void 0 ? true : _ref2$jumpToNext, _ref2$nextLabel = _ref2.nextLabel, nextLabel = _ref2$nextLabel === void 0 ? me.getPlaceholder() : _ref2$nextLabel, _ref2$prevJumpTo = _ref2.prevJumpTo, prevJumpTo = _ref2$prevJumpTo === void 0 ? null : _ref2$prevJumpTo, _ref2$nextBlockPath = _ref2.nextBlockPath, nextBlockPath = _ref2$nextBlockPath === void 0 ? null : _ref2$nextBlockPath; (0, _assert.ok)(nextBlockPath); if (prevJumpTo) { currentBasicBlock.insertAfter(GotoControlStatement(prevJumpTo)); } else if (jumpToNext) { currentBasicBlock.insertAfter(GotoControlStatement(nextLabel)); } currentBasicBlock = new BasicBlock(nextLabel, nextBlockPath); } var prependNodes = []; var functionExpressions = []; function flattenIntoBasicBlocks(bodyIn) { // if (!Array.isArray(bodyIn) && bodyIn.isBlock()) { // currentBasicBlock.parentPath = bodyIn; // } var body = Array.isArray(bodyIn) ? bodyIn : bodyIn.get("body"); var nextBlockPath = Array.isArray(bodyIn) ? currentBasicBlock.parentPath : bodyIn; var _loop = function _loop() { var statement = body[index]; // Keep Imports before everything else if (statement.isImportDeclaration()) { prependNodes.push(statement.node); return 0; // continue } if (statement.isFunctionDeclaration()) { var fnName = statement.node.id.name; var isIllegal = false; if (!flattenFunctionDeclarations || statement.node.async || statement.node.generator || statement.node[_constants.UNSAFE] || statement.node[CFF_UNSAFE] || (0, _astUtils.isStrictMode)(statement)) { isIllegal = true; } var oldBasicBlock = currentBasicBlock; var _fnLabel = me.getPlaceholder(); var sm = currentBasicBlock.scopeManager; var rename = sm.getNewName(fnName); sm.scope.bindings[fnName].kind = "var"; var hoistedBasicBlock = Array.from(basicBlocks.values()).find(function (block) { return block.parentPath === currentBasicBlock.parentPath; }); if (isIllegal) { hoistedBasicBlock.body.unshift(statement.node); return 0; // continue } me.changeData.functions++; var functionExpression = t.functionExpression(null, [], t.blockStatement([])); functionExpressions.push([fnName, _fnLabel, currentBasicBlock, functionExpression]); // Change the function declaration to a variable declaration hoistedBasicBlock.body.unshift(t.variableDeclaration("var", [t.variableDeclarator(t.identifier(fnName), functionExpression)])); var blockStatement = statement.get("body"); endCurrentBasicBlock({ nextLabel: _fnLabel, nextBlockPath: blockStatement, jumpToNext: false }); var fnTopBlock = currentBasicBlock; // Implicit return blockStatement.node.body.push(t.returnStatement(t.identifier("undefined"))); flattenIntoBasicBlocks(blockStatement); scopeToScopeManager.get(statement.scope).requiresInitializing = false; basicBlocks.get(_fnLabel).allowWithDiscriminant = false; // Debug label if (isDebug) { fnTopBlock.body.unshift(t.expressionStatement(t.stringLiteral("Function " + statement.node.id.name + " -> Renamed to " + rename))); } // Unpack parameters from the parameter 'argVar' if (statement.node.params.length > 0) { usedArgVar = true; fnTopBlock.body.unshift(t.variableDeclaration("var", [t.variableDeclarator(t.arrayPattern(statement.node.params), (0, _node.deepClone)(argVar))])); // Change bindings from 'param' to 'var' statement.get("params").forEach(function (param) { var ids = param.getBindingIdentifierPaths(); // Loop over the record of binding identifiers for (var identifierName in ids) { var identifierPath = ids[identifierName]; if (identifierPath.getFunctionParent() === statement) { var binding = statement.scope.getBinding(identifierName); if (binding) { binding.kind = "var"; } } } }); } currentBasicBlock = oldBasicBlock; return 0; // continue } // Convert IF statements into Basic Blocks if (statement.isIfStatement() && flattenIfStatements) { var test = statement.get("test"); var consequent = statement.get("consequent"); var alternate = statement.get("alternate"); // Both consequent and alternate are blocks if (consequent.isBlockStatement() && (!alternate.node || alternate.isBlockStatement())) { me.changeData.ifStatements++; var consequentLabel = me.getPlaceholder(); var alternateLabel = alternate.node ? me.getPlaceholder() : null; var afterPath = me.getPlaceholder(); currentBasicBlock.insertAfter(t.ifStatement(test.node, GotoControlStatement(consequentLabel), alternateLabel ? GotoControlStatement(alternateLabel) : GotoControlStatement(afterPath))); var _oldBasicBlock = currentBasicBlock; endCurrentBasicBlock({ jumpToNext: false, nextLabel: consequentLabel, nextBlockPath: consequent }); flattenIntoBasicBlocks(consequent); currentBasicBlock.initializedScope = _oldBasicBlock.scopeManager; if (alternate.isBlockStatement()) { endCurrentBasicBlock({ prevJumpTo: afterPath, nextLabel: alternateLabel, nextBlockPath: alternate }); flattenIntoBasicBlocks(alternate); } endCurrentBasicBlock({ prevJumpTo: afterPath, nextLabel: afterPath, nextBlockPath: _oldBasicBlock.parentPath }); return 0; // continue } } if (Number(index) === body.length - 1 && statement.isExpressionStatement() && statement.findParent(function (p) { return p.isBlock(); }) === blockPath) { // Return the result of the last expression for eval() purposes currentBasicBlock.insertAfter(t.returnStatement(statement.get("expression").node)); return 0; // continue } // 3 or more statements should be split more if (currentBasicBlock.body.length > 1 && (0, _randomUtils.chance)(50 + currentBasicBlock.body.length)) { endCurrentBasicBlock({ nextBlockPath: nextBlockPath }); } // console.log(currentBasicBlock.thisPath.type); // console.log(currentBasicBlock.body); currentBasicBlock.body.push(statement.node); }, _ret; for (var index in body) { _ret = _loop(); if (_ret === 0) continue; } } // Convert our code into Basic Blocks flattenIntoBasicBlocks(blockPath.get("body")); // Ensure always jumped to the Program end endCurrentBasicBlock({ jumpToNext: true, nextLabel: endLabel, nextBlockPath: defaultBlockPath }); basicBlocks.get(endLabel).allowWithDiscriminant = false; if (!isDebug && addDeadCode) { // DEAD CODE 1/3: Add fake chunks that are never reached var fakeChunkCount = (0, _randomUtils.getRandomInteger)(1, 5); for (var i = 0; i < fakeChunkCount; i++) { // These chunks just jump somewhere random, they are never executed // so it could contain any code var fakeBlock = new BasicBlock(me.getPlaceholder(), blockPath, { impossible: true }); var fakeJump = void 0; do { fakeJump = (0, _randomUtils.choice)(Array.from(basicBlocks.keys())); } while (fakeJump === fakeBlock.label); fakeBlock.insertAfter(GotoControlStatement(fakeJump)); me.changeData.deadCode++; } // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators // "irreducible control flow" basicBlocks.forEach(function (basicBlock) { if ((0, _randomUtils.chance)(30 - basicBlocks.size)) { var randomLabel = (0, _randomUtils.choice)(Array.from(basicBlocks.keys())); // The `false` literal will be mangled basicBlock.insertAfter(new _template["default"]("\n if({predicate}){\n {goto}\n }\n ").single({ "goto": GotoControlStatement(randomLabel), predicate: basicBlock.createFalsePredicate() })); me.changeData.deadCode++; } }); // DEAD CODE 3/3: Clone chunks but these chunks are never ran var cloneChunkCount = (0, _randomUtils.getRandomInteger)(1, 5); var _loop2 = function _loop2() { var randomChunk = (0, _randomUtils.choice)(Array.from(basicBlocks.values())); // Don't double define functions var hasDeclaration = randomChunk.body.find(function (stmt) { return t.isDeclaration(stmt); }); if (!hasDeclaration) { var clonedChunk = new BasicBlock(me.getPlaceholder(), randomChunk.parentPath, { impossible: true }); randomChunk.thisNode.body.map(function (x) { return (0, _node.deepClone)(x); }).forEach(function (node) { if (node.type === "EmptyStatement") return; clonedChunk.insertAfter(node); }); me.changeData.deadCode++; } }; for (var _i2 = 0; _i2 < cloneChunkCount; _i2++) { _loop2(); } } // Select scope managers for the with statement var _iterator = _createForOfIteratorHelper(basicBlocks.values()), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var _basicBlock2$initiali; var _basicBlock2 = _step.value; _basicBlock2.bestWithDiscriminant = (_basicBlock2$initiali = _basicBlock2.initializedScope) === null || _basicBlock2$initiali === void 0 ? void 0 : _basicBlock2$initiali.findBestWithDiscriminant(_basicBlock2); if (isDebug && _basicBlock2.withDiscriminant) { _basicBlock2.body.unshift(t.expressionStatement(t.stringLiteral("With " + _basicBlock2.withDiscriminant.propertyName))); } } /** * Stage 2: Transform 'goto' statements into valid JavaScript * * - 'goto' is replaced with equivalent state updates and break statements * - Original identifiers are converted into member expressions */ // Remap 'GotoStatement' to actual state assignments and Break statements } catch (err) { _iterator.e(err); } finally { _iterator.f(); } var _iterator2 = _createForOfIteratorHelper(basicBlocks.values()), _step2; try { var _loop4 = function _loop4() { var basicBlock = _step2.value; var currentStateValues = basicBlock.stateValues; // Wrap the statement in a Babel path to allow traversal var outerFn = (0, _astUtils.getParentFunctionOrProgram)(basicBlock.parentPath); function isWithinSameFunction(path) { var fn = (0, _astUtils.getParentFunctionOrProgram)(path); return fn.node === outerFn.node; } var visitor = { BooleanLiteral: { exit: function exit(boolPath) { // Don't mangle booleans in debug mode if (isDebug || !mangleBooleanLiterals || me.isSkipped(boolPath)) return; if (!isWithinSameFunction(boolPath)) return; if ((0, _randomUtils.chance)(50 + mangledLiteralsCreated)) return; mangledLiteralsCreated++; var index = (0, _randomUtils.getRandomInteger)(0, stateVars.length - 1); var stateVar = stateVars[index]; var stateVarValue = currentStateValues[index]; var compareValue = (0, _randomUtils.choice)([(0, _randomUtils.getRandomInteger)(-250, 250), stateVarValue]); var compareResult = stateVarValue === compareValue; var newExpression = t.binaryExpression(boolPath.node.value === compareResult ? "==" : "!=", (0, _node.deepClone)(stateVar), (0, _node.numericLiteral)(compareValue)); (0, _astUtils.ensureComputedExpression)(boolPath); boolPath.replaceWith(newExpression); } }, // Mangle numbers with the state values NumericLiteral: { exit: function exit(numPath) { // Don't mangle numbers in debug mode if (isDebug || !mangleNumericalLiterals || me.isSkipped(numPath)) return; var num = numPath.node.value; if (Math.floor(num) !== num || Math.abs(num) > 100000 || !Number.isFinite(num) || Number.isNaN(num)) return; if (!isWithinSameFunction(numPath)) return; if ((0, _randomUtils.chance)(50 + mangledLiteralsCreated)) return; mangledLiteralsCreated++; var index = (0, _randomUtils.getRandomInteger)(0, stateVars.length - 1); var stateVar = stateVars[index]; // num = 50 // stateVar = 30 // stateVar + 30 var diff = t.binaryExpression("+", (0, _node.deepClone)(stateVar), me.skip((0, _node.numericLiteral)(num - currentStateValues[index]))); (0, _astUtils.ensureComputedExpression)(numPath); numPath.replaceWith(diff); numPath.skip(); } }, Identifier: { exit: function exit(path) { if (!(0, _astUtils.isVariableIdentifier)(path)) return; if (me.isSkipped(path)) return; if (path.node[_constants.NO_RENAME] === cffIndex) return; // For identifiers using implicit with discriminant, skip if (path.node[_constants.WITH_STATEMENT]) return; var identifierName = path.node.name; if (identifierName === gotoFunctionName) return; var binding = path.scope.getBinding(identifierName); if (!binding) { return; } if (binding.kind === "var" || binding.kind === "let" || binding.kind === "const") {} else { return; } var scopeManager = scopeToScopeManager.get(binding.scope); if (!scopeManager) return; var newName = scopeManager.getNewName(identifierName, path.node); var memberExpression = scopeManager.getMemberExpression(newName); scopeManager.isNotUsed = false; // Scope object as with discriminant? Use identifier if (typeof basicBlock.withDiscriminant === "undefined") { var id = t.identifier(scopeManager.propertyName); id[_constants.WITH_STATEMENT] = true; id[_constants.NO_RENAME] = cffIndex; memberExpression = scopeManager.getMemberExpression(newName, id); } if ((0, _astUtils.isDefiningIdentifier)(path)) { (0, _astUtils.replaceDefiningIdentifierToMemberExpression)(path, memberExpression); return; } if (!path.container) return; if (basicBlock.withDiscriminant && basicBlock.withDiscriminant === scopeManager && basicBlock.withDiscriminant.hasOwnName(identifierName)) { // The defining mode must directly append to the scope object // Subsequent uses can use the identifier var isDefiningNode = path.node === binding.identifier; if (!isDefiningNode) { memberExpression = basicBlock.identifier(newName, scopeManager); } } me.skip(memberExpression); path.replaceWith(memberExpression); path.skip(); // Preserve proper 'this' context when directly calling functions // X.Y.Z() -> (1, X.Y.Z)() if (path.parentPath.isCallExpression() && path.key === "callee") { path.replaceWith(t.sequenceExpression([t.numericLiteral(1), path.node])); } } }, // Top-level returns set additional flag to indicate that the function has returned ReturnStatement: { exit: function exit(path) { var functionParent = path.getFunctionParent(); if (!functionParent || functionParent.get("body") !== blockPath) return; var returnArgument = path.node.argument || t.identifier("undefined"); path.node.argument = new _template["default"]("\n ({didReturnVar} = true, {returnArgument})\n ").expression({ returnArgument: returnArgument, didReturnVar: (0, _node.deepClone)(_didReturnVar) }); } }, // goto() calls are replaced with state updates and break statements CallExpression: { exit: function exit(path) { if (t.isIdentifier(path.node.callee) && path.node.callee.name === gotoFunctionName) { var _path$node$arguments = _slicedToArray(path.node.arguments, 1), labelNode = _path$node$arguments[0]; (0, _assert.ok)(t.isStringLiteral(labelNode)); var _label = labelNode.value; var jumpBlock = basicBlocks.get(_label); (0, _assert.ok)(jumpBlock, "Label not found: " + _label); var newStateValues = jumpBlock.stateValues, newTotalState = jumpBlock.totalState; var assignments = []; if (jumpBlock.withDiscriminant) { assignments.push(t.assignmentExpression("=", (0, _node.deepClone)(withMemberExpression), jumpBlock.withDiscriminant.getScopeObject())); } else if (basicBlock.withDiscriminant) { // Reset the with discriminant to undefined using fake property // scope["fake"] -> undefined var fakeProperty = scopeNameGen.generate(); assignments.push(t.assignmentExpression("=", (0, _node.deepClone)(withMemberExpression), t.memberExpression((0, _node.deepClone)(scopeVar), t.stringLiteral(fakeProperty), true))); } for (var _i9 = 0; _i9 < stateVars.length; _i9++) { var oldValue = currentStateValues[_i9]; var newValue = newStateValues[_i9]; // console.log(oldValue, newValue); if (oldValue === newValue) continue; // No diff needed if the value doesn't change var assignment = t.assignmentExpression("=", (0, _node.deepClone)(stateVars[_i9]), (0, _node.numericLiteral)(newValue)); if (!isDebug && addRelativeAssignments) { // Use diffs to create confusing code assignment = t.assignmentExpression("+=", (0, _node.deepClone)(stateVars[_i9]), (0, _node.numericLiteral)(newValue - oldValue)); } assignments.push(assignment); } // Add debug label if (isDebug) { assignments.unshift(t.stringLiteral("Goto " + newTotalState)); } path.parentPath.replaceWith(t.expressionStatement(t.sequenceExpression(assignments)))[0].skip(); // Add break after updating state variables path.insertAfter(breakStatement()); } } } }; basicBlock.thisPath.traverse(visitor); }; for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) { _loop4(); } /** * Stage 3: Create a switch statement to handle the control flow * * - Add fake / impossible blocks * - Add