瀏覽代碼

Fixed #123 and #125, added tests for debug protection template

sanex3339 7 年之前
父節點
當前提交
e52fb4c84c

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 ===
+v0.12.3
+---
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/125 (dead code injection and await expression)
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/123
+
 v0.12.2
 ---
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/121

File diff suppressed because it is too large
+ 0 - 0
dist/index.js


+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.12.2",
+  "version": "0.12.3",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -62,6 +62,7 @@
     "mocha": "4.0.1",
     "pre-commit": "1.2.2",
     "sinon": "4.1.2",
+    "threads": "^0.8.1",
     "ts-node": "3.3.0",
     "tslint": "5.8.0",
     "tslint-eslint-rules": "4.1.1",

+ 15 - 0
src/declarations/threads.d.ts

@@ -0,0 +1,15 @@
+declare module 'threads' {
+    type PostMessage <U> = (data: U) => void;
+    type SpawnCallback <T, U> = (data: T, postMessage: PostMessage <U>) => void;
+    type ResponseCallback <U> = (response: U) => void;
+
+    class Thread <T, U> {
+        public killed: boolean;
+
+        public send (data: T): Thread <T, U>;
+        public on (eventType: string, responseCallback: ResponseCallback<U>): Thread <T, U>;
+        public kill (): void;
+    }
+
+    export function spawn <T, U> (spawnCallback: SpawnCallback <T, U>): Thread <T, U>;
+}

+ 1 - 0
src/enums/node/NodeType.ts

@@ -3,6 +3,7 @@ export enum NodeType {
     ArrowFunctionExpression = 'ArrowFunctionExpression',
     AssignmentExpression = 'AssignmentExpression',
     AssignmentPattern = 'AssignmentPattern',
+    AwaitExpression = 'AwaitExpression',
     BinaryExpression = 'BinaryExpression',
     BlockStatement = 'BlockStatement',
     BreakStatement = 'BreakStatement',

+ 2 - 1
src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts

@@ -174,7 +174,8 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
                 if (
                     nestedBlockStatementsCount > DeadCodeInjectionTransformer.maxNestedBlockStatementsCount ||
                     NodeGuards.isBreakStatementNode(node) ||
-                    NodeGuards.isContinueStatementNode(node)
+                    NodeGuards.isContinueStatementNode(node) ||
+                    NodeGuards.isAwaitExpressionNode(node)
                 ) {
                     isValidBlockStatementNode = false;
 

+ 8 - 0
src/node/NodeGuards.ts

@@ -21,6 +21,14 @@ export class NodeGuards {
         return node.type === NodeType.AssignmentPattern;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isAwaitExpressionNode (node: ESTree.Node): node is ESTree.AwaitExpression {
+        return node.type === NodeType.AwaitExpression;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 1 - 1
src/templates/debug-protection-nodes/debug-protection-function-call-node/DebufProtectionFunctionCallTemplate.ts

@@ -5,7 +5,7 @@ export function DebugProtectionFunctionCallTemplate (): string {
     return `
         (function () {
             var regExp1 = new RegExp('function *\\\\( *\\\\)');
-            var regExp2 = new RegExp('\\\\+\\\\+ *_0x([a-f0-9]){4,6}');
+            var regExp2 = new RegExp('\\\\+\\\\+ *\\(?:_0x([a-f0-9]){4,6}|\\\\b[a-zA-Z]{1,2}\\\\b\\)');
             var result = {debugProtectionFunctionName}('init');
             
             if (!regExp1.test(result + 'chain') || !regExp2.test(result + 'input')) {

+ 56 - 3
test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts

@@ -178,7 +178,60 @@ describe('DeadCodeInjectionTransformer', () => {
             });
         });
 
-        describe('variant #5 - chance of `IfStatement` variant', () => {
+        describe('variant #5 - await expression in block statement', () => {
+            const functionRegExp: RegExp = new RegExp(
+                `var *${variableMatch} *= *function *\\(\\) *\\{` +
+                    `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
+                `\\};`,
+                'g'
+            );
+            const awaitExpressionRegExp: RegExp = new RegExp(
+                `await *${variableMatch}\\(\\)`,
+                'g'
+            );
+            const expectedFunctionMatchesLength: number = 4;
+            const expectedAwaitExpressionMatchesLength: number = 1;
+
+            let functionMatchesLength: number = 0,
+                awaitExpressionMatchesLength: number = 0;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/await-expression.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                );
+                const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
+                const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
+                const awaitExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(awaitExpressionRegExp);
+
+                console.log(obfuscatedCode);
+
+                if (functionMatches) {
+                    functionMatchesLength = functionMatches.length;
+                }
+
+                if (awaitExpressionMatches) {
+                    awaitExpressionMatchesLength = awaitExpressionMatches.length;
+                }
+            });
+
+            it('match #1: shouldn\'t add dead code', () => {
+                assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
+            });
+
+            it('match #2: shouldn\'t add dead code', () => {
+                assert.equal(awaitExpressionMatchesLength, expectedAwaitExpressionMatchesLength);
+            });
+        });
+
+        describe('variant #6 - chance of `IfStatement` variant', () => {
             const samplesCount: number = 1000;
             const delta: number = 0.1;
             const expectedDistribution: number = 0.25;
@@ -280,7 +333,7 @@ describe('DeadCodeInjectionTransformer', () => {
             });
         });
 
-        describe('variant #6 - block scope of block statement is `ProgramNode`', () => {
+        describe('variant #7 - block scope of block statement is `ProgramNode`', () => {
             const regExp: RegExp = new RegExp(
                 `if *\\(!!\\[\\]\\) *{` +
                     `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
@@ -310,7 +363,7 @@ describe('DeadCodeInjectionTransformer', () => {
             });
         });
 
-        describe('variant #7 - correct obfuscation of dead-code block statements', () => {
+        describe('variant #8 - correct obfuscation of dead-code block statements', () => {
             const variableName: string = 'importantVariableName';
 
             let obfuscatedCode: string;

+ 25 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/await-expression.js

@@ -0,0 +1,25 @@
+(async function(){
+    if (true) {
+        var foo = function () {
+            console.log('abc');
+        };
+        var bar = function () {
+            console.log('def');
+        };
+        var baz = function () {
+            console.log('ghi');
+        };
+        var bark = function () {
+            console.log('jkl');
+        };
+
+        if (true) {
+            await foo();
+        }
+
+        foo();
+        bar();
+        baz();
+        bark();
+    }
+})();

+ 138 - 0
test/functional-tests/templates/custom-nodes/debug-protection-nodes/DebufProtectionFunctionCallTemplate.spec.ts

@@ -0,0 +1,138 @@
+import { assert } from 'chai';
+import { spawn } from 'threads';
+
+import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
+
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscatorFacade';
+
+function spawnThread(inputCallback: Function, threadCallback: Function, timeoutCallback: Function): void {
+    const thread = spawn<string, number>((input: string, postMessage: Function) => {
+        postMessage(eval(input));
+    });
+
+    const timeout = setTimeout(() => {
+        thread.kill();
+        timeoutCallback();
+    }, 500);
+
+    thread
+        .send(inputCallback())
+        .on('message', (response: number) => {
+            clearTimeout(timeout);
+            thread.kill();
+            threadCallback(response);
+        });
+}
+
+describe('DebugProtectionFunctionCallTemplate (): string', () => {
+    describe('variant #1: correctly obfuscated code`', () => {
+        const expectedEvaluationResult: number = 1;
+
+        let obfuscatedCode: string,
+            evaluationResult: number = 0;
+
+        beforeEach((done) => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    debugProtection: true
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+            spawnThread(
+                () => obfuscatedCode,
+                (response: number) => {
+                    evaluationResult = response;
+                    done();
+                },
+                () => {
+                    done();
+                }
+            );
+        });
+
+        it('should correctly evaluate code with enabled debug protection', () => {
+            assert.equal(evaluationResult, expectedEvaluationResult);
+        });
+    });
+
+    describe('variant #2: correctly obfuscated code with enabled `mangle` option', () => {
+        const expectedEvaluationResult: number = 1;
+
+        let obfuscatedCode: string,
+            evaluationResult: number = 0;
+
+        beforeEach((done) => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    debugProtection: true,
+                    mangle: true
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+
+            spawnThread(
+                () => obfuscatedCode,
+                (response: number) => {
+                    evaluationResult = response;
+                    done();
+                },
+                () => {
+                    done();
+                }
+            );
+        });
+
+        it('should correctly evaluate code with enabled debug protection', () => {
+            assert.equal(evaluationResult, expectedEvaluationResult);
+        });
+    });
+
+    describe('variant #3: obfuscated code with removed debug protection function call', () => {
+        const expectedEvaluationResult: number = 0;
+
+        let obfuscatedCode: string,
+            evaluationResult: number = 0;
+
+        beforeEach((done) => {
+            const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+            const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_CUSTOM_NODES_PRESET,
+                    debugProtection: true
+                }
+            );
+
+            obfuscatedCode = obfuscationResult.getObfuscatedCode();
+            obfuscatedCode = obfuscatedCode.replace(/\+\+ *_0x([a-f0-9]){4,6}/, '++abc');
+
+            spawnThread(
+                () => obfuscatedCode,
+                (response: number) => {
+                    evaluationResult = response;
+                    done();
+                },
+                () => {
+                    done();
+                }
+            );
+        });
+
+        it('should enter code in infinity loop', () => {
+            assert.equal(evaluationResult, expectedEvaluationResult);
+        });
+    });
+});

+ 3 - 0
test/functional-tests/templates/custom-nodes/debug-protection-nodes/fixtures/input.js

@@ -0,0 +1,3 @@
+(function () {
+    return 1;
+})();

+ 1 - 0
test/index.spec.ts

@@ -62,6 +62,7 @@ import './functional-tests/node-transformers/obfuscating-transformers/object-exp
 import './functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec';
 import './functional-tests/node-transformers/parentizing-transformers/obfuscating-guards/black-list-obfuscating-guard/BlackListObfuscatingGuard.spec';
 import './functional-tests/node-transformers/parentizing-transformers/obfuscating-guards/conditional-comment-obfuscating-guard/ConditionalCommentObfuscatingGuard.spec';
+import './functional-tests/templates/custom-nodes/debug-protection-nodes/DebufProtectionFunctionCallTemplate.spec';
 import './functional-tests/templates/custom-nodes/domain-lock-nodes/DomainLockNodeTemplate.spec';
 import './functional-tests/templates/custom-nodes/string-array-nodes/StringArrayCallsWrapperNodeTemplate.spec';
 

+ 16 - 5
yarn.lock

@@ -52,11 +52,7 @@
   version "2.2.44"
   resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e"
 
-"@types/node@*":
-  version "8.0.47"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.47.tgz#968e596f91acd59069054558a00708c445ca30c2"
-
-"@types/[email protected]":
+"@types/node@*", "@types/[email protected]":
   version "8.0.53"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.53.tgz#396b35af826fa66aad472c8cb7b8d5e277f4e6d8"
 
@@ -1532,6 +1528,10 @@ event-emitter@~0.3.5:
     d "1"
     es5-ext "~0.10.14"
 
+eventemitter3@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-2.0.3.tgz#b5e1079b59fb5e1ba2771c0a993be060a58c99ba"
+
 events@^1.0.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
@@ -2683,6 +2683,10 @@ nanomatch@^1.2.5:
     snapdragon "^0.8.1"
     to-regex "^3.0.1"
 
+native-promise-only@^0.8.1:
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11"
+
 nise@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/nise/-/nise-1.2.0.tgz#079d6cadbbcb12ba30e38f1c999f36ad4d6baa53"
@@ -3723,6 +3727,13 @@ text-encoding@^0.6.4:
   version "0.6.4"
   resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19"
 
+threads@^0.8.1:
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/threads/-/threads-0.8.1.tgz#e340115b5947316d2f7ee3123c4c2c5bf9c76d72"
+  dependencies:
+    eventemitter3 "^2.0.2"
+    native-promise-only "^0.8.1"
+
 through@^2.3.6:
   version "2.3.8"
   resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"

Some files were not shown because too many files changed in this diff