anobis
Version:
JavaScript obfuscator
269 lines (219 loc) • 9.31 kB
text/typescript
import { injectable, inject } from 'inversify';
import { ServiceIdentifiers } from '../../../../container/ServiceIdentifiers';
import * as ESTree from 'estree';
import { ICryptUtils } from '../../../../interfaces/utils/ICryptUtils';
import { ICustomNodeGroup } from '../../../../interfaces/custom-nodes/ICustomNodeGroup';
import { IEncodedValue } from '../../../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IEncodedValue';
import { IEscapeSequenceEncoder } from '../../../../interfaces/utils/IEscapeSequenceEncoder';
import { IOptions } from '../../../../interfaces/options/IOptions';
import { IRandomGenerator } from '../../../../interfaces/utils/IRandomGenerator';
import { IStorage } from '../../../../interfaces/storages/IStorage';
import { IStringArrayIndexData } from '../../../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IStringArrayIndexData';
import { StringArrayEncoding } from '../../../../enums/StringArrayEncoding';
import { AbstractObfuscatingReplacer } from '../AbstractObfuscatingReplacer';
import { Nodes } from '../../../../node/Nodes';
import { Utils } from '../../../../utils/Utils';
export class StringLiteralObfuscatingReplacer extends AbstractObfuscatingReplacer {
/**
* @type {number}
*/
private static readonly minimumLengthForStringArray: number = 3;
/**
* @type {number}
*/
private static rc4KeyLength: number = 4;
/**
* @type {number}
*/
private static rc4KeysCount: number = 50;
/**
* @type {ICryptUtils}
*/
private readonly cryptUtils: ICryptUtils;
/**
* @type {IStorage<ICustomNodeGroup>}
*/
private readonly customNodeGroupStorage: IStorage<ICustomNodeGroup>;
/**
* @type {IEscapeSequenceEncoder}
*/
private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
/**
* @type {Map<string, ESTree.Node>}
*/
private readonly nodesCache: Map <string, ESTree.Node> = new Map();
/**
* @type {IRandomGenerator}
*/
private readonly randomGenerator: IRandomGenerator;
/**
* @type {string[]}
*/
private readonly rc4Keys: string[];
/**
* @type {Map<string, string>}
*/
private readonly stringLiteralHexadecimalIndexCache: Map <string, string> = new Map();
/**
* @type {IStorage<string>}
*/
private readonly stringArrayStorage: IStorage<string>;
/**
* @param {IStorage<ICustomNodeGroup>} customNodeGroupStorage
* @param {IStorage<string>} stringArrayStorage
* @param {IEscapeSequenceEncoder} escapeSequenceEncoder
* @param {IRandomGenerator} randomGenerator
* @param {ICryptUtils} cryptUtils
* @param {IOptions} options
*/
constructor (
customNodeGroupStorage: IStorage<ICustomNodeGroup>,
stringArrayStorage: IStorage<string>,
escapeSequenceEncoder: IEscapeSequenceEncoder,
randomGenerator: IRandomGenerator,
cryptUtils: ICryptUtils,
options: IOptions
) {
super(
options
);
this.customNodeGroupStorage = customNodeGroupStorage;
this.stringArrayStorage = stringArrayStorage;
this.escapeSequenceEncoder = escapeSequenceEncoder;
this.randomGenerator = randomGenerator;
this.cryptUtils = cryptUtils;
this.rc4Keys = this.randomGenerator.getRandomGenerator()
.n(
() => this.randomGenerator.getRandomGenerator().string({
length: StringLiteralObfuscatingReplacer.rc4KeyLength
}),
StringLiteralObfuscatingReplacer.rc4KeysCount
);
}
/**
* @param {string} hexadecimalIndex
* @returns {Literal}
*/
private static getHexadecimalLiteralNode (hexadecimalIndex: string): ESTree.Literal {
const hexadecimalLiteralNode: ESTree.Literal = Nodes.getLiteralNode(hexadecimalIndex);
hexadecimalLiteralNode.obfuscatedNode = true;
return hexadecimalLiteralNode;
}
/**
* @param {string} literalValue
* @returns {Literal}
*/
private static getRc4KeyLiteralNode (literalValue: string): ESTree.Literal {
const rc4KeyLiteralNode: ESTree.Literal = Nodes.getLiteralNode(literalValue);
rc4KeyLiteralNode.obfuscatedNode = true;
return rc4KeyLiteralNode;
}
/**
* @param {string} nodeValue
* @returns {Node}
*/
public replace (nodeValue: string): ESTree.Node {
const useStringArray: boolean = this.canUseStringArray(nodeValue);
const cacheKey: string = `${nodeValue}-${String(useStringArray)}`;
const useCacheValue: boolean = this.nodesCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.Rc4;
if (useCacheValue) {
return <ESTree.Node>this.nodesCache.get(cacheKey);
}
const resultNode: ESTree.Node = useStringArray
? this.replaceWithStringArrayCallNode(nodeValue)
: this.replaceWithLiteralNode(nodeValue);
this.nodesCache.set(cacheKey, resultNode);
return resultNode;
}
/**
* @param {string} nodeValue
* @returns {boolean}
*/
private canUseStringArray (nodeValue: string): boolean {
return (
this.options.stringArray &&
nodeValue.length >= StringLiteralObfuscatingReplacer.minimumLengthForStringArray &&
this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold
);
}
/**
* @param {string} value
* @param {number} stringArrayStorageLength
* @returns {IStringArrayIndexData}
*/
private getStringArrayHexadecimalIndex (value: string, stringArrayStorageLength: number): IStringArrayIndexData {
if (this.stringLiteralHexadecimalIndexCache.has(value)) {
return {
fromCache: true,
index: <string>this.stringLiteralHexadecimalIndexCache.get(value)
};
}
const hexadecimalRawIndex: string = Utils.decToHex(stringArrayStorageLength);
const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${hexadecimalRawIndex}`;
this.stringLiteralHexadecimalIndexCache.set(value, hexadecimalIndex);
return {
fromCache: false,
index: hexadecimalIndex
};
}
/**
* @param {string} value
* @returns {IEncodedValue}
*/
private getEncodedValue (value: string): IEncodedValue {
let encodedValue: string,
key: string | null = null;
switch (this.options.stringArrayEncoding) {
case StringArrayEncoding.Rc4:
key = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys);
encodedValue = this.cryptUtils.btoa(this.cryptUtils.rc4(value, key));
break;
case StringArrayEncoding.Base64:
encodedValue = this.cryptUtils.btoa(value);
break;
default:
encodedValue = value;
}
return { encodedValue, key };
}
/**
* @param {string} value
* @returns {Node}
*/
private replaceWithLiteralNode (value: string): ESTree.Node {
return Nodes.getLiteralNode(
this.escapeSequenceEncoder.encode(value, this.options.unicodeEscapeSequence)
);
}
/**
* @param {string} value
* @returns {Node}
*/
private replaceWithStringArrayCallNode (value: string): ESTree.Node {
const { encodedValue, key }: IEncodedValue = this.getEncodedValue(value);
const escapedValue: string = this.escapeSequenceEncoder.encode(encodedValue, this.options.unicodeEscapeSequence);
const stringArrayStorageLength: number = this.stringArrayStorage.getLength();
const rotatedStringArrayStorageId: string = Utils.stringRotate(this.stringArrayStorage.getStorageId(), 1);
const stringArrayStorageCallsWrapperName: string = `_${Utils.hexadecimalPrefix}${rotatedStringArrayStorageId}`;
const { fromCache, index }: IStringArrayIndexData = this.getStringArrayHexadecimalIndex(
escapedValue,
stringArrayStorageLength
);
if (!fromCache) {
this.stringArrayStorage.set(stringArrayStorageLength, escapedValue);
}
const callExpressionArgs: (ESTree.Expression | ESTree.SpreadElement)[] = [
StringLiteralObfuscatingReplacer.getHexadecimalLiteralNode(index)
];
if (key) {
callExpressionArgs.push(StringLiteralObfuscatingReplacer.getRc4KeyLiteralNode(
this.escapeSequenceEncoder.encode(key, this.options.unicodeEscapeSequence)
));
}
return Nodes.getCallExpressionNode(
Nodes.getIdentifierNode(stringArrayStorageCallsWrapperName),
callExpressionArgs
);
}
}