Skip to content

Test: try to fix unescape of escape sequences #775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,11 @@ export class StringArrayStorageAnalyzer implements IStringArrayStorageAnalyzer {
return;
}

this.stringArrayStorageData.set(
literalNode,
this.stringArrayStorage.getOrThrow(literalNode.value)
);
// we have to use `raw` value here because it can contains unicode escape sequence
const key: string = NodeLiteralUtils.getUnwrappedLiteralNodeRawValue(literalNode);
const stringArrayStorageItemData: IStringArrayStorageItemData = this.stringArrayStorage.getOrThrow(key);

this.stringArrayStorageData.set(literalNode, stringArrayStorageItemData);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
}

const literalValue: ESTree.SimpleLiteral['value'] = literalNode.value;
const literalRawValue: ESTree.SimpleLiteral['raw'] = literalNode.raw;

const stringArrayStorageItemData: IStringArrayStorageItemData | undefined = this.stringArrayStorageAnalyzer
.getItemDataForLiteralNode(literalNode);
const cacheKey: string = `${literalValue}-${Boolean(stringArrayStorageItemData)}`;
const cacheKey: string = `${literalRawValue}-${Boolean(stringArrayStorageItemData)}`;
const useCachedValue: boolean = this.nodesCache.has(cacheKey)
&& stringArrayStorageItemData?.encoding !== StringArrayEncoding.Rc4;

Expand All @@ -161,7 +162,7 @@ export class StringArrayTransformer extends AbstractNodeTransformer {

const resultNode: ESTree.Node = stringArrayStorageItemData
? this.getStringArrayCallNode(stringArrayStorageItemData)
: this.getLiteralNode(literalValue);
: this.getLiteralNode(literalValue, literalRawValue ?? literalValue);

this.nodesCache.set(cacheKey, resultNode);

Expand All @@ -172,10 +173,11 @@ export class StringArrayTransformer extends AbstractNodeTransformer {

/**
* @param {string} value
* @param {string} rawValue
* @returns {Node}
*/
private getLiteralNode (value: string): ESTree.Node {
return NodeFactory.literalNode(value);
private getLiteralNode (value: string, rawValue: string): ESTree.Node {
return NodeFactory.literalNode(value, rawValue);
}

/**
Expand Down Expand Up @@ -217,8 +219,10 @@ export class StringArrayTransformer extends AbstractNodeTransformer {
return literalNode;
}

const valueToEncode: string = NodeLiteralUtils.getUnwrappedLiteralNodeRawValue(literalNode);

return NodeFactory.literalNode(
this.escapeSequenceEncoder.encode(literalNode.value, this.options.unicodeEscapeSequence)
this.escapeSequenceEncoder.encode(valueToEncode, this.options.unicodeEscapeSequence)
);
}
}
14 changes: 14 additions & 0 deletions src/node/NodeLiteralUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@ import * as ESTree from 'estree';
import { NodeGuards } from './NodeGuards';

export class NodeLiteralUtils {
/**
* @param {Literal} literalNode
* @returns {string}
*/
public static getUnwrappedLiteralNodeRawValue (literalNode: ESTree.Literal): string {
if (typeof literalNode.value !== 'string') {
throw new Error('Allowed only literal nodes with `string` values');
}

return literalNode.raw
? literalNode.raw.slice(1, -1)
: literalNode.value;
}

/**
* @param {Literal} literalNode
* @param {Node} parentNode
Expand Down
59 changes: 53 additions & 6 deletions src/utils/EscapeSequenceEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import { IEscapeSequenceEncoder } from '../interfaces/utils/IEscapeSequenceEncod

@injectable()
export class EscapeSequenceEncoder implements IEscapeSequenceEncoder {
/**
* @type {string}
*/
private static readonly unicodeLeadingCharacter: string = '\\u';

/**
* @type {string}
*/
private static readonly hexLeadingCharacter: string = '\\x';

/**
* @type {Map<string, string>}
*/
Expand All @@ -21,6 +31,24 @@ export class EscapeSequenceEncoder implements IEscapeSequenceEncoder {
return <string>this.stringsCache.get(cacheKey);
}

const encodedString: string = this.isEscapedString(string)
? this.encodeEscapedString(string)
: this.encodeBaseString(string, encodeAllSymbols);

this.stringsCache.set(cacheKey, encodedString);
this.stringsCache.set(`${encodedString}-${String(encodeAllSymbols)}`, encodedString);

return encodedString;
}

/**
* Base string
*
* @param {string} string
* @param {boolean} encodeAllSymbols
* @returns {string}
*/
public encodeBaseString (string: string, encodeAllSymbols: boolean): string {
const radix: number = 16;
const replaceRegExp: RegExp = new RegExp('[\\s\\S]', 'g');
const escapeSequenceRegExp: RegExp = new RegExp('[\'\"\\\\\\s]');
Expand All @@ -29,25 +57,44 @@ export class EscapeSequenceEncoder implements IEscapeSequenceEncoder {
let prefix: string;
let template: string;

const result: string = string.replace(replaceRegExp, (character: string): string => {
return string.replace(replaceRegExp, (character: string): string => {
if (!encodeAllSymbols && !escapeSequenceRegExp.exec(character)) {
return character;
}

if (regExp.exec(character)) {
prefix = '\\x';
prefix = EscapeSequenceEncoder.hexLeadingCharacter;
template = '00';
} else {
prefix = '\\u';
prefix = EscapeSequenceEncoder.unicodeLeadingCharacter;
template = '0000';
}

return `${prefix}${(template + character.charCodeAt(0).toString(radix)).slice(-template.length)}`;
});
}

/**
* String with escaped unicode escape sequence
*
* Example:
* \\ud83d\\ude03
*
* @param {string} string
* @returns {string}
*/
public encodeEscapedString (string: string): string {
return string;
}

this.stringsCache.set(cacheKey, result);
this.stringsCache.set(`${result}-${String(encodeAllSymbols)}`, result);
/**
* @param {string} string
* @returns {boolean}
*/
private isEscapedString (string: string): boolean {
const unicodeLeadingCharacterRegExp: RegExp = new RegExp(`\\\\${EscapeSequenceEncoder.unicodeLeadingCharacter}`);
const hexLeadingCharacterRegExp: RegExp = new RegExp(`\\\\${EscapeSequenceEncoder.hexLeadingCharacter}`);

return result;
return unicodeLeadingCharacterRegExp.test(string) || hexLeadingCharacterRegExp.test(string);
}
}
12 changes: 6 additions & 6 deletions test/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import { NO_ADDITIONAL_NODES_PRESET } from '../../src/options/presets/NoCustomNo

let obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
`
var string1 = '👋🏼';

console.log(string1);
console.log("\\ud83d\\ude03\\ud83d\\ude03\\ud83d\\ude03");
console.log("😃😃😃");
console.log("abc efgx");
`,
{
...NO_ADDITIONAL_NODES_PRESET,
compact: false,
stringArray: true,
unicodeEscapeSequence: false,
stringArray: false,
stringArrayThreshold: 1,
splitStrings: true,
splitStringsChunkLength: 1,
unicodeEscapeSequence: true
splitStringsChunkLength: 1
}
).getObfuscatedCode();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -815,4 +815,110 @@ describe('StringArrayTransformer', function () {
assert.match(obfuscatedCode, exportNamedDeclarationRegExp);
});
});

describe('Variant #20: already escaped string with unicode escape sequence', () => {
describe('Variant #1: unicode escape sequence', () => {
describe('Variant #1: `unicodeEscapeSequence` option is disabled', () => {
const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\ud83d\\ude03\\ud83d\\ude03\\ud83d\\ude03'];/;

let obfuscatedCode: string;

before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/escaped-with-unicode-escape-sequence-1.js');

obfuscatedCode = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
stringArray: true,
stringArrayThreshold: 1,
unicodeEscapeSequence: false

}
).getObfuscatedCode();
});

it('should keep unicode escape sequence on the string after adding to the string array', () => {
assert.match(obfuscatedCode, stringArrayRegExp);
});
});

describe('Variant #2: `unicodeEscapeSequence` option is enabled', () => {
const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\ud83d\\ude03\\ud83d\\ude03\\ud83d\\ude03'];/;

let obfuscatedCode: string;

before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/escaped-with-unicode-escape-sequence-1.js');

obfuscatedCode = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
stringArray: true,
stringArrayThreshold: 1,
unicodeEscapeSequence: true

}
).getObfuscatedCode();
});

it('should keep unicode escape sequence on the string after adding to the string array', () => {
assert.match(obfuscatedCode, stringArrayRegExp);
});
});
});

describe('Variant #2: hex escape sequence', () => {
describe('Variant #1: `unicodeEscapeSequence` option is disabled', () => {
const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x48\\x65\\x6c\\x6c\\x6f'];/;

let obfuscatedCode: string;

before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/escaped-with-unicode-escape-sequence-2.js');

obfuscatedCode = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
stringArray: true,
stringArrayThreshold: 1,
unicodeEscapeSequence: false

}
).getObfuscatedCode();
});

it('should keep unicode escape sequence on the string after adding to the string array', () => {
assert.match(obfuscatedCode, stringArrayRegExp);
});
});

describe('Variant #2: `unicodeEscapeSequence` option is enabled', () => {
const stringArrayRegExp: RegExp = /^var _0x([a-f0-9]){4} *= *\['\\x48\\x65\\x6c\\x6c\\x6f'];/;

let obfuscatedCode: string;

before(() => {
const code: string = readFileAsString(__dirname + '/fixtures/escaped-with-unicode-escape-sequence-2.js');

obfuscatedCode = JavaScriptObfuscator.obfuscate(
code,
{
...NO_ADDITIONAL_NODES_PRESET,
stringArray: true,
stringArrayThreshold: 1,
unicodeEscapeSequence: true

}
).getObfuscatedCode();
});

it('should keep unicode escape sequence on the string after adding to the string array', () => {
assert.match(obfuscatedCode, stringArrayRegExp);
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var test = '\ud83d\ude03\ud83d\ude03\ud83d\ude03';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
var test = '\x48\x65\x6c\x6c\x6f';
50 changes: 49 additions & 1 deletion test/unit-tests/utils/EscapeSequenceEncoder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'reflect-metadata';

import { assert } from 'chai';

import { InversifyContainerFacade } from '../../../src/container/InversifyContainerFacade';
Expand All @@ -6,7 +8,6 @@ import { ServiceIdentifiers } from '../../../src/container/ServiceIdentifiers';
import { IInversifyContainerFacade } from '../../../src/interfaces/container/IInversifyContainerFacade';
import { IEscapeSequenceEncoder } from '../../../src/interfaces/utils/IEscapeSequenceEncoder';


describe('EscapeSequenceEncoder', () => {
describe('encode', () => {
let escapeSequenceEncoder: IEscapeSequenceEncoder;
Expand Down Expand Up @@ -48,5 +49,52 @@ describe('EscapeSequenceEncoder', () => {
assert.equal(actualString, expectedString);
});
});

describe('Variant #3: ignore already escaped unicode string`', () => {
const string: string = '\\ud83d\\ude03';
const expectedString: string = '\\ud83d\\ude03';

let actualString: string;

before(() => {
actualString = escapeSequenceEncoder.encode(string, false);
});

it('should ignore already escaped string', () => {
assert.equal(actualString, expectedString);
});
});

describe('Variant #4: ignore already escaped hex string`', () => {
describe('Variant #1: base`', () => {
const string: string = '\\x48\\x65\\x6c\\x6c\\x6f';
const expectedString: string = '\\x48\\x65\\x6c\\x6c\\x6f';

let actualString: string;

before(() => {
actualString = escapeSequenceEncoder.encode(string, true);
});

it('should ignore already escaped string', () => {
assert.equal(actualString, expectedString);
});
});

describe('Variant #2: encode string with `x` character`', () => {
const string: string = 'xxx';
const expectedString: string = '\\x78\\x78\\x78';

let actualString: string;

before(() => {
actualString = escapeSequenceEncoder.encode(string, true);
});

it('should encode string with `x` character', () => {
assert.equal(actualString, expectedString);
});
});
});
});
});