import { assert } from 'chai'; import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult'; import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes'; import { getRegExpMatch } from '../../../../helpers/getRegExpMatch'; import { readFileAsString } from '../../../../helpers/readFileAsString'; import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator'; /** * @param hexNumber * @return {RegExp} */ const getStatementRegExp: (hexNumber: string) => RegExp = (hexNumber) => { return new RegExp(`console\\['log'\\]\\(${hexNumber}\\);`); }; describe('BlockStatementControlFlowTransformer', function () { this.timeout(100000); describe('transformNode (blockStatementNode: ESTree.BlockStatement): ESTree.Node', () => { describe('variant #1: 5 simple statements', () => { let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/input-1.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); describe('`console.log` statements', ()=> { const statementRegExp1: RegExp = getStatementRegExp('0x1'); const statementRegExp2: RegExp = getStatementRegExp('0x2'); const statementRegExp3: RegExp = getStatementRegExp('0x3'); const statementRegExp4: RegExp = getStatementRegExp('0x4'); const statementRegExp5: RegExp = getStatementRegExp('0x5'); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp1); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp2); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp3); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp4); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp5); }); }); describe('block statement statements', () => { const switchCaseRegExp: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/; const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g; const expectedSwitchCaseLength: number = 5; let switchCaseLength: number; before(() => { switchCaseLength = obfuscatedCode.match(switchCaseLengthRegExp)!.length; }); it('should wrap block statement statements in switch-case structure', () => { assert.match(obfuscatedCode, switchCaseRegExp); }); it('each statement should be wrapped by switch-case structure', () => { assert.equal(switchCaseLength, expectedSwitchCaseLength); }); }); describe('switch-case map', () => { const switchCaseMapVariableRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6}\['.*'\]\['split'\]\('\|'\)/; const switchCaseMapStringRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *\{'.*' *: *'(.*)'\};/; const expectedSwitchCasesSequence: string[] = ['0', '1', '2', '3', '4']; let switchCaseMap: string[]; before(() => { const switchCaseMapMatch: string = getRegExpMatch(obfuscatedCode, switchCaseMapStringRegExp); switchCaseMap = switchCaseMapMatch.split('|').sort(); }); it('should create switch-case map variable', () => { assert.match(obfuscatedCode, switchCaseMapVariableRegExp); }); it('should create valid switch-case map variable with order of switch cases sequence', () => { assert.deepEqual(switchCaseMap, expectedSwitchCasesSequence); }); }); }); describe('variant #2: 5 simple statements inside while loop without break or continue statement', () => { let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/input-2.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1, unicodeEscapeSequence: false } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); describe('`console.log` statements', ()=> { const statementRegExp1: RegExp = getStatementRegExp('0x1'); const statementRegExp2: RegExp = getStatementRegExp('0x2'); const statementRegExp3: RegExp = getStatementRegExp('0x3'); const statementRegExp4: RegExp = getStatementRegExp('0x4'); const statementRegExp5: RegExp = getStatementRegExp('0x5'); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp1); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp2); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp3); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp4); }); it('should save statement', () => { assert.match(obfuscatedCode, statementRegExp5); }); }); describe('block statement statements', () => { const switchCaseRegExp: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/; const switchCaseLengthRegExp: RegExp = /case *'[0-5]': *console\['log'\]\(0x[0-6]\);/g; const expectedSwitchCaseLength: number = 5; let switchCaseLength: number; before(() => { switchCaseLength = obfuscatedCode.match(switchCaseLengthRegExp)!.length; }); it('should wrap block statement statements in switch-case structure', () => { assert.match(obfuscatedCode, switchCaseRegExp); }); it('each statement should be wrapped by switch-case structure', () => { assert.equal(switchCaseLength, expectedSwitchCaseLength); }); }); describe('switch-case map', () => { const switchCaseMapVariableRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6}\['.*'\]\['split'\]\('\|'\)/; const switchCaseMapStringRegExp: RegExp = /var *_0x(?:[a-f0-9]){4,6} *= *\{'.*' *: *'(.*)'\};/; const expectedSwitchCasesSequence: string[] = ['0', '1', '2', '3', '4']; let switchCaseMap: string[]; before(() => { const switchCaseMapMatch: string = getRegExpMatch(obfuscatedCode, switchCaseMapStringRegExp); switchCaseMap = switchCaseMapMatch.split('|').sort(); }); it('should create switch-case map variable', () => { assert.match(obfuscatedCode, switchCaseMapVariableRegExp); }); it('should create valid switch-case map variable with order of switch cases sequence', () => { assert.deepEqual(switchCaseMap, expectedSwitchCasesSequence); }); }); }); describe('variant #3: statements length less then 5 statements', () => { const statementRegExp: RegExp = /^\(function *\( *\) *\{ *console\['log'\]\(0x1\); *\} *\( *\) *\);$/; let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/one-statement.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); it('shouldn\'t transform block statement', () => { assert.match(obfuscatedCode, statementRegExp); }); }); describe('variant #4: block statement contain variable declaration with `const` kind', () => { const statementRegExp: RegExp = /^\(function *\( *\) *\{ *const *_0x([a-f0-9]){4,6} *= *0x1; *console\['log'\]\(0x1\);/; let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/const-declaration.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); it('shouldn\'t transform block statement', () => { assert.match(obfuscatedCode, statementRegExp); }); }); describe('variant #5: block statement contain variable declaration with `let` kind', () => { const statementRegExp: RegExp = /^\(function *\( *\) *\{ *let *_0x([a-f0-9]){4,6} *= *0x1; *console\['log'\]\(0x1\);/; let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/let-declaration.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); it('shouldn\'t transform block statement', () => { assert.match(obfuscatedCode, statementRegExp); }); }); describe('variant #6: block statement contain break statement', () => { const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *break; *console\['log'\]\(0x1\);/; let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/break-statement.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); it('shouldn\'t transform block statement', () => { assert.match(obfuscatedCode, statementRegExp); }); }); describe('variant #7: block statement contain continue statement', () => { const statementRegExp: RegExp = /^\(function *\( *\) *\{ *while *\(!!\[\]\) *\{ *continue; *console\['log'\]\(0x1\);/; let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/continue-statement.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); it('shouldn\'t transform block statement', () => { assert.match(obfuscatedCode, statementRegExp); }); }); describe('variant #8: block statement contain function declaration', () => { const statementRegExp: RegExp = /^\(function *\( *\) *\{ *function *_0x([a-f0-9]){4,6} *\( *\) *\{ *\} *console\['log'\]\(0x1\);/ let obfuscatedCode: string; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/function-declaration.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code, { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: 1 } ); obfuscatedCode = obfuscationResult.getObfuscatedCode(); }); it('shouldn\'t transform block statement', () => { assert.match(obfuscatedCode, statementRegExp); }); }); describe('variant #9: `controlFlowFlatteningThreshold` chance', () => { const samples: number = 1000; const delta: number = 0.1; const controlFlowFlatteningThreshold: number = 0.5; const regExp1: RegExp = /switch *\(_0x([a-f0-9]){4,6}\[_0x([a-f0-9]){4,6}\+\+\]\) *\{/g; const regExp2: RegExp = /\(function *\( *\) *\{ *console\['log'\]\(0x1\);/g; let transformedStatementPercentage: number, untouchedStatementPercentage: number; before(() => { const code: string = readFileAsString(__dirname + '/fixtures/input-1.js'); const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate( code.repeat(samples), { ...NO_CUSTOM_NODES_PRESET, controlFlowFlattening: true, controlFlowFlatteningThreshold: controlFlowFlatteningThreshold, } ); const transformedStatementMatchesLength: number = obfuscationResult .getObfuscatedCode() .match(regExp1)! .length; const untouchedStatementMatchesLength: number = obfuscationResult .getObfuscatedCode() .match(regExp2)! .length; transformedStatementPercentage = transformedStatementMatchesLength / samples; untouchedStatementPercentage = untouchedStatementMatchesLength / samples; }); it('should transform block statement with `controlFlowFlatteningThreshold` chance', () => { assert.closeTo(transformedStatementPercentage, controlFlowFlatteningThreshold, delta); }); it('should keep block statement with (1 - `controlFlowFlatteningThreshold`) chance', () => { assert.closeTo(untouchedStatementPercentage, controlFlowFlatteningThreshold, delta); }); }); }); });