diff --git a/dist/index.js b/dist/index.js index 7285283a..51d19050 100644 --- a/dist/index.js +++ b/dist/index.js @@ -5090,6 +5090,7 @@ var ServiceIdentifiers_1 = __webpack_require__(4); var CustomNodes_1 = __webpack_require__(16); var AbstractNodeTransformer_1 = __webpack_require__(18); var Node_1 = __webpack_require__(12); +var RandomGeneratorUtils_1 = __webpack_require__(9); var Utils_1 = __webpack_require__(13); var BlockStatementControlFlowTransformer = BlockStatementControlFlowTransformer_1 = function (_AbstractNodeTransfor) { (0, _inherits3.default)(BlockStatementControlFlowTransformer, _AbstractNodeTransfor); @@ -5106,7 +5107,7 @@ var BlockStatementControlFlowTransformer = BlockStatementControlFlowTransformer_ (0, _createClass3.default)(BlockStatementControlFlowTransformer, [{ key: "transformNode", value: function transformNode(blockStatementNode) { - if (BlockStatementControlFlowTransformer_1.blockStatementHasProhibitedStatements(blockStatementNode)) { + if (RandomGeneratorUtils_1.RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold || BlockStatementControlFlowTransformer_1.blockStatementHasProhibitedStatements(blockStatementNode)) { return blockStatementNode; } var blockStatementBody = blockStatementNode.body; diff --git a/src/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.ts b/src/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.ts index 8f160c4c..8c150041 100644 --- a/src/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.ts +++ b/src/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.ts @@ -12,6 +12,7 @@ import { CustomNodes } from '../../enums/container/CustomNodes'; import { AbstractNodeTransformer } from '../AbstractNodeTransformer'; import { Node } from '../../node/Node'; +import { RandomGeneratorUtils } from '../../utils/RandomGeneratorUtils'; import { Utils } from '../../utils/Utils'; @injectable() @@ -53,7 +54,10 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme * @returns {ESTree.Node} */ public transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node { - if (BlockStatementControlFlowTransformer.blockStatementHasProhibitedStatements(blockStatementNode)) { + if ( + RandomGeneratorUtils.getRandomFloat(0, 1) > this.options.controlFlowFlatteningThreshold || + BlockStatementControlFlowTransformer.blockStatementHasProhibitedStatements(blockStatementNode) + ) { return blockStatementNode; } diff --git a/test/dev/dev.ts b/test/dev/dev.ts index 2a896683..0d43324e 100644 --- a/test/dev/dev.ts +++ b/test/dev/dev.ts @@ -11,7 +11,14 @@ if (!(global)._babelPolyfill) { let obfuscatedCode: string = JavaScriptObfuscator.obfuscate( ` (function () { - var abc = 1 + 1; + while (true) { + break; + } + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); })(); `, { diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-2.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-2.js new file mode 100644 index 00000000..19022c33 --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-2.js @@ -0,0 +1,9 @@ +(function () { + while (true) { + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); + } +})(); diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-break-statement.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-break-statement.js new file mode 100644 index 00000000..4aac0647 --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-break-statement.js @@ -0,0 +1,10 @@ +(function () { + while (true) { + break; + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); + } +})(); diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-const-declaration.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-const-declaration.js new file mode 100644 index 00000000..343aace6 --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-const-declaration.js @@ -0,0 +1,8 @@ +(function () { + const variable = 1; + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); +})(); diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-continue-statement.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-continue-statement.js new file mode 100644 index 00000000..9122ef7c --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-continue-statement.js @@ -0,0 +1,10 @@ +(function () { + while (true) { + continue; + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); + } +})(); \ No newline at end of file diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-function-declaration.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-function-declaration.js new file mode 100644 index 00000000..8691a088 --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-function-declaration.js @@ -0,0 +1,8 @@ +(function () { + function foo () {} + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); +})(); diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-let-declaration.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-let-declaration.js new file mode 100644 index 00000000..89853092 --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-let-declaration.js @@ -0,0 +1,8 @@ +(function () { + let variable = 1; + console.log(1); + console.log(2); + console.log(3); + console.log(4); + console.log(5); +})(); diff --git a/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-one-statement.js b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-one-statement.js new file mode 100644 index 00000000..17a626e0 --- /dev/null +++ b/test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-one-statement.js @@ -0,0 +1,3 @@ +(function () { + console.log(1); +})(); diff --git a/test/functional-tests/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.spec.ts b/test/functional-tests/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.spec.ts index 379c7a35..7e8faa5c 100644 --- a/test/functional-tests/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.spec.ts +++ b/test/functional-tests/node-transformers/node-control-flow-transformers/BlockStatementControlFlowTransformer.spec.ts @@ -56,5 +56,199 @@ describe('BlockStatementControlFlowTransformer', () => { assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']); }); }); + + describe('variant #2: 5 simple statements inside while loop without break or continue statement', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-2.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1, + unicodeEscapeSequence: false + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp1: RegExp = /console\['log'\]\(0x1\);/; + const statementRegExp2: RegExp = /console\['log'\]\(0x2\);/; + const statementRegExp3: RegExp = /console\['log'\]\(0x3\);/; + const statementRegExp4: RegExp = /console\['log'\]\(0x4\);/; + const statementRegExp5: RegExp = /console\['log'\]\(0x5\);/; + + const switchCaseRegExp: RegExp = /switch *\(_0x([a-z0-9]){4,6}\[_0x([a-z0-9]){4,6}\+\+\]\) *\{/; + const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g; + const switchCaseLength: number = obfuscatedCode.match(switchCaseLengthRegExp)!.length; + + const switchCaseMapRegExp: RegExp = /var *_0x(?:[a-z0-9]){4,6} *= *'(.*?)'/; + const switchCaseMapMatches: RegExpMatchArray = obfuscatedCode.match(switchCaseMapRegExp); + const switchCaseMapMatch: string = switchCaseMapMatches[1]; + const switchCaseMap: string[] = switchCaseMapMatch.replace(/\\x7c/g, '|').split('|').sort(); + + it('should save all statements', () => { + assert.match(obfuscatedCode, statementRegExp1); + assert.match(obfuscatedCode, statementRegExp2); + assert.match(obfuscatedCode, statementRegExp3); + assert.match(obfuscatedCode, statementRegExp4); + assert.match(obfuscatedCode, statementRegExp5); + }); + + it('should wrap block statement statements in switch case structure', () => { + assert.match(obfuscatedCode, switchCaseRegExp); + assert.equal(switchCaseLength, 5); + }); + + it('should create variable with order of switch cases sequence', () => { + assert.deepEqual(switchCaseMap, ['0', '1', '2', '3', '4']); + }); + }); + + describe('variant #3: less then 5 statements', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-one-statement.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp: RegExp = /^\(function *\( *\) *\{ *console\['(\\x[a-f0-9]*){3}'\]\(0x1\); *\} *\( *\) *\);$/; + + it('shouldn\'t transform block statement if statements length less than 5', () => { + assert.match(obfuscatedCode, statementRegExp); + }); + }); + + describe('variant #4: const declaration inside block statement', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-const-declaration.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp: RegExp = /^\(function *\( *\) *\{ *const *_0x([a-z0-9]){4,6} *= *0x1; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/; + + it('shouldn\'t transform block statement if block statement contain variable declaration with `const` kind', () => { + assert.match(obfuscatedCode, statementRegExp); + }); + }); + + describe('variant #5: let declaration inside block statement', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-let-declaration.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp: RegExp = /^\(function *\( *\) *\{ *let *_0x([a-z0-9]){4,6} *= *0x1; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/; + + it('shouldn\'t transform block statement if block statement contain variable declaration with `let` kind', () => { + assert.match(obfuscatedCode, statementRegExp); + }); + }); + + describe('variant #6: break statement inside block statement', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-break-statement.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *break; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/; + + it('shouldn\'t transform block statement if block statement contain break statement', () => { + assert.match(obfuscatedCode, statementRegExp); + }); + }); + + describe('variant #7: continue statement inside block statement', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-continue-statement.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *continue; *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/; + + it('shouldn\'t transform block statement if block statement contain continue statement', () => { + assert.match(obfuscatedCode, statementRegExp); + }); + }); + + describe('variant #8: function declaration inside block statement', () => { + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-function-declaration.js' + ), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: 1 + } + ); + const obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); + + const statementRegExp: RegExp = /^\(function *\( *\) *\{ *function *_0x([a-z0-9]){4,6} *\( *\) *\{ *\} *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/; + + it('shouldn\'t transform block statement if block statement contain function declaration', () => { + assert.match(obfuscatedCode, statementRegExp); + }); + }); + + describe('variant #9: `controlFlowFlatteningThreshold` chance', () => { + const samples: number = 1000; + const controlFlowFlatteningThreshold: number = 0.5; + const delta: number = 0.1; + const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( + readFileAsString( + './test/fixtures/node-transformers/node-control-flow-transformers/block-statement-control-flow-transformer-1.js' + ).repeat(samples), + { + ...NO_CUSTOM_NODES_PRESET, + controlFlowFlattening: true, + controlFlowFlatteningThreshold: controlFlowFlatteningThreshold, + } + ); + + const regExp1: RegExp = /switch *\(_0x([a-z0-9]){4,6}\[_0x([a-z0-9]){4,6}\+\+\]\) *\{/g; + const regExp2: RegExp = /\(function *\( *\) *\{ *console\['(\\x[a-f0-9]*){3}'\]\(0x1\);/g; + const transformedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp1)!.length; + const untouchedStatementMatchesLength = obfuscationResult.getObfuscatedCode().match(regExp2)!.length; + + it('should transform block statement with `controlFlowFlatteningThreshold` chance', () => { + console.log(obfuscationResult.getObfuscatedCode()); + assert.closeTo(transformedStatementMatchesLength / samples, controlFlowFlatteningThreshold, delta); + assert.closeTo(untouchedStatementMatchesLength / samples, controlFlowFlatteningThreshold, delta); + }); + }); }); });