StringLiteralObfuscatingReplacer.ts 8.7 KB

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