From 6b3539773ad81add78978d1ca0b4fab763a9fb64 Mon Sep 17 00:00:00 2001 From: sanex Date: Sun, 8 Aug 2021 21:23:11 +0300 Subject: [PATCH] Added `stringArrayCallsTransform` and `stringArrayCallsTransformThreshold` options --- CHANGELOG.md | 1 + README.md | 68 ++++-- src/JavaScriptObfuscator.ts | 1 + src/cli/JavaScriptObfuscatorCLI.ts | 12 +- .../modules/custom-nodes/CustomNodesModule.ts | 7 +- .../StringArrayTransformersModule.ts | 5 + .../AbstractStringArrayCallNode.ts | 4 +- .../StringArrayIndexHostNode.ts | 94 +++++++++ src/declarations/ESTree.d.ts | 2 +- .../custom-nodes/StringArrayCustomNode.ts | 1 + .../node-transformers/NodeTransformer.ts | 1 + src/interfaces/options/IOptions.ts | 2 + src/interfaces/utils/IArrayUtils.ts | 7 + .../SplitStringTransformer.ts | 58 +++--- .../MetadataTransformer.ts | 2 +- .../RenamePropertiesTransformer.ts | 23 +- .../StringArrayCallsTransformer.ts | 196 ++++++++++++++++++ .../StringArrayTransformer.ts | 23 +- src/node/NodeFactory.ts | 2 +- src/node/NodeMetadata.ts | 7 +- src/options/Options.ts | 14 ++ src/options/OptionsNormalizer.ts | 2 + .../StringArrayCallsTransform.ts | 19 ++ .../normalizer-rules/StringArrayRule.ts | 2 + src/options/presets/Default.ts | 2 + src/options/presets/HighObfuscation.ts | 1 + src/options/presets/MediumObfuscation.ts | 1 + src/options/presets/NoCustomNodes.ts | 2 + .../VisitedLexicalScopeNodesStackStorage.ts | 4 +- src/utils/ArrayUtils.ts | 11 +- test/dev/dev.ts | 49 ++++- .../options/OptionsNormalizer.spec.ts | 24 +++ .../node/node-metadata/NodeMetadata.spec.ts | 15 +- test/unit-tests/utils/ArrayUtils.spec.ts | 62 ++++++ 34 files changed, 628 insertions(+), 96 deletions(-) create mode 100644 src/custom-nodes/string-array-nodes/StringArrayIndexHostNode.ts create mode 100644 src/node-transformers/string-array-transformers/StringArrayCallsTransformer.ts create mode 100644 src/options/normalizer-rules/StringArrayCallsTransform.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 202c864f..111c4f5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Change Log v2.19.0 --- +* **New options**: `stringArrayCallsTransform` and `stringArrayCallsTransformThreshold` * Fixed very rare cases when `rotateStringArray` couldn't rotate array properly * Improved `selfDefending` option * Installed `npm` package now has `types` directory and doesn't have `src` directory diff --git a/README.md b/README.md index 89bbdeb6..795ee92d 100644 --- a/README.md +++ b/README.md @@ -381,10 +381,12 @@ Following options are available for the JS Obfuscator: splitStrings: false, splitStringsChunkLength: 10, stringArray: true, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 0.5, + stringArrayEncoding: [], stringArrayIndexesType: [ 'hexadecimal-number' ], - stringArrayEncoding: [], stringArrayIndexShift: true, stringArrayWrappersCount: 1, stringArrayWrappersChainedCalls: true, @@ -443,8 +445,10 @@ Following options are available for the JS Obfuscator: --split-strings --split-strings-chunk-length --string-array - --string-array-indexes-type '' (comma separated) [hexadecimal-number, hexadecimal-numeric-string] + --string-array-calls-transform + --string-array-calls-transform-threshold --string-array-encoding '' (comma separated) [none, base64, rc4] + --string-array-indexes-type '' (comma separated) [hexadecimal-number, hexadecimal-numeric-string] --string-array-index-shift --string-array-wrappers-count --string-array-wrappers-chained-calls @@ -1111,10 +1115,24 @@ Type: `boolean` Default: `true` Removes string literals and place them in a special array. For instance, the string `"Hello World"` in `var m = "Hello World";` will be replaced with something like `var m = _0x12c456[0x1];` +### `stringArrayCallsTransform` +Type: `boolean` Default: `true` + +##### :warning: [`stringArray`](#stringarray) option must be enabled + +Enables the transformation of calls to the [`stringArray`](#stringarray). All arguments of these calls may be extracted to different variables depending on [`stringArrayCallsTransformThreshold`](#stringarraycallstransformthreshold) value. For large source code, the internal threshold also could be applied. + +### `stringArrayCallsTransformThreshold` +Type: `number` Default: `0.5` + +##### :warning: [`stringArray`](#stringarray) and [`stringArrayCallsTransformThreshold`](#stringarraycallstransformthreshold) options must be enabled + +You can use this setting to adjust the probability (from 0 to 1) that calls to the string array will be transformed. + ### `stringArrayEncoding` Type: `string[]` Default: `[]` -##### :warning: `stringArray` option must be enabled +##### :warning: [`stringArray`](#stringarray) option must be enabled This option can slow down your script. @@ -1163,6 +1181,17 @@ Type: `boolean` Default: `true` Enables additional index shift for all string array calls +### `stringArrayThreshold` +Type: `number` Default: `0.8` Min: `0` Max: `1` + +##### :warning: [`stringArray`](#stringarray) option must be enabled + +You can use this setting to adjust the probability (from 0 to 1) that a string literal will be inserted into the `stringArray`. + +This setting is especially useful for large code size because it repeatedly calls to the `string array` and can slow down your code. + +`stringArrayThreshold: 0` equals to `stringArray: false`. + ### `stringArrayWrappersCount` Type: `number` Default: `1` @@ -1340,17 +1369,6 @@ function d (c, g) { } test(); ``` - -### `stringArrayThreshold` -Type: `number` Default: `0.8` Min: `0` Max: `1` - -##### :warning: [`stringArray`](#stringarray) option must be enabled - -You can use this setting to adjust the probability (from 0 to 1) that a string literal will be inserted into the `stringArray`. - -This setting is especially useful for large code size because it repeatedly calls to the `string array` and can slow down your code. - -`stringArrayThreshold: 0` equals to `stringArray: false`. ### `target` Type: `string` Default: `browser` @@ -1417,9 +1435,9 @@ Allows to enable/disable string conversion to unicode escape sequence. Unicode escape sequence increases code size greatly and strings easily can be reverted to their original view. Recommended to enable this option only for small source code. ## Preset Options -### High obfuscation, low performance +### High obfuscation -Performance will 50-100% slower than without obfuscation +The performance will be much slower than without obfuscation ```javascript { @@ -1442,6 +1460,8 @@ Performance will 50-100% slower than without obfuscation splitStrings: true, splitStringsChunkLength: 5, stringArray: true, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 1, stringArrayEncoding: ['rc4'], stringArrayIndexShift: true, stringArrayWrappersCount: 5, @@ -1454,9 +1474,9 @@ Performance will 50-100% slower than without obfuscation } ``` -### Medium obfuscation, optimal performance +### Medium obfuscation -Performance will 30-35% slower than without obfuscation +The performance will be slower than without obfuscation ```javascript { @@ -1479,6 +1499,8 @@ Performance will 30-35% slower than without obfuscation splitStrings: true, splitStringsChunkLength: 10, stringArray: true, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 0.75, stringArrayEncoding: ['base64'], stringArrayIndexShift: true, stringArrayWrappersCount: 2, @@ -1491,9 +1513,9 @@ Performance will 30-35% slower than without obfuscation } ``` -### Low obfuscation, High performance +### Low obfuscation -Performance will slightly slower than without obfuscation +The performance will be at a relatively normal level ```javascript { @@ -1513,6 +1535,8 @@ Performance will slightly slower than without obfuscation simplify: true, splitStrings: false, stringArray: true, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 0.5, stringArrayEncoding: [], stringArrayIndexShift: true, stringArrayWrappersCount: 1, @@ -1524,7 +1548,7 @@ Performance will slightly slower than without obfuscation } ``` -### Default preset, High performance +### Default preset ```javascript { @@ -1544,6 +1568,8 @@ Performance will slightly slower than without obfuscation simplify: true, splitStrings: false, stringArray: true, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 0.5, stringArrayEncoding: [], stringArrayIndexShift: true, stringArrayWrappersCount: 1, diff --git a/src/JavaScriptObfuscator.ts b/src/JavaScriptObfuscator.ts index 76deb867..3acaef7d 100644 --- a/src/JavaScriptObfuscator.ts +++ b/src/JavaScriptObfuscator.ts @@ -90,6 +90,7 @@ export class JavaScriptObfuscator implements IJavaScriptObfuscator { NodeTransformer.ScopeIdentifiersTransformer, NodeTransformer.ScopeThroughIdentifiersTransformer, NodeTransformer.SplitStringTransformer, + NodeTransformer.StringArrayCallsTransformer, NodeTransformer.StringArrayRotateFunctionTransformer, NodeTransformer.StringArrayScopeCallsWrapperTransformer, NodeTransformer.StringArrayTransformer, diff --git a/src/cli/JavaScriptObfuscatorCLI.ts b/src/cli/JavaScriptObfuscatorCLI.ts index ed41723b..b5d15149 100644 --- a/src/cli/JavaScriptObfuscatorCLI.ts +++ b/src/cli/JavaScriptObfuscatorCLI.ts @@ -371,9 +371,19 @@ export class JavaScriptObfuscatorCLI implements IInitializable { ) .option( '--string-array ', - 'Disables gathering of all literal strings into an array and replacing every literal string with an array call', + 'Enables gathering of all literal strings into an array and replacing every literal string with the call to the string array', BooleanSanitizer ) + .option( + '--string-array-calls-transform ', + 'Enables the transformation of calls to the string array', + BooleanSanitizer + ) + .option( + '--string-array-calls-transform-threshold ', + 'The probability that that calls to the string array will be transformed', + parseFloat + ) .option( '--string-array-encoding (comma separated, without whitespaces)', 'Encodes each string in strings array using base64 or rc4 (this option can slow down your code speed). ' + diff --git a/src/container/modules/custom-nodes/CustomNodesModule.ts b/src/container/modules/custom-nodes/CustomNodesModule.ts index 08584c19..720e235e 100644 --- a/src/container/modules/custom-nodes/CustomNodesModule.ts +++ b/src/container/modules/custom-nodes/CustomNodesModule.ts @@ -9,6 +9,7 @@ import { ControlFlowCustomNode } from '../../../enums/custom-nodes/ControlFlowCu import { DeadCodeInjectionCustomNode } from '../../../enums/custom-nodes/DeadCodeInjectionCustomNode'; import { ObjectExpressionKeysTransformerCustomNode } from '../../../enums/custom-nodes/ObjectExpressionKeysTransformerCustomNode'; import { StringArrayCustomNode } from '../../../enums/custom-nodes/StringArrayCustomNode'; +import { StringArrayIndexNode } from '../../../enums/custom-nodes/string-array-index-nodes/StringArrayIndexNode'; import { BinaryExpressionFunctionNode } from '../../../custom-nodes/control-flow-flattening-nodes/BinaryExpressionFunctionNode'; import { BlockStatementControlFlowFlatteningNode } from '../../../custom-nodes/control-flow-flattening-nodes/BlockStatementControlFlowFlatteningNode'; @@ -22,7 +23,7 @@ import { ObjectExpressionVariableDeclarationHostNode } from '../../../custom-nod import { StringArrayCallNode } from '../../../custom-nodes/string-array-nodes/StringArrayCallNode'; import { StringArrayHexadecimalNumberIndexNode } from '../../../custom-nodes/string-array-nodes/string-array-index-nodes/StringArrayHexadecimalNumberIndexNode'; import { StringArrayHexadecimalNumericStringIndexNode } from '../../../custom-nodes/string-array-nodes/string-array-index-nodes/StringArrayHexadecimalNumericStringIndexNode'; -import { StringArrayIndexNode } from '../../../enums/custom-nodes/string-array-index-nodes/StringArrayIndexNode'; +import { StringArrayIndexHostNode } from '../../../custom-nodes/string-array-nodes/StringArrayIndexHostNode'; import { StringArrayScopeCallsWrapperFunctionNode } from '../../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperFunctionNode'; import { StringArrayScopeCallsWrapperVariableNode } from '../../../custom-nodes/string-array-nodes/StringArrayScopeCallsWrapperVariableNode'; import { StringLiteralControlFlowStorageCallNode } from '../../../custom-nodes/control-flow-flattening-nodes/control-flow-storage-nodes/StringLiteralControlFlowStorageCallNode'; @@ -81,6 +82,10 @@ export const customNodesModule: interfaces.ContainerModule = new ContainerModule .toConstructor(StringArrayCallNode) .whenTargetNamed(StringArrayCustomNode.StringArrayCallNode); + bind>(ServiceIdentifiers.Newable__ICustomNode) + .toConstructor(StringArrayIndexHostNode) + .whenTargetNamed(StringArrayCustomNode.StringArrayIndexHostNode); + bind>(ServiceIdentifiers.Newable__ICustomNode) .toConstructor(StringArrayScopeCallsWrapperFunctionNode) .whenTargetNamed(StringArrayCustomNode.StringArrayScopeCallsWrapperFunctionNode); diff --git a/src/container/modules/node-transformers/StringArrayTransformersModule.ts b/src/container/modules/node-transformers/StringArrayTransformersModule.ts index 2ab43e48..741c0983 100644 --- a/src/container/modules/node-transformers/StringArrayTransformersModule.ts +++ b/src/container/modules/node-transformers/StringArrayTransformersModule.ts @@ -5,12 +5,17 @@ import { INodeTransformer } from '../../../interfaces/node-transformers/INodeTra import { NodeTransformer } from '../../../enums/node-transformers/NodeTransformer'; +import { StringArrayCallsTransformer } from '../../../node-transformers/string-array-transformers/StringArrayCallsTransformer'; import { StringArrayRotateFunctionTransformer } from '../../../node-transformers/string-array-transformers/StringArrayRotateFunctionTransformer'; import { StringArrayScopeCallsWrapperTransformer } from '../../../node-transformers/string-array-transformers/StringArrayScopeCallsWrapperTransformer'; import { StringArrayTransformer } from '../../../node-transformers/string-array-transformers/StringArrayTransformer'; export const stringArrayTransformersModule: interfaces.ContainerModule = new ContainerModule((bind: interfaces.Bind) => { // strings transformers + bind(ServiceIdentifiers.INodeTransformer) + .to(StringArrayCallsTransformer) + .whenTargetNamed(NodeTransformer.StringArrayCallsTransformer); + bind(ServiceIdentifiers.INodeTransformer) .to(StringArrayRotateFunctionTransformer) .whenTargetNamed(NodeTransformer.StringArrayRotateFunctionTransformer); diff --git a/src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts b/src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts index eb56201b..72915e05 100644 --- a/src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts +++ b/src/custom-nodes/string-array-nodes/AbstractStringArrayCallNode.ts @@ -105,7 +105,7 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode { const stringArrayCallIndexNode: ESTree.Expression = this.stringArrayIndexNodeFactory(stringArrayIndexNodeName) .getNode(normalizedIndex); - NodeMetadata.set(stringArrayCallIndexNode, { replacedLiteral: true }); + NodeMetadata.set(stringArrayCallIndexNode, { stringArrayCallLiteralNode: true }); const hexadecimalNode: ESTree.Expression = isPositive ? stringArrayCallIndexNode @@ -126,7 +126,7 @@ export abstract class AbstractStringArrayCallNode extends AbstractCustomNode { protected getRc4KeyLiteralNode (decodeKey: string): ESTree.Literal { const rc4KeyLiteralNode: ESTree.Literal = NodeFactory.literalNode(decodeKey); - NodeMetadata.set(rc4KeyLiteralNode, { replacedLiteral: true }); + NodeMetadata.set(rc4KeyLiteralNode, { stringArrayCallLiteralNode: true }); return rc4KeyLiteralNode; } diff --git a/src/custom-nodes/string-array-nodes/StringArrayIndexHostNode.ts b/src/custom-nodes/string-array-nodes/StringArrayIndexHostNode.ts new file mode 100644 index 00000000..81678073 --- /dev/null +++ b/src/custom-nodes/string-array-nodes/StringArrayIndexHostNode.ts @@ -0,0 +1,94 @@ +import { inject, injectable, } from 'inversify'; +import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; + +import type * as ESTree from 'estree'; + +import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory'; +import { TStatement } from '../../types/node/TStatement'; +import { TStringArrayIndexNodeFactory } from '../../types/container/custom-nodes/string-array-index-nodes/TStringArrayIndexNodeFactory'; + +import { IArrayUtils } from '../../interfaces/utils/IArrayUtils'; +import { ICustomCodeHelperFormatter } from '../../interfaces/custom-code-helpers/ICustomCodeHelperFormatter'; +import { IOptions } from '../../interfaces/options/IOptions'; +import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator'; +import { IStringArrayStorage } from '../../interfaces/storages/string-array-transformers/IStringArrayStorage'; + +import { initializable } from '../../decorators/Initializable'; + +import { AbstractStringArrayCallNode } from './AbstractStringArrayCallNode'; +import { NodeFactory } from '../../node/NodeFactory'; + +@injectable() +export class StringArrayIndexHostNode extends AbstractStringArrayCallNode { + /** + * @type {Literal} + */ + @initializable() + private stringArrayCallIndexNode!: ESTree.Literal; + + /** + * @type {number} + */ + @initializable() + private variableName!: string; + + /** + * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory + * @param {TStringArrayIndexNodeFactory} stringArrayIndexNodeFactory + * @param {ICustomCodeHelperFormatter} customCodeHelperFormatter + * @param {IStringArrayStorage} stringArrayStorage + * @param {IArrayUtils} arrayUtils + * @param {IRandomGenerator} randomGenerator + * @param {IOptions} options + */ + public constructor ( + @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator) + identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory, + @inject(ServiceIdentifiers.Factory__IStringArrayIndexNode) + stringArrayIndexNodeFactory: TStringArrayIndexNodeFactory, + @inject(ServiceIdentifiers.ICustomCodeHelperFormatter) customCodeHelperFormatter: ICustomCodeHelperFormatter, + @inject(ServiceIdentifiers.IStringArrayStorage) stringArrayStorage: IStringArrayStorage, + @inject(ServiceIdentifiers.IArrayUtils) arrayUtils: IArrayUtils, + @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator, + @inject(ServiceIdentifiers.IOptions) options: IOptions + ) { + super( + identifierNamesGeneratorFactory, + stringArrayIndexNodeFactory, + customCodeHelperFormatter, + stringArrayStorage, + arrayUtils, + randomGenerator, + options + ); + } + + /** + * @param {Literal} stringArrayCallIndexNode + * @param {string} variableName + */ + public initialize ( + stringArrayCallIndexNode: ESTree.Literal, + variableName: string + ): void { + this.stringArrayCallIndexNode = stringArrayCallIndexNode; + this.variableName = variableName; + } + + /** + * @returns {TStatement[]} + */ + protected getNodeStructure (): TStatement[] { + const indexHostVariableNode: ESTree.VariableDeclaration = NodeFactory.variableDeclarationNode( + [ + NodeFactory.variableDeclaratorNode( + NodeFactory.identifierNode(this.variableName), + this.stringArrayCallIndexNode + ) + ], + 'const' + ); + + return [indexHostVariableNode]; + } +} diff --git a/src/declarations/ESTree.d.ts b/src/declarations/ESTree.d.ts index 3254c127..252dfd76 100644 --- a/src/declarations/ESTree.d.ts +++ b/src/declarations/ESTree.d.ts @@ -14,7 +14,7 @@ declare module 'estree' { } export interface LiteralNodeMetadata extends BaseNodeMetadata { - replacedLiteral?: boolean; + stringArrayCallLiteralNode?: boolean; } /** diff --git a/src/enums/custom-nodes/StringArrayCustomNode.ts b/src/enums/custom-nodes/StringArrayCustomNode.ts index 35f1d83d..e8854c03 100644 --- a/src/enums/custom-nodes/StringArrayCustomNode.ts +++ b/src/enums/custom-nodes/StringArrayCustomNode.ts @@ -1,5 +1,6 @@ export enum StringArrayCustomNode { StringArrayCallNode = 'StringArrayCallNode', + StringArrayIndexHostNode = 'StringArrayIndexHostNode', StringArrayScopeCallsWrapperFunctionNode = 'StringArrayScopeCallsWrapperFunctionNode', StringArrayScopeCallsWrapperVariableNode = 'StringArrayScopeCallsWrapperVariableNode' } diff --git a/src/enums/node-transformers/NodeTransformer.ts b/src/enums/node-transformers/NodeTransformer.ts index bd3e1897..64da86fd 100644 --- a/src/enums/node-transformers/NodeTransformer.ts +++ b/src/enums/node-transformers/NodeTransformer.ts @@ -28,6 +28,7 @@ export enum NodeTransformer { ScopeIdentifiersTransformer = 'ScopeIdentifiersTransformer', ScopeThroughIdentifiersTransformer = 'ScopeThroughIdentifiersTransformer', SplitStringTransformer = 'SplitStringTransformer', + StringArrayCallsTransformer = 'StringArrayCallsTransformer', StringArrayTransformer = 'StringArrayTransformer', StringArrayRotateFunctionTransformer = 'StringArrayRotateFunctionTransformer', StringArrayScopeCallsWrapperTransformer = 'StringArrayScopeCallsWrapperTransformer', diff --git a/src/interfaces/options/IOptions.ts b/src/interfaces/options/IOptions.ts index 07d480c9..bfca9657 100644 --- a/src/interfaces/options/IOptions.ts +++ b/src/interfaces/options/IOptions.ts @@ -51,6 +51,8 @@ export interface IOptions { readonly splitStringsChunkLength: number; readonly stringArray: boolean; readonly stringArrayEncoding: TStringArrayEncoding[]; + readonly stringArrayCallsTransform: boolean; + readonly stringArrayCallsTransformThreshold: number; readonly stringArrayIndexesType: TStringArrayIndexesType[]; readonly stringArrayIndexShift: boolean; readonly stringArrayWrappersChainedCalls: boolean; diff --git a/src/interfaces/utils/IArrayUtils.ts b/src/interfaces/utils/IArrayUtils.ts index 86f52c9c..9394adc8 100644 --- a/src/interfaces/utils/IArrayUtils.ts +++ b/src/interfaces/utils/IArrayUtils.ts @@ -24,6 +24,13 @@ export interface IArrayUtils { */ getLastElement (array: T[]): T | undefined; + /** + * @param {T[]} array + * @param {number} index + * @returns {T | undefined} + */ + getLastElementByIndex (array: T[], index: number): T | undefined; + /** * @param array * @param times diff --git a/src/node-transformers/converting-transformers/SplitStringTransformer.ts b/src/node-transformers/converting-transformers/SplitStringTransformer.ts index 81ded683..c79bd305 100644 --- a/src/node-transformers/converting-transformers/SplitStringTransformer.ts +++ b/src/node-transformers/converting-transformers/SplitStringTransformer.ts @@ -23,6 +23,8 @@ import { NodeUtils } from '../../node/NodeUtils'; */ @injectable() export class SplitStringTransformer extends AbstractNodeTransformer { + private static readonly maxStringLengthForSecondPass: number = 30000; + /** * @type {number} */ @@ -80,14 +82,14 @@ export class SplitStringTransformer extends AbstractNodeTransformer { * @returns {IVisitor | null} */ public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null { + if (!this.options.splitStrings) { + return null; + } + switch (nodeTransformationStage) { case NodeTransformationStage.Converting: return { - enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => { - if (!this.options.splitStrings) { - return; - } - + leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => { if (parentNode && NodeGuards.isLiteralNode(node)) { return this.transformNode(node, parentNode); } @@ -113,48 +115,55 @@ export class SplitStringTransformer extends AbstractNodeTransformer { } // pass #1: split string on a large chunks with length of `firstPassChunkLength` - const firstPassChunksNode: ESTree.Node = this.transformLiteralNodeByChunkLength( + const [firstPassChunksNode, stringChunks]: [ESTree.Node, string[]] = this.transformLiteralNodeByChunkLength( literalNode, - parentNode, SplitStringTransformer.firstPassChunkLength ); - // pass #2: split large chunks on a chunks with length of `splitStringsChunkLength` + const stringChunksCount: number = stringChunks.length; + const isLargeString: boolean = SplitStringTransformer.maxStringLengthForSecondPass + / SplitStringTransformer.firstPassChunkLength + <= stringChunksCount; + const minSecondPathChunkLength = isLargeString + ? Math.floor(stringChunksCount / this.options.splitStringsChunkLength) + : this.options.splitStringsChunkLength; + + // pass #2: split large chunks on a chunks with length of min length === `splitStringsChunkLength` const secondPassChunksNode: ESTree.Node = estraverse.replace(firstPassChunksNode, { // eslint-disable-next-line @typescript-eslint/no-shadow - enter: (node: ESTree.Node, parentNode: ESTree.Node | null) => { - if (parentNode && NodeGuards.isLiteralNode(node)) { + enter: (node: ESTree.Node) => { + if (NodeGuards.isLiteralNode(node)) { return this.transformLiteralNodeByChunkLength( node, - parentNode, - this.options.splitStringsChunkLength - ); + minSecondPathChunkLength + )[0]; } } }); + NodeUtils.parentizeNode(secondPassChunksNode, parentNode); + NodeUtils.parentizeAst(secondPassChunksNode); + return secondPassChunksNode; } /** * @param {Literal} literalNode - * @param {Node} parentNode * @param {number} chunkLength - * @returns {Node} + * @returns {[resultNode: Node, stringChunks: string[]]} */ private transformLiteralNodeByChunkLength ( literalNode: ESTree.Literal, - parentNode: ESTree.Node, chunkLength: number - ): ESTree.Node { + ): [resultNode: ESTree.Node, stringChunks: string[]] { if (!NodeLiteralUtils.isStringLiteralNode(literalNode)) { - return literalNode; + return [literalNode, []]; } const valueLength: number = stringz.length(literalNode.value); if (chunkLength >= valueLength) { - return literalNode; + return [literalNode, []]; } const stringChunks: string[] = SplitStringTransformer.chunkString( @@ -163,13 +172,10 @@ export class SplitStringTransformer extends AbstractNodeTransformer { chunkLength ); - const binaryExpressionNode: ESTree.BinaryExpression = - this.transformStringChunksToBinaryExpressionNode(stringChunks); - - NodeUtils.parentizeAst(binaryExpressionNode); - NodeUtils.parentizeNode(binaryExpressionNode, parentNode); - - return binaryExpressionNode; + return [ + this.transformStringChunksToBinaryExpressionNode(stringChunks), + stringChunks + ]; } /** diff --git a/src/node-transformers/preparing-transformers/MetadataTransformer.ts b/src/node-transformers/preparing-transformers/MetadataTransformer.ts index 6d97722d..0df45daf 100644 --- a/src/node-transformers/preparing-transformers/MetadataTransformer.ts +++ b/src/node-transformers/preparing-transformers/MetadataTransformer.ts @@ -65,7 +65,7 @@ export class MetadataTransformer extends AbstractNodeTransformer { NodeMetadata.set(node, { ignoredNode: false }); if (NodeGuards.isLiteralNode(node)) { - NodeMetadata.set(node, { replacedLiteral: false }); + NodeMetadata.set(node, { stringArrayCallLiteralNode: false }); } return node; diff --git a/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts b/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts index 1391acb6..b3dd84a7 100644 --- a/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts +++ b/src/node-transformers/rename-properties-transformers/RenamePropertiesTransformer.ts @@ -14,6 +14,7 @@ import { AbstractNodeTransformer } from '../AbstractNodeTransformer'; import { NodeGuards } from '../../node/NodeGuards'; import { NodeLiteralUtils } from '../../node/NodeLiteralUtils'; import { RenamePropertiesMode } from '../../enums/node-transformers/rename-properties-transformers/RenamePropertiesMode'; +import { NodeUtils } from '../../node/NodeUtils'; @injectable() export class RenamePropertiesTransformer extends AbstractNodeTransformer { @@ -106,20 +107,20 @@ export class RenamePropertiesTransformer extends AbstractNodeTransformer { * @returns {Node} */ public transformNode (node: ESTree.Node, parentNode: ESTree.Node): ESTree.Node { + let propertyNode: ESTree.Node | null = null; + if (NodeGuards.isPropertyNode(node)) { - return this.transformPropertyNode(node); + propertyNode = this.transformPropertyNode(node); + } else if (NodeGuards.isPropertyDefinitionNode(node)) { + propertyNode = this.transformPropertyDefinitionNode(node); + } else if (NodeGuards.isMemberExpressionNode(node)) { + propertyNode = this.transformMemberExpressionNode(node); + } else if (NodeGuards.isMethodDefinitionNode(node)) { + propertyNode = this.transformMethodDefinitionNode(node); } - if (NodeGuards.isPropertyDefinitionNode(node)) { - return this.transformPropertyDefinitionNode(node); - } - - if (NodeGuards.isMemberExpressionNode(node)) { - return this.transformMemberExpressionNode(node); - } - - if (NodeGuards.isMethodDefinitionNode(node)) { - return this.transformMethodDefinitionNode(node); + if (propertyNode) { + NodeUtils.parentizeNode(propertyNode, parentNode); } return node; diff --git a/src/node-transformers/string-array-transformers/StringArrayCallsTransformer.ts b/src/node-transformers/string-array-transformers/StringArrayCallsTransformer.ts new file mode 100644 index 00000000..4babb143 --- /dev/null +++ b/src/node-transformers/string-array-transformers/StringArrayCallsTransformer.ts @@ -0,0 +1,196 @@ +import { inject, injectable, } from 'inversify'; +import { ServiceIdentifiers } from '../../container/ServiceIdentifiers'; + +import * as ESTree from 'estree'; + +import { TIdentifierNamesGeneratorFactory } from '../../types/container/generators/TIdentifierNamesGeneratorFactory'; +import { TInitialData } from '../../types/TInitialData'; +import { TNodeWithLexicalScopeStatements } from '../../types/node/TNodeWithLexicalScopeStatements'; +import { TStatement } from '../../types/node/TStatement'; +import { TStringArrayCustomNodeFactory } from '../../types/container/custom-nodes/TStringArrayCustomNodeFactory'; + +import { ICustomNode } from '../../interfaces/custom-nodes/ICustomNode'; +import { IIdentifierNamesGenerator } from '../../interfaces/generators/identifier-names-generators/IIdentifierNamesGenerator'; +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 { NodeTransformationStage } from '../../enums/node-transformers/NodeTransformationStage'; +import { StringArrayCustomNode } from '../../enums/custom-nodes/StringArrayCustomNode'; + +import { AbstractNodeTransformer } from '../AbstractNodeTransformer'; +import { NodeAppender } from '../../node/NodeAppender'; +import { NodeFactory } from '../../node/NodeFactory'; +import { NodeGuards } from '../../node/NodeGuards'; +import { NodeMetadata } from '../../node/NodeMetadata'; +import { NodeUtils } from '../../node/NodeUtils'; +import { StringArrayIndexHostNode } from '../../custom-nodes/string-array-nodes/StringArrayIndexHostNode'; + +@injectable() +export class StringArrayCallsTransformer extends AbstractNodeTransformer { + /** + * @type {number} + */ + private static readonly lexicalScopeBodyLengthThreshold: number = 20000; + + /** + * @type {NodeTransformer[]} + */ + public override readonly runAfter: NodeTransformer[] = [ + NodeTransformer.StringArrayTransformer + ]; + + /** + * @type {IIdentifierNamesGenerator} + */ + private readonly identifierNamesGenerator: IIdentifierNamesGenerator; + + /** + * @type {TStringArrayCustomNodeFactory} + */ + private readonly stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory; + + /** + * @type {TNodeWithLexicalScopeStatements[]} + */ + private readonly visitedLexicalScopeNodesStack: TNodeWithLexicalScopeStatements[] = []; + + /** + * @param {IRandomGenerator} randomGenerator + * @param {IOptions} options + * @param {TIdentifierNamesGeneratorFactory} identifierNamesGeneratorFactory + * @param {TStringArrayCustomNodeFactory} stringArrayTransformerCustomNodeFactory + */ + public constructor ( + @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator, + @inject(ServiceIdentifiers.IOptions) options: IOptions, + @inject(ServiceIdentifiers.Factory__IIdentifierNamesGenerator) + identifierNamesGeneratorFactory: TIdentifierNamesGeneratorFactory, + @inject(ServiceIdentifiers.Factory__IStringArrayCustomNode) + stringArrayTransformerCustomNodeFactory: TStringArrayCustomNodeFactory + ) { + super(randomGenerator, options); + + this.identifierNamesGenerator = identifierNamesGeneratorFactory(options); + this.stringArrayTransformerCustomNodeFactory = stringArrayTransformerCustomNodeFactory; + this.stringArrayTransformerCustomNodeFactory = stringArrayTransformerCustomNodeFactory; + } + + /** + * @param {NodeTransformationStage} nodeTransformationStage + * @returns {IVisitor | null} + */ + public getVisitor (nodeTransformationStage: NodeTransformationStage): IVisitor | null { + if (!this.options.stringArrayCallsTransform) { + return null; + } + + switch (nodeTransformationStage) { + case NodeTransformationStage.StringArray: + return { + enter: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => { + if (parentNode && NodeGuards.isNodeWithLexicalScopeStatements(node, parentNode)) { + this.onLexicalScopeNodeEnter(node); + + return node; + } + }, + leave: (node: ESTree.Node, parentNode: ESTree.Node | null): ESTree.Node | undefined => { + if (parentNode && NodeGuards.isNodeWithLexicalScopeStatements(node, parentNode)) { + this.onLexicalScopeNodeLeave(); + + return node; + } + + if ( + parentNode + && NodeGuards.isLiteralNode(node) + && NodeMetadata.isStringArrayCallLiteralNode(node) + ) { + return this.transformNode(node, parentNode); + } + } + }; + + default: + return null; + } + } + + /** + * @param {Literal} literalNode + * @param {Node} parentNode + * @returns {Literal | Identifier} + */ + public transformNode (literalNode: ESTree.Literal, parentNode: ESTree.Node): ESTree.Literal | ESTree.Identifier { + const randomLexicalScopeNode: TNodeWithLexicalScopeStatements = this.randomGenerator + .getRandomGenerator() + .pickone(this.visitedLexicalScopeNodesStack); + + if (!randomLexicalScopeNode) { + return literalNode; + } + + const randomNumber: number = this.randomGenerator.getMathRandom(); + const isOptionThresholdPassed: boolean = randomNumber <= this.options.stringArrayCallsTransformThreshold; + const isLargeLexicalScopeBodyLengthThresholdPassed: boolean = randomNumber > ( + randomLexicalScopeNode.body.length / StringArrayCallsTransformer.lexicalScopeBodyLengthThreshold + ); + + if (!isOptionThresholdPassed || !isLargeLexicalScopeBodyLengthThresholdPassed) { + return literalNode; + } + + const hostVariableName: string = this.identifierNamesGenerator.generateNext(); + const hostVariableNode: ESTree.VariableDeclaration = this.getStringArrayIndexHostNode( + literalNode, + hostVariableName + ); + NodeUtils.parentizeAst(hostVariableNode); + + NodeAppender.prepend(randomLexicalScopeNode, [hostVariableNode]); + + const identifierNode: ESTree.Identifier = NodeFactory.identifierNode(hostVariableName); + NodeUtils.parentizeNode(identifierNode, parentNode); + + return identifierNode; + } + + /** + * @param {Literal} stringArrayCallIndexNode + * @param {string} variableName + * @returns {VariableDeclaration} + */ + private getStringArrayIndexHostNode ( + stringArrayCallIndexNode: ESTree.Literal, + variableName: string + ): ESTree.VariableDeclaration { + const stringArrayCallCustomNode: ICustomNode> = + this.stringArrayTransformerCustomNodeFactory(StringArrayCustomNode.StringArrayIndexHostNode); + + stringArrayCallCustomNode.initialize( + stringArrayCallIndexNode, + variableName + ); + + const statementNode: TStatement = stringArrayCallCustomNode.getNode()[0]; + + if (!NodeGuards.isVariableDeclarationNode(statementNode)) { + throw new Error('`stringArrayIndexHostNode.getNode()[0]` should returns array with `isVariableDeclarationNode` node'); + } + + return statementNode; + } + + /** + * @param {TNodeWithLexicalScopeStatements} lexicalScopeBodyNode + */ + private onLexicalScopeNodeEnter (lexicalScopeBodyNode: TNodeWithLexicalScopeStatements): void { + this.visitedLexicalScopeNodesStack.push(lexicalScopeBodyNode); + } + + private onLexicalScopeNodeLeave (): void { + this.visitedLexicalScopeNodesStack.pop(); + } +} diff --git a/src/node-transformers/string-array-transformers/StringArrayTransformer.ts b/src/node-transformers/string-array-transformers/StringArrayTransformer.ts index 0ffabe75..747791f4 100644 --- a/src/node-transformers/string-array-transformers/StringArrayTransformer.ts +++ b/src/node-transformers/string-array-transformers/StringArrayTransformer.ts @@ -139,7 +139,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer { this.prepareNode(node); } - if (parentNode && NodeGuards.isLiteralNode(node) && !NodeMetadata.isReplacedLiteral(node)) { + if ( + parentNode + && NodeGuards.isLiteralNode(node) + && !NodeMetadata.isStringArrayCallLiteralNode(node) + ) { return this.transformNode(node, parentNode); } } @@ -187,16 +191,19 @@ export class StringArrayTransformer extends AbstractNodeTransformer { const cacheKey: string = this.literalNodesCacheStorage.buildKey(literalValue, stringArrayStorageItemData); const useCachedValue: boolean = this.literalNodesCacheStorage.shouldUseCachedValue(cacheKey, stringArrayStorageItemData); + let resultNode: ESTree.Node; + if (useCachedValue) { - return this.literalNodesCacheStorage.get(cacheKey); + const nodeFromCache: ESTree.Node = this.literalNodesCacheStorage.get(cacheKey); + + resultNode = NodeUtils.clone(nodeFromCache); + } else { + resultNode = stringArrayStorageItemData + ? this.getStringArrayCallNode(stringArrayStorageItemData) + : literalNode; + this.literalNodesCacheStorage.set(cacheKey, resultNode); } - const resultNode: ESTree.Node = stringArrayStorageItemData - ? this.getStringArrayCallNode(stringArrayStorageItemData) - : literalNode; - - this.literalNodesCacheStorage.set(cacheKey, resultNode); - NodeUtils.parentizeNode(resultNode, parentNode); return resultNode; diff --git a/src/node/NodeFactory.ts b/src/node/NodeFactory.ts index 8c33e744..a759ff46 100644 --- a/src/node/NodeFactory.ts +++ b/src/node/NodeFactory.ts @@ -63,7 +63,7 @@ export class NodeFactory { public static binaryExpressionNode ( operator: ESTree.BinaryOperator, left: ESTree.Expression, - right: ESTree.Expression, + right: ESTree.Expression ): ESTree.BinaryExpression { return { type: NodeType.BinaryExpression, diff --git a/src/node/NodeMetadata.ts b/src/node/NodeMetadata.ts index 702165c5..acb1e7e4 100644 --- a/src/node/NodeMetadata.ts +++ b/src/node/NodeMetadata.ts @@ -43,7 +43,10 @@ export class NodeMetadata { * @param {Node} literalNode * @returns {boolean} */ - public static isReplacedLiteral (literalNode: ESTree.Literal): boolean { - return NodeMetadata.get(literalNode, 'replacedLiteral') === true; + public static isStringArrayCallLiteralNode (literalNode: ESTree.Literal): boolean { + return NodeMetadata.get< + ESTree.LiteralNodeMetadata, + 'stringArrayCallLiteralNode' + >(literalNode, 'stringArrayCallLiteralNode') === true; } } diff --git a/src/options/Options.ts b/src/options/Options.ts index 84044afe..73028a18 100644 --- a/src/options/Options.ts +++ b/src/options/Options.ts @@ -340,6 +340,20 @@ export class Options implements IOptions { @IsBoolean() public readonly stringArray!: boolean; + /** + * @type {boolean} + */ + @IsBoolean() + public readonly stringArrayCallsTransform!: boolean; + + /** + * @type {number} + */ + @IsNumber() + @Min(0) + @Max(1) + public readonly stringArrayCallsTransformThreshold!: number; + /** * @type {TStringArrayEncoding[]} */ diff --git a/src/options/OptionsNormalizer.ts b/src/options/OptionsNormalizer.ts index 84d269cb..d28dfea3 100644 --- a/src/options/OptionsNormalizer.ts +++ b/src/options/OptionsNormalizer.ts @@ -20,6 +20,7 @@ import { SplitStringsChunkLengthRule } from './normalizer-rules/SplitStringsChun import { StringArrayRule } from './normalizer-rules/StringArrayRule'; import { StringArrayEncodingRule } from './normalizer-rules/StringArrayEncodingRule'; import { StringArrayWrappersChainedCallsRule } from './normalizer-rules/StringArrayWappersChainedCalls'; +import { StringArrayCallsTransformRule } from './normalizer-rules/StringArrayCallsTransform'; @injectable() export class OptionsNormalizer implements IOptionsNormalizer { @@ -39,6 +40,7 @@ export class OptionsNormalizer implements IOptionsNormalizer { SourceMapBaseUrlRule, SourceMapFileNameRule, SplitStringsChunkLengthRule, + StringArrayCallsTransformRule, StringArrayRule, StringArrayEncodingRule, StringArrayWrappersChainedCallsRule, diff --git a/src/options/normalizer-rules/StringArrayCallsTransform.ts b/src/options/normalizer-rules/StringArrayCallsTransform.ts new file mode 100644 index 00000000..e606ba28 --- /dev/null +++ b/src/options/normalizer-rules/StringArrayCallsTransform.ts @@ -0,0 +1,19 @@ +import { TOptionsNormalizerRule } from '../../types/options/TOptionsNormalizerRule'; + +import { IOptions } from '../../interfaces/options/IOptions'; + +/** + * @param {IOptions} options + * @returns {IOptions} + */ +export const StringArrayCallsTransformRule: TOptionsNormalizerRule = (options: IOptions): IOptions => { + if (!options.stringArrayCallsTransform) { + options = { + ...options, + stringArrayCallsTransform: false, + stringArrayCallsTransformThreshold: 0 + }; + } + + return options; +}; diff --git a/src/options/normalizer-rules/StringArrayRule.ts b/src/options/normalizer-rules/StringArrayRule.ts index dd9ff051..10280982 100644 --- a/src/options/normalizer-rules/StringArrayRule.ts +++ b/src/options/normalizer-rules/StringArrayRule.ts @@ -15,6 +15,8 @@ export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOpt rotateStringArray: false, shuffleStringArray: false, stringArray: false, + stringArrayCallsTransform: false, + stringArrayCallsTransformThreshold: 0, stringArrayEncoding: [ StringArrayEncoding.None ], diff --git a/src/options/presets/Default.ts b/src/options/presets/Default.ts index de79de29..0e8dc451 100644 --- a/src/options/presets/Default.ts +++ b/src/options/presets/Default.ts @@ -51,6 +51,8 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({ splitStrings: false, splitStringsChunkLength: 10, stringArray: true, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 0.5, stringArrayEncoding: [ StringArrayEncoding.None ], diff --git a/src/options/presets/HighObfuscation.ts b/src/options/presets/HighObfuscation.ts index 0a01af73..e704b890 100644 --- a/src/options/presets/HighObfuscation.ts +++ b/src/options/presets/HighObfuscation.ts @@ -13,6 +13,7 @@ export const HIGH_OBFUSCATION_PRESET: TInputOptions = Object.freeze({ debugProtectionInterval: true, optionsPreset: OptionsPreset.HighObfuscation, splitStringsChunkLength: 5, + stringArrayCallsTransformThreshold: 1, stringArrayEncoding: [ StringArrayEncoding.Rc4 ], diff --git a/src/options/presets/MediumObfuscation.ts b/src/options/presets/MediumObfuscation.ts index a88044e6..721a1cfe 100644 --- a/src/options/presets/MediumObfuscation.ts +++ b/src/options/presets/MediumObfuscation.ts @@ -14,6 +14,7 @@ export const MEDIUM_OBFUSCATION_PRESET: TInputOptions = Object.freeze({ optionsPreset: OptionsPreset.MediumObfuscation, splitStrings: true, splitStringsChunkLength: 10, + stringArrayCallsTransformThreshold: 0.75, stringArrayEncoding: [ StringArrayEncoding.Base64 ], diff --git a/src/options/presets/NoCustomNodes.ts b/src/options/presets/NoCustomNodes.ts index 647dcde2..705f995a 100644 --- a/src/options/presets/NoCustomNodes.ts +++ b/src/options/presets/NoCustomNodes.ts @@ -47,6 +47,8 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({ splitStrings: false, splitStringsChunkLength: 0, stringArray: false, + stringArrayCallsTransform: false, + stringArrayCallsTransformThreshold: 0, stringArrayEncoding: [ StringArrayEncoding.None ], diff --git a/src/storages/string-array-transformers/VisitedLexicalScopeNodesStackStorage.ts b/src/storages/string-array-transformers/VisitedLexicalScopeNodesStackStorage.ts index b2b32550..80a5883e 100644 --- a/src/storages/string-array-transformers/VisitedLexicalScopeNodesStackStorage.ts +++ b/src/storages/string-array-transformers/VisitedLexicalScopeNodesStackStorage.ts @@ -43,9 +43,7 @@ export class VisitedLexicalScopeNodesStackStorage extends ArrayStorage (array: T[]): T | undefined { + return this.getLastElementByIndex(array, 0); + } + + /** + * @param {T[]} array + * @param {number} index + * @returns {T | undefined} + */ + public getLastElementByIndex (array: T[], index: number): T | undefined { const arrayLength: number = array.length; - return array[arrayLength - 1] ?? undefined; + return array[arrayLength - 1 - index] ?? undefined; } /** diff --git a/test/dev/dev.ts b/test/dev/dev.ts index cdb55b46..66121454 100644 --- a/test/dev/dev.ts +++ b/test/dev/dev.ts @@ -1,25 +1,52 @@ 'use strict'; -import { readFileAsString } from '../helpers/readFileAsString'; +import { StringArrayWrappersType } from '../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType'; +import { StringArrayEncoding } from '../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding'; (function () { const JavaScriptObfuscator: any = require('../../index'); - const code: string = readFileAsString(__dirname + '/../functional-tests/javascript-obfuscator/fixtures/custom-nodes-identifier-names-collision.js'); - let obfuscationResult = JavaScriptObfuscator.obfuscate( - code, + let obfuscatedCode: string = JavaScriptObfuscator.obfuscate( + ` + class Test { + constructor () { + let test = {} + } + + static methodA = () => { + console.log('method_A'); + } + + methodB () { + console.log('method_B'); + console.log('method_C'); + console.log('method_D'); + console.log('method_E'); + + Test.methodA(); + } + } + + const instance = new Test(); + + Test.methodA(); + instance.methodB(); + `, { identifierNamesGenerator: 'mangled', compact: false, + simplify: false, stringArray: true, - seed: 429105580 + stringArrayThreshold: 1, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 1, + stringArrayWrappersCount: 2, + stringArrayWrappersParametersMaxCount: 5, + stringArrayWrappersType: StringArrayWrappersType.Function, + stringArrayEncoding: [StringArrayEncoding.Rc4] } - ); - - let obfuscatedCode: string = obfuscationResult.getObfuscatedCode(); - let identifierNamesCache = obfuscationResult.getIdentifierNamesCache(); + ).getObfuscatedCode(); console.log(obfuscatedCode); console.log(eval(obfuscatedCode)); - console.log(identifierNamesCache); -})(); \ No newline at end of file +})(); diff --git a/test/functional-tests/options/OptionsNormalizer.spec.ts b/test/functional-tests/options/OptionsNormalizer.spec.ts index de6d72c2..671bddb7 100644 --- a/test/functional-tests/options/OptionsNormalizer.spec.ts +++ b/test/functional-tests/options/OptionsNormalizer.spec.ts @@ -741,6 +741,8 @@ describe('OptionsNormalizer', () => { ...getDefaultOptions(), shuffleStringArray: true, stringArray: false, + stringArrayCallsTransform: true, + stringArrayCallsTransformThreshold: 1, stringArrayEncoding: [StringArrayEncoding.Rc4], stringArrayIndexShift: true, stringArrayWrappersChainedCalls: true, @@ -753,6 +755,8 @@ describe('OptionsNormalizer', () => { ...getDefaultOptions(), shuffleStringArray: false, stringArray: false, + stringArrayCallsTransform: false, + stringArrayCallsTransformThreshold: 0, stringArrayEncoding: [StringArrayEncoding.None], stringArrayIndexShift: false, stringArrayWrappersChainedCalls: false, @@ -767,6 +771,26 @@ describe('OptionsNormalizer', () => { }); }); + describe('stringArrayCallsTransformRule', () => { + before(() => { + optionsPreset = getNormalizedOptions({ + ...getDefaultOptions(), + stringArrayCallsTransform: false, + stringArrayCallsTransformThreshold: 1 + }); + + expectedOptionsPreset = { + ...getDefaultOptions(), + stringArrayCallsTransform: false, + stringArrayCallsTransformThreshold: 0 + }; + }); + + it('should normalize options preset', () => { + assert.deepEqual(optionsPreset, expectedOptionsPreset); + }); + }); + describe('stringArrayEncodingRule', () => { before(() => { optionsPreset = getNormalizedOptions({ diff --git a/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts b/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts index ca0ddd29..fe3b8781 100644 --- a/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts +++ b/test/unit-tests/node/node-metadata/NodeMetadata.spec.ts @@ -11,7 +11,7 @@ describe('NodeMetadata', () => { describe('set', () => { const expectedMetadata: ESTree.LiteralNodeMetadata = { ignoredNode: true, - replacedLiteral: true + stringArrayCallLiteralNode: true }; let node: ESTree.Literal; @@ -20,7 +20,7 @@ describe('NodeMetadata', () => { node = NodeFactory.literalNode('foo'); NodeMetadata.set(node, { ignoredNode: true, - replacedLiteral: true + stringArrayCallLiteralNode: true }) }); @@ -38,8 +38,11 @@ describe('NodeMetadata', () => { before(() => { node = NodeFactory.literalNode('foo'); node.metadata = {}; - node.metadata.replacedLiteral = true; - value = NodeMetadata.get(node, 'replacedLiteral'); + node.metadata.stringArrayCallLiteralNode = true; + value = NodeMetadata.get( + node, + 'stringArrayCallLiteralNode' + ); }); it('should get metadata value of the node', () => { @@ -92,8 +95,8 @@ describe('NodeMetadata', () => { before(() => { node = NodeFactory.literalNode('foo'); node.metadata = {}; - node.metadata.replacedLiteral = true; - value = NodeMetadata.isReplacedLiteral(node); + node.metadata.stringArrayCallLiteralNode = true; + value = NodeMetadata.isStringArrayCallLiteralNode(node); }); it('should return metadata value', () => { diff --git a/test/unit-tests/utils/ArrayUtils.spec.ts b/test/unit-tests/utils/ArrayUtils.spec.ts index e5ba41d1..0d8d1419 100644 --- a/test/unit-tests/utils/ArrayUtils.spec.ts +++ b/test/unit-tests/utils/ArrayUtils.spec.ts @@ -215,6 +215,68 @@ describe('ArrayUtils', () => { }); }); + describe('getLastElementByIndex', () => { + describe('empty array', () => { + const array: string[] = []; + const expectedLastElement: undefined = undefined; + + let lastElement: string | undefined; + + before(() => { + lastElement = arrayUtils.getLastElementByIndex(array, 1); + }); + + it('should return undefined if array is empty', () => { + assert.equal(lastElement, expectedLastElement); + }); + }); + + describe('array length: `1` and index is out of array boundary', () => { + const array: string[] = ['foo']; + const expectedLastElement: undefined = undefined; + + let lastElement: string | undefined; + + before(() => { + lastElement = arrayUtils.getLastElementByIndex(array, 2); + }); + + it('should return undefined', () => { + assert.equal(lastElement, expectedLastElement); + }); + }); + + describe('array length: `3` and index is `0`', () => { + const array: string[] = ['foo', 'bar', 'baz']; + const expectedLastElement: string = 'baz'; + + let lastElement: string | undefined; + + before(() => { + lastElement = arrayUtils.getLastElementByIndex(array, 0); + }); + + it('should return element with a correct index', () => { + assert.equal(lastElement, expectedLastElement); + }); + }); + + describe('array length: `3` and index is `1`', () => { + const array: string[] = ['foo', 'bar', 'baz']; + const expectedLastElement: string = 'bar'; + + let lastElement: string | undefined; + + before(() => { + lastElement = arrayUtils.getLastElementByIndex(array, 1); + }); + + it('should return element with a correct index', () => { + assert.equal(lastElement, expectedLastElement); + }); + }); + }); + describe('rotate', () => { let array: number[], rotatedArray: number[];