DeadCodeInjectionTransformer.spec.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import { assert } from 'chai';
  2. import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
  3. import { NO_CUSTOM_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
  4. import { readFileAsString } from '../../../helpers/readFileAsString';
  5. import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
  6. describe('DeadCodeInjectionTransformer', () => {
  7. const variableMatch: string = '_0x([a-f0-9]){4,6}';
  8. const hexMatch: string = '0x[a-f0-9]';
  9. describe('transformNode (programNode: ESTree.Program, parentNode: ESTree.Node): ESTree.Node', function () {
  10. this.timeout(100000);
  11. describe('variant #1 - 5 simple block statements', () => {
  12. const regExp: RegExp = new RegExp(
  13. `if *\\(${variableMatch}\\('${hexMatch}'\\) *[=|!]== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{`+
  14. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  15. `\\} *else *\\{`+
  16. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  17. `\\}`,
  18. 'g'
  19. );
  20. const expectedMatchesLength: number = 5;
  21. let matchesLength: number = 0;
  22. before(() => {
  23. const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
  24. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  25. code,
  26. {
  27. ...NO_CUSTOM_NODES_PRESET,
  28. deadCodeInjection: true,
  29. deadCodeInjectionThreshold: 1,
  30. stringArray: true,
  31. stringArrayThreshold: 1
  32. }
  33. );
  34. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  35. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
  36. if (matches) {
  37. matchesLength = matches.length;
  38. }
  39. });
  40. it('should replace block statements with condition with original block statements and dead code', () => {
  41. assert.equal(matchesLength, expectedMatchesLength);
  42. });
  43. });
  44. describe('variant #2 - block statements count is less than `5`', () => {
  45. const regexp: RegExp = new RegExp(
  46. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  47. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  48. `\\};`,
  49. 'g'
  50. );
  51. const expectedMatchesLength: number = 4;
  52. let matchesLength: number = 0;
  53. before(() => {
  54. const code: string = readFileAsString(__dirname + '/fixtures/block-statements-min-count.js');
  55. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  56. code,
  57. {
  58. ...NO_CUSTOM_NODES_PRESET,
  59. deadCodeInjection: true,
  60. deadCodeInjectionThreshold: 1,
  61. stringArray: true,
  62. stringArrayThreshold: 1
  63. }
  64. );
  65. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  66. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  67. if (matches) {
  68. matchesLength = matches.length;
  69. }
  70. });
  71. it('shouldn\'t add dead code', () => {
  72. assert.equal(matchesLength, expectedMatchesLength);
  73. });
  74. });
  75. describe('variant #3 - deadCodeInjectionThreshold: 0', () => {
  76. const regexp: RegExp = new RegExp(
  77. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  78. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  79. `\\};`,
  80. 'g'
  81. );
  82. const expectedMatchesLength: number = 5;
  83. let matchesLength: number = 0;
  84. before(() => {
  85. const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
  86. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  87. code,
  88. {
  89. ...NO_CUSTOM_NODES_PRESET,
  90. deadCodeInjection: true,
  91. deadCodeInjectionThreshold: 0,
  92. stringArray: true,
  93. stringArrayThreshold: 1
  94. }
  95. );
  96. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  97. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  98. if (matches) {
  99. matchesLength = matches.length;
  100. }
  101. });
  102. it('shouldn\'t add dead code', () => {
  103. assert.equal(matchesLength, expectedMatchesLength);
  104. });
  105. });
  106. describe('variant #4 - break or continue statement in block statement', () => {
  107. const functionRegExp: RegExp = new RegExp(
  108. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  109. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  110. `\\};`,
  111. 'g'
  112. );
  113. const loopRegExp: RegExp = new RegExp(
  114. `for *\\(var *${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *\\{` +
  115. `(?:continue|break);` +
  116. `\\}`,
  117. 'g'
  118. );
  119. const expectedFunctionMatchesLength: number = 4;
  120. const expectedLoopMatchesLength: number = 2;
  121. let functionMatchesLength: number = 0,
  122. loopMatchesLength: number = 0;
  123. before(() => {
  124. const code: string = readFileAsString(__dirname + '/fixtures/break-continue-statement.js');
  125. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  126. code,
  127. {
  128. ...NO_CUSTOM_NODES_PRESET,
  129. deadCodeInjection: true,
  130. deadCodeInjectionThreshold: 1,
  131. stringArray: true,
  132. stringArrayThreshold: 1
  133. }
  134. );
  135. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  136. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  137. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
  138. if (functionMatches) {
  139. functionMatchesLength = functionMatches.length;
  140. }
  141. if (loopMatches) {
  142. loopMatchesLength = loopMatches.length;
  143. }
  144. });
  145. it('match #1: shouldn\'t add dead code', () => {
  146. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  147. });
  148. it('match #2: shouldn\'t add dead code', () => {
  149. assert.equal(loopMatchesLength, expectedLoopMatchesLength);
  150. });
  151. });
  152. describe('variant #5 - chance of `IfStatement` variant', () => {
  153. const samplesCount: number = 1000;
  154. const delta: number = 0.1;
  155. const expectedDistribution: number = 0.25;
  156. const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
  157. const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
  158. const match1: string = `` +
  159. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  160. `console.*` +
  161. `\\} *else *\\{` +
  162. `${variableMatch}.*` +
  163. `\\}` +
  164. ``;
  165. const match2: string = `` +
  166. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  167. `console.*` +
  168. `\\} *else *\\{` +
  169. `${variableMatch}.*` +
  170. `\\}` +
  171. ``;
  172. const match3: string = `` +
  173. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  174. `${variableMatch}.*` +
  175. `\\} *else *\\{` +
  176. `console.*` +
  177. `\\}` +
  178. ``;
  179. const match4: string = `` +
  180. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  181. `${variableMatch}.*` +
  182. `\\} *else *\\{` +
  183. `console.*` +
  184. `\\}` +
  185. ``;
  186. let distribution1: number = 0,
  187. distribution2: number = 0,
  188. distribution3: number = 0,
  189. distribution4: number = 0;
  190. before(() => {
  191. const code: string = readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js');
  192. const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
  193. const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
  194. const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
  195. const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
  196. let count1: number = 0;
  197. let count2: number = 0;
  198. let count3: number = 0;
  199. let count4: number = 0;
  200. for (let i = 0; i < samplesCount; i++) {
  201. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  202. code,
  203. {
  204. ...NO_CUSTOM_NODES_PRESET,
  205. deadCodeInjection: true,
  206. deadCodeInjectionThreshold: 1,
  207. stringArray: true,
  208. stringArrayThreshold: 1
  209. }
  210. );
  211. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  212. if (regExp1.test(obfuscatedCode)) {
  213. count1++;
  214. } else if (regExp2.test(obfuscatedCode)) {
  215. count2++;
  216. } else if (regExp3.test(obfuscatedCode)) {
  217. count3++;
  218. } else if (regExp4.test(obfuscatedCode)) {
  219. count4++;
  220. }
  221. }
  222. distribution1 = count1 / samplesCount;
  223. distribution2 = count2 / samplesCount;
  224. distribution3 = count3 / samplesCount;
  225. distribution4 = count4 / samplesCount;
  226. });
  227. it('variant #1: `IfStatement` variant should have distribution close to `0.25`', () => {
  228. assert.closeTo(distribution1, expectedDistribution, delta);
  229. });
  230. it('variant #2: `IfStatement` variant should have distribution close to `0.25`', () => {
  231. assert.closeTo(distribution2, expectedDistribution, delta);
  232. });
  233. it('variant #3: `IfStatement` variant should have distribution close to `0.25`', () => {
  234. assert.closeTo(distribution3, expectedDistribution, delta);
  235. });
  236. it('variant #4: `IfStatement` variant should have distribution close to `0.25`', () => {
  237. assert.closeTo(distribution4, expectedDistribution, delta);
  238. });
  239. });
  240. });
  241. });