Parcourir la source

Fixed cases when dead code is added to the inner code of `eval` expressions (#1062)

Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1053
Timofey Kachalov il y a 3 ans
Parent
commit
30ba697c55

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v3.2.7
+---
+* Fixed cases when dead code is added to the inner code of `eval` expressions. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1053
+
 v3.2.6
 ---
 * Improved integration between `renameProperties` and `controlFlowFlattening` options. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/1053

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "3.2.6",
+  "version": "3.2.7",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 8 - 0
src/declarations/ESTree.d.ts

@@ -13,6 +13,10 @@ declare module 'estree' {
         ignoredNode?: boolean;
     }
 
+    export interface FunctionExpressionNodeMetadata extends BaseNodeMetadata {
+        evalHostNode?: boolean;
+    }
+
     export interface IdentifierNodeMetadata extends BaseNodeMetadata {
         propertyKeyToRenameNode?: boolean
     }
@@ -40,6 +44,10 @@ declare module 'estree' {
         loc?: acorn.SourceLocation;
     }
 
+    interface FunctionExpression extends BaseFunction, BaseExpression {
+        metadata?: FunctionExpressionNodeMetadata;
+    }
+
     interface Program extends BaseNode {
         scope?: eslintScope.Scope | null;
     }

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

@@ -23,6 +23,7 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { BlockStatementDeadCodeInjectionNode } from '../../custom-nodes/dead-code-injection-nodes/BlockStatementDeadCodeInjectionNode';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
+import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeStatementUtils } from '../../node/NodeStatementUtils';
 import { NodeUtils } from '../../node/NodeUtils';
 
@@ -184,9 +185,18 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {BlockStatement} blockStatementNode
+     * @param {Node} parentNode
      * @returns {boolean}
      */
-    private static isValidWrappedBlockStatementNode (blockStatementNode: ESTree.BlockStatement): boolean {
+    private static isValidWrappedBlockStatementNode (blockStatementNode: ESTree.BlockStatement, parentNode: ESTree.Node): boolean {
+        /**
+         * Special case for ignoring all EvalHost nodes that are added by EvalCallExpressionTransformer
+         * So, all content of eval expressions should not be affected by dead code injection
+         */
+        if (NodeMetadata.isEvalHostNode(parentNode)) {
+            return false;
+        }
+
         if (!blockStatementNode.body.length) {
             return false;
         }
@@ -303,7 +313,7 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
 
         if (
             this.randomGenerator.getMathRandom() > this.options.deadCodeInjectionThreshold
-            || !DeadCodeInjectionTransformer.isValidWrappedBlockStatementNode(blockStatementNode)
+            || !DeadCodeInjectionTransformer.isValidWrappedBlockStatementNode(blockStatementNode, parentNode)
         ) {
             return blockStatementNode;
         }

+ 30 - 33
src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts

@@ -13,6 +13,7 @@ import { NodeTransformationStage } from '../../enums/node-transformers/NodeTrans
 import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
 import { NodeFactory } from '../../node/NodeFactory';
 import { NodeGuards } from '../../node/NodeGuards';
+import { NodeMetadata } from '../../node/NodeMetadata';
 import { NodeUtils } from '../../node/NodeUtils';
 import { StringUtils } from '../../utils/StringUtils';
 
@@ -27,11 +28,6 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
         NodeTransformer.VariablePreserveTransformer
     ];
 
-    /**
-     * @type {Set <FunctionExpression>}
-     */
-    private readonly evalRootAstHostNodeSet: Set <ESTree.FunctionExpression> = new Set();
-
     /**
      * @param {IRandomGenerator} randomGenerator
      * @param {IOptions} options
@@ -93,25 +89,16 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
             case NodeTransformationStage.Preparing:
                 return {
                     enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
-                        if (
-                            parentNode
-                            && NodeGuards.isCallExpressionNode(node)
-                            && NodeGuards.isIdentifierNode(node.callee)
-                            && node.callee.name === 'eval'
-                        ) {
+                        if (parentNode) {
                             return this.transformNode(node, parentNode);
                         }
                     }
                 };
 
             case NodeTransformationStage.Finalizing:
-                if (!this.evalRootAstHostNodeSet.size) {
-                    return null;
-                }
-
                 return {
                     leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => {
-                        if (parentNode && this.isEvalRootAstHostNode(node)) {
+                        if (parentNode) {
                             return this.restoreNode(node, parentNode);
                         }
                     }
@@ -123,22 +110,31 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
     }
 
     /**
-     * @param {CallExpression} callExpressionNode
+     * @param {Node} node
      * @param {Node} parentNode
      * @returns {Node}
      */
-    public transformNode (callExpressionNode: ESTree.CallExpression, parentNode: ESTree.Node): ESTree.Node {
-        const callExpressionFirstArgument: ESTree.Expression | ESTree.SpreadElement | undefined = callExpressionNode.arguments[0];
+    public transformNode (node: ESTree.Node, parentNode: ESTree.Node): ESTree.Node {
+        const isEvalCallExpressionNode = parentNode
+            && NodeGuards.isCallExpressionNode(node)
+            && NodeGuards.isIdentifierNode(node.callee)
+            && node.callee.name === 'eval';
+
+        if (!isEvalCallExpressionNode) {
+            return node;
+        }
+
+        const evalCallExpressionFirstArgument: ESTree.Expression | ESTree.SpreadElement | undefined = node.arguments[0];
 
-        if (!callExpressionFirstArgument) {
-            return callExpressionNode;
+        if (!evalCallExpressionFirstArgument) {
+            return node;
         }
 
         const evalString: string | null = EvalCallExpressionTransformer
-            .extractEvalStringFromCallExpressionArgument(callExpressionFirstArgument);
+            .extractEvalStringFromCallExpressionArgument(evalCallExpressionFirstArgument);
 
         if (!evalString) {
-            return callExpressionNode;
+            return node;
         }
 
         let ast: ESTree.Statement[];
@@ -147,7 +143,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
         try {
             ast = NodeUtils.convertCodeToStructure(evalString);
         } catch {
-            return callExpressionNode;
+            return node;
         }
 
         /**
@@ -157,24 +153,25 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
         const evalRootAstHostNode: ESTree.FunctionExpression = NodeFactory
             .functionExpressionNode([], NodeFactory.blockStatementNode(ast));
 
+        NodeMetadata.set(evalRootAstHostNode, { evalHostNode: true });
+
         NodeUtils.parentizeAst(evalRootAstHostNode);
         NodeUtils.parentizeNode(evalRootAstHostNode, parentNode);
 
-        /**
-         * we should store that host node and then extract AST-tree on the `finalizing` stage
-         */
-        this.evalRootAstHostNodeSet.add(evalRootAstHostNode);
-
         return evalRootAstHostNode;
     }
 
     /**
-     * @param {FunctionExpression} evalRootAstHostNode
+     * @param {Node} node
      * @param {Node} parentNode
      * @returns {Node}
      */
-    public restoreNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
-        const targetAst: ESTree.Statement[] = evalRootAstHostNode.body.body;
+    public restoreNode (node: ESTree.Node, parentNode: ESTree.Node): ESTree.Node {
+        if (!this.isEvalRootAstHostNode(node)) {
+            return node;
+        }
+
+        const targetAst: ESTree.Statement[] = node.body.body;
         const obfuscatedCode: string = NodeUtils.convertStructureToCode(targetAst);
 
         return NodeFactory.callExpressionNode(
@@ -190,6 +187,6 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
      * @returns {boolean}
      */
     private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
-        return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeSet.has(node);
+        return NodeMetadata.isEvalHostNode(node);
     }
 }

+ 8 - 0
src/node/NodeMetadata.ts

@@ -23,6 +23,14 @@ export class NodeMetadata {
             : undefined;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isEvalHostNode (node: ESTree.Node): boolean {
+        return NodeMetadata.get<ESTree.FunctionExpressionNodeMetadata, 'evalHostNode'>(node, 'evalHostNode') === true;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 29 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts

@@ -1090,5 +1090,34 @@ describe('DeadCodeInjectionTransformer', () => {
                 assert.equal(matchesLength, expectedMatchesLength);
             });
         });
+
+        describe('Variant #13 - correct integration with `EvalCallExpressionTransformer`', () => {
+            const evalWithDeadCodeRegExp: RegExp = new RegExp(
+                `eval\\(\'if *\\(${variableMatch}`,
+                'g'
+            );
+
+            let obfuscatedCode: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/eval-call-expression-transformer-integration.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        deadCodeInjection: true,
+                        deadCodeInjectionThreshold: 1,
+                        stringArray: true,
+                        stringArrayThreshold: 1
+                    }
+                ).getObfuscatedCode();
+                console.log(obfuscatedCode);
+            });
+
+            it('match #1: shouldn\'t add dead code to the eval call expression', () => {
+                assert.notMatch(obfuscatedCode, evalWithDeadCodeRegExp);
+            });
+        });
     });
 });

+ 25 - 0
test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/eval-call-expression-transformer-integration.js

@@ -0,0 +1,25 @@
+(function(){
+    if (true) {
+        var foo = function () {
+            return true;
+        };
+        var bar = function () {
+            return true;
+        };
+        var baz = function () {
+            return true;
+        };
+        var bark = function () {
+            return true;
+        };
+
+        if (true) {
+            eval('const eval = 1');
+        }
+
+        foo();
+        bar();
+        baz();
+        bark();
+    }
+})();

+ 18 - 0
test/unit-tests/node/node-metadata/NodeMetadata.spec.ts

@@ -50,6 +50,24 @@ describe('NodeMetadata', () => {
         });
     });
 
+    describe('isEvalHostNode', () => {
+        const expectedValue: boolean = true;
+
+        let node: ESTree.FunctionExpression,
+            value: boolean | undefined;
+
+        before(() => {
+            node = NodeFactory.functionExpressionNode([], NodeFactory.blockStatementNode([]));
+            node.metadata = {};
+            node.metadata.evalHostNode = true;
+            value = NodeMetadata.isEvalHostNode(node);
+        });
+
+        it('should return metadata value', () => {
+            assert.equal(value, expectedValue);
+        });
+    });
+
     describe('isForceTransformNode', () => {
         const expectedValue: boolean = true;