* The splitStrings option now correctly works with transformObjectKeys option

* Internal `TransformersRunner` rework to support topological sort of node transformers
This commit is contained in:
sanex3339
2019-12-30 19:46:49 +03:00
parent 4a1361c86d
commit c313802011
20 changed files with 491 additions and 40 deletions
+5
View File
@@ -1,5 +1,10 @@
Change Log
v0.19.3
---
* The `splitStrings` option now correctly works with `transformObjectKeys` option
* Internal `TransformersRunner` rework to support topological sort of node transformers
v0.19.2
---
* The `splitStrings` option now correctly splits strings inside objects
+3 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "javascript-obfuscator",
"version": "0.19.2",
"version": "0.19.3",
"description": "JavaScript obfuscator",
"keywords": [
"obfuscator",
+2
View File
@@ -23,9 +23,11 @@ export enum ServiceIdentifiers {
IIdentifierNamesGenerator = 'IIdentifierNamesGenerator',
IIdentifierObfuscatingReplacer = 'IIdentifierObfuscatingReplacer',
IJavaScriptObfuscator = 'IJavaScriptObfuscator',
ILevelledTopologicalSorter = 'ILevelledTopologicalSorter',
ILogger = 'ILogger',
INodeGuard = 'INodeGuard',
INodeTransformer = 'INodeTransformer',
INodeTransformerNamesGroupsBuilder = 'INodeTransformerNamesGroupsBuilder',
IObfuscationEventEmitter = 'IObfuscationEventEmitter',
IObfuscatedCode = 'IObfuscatedCode',
IOptions = 'IOptions',
@@ -4,11 +4,15 @@ import { ServiceIdentifiers } from '../../ServiceIdentifiers';
import { IArrayUtils } from '../../../interfaces/utils/IArrayUtils';
import { ICryptUtils } from '../../../interfaces/utils/ICryptUtils';
import { IEscapeSequenceEncoder } from '../../../interfaces/utils/IEscapeSequenceEncoder';
import { ILevelledTopologicalSorter } from '../../../interfaces/utils/ILevelledTopologicalSorter';
import { INodeTransformerNamesGroupsBuilder } from '../../../interfaces/utils/INodeTransformerNamesGroupsBuilder';
import { IRandomGenerator } from '../../../interfaces/utils/IRandomGenerator';
import { ArrayUtils } from '../../../utils/ArrayUtils';
import { CryptUtils } from '../../../utils/CryptUtils';
import { EscapeSequenceEncoder } from '../../../utils/EscapeSequenceEncoder';
import { LevelledTopologicalSorter } from '../../../utils/LevelledTopologicalSorter';
import { NodeTransformerNamesGroupsBuilder } from '../../../utils/NodeTransformerNamesGroupsBuilder';
import { RandomGenerator } from '../../../utils/RandomGenerator';
export const utilsModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => {
@@ -31,4 +35,13 @@ export const utilsModule: interfaces.ContainerModule = new ContainerModule((bind
bind<IEscapeSequenceEncoder>(ServiceIdentifiers.IEscapeSequenceEncoder)
.to(EscapeSequenceEncoder)
.inSingletonScope();
// levelled topological sorter
bind<ILevelledTopologicalSorter>(ServiceIdentifiers.ILevelledTopologicalSorter)
.to(LevelledTopologicalSorter);
// node transformer names groups builder
bind<INodeTransformerNamesGroupsBuilder>(ServiceIdentifiers.INodeTransformerNamesGroupsBuilder)
.to(NodeTransformerNamesGroupsBuilder)
.inSingletonScope();
});
@@ -3,9 +3,15 @@ import * as ESTree from 'estree';
import { IVisitor } from './IVisitor';
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
export interface INodeTransformer {
/**
* @type {NodeTransformer[] | undefined}
*/
runAfter?: NodeTransformer[];
/**
* @param {TransformationStage} transformationStage
* @returns {IVisitor | null}
+21
View File
@@ -0,0 +1,21 @@
export interface ILevelledTopologicalSorter <TValue extends string = string> {
/**
* @param {TValue} precedent
* @param {TValue | null} consequent
* @returns {this}
*/
add (
precedent: TValue,
consequent: TValue | null
): this;
/**
* @returns {TValue[]}
*/
sort (): TValue[];
/**
* @returns {TValue[][]}
*/
sortByGroups (): TValue[][];
}
@@ -0,0 +1,11 @@
import { TNormalizedNodeTransformers } from '../../types/node-transformers/TNormalizedNodeTransformers';
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
export interface INodeTransformerNamesGroupsBuilder {
/**
* @param {TNormalizedNodeTransformers} normalizedNodeTransformers
* @returns {NodeTransformer[][]}
*/
build (normalizedNodeTransformers: TNormalizedNodeTransformers): NodeTransformer[][];
}
@@ -9,10 +9,16 @@ import { IOptions } from '../interfaces/options/IOptions';
import { IRandomGenerator } from '../interfaces/utils/IRandomGenerator';
import { IVisitor } from '../interfaces/node-transformers/IVisitor';
import { NodeTransformer } from '../enums/node-transformers/NodeTransformer';
import { TransformationStage } from '../enums/node-transformers/TransformationStage';
@injectable()
export abstract class AbstractNodeTransformer implements INodeTransformer {
/**
* @type {NodeTransformer[] | undefined}
*/
public readonly runAfter: NodeTransformer[] | undefined;
/**
* @type {IOptions}
*/
+76 -27
View File
@@ -1,14 +1,19 @@
import { inject, injectable } from 'inversify';
import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
import * as estraverse from 'estraverse';
import * as ESTree from 'estree';
import { TNodeTransformerFactory } from '../types/container/node-transformers/TNodeTransformerFactory';
import { TNormalizedNodeTransformers } from '../types/node-transformers/TNormalizedNodeTransformers';
import { TTransformersRunnerData } from '../types/node-transformers/TTransformersRunnerData';
import { TVisitorDirection } from '../types/node-transformers/TVisitorDirection';
import { TVisitorFunction } from '../types/node-transformers/TVisitorFunction';
import { TVisitorResult } from '../types/node-transformers/TVisitorResult';
import { INodeTransformer } from '../interfaces/node-transformers/INodeTransformer';
import { INodeTransformerNamesGroupsBuilder } from '../interfaces/utils/INodeTransformerNamesGroupsBuilder';
import { ITransformersRunner } from '../interfaces/node-transformers/ITransformersRunner';
import { IVisitor } from '../interfaces/node-transformers/IVisitor';
@@ -21,69 +26,113 @@ import { NodeMetadata } from '../node/NodeMetadata';
@injectable()
export class TransformersRunner implements ITransformersRunner {
/**
* @type {Map<NodeTransformer[], TTransformersRunnerData>}
*/
private readonly cachedNodeTransformersData: Map<NodeTransformer[], TTransformersRunnerData> = new Map();
/**
* @type {TNodeTransformerFactory}
*/
private readonly nodeTransformerFactory: TNodeTransformerFactory;
/**
* @type {INodeTransformerNamesGroupsBuilder}
*/
private readonly nodeTransformerNamesGroupsBuilder: INodeTransformerNamesGroupsBuilder;
/**
* @param {TNodeTransformerFactory} nodeTransformerFactory
* @param {INodeTransformerNamesGroupsBuilder} nodeTransformerNamesGroupsBuilder
*/
constructor (
@inject(ServiceIdentifiers.Factory__INodeTransformer) nodeTransformerFactory: TNodeTransformerFactory,
@inject(ServiceIdentifiers.Factory__INodeTransformer)
nodeTransformerFactory: TNodeTransformerFactory,
@inject(ServiceIdentifiers.INodeTransformerNamesGroupsBuilder)
nodeTransformerNamesGroupsBuilder: INodeTransformerNamesGroupsBuilder,
) {
this.nodeTransformerFactory = nodeTransformerFactory;
this.nodeTransformerNamesGroupsBuilder = nodeTransformerNamesGroupsBuilder;
}
/**
* @param {T} astTree
* @param {NodeTransformer[]} nodeTransformers
* @param {NodeTransformer[]} nodeTransformerNames
* @param {TransformationStage} transformationStage
* @returns {T}
*/
public transform <T extends ESTree.Node = ESTree.Program> (
astTree: T,
nodeTransformers: NodeTransformer[],
nodeTransformerNames: NodeTransformer[],
transformationStage: TransformationStage
): T {
if (!nodeTransformers.length) {
if (!nodeTransformerNames.length) {
return astTree;
}
const enterVisitors: IVisitor[] = [];
const leaveVisitors: IVisitor[] = [];
const nodeTransformersLength: number = nodeTransformers.length;
let normalizedNodeTransformers: TNormalizedNodeTransformers;
let nodeTransformerNamesGroups: NodeTransformer[][];
let visitor: IVisitor | null;
if (!this.cachedNodeTransformersData.has(nodeTransformerNames)) {
normalizedNodeTransformers = this.buildNormalizedNodeTransformers(nodeTransformerNames);
nodeTransformerNamesGroups = this.nodeTransformerNamesGroupsBuilder.build(normalizedNodeTransformers);
this.cachedNodeTransformersData.set(nodeTransformerNames, [normalizedNodeTransformers, nodeTransformerNamesGroups]);
} else {
[
normalizedNodeTransformers,
nodeTransformerNamesGroups
] = <TTransformersRunnerData>this.cachedNodeTransformersData.get(nodeTransformerNames);
}
for (let i: number = 0; i < nodeTransformersLength; i++) {
visitor = this.nodeTransformerFactory(nodeTransformers[i]).getVisitor(transformationStage);
for (const nodeTransformerNamesGroup of nodeTransformerNamesGroups) {
const enterVisitors: IVisitor[] = [];
const leaveVisitors: IVisitor[] = [];
if (!visitor) {
for (const nodeTransformerName of nodeTransformerNamesGroup) {
const nodeTransformer: INodeTransformer = normalizedNodeTransformers[nodeTransformerName];
const visitor: IVisitor | null = nodeTransformer.getVisitor(transformationStage);
if (!visitor) {
continue;
}
if (visitor.enter) {
enterVisitors.push({ enter: visitor.enter });
}
if (visitor.leave) {
leaveVisitors.push({ leave: visitor.leave });
}
}
if (!enterVisitors.length && !leaveVisitors.length) {
continue;
}
if (visitor.enter) {
enterVisitors.push({ enter: visitor.enter });
}
if (visitor.leave) {
leaveVisitors.push({ leave: visitor.leave });
}
estraverse.replace(astTree, {
enter: this.mergeVisitorsForDirection(enterVisitors, VisitorDirection.Enter),
leave: this.mergeVisitorsForDirection(leaveVisitors, VisitorDirection.Leave)
});
}
if (!enterVisitors.length && !leaveVisitors.length) {
return astTree;
}
estraverse.replace(astTree, {
enter: this.mergeVisitorsForDirection(enterVisitors, VisitorDirection.Enter),
leave: this.mergeVisitorsForDirection(leaveVisitors, VisitorDirection.Leave)
});
return astTree;
}
/**
* @param {NodeTransformer[]} nodeTransformerNames
* @returns {TNormalizedNodeTransformers}
*/
private buildNormalizedNodeTransformers (nodeTransformerNames: NodeTransformer[]): TNormalizedNodeTransformers {
return nodeTransformerNames
.reduce<TNormalizedNodeTransformers>(
(acc: TNormalizedNodeTransformers, nodeTransformerName: NodeTransformer) => ({
...acc,
[nodeTransformerName]: this.nodeTransformerFactory(nodeTransformerName)
}),
{}
);
}
/**
* @param {IVisitor[]} visitors
* @param {TVisitorDirection} direction
@@ -7,6 +7,7 @@ import { IOptions } from '../../interfaces/options/IOptions';
import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator';
import { IVisitor } from '../../interfaces/node-transformers/IVisitor';
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
import { TransformationStage } from '../../enums/node-transformers/TransformationStage';
import { AbstractNodeTransformer } from '../AbstractNodeTransformer';
@@ -18,6 +19,14 @@ import { NodeGuards } from '../../node/NodeGuards';
*/
@injectable()
export class SplitStringTransformer extends AbstractNodeTransformer {
/**
* @type {NodeTransformer[]}
*/
public runAfter: NodeTransformer[] = [
NodeTransformer.ObjectExpressionKeysTransformer,
NodeTransformer.TemplateLiteralTransformer
];
/**
* @param {IRandomGenerator} randomGenerator
* @param {IOptions} options
@@ -59,7 +68,7 @@ export class SplitStringTransformer extends AbstractNodeTransformer {
switch (transformationStage) {
case TransformationStage.Converting:
return {
leave: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => {
if (!this.options.splitStrings) {
return;
}
@@ -0,0 +1,3 @@
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
export type TNodeTransformersRelationEdge = [NodeTransformer, NodeTransformer | null];
@@ -0,0 +1,5 @@
import { TObject } from '../TObject';
import { INodeTransformer } from '../../interfaces/node-transformers/INodeTransformer';
export type TNormalizedNodeTransformers = TObject<INodeTransformer>;
@@ -0,0 +1,5 @@
import { TNormalizedNodeTransformers } from './TNormalizedNodeTransformers';
import { NodeTransformer } from '../../enums/node-transformers/NodeTransformer';
export type TTransformersRunnerData = [TNormalizedNodeTransformers, NodeTransformer[][]];
+216
View File
@@ -0,0 +1,216 @@
import { injectable } from 'inversify';
import { ILevelledTopologicalSorter } from '../interfaces/utils/ILevelledTopologicalSorter';
type TVisitMark = 'ok' | 'visiting';
interface IVisitMarks <TValue extends string> {
[key: string]: TVisitMark;
}
/**
* Port and rework of https://github.com/loveencounterflow/ltsort
*/
@injectable()
export class LevelledTopologicalSorter <TValue extends string = string> implements ILevelledTopologicalSorter<TValue> {
/**
* @type {Map<TValue, TValue[]}
*/
private readonly precedents: Map<TValue, TValue[]> = new Map();
/**
* @param {TValue} precedent
* @param {TValue | null} consequent
* @returns {this}
*/
public add (
precedent: TValue,
consequent: TValue | null = null
): this {
if (consequent !== null) {
return this.link(precedent, consequent);
}
return this.register(precedent);
}
/**
* As given in http://en.wikipedia.org/wiki/Topological_sorting
*
* @returns {TValue[]}
*/
public sort (): TValue[] {
const consequents: TValue[] = Array.from(this.precedents.keys());
const results: TValue[] = [];
const marks: IVisitMarks<TValue> = {};
for (const consequent of consequents) {
if (marks[consequent] !== undefined) {
continue;
}
this.visit(results, marks, consequent);
}
return results;
}
/**
* @returns {TValue[][]}
*/
public sortByGroups (): TValue[][] {
this.sort();
const resultItemsGroups: TValue[][] = [];
while (this.hasNodes()) {
const rootNodes: TValue[] = this.findRootNodes();
resultItemsGroups.push(rootNodes);
for (const rootNode of rootNodes) {
this.delete(rootNode);
}
}
return resultItemsGroups;
}
/**
* @param {TValue} name
*/
private delete (name: TValue): void {
const precedents: TValue[] = this.getPrecedents(name);
if (precedents.length) {
throw new Error(`Unable to remove non-root node: ${name}`);
}
this.precedents.delete(name);
const precedentsGroups: string[][] = Array.from(this.precedents.values());
for (const precedentsGroup of precedentsGroups) {
const precedentsCount: number = precedentsGroup.length - 1;
for (let index: number = precedentsCount; index >= 0; index = index - 1) {
if (precedentsGroup[index] !== name) {
continue;
}
precedentsGroup.splice(index, 1);
}
}
}
/**
* @returns {TValue[]}
*/
private findRootNodes (): TValue[] {
const precedents: TValue[] = Array.from(this.precedents.keys());
const rootNodes: TValue[] = [];
for (const name of precedents) {
if (!this.hasPrecedents(name)) {
rootNodes.push(name);
}
}
return rootNodes;
}
/**
* @param {TValue} name
* @returns {TValue[]}
*/
private getPrecedents (name: TValue): TValue[] {
const precedents: TValue[] | undefined = this.precedents.get(name);
if (!precedents) {
throw new Error(`Unknown node: ${name}`);
}
return precedents;
}
/**
* @returns {boolean}
*/
private hasNodes (): boolean {
return this.precedents.size > 0;
}
/**
* @param {TValue} name
* @returns {boolean}
*/
private hasPrecedents (name: TValue): boolean {
return this.getPrecedents(name).length > 0;
}
/**
* @param {TValue} precedent
* @param {TValue} consequent
* @returns {this}
*/
private link (precedent: TValue, consequent: TValue): this {
this.register(precedent);
this.register(consequent);
const target: TValue[] | undefined = this.precedents.get(consequent);
if (target && !target.includes(precedent)) {
target.push(precedent);
}
return this;
}
/**
* @param {TValue} name
* @returns {this}
*/
private register (name: TValue): this {
if (!this.precedents.has(name)) {
this.precedents.set(name, []);
}
return this;
}
/**
* @param {TValue[]} results
* @param {IVisitMarks<TValue>} marks
* @param {TValue} name
* @returns {null}
*/
private visit (
results: TValue[],
marks: IVisitMarks<TValue>,
name: TValue
): void {
const mark: TVisitMark = marks[name];
if (mark === 'visiting') {
throw new Error(`Detected cycle involving node: ${name}`);
}
if (mark) {
return;
}
marks[name] = 'visiting';
const references: TValue[] = this.getPrecedents(name);
for (const precedent of references) {
this.visit(results, marks, precedent);
}
marks[name] = 'ok';
results.push(name);
return;
}
}
@@ -0,0 +1,81 @@
import { inject, injectable } from 'inversify';
import { ServiceIdentifiers } from '../container/ServiceIdentifiers';
import { TNodeTransformersRelationEdge } from '../types/node-transformers/TNodeTransformersRelationEdge';
import { TNormalizedNodeTransformers } from '../types/node-transformers/TNormalizedNodeTransformers';
import { ILevelledTopologicalSorter } from '../interfaces/utils/ILevelledTopologicalSorter';
import { INodeTransformer } from '../interfaces/node-transformers/INodeTransformer';
import { INodeTransformerNamesGroupsBuilder } from '../interfaces/utils/INodeTransformerNamesGroupsBuilder';
import { NodeTransformer } from '../enums/node-transformers/NodeTransformer';
@injectable()
export class NodeTransformerNamesGroupsBuilder implements INodeTransformerNamesGroupsBuilder {
/**
* @type {ILevelledTopologicalSorter<NodeTransformer>}
*/
private readonly levelledTopologicalSorter: ILevelledTopologicalSorter<NodeTransformer>;
constructor (
@inject(ServiceIdentifiers.ILevelledTopologicalSorter)
levelledTopologicalSorter: ILevelledTopologicalSorter<NodeTransformer>
) {
this.levelledTopologicalSorter = levelledTopologicalSorter;
}
/**
* Builds sorted NodeTransformer names by topological sort with levels
*
* @param {TNormalizedNodeTransformers} normalizedNodeTransformers
* @returns {NodeTransformer[][]}
*/
public build (normalizedNodeTransformers: TNormalizedNodeTransformers): NodeTransformer[][] {
const nodeTransformerNames: NodeTransformer[] = <NodeTransformer[]>Object.keys(normalizedNodeTransformers);
const relationEdges: TNodeTransformersRelationEdge[] = this.buildNodeTransformersRelationEdges(
nodeTransformerNames,
normalizedNodeTransformers
);
for (const [precedent, consequent] of relationEdges) {
this.levelledTopologicalSorter.add(precedent, consequent);
}
return this.levelledTopologicalSorter.sortByGroups();
}
/**
* @param {NodeTransformer[]} nodeTransformerNames
* @param {TNormalizedNodeTransformers} normalizedNodeTransformers
* @returns {[NodeTransformer, NodeTransformer][]}
*/
private buildNodeTransformersRelationEdges (
nodeTransformerNames: NodeTransformer[],
normalizedNodeTransformers: TNormalizedNodeTransformers
): TNodeTransformersRelationEdge[] {
const relationEdges: TNodeTransformersRelationEdge[] = [];
for (const nodeTransformerName of nodeTransformerNames) {
const nodeTransformer: INodeTransformer = normalizedNodeTransformers[nodeTransformerName];
const runAfterRelations: NodeTransformer[] | undefined = nodeTransformer.runAfter;
if (!runAfterRelations || !runAfterRelations.length) {
relationEdges.push([nodeTransformerName, null]);
continue;
}
for (const runAfterRelation of runAfterRelations) {
const isUnknownRelation: boolean = !normalizedNodeTransformers[runAfterRelation];
if (isUnknownRelation) {
relationEdges.push([nodeTransformerName, null]);
continue;
}
relationEdges.push([runAfterRelation, nodeTransformerName]);
}
}
return relationEdges;
}
}
+4 -6
View File
@@ -6,18 +6,16 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
`
function hi() {
console.log("Hello World!");
}
hi();
var s = {
'abcdefg': 'abcdefg'
};
`,
{
...NO_ADDITIONAL_NODES_PRESET,
compact: false,
splitStrings: true,
splitStringsChunkLength: 4,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 1
transformObjectKeys: true
}
).getObfuscatedCode();
@@ -162,4 +162,25 @@ describe('SplitStringTransformer', () => {
assert.match(obfuscatedCode, /^var *test *= *{\['ab' *\+ *'cd' *\+ *'ef' *\+ *'g'] *: *0x1};$/);
});
});
describe('Variant #10: Integration with `transformObjectKeys` option', () => {
it('should correctly transform string when `transformObjectKeys` option is enabled', () => {
const code: string = readFileAsString(__dirname + '/fixtures/object-string-literal.js');
obfuscatedCode = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
splitStrings: true,
splitStringsChunkLength: 2,
transformObjectKeys: true
}
).getObfuscatedCode();
assert.match(
obfuscatedCode,
/^var *test *= *{}; *test\['ab' *\+ *'cd' *\+ *'ef' *\+ *'g'] *= *'ab' *\+ *'cd' *\+ *'ef' *\+ *'g';$/
);
});
});
});