StringLiteralObfuscatingReplacer.ts 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { inject, injectable, } from 'inversify';
  2. import { ServiceIdentifiers } from '../../../../container/ServiceIdentifiers';
  3. import * as ESTree from 'estree';
  4. import { TStringArrayStorage } from '../../../../types/storages/TStringArrayStorage';
  5. import { ICryptUtils } from '../../../../interfaces/utils/ICryptUtils';
  6. import { IEncodedValue } from '../../../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IEncodedValue';
  7. import { IEscapeSequenceEncoder } from '../../../../interfaces/utils/IEscapeSequenceEncoder';
  8. import { IOptions } from '../../../../interfaces/options/IOptions';
  9. import { IRandomGenerator } from '../../../../interfaces/utils/IRandomGenerator';
  10. import { IStringArrayIndexData } from '../../../../interfaces/node-transformers/obfuscating-transformers/obfuscating-replacers/literal-obfuscating-replacers/IStringArrayIndexData';
  11. import { StringArrayEncoding } from '../../../../enums/StringArrayEncoding';
  12. import { AbstractObfuscatingReplacer } from '../AbstractObfuscatingReplacer';
  13. import { NodeMetadata } from '../../../../node/NodeMetadata';
  14. import { NodeFactory } from '../../../../node/NodeFactory';
  15. import { NumberUtils } from '../../../../utils/NumberUtils';
  16. import { Utils } from '../../../../utils/Utils';
  17. @injectable()
  18. export class StringLiteralObfuscatingReplacer extends AbstractObfuscatingReplacer {
  19. /**
  20. * @type {number}
  21. */
  22. private static readonly minimumLengthForStringArray: number = 3;
  23. /**
  24. * @type {number}
  25. */
  26. private static readonly rc4KeyLength: number = 4;
  27. /**
  28. * @type {number}
  29. */
  30. private static readonly rc4KeysCount: number = 50;
  31. /**
  32. * @type {ICryptUtils}
  33. */
  34. private readonly cryptUtils: ICryptUtils;
  35. /**
  36. * @type {IEscapeSequenceEncoder}
  37. */
  38. private readonly escapeSequenceEncoder: IEscapeSequenceEncoder;
  39. /**
  40. * @type {Map<string, ESTree.Node>}
  41. */
  42. private readonly nodesCache: Map <string, ESTree.Node> = new Map();
  43. /**
  44. * @type {IRandomGenerator}
  45. */
  46. private readonly randomGenerator: IRandomGenerator;
  47. /**
  48. * @type {string[]}
  49. */
  50. private readonly rc4Keys: string[];
  51. /**
  52. * @type {Map<string, string>}
  53. */
  54. private readonly stringLiteralHexadecimalIndexCache: Map <string, string> = new Map();
  55. /**
  56. * @type {TStringArrayStorage}
  57. */
  58. private readonly stringArrayStorage: TStringArrayStorage;
  59. /**
  60. * @param {TStringArrayStorage} stringArrayStorage
  61. * @param {IEscapeSequenceEncoder} escapeSequenceEncoder
  62. * @param {IRandomGenerator} randomGenerator
  63. * @param {ICryptUtils} cryptUtils
  64. * @param {IOptions} options
  65. */
  66. constructor (
  67. @inject(ServiceIdentifiers.TStringArrayStorage) stringArrayStorage: TStringArrayStorage,
  68. @inject(ServiceIdentifiers.IEscapeSequenceEncoder) escapeSequenceEncoder: IEscapeSequenceEncoder,
  69. @inject(ServiceIdentifiers.IRandomGenerator) randomGenerator: IRandomGenerator,
  70. @inject(ServiceIdentifiers.ICryptUtils) cryptUtils: ICryptUtils,
  71. @inject(ServiceIdentifiers.IOptions) options: IOptions
  72. ) {
  73. super(
  74. options
  75. );
  76. this.stringArrayStorage = stringArrayStorage;
  77. this.escapeSequenceEncoder = escapeSequenceEncoder;
  78. this.randomGenerator = randomGenerator;
  79. this.cryptUtils = cryptUtils;
  80. this.rc4Keys = this.randomGenerator.getRandomGenerator()
  81. .n(
  82. () => this.randomGenerator.getRandomGenerator().string({
  83. length: StringLiteralObfuscatingReplacer.rc4KeyLength
  84. }),
  85. StringLiteralObfuscatingReplacer.rc4KeysCount
  86. );
  87. }
  88. /**
  89. * @param {string} hexadecimalIndex
  90. * @returns {Literal}
  91. */
  92. private static getHexadecimalLiteralNode (hexadecimalIndex: string): ESTree.Literal {
  93. const hexadecimalLiteralNode: ESTree.Literal = NodeFactory.literalNode(hexadecimalIndex);
  94. NodeMetadata.set(hexadecimalLiteralNode, { replacedLiteral: true });
  95. return hexadecimalLiteralNode;
  96. }
  97. /**
  98. * @param {string} literalValue
  99. * @returns {Literal}
  100. */
  101. private static getRc4KeyLiteralNode (literalValue: string): ESTree.Literal {
  102. const rc4KeyLiteralNode: ESTree.Literal = NodeFactory.literalNode(literalValue);
  103. NodeMetadata.set(rc4KeyLiteralNode, { replacedLiteral: true });
  104. return rc4KeyLiteralNode;
  105. }
  106. /**
  107. * @param {string} nodeValue
  108. * @returns {Node}
  109. */
  110. public replace (nodeValue: string): ESTree.Node {
  111. const useStringArray: boolean = this.canUseStringArray(nodeValue);
  112. const cacheKey: string = `${nodeValue}-${String(useStringArray)}`;
  113. const useCacheValue: boolean = this.nodesCache.has(cacheKey) && this.options.stringArrayEncoding !== StringArrayEncoding.Rc4;
  114. if (useCacheValue) {
  115. return <ESTree.Node>this.nodesCache.get(cacheKey);
  116. }
  117. const resultNode: ESTree.Node = useStringArray
  118. ? this.replaceWithStringArrayCallNode(nodeValue)
  119. : this.replaceWithLiteralNode(nodeValue);
  120. this.nodesCache.set(cacheKey, resultNode);
  121. return resultNode;
  122. }
  123. /**
  124. * @param {string} nodeValue
  125. * @returns {boolean}
  126. */
  127. private canUseStringArray (nodeValue: string): boolean {
  128. return (
  129. this.options.stringArray &&
  130. nodeValue.length >= StringLiteralObfuscatingReplacer.minimumLengthForStringArray &&
  131. this.randomGenerator.getMathRandom() <= this.options.stringArrayThreshold
  132. );
  133. }
  134. /**
  135. * @param {string} value
  136. * @param {number} stringArrayStorageLength
  137. * @returns {IStringArrayIndexData}
  138. */
  139. private getStringArrayHexadecimalIndex (value: string, stringArrayStorageLength: number): IStringArrayIndexData {
  140. if (this.stringLiteralHexadecimalIndexCache.has(value)) {
  141. return {
  142. fromCache: true,
  143. index: <string>this.stringLiteralHexadecimalIndexCache.get(value)
  144. };
  145. }
  146. const hexadecimalRawIndex: string = NumberUtils.toHex(stringArrayStorageLength);
  147. const hexadecimalIndex: string = `${Utils.hexadecimalPrefix}${hexadecimalRawIndex}`;
  148. this.stringLiteralHexadecimalIndexCache.set(value, hexadecimalIndex);
  149. return {
  150. fromCache: false,
  151. index: hexadecimalIndex
  152. };
  153. }
  154. /**
  155. * @param {string} value
  156. * @returns {IEncodedValue}
  157. */
  158. private getEncodedValue (value: string): IEncodedValue {
  159. let encodedValue: string;
  160. let key: string | null = null;
  161. switch (this.options.stringArrayEncoding) {
  162. case StringArrayEncoding.Rc4:
  163. key = this.randomGenerator.getRandomGenerator().pickone(this.rc4Keys);
  164. encodedValue = this.cryptUtils.btoa(this.cryptUtils.rc4(value, key));
  165. break;
  166. case StringArrayEncoding.Base64:
  167. encodedValue = this.cryptUtils.btoa(value);
  168. break;
  169. default:
  170. encodedValue = value;
  171. }
  172. return { encodedValue, key };
  173. }
  174. /**
  175. * @param {string} value
  176. * @returns {Node}
  177. */
  178. private replaceWithLiteralNode (value: string): ESTree.Node {
  179. return NodeFactory.literalNode(
  180. this.escapeSequenceEncoder.encode(value, this.options.unicodeEscapeSequence)
  181. );
  182. }
  183. /**
  184. * @param {string} value
  185. * @returns {Node}
  186. */
  187. private replaceWithStringArrayCallNode (value: string): ESTree.Node {
  188. const { encodedValue, key }: IEncodedValue = this.getEncodedValue(value);
  189. const escapedValue: string = this.escapeSequenceEncoder.encode(encodedValue, this.options.unicodeEscapeSequence);
  190. const stringArrayStorageLength: number = this.stringArrayStorage.getLength();
  191. const stringArrayStorageCallsWrapperName: string = this.stringArrayStorage.getStorageId().split('|')[1];
  192. const { fromCache, index }: IStringArrayIndexData = this.getStringArrayHexadecimalIndex(
  193. escapedValue,
  194. stringArrayStorageLength
  195. );
  196. if (!fromCache) {
  197. this.stringArrayStorage.set(stringArrayStorageLength, escapedValue);
  198. }
  199. const callExpressionArgs: (ESTree.Expression | ESTree.SpreadElement)[] = [
  200. StringLiteralObfuscatingReplacer.getHexadecimalLiteralNode(index)
  201. ];
  202. if (key) {
  203. callExpressionArgs.push(StringLiteralObfuscatingReplacer.getRc4KeyLiteralNode(
  204. this.escapeSequenceEncoder.encode(key, this.options.unicodeEscapeSequence)
  205. ));
  206. }
  207. const stringArrayIdentifierNode: ESTree.Identifier = NodeFactory.identifierNode(stringArrayStorageCallsWrapperName);
  208. // prevent obfuscation of this identifier
  209. NodeMetadata.set(stringArrayIdentifierNode, { renamedIdentifier: true });
  210. return NodeFactory.callExpressionNode(
  211. stringArrayIdentifierNode,
  212. callExpressionArgs
  213. );
  214. }
  215. }