Jelajahi Sumber

Merge branch 'dev' into control-flow-flattening

# Conflicts:
#	dist/index.js
#	src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts
#	src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts
#	src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts
#	src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts
sanex3339 8 tahun lalu
induk
melakukan
eeedc9d2ad

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 Change Log
 Change Log
 ===
 ===
+v0.8.2
+---
+* New option `seed` sets seed for random generator. This is useful for creating repeatable results.
+* IE8 runtime error fix.
+
 v0.8.1
 v0.8.1
 ---
 ---
 * `disableConsoleOutput` option now replaces `console.xxx` functions on empty function instead of infinity loop.
 * `disableConsoleOutput` option now replaces `console.xxx` functions on empty function instead of infinity loop.

+ 9 - 1
README.md

@@ -123,6 +123,7 @@ Following options available for the JS Obfuscator:
     disableConsoleOutput: true,
     disableConsoleOutput: true,
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     selfDefending: true,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',
@@ -131,7 +132,7 @@ Following options available for the JS Obfuscator:
     stringArray: true,
     stringArray: true,
     stringArrayEncoding: false,
     stringArrayEncoding: false,
     stringArrayThreshold: 0.8,
     stringArrayThreshold: 0.8,
-    unicdeEscapeSequence: true
+    unicodeEscapeSequence: true
 }
 }
 ```
 ```
 
 
@@ -148,6 +149,7 @@ Following options available for the JS Obfuscator:
     --disableConsoleOutput <boolean>
     --disableConsoleOutput <boolean>
     --reservedNames <list> (comma separated)
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
     --rotateStringArray <boolean>
+    --seed <number>
     --selfDefending <boolean>
     --selfDefending <boolean>
     --sourceMap <boolean>
     --sourceMap <boolean>
     --sourceMapBaseUrl <string>
     --sourceMapBaseUrl <string>
@@ -218,6 +220,12 @@ Shift the `stringArray` array by a fixed and random (generated at the code obfus
 
 
 This option is recommended if your original source code isn't small, as the helper function can attract attention.
 This option is recommended if your original source code isn't small, as the helper function can attract attention.
 
 
+### `seed`
+Type: `number` Default: `0`
+
+This option sets seed for random generator. This is useful for creating repeatable results.
+
+If seed is `0` - random generator will work without seed.
 
 
 ### `selfDefending`
 ### `selfDefending`
 Type: `boolean` Default: `true`
 Type: `boolean` Default: `true`

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "javascript-obfuscator",
   "name": "javascript-obfuscator",
-  "version": "0.8.1",
+  "version": "0.8.2",
   "description": "JavaScript obfuscator",
   "description": "JavaScript obfuscator",
   "keywords": [
   "keywords": [
     "obfuscator",
     "obfuscator",

+ 7 - 0
src/JavaScriptObfuscatorInternal.ts

@@ -2,6 +2,8 @@ import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 import * as ESTree from 'estree';
 
 
+import { Chance } from 'chance';
+
 import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
@@ -11,6 +13,7 @@ import { ObfuscationResult } from './ObfuscationResult';
 import { Obfuscator } from './Obfuscator';
 import { Obfuscator } from './Obfuscator';
 import { Options } from './options/Options';
 import { Options } from './options/Options';
 import { SourceMapCorrector } from './SourceMapCorrector';
 import { SourceMapCorrector } from './SourceMapCorrector';
+import { Utils } from './Utils';
 
 
 export class JavaScriptObfuscatorInternal {
 export class JavaScriptObfuscatorInternal {
     /**
     /**
@@ -91,6 +94,10 @@ export class JavaScriptObfuscatorInternal {
             loc: true
             loc: true
         });
         });
 
 
+        if (this.options.seed !== 0) {
+            Utils.setRandomGenerator(new Chance(this.options.seed));
+        }
+
         astTree = new Obfuscator(this.options).obfuscateNode(astTree);
         astTree = new Obfuscator(this.options).obfuscateNode(astTree);
 
 
         this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);
         this.generatorOutput = JavaScriptObfuscatorInternal.generateCode(this.sourceCode, astTree, this.options);

+ 42 - 7
src/Utils.ts

@@ -4,9 +4,9 @@ import { JSFuck } from './enums/JSFuck';
 
 
 export class Utils {
 export class Utils {
     /**
     /**
-     * @type {Chance.Chance}
+     * @type {Chance.Chance | Chance.SeededChance}
      */
      */
-    private static randomGenerator: Chance.Chance = new Chance();
+    private static randomGenerator: Chance.Chance | Chance.SeededChance = new Chance();
 
 
     /**
     /**
      * @param array
      * @param array
@@ -99,13 +99,44 @@ export class Utils {
         return domain;
         return domain;
     }
     }
 
 
+    /**
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    public static getRandomFloat (min: number, max: number): number {
+        return Utils.getRandomGenerator().floating({
+            min: min,
+            max: max,
+            fixed: 7
+        });
+    }
+
     /**
     /**
      * @returns {Chance.Chance}
      * @returns {Chance.Chance}
      */
      */
     public static getRandomGenerator (): Chance.Chance {
     public static getRandomGenerator (): Chance.Chance {
+        const randomGenerator: Chance.Chance = Utils.randomGenerator;
+
+        if (!randomGenerator) {
+            throw new Error(`\`randomGenerator\` static property is undefined`);
+        }
+
         return Utils.randomGenerator;
         return Utils.randomGenerator;
     }
     }
 
 
+    /**
+     * @param min
+     * @param max
+     * @returns {number}
+     */
+    public static getRandomInteger (min: number, max: number): number {
+        return Utils.getRandomGenerator().integer({
+            min: min,
+            max: max
+        });
+    }
+
     /**
     /**
      * @param length
      * @param length
      * @returns {string}
      * @returns {string}
@@ -117,10 +148,7 @@ export class Utils {
 
 
         return `${prefix}${(
         return `${prefix}${(
             Utils.decToHex(
             Utils.decToHex(
-                Utils.getRandomGenerator().integer({
-                    min: rangeMinInteger,
-                    max: rangeMaxInteger
-                })
+                Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
             )
             )
         ).substr(0, length)}`;
         ).substr(0, length)}`;
     }
     }
@@ -140,7 +168,7 @@ export class Utils {
                 result: string = '';
                 result: string = '';
 
 
             while (i1 < s1.length || i2 < s2.length) {
             while (i1 < s1.length || i2 < s2.length) {
-                if (Math.random() < 0.5 && i2 < s2.length) {
+                if (Utils.getRandomFloat(0, 1) < 0.5 && i2 < s2.length) {
                     result += s2.charAt(++i2);
                     result += s2.charAt(++i2);
                 } else {
                 } else {
                     result += s1.charAt(++i1);
                     result += s1.charAt(++i1);
@@ -213,6 +241,13 @@ export class Utils {
         return result;
         return result;
     }
     }
 
 
+    /**
+     * @param randomGenerator
+     */
+    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
+        Utils.randomGenerator = randomGenerator;
+    }
+
     /**
     /**
      * @param obj
      * @param obj
      * @returns {T}
      * @returns {T}

+ 1 - 0
src/cli/JavaScriptObfuscatorCLI.ts

@@ -152,6 +152,7 @@ export class JavaScriptObfuscatorCLI {
             .option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--domainLock <list>', 'Blocks the execution of the code in domains that do not match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--reservedNames <list>', 'Disable obfuscation of variable names, function names and names of function parameters that match the passed RegExp patterns (comma separated)', (val: string) => val.split(','))
             .option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--rotateStringArray <boolean>', 'Disable rotation of unicode array values during obfuscation', JavaScriptObfuscatorCLI.parseBoolean)
+            .option('--seed <number>', 'Sets seed for random generator. This is useful for creating repeatable results.', parseFloat)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--selfDefending <boolean>', 'Disables self-defending for obfuscated code', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
             .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')
             .option('--sourceMapBaseUrl <string>', 'Sets base url to the source map import url when `--sourceMapMode=separate`')

+ 1 - 4
src/custom-nodes/debug-protection-nodes/DebugProtectionFunctionNode.ts

@@ -38,10 +38,7 @@ export class DebugProtectionFunctionNode extends AbstractCustomNode {
      */
      */
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
     public appendNode (blockScopeNode: TNodeWithBlockStatement): void {
         let programBodyLength: number = blockScopeNode.body.length,
         let programBodyLength: number = blockScopeNode.body.length,
-            randomIndex: number = Utils.getRandomGenerator().integer({
-                min: 0,
-                max: programBodyLength
-            });
+            randomIndex: number = Utils.getRandomInteger(0, programBodyLength);
 
 
         NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
         NodeAppender.insertNodeAtIndex(blockScopeNode, this.getNode(), randomIndex);
     }
     }

+ 3 - 1
src/custom-nodes/node-calls-controller-nodes/NodeCallsControllerFunctionNode.ts

@@ -80,7 +80,9 @@ export class NodeCallsControllerFunctionNode extends AbstractCustomNode {
                 format(SingleNodeCallControllerTemplate(), {
                 format(SingleNodeCallControllerTemplate(), {
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                     singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 }),
                 }),
-                NO_CUSTOM_NODES_PRESET
+                Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                    seed: this.options.seed
+                })
             ).getObfuscatedCode();
             ).getObfuscatedCode();
         }
         }
 
 

+ 3 - 1
src/custom-nodes/self-defending-nodes/SelfDefendingUnicodeNode.ts

@@ -77,7 +77,9 @@ export class SelfDefendingUnicodeNode extends AbstractCustomNode {
                 selfDefendingFunctionName: Utils.getRandomVariableName(),
                 selfDefendingFunctionName: Utils.getRandomVariableName(),
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
                 singleNodeCallControllerFunctionName: this.callsControllerFunctionName
             }),
             }),
-            NO_CUSTOM_NODES_PRESET
+            Object.assign({},  NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })
         ).getObfuscatedCode();
         ).getObfuscatedCode();
     }
     }
 }
 }

+ 3 - 1
src/custom-nodes/string-array-nodes/StringArrayCallsWrapper.ts

@@ -85,7 +85,9 @@ export class StringArrayCallsWrapper extends AbstractCustomNode {
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayCallsWrapperName: this.stringArrayCallsWrapperName,
                 stringArrayName: this.stringArrayName
                 stringArrayName: this.stringArrayName
             }),
             }),
-            NO_CUSTOM_NODES_PRESET
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })
         ).getObfuscatedCode();
         ).getObfuscatedCode();
     }
     }
 
 

+ 3 - 1
src/custom-nodes/string-array-nodes/StringArrayRotateFunctionNode.ts

@@ -93,7 +93,9 @@ export class StringArrayRotateFunctionNode extends AbstractCustomNode {
                 stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
                 stringArrayRotateValue: Utils.decToHex(this.stringArrayRotateValue),
                 whileFunctionName
                 whileFunctionName
             }),
             }),
-            NO_CUSTOM_NODES_PRESET
+            Object.assign({}, NO_CUSTOM_NODES_PRESET, {
+                seed: this.options.seed
+            })
         ).getObfuscatedCode();
         ).getObfuscatedCode();
     }
     }
 }
 }

+ 0 - 2
src/enums/JSFuck.ts

@@ -1,6 +1,4 @@
 export const JSFuck: any = {
 export const JSFuck: any = {
-    Window: '[]["filter"]["constructor"]("return this")()',
-
     False: '![]',
     False: '![]',
     True: '!![]',
     True: '!![]',
 
 

+ 1 - 0
src/interfaces/IObfuscatorOptions.d.ts

@@ -10,6 +10,7 @@ export interface IObfuscatorOptions {
     domainLock?: string[];
     domainLock?: string[];
     reservedNames?: string[];
     reservedNames?: string[];
     rotateStringArray?: boolean;
     rotateStringArray?: boolean;
+    seed?: number;
     selfDefending?: boolean;
     selfDefending?: boolean;
     sourceMap?: boolean;
     sourceMap?: boolean;
     sourceMapBaseUrl?: string;
     sourceMapBaseUrl?: string;

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

@@ -10,6 +10,7 @@ export interface IOptions {
     readonly domainLock: string[];
     readonly domainLock: string[];
     readonly reservedNames: string[];
     readonly reservedNames: string[];
     readonly rotateStringArray: boolean;
     readonly rotateStringArray: boolean;
+    readonly seed: number;
     readonly selfDefending: boolean;
     readonly selfDefending: boolean;
     readonly sourceMap: boolean;
     readonly sourceMap: boolean;
     readonly sourceMapBaseUrl: string;
     readonly sourceMapBaseUrl: string;

+ 1 - 4
src/node-groups/StringArrayNodesGroup.ts

@@ -40,10 +40,7 @@ export class StringArrayNodesGroup extends AbstractNodesGroup {
         }
         }
 
 
         if (this.options.rotateStringArray) {
         if (this.options.rotateStringArray) {
-            this.stringArrayRotateValue = Utils.getRandomGenerator().integer({
-                min: 100,
-                max: 500
-            });
+            this.stringArrayRotateValue = Utils.getRandomInteger(100, 500);
         } else {
         } else {
             this.stringArrayRotateValue = 0;
             this.stringArrayRotateValue = 0;
         }
         }

+ 1 - 1
src/node-obfuscators/replacers/StringLiteralReplacer.ts

@@ -27,7 +27,7 @@ export class StringLiteralReplacer extends AbstractReplacer {
     public replace (nodeValue: string): string {
     public replace (nodeValue: string): string {
         const replaceWithStringArrayFlag: boolean = (
         const replaceWithStringArrayFlag: boolean = (
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && Math.random() <= this.options.stringArrayThreshold
+            && Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
         );
         );
 
 
         if (this.options.stringArray && replaceWithStringArrayFlag) {
         if (this.options.stringArray && replaceWithStringArrayFlag) {

+ 1 - 4
src/node/NodeAppender.ts

@@ -101,10 +101,7 @@ export class NodeAppender {
      * @param stackTraceRootLength
      * @param stackTraceRootLength
      */
      */
     public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
     public static getRandomStackTraceIndex (stackTraceRootLength: number): number {
-        return Utils.getRandomGenerator().integer({
-            min: 0,
-            max: Math.max(0, Math.round(stackTraceRootLength - 1))
-        });
+        return Utils.getRandomInteger(0, Math.max(0, Math.round(stackTraceRootLength - 1)));
     }
     }
 
 
     /**
     /**

+ 6 - 0
src/options/Options.ts

@@ -91,6 +91,12 @@ export class Options implements IOptions {
     @IsBoolean()
     @IsBoolean()
     public readonly rotateStringArray: boolean;
     public readonly rotateStringArray: boolean;
 
 
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly seed: number;
+
     /**
     /**
      * @type {boolean}
      * @type {boolean}
      */
      */

+ 1 - 0
src/preset-options/DefaultPreset.ts

@@ -11,6 +11,7 @@ export const DEFAULT_PRESET: IObfuscatorOptions = Object.freeze({
     domainLock: [],
     domainLock: [],
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: true,
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     selfDefending: true,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',

+ 1 - 0
src/preset-options/NoCustomNodesPreset.ts

@@ -11,6 +11,7 @@ export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     domainLock: [],
     domainLock: [],
     reservedNames: [],
     reservedNames: [],
     rotateStringArray: false,
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     selfDefending: false,
     sourceMap: false,
     sourceMap: false,
     sourceMapBaseUrl: '',
     sourceMapBaseUrl: '',

+ 2 - 2
src/templates/custom-nodes/debug-protection-nodes/debug-protection-function-node/DebugProtectionFunctionTemplate.ts

@@ -8,9 +8,9 @@ export function DebugProtectionFunctionTemplate (): string {
         var {debugProtectionFunctionName} = function () {
         var {debugProtectionFunctionName} = function () {
             function debuggerProtection (counter) {
             function debuggerProtection (counter) {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
-                    (function () {}.constructor('debugger')());
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 } else {
                 } else {
-                    [].filter.constructor(${Utils.stringToJSFuck('debugger')})();
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 }
                 }
                 
                 
                 debuggerProtection(++counter);
                 debuggerProtection(++counter);

+ 36 - 0
test/functional-tests/JavaScriptObfuscator.spec.ts

@@ -3,6 +3,7 @@ import { IObfuscationResult } from '../../src/interfaces/IObfuscationResult';
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 import { JavaScriptObfuscator } from '../../src/JavaScriptObfuscator';
 
 
 import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
 import { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { readFileAsString } from '../helpers/readFileAsString';
 
 
 const assert: Chai.AssertStatic = require('chai').assert;
 const assert: Chai.AssertStatic = require('chai').assert;
 
 
@@ -125,5 +126,40 @@ describe('JavaScriptObfuscator', () => {
             assert.match(obfuscatedCode2, pattern1);
             assert.match(obfuscatedCode2, pattern1);
             assert.match(obfuscatedCode2, pattern2);
             assert.match(obfuscatedCode2, pattern2);
         });
         });
+
+        it('should returns same code every time with same `seed`', () => {
+            const code: string = readFileAsString('./test/fixtures/sample.js');
+            const seed: number = 12345;
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: seed }
+            );
+            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: seed }
+            );
+
+            assert.equal(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
+        });
+
+        it('should returns different code with different `seed` option value', () => {
+            const code: string = readFileAsString('./test/fixtures/sample.js');
+
+            const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 12345 }
+            );
+            const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 12346 }
+            );
+
+            const obfuscationResult3: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 0 }
+            );
+            const obfuscationResult4: IObfuscationResult = JavaScriptObfuscator.obfuscate(
+                code, { seed: 0 }
+            );
+
+            assert.notEqual(obfuscationResult1.getObfuscatedCode(), obfuscationResult2.getObfuscatedCode());
+            assert.notEqual(obfuscationResult3.getObfuscatedCode(), obfuscationResult4.getObfuscatedCode());
+        });
     });
     });
 });
 });