Skip to content

Commit

Permalink
Fixed cases when dead code is added to the inner code of eval expre…
Browse files Browse the repository at this point in the history
…ssions (#1062)

Fixed #1053
  • Loading branch information
sanex3339 committed Feb 14, 2022
1 parent c4dae41 commit 30ba697
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 36 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "3.2.6",
"version": "3.2.7",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
Expand Down
8 changes: 8 additions & 0 deletions src/declarations/ESTree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ declare module 'estree' {
ignoredNode?: boolean;
}

export interface FunctionExpressionNodeMetadata extends BaseNodeMetadata {
evalHostNode?: boolean;
}

export interface IdentifierNodeMetadata extends BaseNodeMetadata {
propertyKeyToRenameNode?: boolean
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -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[];
Expand All @@ -147,7 +143,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
try {
ast = NodeUtils.convertCodeToStructure(evalString);
} catch {
return callExpressionNode;
return node;
}

/**
Expand All @@ -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(
Expand All @@ -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 changes: 8 additions & 0 deletions src/node/NodeMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions test/unit-tests/node/node-metadata/NodeMetadata.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit 30ba697

Please sign in to comment.