DeadCodeInjectionTransformer.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. import { assert } from 'chai';
  2. import { IObfuscationResult } from '../../../../src/interfaces/IObfuscationResult';
  3. import { NO_ADDITIONAL_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_ADDITIONAL_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_ADDITIONAL_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_ADDITIONAL_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_ADDITIONAL_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_ADDITIONAL_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 - super expression in block statement', () => {
  197. const functionRegExp: RegExp = new RegExp(
  198. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  199. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  200. `\\};`,
  201. 'g'
  202. );
  203. const superExpressionRegExp: RegExp = new RegExp(
  204. `super *\\(\\);`,
  205. 'g'
  206. );
  207. const expectedFunctionMatchesLength: number = 4;
  208. const expectedSuperExpressionMatchesLength: number = 1;
  209. let functionMatchesLength: number = 0,
  210. superExpressionMatchesLength: number = 0;
  211. before(() => {
  212. const code: string = readFileAsString(__dirname + '/fixtures/super-expression.js');
  213. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  214. code,
  215. {
  216. ...NO_ADDITIONAL_NODES_PRESET,
  217. deadCodeInjection: true,
  218. deadCodeInjectionThreshold: 1,
  219. stringArray: true,
  220. stringArrayThreshold: 1
  221. }
  222. );
  223. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  224. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  225. const superExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(superExpressionRegExp);
  226. if (functionMatches) {
  227. functionMatchesLength = functionMatches.length;
  228. }
  229. if (superExpressionMatches) {
  230. superExpressionMatchesLength = superExpressionMatches.length;
  231. }
  232. });
  233. it('match #1: shouldn\'t add dead code', () => {
  234. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  235. });
  236. it('match #2: shouldn\'t add dead code', () => {
  237. assert.equal(superExpressionMatchesLength, expectedSuperExpressionMatchesLength);
  238. });
  239. });
  240. describe('variant #7 - chance of `IfStatement` variant', () => {
  241. const samplesCount: number = 1000;
  242. const delta: number = 0.1;
  243. const expectedDistribution: number = 0.25;
  244. const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
  245. const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
  246. const match1: string = `` +
  247. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  248. `console.*` +
  249. `\\} *else *\\{` +
  250. `alert.*` +
  251. `\\}` +
  252. ``;
  253. const match2: string = `` +
  254. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  255. `console.*` +
  256. `\\} *else *\\{` +
  257. `alert.*` +
  258. `\\}` +
  259. ``;
  260. const match3: string = `` +
  261. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  262. `alert.*` +
  263. `\\} *else *\\{` +
  264. `console.*` +
  265. `\\}` +
  266. ``;
  267. const match4: string = `` +
  268. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  269. `alert.*` +
  270. `\\} *else *\\{` +
  271. `console.*` +
  272. `\\}` +
  273. ``;
  274. let distribution1: number = 0,
  275. distribution2: number = 0,
  276. distribution3: number = 0,
  277. distribution4: number = 0;
  278. before(() => {
  279. const code: string = readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js');
  280. const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
  281. const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
  282. const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
  283. const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
  284. let count1: number = 0;
  285. let count2: number = 0;
  286. let count3: number = 0;
  287. let count4: number = 0;
  288. for (let i = 0; i < samplesCount; i++) {
  289. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  290. code,
  291. {
  292. ...NO_ADDITIONAL_NODES_PRESET,
  293. deadCodeInjection: true,
  294. deadCodeInjectionThreshold: 1,
  295. stringArray: true,
  296. stringArrayThreshold: 1
  297. }
  298. );
  299. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  300. if (regExp1.test(obfuscatedCode)) {
  301. count1++;
  302. } else if (regExp2.test(obfuscatedCode)) {
  303. count2++;
  304. } else if (regExp3.test(obfuscatedCode)) {
  305. count3++;
  306. } else if (regExp4.test(obfuscatedCode)) {
  307. count4++;
  308. }
  309. }
  310. distribution1 = count1 / samplesCount;
  311. distribution2 = count2 / samplesCount;
  312. distribution3 = count3 / samplesCount;
  313. distribution4 = count4 / samplesCount;
  314. });
  315. it('variant #1: `IfStatement` variant should have distribution close to `0.25`', () => {
  316. assert.closeTo(distribution1, expectedDistribution, delta);
  317. });
  318. it('variant #2: `IfStatement` variant should have distribution close to `0.25`', () => {
  319. assert.closeTo(distribution2, expectedDistribution, delta);
  320. });
  321. it('variant #3: `IfStatement` variant should have distribution close to `0.25`', () => {
  322. assert.closeTo(distribution3, expectedDistribution, delta);
  323. });
  324. it('variant #4: `IfStatement` variant should have distribution close to `0.25`', () => {
  325. assert.closeTo(distribution4, expectedDistribution, delta);
  326. });
  327. });
  328. describe('variant #8 - block scope of block statement is `ProgramNode`', () => {
  329. const regExp: RegExp = new RegExp(
  330. `if *\\(!!\\[\\]\\) *{` +
  331. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  332. `\\}`
  333. );
  334. let obfuscatedCode: string;
  335. before(() => {
  336. const code: string = readFileAsString(__dirname + '/fixtures/block-scope-is-program-node.js');
  337. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  338. code,
  339. {
  340. ...NO_ADDITIONAL_NODES_PRESET,
  341. stringArray: true,
  342. stringArrayThreshold: 1,
  343. deadCodeInjection: true,
  344. deadCodeInjectionThreshold: 1
  345. }
  346. );
  347. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  348. });
  349. it('shouldn\'t add dead code in block statements with `ProgramNode` block scope', () => {
  350. assert.match(obfuscatedCode, regExp);
  351. });
  352. });
  353. describe('variant #9 - correct obfuscation of dead-code block statements', () => {
  354. const variableName: string = 'importantVariableName';
  355. let obfuscatedCode: string;
  356. before(() => {
  357. const code: string = readFileAsString(__dirname + '/fixtures/obfuscation-of-dead-code-block-statements.js');
  358. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  359. code,
  360. {
  361. ...NO_ADDITIONAL_NODES_PRESET,
  362. deadCodeInjection: true,
  363. deadCodeInjectionThreshold: 1,
  364. debugProtection: true
  365. }
  366. );
  367. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  368. });
  369. it('should correctly obfuscate dead-code block statements and prevent any exposing of internal variable names', () => {
  370. assert.notInclude(obfuscatedCode, variableName);
  371. });
  372. });
  373. });
  374. });