diff --git a/CHANGELOG.md b/CHANGELOG.md index baedc626..ef3cad90 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/package.json b/package.json index d7a40196..0b635739 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "javascript-obfuscator", - "version": "3.2.6", + "version": "3.2.7", "description": "JavaScript obfuscator", "keywords": [ "obfuscator", diff --git a/src/declarations/ESTree.d.ts b/src/declarations/ESTree.d.ts index b11c2b82..2b4bed81 100644 --- a/src/declarations/ESTree.d.ts +++ b/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; } diff --git a/src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts b/src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts index abf6b85d..d1fd7bb1 100644 --- a/src/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.ts +++ b/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; } diff --git a/src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts b/src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts index babab53c..f762fe44 100644 --- a/src/node-transformers/preparing-transformers/EvalCallExpressionTransformer.ts +++ b/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 } - */ - private readonly evalRootAstHostNodeSet: Set = 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); } } diff --git a/src/node/NodeMetadata.ts b/src/node/NodeMetadata.ts index d3c57fb1..aa93c85c 100644 --- a/src/node/NodeMetadata.ts +++ b/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(node, 'evalHostNode') === true; + } + /** * @param {Node} node * @returns {boolean} diff --git a/test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts b/test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts index 239f152a..8024e4b1 100644 --- a/test/functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec.ts +++ b/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); + }); + }); }); }); diff --git a/test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/eval-call-expression-transformer-integration.js b/test/functional-tests/node-transformers/dead-code-injection-transformers/fixtures/eval-call-expression-transformer-integration.js new file mode 100644 index 00000000..ad61ed11 --- /dev/null +++ b/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(); + } +})(); \ No newline at end of file diff --git a/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts b/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts index 2ce34c5d..b508077b 100644 --- a/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts +++ b/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;