DeadCodeInjectionTransformer.spec.ts 16 KB

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