Browse Source

Merge pull request #482 from javascript-obfuscator/assignment-without-declaration-fix

Added logic to skip variable declaration transformation for object destructing without declaration
Timofey Kachalov 5 years ago
parent
commit
df70655ef9

+ 1 - 0
CHANGELOG.md

@@ -6,6 +6,7 @@ v0.21.0
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/406
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/387
 * Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/333
+* Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/328
 
 v0.20.4
 ---

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


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


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


+ 119 - 9
src/node-transformers/obfuscating-transformers/VariableDeclarationTransformer.ts

@@ -41,6 +41,11 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
      */
     private readonly identifierObfuscatingReplacer: IIdentifierObfuscatingReplacer;
 
+    /**
+     * @type {Map<TNodeWithLexicalScope, boolean>}
+     */
+    private readonly lexicalScopesWithObjectPatternWithoutDeclarationMap: Map<TNodeWithLexicalScope, boolean> = new Map();
+
     /**
      * @type {TReplaceableIdentifiers}
      */
@@ -134,13 +139,115 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
         lexicalScopeNode: TNodeWithLexicalScope,
         isGlobalDeclaration: boolean
     ): void {
-        this.traverseDeclarationIdentifiers(variableDeclarationNode, (identifierNode: ESTree.Identifier) => {
-            if (isGlobalDeclaration) {
-                this.identifierObfuscatingReplacer.storeGlobalName(identifierNode.name, lexicalScopeNode);
-            } else {
-                this.identifierObfuscatingReplacer.storeLocalName(identifierNode.name, lexicalScopeNode);
+        this.traverseDeclarationIdentifiers(
+            variableDeclarationNode,
+            (identifierNode: ESTree.Identifier) => {
+                if (
+                    this.isProhibitedVariableName(
+                        identifierNode,
+                        lexicalScopeNode,
+                        variableDeclarationNode
+                    )
+                ) {
+                    return;
+                }
+
+                if (isGlobalDeclaration) {
+                    this.identifierObfuscatingReplacer.storeGlobalName(identifierNode.name, lexicalScopeNode);
+                } else {
+                    this.identifierObfuscatingReplacer.storeLocalName(identifierNode.name, lexicalScopeNode);
+                }
+            }
+        );
+    }
+
+    /**
+     * @param {Identifier} identifierNode
+     * @param {TNodeWithLexicalScope} lexicalScopeNode
+     * @param {VariableDeclaration} hostVariableDeclarationNode
+     * @returns {boolean}
+     */
+    private isProhibitedVariableName (
+        identifierNode: ESTree.Identifier,
+        lexicalScopeNode: TNodeWithLexicalScope,
+        hostVariableDeclarationNode: ESTree.VariableDeclaration
+    ): boolean {
+        return this.isProhibitedVariableNameUsedInObjectPatternNode(
+            identifierNode,
+            lexicalScopeNode,
+            hostVariableDeclarationNode
+        );
+    }
+
+    /**
+     * Should not rename identifiers that used inside destructing assignment without declaration
+     *
+     * var a, b; // should not be renamed
+     * ({a, b} = {a: 1, b: 2});
+     *
+     * @param {Identifier} identifierNode
+     * @param {TNodeWithLexicalScope} lexicalScopeNode
+     * @param {VariableDeclaration} hostVariableDeclarationNode
+     * @returns {boolean}
+     */
+    private isProhibitedVariableNameUsedInObjectPatternNode (
+        identifierNode: ESTree.Identifier,
+        lexicalScopeNode: TNodeWithLexicalScope,
+        hostVariableDeclarationNode: ESTree.VariableDeclaration
+    ): boolean {
+        // should transform variable declarations that cannot be reassigned
+        if (hostVariableDeclarationNode.kind === 'const') {
+            return false;
+        }
+
+        let isLexicalScopeHasObjectPatternWithoutDeclaration: boolean | undefined =
+            this.lexicalScopesWithObjectPatternWithoutDeclarationMap.get(lexicalScopeNode);
+
+        // lexical scope was traversed before and object pattern without declaration was not found
+        if (isLexicalScopeHasObjectPatternWithoutDeclaration === false) {
+            return false;
+        }
+
+        let isProhibitedVariableDeclaration: boolean = false;
+
+        estraverse.traverse(lexicalScopeNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node | null): void | estraverse.VisitorOption => {
+                if (
+                    NodeGuards.isObjectPatternNode(node)
+                    && parentNode
+                    && NodeGuards.isAssignmentExpressionNode(parentNode)
+                ) {
+                    isLexicalScopeHasObjectPatternWithoutDeclaration = true;
+
+                    const properties: ESTree.Property[] = node.properties;
+
+                    for (const property of properties) {
+                        if (property.computed || !property.shorthand) {
+                            continue;
+                        }
+
+                        if (!NodeGuards.isIdentifierNode(property.key)) {
+                            continue;
+                        }
+
+                        if (identifierNode.name !== property.key.name) {
+                            continue;
+                        }
+
+                        isProhibitedVariableDeclaration = true;
+
+                        return estraverse.VisitorOption.Break;
+                    }
+                }
             }
         });
+
+        this.lexicalScopesWithObjectPatternWithoutDeclarationMap.set(
+            lexicalScopeNode,
+            isLexicalScopeHasObjectPatternWithoutDeclaration ?? false
+        );
+
+        return isProhibitedVariableDeclaration;
     }
 
     /**
@@ -229,18 +336,21 @@ export class VariableDeclarationTransformer extends AbstractNodeTransformer {
      */
     private traverseDeclarationIdentifiers (
         variableDeclarationNode: ESTree.VariableDeclaration,
-        callback: (identifier: ESTree.Identifier) => void
+        callback: (
+            identifier: ESTree.Identifier,
+            variableDeclarator: ESTree.VariableDeclarator
+        ) => void
     ): void {
         variableDeclarationNode.declarations
-            .forEach((declarationNode: ESTree.VariableDeclarator) => {
-                estraverse.traverse(declarationNode.id, {
+            .forEach((variableDeclaratorNode: ESTree.VariableDeclarator) => {
+                estraverse.traverse(variableDeclaratorNode.id, {
                     enter: (node: ESTree.Node) => {
                         if (NodeGuards.isPropertyNode(node)) {
                             return estraverse.VisitorOption.Skip;
                         }
 
                         if (NodeGuards.isIdentifierNode(node)) {
-                            callback(node);
+                            callback(node, variableDeclaratorNode);
                         }
                     }
                 });

+ 8 - 0
src/node/NodeGuards.ts

@@ -32,6 +32,14 @@ export class NodeGuards {
         return node.type === NodeType.ArrowFunctionExpression;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isAssignmentExpressionNode (node: ESTree.Node): node is ESTree.AssignmentExpression {
+        return node.type === NodeType.AssignmentExpression;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 6 - 5
test/dev/dev.ts

@@ -7,11 +7,12 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            function foo () {
-                var bar;
-                if (false)
-                    bar = {baz: 1};
-            }
+            (function () {
+                var a = 'a', b = 'b';
+                ({[a]: a, [b]: b} = {a: 1, b: 2});
+            
+                console.log(a, b);
+            })();
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,

+ 72 - 0
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/VariableDeclarationTransformer.spec.ts

@@ -605,4 +605,76 @@ describe('VariableDeclarationTransformer', () => {
             assert.match(obfuscatedCode, objectRestRegExp);
         });
     });
+
+    describe('Variant #18: destructing assignment without declaration #1', () => {
+        const variablesDeclaration: RegExp = /var *a, *b, *_0x[a-f0-9]{4,6};/;
+        const destructingAssignmentRegExp: RegExp = /\({ *a, *b *} *= *{ *'a' *: *0x1, *'b' *: *0x2 *}\);/;
+        const identifierAssignmentRegExp: RegExp = /_0x[a-f0-9]{4,6} *= *0x3;/;
+        const variablesUsageRegExp: RegExp = /console\['log']\(a, *b, *_0x[a-f0-9]{4,6}\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/destructing-assignment-without-declaration-1.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('match #1: should transform variables declaration', () => {
+            assert.match(obfuscatedCode, variablesDeclaration);
+        });
+
+        it('match #2: should transform destructing assignment without declaration', () => {
+            assert.match(obfuscatedCode, destructingAssignmentRegExp);
+        });
+
+        it('match #3: should transform identifier assignment', () => {
+            assert.match(obfuscatedCode, identifierAssignmentRegExp);
+        });
+
+        it('match #4: should transform variables usage', () => {
+            assert.match(obfuscatedCode, variablesUsageRegExp);
+        });
+    });
+
+    describe('Variant #19: destructing assignment without declaration #2', () => {
+        const variablesDeclaration: RegExp = /var *_0x[a-f0-9]{4,6} *= *'a', *_0x[a-f0-9]{4,6} *= *'b', *_0x[a-f0-9]{4,6};/;
+        const destructingAssignmentRegExp: RegExp = /\({ *\[_0x[a-f0-9]{4,6}]: *_0x[a-f0-9]{4,6}, *\[_0x[a-f0-9]{4,6}]: *_0x[a-f0-9]{4,6} *} *= *{ *'a' *: *0x1, *'b' *: *0x2 *}\);/;
+        const identifierAssignmentRegExp: RegExp = /_0x[a-f0-9]{4,6} *= *0x3;/;
+        const variablesUsageRegExp: RegExp = /console\['log']\(_0x[a-f0-9]{4,6}, *_0x[a-f0-9]{4,6}, *_0x[a-f0-9]{4,6}\);/;
+
+        let obfuscatedCode: string;
+
+        before(() => {
+            const code: string = readFileAsString(__dirname + '/fixtures/destructing-assignment-without-declaration-2.js');
+
+            obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                code,
+                {
+                    ...NO_ADDITIONAL_NODES_PRESET
+                }
+            ).getObfuscatedCode();
+        });
+
+        it('match #1: should transform variables declaration', () => {
+            assert.match(obfuscatedCode, variablesDeclaration);
+        });
+
+        it('match #2: should transform destructing assignment without declaration', () => {
+            assert.match(obfuscatedCode, destructingAssignmentRegExp);
+        });
+
+        it('match #3: should transform identifier assignment', () => {
+            assert.match(obfuscatedCode, identifierAssignmentRegExp);
+        });
+
+        it('match #4: should transform variables usage', () => {
+            assert.match(obfuscatedCode, variablesUsageRegExp);
+        });
+    });
 });

+ 7 - 0
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/destructing-assignment-without-declaration-1.js

@@ -0,0 +1,7 @@
+(function () {
+    var a, b, c;
+    ({a, b} = {a: 1, b: 2});
+    c = 3;
+
+    console.log(a, b, c);
+})();

+ 7 - 0
test/functional-tests/node-transformers/obfuscating-transformers/variable-declaration-transformer/fixtures/destructing-assignment-without-declaration-2.js

@@ -0,0 +1,7 @@
+(function () {
+    var a = 'a', b = 'b', c;
+    ({[a]: a, [b]: b} = {a: 1, b: 2});
+    c = 3;
+
+    console.log(a, b, c);
+})();

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