Browse Source

BlockStatement control flow transformation: first draft solution

sanex3339 8 years ago
parent
commit
463f82604f

+ 2 - 2
README.md

@@ -28,7 +28,7 @@ Example of obfuscated code: [gist.github.com](https://gist.github.com/sanex3339/
 ## :warning: Important
 ## :warning: Important
 #####Obfuscate only the code that belongs to you. 
 #####Obfuscate only the code that belongs to you. 
 
 
-It is not recommended to obfuscate vendor scripts and polyfills, since the obfuscated code is 25-30% slower and the files are significantly larger.
+It is not recommended to obfuscate vendor scripts and polyfills, since the obfuscated code is 15-80% slower (depends on options) and the files are significantly larger.
 
 
 ## Installation
 ## Installation
 
 
@@ -381,7 +381,7 @@ Available values:
 * `true` (`boolean`): encode `stringArray` values using `base64`
 * `true` (`boolean`): encode `stringArray` values using `base64`
 * `false` (`boolean`): don't encode `stringArray` values
 * `false` (`boolean`): don't encode `stringArray` values
 * `'base64'` (`string`): encode `stringArray` values using `base64`
 * `'base64'` (`string`): encode `stringArray` values using `base64`
-* `'rc4'` (`string`): encode `stringArray` values using `rc4`. **About 30-50% slower then `base64`, but more harder to get initial values.** It is recommended to disable [`unicodeEscapeSequence`](#unicodeescapesecuence) option with `rc4` encoding to prevent very large size of obfuscated code.
+* `'rc4'` (`string`): encode `stringArray` values using `rc4`. **About 30-50% slower then `base64`, but more harder to get initial values.** It is recommended to disable [`unicodeEscapeSequence`](#unicodeescapesequence) option with `rc4` encoding to prevent very large size of obfuscated code.
     
     
 ### `stringArrayThreshold`
 ### `stringArrayThreshold`
 Type: `number` Default: `0.8` Min: `0` Max: `1`
 Type: `number` Default: `0.8` Min: `0` Max: `1`

+ 150 - 23
dist/index.js

@@ -759,6 +759,10 @@ var _getIterator2 = __webpack_require__(21);
 
 
 var _getIterator3 = _interopRequireDefault(_getIterator2);
 var _getIterator3 = _interopRequireDefault(_getIterator2);
 
 
+var _toConsumableArray2 = __webpack_require__(31);
+
+var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
+
 var _classCallCheck2 = __webpack_require__(0);
 var _classCallCheck2 = __webpack_require__(0);
 
 
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
 var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
@@ -771,6 +775,7 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 
 var _ = __webpack_require__(144);
 var _ = __webpack_require__(144);
 var JSFuck_1 = __webpack_require__(36);
 var JSFuck_1 = __webpack_require__(36);
+var RandomGeneratorUtils_1 = __webpack_require__(10);
 
 
 var Utils = function () {
 var Utils = function () {
     function Utils() {
     function Utils() {
@@ -794,6 +799,18 @@ var Utils = function () {
             }
             }
             return newArray;
             return newArray;
         }
         }
+    }, {
+        key: "arrayShuffle",
+        value: function arrayShuffle(array) {
+            var shuffledArray = [].concat((0, _toConsumableArray3.default)(array));
+            for (var i = shuffledArray.length; i; i--) {
+                var j = Math.floor(RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) * i);
+                var _ref = [shuffledArray[j], shuffledArray[i - 1]];
+                shuffledArray[i - 1] = _ref[0];
+                shuffledArray[j] = _ref[1];
+            }
+            return shuffledArray;
+        }
     }, {
     }, {
         key: "decToHex",
         key: "decToHex",
         value: function decToHex(dec) {
         value: function decToHex(dec) {
@@ -1282,6 +1299,17 @@ var Nodes = function () {
                 obfuscated: false
                 obfuscated: false
             };
             };
         }
         }
+    }, {
+        key: "getAssignmentExpressionNode",
+        value: function getAssignmentExpressionNode(operator, left, right) {
+            return {
+                type: NodeType_1.NodeType.AssignmentExpression,
+                operator: operator,
+                left: left,
+                right: right,
+                obfuscated: false
+            };
+        }
     }, {
     }, {
         key: "getBinaryExpressionNode",
         key: "getBinaryExpressionNode",
         value: function getBinaryExpressionNode(operator, left, right) {
         value: function getBinaryExpressionNode(operator, left, right) {
@@ -1340,6 +1368,18 @@ var Nodes = function () {
                 obfuscated: false
                 obfuscated: false
             };
             };
         }
         }
+    }, {
+        key: "getContinueStatement",
+        value: function getContinueStatement(label) {
+            var continueStatementNode = {
+                type: NodeType_1.NodeType.ContinueStatement,
+                obfuscated: false
+            };
+            if (label) {
+                continueStatementNode.label = label;
+            }
+            return continueStatementNode;
+        }
     }, {
     }, {
         key: "getExpressionStatementNode",
         key: "getExpressionStatementNode",
         value: function getExpressionStatementNode(expression) {
         value: function getExpressionStatementNode(expression) {
@@ -1391,6 +1431,16 @@ var Nodes = function () {
                 obfuscated: false
                 obfuscated: false
             };
             };
         }
         }
+    }, {
+        key: "getLabeledStatement",
+        value: function getLabeledStatement(label, body) {
+            return {
+                type: NodeType_1.NodeType.LabeledStatement,
+                label: label,
+                body: body,
+                obfuscated: false
+            };
+        }
     }, {
     }, {
         key: "getLiteralNode",
         key: "getLiteralNode",
         value: function getLiteralNode(value) {
         value: function getLiteralNode(value) {
@@ -1496,6 +1546,17 @@ var Nodes = function () {
                 obfuscated: false
                 obfuscated: false
             };
             };
         }
         }
+    }, {
+        key: "getUpdateExpressionNode",
+        value: function getUpdateExpressionNode(operator, argumentExpr) {
+            return {
+                type: NodeType_1.NodeType.UpdateExpression,
+                operator: operator,
+                argument: argumentExpr,
+                prefix: false,
+                obfuscated: false
+            };
+        }
     }, {
     }, {
         key: "getVariableDeclarationNode",
         key: "getVariableDeclarationNode",
         value: function getVariableDeclarationNode() {
         value: function getVariableDeclarationNode() {
@@ -1519,6 +1580,16 @@ var Nodes = function () {
                 obfuscated: false
                 obfuscated: false
             };
             };
         }
         }
+    }, {
+        key: "getWhileStatementNode",
+        value: function getWhileStatementNode(test, body) {
+            return {
+                type: NodeType_1.NodeType.WhileStatement,
+                test: test,
+                body: body,
+                obfuscated: false
+            };
+        }
     }]);
     }]);
     return Nodes;
     return Nodes;
 }();
 }();
@@ -4892,6 +4963,14 @@ exports.ObfuscationEventEmitter = ObfuscationEventEmitter;
 "use strict";
 "use strict";
 
 
 
 
+var _keys = __webpack_require__(47);
+
+var _keys2 = _interopRequireDefault(_keys);
+
+var _assign = __webpack_require__(133);
+
+var _assign2 = _interopRequireDefault(_assign);
+
 var _map = __webpack_require__(11);
 var _map = __webpack_require__(11);
 
 
 var _map2 = _interopRequireDefault(_map);
 var _map2 = _interopRequireDefault(_map);
@@ -4928,8 +5007,10 @@ var AbstractNodeTransformer_1 = __webpack_require__(18);
 var Node_1 = __webpack_require__(12);
 var Node_1 = __webpack_require__(12);
 var NodeAppender_1 = __webpack_require__(24);
 var NodeAppender_1 = __webpack_require__(24);
 var NodeControlFlowReplacers_1 = __webpack_require__(39);
 var NodeControlFlowReplacers_1 = __webpack_require__(39);
+var Nodes_1 = __webpack_require__(25);
 var NodeUtils_1 = __webpack_require__(9);
 var NodeUtils_1 = __webpack_require__(9);
 var RandomGeneratorUtils_1 = __webpack_require__(10);
 var RandomGeneratorUtils_1 = __webpack_require__(10);
+var Utils_1 = __webpack_require__(14);
 var FunctionControlFlowTransformer = FunctionControlFlowTransformer_1 = function (_AbstractNodeTransfor) {
 var FunctionControlFlowTransformer = FunctionControlFlowTransformer_1 = function (_AbstractNodeTransfor) {
     (0, _inherits3.default)(FunctionControlFlowTransformer, _AbstractNodeTransfor);
     (0, _inherits3.default)(FunctionControlFlowTransformer, _AbstractNodeTransfor);
 
 
@@ -4949,48 +5030,94 @@ var FunctionControlFlowTransformer = FunctionControlFlowTransformer_1 = function
     (0, _createClass3.default)(FunctionControlFlowTransformer, [{
     (0, _createClass3.default)(FunctionControlFlowTransformer, [{
         key: "transformNode",
         key: "transformNode",
         value: function transformNode(functionNode) {
         value: function transformNode(functionNode) {
-            this.changeFunctionBodyControlFlow(functionNode);
-            return functionNode;
-        }
-    }, {
-        key: "changeFunctionBodyControlFlow",
-        value: function changeFunctionBodyControlFlow(functionNode) {
             var _this2 = this;
             var _this2 = this;
 
 
             if (Node_1.Node.isArrowFunctionExpressionNode(functionNode)) {
             if (Node_1.Node.isArrowFunctionExpressionNode(functionNode)) {
-                return;
+                return functionNode;
             }
             }
-            var controlFlowStorage = this.controlFlowStorageFactory();
             var hostNode = FunctionControlFlowTransformer_1.getHostNode(functionNode);
             var hostNode = FunctionControlFlowTransformer_1.getHostNode(functionNode);
-            if (this.controlFlowData.has(hostNode)) {
-                if (this.hostNodesWithControlFlowNode.indexOf(hostNode) !== -1) {
-                    hostNode.body.shift();
-                }
-                var hostControlFlowStorage = this.controlFlowData.get(hostNode);
-                controlFlowStorage.mergeWith(hostControlFlowStorage, true);
-            }
+            var controlFlowStorage = this.getControlFlowStorage(hostNode);
+            var functionHasProhibitedStatements = false;
             this.controlFlowData.set(hostNode, controlFlowStorage);
             this.controlFlowData.set(hostNode, controlFlowStorage);
             estraverse.replace(functionNode.body, {
             estraverse.replace(functionNode.body, {
                 enter: function enter(node, parentNode) {
                 enter: function enter(node, parentNode) {
-                    if (!FunctionControlFlowTransformer_1.controlFlowReplacersMap.has(node.type)) {
-                        return;
-                    }
-                    if (RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) > _this2.options.controlFlowFlatteningThreshold) {
-                        return;
+                    if (!functionHasProhibitedStatements) {
+                        functionHasProhibitedStatements = FunctionControlFlowTransformer_1.functionHasProhibitedStatements(node);
                     }
                     }
-                    var controlFlowReplacerName = FunctionControlFlowTransformer_1.controlFlowReplacersMap.get(node.type);
-                    return tslib_1.__assign({}, _this2.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage), { parentNode: parentNode });
+                    return _this2.transformFunctionNodes(node, parentNode, controlFlowStorage);
                 }
                 }
             });
             });
+            if (!functionHasProhibitedStatements) {
+                this.transformFunctionStatements(functionNode);
+            }
             if (!controlFlowStorage.getLength()) {
             if (!controlFlowStorage.getLength()) {
-                return;
+                return functionNode;
             }
             }
             var controlFlowStorageCustomNode = this.customNodeFactory(CustomNodes_1.CustomNodes.ControlFlowStorageNode);
             var controlFlowStorageCustomNode = this.customNodeFactory(CustomNodes_1.CustomNodes.ControlFlowStorageNode);
             controlFlowStorageCustomNode.initialize(controlFlowStorage);
             controlFlowStorageCustomNode.initialize(controlFlowStorage);
             NodeAppender_1.NodeAppender.prependNode(hostNode, controlFlowStorageCustomNode.getNode());
             NodeAppender_1.NodeAppender.prependNode(hostNode, controlFlowStorageCustomNode.getNode());
             this.hostNodesWithControlFlowNode.push(hostNode);
             this.hostNodesWithControlFlowNode.push(hostNode);
+            return functionNode;
+        }
+    }, {
+        key: "getControlFlowStorage",
+        value: function getControlFlowStorage(hostNode) {
+            var controlFlowStorage = this.controlFlowStorageFactory();
+            if (this.controlFlowData.has(hostNode)) {
+                if (this.hostNodesWithControlFlowNode.indexOf(hostNode) !== -1) {
+                    hostNode.body.shift();
+                }
+                var hostControlFlowStorage = this.controlFlowData.get(hostNode);
+                controlFlowStorage.mergeWith(hostControlFlowStorage, true);
+            }
+            return controlFlowStorage;
+        }
+    }, {
+        key: "transformFunctionNodes",
+        value: function transformFunctionNodes(node, parentNode, controlFlowStorage) {
+            if (!FunctionControlFlowTransformer_1.controlFlowReplacersMap.has(node.type)) {
+                return node;
+            }
+            if (RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold) {
+                return node;
+            }
+            var controlFlowReplacerName = FunctionControlFlowTransformer_1.controlFlowReplacersMap.get(node.type);
+            return tslib_1.__assign({}, this.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage), { parentNode: parentNode });
+        }
+    }, {
+        key: "transformFunctionStatements",
+        value: function transformFunctionStatements(functionNode) {
+            if (RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold) {
+                return;
+            }
+            var functionStatements = functionNode.body.body;
+            var functionStatementsObject = (0, _assign2.default)({}, functionStatements);
+            var originalKeys = (0, _keys2.default)(functionStatementsObject).map(function (key) {
+                return parseInt(key, 10);
+            });
+            var shuffledKeys = Utils_1.Utils.arrayShuffle(originalKeys);
+            var originalKeysIndexesInShuffledArray = originalKeys.map(function (key) {
+                return shuffledKeys.indexOf(key);
+            });
+            if (functionStatements.length <= 4) {
+                return;
+            } else if (!functionStatements.length) {
+                functionStatements.push(Nodes_1.Nodes.getReturnStatementNode(Nodes_1.Nodes.getLiteralNode(true)));
+            }
+            var controllerIdentifierName = RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomString(3);
+            functionNode.body.body = [Nodes_1.Nodes.getVariableDeclarationNode([Nodes_1.Nodes.getVariableDeclaratorNode(Nodes_1.Nodes.getIdentifierNode(controllerIdentifierName), Nodes_1.Nodes.getCallExpressionNode(Nodes_1.Nodes.getMemberExpressionNode(Nodes_1.Nodes.getLiteralNode(originalKeysIndexesInShuffledArray.join('|')), Nodes_1.Nodes.getIdentifierNode('split')), [Nodes_1.Nodes.getLiteralNode('|')]))]), Nodes_1.Nodes.getWhileStatementNode(Nodes_1.Nodes.getLiteralNode(true), Nodes_1.Nodes.getBlockStatementNode([Nodes_1.Nodes.getSwitchStatementNode(Nodes_1.Nodes.getCallExpressionNode(Nodes_1.Nodes.getMemberExpressionNode(Nodes_1.Nodes.getIdentifierNode(controllerIdentifierName), Nodes_1.Nodes.getIdentifierNode('shift'))), shuffledKeys.map(function (key, index) {
+                return Nodes_1.Nodes.getSwitchCaseNode(Nodes_1.Nodes.getLiteralNode(String(index)), [functionStatementsObject[key], Nodes_1.Nodes.getContinueStatement()]);
+            })), Nodes_1.Nodes.getBreakStatement()]))];
+            NodeUtils_1.NodeUtils.parentize(functionNode.body);
         }
         }
     }], [{
     }], [{
+        key: "functionHasProhibitedStatements",
+        value: function functionHasProhibitedStatements(node) {
+            var isBreakOrContinueStatement = Node_1.Node.isBreakStatementNode(node) || Node_1.Node.isContinueStatementNode(node);
+            var isVariableDeclarationWithLetOrConstKind = Node_1.Node.isVariableDeclarationNode(node) && (node.kind === 'const' || node.kind === 'let');
+            return Node_1.Node.isFunctionDeclarationNode(node) || isBreakOrContinueStatement || isVariableDeclarationWithLetOrConstKind;
+        }
+    }, {
         key: "getHostNode",
         key: "getHostNode",
         value: function getHostNode(functionNode) {
         value: function getHostNode(functionNode) {
             var blockScopesOfNode = NodeUtils_1.NodeUtils.getBlockScopesOfNode(functionNode);
             var blockScopesOfNode = NodeUtils_1.NodeUtils.getBlockScopesOfNode(functionNode);

+ 1 - 1
package.json

@@ -47,7 +47,7 @@
     "@types/lodash": "^4.14.45",
     "@types/lodash": "^4.14.45",
     "@types/mkdirp": "0.3.29",
     "@types/mkdirp": "0.3.29",
     "@types/mocha": "2.2.35",
     "@types/mocha": "2.2.35",
-    "@types/node": "6.0.55",
+    "@types/node": "6.0.56",
     "@types/sinon": "1.16.34",
     "@types/sinon": "1.16.34",
     "@types/string-template": "1.0.2",
     "@types/string-template": "1.0.2",
     "awesome-typescript-loader": "3.0.0-beta.17",
     "awesome-typescript-loader": "3.0.0-beta.17",

+ 142 - 31
src/node-transformers/node-control-flow-transformers/FunctionControlFlowTransformer.ts

@@ -20,8 +20,10 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { Node } from '../../node/Node';
 import { Node } from '../../node/Node';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeAppender } from '../../node/NodeAppender';
 import { NodeControlFlowReplacers } from '../../enums/container/NodeControlFlowReplacers';
 import { NodeControlFlowReplacers } from '../../enums/container/NodeControlFlowReplacers';
+import { Nodes } from '../../node/Nodes';
 import { NodeUtils } from '../../node/NodeUtils';
 import { NodeUtils } from '../../node/NodeUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
 import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils';
+import { Utils } from '../../utils/Utils';
 
 
 @injectable()
 @injectable()
 export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
 export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
@@ -88,6 +90,18 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
         this.customNodeFactory = customNodeFactory;
         this.customNodeFactory = customNodeFactory;
     }
     }
 
 
+    /**
+     * @param node
+     * @return {boolean}
+     */
+    private static functionHasProhibitedStatements (node: ESTree.Node): boolean {
+        const isBreakOrContinueStatement: boolean = Node.isBreakStatementNode(node) || Node.isContinueStatementNode(node);
+        const isVariableDeclarationWithLetOrConstKind: boolean = Node.isVariableDeclarationNode(node) &&
+            (node.kind === 'const' ||  node.kind === 'let');
+
+        return Node.isFunctionDeclarationNode(node) || isBreakOrContinueStatement || isVariableDeclarationWithLetOrConstKind;
+    }
+
     /**
     /**
      * @param functionNode
      * @param functionNode
      * @returns {TNodeWithBlockStatement}
      * @returns {TNodeWithBlockStatement}
@@ -117,21 +131,50 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
      * @returns {ESTree.Node}
      * @returns {ESTree.Node}
      */
      */
     public transformNode (functionNode: ESTree.Function): ESTree.Node {
     public transformNode (functionNode: ESTree.Function): ESTree.Node {
-        this.changeFunctionBodyControlFlow(functionNode);
+        if (Node.isArrowFunctionExpressionNode(functionNode)) {
+            return functionNode;
+        }
+
+        const hostNode: TNodeWithBlockStatement = FunctionControlFlowTransformer.getHostNode(functionNode);
+        const controlFlowStorage: IStorage<ICustomNode> = this.getControlFlowStorage(hostNode);
+
+        let functionHasProhibitedStatements: boolean = false;
+
+        this.controlFlowData.set(hostNode, controlFlowStorage);
+
+        estraverse.replace(functionNode.body, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (!functionHasProhibitedStatements) {
+                    functionHasProhibitedStatements = FunctionControlFlowTransformer.functionHasProhibitedStatements(node);
+                }
+
+                return this.transformFunctionNodes(node, parentNode, controlFlowStorage);
+            }
+        });
+
+        if (!functionHasProhibitedStatements) {
+            this.transformFunctionStatements(functionNode);
+        }
+
+        if (!controlFlowStorage.getLength()) {
+            return functionNode;
+        }
+
+        const controlFlowStorageCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageNode);
+
+        controlFlowStorageCustomNode.initialize(controlFlowStorage);
+        NodeAppender.prependNode(hostNode, controlFlowStorageCustomNode.getNode());
+        this.hostNodesWithControlFlowNode.push(hostNode);
 
 
         return functionNode;
         return functionNode;
     }
     }
 
 
     /**
     /**
-     * @param functionNode
+     * @param hostNode
+     * @return {IStorage<ICustomNode>}
      */
      */
-    private changeFunctionBodyControlFlow (functionNode: ESTree.Function): void {
-        if (Node.isArrowFunctionExpressionNode(functionNode)) {
-            return;
-        }
-
+    private getControlFlowStorage (hostNode: TNodeWithBlockStatement): IStorage<ICustomNode> {
         const controlFlowStorage: IStorage <ICustomNode> = this.controlFlowStorageFactory();
         const controlFlowStorage: IStorage <ICustomNode> = this.controlFlowStorageFactory();
-        const hostNode: TNodeWithBlockStatement = FunctionControlFlowTransformer.getHostNode(functionNode);
 
 
         if (this.controlFlowData.has(hostNode)) {
         if (this.controlFlowData.has(hostNode)) {
             if (this.hostNodesWithControlFlowNode.indexOf(hostNode) !== -1) {
             if (this.hostNodesWithControlFlowNode.indexOf(hostNode) !== -1) {
@@ -143,37 +186,105 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
             controlFlowStorage.mergeWith(hostControlFlowStorage, true);
             controlFlowStorage.mergeWith(hostControlFlowStorage, true);
         }
         }
 
 
-        this.controlFlowData.set(hostNode, controlFlowStorage);
+        return controlFlowStorage;
+    }
 
 
-        estraverse.replace(functionNode.body, {
-            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
-                if (!FunctionControlFlowTransformer.controlFlowReplacersMap.has(node.type)) {
-                    return;
-                }
+    /**
+     * @param node
+     * @param parentNode
+     * @param controlFlowStorage
+     * @return {ESTree.Node}
+     */
+    private transformFunctionNodes (
+        node: ESTree.Node,
+        parentNode: ESTree.Node,
+        controlFlowStorage: IStorage<ICustomNode>
+    ): ESTree.Node {
+        if (!FunctionControlFlowTransformer.controlFlowReplacersMap.has(node.type)) {
+            return node;
+        }
 
 
-                if (RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold) {
-                    return;
-                }
+        if (RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold) {
+            return node;
+        }
 
 
-                const controlFlowReplacerName: NodeControlFlowReplacers = <NodeControlFlowReplacers>FunctionControlFlowTransformer
-                    .controlFlowReplacersMap.get(node.type);
+        const controlFlowReplacerName: NodeControlFlowReplacers = <NodeControlFlowReplacers>FunctionControlFlowTransformer
+            .controlFlowReplacersMap.get(node.type);
 
 
-                return {
-                    ...this.controlFlowReplacerFactory(controlFlowReplacerName)
-                        .replace(node, parentNode, controlFlowStorage),
-                    parentNode
-                };
-            }
-        });
+        return {
+            ...this.controlFlowReplacerFactory(controlFlowReplacerName).replace(node, parentNode, controlFlowStorage),
+            parentNode
+        };
+    }
 
 
-        if (!controlFlowStorage.getLength()) {
+    /**
+     * @param functionNode
+     */
+    private transformFunctionStatements (functionNode: ESTree.FunctionExpression | ESTree.FunctionDeclaration): void {
+        if (RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold) {
             return;
             return;
         }
         }
 
 
-        const controlFlowStorageCustomNode: ICustomNode = this.customNodeFactory(CustomNodes.ControlFlowStorageNode);
+        const functionStatements: ESTree.Statement[] = functionNode.body.body;
+        const functionStatementsObject: any = Object.assign({}, functionStatements);
+        const originalKeys: number[] = Object.keys(functionStatementsObject).map((key: string) => parseInt(key, 10));
+        const shuffledKeys: number[] = Utils.arrayShuffle(originalKeys);
+        const originalKeysIndexesInShuffledArray: number[] = originalKeys.map((key: number) => shuffledKeys.indexOf(key));
 
 
-        controlFlowStorageCustomNode.initialize(controlFlowStorage);
-        NodeAppender.prependNode(hostNode, controlFlowStorageCustomNode.getNode());
-        this.hostNodesWithControlFlowNode.push(hostNode);
+        if (functionStatements.length <= 4) {
+            return;
+        } else if (!functionStatements.length) {
+            functionStatements.push(
+                Nodes.getReturnStatementNode(
+                    Nodes.getLiteralNode(true)
+                )
+            );
+        }
+
+        const controllerIdentifierName: string = RandomGeneratorUtils.getRandomString(3);
+
+        functionNode.body.body = [
+            Nodes.getVariableDeclarationNode([
+                Nodes.getVariableDeclaratorNode(
+                    Nodes.getIdentifierNode(controllerIdentifierName),
+                    Nodes.getCallExpressionNode(
+                        Nodes.getMemberExpressionNode(
+                            Nodes.getLiteralNode(
+                                originalKeysIndexesInShuffledArray.join('|')
+                            ),
+                            Nodes.getIdentifierNode('split')
+                        ),
+                        [
+                            Nodes.getLiteralNode('|')
+                        ]
+                    )
+                )
+            ]),
+            Nodes.getWhileStatementNode(
+                Nodes.getLiteralNode(true),
+                Nodes.getBlockStatementNode([
+                    Nodes.getSwitchStatementNode(
+                        Nodes.getCallExpressionNode(
+                            Nodes.getMemberExpressionNode(
+                                Nodes.getIdentifierNode(controllerIdentifierName),
+                                Nodes.getIdentifierNode('shift')
+                            )
+                        ),
+                        shuffledKeys.map((key: number, index: number) => {
+                            return Nodes.getSwitchCaseNode(
+                                Nodes.getLiteralNode(String(index)),
+                                [
+                                    functionStatementsObject[key],
+                                    Nodes.getContinueStatement()
+                                ]
+                            );
+                        })
+                    ),
+                    Nodes.getBreakStatement()
+                ])
+            )
+        ];
+
+        NodeUtils.parentize(functionNode.body);
     }
     }
 }
 }

+ 80 - 0
src/node/Nodes.ts

@@ -19,6 +19,26 @@ export class Nodes {
         };
         };
     }
     }
 
 
+    /**
+     * @param operator
+     * @param left
+     * @param right
+     * @return {ESTree.AssignmentExpression}
+     */
+    public static getAssignmentExpressionNode (
+        operator: ESTree.AssignmentOperator,
+        left: ESTree.Pattern | ESTree.MemberExpression,
+        right: ESTree.Expression
+    ): ESTree.AssignmentExpression {
+        return {
+            type: NodeType.AssignmentExpression,
+            operator,
+            left,
+            right,
+            obfuscated: false
+        };
+    }
+
     /**
     /**
      * @param operator
      * @param operator
      * @param left
      * @param left
@@ -98,6 +118,23 @@ export class Nodes {
         };
         };
     }
     }
 
 
+    /**
+     * @param label
+     * @return {ESTree.ContinueStatement}
+     */
+    public static getContinueStatement (label?: ESTree.Identifier): ESTree.ContinueStatement {
+        const continueStatementNode: ESTree.ContinueStatement = {
+            type: NodeType.ContinueStatement,
+            obfuscated: false
+        };
+
+        if (label) {
+            continueStatementNode.label = label;
+        }
+
+        return continueStatementNode;
+    }
+
     /**
     /**
      * @param expression
      * @param expression
      * @returns {ESTree.ExpressionStatement}
      * @returns {ESTree.ExpressionStatement}
@@ -175,6 +212,20 @@ export class Nodes {
         };
         };
     }
     }
 
 
+    /**
+     * @param label
+     * @param body
+     * @returns {ESTree.LabeledStatement}
+     */
+    public static getLabeledStatement (label: ESTree.Identifier, body: ESTree.Statement): ESTree.LabeledStatement {
+        return {
+            type: NodeType.LabeledStatement,
+            label,
+            body,
+            obfuscated: false
+        };
+    }
+
     /**
     /**
      * @param value
      * @param value
      * @returns {ESTree.Literal}
      * @returns {ESTree.Literal}
@@ -327,6 +378,21 @@ export class Nodes {
         };
         };
     }
     }
 
 
+    /**
+     * @param operator
+     * @param argumentExpr
+     * @returns {ESTree.UpdateExpression}
+     */
+    public static getUpdateExpressionNode (operator: ESTree.UpdateOperator, argumentExpr: ESTree.Expression): ESTree.UpdateExpression {
+        return {
+            type: NodeType.UpdateExpression,
+            operator,
+            argument: argumentExpr,
+            prefix: false,
+            obfuscated: false
+        };
+    }
+
     /**
     /**
      * @param declarations
      * @param declarations
      * @param kind
      * @param kind
@@ -357,4 +423,18 @@ export class Nodes {
             obfuscated: false
             obfuscated: false
         };
         };
     }
     }
+
+    /**
+     * @param test
+     * @param body
+     * @return {ESTree.WhileStatement}
+     */
+    public static getWhileStatementNode (test: ESTree.Expression, body: ESTree.Statement): ESTree.WhileStatement {
+        return {
+            type: NodeType.WhileStatement,
+            test,
+            body,
+            obfuscated: false
+        };
+    }
 }
 }

+ 18 - 0
src/utils/Utils.ts

@@ -1,6 +1,8 @@
 import * as _ from 'lodash';
 import * as _ from 'lodash';
 import { JSFuck } from '../enums/JSFuck';
 import { JSFuck } from '../enums/JSFuck';
 
 
+import { RandomGeneratorUtils } from './RandomGeneratorUtils';
+
 export class Utils {
 export class Utils {
     /**
     /**
      * @type {string}
      * @type {string}
@@ -38,6 +40,22 @@ export class Utils {
         return newArray;
         return newArray;
     }
     }
 
 
+    /**
+     * @param array
+     * @return {T[]}
+     */
+    public static arrayShuffle <T> (array: T[]): T[] {
+        const shuffledArray: T[] = [...array];
+
+        for (let i: number = shuffledArray.length; i; i--) {
+            const j: number = Math.floor(RandomGeneratorUtils.getRandomFloat(0, 1) * i);
+
+            [shuffledArray[i - 1], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i - 1]];
+        }
+
+        return shuffledArray;
+    }
+
     /**
     /**
      * @param dec
      * @param dec
      * @returns {string}
      * @returns {string}

+ 89 - 5
test/dev/dev.ts

@@ -1,5 +1,4 @@
 'use strict';
 'use strict';
-import { NO_CUSTOM_NODES_PRESET } from '../../src/options/presets/NoCustomNodes';
 
 
 if (!(<any>global)._babelPolyfill) {
 if (!(<any>global)._babelPolyfill) {
     require('babel-polyfill');
     require('babel-polyfill');
@@ -10,13 +9,98 @@ if (!(<any>global)._babelPolyfill) {
 
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
         `
-            var test = console.log;
+            (function(){
+                var result = 1,
+                    term1 = 0,
+                    term2 = 1,
+                    i = 1;
+                while(i < 10)
+                {
+                    var test = 10;
+                    result = term1 + term2;
+                    console.log(result);
+                    term1 = term2;
+                    term2 = result;
+                    i++;
+                }
+        
+                console.log(test);
+                
+                var test = function (test) {
+                    console.log(test);
+                    
+                    if (true) {
+                        var test = 5
+                    }
+                    
+                    return test;
+                }
+                
+                console.log(test(1));
+                
+                function test2 (abc) {
+                    function test1 () {
+                      console.log('inside', abc.item);
+                    }
+                    
+                    console.log('тест', abc);
+                    
+                    var abc = {};
+                    
+                    return abc.item = 15, test1();
+                };
+                
+                var regexptest = /version\\/(\\d+)/i;
+                console.log(regexptest);
+                
+                test2(22);
+                console.log(105.4);
+                console.log(true, false);
+                
+                var sA = 'shorthand1';
+                var sB = 'shorthand2';
+                
+                console.log({sA, sB});
+                
+                function foo () {
+                    return function () {
+                        var sum1 = 10 + 20;
+                        var sum2 = 20 + 30;
+                        var sum3 = 30 + 50;
+                        var sub = sum3 - sum2;
+                        
+                        return sum1 + sub;
+                    }
+                }
+                
+                try {
+                } catch (error) {
+                    console.log(error);
+                }
+                
+                console.log(foo()());
+                
+                function bar () {
+                    console.log(1);
+                    console.log(2);
+                    console.log(3);
+                    console.log(4);
+                    console.log(5);
+                    console.log(6);
+                    console.log(7);
+                    console.log(8);
+                }
+                
+                bar();
+            })();
         `,
         `,
         {
         {
-            ...NO_CUSTOM_NODES_PRESET,
             compact: false,
             compact: false,
-            stringArray: true,
-            stringArrayThreshold: 1
+            controlFlowFlattening: true,
+            controlFlowFlatteningThreshold: 1,
+            disableConsoleOutput: false,
+            stringArray: false,
+            unicodeEscapeSequence: false
         }
         }
     ).getObfuscatedCode();
     ).getObfuscatedCode();