js-confuser
Version:
JavaScript Obfuscation Tool.
1,013 lines (958 loc) • 67.1 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");
var _xorStringTemplate = require("../templates/xorStringTemplate");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
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 _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 _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 _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 _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 _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");
// For skipping literals
var CFF_SKIP = Symbol("CFF_SKIP");
var HashFunctionTemplate = new _template["default"]("\nfunction {fnName}(int) {\n var a = (int | 0) ^ 0x9e3779b9, b = 0x243f6a88 | 0, k = 0x6a09e667 | 0;\n for (var i = 0; i < 19; i++) {\n a = (a + ((b << 7) ^ (b >>> 3)) + k) | 0;\n a = (a ^ (a >>> 15)) | 0;\n a = (a + (a << 11)) | 0;\n b = (b ^ ((a << 4) + (a >>> 9) + k)) | 0;\n b = (b + (b << 6)) | 0;\n b = (b ^ (b >>> 13)) | 0;\n k = (k + 0x7f4a7c15) | 0;\n }\n a = (a ^ b) | 0;\n a = (a + (a << 3)) | 0;\n a = (a ^ (a >>> 11)) | 0;\n a = (a + (a << 15)) | 0;\n b = (b ^ (b >>> 13)) | 0;\n b = (b + (b << 7)) | 0;\n b = (b ^ (b >>> 17)) | 0;\n return (a >>> 0) * 0x100000 + (b >>> 12);\n}\n");
function HashFunction(_int) {
var a = (_int | 0) ^ 0x9e3779b9,
b = 0x243f6a88 | 0,
k = 0x6a09e667 | 0;
for (var i = 0; i < 19; i++) {
a = a + (b << 7 ^ b >>> 3) + k | 0;
a = a ^ a >>> 15 | 0;
a = a + (a << 11) | 0;
b = b ^ (a << 4) + (a >>> 9) + k | 0;
b = b + (b << 6) | 0;
b = b ^ b >>> 13 | 0;
k = k + 0x7f4a7c15 | 0;
}
a = a ^ b | 0;
a = a + (a << 3) | 0;
a = a ^ a >>> 11 | 0;
a = a + (a << 15) | 0;
b = b ^ b >>> 13 | 0;
b = b + (b << 7) | 0;
b = b ^ b >>> 17 | 0;
return (a >>> 0) * 0x100000 + (b >>> 12);
}
/**
* 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 addAssertions = true; // use @js-confuser-assert values?
var mangleNumericalLiterals = true; // 50 => state + X
var mangleBooleanLiterals = true; // true => state == X
var mangleStringLiterals = true; // "hi" => xor("fOq", state + X)
var booleanLiteralChance = 100; // 100% for booleans
var numericLiteralChance = 50; // 50% for numbers
var stringLiteralChance = 100; // 100% for strings
var sequenceSizeRange = [100, 125];
var stateVarsRange = [75, 100];
var stateValueRange = [-999, 999];
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;
}
var needsSumFunction = false;
var sumFnName = me.getPlaceholder() + "_cff_sum";
var xorFnName = me.getPlaceholder() + "_cff_xor";
var sequenceName = me.getPlaceholder() + "_cff_sequence";
var sliceFnName = me.getPlaceholder() + "_cff_slice";
var hashFnName = me.getPlaceholder() + "_cff_hash";
var stringsName = me.getPlaceholder() + "_strings";
var stringsValue = "";
var sequence = new Array((0, _randomUtils.getRandomInteger)(sequenceSizeRange[0], sequenceSizeRange[1])).fill(0).map(function (_) {
return (0, _randomUtils.getRandomInteger)(stateValueRange[0], stateValueRange[1]);
});
function cffMain(_path) {
var programOrFunctionPath = _path;
// if (cffOptions.excludeLoops) {
// // Exclude loops
// if (programOrFunctionPath.find((p) => 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 identifier = function identifier(suffix) {
var name = prefix + "_" + suffix;
var id = t.identifier(name);
return id;
};
var mainFnName = identifier("main");
var scopeVar = identifier("scope");
var statesVar = identifier("states");
var runtimeVar = identifier("runtime");
var stateVars = new Array(isDebug ? 1 : (0, _randomUtils.getRandomInteger)(stateVarsRange[0], stateVarsRange[1])).fill("").map(function (_, i) {
return t.memberExpression((0, _node.deepClone)(statesVar), t.numericLiteral(i), true);
});
var dynamicIndexes = [];
var dynamicIndexesSet = new Set();
var dynamicIndexesAmount = (0, _randomUtils.getRandomInteger)(8, 12);
for (var i = 0; i < dynamicIndexesAmount && dynamicIndexesSet.size < stateVars.length; i++) {
var proposed = void 0;
do {
proposed = (0, _randomUtils.getRandomInteger)(0, stateVars.length);
} while (dynamicIndexesSet.has(proposed));
dynamicIndexes.push(proposed);
dynamicIndexesSet.add(proposed);
}
var argVar = identifier("_arg");
var usedArgVar = false;
var _didReturnVar = identifier("return");
var basicBlocks = new Map();
// Map labels to states
var stateIntGen = new _IntGen.IntGen(stateValueRange[0], stateValueRange[1]);
var defaultBlockPath = blockPath;
var scopeCounter = 0;
var scopeNameGen = new _NameGen.NameGen(me.options.identifierGenerator);
if (!isDebug) {
scopeNameGen = me.obfuscator.nameGen;
}
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", new _NameGen.NameGen(me.options.identifierGenerator));
this.scope = scope;
this.initializingBasicBlock = initializingBasicBlock;
this.propertyName = isDebug ? "_" + cffIndex + "_" + scopeCounter++ : scopeNameGen.generate();
}
return _createClass(ScopeManager, [{
key: "getNewName",
value: function getNewName(name, originalNode) {
if (!this.nameMap.has(name)) {
var newName = this.nameGen.generate(false);
if (isDebug) {
newName = "_" + name;
}
this.nameMap.set(name, newName);
me.changeData.variables++;
// console.log( `Renaming ${name} to ${newName}: ${this.scope.path.type} (scope ${this.propertyName})` );
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);
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 90% sequence 10% random numbers
this.stateValues = stateVars.map(function (_, i) {
return dynamicIndexesSet.has(i) ? (0, _randomUtils.getRandomInteger)(stateValueRange[0], stateValueRange[1]) : sequence[i];
});
// Correct one of the values so that the accumulated sum is equal to the state
var correctIndex = (0, _randomUtils.choice)(dynamicIndexes);
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: "createPath",
value:
// User-provided variables of their own runtime values with guaranteed 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)(stateValueRange[0], stateValueRange[1])]);
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) {
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);
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$node$leadi;
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);
var hoistedBasicBlock = Array.from(basicBlocks.values()).find(function (block) {
return block.parentPath === currentBasicBlock.parentPath;
});
// Added into scope object due to function flattening causes redeclaration (Test #42 and #44)
hoistedBasicBlock.scope.bindings[fnName].kind = "var";
if (isIllegal) {
// Function must be converted to function expression as hoisted function declaration
// gets redeclared (and losing prototype methods) each iteration (Test #43)
var asFunctionExpression = statement.node;
asFunctionExpression.type = "FunctionExpression";
hoistedBasicBlock.body.unshift(t.variableDeclaration("var", [t.variableDeclarator(t.identifier(fnName), asFunctionExpression)]));
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;
// 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) {
// console.log(`Remapped param binding ${identifierName} ${binding.kind} ${binding.identifier?.loc?.start} to var`);
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 && programPath // <- ONLY APPLY TO TOP-LEVEL NEVER FUNCTIONS!
) {
// 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
});
}
if (addAssertions && statement.node.type === "ExpressionStatement" && (_statement$node$leadi = statement.node.leadingComments) !== null && _statement$node$leadi !== void 0 && _statement$node$leadi.some(function (comment) {
return comment.value.includes("@js-confuser-assert");
})) {
var expr = statement.node.expression;
(0, _assert.ok)(expr.type === "BinaryExpression" && ["==", "==="].includes(expr.operator), "Must be == or === for js-confuser-assert");
var rightValue = parseInt(me.obfuscator.generateCode(expr.right).code);
(0, _assert.ok)(typeof rightValue === "number" && !Number.isNaN(rightValue), "right value must be number for js-confuser-assert");
var userVar = me.getPlaceholder();
var userExpr = expr.left;
var salt = (0, _randomUtils.getRandomInteger)(1000000, 1000000000);
var hashedValue = HashFunction(salt + rightValue);
currentBasicBlock.body.push(new _template["default"]("var {userVar} = {hashFnName}({salt} + {userExpr})").single({
userVar: userVar,
userExpr: userExpr,
hashFnName: hashFnName,
salt: salt
}));
currentBasicBlock.scope.push({
id: t.identifier(userVar),
init: userExpr,
kind: "var"
});
endCurrentBasicBlock({
nextBlockPath: nextBlockPath
});
currentBasicBlock.assertions = _defineProperty({}, userVar, hashedValue);
return 0; // continue
}
// 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
});
if (!isDebug && addDeadCode) {
// DEAD CODE 1/3: Add fake chunks that are never reached
var fakeChunkCount = (0, _randomUtils.getRandomInteger)(2, 6);
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"
var addFakeJumpsCount = (0, _randomUtils.getRandomInteger)(3, 6);
for (var _i2 = 0; _i2 < addFakeJumpsCount; _i2++) {
var _basicBlock = (0, _randomUtils.choice)(Array.from(basicBlocks.values()));
if (!_basicBlock) continue;
var randomLabel = (0, _randomUtils.choice)(Array.from(basicBlocks.keys()));
// Smartly find index BEFORE current jumps so that Minify doesn't remove them
var insertIndex = _basicBlock.body.findIndex(function (p) {
return p.type === "ExpressionStatement" && p.expression.type === "CallExpression" && p.expression.callee.type === "Identifier" && p.expression.callee.name === gotoFunctionName;
});
if (insertIndex === -1) {
insertIndex = _basicBlock.body.length;
}
_basicBlock.body.splice(insertIndex, 0, 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, 4);
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 _i3 = 0; _i3 < cloneChunkCount; _i3++) {
_loop2();
}
}
/**
* 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
var _iterator = _createForOfIteratorHelper(basicBlocks.values()),
_step;
try {
var _loop4 = function _loop4() {
var basicBlock = _step.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 = {
SequenceExpression: {
enter: function enter(path) {
var _path$parentPath;
var hasComment = function hasComment(node) {
var _node$leadingComments;
return node === null || node === void 0 || (_node$leadingComments = node.leadingComments) === null || _node$leadingComments === void 0 ? void 0 : _node$leadingComments.some(function (p) {
return p.value.includes("@js-confuser-assert");
});
};
var asserted = hasComment(path.node) || hasComment((_path$parentPath = path.parentPath) === null || _path$parentPath === void 0 ? void 0 : _path$parentPath.node);
if (!asserted) {
return;
}
var exprs = path.get("expressions");
if (exprs.length !== 2) {
throw new Error("Asserted sequence expressions must have exactly 2 expressions");
}
var assertedValue = exprs[1].evaluate();
if (!assertedValue.confident) {
throw new Error("Asserted sequence expression must be statically evaluable");
}
(0, _assert.ok)(typeof assertedValue.value === "number", "Asserted sequence expression must evaluate to a number");
path.replaceWith(t.assignmentExpression("=", (0, _node.deepClone)(runtimeVar), exprs[0].node));
}
},
BooleanLiteral: {
exit: function exit(boolPath) {
// Don't mangle booleans in debug mode
if (isDebug || !mangleBooleanLiterals || boolPath.find(function (p) {
return p.node[CFF_SKIP];
})) return;
if (!isWithinSameFunction(boolPath)) return;
if (!(0, _randomUtils.chance)(booleanLiteralChance)) 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)(stateValueRange[0], stateValueRange[1]), 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);
newExpression[CFF_SKIP] = cffIndex;
}
},
// Mangle numbers with the state values
NumericLiteral: {
exit: function exit(numPath) {
// Don't mangle numbers in debug mode
if (isDebug || !mangleNumericalLiterals || numPath.find(function (p) {
return p.node[CFF_SKIP];
})) 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)(numericLiteralChance)) 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();
diff[CFF_SKIP] = cffIndex;
}
},
// Xor encrypt string literals
StringLiteral: {
exit: function exit(strPath) {
// Don't mangle strings in debug mode
if (isDebug || !mangleStringLiterals || strPath.find(function (p) {
return p.node[CFF_SKIP];
})) return;
// Don't accidentally break goto call expressions
var p = strPath.parentPath;
if (p !== null && p !== void 0 && p.isCallExpression() && t.isIdentifier(p.node.callee) && p.node.callee.name === gotoFunctionName) {
return;
}
var str = strPath.node.value;
if (str.length > 5000 || str.length < 3) return;
if (!isWithinSameFunction(strPath)) return;
if (!(0, _randomUtils.chance)(stringLiteralChance)) return;
var num = (0, _randomUtils.getRandomInteger)(0, 1000000); // XOR string encrypt key
var encryptedStr = (0, _xorStringTemplate.xorEncodeString)(str, num);
var decryptedStr = (0, _xorStringTemplate.xorDecodeString)(encryptedStr, num);
// If decryption fails for some reason, skip
if (decryptedStr !== str) {
return;
}
mangledLiteralsCreated++;
var index = (0, _randomUtils.getRandomInteger)(0, stateVars.length - 1);
var stateVar = stateVars[index];
// Equals 'num' but entangled with current state var
var diff = t.binaryExpression("+", (0, _node.deepClone)(stateVar), me.skip((0, _node.numericLiteral)(num - currentStateValues[index])));
// Random printable-ASCII char used as decoy padding
var decoyChar = function decoyChar() {
return String.fromCharCode((0, _randomUtils.getRandomInteger)(32, 127));
};
// Leading decoy
for (var _p = (0, _randomUtils.getRandomInteger)(0, 5); _p > 0; _p--) {
stringsValue += decoyChar();
}
var start = stringsValue.length;
stringsValue += encryptedStr;
// Trailing decoy
for (var _p2 = (0, _randomUtils.getRandomInteger)(0, 5); _p2 > 0; _p2--) {
stringsValue += decoyChar();
}
var newExpr = t.callExpression(t.identifier(xorFnName), [diff, t.numericLiteral(start), t.numericLiteral(encryptedStr.length)]);
(0, _astUtils.ensureComputedExpression)(strPath);
strPath.replaceWith(newExpr);
strPath.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;
if ((0, _astUtils.isDefiningIdentifier)(path)) {
(0, _astUtils.replaceDefiningIdentifierToMemberExpression)(path, memberExpression);
return;
}
if (!path.container) return;
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 = [];
var mutatingStateValues = _toConsumableArray(currentStateValues);
var hasAssertions = Object.keys(jumpBlock.assertions || {}).length > 0;
var _loop5 = function _loop5(_i8) {
var oldValue = currentStateValues[_i8];
var newValue = newStateValues[_i8];
// console.log(oldValue, newValue);
if (oldValue === newValue) return 1; // continue
// No diff needed if the value doesn't change
var assignment = t.assignmentExpression("=", (0, _node.deepClone)(stateVars[_i8]), (0, _node.numericLiteral)(newValue));
if (!isDebug && addRelativeAssignments) {
// Use two level of diffs to create confusing code:
// stateVar += anotherStateVar - diff
// has the effect of:
// stateVar = newState
var mangledNumericLiteral = function mangledNumericLiteral(value) {
var stateVarIndex;
do {
stateVarIndex = (0, _randomUtils.getRandomInteger)(0, stateVars.length);
} while (stateVarIndex === _i8 && stateVars.length > 1);
var stateVarId = stateVars[stateVarIndex];
var stateVarValue = mutatingStateValues[stateVarIndex];
var diff = stateVarValue - value;
return t.binaryExpression("-", (0, _node.deepClone)(stateVarId), hasAssertions ? mangledAssertion(diff) : (0, _node.numericLiteral)(diff));
};
var mangledAssertion = function mangledAssertion(value) {
var userVar = (0, _randomUtils.choice)(Object.keys(jumpBlock.assertions));
if (!hasAssertions) return (0, _node.numericLiteral)(value);
var assertedValue = jumpBlock.assertions[userVar];