Dead code injection refactoring #1

This commit is contained in:
sanex3339
2018-01-13 02:05:10 +03:00
parent 2950613d44
commit 43f64f73e7
8 changed files with 10031 additions and 108 deletions
+94 -54
View File
@@ -184,19 +184,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
return astTree;
}
// first pass: AST-tree preparation
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Preparing);
astTree = this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Preparing
);
astTree = this.runPreparingStage(astTree);
// second pass: AST-tree analyzing
this.logger.info(LoggingMessage.AnalyzingASTTreeStage);
const stackTraceData: IStackTraceData[] = this.stackTraceAnalyzer.analyze(astTree);
// initialize custom node groups and configure custom nodes
this.customNodeGroupStorage
.getStorage()
.forEach((customNodeGroup: ICustomNodeGroup) => {
@@ -210,51 +202,11 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
this.obfuscationEventEmitter.emit(ObfuscationEvent.BeforeObfuscation, astTree, stackTraceData);
// third pass: dead code injection transformer
if (this.options.deadCodeInjection) {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.DeadCodeInjection);
astTree = this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.DeadCodeInjection
);
}
// fourth pass: control flow flattening transformers
if (this.options.controlFlowFlattening) {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.ControlFlowFlattening);
astTree = this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.ControlFlowFlattening
);
}
// fifth pass: converting transformers
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Converting);
astTree = this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Converting
);
// sixth pass: obfuscating transformers
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Obfuscating);
astTree = this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Obfuscating
);
// seventh pass: finalizing transformers
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Finalizing);
astTree = this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Finalizing
);
astTree = this.runDeadCodeInjectionStage(astTree);
astTree = this.runControlFlowFlatteningStage(astTree);
astTree = this.runConvertingStage(astTree);
astTree = this.runObfuscatingStage(astTree);
astTree = this.runFinalizingStage(astTree);
this.obfuscationEventEmitter.emit(ObfuscationEvent.AfterObfuscation, astTree, stackTraceData);
@@ -298,4 +250,92 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
generatorOutput.map
);
}
/**
* @param {Program} astTree
* @returns {Program}
*/
private runPreparingStage (astTree: ESTree.Program): ESTree.Program {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Preparing);
return this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Preparing
);
}
/**
* @param {Program} astTree
* @returns {Program}
*/
private runDeadCodeInjectionStage (astTree: ESTree.Program): ESTree.Program {
if (this.options.deadCodeInjection) {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.DeadCodeInjection);
}
return this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.DeadCodeInjection
);
}
/**
* @param {Program} astTree
* @returns {Program}
*/
private runControlFlowFlatteningStage (astTree: ESTree.Program): ESTree.Program {
if (this.options.controlFlowFlattening) {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.ControlFlowFlattening);
}
return this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.ControlFlowFlattening
);
}
/**
* @param {Program} astTree
* @returns {Program}
*/
private runConvertingStage (astTree: ESTree.Program): ESTree.Program {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Converting);
return this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Converting
);
}
/**
* @param {Program} astTree
* @returns {Program}
*/
private runObfuscatingStage (astTree: ESTree.Program): ESTree.Program {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Obfuscating);
return this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Obfuscating
);
}
/**
* @param {Program} astTree
* @returns {Program}
*/
private runFinalizingStage (astTree: ESTree.Program): ESTree.Program {
this.logger.info(LoggingMessage.TransformationStage, TransformationStage.Finalizing);
return this.transformersRunner.transform(
astTree,
JavaScriptObfuscator.transformersList,
TransformationStage.Finalizing
);
}
}
@@ -27,7 +27,7 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
* @type {BlockStatement}
*/
@initializable()
private randomBlockStatementNode: BlockStatement;
private deadCodeInjectionRootAstHostNode: BlockStatement;
/**
* @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory
@@ -45,14 +45,14 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
/**
* @param {BlockStatement} blockStatementNode
* @param {BlockStatement} randomBlockStatementNode
* @param {BlockStatement} deadCodeInjectionRootAstHostNode
*/
public initialize (
blockStatementNode: BlockStatement,
randomBlockStatementNode: BlockStatement
deadCodeInjectionRootAstHostNode: BlockStatement
): void {
this.blockStatementNode = blockStatementNode;
this.randomBlockStatementNode = randomBlockStatementNode;
this.deadCodeInjectionRootAstHostNode = deadCodeInjectionRootAstHostNode;
}
/**
@@ -67,8 +67,8 @@ export class BlockStatementDeadCodeInjectionNode extends AbstractCustomNode {
const rightString: string = random2 ? leftString : this.randomGenerator.getRandomString(5);
const [consequent, alternate]: [BlockStatement, BlockStatement] = random1 === random2
? [this.blockStatementNode, this.randomBlockStatementNode]
: [this.randomBlockStatementNode, this.blockStatementNode];
? [this.blockStatementNode, this.deadCodeInjectionRootAstHostNode]
: [this.deadCodeInjectionRootAstHostNode, this.blockStatementNode];
const structure: BlockStatement = Nodes.getBlockStatementNode([
Nodes.getIfStatementNode(
@@ -18,6 +18,12 @@ export interface INodeTransformer {
*/
analyzeNode ? (node: ESTree.Node, parentNode: ESTree.Node | null): void;
/**
* @param {Node} node
* @param {Node | null} parentNode
*/
restoreNode ? (node: ESTree.Node, parentNode: ESTree.Node | null): void;
/**
* @param {Node} node
* @param {Node | null} parentNode
@@ -102,6 +102,10 @@ export class BlockStatementControlFlowTransformer extends AbstractNodeTransforme
* @returns {IVisitor | null}
*/
public getVisitor (transformationStage: TransformationStage): IVisitor | null {
if (!this.options.controlFlowFlattening) {
return null;
}
switch (transformationStage) {
case TransformationStage.ControlFlowFlattening:
return {
@@ -106,6 +106,10 @@ export class FunctionControlFlowTransformer extends AbstractNodeTransformer {
* @returns {IVisitor | null}
*/
public getVisitor (transformationStage: TransformationStage): IVisitor | null {
if (!this.options.controlFlowFlattening) {
return null;
}
switch (transformationStage) {
case TransformationStage.ControlFlowFlattening:
return {
@@ -10,16 +10,15 @@ import { TNodeWithBlockScope } from '../../types/node/TNodeWithBlockScope';
import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode';
import { IOptions } from '../../interfaces/options/IOptions';
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
import { ITransformersRunner } from '../../interfaces/node-transformers/ITransformersRunner';
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
import { DeadCodeInjectionCustomNode } from '../../enums/custom-nodes/DeadCodeInjectionCustomNode';
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
import { NodeType } from '../../enums/node/NodeType';
import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
import { NodeGuards } from '../../node/NodeGuards';
import { Nodes } from '../../node/Nodes';
import { NodeUtils } from '../../node/NodeUtils';
@injectable()
@@ -35,16 +34,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
private static readonly minCollectedBlockStatementsCount: number = 5;
/**
* @type {NodeTransformer[]}
* @type {WeakSet <BlockStatement>}
*/
private static readonly transformersToRenameBlockScopeIdentifiers: NodeTransformer[] = [
NodeTransformer.CatchClauseTransformer,
NodeTransformer.ClassDeclarationTransformer,
NodeTransformer.FunctionDeclarationTransformer,
NodeTransformer.FunctionTransformer,
NodeTransformer.LabeledStatementTransformer,
NodeTransformer.VariableDeclarationTransformer
];
private readonly deadCodeInjectionRootAstHostNodeSet: WeakSet <ESTree.BlockStatement> = new WeakSet();
/**
* @type {ESTree.BlockStatement[]}
@@ -61,28 +53,20 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
*/
private readonly deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory;
/**
* @type {ITransformersRunner}
*/
private readonly transformersRunner: ITransformersRunner;
/**
* @param {TControlFlowCustomNodeFactory} deadCodeInjectionCustomNodeFactory
* @param {ITransformersRunner} transformersRunner
* @param {IRandomGenerator} randomGenerator
* @param {IOptions} options
*/
constructor (
@inject(ServiceIdentifiers.Factory__IDeadCodeInjectionCustomNode)
deadCodeInjectionCustomNodeFactory: TDeadNodeInjectionCustomNodeFactory,
@inject(ServiceIdentifiers.ITransformersRunner) transformersRunner: ITransformersRunner,
@inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
@inject(ServiceIdentifiers.IOptions) options: IOptions
) {
super(randomGenerator, options);
this.deadCodeInjectionCustomNodeFactory = deadCodeInjectionCustomNodeFactory;
this.transformersRunner = transformersRunner;
}
/**
@@ -101,6 +85,10 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
* @returns {IVisitor | null}
*/
public getVisitor (transformationStage: TransformationStage): IVisitor | null {
if (!this.options.deadCodeInjection) {
return null;
}
switch (transformationStage) {
case TransformationStage.DeadCodeInjection:
return {
@@ -118,6 +106,15 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
}
};
case TransformationStage.Finalizing:
return {
enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
if (parentNode && this.isDeadCodeInjectionRootAstHostNode(node)) {
return this.restoreNode(node, parentNode);
}
}
};
default:
return null;
}
@@ -176,6 +173,21 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
return this.replaceBlockStatementNode(blockStatementNode, randomBlockStatementNode, parentNode);
}
/**
* @param {FunctionExpression} deadCodeInjectionRootAstHostNode
* @param {Node} parentNode
* @returns {Node}
*/
public restoreNode (deadCodeInjectionRootAstHostNode: ESTree.BlockStatement, parentNode: ESTree.Node): ESTree.Node {
const hostNodeFirstStatement: ESTree.Statement = deadCodeInjectionRootAstHostNode.body[0];
if (!NodeGuards.isFunctionDeclarationNode(hostNodeFirstStatement)) {
throw new Error('Wrong dead code injection root AST host node. Host node should contain `FunctionDeclaration` node');
}
return hostNodeFirstStatement.body;
}
/**
* @param {BlockStatement} blockStatementNode
* @param {BlockStatement[]} collectedBlockStatements
@@ -186,8 +198,9 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
collectedBlockStatements: ESTree.BlockStatement[],
parentNode: ESTree.Node
): void {
let clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode),
nestedBlockStatementsCount: number = 0,
const clonedBlockStatementNode: ESTree.BlockStatement = NodeUtils.clone(blockStatementNode);
let nestedBlockStatementsCount: number = 0,
isValidBlockStatementNode: boolean = true;
estraverse.replace(clonedBlockStatementNode, {
@@ -220,19 +233,17 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
return;
}
/**
* We should transform identifiers in the dead code block statement to avoid conflicts with original code
*/
NodeUtils.parentizeNode(clonedBlockStatementNode, parentNode);
clonedBlockStatementNode = this.transformersRunner.transform(
clonedBlockStatementNode,
DeadCodeInjectionTransformer.transformersToRenameBlockScopeIdentifiers,
TransformationStage.Obfuscating
);
collectedBlockStatements.push(clonedBlockStatementNode);
}
/**
* @param {Node} node
* @returns {boolean}
*/
private isDeadCodeInjectionRootAstHostNode (node: ESTree.Node): node is ESTree.BlockStatement {
return NodeGuards.isBlockStatementNode(node) && this.deadCodeInjectionRootAstHostNodeSet.has(node);
}
/**
* @param {BlockStatement} blockStatementNode
* @param {BlockStatement} randomBlockStatementNode
@@ -244,14 +255,29 @@ export class DeadCodeInjectionTransformer extends AbstractNodeTransformer {
randomBlockStatementNode: ESTree.BlockStatement,
parentNode: ESTree.Node
): ESTree.BlockStatement {
/**
* we should wrap original random block statement node into the parent block statement node (ast root host node)
* with function declaration node. This function declaration node will create block scope for all identifiers
* inside random block statement node and this identifiers won't affect identifiers of the rest AST tree.
*/
const deadCodeInjectionRootAstHostNode: ESTree.BlockStatement = Nodes.getBlockStatementNode([
Nodes.getFunctionDeclarationNode(
'foobar',
[],
randomBlockStatementNode
)
]);
/**
* we should store that host node and then extract random block statement node on the `finalizing` stage
*/
this.deadCodeInjectionRootAstHostNodeSet.add(deadCodeInjectionRootAstHostNode);
const blockStatementDeadCodeInjectionCustomNode: ICustomNode = this.deadCodeInjectionCustomNodeFactory(
DeadCodeInjectionCustomNode.BlockStatementDeadCodeInjectionNode
);
blockStatementDeadCodeInjectionCustomNode.initialize(
blockStatementNode,
randomBlockStatementNode
);
blockStatementDeadCodeInjectionCustomNode.initialize(blockStatementNode, deadCodeInjectionRootAstHostNode);
const newBlockStatementNode: ESTree.BlockStatement = <ESTree.BlockStatement>blockStatementDeadCodeInjectionCustomNode.getNode()[0];
@@ -22,7 +22,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
/**
* @type {WeakSet <FunctionExpression>}
*/
private readonly evalRootAstHostNodeStorage: WeakSet <ESTree.FunctionExpression> = new WeakSet();
private readonly evalRootAstHostNodeSet: WeakSet <ESTree.FunctionExpression> = new WeakSet();
/**
* @param {IRandomGenerator} randomGenerator
@@ -100,7 +100,7 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
return {
leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
if (parentNode && this.isEvalRootAstHostNode(node)) {
return this.restoreEvalExpressionNode(node, parentNode);
return this.restoreNode(node, parentNode);
}
}
};
@@ -138,28 +138,27 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
return callExpressionNode;
}
/**
* we should wrap AST-tree into the parent function expression node (ast root host node).
* This function expression node will help to correctly transform AST-tree.
*/
const evalRootAstHostNode: ESTree.FunctionExpression = Nodes
.getFunctionExpressionNode([], Nodes.getBlockStatementNode(<any>ast));
this.evalRootAstHostNodeStorage.add(evalRootAstHostNode);
/**
* we should store that host node and then extract AST-tree on the `finalizing` stage
*/
this.evalRootAstHostNodeSet.add(evalRootAstHostNode);
return evalRootAstHostNode;
}
/**
* @param {Node} node
* @returns {boolean}
*/
private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeStorage.has(node);
}
/**
* @param {FunctionExpression} evalRootAstHostNode
* @param {Node} parentNode
* @returns {Node}
*/
private restoreEvalExpressionNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
public restoreNode (evalRootAstHostNode: ESTree.FunctionExpression, parentNode: ESTree.Node): ESTree.Node {
const targetAst: ESTree.Statement[] = evalRootAstHostNode.body.body;
const obfuscatedCode: string = NodeUtils.convertStructureToCode(targetAst);
@@ -170,4 +169,12 @@ export class EvalCallExpressionTransformer extends AbstractNodeTransformer {
]
);
}
/**
* @param {Node} node
* @returns {boolean}
*/
private isEvalRootAstHostNode (node: ESTree.Node): node is ESTree.FunctionExpression {
return NodeGuards.isFunctionExpressionNode(node) && this.evalRootAstHostNodeSet.has(node);
}
}