DeadCodeInjectionTransformer.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  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 - await expression in block statement', () => {
  153. const functionRegExp: RegExp = new RegExp(
  154. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  155. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  156. `\\};`,
  157. 'g'
  158. );
  159. const awaitExpressionRegExp: RegExp = new RegExp(
  160. `await *${variableMatch}\\(\\)`,
  161. 'g'
  162. );
  163. const expectedFunctionMatchesLength: number = 4;
  164. const expectedAwaitExpressionMatchesLength: number = 1;
  165. let functionMatchesLength: number = 0,
  166. awaitExpressionMatchesLength: number = 0;
  167. before(() => {
  168. const code: string = readFileAsString(__dirname + '/fixtures/await-expression.js');
  169. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  170. code,
  171. {
  172. ...NO_CUSTOM_NODES_PRESET,
  173. deadCodeInjection: true,
  174. deadCodeInjectionThreshold: 1,
  175. stringArray: true,
  176. stringArrayThreshold: 1
  177. }
  178. );
  179. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  180. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  181. const awaitExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(awaitExpressionRegExp);
  182. if (functionMatches) {
  183. functionMatchesLength = functionMatches.length;
  184. }
  185. if (awaitExpressionMatches) {
  186. awaitExpressionMatchesLength = awaitExpressionMatches.length;
  187. }
  188. });
  189. it('match #1: shouldn\'t add dead code', () => {
  190. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  191. });
  192. it('match #2: shouldn\'t add dead code', () => {
  193. assert.equal(awaitExpressionMatchesLength, expectedAwaitExpressionMatchesLength);
  194. });
  195. });
  196. describe('variant #6 - chance of `IfStatement` variant', () => {
  197. const samplesCount: number = 1000;
  198. const delta: number = 0.1;
  199. const expectedDistribution: number = 0.25;
  200. const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
  201. const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
  202. const match1: string = `` +
  203. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  204. `console.*` +
  205. `\\} *else *\\{` +
  206. `alert.*` +
  207. `\\}` +
  208. ``;
  209. const match2: string = `` +
  210. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  211. `console.*` +
  212. `\\} *else *\\{` +
  213. `alert.*` +
  214. `\\}` +
  215. ``;
  216. const match3: string = `` +
  217. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  218. `alert.*` +
  219. `\\} *else *\\{` +
  220. `console.*` +
  221. `\\}` +
  222. ``;
  223. const match4: string = `` +
  224. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  225. `alert.*` +
  226. `\\} *else *\\{` +
  227. `console.*` +
  228. `\\}` +
  229. ``;
  230. let distribution1: number = 0,
  231. distribution2: number = 0,
  232. distribution3: number = 0,
  233. distribution4: number = 0;
  234. before(() => {
  235. const code: string = readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js');
  236. const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
  237. const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
  238. const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
  239. const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
  240. let count1: number = 0;
  241. let count2: number = 0;
  242. let count3: number = 0;
  243. let count4: number = 0;
  244. for (let i = 0; i < samplesCount; i++) {
  245. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  246. code,
  247. {
  248. ...NO_CUSTOM_NODES_PRESET,
  249. deadCodeInjection: true,
  250. deadCodeInjectionThreshold: 1,
  251. stringArray: true,
  252. stringArrayThreshold: 1
  253. }
  254. );
  255. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  256. if (regExp1.test(obfuscatedCode)) {
  257. count1++;
  258. } else if (regExp2.test(obfuscatedCode)) {
  259. count2++;
  260. } else if (regExp3.test(obfuscatedCode)) {
  261. count3++;
  262. } else if (regExp4.test(obfuscatedCode)) {
  263. count4++;
  264. }
  265. }
  266. distribution1 = count1 / samplesCount;
  267. distribution2 = count2 / samplesCount;
  268. distribution3 = count3 / samplesCount;
  269. distribution4 = count4 / samplesCount;
  270. });
  271. it('variant #1: `IfStatement` variant should have distribution close to `0.25`', () => {
  272. assert.closeTo(distribution1, expectedDistribution, delta);
  273. });
  274. it('variant #2: `IfStatement` variant should have distribution close to `0.25`', () => {
  275. assert.closeTo(distribution2, expectedDistribution, delta);
  276. });
  277. it('variant #3: `IfStatement` variant should have distribution close to `0.25`', () => {
  278. assert.closeTo(distribution3, expectedDistribution, delta);
  279. });
  280. it('variant #4: `IfStatement` variant should have distribution close to `0.25`', () => {
  281. assert.closeTo(distribution4, expectedDistribution, delta);
  282. });
  283. });
  284. describe('variant #7 - block scope of block statement is `ProgramNode`', () => {
  285. const regExp: RegExp = new RegExp(
  286. `if *\\(!!\\[\\]\\) *{` +
  287. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  288. `\\}`
  289. );
  290. let obfuscatedCode: string;
  291. before(() => {
  292. const code: string = readFileAsString(__dirname + '/fixtures/block-scope-is-program-node.js');
  293. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  294. code,
  295. {
  296. ...NO_CUSTOM_NODES_PRESET,
  297. stringArray: true,
  298. stringArrayThreshold: 1,
  299. deadCodeInjection: true,
  300. deadCodeInjectionThreshold: 1
  301. }
  302. );
  303. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  304. });
  305. it('shouldn\'t add dead code in block statements with `ProgramNode` block scope', () => {
  306. assert.match(obfuscatedCode, regExp);
  307. });
  308. });
  309. describe('variant #8 - correct obfuscation of dead-code block statements', () => {
  310. const variableName: string = 'importantVariableName';
  311. let obfuscatedCode: string;
  312. before(() => {
  313. const code: string = readFileAsString(__dirname + '/fixtures/obfuscation-of-dead-code-block-statements.js');
  314. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  315. code,
  316. {
  317. ...NO_CUSTOM_NODES_PRESET,
  318. deadCodeInjection: true,
  319. deadCodeInjectionThreshold: 1,
  320. debugProtection: true
  321. }
  322. );
  323. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  324. });
  325. it('should correctly obfuscate dead-code block statements and prevent any exposing of internal variable names', () => {
  326. assert.notInclude(obfuscatedCode, variableName);
  327. });
  328. });
  329. });
  330. });