Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
Change Log

v2.9.6
---
* Preventing move of `"use strict";` directive during obfuscation

v2.9.5
---
* Fixed runtime errors in large obfuscated code when both `rc4` and `base64` encodings are enabled
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The example of obfuscated code: [github.com](https://github.com/javascript-obfus
* (OpenCollective) https://opencollective.com/javascript-obfuscator
* PayPal credit card [https://www.paypal.com/donate](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=javascript-obfuscator@yandex.ru&lc=US&no_note=0&item_name=Support+javascript-obfuscator&cn=&curency_code=USD&bn=PP-DonationsBF:btn_donateCC_LG.gif:NonHosted)
* PayPal https://www.paypal.me/javascriptobfuscator
* (Bitcoin) 14yhtZxLNp6ekZAgmEmPJqEKUP2VtUxQK6
* (Bitcoin) 1Nv2773RDNzodHDxuxaYkTvwBkYRHmPhnG

Huge thanks to all supporters!

Expand Down
2 changes: 1 addition & 1 deletion dist/index.browser.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.cli.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

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": "2.9.5",
"version": "2.9.6",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
Expand Down
1 change: 1 addition & 0 deletions src/JavaScriptObfuscator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator {
NodeTransformer.StringArrayScopeCallsWrapperTransformer,
NodeTransformer.StringArrayTransformer,
NodeTransformer.TemplateLiteralTransformer,
NodeTransformer.DirectivePlacementTransformer,
NodeTransformer.VariableDeclarationsMergeTransformer,
NodeTransformer.VariablePreserveTransformer
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTra

import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer';

import { DirectivePlacementTransformer } from '../../../node-transformers/finalizing-transformers/DirectivePlacementTransformer';
import { EscapeSequenceTransformer } from '../../../node-transformers/finalizing-transformers/EscapeSequenceTransformer';

export const finalizingTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
// finalizing transformers
bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
.to(DirectivePlacementTransformer)
.whenTargetNamed(NodeTransformer.DirectivePlacementTransformer);

bind<INodeTransformer>(ServiceIdentifiers.INodeTransformer)
.to(EscapeSequenceTransformer)
.whenTargetNamed(NodeTransformer.EscapeSequenceTransformer);
Expand Down
47 changes: 37 additions & 10 deletions src/declarations/ESTree.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,67 @@ import * as escodegen from '@javascript-obfuscator/escodegen';
import * as eslintScope from 'eslint-scope';

declare module 'estree' {
/**
* Nodes metadata
*/
export interface BaseNodeMetadata {
forceTransformNode?: boolean;
ignoredNode?: boolean;
}

export interface Comment {
start: number;
end: number;
loc?: acorn.SourceLocation;
export interface FunctionNodeMetadata extends BaseNodeMetadata {
directiveNode?: Directive | null;
}

export interface LiteralNodeMetadata extends BaseNodeMetadata {
replacedLiteral?: boolean;
}

export interface ProgramNodeMetadata extends BaseNodeMetadata {
directiveNode?: Directive | null;
}

/**
* Nodes
*/
interface ArrowFunctionExpression extends BaseNode {
metadata?: FunctionNodeMetadata;
}

interface BaseNode {
metadata?: BaseNodeMetadata;
parentNode?: Node;
}

interface BigIntLiteral extends SimpleLiteral {
bigint: string;
}

export interface Comment {
start: number;
end: number;
loc?: acorn.SourceLocation;
}

interface FunctionExpression extends BaseNode {
metadata?: FunctionNodeMetadata;
}

interface FunctionDeclaration extends BaseNode {
metadata?: FunctionNodeMetadata;
}

interface Program extends BaseNode {
metadata?: ProgramNodeMetadata;
scope?: eslintScope.Scope | null;
}

interface SimpleLiteral extends BaseNode {
interface RegExpLiteral extends BaseNode {
metadata?: LiteralNodeMetadata;
'x-verbatim-property'?: escodegen.XVerbatimProperty;
}

interface BigIntLiteral extends SimpleLiteral {
bigint: string;
}

interface RegExpLiteral extends BaseNode {
interface SimpleLiteral extends BaseNode {
metadata?: LiteralNodeMetadata;
'x-verbatim-property'?: escodegen.XVerbatimProperty;
}
Expand Down
1 change: 1 addition & 0 deletions src/enums/node-transformers/NodeTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum NodeTransformer {
CommentsTransformer = 'CommentsTransformer',
CustomCodeHelpersTransformer = 'CustomCodeHelpersTransformer',
DeadCodeInjectionTransformer = 'DeadCodeInjectionTransformer',
DirectivePlacementTransformer = 'DirectivePlacementTransformer',
EscapeSequenceTransformer = 'EscapeSequenceTransformer',
EvalCallExpressionTransformer = 'EvalCallExpressionTransformer',
ExportSpecifierTransformer = 'ExportSpecifierTransformer',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { inject, injectable, } from 'inversify';
import { ServiceIdentifiers } from '../../container/ServiceIdentifiers';

import * as estraverse from 'estraverse';
import * as ESTree from 'estree';

import { TNodeWithLexicalScopeStatements } from '../../types/node/TNodeWithLexicalScopeStatements';

import { IOptions } from '../../interfaces/options/IOptions';
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';

import { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage';
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';

import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
import { NodeAppender } from '../../node/NodeAppender';
import { NodeGuards } from '../../node/NodeGuards';
import { NodeMetadata } from '../../node/NodeMetadata';
import { NodeUtils } from '../../node/NodeUtils';

/**
* It's easier to fix "use strict"; placement after obfuscation as a separate stage
* than ignore this directive in other transformers like control flow and dead code injection transformers
*/
@injectable()
export class DirectivePlacementTransformer extends AbstractNodeTransformer {
/**
* @type {NodeTransformer[]}
*/
public readonly runAfter: NodeTransformer[] = [
NodeTransformer.CustomCodeHelpersTransformer
];

/**
* @param {IRandomGenerator} randomGenerator
* @param {IOptions} options
*/
public constructor (
@inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
@inject(ServiceIdentifiers.IOptions) options: IOptions,
) {
super(randomGenerator, options);
}

/**
* @param {NodeTransformationStage} nodeTransformationStage
* @returns {IVisitor | null}
*/
public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null {
switch (nodeTransformationStage) {
case NodeTransformationStage.Preparing:
return {
enter: (
node: ESTree.Node,
parentNode: ESTree.Node | null
): ESTree.Node | estraverse.VisitorOption | undefined => {
if (parentNode && NodeGuards.isNodeWithLexicalScopeStatements(node, parentNode)) {
return this.analyzeNode(node, parentNode);
}
}
};

case NodeTransformationStage.Finalizing:
return {
enter: (
node: ESTree.Node,
parentNode: ESTree.Node | null
): ESTree.Node | estraverse.VisitorOption | undefined => {
if (parentNode && NodeGuards.isNodeWithLexicalScopeStatements(node, parentNode)) {
return this.transformNode(node, parentNode);
}
}
};

default:
return null;
}
}

/**
* @param {TNodeWithLexicalScopeStatements} nodeWithLexicalScopeStatements
* @param {Node} parentNode
* @returns {TNodeWithLexicalScopeStatements}
*/
public analyzeNode (
nodeWithLexicalScopeStatements: TNodeWithLexicalScopeStatements,
parentNode: ESTree.Node
): TNodeWithLexicalScopeStatements {
if (!NodeGuards.isNodeWithLexicalScope(parentNode)) {
return nodeWithLexicalScopeStatements;
}

const firstStatementNode = nodeWithLexicalScopeStatements.body[0] ?? null;

if (firstStatementNode && NodeGuards.isDirectiveNode(firstStatementNode)) {
NodeMetadata.set(parentNode, {directiveNode: firstStatementNode});
}

return nodeWithLexicalScopeStatements;
}

/**
* @param {TNodeWithLexicalScopeStatements} nodeWithLexicalScopeStatements
* @param {Node | null} parentNode
* @returns {TNodeWithLexicalScope}
*/
public transformNode (
nodeWithLexicalScopeStatements: TNodeWithLexicalScopeStatements,
parentNode: ESTree.Node
): TNodeWithLexicalScopeStatements {
if (!NodeGuards.isNodeWithLexicalScope(parentNode)) {
return nodeWithLexicalScopeStatements;
}

const directiveNode: ESTree.Directive | null | undefined = NodeMetadata.getDirectiveNode(parentNode);

if (directiveNode) {
const newDirectiveNode: ESTree.Directive = NodeUtils.clone(directiveNode);

// append new directive node at the top of lexical scope statements
NodeAppender.prepend(nodeWithLexicalScopeStatements, [newDirectiveNode]);

// remove found directive node
let isDirectiveNodeRemoved: boolean = false;
estraverse.replace(nodeWithLexicalScopeStatements, {
enter: (node: ESTree.Node): estraverse.VisitorOption | undefined => {
if (isDirectiveNodeRemoved) {
return estraverse.VisitorOption.Break;
}

if (node === directiveNode) {
isDirectiveNodeRemoved = true;

return estraverse.VisitorOption.Remove;
}
}
});
}

return nodeWithLexicalScopeStatements;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class BlackListObfuscatingGuard implements IObfuscatingGuard {
* @type {((node: Node) => boolean)[]}
*/
private static readonly blackListGuards: ((node: ESTree.Node) => boolean)[] = [
NodeGuards.isUseStrictOperator
NodeGuards.isDirectiveNode
];

/**
Expand Down
9 changes: 0 additions & 9 deletions src/node/NodeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,15 +422,6 @@ export class NodeGuards {
return node.type === NodeType.UnaryExpression;
}

/**
* @param {Node} node
* @returns {boolean}
*/
public static isUseStrictOperator (node: ESTree.Node): node is ESTree.Directive {
return NodeGuards.isDirectiveNode(node)
&& node.directive === 'use strict';
}

/**
* @param {Node} node
* @returns {boolean}
Expand Down
24 changes: 20 additions & 4 deletions src/node/NodeMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as ESTree from 'estree';
import { TNodeWithLexicalScope } from '../types/node/TNodeWithLexicalScope';

export class NodeMetadata {
/**
Expand All @@ -14,7 +15,10 @@ export class NodeMetadata {
* @param {keyof T} metadataKey
* @returns {T[keyof T] | undefined}
*/
public static get <T extends ESTree.BaseNodeMetadata> (node: ESTree.Node, metadataKey: keyof T): T[keyof T] | undefined {
public static get <
T extends ESTree.BaseNodeMetadata,
TMetadataKey extends keyof T
> (node: ESTree.Node, metadataKey: TMetadataKey): T[TMetadataKey] | undefined {
return node.metadata !== undefined
? (<T>node.metadata)[metadataKey]
: undefined;
Expand All @@ -25,22 +29,34 @@ export class NodeMetadata {
* @returns {boolean}
*/
public static isForceTransformNode (node: ESTree.Node): boolean {
return NodeMetadata.get(node, 'forceTransformNode') === true;
return NodeMetadata.get<ESTree.BaseNodeMetadata, 'forceTransformNode'>(node, 'forceTransformNode') === true;
}

/**
* @param {Node} node
* @returns {boolean}
*/
public static isIgnoredNode (node: ESTree.Node): boolean {
return NodeMetadata.get(node, 'ignoredNode') === true;
return NodeMetadata.get<ESTree.BaseNodeMetadata, 'ignoredNode'>(node, 'ignoredNode') === true;
}

/**
* @param {Node} literalNode
* @returns {boolean}
*/
public static isReplacedLiteral (literalNode: ESTree.Literal): boolean {
return NodeMetadata.get<ESTree.LiteralNodeMetadata>(literalNode, 'replacedLiteral') === true;
return NodeMetadata.get<ESTree.LiteralNodeMetadata, 'replacedLiteral'>(literalNode, 'replacedLiteral') === true;
}

/**
* @param {TNodeWithLexicalScope} nodeWithLexicalScope
* @returns {Directive | null | undefined}
*/
public static getDirectiveNode (nodeWithLexicalScope: TNodeWithLexicalScope): ESTree.Directive | null | undefined {
return NodeMetadata.get<
ESTree.ProgramNodeMetadata
| ESTree.FunctionNodeMetadata,
'directiveNode'
>(nodeWithLexicalScope, 'directiveNode');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ describe('SplitStringTransformer', () => {
describe('Variant #10: string with emoji', () => {
describe('Variant #1: single emoji', () => {
it('should correctly split string with emoji', () => {
const regExp: RegExp = /^'a' *\+ *'b' *\+ *'👋🏼' *\+ *'c' *\+ *'d';$/;
const regExp: RegExp = /^var test *= *'a' *\+ *'b' *\+ *'👋🏼' *\+ *'c' *\+ *'d'; *test;$/;

const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');

Expand Down Expand Up @@ -206,7 +206,7 @@ describe('SplitStringTransformer', () => {

describe('Variant #2: multiple emoji', () => {
it('should correctly split string with emoji', () => {
const regExp: RegExp = /^'a' *\+ *'b' *\+ *'😴' *\+ *'😄' *\+ *'c' *\+ *'d';$/;
const regExp: RegExp = /^var test *= *'a' *\+ *'b' *\+ *'😴' *\+ *'😄' *\+ *'c' *\+ *'d'; *test;$/;

const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-2.js');

Expand Down Expand Up @@ -244,7 +244,7 @@ describe('SplitStringTransformer', () => {

describe('Variant #3: correct split emoji', () => {
it('should correctly split string with emoji', () => {
const regExp: RegExp = /^'ab👋🏼' *\+ *'cd';$/;
const regExp: RegExp = /^var test *= *'ab👋🏼' *\+ *'cd'; *test;$/;

const code: string = readFileAsString(__dirname + '/fixtures/string-with-emoji-1.js');

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
'ab👋🏼cd';
var test = 'ab👋🏼cd';
test;
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
'ab😴😄cd';
var test = 'ab😴😄cd';
test;
Loading