From 54cbdf6d27041f5a3eed8d2e5a5ff04abf9e1f11 Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 5 Sep 2023 11:26:01 +0200 Subject: [PATCH] Add target 'service-worker' (#1134) --- .../AbstractCustomCodeHelper.ts | 16 +- .../GlobalVariableServiceWorkerTemplate.ts | 6 + .../ConsoleOutputDisableCodeHelper.ts | 8 +- src/enums/ObfuscationTarget.ts | 4 +- src/options/Options.ts | 2 +- ...ebugProtectionFunctionCallTemplate.spec.ts | 37 +++- .../ClassDeclaration.spec.ts | 179 ++++++++++++++++++ .../options/domain-lock/Validation.spec.ts | 42 ++-- 8 files changed, 268 insertions(+), 26 deletions(-) create mode 100644 src/custom-code-helpers/common/templates/GlobalVariableServiceWorkerTemplate.ts diff --git a/src/custom-code-helpers/AbstractCustomCodeHelper.ts b/src/custom-code-helpers/AbstractCustomCodeHelper.ts index d43325ca..a6c55013 100644 --- a/src/custom-code-helpers/AbstractCustomCodeHelper.ts +++ b/src/custom-code-helpers/AbstractCustomCodeHelper.ts @@ -13,6 +13,9 @@ import { IRandomGenerator } from '../interfaces/utils/IRandomGenerator'; import { GlobalVariableTemplate1 } from './common/templates/GlobalVariableTemplate1'; import { GlobalVariableTemplate2 } from './common/templates/GlobalVariableTemplate2'; +import { ObfuscationTarget } from '../enums/ObfuscationTarget'; +import { GlobalVariableNoEvalTemplate } from './common/templates/GlobalVariableNoEvalTemplate'; +import { GlobalVariableServiceWorkerTemplate } from './common/templates/GlobalVariableServiceWorkerTemplate'; @injectable() export abstract class AbstractCustomCodeHelper < @@ -97,9 +100,16 @@ export abstract class AbstractCustomCodeHelper < * @returns {string} */ protected getGlobalVariableTemplate (): string { - return this.randomGenerator - .getRandomGenerator() - .pickone(AbstractCustomCodeHelper.globalVariableTemplateFunctions); + switch (this.options.target) { + case ObfuscationTarget.BrowserNoEval: + return GlobalVariableNoEvalTemplate(); + case ObfuscationTarget.ServiceWorker: + return GlobalVariableServiceWorkerTemplate(); + default: + return this.randomGenerator + .getRandomGenerator() + .pickone(AbstractCustomCodeHelper.globalVariableTemplateFunctions); + } } /** diff --git a/src/custom-code-helpers/common/templates/GlobalVariableServiceWorkerTemplate.ts b/src/custom-code-helpers/common/templates/GlobalVariableServiceWorkerTemplate.ts new file mode 100644 index 00000000..fa07480c --- /dev/null +++ b/src/custom-code-helpers/common/templates/GlobalVariableServiceWorkerTemplate.ts @@ -0,0 +1,6 @@ +/** + * @returns {string} + */ +export function GlobalVariableServiceWorkerTemplate (): string { + return `const that = typeof global === 'object' ? global : this;`; +} diff --git a/src/custom-code-helpers/console-output/ConsoleOutputDisableCodeHelper.ts b/src/custom-code-helpers/console-output/ConsoleOutputDisableCodeHelper.ts index 5c67e5e9..8f34a5bc 100644 --- a/src/custom-code-helpers/console-output/ConsoleOutputDisableCodeHelper.ts +++ b/src/custom-code-helpers/console-output/ConsoleOutputDisableCodeHelper.ts @@ -9,10 +9,8 @@ import { ICustomCodeHelperObfuscator } from '../../interfaces/custom-code-helper import { IOptions } from '../../interfaces/options/IOptions'; import { IRandomGenerator } from '../../interfaces/utils/IRandomGenerator'; -import { ObfuscationTarget } from '../../enums/ObfuscationTarget'; import { ConsoleOutputDisableTemplate } from './templates/ConsoleOutputDisableTemplate'; -import { GlobalVariableNoEvalTemplate } from '../common/templates/GlobalVariableNoEvalTemplate'; import { initializable } from '../../decorators/Initializable'; @@ -78,14 +76,10 @@ export class ConsoleOutputDisableCodeHelper extends AbstractCustomCodeHelper { * @returns {string} */ protected override getCodeHelperTemplate (): string { - const globalVariableTemplate: string = this.options.target !== ObfuscationTarget.BrowserNoEval - ? this.getGlobalVariableTemplate() - : GlobalVariableNoEvalTemplate(); - return this.customCodeHelperFormatter.formatTemplate(ConsoleOutputDisableTemplate(), { callControllerFunctionName: this.callsControllerFunctionName, consoleLogDisableFunctionName: this.consoleOutputDisableFunctionName, - globalVariableTemplate + globalVariableTemplate: this.getGlobalVariableTemplate(), }); } } diff --git a/src/enums/ObfuscationTarget.ts b/src/enums/ObfuscationTarget.ts index b4fe42e9..7fb67763 100644 --- a/src/enums/ObfuscationTarget.ts +++ b/src/enums/ObfuscationTarget.ts @@ -4,8 +4,10 @@ export const ObfuscationTarget: Readonly<{ Browser: 'browser'; BrowserNoEval: 'browser-no-eval'; Node: 'node'; + ServiceWorker: 'service-worker'; }> = Utils.makeEnum({ Browser: 'browser', BrowserNoEval: 'browser-no-eval', - Node: 'node' + Node: 'node', + ServiceWorker: 'service-worker', }); diff --git a/src/options/Options.ts b/src/options/Options.ts index 234673a5..c47f091d 100644 --- a/src/options/Options.ts +++ b/src/options/Options.ts @@ -415,7 +415,7 @@ export class Options implements IOptions { /** * @type {ObfuscationTarget} */ - @IsIn([ObfuscationTarget.Browser, ObfuscationTarget.BrowserNoEval, ObfuscationTarget.Node]) + @IsIn([ObfuscationTarget.Browser, ObfuscationTarget.BrowserNoEval, ObfuscationTarget.Node, ObfuscationTarget.ServiceWorker]) public readonly target!: TTypeFromEnum; /** diff --git a/test/functional-tests/custom-code-helpers/debug-protection/templates/debug-protection-function-call-template/DebugProtectionFunctionCallTemplate.spec.ts b/test/functional-tests/custom-code-helpers/debug-protection/templates/debug-protection-function-call-template/DebugProtectionFunctionCallTemplate.spec.ts index a8ebe185..5a617364 100644 --- a/test/functional-tests/custom-code-helpers/debug-protection/templates/debug-protection-function-call-template/DebugProtectionFunctionCallTemplate.spec.ts +++ b/test/functional-tests/custom-code-helpers/debug-protection/templates/debug-protection-function-call-template/DebugProtectionFunctionCallTemplate.spec.ts @@ -149,7 +149,40 @@ describe('DebugProtectionFunctionCallTemplate', function () { }); }); - describe('Variant #5: obfuscated code with removed debug protection code', () => { + describe('Variant #5: correctly obfuscated code with target `ServiceWorker', () => { + const expectedEvaluationResult: number = 1; + + let obfuscatedCode: string, + evaluationResult: number = 0; + + beforeEach(() => { + const code: string = readFileAsString(__dirname + '/fixtures/input.js'); + + obfuscatedCode = JavaScriptObfuscator.obfuscate( + code, + { + ...NO_ADDITIONAL_NODES_PRESET, + debugProtection: true, + target: ObfuscationTarget.ServiceWorker + } + ).getObfuscatedCode(); + + return evaluateInWorker(obfuscatedCode, evaluationTimeout) + .then((result: string | null) => { + if (!result) { + return; + } + + evaluationResult = parseInt(result, 10); + }); + }); + + it('should correctly evaluate code with enabled debug protection', () => { + assert.equal(evaluationResult, expectedEvaluationResult); + }); + }); + + describe('Variant #6: obfuscated code with removed debug protection code', () => { const expectedEvaluationResult: number = 0; let obfuscatedCode: string, @@ -182,7 +215,7 @@ describe('DebugProtectionFunctionCallTemplate', function () { }); }); - describe('Variant #6: single call of debug protection code', () => { + describe('Variant #7: single call of debug protection code', () => { const expectedEvaluationResult: number = 1; let obfuscatedCode: string, diff --git a/test/functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts b/test/functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts index 78cbcf72..2be6ed5d 100644 --- a/test/functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts +++ b/test/functional-tests/node-transformers/rename-identifiers-transformers/scope-identifiers-transformer/class-declaration/ClassDeclaration.spec.ts @@ -227,6 +227,87 @@ describe('ScopeIdentifiersTransformer ClassDeclaration identifiers', () => { }); }); }); + + describe('Variant #3: target `service-worker', () => { + describe('Variant #1: correct class name references in global scope', () => { + const classNameIdentifierRegExp: RegExp = /class A *\{/; + const outerClassNameReferenceRegExp: RegExp = /console\['log']\(A\);/; + const innerClassNameReferenceRegExp: RegExp = /return A;/; + + let obfuscatedCode: string; + + before(() => { + const code: string = readFileAsString(__dirname + '/fixtures/class-name-references-global-scope.js'); + + obfuscatedCode = JavaScriptObfuscator.obfuscate( + code, + { + ...NO_ADDITIONAL_NODES_PRESET, + target: ObfuscationTarget.ServiceWorker + } + ).getObfuscatedCode(); + }); + + it('match #1: shouldn\'t transform class name', () => { + assert.match(obfuscatedCode, classNameIdentifierRegExp); + }); + + it('match #2: shouldn\'t transform class name reference outside of class', () => { + assert.match(obfuscatedCode, outerClassNameReferenceRegExp); + }); + + it('match #3: shouldn\'t transform class name reference inside class', () => { + assert.match(obfuscatedCode, innerClassNameReferenceRegExp); + }); + }); + + describe('Variant #2: correct class name references in function scope', () => { + const classNameIdentifierRegExp: RegExp = /class (_0x[a-f0-9]{4,6}) *\{/; + const outerClassNameReferenceRegExp: RegExp = /console\['log']\((_0x[a-f0-9]{4,6})\);/; + const innerClassNameReferenceRegExp: RegExp = /return (_0x[a-f0-9]{4,6});/; + + let obfuscatedCode: string; + let classNameIdentifier: string; + let outerClassNameReferenceIdentifierName: string; + let innerClassNameReferenceIdentifierName: string; + + before(() => { + const code: string = readFileAsString(__dirname + '/fixtures/class-name-references-function-scope.js'); + + obfuscatedCode = JavaScriptObfuscator.obfuscate( + code, + { + ...NO_ADDITIONAL_NODES_PRESET, + target: ObfuscationTarget.ServiceWorker + } + ).getObfuscatedCode(); + + classNameIdentifier = getRegExpMatch(obfuscatedCode, classNameIdentifierRegExp); + outerClassNameReferenceIdentifierName = getRegExpMatch(obfuscatedCode, outerClassNameReferenceRegExp); + innerClassNameReferenceIdentifierName = getRegExpMatch(obfuscatedCode, innerClassNameReferenceRegExp); + }); + + it('match #1: should transform class name', () => { + assert.match(obfuscatedCode, classNameIdentifierRegExp); + }); + + it('match #2: should transform class name reference outside of class', () => { + assert.match(obfuscatedCode, outerClassNameReferenceRegExp); + }); + + it('match #3: should transform class name reference inside class', () => { + assert.match(obfuscatedCode, innerClassNameReferenceRegExp); + }); + + it('match #4: should generate same identifier names for class name and outer class name reference', () => { + assert.equal(classNameIdentifier, outerClassNameReferenceIdentifierName); + }); + + it('match #5: should generate same identifier names for class name and inner class name reference', () => { + assert.equal(classNameIdentifier, innerClassNameReferenceIdentifierName); + }); + }); + }); }); describe('Variant #2: `renameGlobals` option is enabled', () => { @@ -479,6 +560,104 @@ describe('ScopeIdentifiersTransformer ClassDeclaration identifiers', () => { }); }); }); + + describe('Variant #4: target: `service-worker', () => { + describe('Variant #1: correct class name references in global scope', () => { + const classNameIdentifierRegExp: RegExp = /class (_0x[a-f0-9]{4,6}) *\{/; + const outerClassNameReferenceRegExp: RegExp = /console\['log']\((_0x[a-f0-9]{4,6})\);/; + const innerClassNameReferenceRegExp: RegExp = /return (_0x[a-f0-9]{4,6});/; + + let obfuscatedCode: string; + let classNameIdentifier: string; + let outerClassNameReferenceIdentifierName: string; + let innerClassNameReferenceIdentifierName: string; + + before(() => { + const code: string = readFileAsString(__dirname + '/fixtures/class-name-references-global-scope.js'); + + obfuscatedCode = JavaScriptObfuscator.obfuscate( + code, + { + ...NO_ADDITIONAL_NODES_PRESET, + renameGlobals: true, + target: ObfuscationTarget.ServiceWorker + } + ).getObfuscatedCode(); + + classNameIdentifier = getRegExpMatch(obfuscatedCode, classNameIdentifierRegExp); + outerClassNameReferenceIdentifierName = getRegExpMatch(obfuscatedCode, outerClassNameReferenceRegExp); + innerClassNameReferenceIdentifierName = getRegExpMatch(obfuscatedCode, innerClassNameReferenceRegExp); + }); + + it('match #1: should transform class name', () => { + assert.match(obfuscatedCode, classNameIdentifierRegExp); + }); + + it('match #2: should transform class name reference outside of class', () => { + assert.match(obfuscatedCode, outerClassNameReferenceRegExp); + }); + + it('match #3: should transform class name reference inside class', () => { + assert.match(obfuscatedCode, innerClassNameReferenceRegExp); + }); + + it('match #4: should generate same identifier names for class name and outer class name reference', () => { + assert.equal(classNameIdentifier, outerClassNameReferenceIdentifierName); + }); + + it('match #5: should generate same identifier names for class name and inner class name reference', () => { + assert.equal(classNameIdentifier, innerClassNameReferenceIdentifierName); + }); + }); + + describe('Variant #2: correct class name references in function scope', () => { + const classNameIdentifierRegExp: RegExp = /class (_0x[a-f0-9]{4,6}) *\{/; + const outerClassNameReferenceRegExp: RegExp = /console\['log']\((_0x[a-f0-9]{4,6})\);/; + const innerClassNameReferenceRegExp: RegExp = /return (_0x[a-f0-9]{4,6});/; + + let obfuscatedCode: string; + let classNameIdentifier: string; + let outerClassNameReferenceIdentifierName: string; + let innerClassNameReferenceIdentifierName: string; + + before(() => { + const code: string = readFileAsString(__dirname + '/fixtures/class-name-references-function-scope.js'); + + obfuscatedCode = JavaScriptObfuscator.obfuscate( + code, + { + ...NO_ADDITIONAL_NODES_PRESET, + renameGlobals: true, + target: ObfuscationTarget.ServiceWorker + } + ).getObfuscatedCode(); + + classNameIdentifier = getRegExpMatch(obfuscatedCode, classNameIdentifierRegExp); + outerClassNameReferenceIdentifierName = getRegExpMatch(obfuscatedCode, outerClassNameReferenceRegExp); + innerClassNameReferenceIdentifierName = getRegExpMatch(obfuscatedCode, innerClassNameReferenceRegExp); + }); + + it('match #1: should transform class name', () => { + assert.match(obfuscatedCode, classNameIdentifierRegExp); + }); + + it('match #2: should transform class name reference outside of class', () => { + assert.match(obfuscatedCode, outerClassNameReferenceRegExp); + }); + + it('match #3: should transform class name reference inside class', () => { + assert.match(obfuscatedCode, innerClassNameReferenceRegExp); + }); + + it('match #4: should generate same identifier names for class name and outer class name reference', () => { + assert.equal(classNameIdentifier, outerClassNameReferenceIdentifierName); + }); + + it('match #5: should generate same identifier names for class name and inner class name reference', () => { + assert.equal(classNameIdentifier, innerClassNameReferenceIdentifierName); + }); + }); + }); }); }); diff --git a/test/functional-tests/options/domain-lock/Validation.spec.ts b/test/functional-tests/options/domain-lock/Validation.spec.ts index 76bd37c7..4ec195dd 100644 --- a/test/functional-tests/options/domain-lock/Validation.spec.ts +++ b/test/functional-tests/options/domain-lock/Validation.spec.ts @@ -50,22 +50,40 @@ describe('`domainLock` validation', () => { describe('Variant #2: negative validation', () => { const expectedError: string = 'This option allowed only for obfuscation targets'; - let testFunc: () => string; - beforeEach(() => { - testFunc = () => JavaScriptObfuscator.obfuscate( - '', - { - ...NO_ADDITIONAL_NODES_PRESET, - domainLock: ['www.example.com'], - target: ObfuscationTarget.Node - } - ).getObfuscatedCode(); + describe('Variant #1: obfuscation target: `node`', () => { + beforeEach(() => { + testFunc = () => JavaScriptObfuscator.obfuscate( + '', + { + ...NO_ADDITIONAL_NODES_PRESET, + domainLock: ['www.example.com'], + target: ObfuscationTarget.Node + } + ).getObfuscatedCode(); + }); + + it('should not pass validation when obfuscation target is `node` and value is not default', () => { + assert.throws(testFunc, expectedError); + }); }); - it('should not pass validation when obfuscation target is `node` and value is not default', () => { - assert.throws(testFunc, expectedError); + describe('Variant #1: obfuscation target: `service-worker`', () => { + beforeEach(() => { + testFunc = () => JavaScriptObfuscator.obfuscate( + '', + { + ...NO_ADDITIONAL_NODES_PRESET, + domainLock: ['www.example.com'], + target: ObfuscationTarget.ServiceWorker + } + ).getObfuscatedCode(); + }); + + it('should not pass validation when obfuscation target is `service-worker` and value is not default', () => { + assert.throws(testFunc, expectedError); + }); }); }); });