js-confuser
Version:
JavaScript Obfuscation Tool.
955 lines (902 loc) • 64.7 kB
JavaScript
"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