Sfoglia il codice sorgente

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 anni fa
parent
commit
eeedc9d2ad

+ 5 - 0
CHANGELOG.md

@@ -1,5 +1,10 @@
 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
 ---
 * `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,
     reservedNames: [],
     rotateStringArray: true,
+    seed: 0,
     selfDefending: true,
     sourceMap: false,
     sourceMapBaseUrl: '',
@@ -131,7 +132,7 @@ Following options available for the JS Obfuscator:
     stringArray: true,
     stringArrayEncoding: false,
     stringArrayThreshold: 0.8,
-    unicdeEscapeSequence: true
+    unicodeEscapeSequence: true
 }
 ```
 
@@ -148,6 +149,7 @@ Following options available for the JS Obfuscator:
     --disableConsoleOutput <boolean>
     --reservedNames <list> (comma separated)
     --rotateStringArray <boolean>
+    --seed <number>
     --selfDefending <boolean>
     --sourceMap <boolean>
     --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.
 
+### `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`
 Type: `boolean` Default: `true`

+ 1 - 1
package.json

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

+ 7 - 0
src/JavaScriptObfuscatorInternal.ts

@@ -2,6 +2,8 @@ import * as esprima from 'esprima';
 import * as escodegen from 'escodegen';
 import * as ESTree from 'estree';
 
+import { Chance } from 'chance';
+
 import { IObfuscatorOptions } from './interfaces/IObfuscatorOptions';
 import { IGeneratorOutput } from './interfaces/IGeneratorOutput';
 import { IObfuscationResult } from './interfaces/IObfuscationResult';
@@ -11,6 +13,7 @@ import { ObfuscationResult } from './ObfuscationResult';
 import { Obfuscator } from './Obfuscator';
 import { Options } from './options/Options';
 import { SourceMapCorrector } from './SourceMapCorrector';
+import { Utils } from './Utils';
 
 export class JavaScriptObfuscatorInternal {
     /**
@@ -91,6 +94,10 @@ export class JavaScriptObfuscatorInternal {
             loc: true
         });
 
+        if (this.options.seed !== 0) {
+            Utils.setRandomGenerator(new Chance(this.options.seed));
+        }
+
         astTree = new Obfuscator(this.options).obfuscateNode(astTree);
 
         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 {
     /**
-     * @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
@@ -99,13 +99,44 @@ export class Utils {
         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}
      */
     public static getRandomGenerator (): Chance.Chance {
+        const randomGenerator: Chance.Chance = Utils.randomGenerator;
+
+        if (!randomGenerator) {
+            throw new Error(`\`randomGenerator\` static property is undefined`);
+        }
+
         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
      * @returns {string}
@@ -117,10 +148,7 @@ export class Utils {
 
         return `${prefix}${(
             Utils.decToHex(
-                Utils.getRandomGenerator().integer({
-                    min: rangeMinInteger,
-                    max: rangeMaxInteger
-                })
+                Utils.getRandomInteger(rangeMinInteger, rangeMaxInteger)
             )
         ).substr(0, length)}`;
     }
@@ -140,7 +168,7 @@ export class Utils {
                 result: string = '';
 
             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);
                 } else {
                     result += s1.charAt(++i1);
@@ -213,6 +241,13 @@ export class Utils {
         return result;
     }
 
+    /**
+     * @param randomGenerator
+     */
+    public static setRandomGenerator (randomGenerator: Chance.Chance | Chance.SeededChance): void {
+        Utils.randomGenerator = randomGenerator;
+    }
+
     /**
      * @param obj
      * @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('--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('--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('--sourceMap <boolean>', 'Enables source map generation', JavaScriptObfuscatorCLI.parseBoolean)
             .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 {
         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);
     }

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

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

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

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

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

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

+ 0 - 2
src/enums/JSFuck.ts

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

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

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

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

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

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

@@ -40,10 +40,7 @@ export class StringArrayNodesGroup extends AbstractNodesGroup {
         }
 
         if (this.options.rotateStringArray) {
-            this.stringArrayRotateValue = Utils.getRandomGenerator().integer({
-                min: 100,
-                max: 500
-            });
+            this.stringArrayRotateValue = Utils.getRandomInteger(100, 500);
         } else {
             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 {
         const replaceWithStringArrayFlag: boolean = (
             nodeValue.length >= StringLiteralReplacer.minimumLengthForStringArray
-            && Math.random() <= this.options.stringArrayThreshold
+            && Utils.getRandomFloat(0, 1) <= this.options.stringArrayThreshold
         );
 
         if (this.options.stringArray && replaceWithStringArrayFlag) {

+ 1 - 4
src/node/NodeAppender.ts

@@ -101,10 +101,7 @@ export class NodeAppender {
      * @param stackTraceRootLength
      */
     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()
     public readonly rotateStringArray: boolean;
 
+    /**
+     * @type {number}
+     */
+    @IsNumber()
+    public readonly seed: number;
+
     /**
      * @type {boolean}
      */

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

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

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

@@ -11,6 +11,7 @@ export const NO_CUSTOM_NODES_PRESET: IObfuscatorOptions = Object.freeze({
     domainLock: [],
     reservedNames: [],
     rotateStringArray: false,
+    seed: 0,
     selfDefending: false,
     sourceMap: false,
     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 () {
             function debuggerProtection (counter) {
                 if (('' + counter / counter)['length'] !== 1 || counter % 20 === 0) {
-                    (function () {}.constructor('debugger')());
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 } else {
-                    [].filter.constructor(${Utils.stringToJSFuck('debugger')})();
+                    (function () {}.constructor(${Utils.stringToJSFuck('debugger')})());
                 }
                 
                 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 { NO_CUSTOM_NODES_PRESET } from '../../src/preset-options/NoCustomNodesPreset';
+import { readFileAsString } from '../helpers/readFileAsString';
 
 const assert: Chai.AssertStatic = require('chai').assert;
 
@@ -125,5 +126,40 @@ describe('JavaScriptObfuscator', () => {
             assert.match(obfuscatedCode2, pattern1);
             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());
+        });
     });
 });