Преглед изворни кода

Merge pull request #810 from javascript-obfuscator/string-array-index-shift-option

New option: `stringArrayIndexShift`
Timofey Kachalov пре 4 година
родитељ
комит
63bbd97254
28 измењених фајлова са 525 додато и 105 уклоњено
  1. 4 0
      CHANGELOG.md
  2. 13 0
      README.md
  3. 0 0
      dist/index.browser.js
  4. 0 0
      dist/index.cli.js
  5. 0 0
      dist/index.js
  6. 3 3
      package.json
  7. 5 0
      src/cli/JavaScriptObfuscatorCLI.ts
  8. 12 2
      src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts
  9. 6 3
      src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts
  10. 1 1
      src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate.ts
  11. 10 1
      src/custom-nodes/string-array-nodes/StringArrayCallNode.ts
  12. 1 0
      src/interfaces/options/IOptions.ts
  13. 5 0
      src/interfaces/storages/string-array-transformers/IStringArrayStorage.ts
  14. 8 3
      src/node-transformers/string-array-transformers/StringArrayTransformer.ts
  15. 6 0
      src/options/Options.ts
  16. 1 0
      src/options/normalizer-rules/StringArrayRule.ts
  17. 1 0
      src/options/presets/Default.ts
  18. 1 0
      src/options/presets/NoCustomNodes.ts
  19. 28 0
      src/storages/string-array-transformers/StringArrayStorage.ts
  20. 28 10
      test/dev/dev.ts
  21. 160 58
      test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts
  22. 1 0
      test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts
  23. 210 15
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts
  24. 9 0
      test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-index-shift.js
  25. 2 0
      test/functional-tests/options/OptionsNormalizer.spec.ts
  26. 1 0
      test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts
  27. 1 1
      test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts
  28. 8 8
      yarn.lock

+ 4 - 0
CHANGELOG.md

@@ -1,5 +1,9 @@
 Change Log
 
+v2.9.0
+---
+* New option: `stringArrayIndexShift` enables additional index shift for all string array calls
+
 v2.8.1
 ---
 * Fixed incorrect rename of the identifiers of the added helpers in some rare cases. Fixed https://github.com/javascript-obfuscator/javascript-obfuscator/issues/804

+ 13 - 0
README.md

@@ -365,6 +365,7 @@ Following options are available for the JS Obfuscator:
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArrayEncoding: [],
+    stringArrayIndexShift: true,
     stringArrayWrappersCount: 1,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersType: 'variable',
@@ -418,6 +419,7 @@ Following options are available for the JS Obfuscator:
     --split-strings-chunk-length <number>
     --string-array <boolean>
     --string-array-encoding '<list>' (comma separated) [none, base64, rc4]
+    --string-array-index-shift <boolean>
     --string-array-wrappers-count <number>
     --string-array-wrappers-chained-calls <boolean>
     --string-array-wrappers-type <string> [variable, function]
@@ -987,6 +989,13 @@ stringArrayEncoding: [
 ]
 ```
 
+### `stringArrayIndexShift`
+Type: `boolean` Default: `true`
+
+##### :warning: `stringArray` option must be enabled
+
+Enables additional index shift for all string array calls
+
 ### `stringArrayWrappersCount`
 Type: `number` Default: `1`
 
@@ -1236,6 +1245,7 @@ Performance will 50-100% slower than without obfuscation
     splitStringsChunkLength: 5,
     stringArray: true,
     stringArrayEncoding: ['rc4'],
+    stringArrayIndexShift: true,
     stringArrayWrappersCount: 5,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersType: 'function',
@@ -1271,6 +1281,7 @@ Performance will 30-35% slower than without obfuscation
     splitStringsChunkLength: 10,
     stringArray: true,
     stringArrayEncoding: ['base64'],
+    stringArrayIndexShift: true,
     stringArrayWrappersCount: 2,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersType: 'variable',
@@ -1303,6 +1314,7 @@ Performance will slightly slower than without obfuscation
     splitStrings: false,
     stringArray: true,
     stringArrayEncoding: [],
+    stringArrayIndexShift: true,
     stringArrayWrappersCount: 1,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersType: 'variable',
@@ -1332,6 +1344,7 @@ Performance will slightly slower than without obfuscation
     splitStrings: false,
     stringArray: true,
     stringArrayEncoding: [],
+    stringArrayIndexShift: true,
     stringArrayWrappersCount: 1,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersType: 'variable',

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
dist/index.browser.js


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
dist/index.cli.js


Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
dist/index.js


+ 3 - 3
package.json

@@ -1,6 +1,6 @@
 {
   "name": "javascript-obfuscator",
-  "version": "2.8.1",
+  "version": "2.9.0",
   "description": "JavaScript obfuscator",
   "keywords": [
     "obfuscator",
@@ -56,7 +56,7 @@
     "@types/js-string-escape": "1.0.0",
     "@types/md5": "2.2.1",
     "@types/mkdirp": "1.0.1",
-    "@types/mocha": "8.0.3",
+    "@types/mocha": "8.0.4",
     "@types/multimatch": "4.0.0",
     "@types/node": "14.14.7",
     "@types/rimraf": "3.0.0",
@@ -71,7 +71,7 @@
     "cross-env": "7.0.2",
     "eslint": "7.13.0",
     "eslint-plugin-import": "2.22.1",
-    "eslint-plugin-jsdoc": "30.7.7",
+    "eslint-plugin-jsdoc": "30.7.8",
     "eslint-plugin-no-null": "1.0.2",
     "eslint-plugin-prefer-arrow": "1.2.2",
     "eslint-plugin-unicorn": "23.0.0",

+ 5 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -350,6 +350,11 @@ export class JavaScriptObfuscatorCLI implements IInitializable {
                 `Default: ${StringArrayEncoding.None}`,
                 ArraySanitizer
             )
+            .option(
+                '--string-array-index-shift <boolean>',
+                'Enables additional index shift for all string array calls',
+                BooleanSanitizer
+            )
             .option(
                 '--string-array-wrappers-count <number>',
                 'Sets the count of wrappers for the string array inside each root or function scope',

+ 12 - 2
src/custom-code-helpers/string-array/StringArrayCallsWrapperCodeHelper.ts

@@ -20,6 +20,12 @@ import { NodeUtils } from '../../node/NodeUtils';
 
 @injectable()
 export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper {
+    /**
+     * @type {number}
+     */
+    @initializable()
+    protected indexShiftAmount!: number;
+
     /**
      * @type {string}
      */
@@ -68,13 +74,16 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
     /**
      * @param {string} stringArrayName
      * @param {string} stringArrayCallsWrapperName
+     * @param {number} indexShiftAmount
      */
     public initialize (
         stringArrayName: string,
-        stringArrayCallsWrapperName: string
+        stringArrayCallsWrapperName: string,
+        indexShiftAmount: number
     ): void {
         this.stringArrayName = stringArrayName;
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
+        this.indexShiftAmount = indexShiftAmount;
     }
 
     /**
@@ -97,7 +106,8 @@ export class StringArrayCallsWrapperCodeHelper extends AbstractCustomCodeHelper
             this.customCodeHelperFormatter.formatTemplate(StringArrayCallsWrapperTemplate(), {
                 decodeCodeHelperTemplate,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
-                stringArrayName: this.stringArrayName
+                stringArrayName: this.stringArrayName,
+                indexShiftAmount: this.indexShiftAmount
             }),
             {
                 reservedNames: preservedNames

+ 6 - 3
src/custom-code-helpers/string-array/group/StringArrayCodeHelperGroup.ts

@@ -142,7 +142,8 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
 
             stringArrayCallsWrapperCodeHelper.initialize(
                 stringArrayName,
-                stringArrayCallsWrapperName
+                stringArrayCallsWrapperName,
+                this.stringArrayStorage.getIndexShiftAmount()
             );
 
             this.customCodeHelpers.set(stringArrayCallsWrapperCodeHelperName, stringArrayCallsWrapperCodeHelper);
@@ -151,9 +152,11 @@ export class StringArrayCodeHelperGroup extends AbstractCustomCodeHelperGroup {
         // stringArrayRotateFunction helper initialize
         const stringArrayRotateFunctionCodeHelper: ICustomCodeHelper<TInitialData<StringArrayRotateFunctionCodeHelper>> =
             this.customCodeHelperFactory(CustomCodeHelper.StringArrayRotateFunction);
-        const stringArrayRotationAmount: number = this.stringArrayStorage.getRotationAmount();
 
-        stringArrayRotateFunctionCodeHelper.initialize(stringArrayName, stringArrayRotationAmount);
+        stringArrayRotateFunctionCodeHelper.initialize(
+            stringArrayName,
+            this.stringArrayStorage.getRotationAmount()
+        );
 
         if (this.options.rotateStringArray) {
             this.customCodeHelpers.set(CustomCodeHelper.StringArrayRotateFunction, stringArrayRotateFunctionCodeHelper);

+ 1 - 1
src/custom-code-helpers/string-array/templates/string-array-calls-wrapper/StringArrayCallsWrapperTemplate.ts

@@ -4,7 +4,7 @@
 export function StringArrayCallsWrapperTemplate (): string {
     return `
         const {stringArrayCallsWrapperName} = function (index, key) {
-            index = index - 0;
+            index = index - {indexShiftAmount};
             
             let value = {stringArrayName}[index];
             

+ 10 - 1
src/custom-nodes/string-array-nodes/StringArrayCallNode.ts

@@ -30,6 +30,12 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
     @initializable()
     private index!: number;
 
+    /**
+     * @type {number}
+     */
+    @initializable()
+    private indexShiftAmount!: number;
+
     /**
      * @type {string}
      */
@@ -60,15 +66,18 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
     /**
      * @param {string} stringArrayCallsWrapperName
      * @param {number} index
+     * @param {number} indexShiftAmount
      * @param {string | null} decodeKey
      */
     public initialize (
         stringArrayCallsWrapperName: string,
         index: number,
+        indexShiftAmount: number,
         decodeKey: string | null
     ): void {
         this.stringArrayCallsWrapperName = stringArrayCallsWrapperName;
         this.index = index;
+        this.indexShiftAmount = indexShiftAmount;
         this.decodeKey = decodeKey;
     }
 
@@ -77,7 +86,7 @@ export class StringArrayCallNode extends AbstractStringArrayCallNode {
      */
     protected getNodeStructure (): TStatement[] {
         const callExpressionArgs: ESTree.Expression[] = [
-            this.getHexadecimalNode(this.index)
+            this.getHexadecimalNode(this.indexShiftAmount + this.index)
         ];
 
         if (this.decodeKey) {

+ 1 - 0
src/interfaces/options/IOptions.ts

@@ -43,6 +43,7 @@ export interface IOptions {
     readonly splitStringsChunkLength: number;
     readonly stringArray: boolean;
     readonly stringArrayEncoding: TStringArrayEncoding[];
+    readonly stringArrayIndexShift: boolean;
     readonly stringArrayWrappersChainedCalls: boolean;
     readonly stringArrayWrappersCount: number;
     readonly stringArrayWrappersType: TStringArrayWrappersType;

+ 5 - 0
src/interfaces/storages/string-array-transformers/IStringArrayStorage.ts

@@ -4,6 +4,11 @@ import { IMapStorage } from '../IMapStorage';
 import { IStringArrayStorageItemData } from './IStringArrayStorageItem';
 
 export interface IStringArrayStorage extends IMapStorage <string, IStringArrayStorageItemData> {
+    /**
+     * @returns {number}
+     */
+    getIndexShiftAmount (): number;
+
     /**
      * @returns {number}
      */

+ 8 - 3
src/node-transformers/string-array-transformers/StringArrayTransformer.ts

@@ -204,16 +204,21 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
 
     /**
      * @param {IStringArrayStorageItemData} stringArrayStorageItemData
-     * @returns {Node}
+     * @returns {Expression}
      */
-    private getStringArrayCallNode (stringArrayStorageItemData: IStringArrayStorageItemData): ESTree.Node {
+    private getStringArrayCallNode (stringArrayStorageItemData: IStringArrayStorageItemData): ESTree.Expression {
         const [stringArrayCallsWrapperName, index] = this.getStringArrayCallsWrapperData(stringArrayStorageItemData);
         const {decodeKey } = stringArrayStorageItemData;
 
         const stringArrayCallCustomNode: ICustomNode<TInitialData<StringArrayCallNode>> =
             this.stringArrayTransformerCustomNodeFactory(StringArrayCustomNode.StringArrayCallNode);
 
-        stringArrayCallCustomNode.initialize(stringArrayCallsWrapperName, index, decodeKey);
+        stringArrayCallCustomNode.initialize(
+            stringArrayCallsWrapperName,
+            index,
+            this.stringArrayStorage.getIndexShiftAmount(),
+            decodeKey
+        );
 
         const statementNode: TStatement = stringArrayCallCustomNode.getNode()[0];
 

+ 6 - 0
src/options/Options.ts

@@ -314,6 +314,12 @@ export class Options implements IOptions {
     @IsIn([StringArrayEncoding.None, StringArrayEncoding.Base64, StringArrayEncoding.Rc4], { each: true })
     public readonly stringArrayEncoding!: TStringArrayEncoding[];
 
+    /**
+     * @type {boolean}
+     */
+    @IsBoolean()
+    public readonly stringArrayIndexShift!: boolean;
+
     /**
      * @type {boolean}
      */

+ 1 - 0
src/options/normalizer-rules/StringArrayRule.ts

@@ -18,6 +18,7 @@ export const StringArrayRule: TOptionsNormalizerRule = (options: IOptions): IOpt
             stringArrayEncoding: [
                 StringArrayEncoding.None
             ],
+            stringArrayIndexShift: false,
             stringArrayWrappersChainedCalls: false,
             stringArrayWrappersCount: 0,
             stringArrayThreshold: 0

+ 1 - 0
src/options/presets/Default.ts

@@ -47,6 +47,7 @@ export const DEFAULT_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: [
         StringArrayEncoding.None
     ],
+    stringArrayIndexShift: true,
     stringArrayWrappersChainedCalls: true,
     stringArrayWrappersCount: 1,
     stringArrayWrappersType: StringArrayWrappersType.Variable,

+ 1 - 0
src/options/presets/NoCustomNodes.ts

@@ -44,6 +44,7 @@ export const NO_ADDITIONAL_NODES_PRESET: TInputOptions = Object.freeze({
     stringArrayEncoding: [
         StringArrayEncoding.None
     ],
+    stringArrayIndexShift: false,
     stringArrayWrappersChainedCalls: false,
     stringArrayWrappersCount: 0,
     stringArrayWrappersType: StringArrayWrappersType.Variable,

+ 28 - 0
src/storages/string-array-transformers/StringArrayStorage.ts

@@ -29,6 +29,16 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      */
     private static readonly maximumRotationAmount: number = 500;
 
+    /**
+     * @type {number}
+     */
+    private static readonly minimumIndexShiftAmount: number = 100;
+
+    /**
+     * @type {number}
+     */
+    private static readonly maximumIndexShiftAmount: number = 500;
+
     /**
      * @type {number}
      */
@@ -69,6 +79,11 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
      */
     private readonly rc4EncodedValuesSourcesCache: Map<string, string[]> = new Map();
 
+    /**
+     * @type {number}
+     */
+    private indexShiftAmount: number = 0;
+
     /**
      * @type {number}
      */
@@ -118,6 +133,12 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
     public initialize (): void {
         super.initialize();
 
+        this.indexShiftAmount = this.options.stringArrayIndexShift
+            ? this.randomGenerator.getRandomInteger(
+                StringArrayStorage.minimumIndexShiftAmount,
+                StringArrayStorage.maximumIndexShiftAmount
+            )
+            : 0;
         this.rotationAmount = this.options.rotateStringArray
             ? this.randomGenerator.getRandomInteger(
                 StringArrayStorage.minimumRotationAmount,
@@ -133,6 +154,13 @@ export class StringArrayStorage extends MapStorage <string, IStringArrayStorageI
         return this.getOrSetIfDoesNotExist(value);
     }
 
+    /**
+     * @returns {number}
+     */
+    public getIndexShiftAmount (): number {
+        return this.indexShiftAmount;
+    }
+
     /**
      * @returns {number}
      */

+ 28 - 10
test/dev/dev.ts

@@ -7,20 +7,38 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo
 
     let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
         `
-            const foo = { 
-                baz: 1
-            };
-            const bar = {
-                baz: 2,
-                bark: 3,
-                ...foo,
-            };
-            console.log(bar);
+            const foo = 'foo';
+            const bar = 'bar';
+            
+            function test1 () {
+                const baz = 'baz';
+                
+                function test2() {
+                    const bark = 'bark';
+                    
+                    console.log(bark);
+                }
+                
+                console.log(baz);
+                
+                test2();
+            }
+            
+            console.log(foo, bar);
+            
+            test1();
         `,
         {
             ...NO_ADDITIONAL_NODES_PRESET,
             compact: false,
-            transformObjectKeys: true
+            rotateStringArray: true,
+            shuffleStringArray: true,
+            stringArray: true,
+            stringArrayIndexShift: true,
+            stringArrayThreshold: 1,
+            stringArrayWrappersCount: 2,
+            stringArrayWrappersChainedCalls: true,
+            stringArrayWrappersType: 'function'
         }
     ).getObfuscatedCode();
 

+ 160 - 58
test/functional-tests/custom-code-helpers/string-array/templates/string-array-calls-wrapper-node-template/StringArrayCallsWrapperTemplate.spec.ts

@@ -42,84 +42,186 @@ describe('StringArrayCallsWrapperTemplate', () => {
     });
 
     describe('Variant #1: `base64` encoding', () => {
-        const index: string = '0x0';
-        const expectedDecodedValue: string = 'test1';
-
-        let decodedValue: string;
-
-        before(() => {
-            const atobPolyfill = format(AtobTemplate(), {
-                atobFunctionName
-            });
-            const atobDecodeTemplate: string = format(
-                StringArrayBase64DecodeTemplate(randomGenerator),
-                {
-                    atobPolyfill,
-                    atobFunctionName,
-                    selfDefendingCode: '',
-                    stringArrayCallsWrapperName
-                }
-            );
-            const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                decodeCodeHelperTemplate: atobDecodeTemplate,
-                stringArrayCallsWrapperName,
-                stringArrayName
-            });
-
-            decodedValue = Function(`
+        describe('Variant #1: index shift amount is `0`', () => {
+            const index: string = '0x0';
+
+            const indexShiftAmount: number = 0;
+
+            const expectedDecodedValue: string = 'test1';
+
+            let decodedValue: string;
+
+            before(() => {
+                const atobPolyfill = format(AtobTemplate(), {
+                    atobFunctionName
+                });
+                const atobDecodeTemplate: string = format(
+                    StringArrayBase64DecodeTemplate(randomGenerator),
+                    {
+                        atobPolyfill,
+                        atobFunctionName,
+                        selfDefendingCode: '',
+                        stringArrayCallsWrapperName
+                    }
+                );
+                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                    decodeCodeHelperTemplate: atobDecodeTemplate,
+                    indexShiftAmount,
+                    stringArrayCallsWrapperName,
+                    stringArrayName
+                });
+
+                decodedValue = Function(`
                 var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa('test1')}'];
             
                 ${stringArrayCallsWrapperTemplate}
                 
                 return ${stringArrayCallsWrapperName}(${index});
             `)();
+            });
+
+            it('should correctly return decoded value', () => {
+                assert.deepEqual(decodedValue, expectedDecodedValue);
+            });
         });
 
-        it('should correctly return decoded value', () => {
-            assert.deepEqual(decodedValue, expectedDecodedValue);
+        describe('Variant #2: index shift amount is `5`', () => {
+            const index: string = '0x5';
+
+            const indexShiftAmount: number = 5;
+
+            const expectedDecodedValue: string = 'test1';
+
+            let decodedValue: string;
+
+            before(() => {
+                const atobPolyfill = format(AtobTemplate(), {
+                    atobFunctionName
+                });
+                const atobDecodeTemplate: string = format(
+                    StringArrayBase64DecodeTemplate(randomGenerator),
+                    {
+                        atobPolyfill,
+                        atobFunctionName,
+                        selfDefendingCode: '',
+                        stringArrayCallsWrapperName
+                    }
+                );
+                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                    decodeCodeHelperTemplate: atobDecodeTemplate,
+                    indexShiftAmount,
+                    stringArrayCallsWrapperName,
+                    stringArrayName
+                });
+
+                decodedValue = Function(`
+                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa('test1')}'];
+            
+                ${stringArrayCallsWrapperTemplate}
+                
+                return ${stringArrayCallsWrapperName}(${index});
+            `)();
+            });
+
+            it('should correctly return decoded value', () => {
+                assert.deepEqual(decodedValue, expectedDecodedValue);
+            });
         });
     });
 
     describe('Variant #2: `rc4` encoding', () => {
-        const index: string = '0x0';
-        const key: string = 'key';
-        const expectedDecodedValue: string = 'test1';
-
-        let decodedValue: string;
-
-        before(() => {
-            const atobPolyfill = format(AtobTemplate(), {
-                atobFunctionName
-            });
-            const rc4Polyfill = format(Rc4Template(), {
-                atobFunctionName
-            });
-            const rc4decodeCodeHelperTemplate: string = format(
-                StringArrayRC4DecodeTemplate(randomGenerator),
-                {
-                    atobPolyfill,
-                    rc4Polyfill,
-                    selfDefendingCode: '',
-                    stringArrayCallsWrapperName
-                }
-            );
-            const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
-                decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
-                stringArrayCallsWrapperName,
-                stringArrayName
-            });
-
-            decodedValue = Function(`
+        describe('Variant #1: index shift amount is `0`', () => {
+            const index: string = '0x0';
+            const key: string = 'key';
+
+            const indexShiftAmount: number = 0;
+
+            const expectedDecodedValue: string = 'test1';
+
+            let decodedValue: string;
+
+            before(() => {
+                const atobPolyfill = format(AtobTemplate(), {
+                    atobFunctionName
+                });
+                const rc4Polyfill = format(Rc4Template(), {
+                    atobFunctionName
+                });
+                const rc4decodeCodeHelperTemplate: string = format(
+                    StringArrayRC4DecodeTemplate(randomGenerator),
+                    {
+                        atobPolyfill,
+                        rc4Polyfill,
+                        selfDefendingCode: '',
+                        stringArrayCallsWrapperName
+                    }
+                );
+                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                    decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                    indexShiftAmount,
+                    stringArrayCallsWrapperName,
+                    stringArrayName
+                });
+
+                decodedValue = Function(`
                 var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'];
             
                 ${stringArrayCallsWrapperTemplate}
                 
                 return ${stringArrayCallsWrapperName}('${index}', '${key}');
             `)();
+            });
+
+            it('should correctly return decoded value', () => {
+                assert.deepEqual(decodedValue, expectedDecodedValue);
+            });
         });
 
-        it('should correctly return decoded value', () => {
-            assert.deepEqual(decodedValue, expectedDecodedValue);
+        describe('Variant #2: index shift amount is `5`', () => {
+            const index: string = '0x5';
+            const key: string = 'key';
+
+            const indexShiftAmount: number = 5;
+
+            const expectedDecodedValue: string = 'test1';
+
+            let decodedValue: string;
+
+            before(() => {
+                const atobPolyfill = format(AtobTemplate(), {
+                    atobFunctionName
+                });
+                const rc4Polyfill = format(Rc4Template(), {
+                    atobFunctionName
+                });
+                const rc4decodeCodeHelperTemplate: string = format(
+                    StringArrayRC4DecodeTemplate(randomGenerator),
+                    {
+                        atobPolyfill,
+                        rc4Polyfill,
+                        selfDefendingCode: '',
+                        stringArrayCallsWrapperName
+                    }
+                );
+                const stringArrayCallsWrapperTemplate: string = format(StringArrayCallsWrapperTemplate(), {
+                    decodeCodeHelperTemplate: rc4decodeCodeHelperTemplate,
+                    indexShiftAmount,
+                    stringArrayCallsWrapperName,
+                    stringArrayName
+                });
+
+                decodedValue = Function(`
+                var ${stringArrayName} = ['${cryptUtilsSwappedAlphabet.btoa(cryptUtilsSwappedAlphabet.rc4('test1', key))}'];
+            
+                ${stringArrayCallsWrapperTemplate}
+                
+                return ${stringArrayCallsWrapperName}('${index}', '${key}');
+            `)();
+            });
+
+            it('should correctly return decoded value', () => {
+                assert.deepEqual(decodedValue, expectedDecodedValue);
+            });
         });
     });
 

+ 1 - 0
test/functional-tests/javascript-obfuscator/JavaScriptObfuscator.spec.ts

@@ -918,6 +918,7 @@ describe('JavaScriptObfuscator', () => {
                             StringArrayEncoding.Base64,
                             StringArrayEncoding.Rc4
                         ],
+                        stringArrayIndexShift: true,
                         stringArrayWrappersChainedCalls: true,
                         stringArrayWrappersCount: 10,
                         stringArrayWrappersType: StringArrayWrappersType.Function,

+ 210 - 15
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/StringArrayTransformer.spec.ts

@@ -2,6 +2,7 @@ import { assert } from 'chai';
 
 import { IdentifierNamesGenerator } from '../../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
 import { StringArrayEncoding } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayEncoding';
+import { StringArrayWrappersType } from '../../../../../src/enums/node-transformers/string-array-transformers/StringArrayWrappersType';
 
 import { NO_ADDITIONAL_NODES_PRESET } from '../../../../../src/options/presets/NoCustomNodes';
 
@@ -63,7 +64,201 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #3: same literal node values', () => {
+    describe('Variant #3: `stringArrayIndexShift` option is enabled', () => {
+        const stringArrayIndexShiftRegExp: RegExp = /_0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4,6} *- *(0x[a-z0-9]{1,3});/;
+        const stringArrayCallRegExp1: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4}\('(0x[a-z0-9]{1,3})'\) *\+ *0x1;/;
+        const stringArrayCallRegExp2: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4}\('(0x[a-z0-9]{1,3})'\) *\+ *0x2;/;
+        const stringArrayCallRegExp3: RegExp = /var _0x(?:[a-f0-9]){4,6} *= *_0x(?:[a-f0-9]){4}\('(0x[a-z0-9]{1,3})'\) *\+ *0x3;/;
+
+        const expectedEvaluationResult: string = 'foo1bar2baz3';
+
+        let obfuscatedCode: string;
+
+        let stringArrayIndexShiftIndexValue: number;
+        let stringArrayCallIndexValue1: number;
+        let stringArrayCallIndexValue2: number;
+        let stringArrayCallIndexValue3: number;
+
+        let evaluationResult: string;
+
+        describe('Variant #1: simple', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-index-shift.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayIndexShift: true
+                    }
+                ).getObfuscatedCode();
+
+                stringArrayIndexShiftIndexValue = parseInt(getRegExpMatch(obfuscatedCode, stringArrayIndexShiftRegExp), 16);
+                stringArrayCallIndexValue1 = parseInt(getRegExpMatch(obfuscatedCode, stringArrayCallRegExp1), 16);
+                stringArrayCallIndexValue2 = parseInt(getRegExpMatch(obfuscatedCode, stringArrayCallRegExp2), 16);
+                stringArrayCallIndexValue3 = parseInt(getRegExpMatch(obfuscatedCode, stringArrayCallRegExp3), 16);
+
+                evaluationResult = eval(obfuscatedCode);
+            });
+
+            it('should shift string array index in calls wrapper', () => {
+                assert.match(obfuscatedCode, stringArrayIndexShiftRegExp);
+            });
+
+            it('Match #1: should create string array call with shifted index', () => {
+                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue1);
+            });
+
+            it('Match #2: should create string array call with shifted index', () => {
+                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue2 - 1);
+            });
+
+            it('Match #3: should create string array call with shifted index', () => {
+                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue3 - 2);
+            });
+
+            it('should correctly evaluate code', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+
+        describe('Variant #2: `rotateStringArray` option is enabled', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-index-shift.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayIndexShift: true,
+                        rotateStringArray: true
+                    }
+                ).getObfuscatedCode();
+
+                stringArrayIndexShiftIndexValue = parseInt(getRegExpMatch(obfuscatedCode, stringArrayIndexShiftRegExp), 16);
+                stringArrayCallIndexValue1 = parseInt(getRegExpMatch(obfuscatedCode, stringArrayCallRegExp1), 16);
+                stringArrayCallIndexValue2 = parseInt(getRegExpMatch(obfuscatedCode, stringArrayCallRegExp2), 16);
+                stringArrayCallIndexValue3 = parseInt(getRegExpMatch(obfuscatedCode, stringArrayCallRegExp3), 16);
+
+                evaluationResult = eval(obfuscatedCode);
+            });
+
+            it('should shift string array index in calls wrapper', () => {
+                assert.match(obfuscatedCode, stringArrayIndexShiftRegExp);
+            });
+
+            it('Match #1: should create string array call with shifted index', () => {
+                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue1);
+            });
+
+            it('Match #2: should create string array call with shifted index', () => {
+                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue2 - 1);
+            });
+
+            it('Match #3: should create string array call with shifted index', () => {
+                assert.equal(stringArrayIndexShiftIndexValue, stringArrayCallIndexValue3 - 2);
+            });
+
+            it('should correctly evaluate code', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+
+        describe('Variant #3: `shuffleStringArray` option is enabled', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-index-shift.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayIndexShift: true,
+                        shuffleStringArray: true
+                    }
+                ).getObfuscatedCode();
+
+                evaluationResult = eval(obfuscatedCode);
+            });
+
+            it('should shift string array index in calls wrapper', () => {
+                assert.match(obfuscatedCode, stringArrayIndexShiftRegExp);
+            });
+
+            it('should correctly evaluate code', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+
+        describe('Variant #4: `stringArrayWrappersCount` option is enabled and type is `function`', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-index-shift.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        stringArray: true,
+                        stringArrayThreshold: 1,
+                        stringArrayIndexShift: true,
+                        stringArrayWrappersCount: 1,
+                        stringArrayWrappersType: StringArrayWrappersType.Function
+                    }
+                ).getObfuscatedCode();
+
+                evaluationResult = eval(obfuscatedCode);
+            });
+
+            it('should shift string array index in calls wrapper', () => {
+                assert.match(obfuscatedCode, stringArrayIndexShiftRegExp);
+            });
+
+            it('should correctly evaluate code', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+
+        describe('Variant #5: all string array options are enabled', () => {
+            before(() => {
+                const code: string = readFileAsString(__dirname + '/fixtures/string-array-index-shift.js');
+
+                obfuscatedCode = JavaScriptObfuscator.obfuscate(
+                    code,
+                    {
+                        ...NO_ADDITIONAL_NODES_PRESET,
+                        rotateStringArray: true,
+                        shuffleStringArray: true,
+                        stringArray: true,
+                        stringArrayEncoding: [
+                            StringArrayEncoding.Rc4
+                        ],
+                        stringArrayIndexShift: true,
+                        stringArrayThreshold: 1,
+                        stringArrayWrappersCount: 2,
+                        stringArrayWrappersChainedCalls: true,
+                        stringArrayWrappersType: 'function'
+                    }
+                ).getObfuscatedCode();
+
+                evaluationResult = eval(obfuscatedCode);
+            });
+
+            it('should shift string array index in calls wrapper', () => {
+                assert.match(obfuscatedCode, stringArrayIndexShiftRegExp);
+            });
+
+            it('should correctly evaluate code', () => {
+                assert.equal(evaluationResult, expectedEvaluationResult);
+            });
+        });
+    });
+
+    describe('Variant #4: same literal node values', () => {
         const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['test'\];/;
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
@@ -91,7 +286,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #4: short literal node value', () => {
+    describe('Variant #5: short literal node value', () => {
         const regExp: RegExp = /var test *= *'te';/;
 
         let obfuscatedCode: string;
@@ -114,7 +309,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #5: base64 encoding', () => {
+    describe('Variant #6: base64 encoding', () => {
         const stringArrayRegExp: RegExp = new RegExp(`^var _0x([a-f0-9]){4} *= *\\['${swapLettersCase('dGVzdA==')}'];`);
         const stringArrayCallRegExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0'\);/;
 
@@ -143,7 +338,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #6: rc4 encoding', () => {
+    describe('Variant #7: rc4 encoding', () => {
         describe('Variant #1: single string literal', () => {
             const regExp: RegExp = /var test *= *_0x([a-f0-9]){4}\('0x0', *'.{4}'\);/;
 
@@ -209,7 +404,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #7: none and base64 encoding', () => {
+    describe('Variant #8: none and base64 encoding', () => {
         describe('Variant #1: string array values', () => {
             const samplesCount: number = 300;
             const expectedMatchesChance: number = 0.5;
@@ -265,7 +460,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #8: none and rc4 encoding', () => {
+    describe('Variant #9: none and rc4 encoding', () => {
         describe('Variant #1: string array calls wrapper call', () => {
             const samplesCount: number = 300;
             const expectedMatchesChance: number = 0.5;
@@ -321,7 +516,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #9: base64 and rc4 encoding', () => {
+    describe('Variant #10: base64 and rc4 encoding', () => {
         describe('Variant #1: single string literal', () => {
             const samplesCount: number = 300;
             const expectedMatchesChance: number = 0.5;
@@ -377,7 +572,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #10: `stringArrayThreshold` option value', () => {
+    describe('Variant #11: `stringArrayThreshold` option value', () => {
         const samples: number = 1000;
         const stringArrayThreshold: number = 0.5;
         const delta: number = 0.1;
@@ -420,7 +615,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #11: string array calls wrapper name', () => {
+    describe('Variant #12: string array calls wrapper name', () => {
         const regExp: RegExp = /console\[b\('0x0'\)]\('a'\);/;
 
         let obfuscatedCode: string;
@@ -444,7 +639,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #12: `reservedStrings` option is enabled', () => {
+    describe('Variant #13: `reservedStrings` option is enabled', () => {
         describe('Variant #1: base `reservedStrings` values', () => {
             describe('Variant #1: single reserved string value', () => {
                 const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
@@ -566,7 +761,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #13: `forceTransformStrings` option is enabled', () => {
+    describe('Variant #14: `forceTransformStrings` option is enabled', () => {
         describe('Variant #1: base `forceTransformStrings` values', () => {
             describe('Variant #1: single force transform string value', () => {
                 const stringLiteralRegExp1: RegExp = /const foo *= *'foo';/;
@@ -787,7 +982,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #14: object expression key literal', () => {
+    describe('Variant #15: object expression key literal', () => {
         describe('Variant #1: base key literal', () => {
             const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['bar'];/;
             const objectExpressionRegExp: RegExp = /var test *= *{'foo' *: *_0x([a-f0-9]){4}\('0x0'\)};/;
@@ -845,7 +1040,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #15: import declaration source literal', () => {
+    describe('Variant #16: import declaration source literal', () => {
         const importDeclarationRegExp: RegExp = /import *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -868,7 +1063,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #16: export all declaration source literal', () => {
+    describe('Variant #17: export all declaration source literal', () => {
         const exportAllDeclarationRegExp: RegExp = /export *\* *from *'foo';/;
 
         let obfuscatedCode: string;
@@ -891,7 +1086,7 @@ describe('StringArrayTransformer', function () {
         });
     });
 
-    describe('Variant #17: export named declaration source literal', () => {
+    describe('Variant #18: export named declaration source literal', () => {
         const exportNamedDeclarationRegExp: RegExp = /export *{ *bar *} *from *'foo';/;
 
         let obfuscatedCode: string;

+ 9 - 0
test/functional-tests/node-transformers/string-array-transformers/string-array-transformer/fixtures/string-array-index-shift.js

@@ -0,0 +1,9 @@
+function test () {
+    var foo = 'foo' + 1;
+    var bar = 'bar' + 2;
+    var baz = 'baz' + 3;
+
+    return foo + bar + baz;
+}
+
+test();

+ 2 - 0
test/functional-tests/options/OptionsNormalizer.spec.ts

@@ -544,6 +544,7 @@ describe('OptionsNormalizer', () => {
                     shuffleStringArray: true,
                     stringArray: false,
                     stringArrayEncoding: [StringArrayEncoding.Rc4],
+                    stringArrayIndexShift: true,
                     stringArrayWrappersChainedCalls: true,
                     stringArrayWrappersCount: 5,
                     stringArrayThreshold: 0.5,
@@ -555,6 +556,7 @@ describe('OptionsNormalizer', () => {
                     shuffleStringArray: false,
                     stringArray: false,
                     stringArrayEncoding: [StringArrayEncoding.None],
+                    stringArrayIndexShift: false,
                     stringArrayWrappersChainedCalls: false,
                     stringArrayWrappersCount: 0,
                     stringArrayThreshold: 0,

+ 1 - 0
test/runtime-tests/JavaScriptObfuscatorRuntime.spec.ts

@@ -40,6 +40,7 @@ describe('JavaScriptObfuscator runtime eval', function () {
             StringArrayEncoding.Base64,
             StringArrayEncoding.Rc4
         ],
+        stringArrayIndexShift: true,
         stringArrayWrappersChainedCalls: true,
         stringArrayWrappersCount: 5,
         stringArrayWrappersType: StringArrayWrappersType.Function,

+ 1 - 1
test/unit-tests/analyzers/string-array-storage-analyzer/StringArrayStorageAnalyzer.spec.ts

@@ -434,7 +434,7 @@ describe('StringArrayStorageAnalyzer', () => {
                 before(() => {
                     stringArrayStorageAnalyzer = getStringArrayStorageAnalyzer({
                         stringArrayThreshold: 0.5,
-                        seed: 1
+                        seed: 3
                     });
 
                     const astTree: ESTree.Program = NodeFactory.programNode([

+ 8 - 8
yarn.lock

@@ -513,10 +513,10 @@
   dependencies:
     "@types/node" "*"
 
-"@types/[email protected].3":
-  version "8.0.3"
-  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.3.tgz#51b21b6acb6d1b923bbdc7725c38f9f455166402"
-  integrity sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg==
+"@types/[email protected].4":
+  version "8.0.4"
+  resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.4.tgz#b840c2dce46bacf286e237bfb59a29e843399148"
+  integrity sha512-M4BwiTJjHmLq6kjON7ZoI2JMlBvpY3BYSdiP6s/qCT3jb1s9/DeJF0JELpAxiVSIxXDzfNKe+r7yedMIoLbknQ==
 
 "@types/[email protected]":
   version "4.0.0"
@@ -1813,10 +1813,10 @@ [email protected]:
     resolve "^1.17.0"
     tsconfig-paths "^3.9.0"
 
[email protected].7:
-  version "30.7.7"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.7.tgz#2af2c68e5adaf8a59fc3c72081a4b137098268bb"
-  integrity sha512-DmVMJC2AbpYX7X1KhnVT1a9ex1AUvG+q9G8i6hzjp3cpjW8vmKQTUmZnRS0//W+7HvMqeb+eXPANdCOzGVVZBQ==
[email protected].8:
+  version "30.7.8"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-30.7.8.tgz#4a678c25ddb2c5732163f0258bb1d05edf34f61c"
+  integrity sha512-OWm2AYvXjCl7nRbpcw5xisfSVkpVAyp4lGqL9T+DeK4kaPm6ecnmTc/G5s1PtcRrwbaI8bIWGzwScqv5CdGyxA==
   dependencies:
     comment-parser "^0.7.6"
     debug "^4.2.0"

Неке датотеке нису приказане због велике количине промена