瀏覽代碼

Added obfuscation of es2015 class names

sanex3339 7 年之前
父節點
當前提交
bbaa843739

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 ===
+v0.12.0
+---
+* Added obfuscation of `es2015` class names.
+
 v0.11.0
 ---
 * **New option:** `log` enables logging of the information to the console.

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


+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "0.11.0",
+  "version": "0.12.0-dev.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",

+ 1 - 0
src/Obfuscator.ts

@@ -66,6 +66,7 @@ export class Obfuscator implements IObfuscator {
      */
     private static readonly obfuscatingTransformersList: NodeTransformer[] = [
         NodeTransformer.CatchClauseTransformer,
+        NodeTransformer.ClassDeclarationTransformer,
         NodeTransformer.FunctionDeclarationTransformer,
         NodeTransformer.FunctionTransformer,
         NodeTransformer.LabeledStatementTransformer,

+ 5 - 0
src/container/modules/node-transformers/NodeTransformersModule.ts

@@ -11,6 +11,7 @@ import { FunctionControlFlowTransformer } from '../../../node-transformers/contr
 import { BlockStatementControlFlowTransformer } from '../../../node-transformers/control-flow-transformers/BlockStatementControlFlowTransformer';
 import { DeadCodeInjectionTransformer } from '../../../node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer';
 import { CatchClauseTransformer } from '../../../node-transformers/obfuscating-transformers/CatchClauseTransformer';
+import { ClassDeclarationTransformer } from '../../../node-transformers/obfuscating-transformers/ClassDeclarationTransformer';
 import { FunctionDeclarationTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionDeclarationTransformer';
 import { FunctionTransformer } from '../../../node-transformers/obfuscating-transformers/FunctionTransformer';
 import { LabeledStatementTransformer } from '../../../node-transformers/obfuscating-transformers/LabeledStatementTransformer';
@@ -53,6 +54,10 @@ export const nodeTransformersModule: interfaces.ContainerModule = new ContainerM
         .to(CatchClauseTransformer)
         .whenTargetNamed(NodeTransformer.CatchClauseTransformer);
 
+    bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
+        .to(ClassDeclarationTransformer)
+        .whenTargetNamed(NodeTransformer.ClassDeclarationTransformer);
+
     bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
         .to(FunctionDeclarationTransformer)
         .whenTargetNamed(NodeTransformer.FunctionDeclarationTransformer);

+ 1 - 0
src/enums/container/node-transformers/NodeTransformer.ts

@@ -1,5 +1,6 @@
 export enum NodeTransformer {
     BlockStatementControlFlowTransformer,
+    ClassDeclarationTransformer,
     DeadCodeInjectionTransformer,
     FunctionControlFlowTransformer,
     CatchClauseTransformer,

+ 146 - 0
src/node-transformers/obfuscating-transformers/ClassDeclarationTransformer.ts

@@ -0,0 +1,146 @@
+import { injectable, inject } from 'inversify';
+import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';
+
+import * as estraverse from 'estraverse';
+import * as ESTree from 'estree';
+
+import { TIdentifierObfuscatingReplacerFactory } from "../../types/container/node-transformers/TIdentifierObfuscatingReplacerFactory";
+import { TNodeWithBlockStatement } from '../../types/node/TNodeWithBlockStatement';
+
+import { IIdentifierObfuscatingReplacer } from '../../interfaces/node-transformers/obfuscating-transformers/IIdentifierObfuscatingReplacer';
+import { IOptions } from '../../interfaces/options/IOptions';
+import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
+import { IVisitor } from '../../interfaces/IVisitor';
+
+import { IdentifierObfuscatingReplacer } from "../../enums/container/node-transformers/IdentifierObfuscatingReplacer";
+import { NodeType } from '../../enums/NodeType';
+
+import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
+import { Node } from '../../node/Node';
+import { NodeUtils } from '../../node/NodeUtils';
+
+/**
+ * replaces:
+ *     class Foo { //... };
+ *     new Foo();
+ *
+ * on:
+ *     class _0x12d45f { //... };
+ *     new _0x12d45f();
+ */
+@injectable()
+export class ClassDeclarationTransformer extends AbstractNodeTransformer {
+    /**
+     * @type {IIdentifierObfuscatingReplacer}
+     */
+    private readonly identifierObfuscatingReplacer: IIdentifierObfuscatingReplacer;
+
+    /**
+     * @type {Map<ESTree.Node, ESTree.Identifier[]>}
+     */
+    private readonly replaceableIdentifiers: Map <ESTree.Node, ESTree.Identifier[]> = new Map();
+
+    /**
+     * @param {TIdentifierObfuscatingReplacerFactory} identifierObfuscatingReplacerFactory
+     * @param {IRandomGenerator} randomGenerator
+     * @param {IOptions} options
+     */
+    constructor (
+        @inject(ServiceIdentifiers.Factory__IIdentifierObfuscatingReplacer)
+            identifierObfuscatingReplacerFactory: TIdentifierObfuscatingReplacerFactory,
+        @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
+        @inject(ServiceIdentifiers.IOptions) options: IOptions
+    ) {
+        super(randomGenerator, options);
+
+        this.identifierObfuscatingReplacer = identifierObfuscatingReplacerFactory(
+            IdentifierObfuscatingReplacer.BaseIdentifierObfuscatingReplacer
+        );
+    }
+
+    /**
+     * @return {IVisitor}
+     */
+    public getVisitor (): IVisitor {
+        return {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node) => {
+                if (Node.isClassDeclarationNode(node)) {
+                    return this.transformNode(node, parentNode);
+                }
+            }
+        };
+    }
+
+    /**
+     * @param {ClassDeclaration} classDeclarationNode
+     * @param {Node} parentNode
+     * @returns {Node}
+     */
+    public transformNode (classDeclarationNode: ESTree.ClassDeclaration, parentNode: ESTree.Node): ESTree.Node {
+        const nodeIdentifier: number = this.nodeIdentifier++;
+        const blockScopeOfClassDeclarationNode: TNodeWithBlockStatement = NodeUtils
+            .getBlockScopesOfNode(classDeclarationNode)[0];
+
+        if (!this.options.renameGlobals && blockScopeOfClassDeclarationNode.type === NodeType.Program) {
+            return classDeclarationNode;
+        }
+
+        this.storeClassName(classDeclarationNode, nodeIdentifier);
+
+        // check for cached identifiers for current scope node. If exist - loop through them.
+        if (this.replaceableIdentifiers.has(blockScopeOfClassDeclarationNode)) {
+            this.replaceScopeCachedIdentifiers(blockScopeOfClassDeclarationNode, nodeIdentifier);
+        } else {
+            this.replaceScopeIdentifiers(blockScopeOfClassDeclarationNode, nodeIdentifier);
+        }
+
+        return classDeclarationNode;
+    }
+
+    /**
+     * @param {ClassDeclaration} classDeclarationNode
+     * @param {number} nodeIdentifier
+     */
+    private storeClassName (classDeclarationNode: ESTree.ClassDeclaration, nodeIdentifier: number): void {
+        this.identifierObfuscatingReplacer.storeNames(classDeclarationNode.id.name, nodeIdentifier);
+    }
+
+    /**
+     * @param {Node} scopeNode
+     * @param {number} nodeIdentifier
+     */
+    private replaceScopeCachedIdentifiers (scopeNode: ESTree.Node, nodeIdentifier: number): void {
+        const cachedReplaceableIdentifiers: ESTree.Identifier[] = <ESTree.Identifier[]>this.replaceableIdentifiers.get(scopeNode);
+
+        cachedReplaceableIdentifiers.forEach((replaceableIdentifier: ESTree.Identifier) => {
+            const newReplaceableIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer.replace(replaceableIdentifier.name, nodeIdentifier);
+
+            replaceableIdentifier.name = newReplaceableIdentifier.name;
+        });
+    }
+
+    /**
+     * @param {Node} scopeNode
+     * @param {number} nodeIdentifier
+     */
+    private replaceScopeIdentifiers (scopeNode: ESTree.Node, nodeIdentifier: number): void {
+        const storedReplaceableIdentifiers: ESTree.Identifier[] = [];
+
+        estraverse.replace(scopeNode, {
+            enter: (node: ESTree.Node, parentNode: ESTree.Node): any => {
+                if (Node.isReplaceableIdentifierNode(node, parentNode)) {
+                    const newIdentifier: ESTree.Identifier = this.identifierObfuscatingReplacer.replace(node.name, nodeIdentifier);
+                    const newIdentifierName: string = newIdentifier.name;
+
+                    if (node.name !== newIdentifierName) {
+                        node.name = newIdentifierName;
+                    } else {
+                        storedReplaceableIdentifiers.push(node);
+                    }
+                }
+            }
+        });
+
+        this.replaceableIdentifiers.set(scopeNode, storedReplaceableIdentifiers);
+    }
+}

+ 8 - 0
src/node/Node.ts

@@ -53,6 +53,14 @@ export class Node {
         return node.type === NodeType.CatchClause;
     }
 
+    /**
+     * @param {Node} node
+     * @returns {boolean}
+     */
+    public static isClassDeclarationNode (node: ESTree.Node): node is ESTree.ClassDeclaration {
+        return node.type === NodeType.ClassDeclaration;
+    }
+
     /**
      * @param {Node} node
      * @returns {boolean}

+ 98 - 0
test/functional-tests/node-transformers/obfuscating-transformers/class-declaration-transformer/ClassDeclarationTransformer.spec.ts

@@ -0,0 +1,98 @@
+import { assert } from 'chai';
+
+import { IObfuscationResult } from '../../../../../src/interfaces/IObfuscationResult';
+
+import { NO_CUSTOM_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
+
+import { getRegExpMatch } from '../../../../helpers/getRegExpMatch';
+import { readFileAsString } from '../../../../helpers/readFileAsString';
+
+import { JavaScriptObfuscator } from '../../../../../src/JavaScriptObfuscator';
+
+describe('ClassDeclarationTransformer', () => {
+    describe('transformation of `classDeclaration` node names', () => {
+        describe('variant #1: `classDeclaration` parent block scope is not a `ProgramNode`', () => {
+            const classNameIdentifierRegExp: RegExp = /class *(_0x[a-f0-9]{4,6}) *\{/;
+            const classCallIdentifierRegExp: RegExp = /new *(_0x[a-f0-9]{4,6}) *\( *\);/;
+
+            let obfuscatedCode: string,
+                classNameIdentifier: string,
+                classCallIdentifier: string;
+
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/input.js');
+                const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_CUSTOM_NODES_PRESET
+                    }
+                );
+
+                obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                classNameIdentifier = getRegExpMatch(obfuscatedCode, classNameIdentifierRegExp);
+                classCallIdentifier = getRegExpMatch(obfuscatedCode, classCallIdentifierRegExp);
+            });
+
+            it('should transform class name', () => {
+                assert.equal(classNameIdentifier, classCallIdentifier);
+            });
+        });
+
+        describe('variant #2: `classDeclaration` parent block scope is a `ProgramNode`', () => {
+            describe('variant #1: `renameGlobals` option is disabled', () => {
+                const classNameIdentifierRegExp: RegExp = /class *Foo *\{/;
+                const classCallIdentifierRegExp: RegExp = /new *Foo *\( *\);/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/parent-block-scope-is-program-node.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('match #1: shouldn\'t transform class name', () => {
+                    assert.match(obfuscatedCode, classNameIdentifierRegExp);
+                });
+
+                it('match #2: shouldn\'t transform class name', () => {
+                    assert.match(obfuscatedCode, classCallIdentifierRegExp);
+                });
+            });
+
+            describe('variant #2: `renameGlobals` option is enabled', () => {
+                const classNameIdentifierRegExp: RegExp = /class *(_0x[a-f0-9]{4,6}) *\{/;
+                const classCallIdentifierRegExp: RegExp = /new *(_0x[a-f0-9]{4,6}) *\( *\);/;
+
+                let obfuscatedCode: string;
+
+                before(() => {
+                    const code: string = readFileAsString(__dirname + '/fixtures/parent-block-scope-is-program-node.js');
+                    const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                        code,
+                        {
+                            ...NO_CUSTOM_NODES_PRESET,
+                            renameGlobals: true
+                        }
+                    );
+
+                    obfuscatedCode = obfuscationResult.getObfuscatedCode();
+                });
+
+                it('match #1: should transform class name', () => {
+                    assert.match(obfuscatedCode, classNameIdentifierRegExp);
+                });
+
+                it('match #2: should transform class name', () => {
+                    assert.match(obfuscatedCode, classCallIdentifierRegExp);
+                });
+            });
+        });
+    });
+});

+ 9 - 0
test/functional-tests/node-transformers/obfuscating-transformers/class-declaration-transformer/fixtures/input.js

@@ -0,0 +1,9 @@
+(function () {
+    class Foo {
+        getInstance () {
+            return new Foo();
+        }
+    }
+
+    new Foo();
+})();

+ 7 - 0
test/functional-tests/node-transformers/obfuscating-transformers/class-declaration-transformer/fixtures/parent-block-scope-is-program-node.js

@@ -0,0 +1,7 @@
+class Foo {
+    getInstance () {
+        return new Foo();
+    }
+}
+
+new Foo();

+ 1 - 0
test/index.spec.ts

@@ -50,6 +50,7 @@ import './functional-tests/node-transformers/converting-transformers/method-defi
 import './functional-tests/node-transformers/converting-transformers/template-literal-transformer/TemplateLiteralTransformer.spec';
 import './functional-tests/node-transformers/dead-code-injection-transformers/DeadCodeInjectionTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/catch-clause-transformer/CatchClauseTransformer.spec';
+import './functional-tests/node-transformers/obfuscating-transformers/class-declaration-transformer/ClassDeclarationTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/function-declaration-transformer/FunctionDeclarationTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/function-transformer/FunctionTransformer.spec';
 import './functional-tests/node-transformers/obfuscating-transformers/labeled-statement-transformer/LabeledStatementTransformer.spec';

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