DeadCodeInjectionTransformer.spec.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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/JavaScriptObfuscator';
  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', () => {
  10. describe('variant #1 - 5 simple block statements', () => {
  11. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  12. readFileAsString(__dirname + '/fixtures/input-1.js'),
  13. {
  14. ...NO_CUSTOM_NODES_PRESET,
  15. deadCodeInjection: true,
  16. deadCodeInjectionThreshold: 1,
  17. stringArray: true,
  18. stringArrayThreshold: 1
  19. }
  20. );
  21. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  22. const deadCodeMatch: string = `` +
  23. `if *\\(${variableMatch}\\('${hexMatch}'\\) *[=|!]== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{`+
  24. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  25. `\\} *else *\\{`+
  26. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  27. `\\}` +
  28. ``;
  29. const regexp: RegExp = new RegExp(deadCodeMatch, 'g');
  30. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  31. it('should replace block statements with condition with original block statements and dead code', () => {
  32. assert.isNotNull(matches);
  33. assert.equal(matches.length, 5);
  34. });
  35. });
  36. describe('variant #2 - 4 simple block statements', () => {
  37. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  38. readFileAsString(__dirname + '/fixtures/block-statements-min-count.js'),
  39. {
  40. ...NO_CUSTOM_NODES_PRESET,
  41. deadCodeInjection: true,
  42. deadCodeInjectionThreshold: 1,
  43. stringArray: true,
  44. stringArrayThreshold: 1
  45. }
  46. );
  47. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  48. const codeMatch: string = `` +
  49. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  50. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  51. `\\};` +
  52. ``;
  53. const regexp: RegExp = new RegExp(codeMatch, 'g');
  54. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  55. it('shouldn\'t add dead code if block statements count is less than 5', () => {
  56. assert.isNotNull(matches);
  57. assert.equal(matches.length, 4);
  58. });
  59. });
  60. describe('variant #3 - deadCodeInjectionThreshold: 0', () => {
  61. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  62. readFileAsString(__dirname + '/fixtures/input-1.js'),
  63. {
  64. ...NO_CUSTOM_NODES_PRESET,
  65. deadCodeInjection: true,
  66. deadCodeInjectionThreshold: 0,
  67. stringArray: true,
  68. stringArrayThreshold: 1
  69. }
  70. );
  71. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  72. const codeMatch: string = `` +
  73. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  74. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  75. `\\};` +
  76. ``;
  77. const regexp: RegExp = new RegExp(codeMatch, 'g');
  78. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  79. it('shouldn\'t add dead code if `deadCodeInjectionThreshold` option value is `0`', () => {
  80. assert.isNotNull(matches);
  81. assert.equal(matches.length, 5);
  82. });
  83. });
  84. describe('variant #4 - break or continue statement in block statement', () => {
  85. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  86. readFileAsString(__dirname + '/fixtures/break-continue-statement.js'),
  87. {
  88. ...NO_CUSTOM_NODES_PRESET,
  89. deadCodeInjection: true,
  90. deadCodeInjectionThreshold: 1,
  91. stringArray: true,
  92. stringArrayThreshold: 1
  93. }
  94. );
  95. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  96. const functionMatch: string = `` +
  97. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  98. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  99. `\\};` +
  100. ``;
  101. const loopMatch: string = `` +
  102. `for *\\(var *${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *\\{` +
  103. `(?:continue|break);` +
  104. `\\}` +
  105. ``;
  106. const functionRegExp: RegExp = new RegExp(functionMatch, 'g');
  107. const loopRegExp: RegExp = new RegExp(loopMatch, 'g');
  108. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  109. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
  110. it('shouldn\'t add dead code if block statement contains `continue` or `break` statements', () => {
  111. assert.isNotNull(functionMatches);
  112. assert.isNotNull(loopMatches);
  113. assert.equal(functionMatches.length, 4);
  114. assert.equal(loopMatches.length, 2);
  115. });
  116. });
  117. describe('variant #5 - chance of `IfStatement` variant', () => {
  118. const samplesCount: number = 1000;
  119. const delta: number = 0.1;
  120. const expectedValue: number = 0.25;
  121. const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
  122. const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
  123. const match1: string = `` +
  124. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  125. `console.*` +
  126. `\\} *else *\\{` +
  127. `${variableMatch}.*` +
  128. `\\}` +
  129. ``;
  130. const match2: string = `` +
  131. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  132. `console.*` +
  133. `\\} *else *\\{` +
  134. `${variableMatch}.*` +
  135. `\\}` +
  136. ``;
  137. const match3: string = `` +
  138. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  139. `${variableMatch}.*` +
  140. `\\} *else *\\{` +
  141. `console.*` +
  142. `\\}` +
  143. ``;
  144. const match4: string = `` +
  145. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  146. `${variableMatch}.*` +
  147. `\\} *else *\\{` +
  148. `console.*` +
  149. `\\}` +
  150. ``;
  151. const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
  152. const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
  153. const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
  154. const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
  155. let count1: number = 0;
  156. let count2: number = 0;
  157. let count3: number = 0;
  158. let count4: number = 0;
  159. for (let i = 0; i < samplesCount; i++) {
  160. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  161. readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js'),
  162. {
  163. ...NO_CUSTOM_NODES_PRESET,
  164. deadCodeInjection: true,
  165. deadCodeInjectionThreshold: 1,
  166. stringArray: true,
  167. stringArrayThreshold: 1
  168. }
  169. );
  170. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  171. if (regExp1.test(obfuscatedCode)) {
  172. count1++;
  173. } else if (regExp2.test(obfuscatedCode)) {
  174. count2++;
  175. } else if (regExp3.test(obfuscatedCode)) {
  176. count3++;
  177. } else if (regExp4.test(obfuscatedCode)) {
  178. count4++;
  179. }
  180. }
  181. it('each of four `IfStatement` variant should have distribution close to `0.25`', () => {
  182. assert.closeTo(count1 / samplesCount, expectedValue, delta);
  183. assert.closeTo(count2 / samplesCount, expectedValue, delta);
  184. assert.closeTo(count3 / samplesCount, expectedValue, delta);
  185. assert.closeTo(count4 / samplesCount, expectedValue, delta);
  186. });
  187. });
  188. });
  189. });