DeadCodeInjectionTransformer.spec.ts 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  1. import { assert } from 'chai';
  2. import { NO_ADDITIONAL_NODES_PRESET } from '../../../../src/options/presets/NoCustomNodes';
  3. import { IdentifierNamesGenerator } from '../../../../src/enums/generators/identifier-names-generators/IdentifierNamesGenerator';
  4. import { getRegExpMatch } from '../../../helpers/getRegExpMatch';
  5. import { readFileAsString } from '../../../helpers/readFileAsString';
  6. import { JavaScriptObfuscator } from '../../../../src/JavaScriptObfuscatorFacade';
  7. describe('DeadCodeInjectionTransformer', () => {
  8. const variableMatch: string = '_0x([a-f0-9]){4,6}';
  9. const hexMatch: string = '0x[a-f0-9]';
  10. const stringArrayCallMatch: string = `${variableMatch}\\('${hexMatch}'\\)`;
  11. describe('transformNode', function () {
  12. this.timeout(100000);
  13. describe('Variant #1 - 5 simple block statements', () => {
  14. const regExp: RegExp = new RegExp(
  15. `if *\\(${variableMatch}\\('${hexMatch}'\\) *[=|!]== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{`+
  16. `(?:console|${variableMatch})\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  17. `\\} *else *\\{`+
  18. `(?:console|${variableMatch})\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  19. `\\}`,
  20. 'g'
  21. );
  22. const expectedMatchesLength: number = 5;
  23. let matchesLength: number = 0;
  24. before(() => {
  25. const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
  26. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  27. code,
  28. {
  29. ...NO_ADDITIONAL_NODES_PRESET,
  30. deadCodeInjection: true,
  31. deadCodeInjectionThreshold: 1,
  32. stringArray: true,
  33. stringArrayThreshold: 1
  34. }
  35. ).getObfuscatedCode();
  36. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
  37. if (matches) {
  38. matchesLength = matches.length;
  39. }
  40. });
  41. it('should replace block statements with condition with original block statements and dead code', () => {
  42. assert.equal(matchesLength, expectedMatchesLength);
  43. });
  44. });
  45. describe('Variant #2 - block statements count is less than `5`', () => {
  46. const regexp: RegExp = new RegExp(
  47. `var ${variableMatch} *= *function *\\(\\) *\\{` +
  48. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  49. `\\};`,
  50. 'g'
  51. );
  52. const expectedMatchesLength: number = 4;
  53. let matchesLength: number = 0;
  54. before(() => {
  55. const code: string = readFileAsString(__dirname + '/fixtures/block-statements-min-count.js');
  56. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  57. code,
  58. {
  59. ...NO_ADDITIONAL_NODES_PRESET,
  60. deadCodeInjection: true,
  61. deadCodeInjectionThreshold: 1,
  62. stringArray: true,
  63. stringArrayThreshold: 1
  64. }
  65. ).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 obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  87. code,
  88. {
  89. ...NO_ADDITIONAL_NODES_PRESET,
  90. deadCodeInjection: true,
  91. deadCodeInjectionThreshold: 0,
  92. stringArray: true,
  93. stringArrayThreshold: 1
  94. }
  95. ).getObfuscatedCode();
  96. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  97. if (matches) {
  98. matchesLength = matches.length;
  99. }
  100. });
  101. it('shouldn\'t add dead code', () => {
  102. assert.equal(matchesLength, expectedMatchesLength);
  103. });
  104. });
  105. describe('Variant #4 - prohibited node inside collected block statement', () => {
  106. describe('Variant #1 - function declaration 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 functionDeclarationRegExp: RegExp = new RegExp(
  114. `function *${variableMatch} *\\(${variableMatch}\\) *{}`,
  115. 'g'
  116. );
  117. const expectedFunctionMatchesLength: number = 4;
  118. const expectedFunctionDeclarationMatchesLength: number = 1;
  119. let functionMatchesLength: number = 0,
  120. functionDeclarationMatchesLength: number = 0;
  121. before(() => {
  122. const code: string = readFileAsString(__dirname + '/fixtures/function-declaration-inside-block-statement.js');
  123. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  124. code,
  125. {
  126. ...NO_ADDITIONAL_NODES_PRESET,
  127. deadCodeInjection: true,
  128. deadCodeInjectionThreshold: 1,
  129. stringArray: true,
  130. stringArrayThreshold: 1
  131. }
  132. ).getObfuscatedCode();
  133. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  134. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionDeclarationRegExp);
  135. if (functionMatches) {
  136. functionMatchesLength = functionMatches.length;
  137. }
  138. if (loopMatches) {
  139. functionDeclarationMatchesLength = loopMatches.length;
  140. }
  141. });
  142. it('match #1: shouldn\'t add dead code', () => {
  143. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  144. });
  145. it('match #2: shouldn\'t add dead code', () => {
  146. assert.equal(functionDeclarationMatchesLength, expectedFunctionDeclarationMatchesLength);
  147. });
  148. });
  149. describe('Variant #2 - break or continue statement in block statement', () => {
  150. describe('Variant #1', () => {
  151. const functionRegExp: RegExp = new RegExp(
  152. `var ${variableMatch} *= *function *\\(\\) *\\{` +
  153. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  154. `\\};`,
  155. 'g'
  156. );
  157. const loopRegExp: RegExp = new RegExp(
  158. `for *\\(var ${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *\\{` +
  159. `(?:continue|break);` +
  160. `\\}`,
  161. 'g'
  162. );
  163. const expectedFunctionMatchesLength: number = 4;
  164. const expectedLoopMatchesLength: number = 2;
  165. let functionMatchesLength: number = 0,
  166. loopMatchesLength: number = 0;
  167. before(() => {
  168. const code: string = readFileAsString(__dirname + '/fixtures/break-continue-statement-1.js');
  169. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  170. code,
  171. {
  172. ...NO_ADDITIONAL_NODES_PRESET,
  173. deadCodeInjection: true,
  174. deadCodeInjectionThreshold: 1,
  175. stringArray: true,
  176. stringArrayThreshold: 1
  177. }
  178. ).getObfuscatedCode();
  179. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  180. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
  181. if (functionMatches) {
  182. functionMatchesLength = functionMatches.length;
  183. }
  184. if (loopMatches) {
  185. loopMatchesLength = loopMatches.length;
  186. }
  187. });
  188. it('match #1: shouldn\'t add dead code', () => {
  189. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  190. });
  191. it('match #2: shouldn\'t add dead code', () => {
  192. assert.equal(loopMatchesLength, expectedLoopMatchesLength);
  193. });
  194. });
  195. describe('Variant #2', () => {
  196. const functionRegExp: RegExp = new RegExp(
  197. `var ${variableMatch} *= *function *\\(\\) *\\{` +
  198. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  199. `\\};`,
  200. 'g'
  201. );
  202. const loopRegExp: RegExp = new RegExp(
  203. `for *\\(var ${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *` +
  204. `(?:continue|break);`,
  205. 'g'
  206. );
  207. const expectedFunctionMatchesLength: number = 4;
  208. const expectedLoopMatchesLength: number = 2;
  209. let functionMatchesLength: number = 0,
  210. loopMatchesLength: number = 0;
  211. before(() => {
  212. const code: string = readFileAsString(__dirname + '/fixtures/break-continue-statement-2.js');
  213. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  214. code,
  215. {
  216. ...NO_ADDITIONAL_NODES_PRESET,
  217. deadCodeInjection: true,
  218. deadCodeInjectionThreshold: 1,
  219. stringArray: true,
  220. stringArrayThreshold: 1
  221. }
  222. ).getObfuscatedCode();
  223. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  224. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
  225. if (functionMatches) {
  226. functionMatchesLength = functionMatches.length;
  227. }
  228. if (loopMatches) {
  229. loopMatchesLength = loopMatches.length;
  230. }
  231. });
  232. it('match #1: shouldn\'t add dead code', () => {
  233. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  234. });
  235. it('match #2: shouldn\'t add dead code', () => {
  236. assert.equal(loopMatchesLength, expectedLoopMatchesLength);
  237. });
  238. });
  239. });
  240. describe('Variant #3 - await expression in block statement', () => {
  241. const functionRegExp: RegExp = new RegExp(
  242. `var ${variableMatch} *= *function *\\(\\) *\\{` +
  243. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  244. `\\};`,
  245. 'g'
  246. );
  247. const awaitExpressionRegExp: RegExp = new RegExp(
  248. `await *${variableMatch}\\(\\)`,
  249. 'g'
  250. );
  251. const expectedFunctionMatchesLength: number = 4;
  252. const expectedAwaitExpressionMatchesLength: number = 1;
  253. let functionMatchesLength: number = 0,
  254. awaitExpressionMatchesLength: number = 0;
  255. before(() => {
  256. const code: string = readFileAsString(__dirname + '/fixtures/await-expression.js');
  257. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  258. code,
  259. {
  260. ...NO_ADDITIONAL_NODES_PRESET,
  261. deadCodeInjection: true,
  262. deadCodeInjectionThreshold: 1,
  263. stringArray: true,
  264. stringArrayThreshold: 1
  265. }
  266. ).getObfuscatedCode();
  267. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  268. const awaitExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(awaitExpressionRegExp);
  269. if (functionMatches) {
  270. functionMatchesLength = functionMatches.length;
  271. }
  272. if (awaitExpressionMatches) {
  273. awaitExpressionMatchesLength = awaitExpressionMatches.length;
  274. }
  275. });
  276. it('match #1: shouldn\'t add dead code', () => {
  277. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  278. });
  279. it('match #2: shouldn\'t add dead code', () => {
  280. assert.equal(awaitExpressionMatchesLength, expectedAwaitExpressionMatchesLength);
  281. });
  282. });
  283. describe('Variant #4 - yield expression in block statement', () => {
  284. const functionRegExp: RegExp = new RegExp(
  285. `var ${variableMatch} *= *function *\\(\\) *\\{` +
  286. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  287. `\\};`,
  288. 'g'
  289. );
  290. const yieldExpressionRegExp: RegExp = new RegExp(
  291. `yield *${variableMatch}\\(\\)`,
  292. 'g'
  293. );
  294. const expectedFunctionMatchesLength: number = 4;
  295. const expectedAwaitExpressionMatchesLength: number = 1;
  296. let functionMatchesLength: number = 0,
  297. yieldExpressionMatchesLength: number = 0;
  298. before(() => {
  299. const code: string = readFileAsString(__dirname + '/fixtures/yield-expression.js');
  300. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  301. code,
  302. {
  303. ...NO_ADDITIONAL_NODES_PRESET,
  304. deadCodeInjection: true,
  305. deadCodeInjectionThreshold: 1,
  306. stringArray: true,
  307. stringArrayThreshold: 1
  308. }
  309. ).getObfuscatedCode();
  310. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  311. const yieldExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(yieldExpressionRegExp);
  312. if (functionMatches) {
  313. functionMatchesLength = functionMatches.length;
  314. }
  315. if (yieldExpressionMatches) {
  316. yieldExpressionMatchesLength = yieldExpressionMatches.length;
  317. }
  318. });
  319. it('match #1: shouldn\'t add dead code', () => {
  320. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  321. });
  322. it('match #2: shouldn\'t add dead code', () => {
  323. assert.equal(yieldExpressionMatchesLength, expectedAwaitExpressionMatchesLength);
  324. });
  325. });
  326. describe('Variant #5 - super expression in block statement', () => {
  327. const functionRegExp: RegExp = new RegExp(
  328. `var ${variableMatch} *= *function *\\(\\) *\\{` +
  329. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  330. `\\};`,
  331. 'g'
  332. );
  333. const superExpressionRegExp: RegExp = new RegExp(
  334. `super *\\(\\);`,
  335. 'g'
  336. );
  337. const expectedFunctionMatchesLength: number = 4;
  338. const expectedSuperExpressionMatchesLength: number = 1;
  339. let functionMatchesLength: number = 0,
  340. superExpressionMatchesLength: number = 0;
  341. before(() => {
  342. const code: string = readFileAsString(__dirname + '/fixtures/super-expression.js');
  343. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  344. code,
  345. {
  346. ...NO_ADDITIONAL_NODES_PRESET,
  347. deadCodeInjection: true,
  348. deadCodeInjectionThreshold: 1,
  349. stringArray: true,
  350. stringArrayThreshold: 1
  351. }
  352. ).getObfuscatedCode();
  353. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  354. const superExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(superExpressionRegExp);
  355. if (functionMatches) {
  356. functionMatchesLength = functionMatches.length;
  357. }
  358. if (superExpressionMatches) {
  359. superExpressionMatchesLength = superExpressionMatches.length;
  360. }
  361. });
  362. it('match #1: shouldn\'t add dead code', () => {
  363. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  364. });
  365. it('match #2: shouldn\'t add dead code', () => {
  366. assert.equal(superExpressionMatchesLength, expectedSuperExpressionMatchesLength);
  367. });
  368. });
  369. });
  370. describe('Variant #5 - chance of `IfStatement` variant', () => {
  371. const samplesCount: number = 1000;
  372. const delta: number = 0.1;
  373. const expectedDistribution: number = 0.25;
  374. const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
  375. const functionMatch: string = `var ${variableMatch} *= *function *\\(\\) *\\{`;
  376. const match1: string = `` +
  377. `if *\\(${stringArrayCallMatch} *=== *${stringArrayCallMatch}\\) *\\{` +
  378. `console\\[${stringArrayCallMatch}]\\(${stringArrayCallMatch}\\);` +
  379. `\\} *else *\\{` +
  380. `${variableMatch}\\(${stringArrayCallMatch}\\);` +
  381. `\\}` +
  382. ``;
  383. const match2: string = `` +
  384. `if *\\(${stringArrayCallMatch} *!== *${stringArrayCallMatch}\\) *\\{` +
  385. `console\\[${stringArrayCallMatch}]\\(${stringArrayCallMatch}\\);` +
  386. `\\} *else *\\{` +
  387. `${variableMatch}\\(${stringArrayCallMatch}\\);` +
  388. `\\}` +
  389. ``;
  390. const match3: string = `` +
  391. `if *\\(${stringArrayCallMatch} *=== *${stringArrayCallMatch}\\) *\\{` +
  392. `${variableMatch}\\(${stringArrayCallMatch}\\);` +
  393. `\\} *else *\\{` +
  394. `console\\[${stringArrayCallMatch}]\\(${stringArrayCallMatch}\\);` +
  395. `\\}` +
  396. ``;
  397. const match4: string = `` +
  398. `if *\\(${stringArrayCallMatch} *!== *${stringArrayCallMatch}\\) *\\{` +
  399. `${variableMatch}\\(${stringArrayCallMatch}\\);` +
  400. `\\} *else *\\{` +
  401. `console\\[${stringArrayCallMatch}]\\(${stringArrayCallMatch}\\);` +
  402. `\\}` +
  403. ``;
  404. let distribution1: number = 0,
  405. distribution2: number = 0,
  406. distribution3: number = 0,
  407. distribution4: number = 0;
  408. before(() => {
  409. const code: string = readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js');
  410. const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
  411. const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
  412. const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
  413. const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
  414. let count1: number = 0;
  415. let count2: number = 0;
  416. let count3: number = 0;
  417. let count4: number = 0;
  418. for (let i = 0; i < samplesCount; i++) {
  419. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  420. code,
  421. {
  422. ...NO_ADDITIONAL_NODES_PRESET,
  423. deadCodeInjection: true,
  424. deadCodeInjectionThreshold: 1,
  425. stringArray: true,
  426. stringArrayThreshold: 1
  427. }
  428. ).getObfuscatedCode();
  429. if (regExp1.test(obfuscatedCode)) {
  430. count1++;
  431. } else if (regExp2.test(obfuscatedCode)) {
  432. count2++;
  433. } else if (regExp3.test(obfuscatedCode)) {
  434. count3++;
  435. } else if (regExp4.test(obfuscatedCode)) {
  436. count4++;
  437. }
  438. }
  439. distribution1 = count1 / samplesCount;
  440. distribution2 = count2 / samplesCount;
  441. distribution3 = count3 / samplesCount;
  442. distribution4 = count4 / samplesCount;
  443. });
  444. it('Variant #1: `IfStatement` variant should have distribution close to `0.25`', () => {
  445. assert.closeTo(distribution1, expectedDistribution, delta);
  446. });
  447. it('Variant #2: `IfStatement` variant should have distribution close to `0.25`', () => {
  448. assert.closeTo(distribution2, expectedDistribution, delta);
  449. });
  450. it('Variant #3: `IfStatement` variant should have distribution close to `0.25`', () => {
  451. assert.closeTo(distribution3, expectedDistribution, delta);
  452. });
  453. it('Variant #4: `IfStatement` variant should have distribution close to `0.25`', () => {
  454. assert.closeTo(distribution4, expectedDistribution, delta);
  455. });
  456. });
  457. describe('Variant #6 - block scope of block statement is `ProgramNode`', () => {
  458. const regExp: RegExp = new RegExp(
  459. `if *\\(!!\\[\\]\\) *{` +
  460. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  461. `\\}`
  462. );
  463. let obfuscatedCode: string;
  464. before(() => {
  465. const code: string = readFileAsString(__dirname + '/fixtures/block-scope-is-program-node.js');
  466. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  467. code,
  468. {
  469. ...NO_ADDITIONAL_NODES_PRESET,
  470. stringArray: true,
  471. stringArrayThreshold: 1,
  472. deadCodeInjection: true,
  473. deadCodeInjectionThreshold: 1
  474. }
  475. ).getObfuscatedCode();
  476. });
  477. it('shouldn\'t add dead code in block statements with `ProgramNode` block scope', () => {
  478. assert.match(obfuscatedCode, regExp);
  479. });
  480. });
  481. describe('Variant #7 - correct obfuscation of dead-code block statements', () => {
  482. const variableName: string = 'importantVariableName';
  483. let obfuscatedCode: string;
  484. before(() => {
  485. const code: string = readFileAsString(__dirname + '/fixtures/obfuscation-of-dead-code-block-statements.js');
  486. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  487. code,
  488. {
  489. ...NO_ADDITIONAL_NODES_PRESET,
  490. deadCodeInjection: true,
  491. deadCodeInjectionThreshold: 1,
  492. debugProtection: true
  493. }
  494. ).getObfuscatedCode();
  495. });
  496. it('should correctly obfuscate dead-code block statements and prevent any exposing of internal variable names', () => {
  497. assert.notInclude(obfuscatedCode, variableName);
  498. });
  499. });
  500. describe('Variant #8 - unique names for dead code identifiers', () => {
  501. /**
  502. * Code:
  503. *
  504. * (function(variable){
  505. * function foo () {
  506. * return variable.push(1);
  507. * }
  508. *
  509. * function bar () {
  510. * var variable = 1;
  511. * }
  512. *
  513. * function baz() {
  514. * var variable = 2;
  515. * }
  516. *
  517. * function bark() {
  518. * var variable = 3;
  519. * }
  520. *
  521. * function hawk() {
  522. * var variable = 4;
  523. * }
  524. * })([]);
  525. *
  526. * With this code, dead code can be added to the first function `foo`
  527. * If dead code won't be renamed before add - identifier name inside dead code block statement can be
  528. * the same as identifier name inside transformed block statement:
  529. *
  530. * (function(variable){
  531. * function foo () {
  532. * if (1 !== 1) {
  533. * var variable = 1; // <- overwriting value of function parameter
  534. * } else {
  535. * return variable.push(1);
  536. * }
  537. * }
  538. *
  539. * function bar () {
  540. * var variable = 1;
  541. * }
  542. *
  543. * function baz() {
  544. * var variable = 2;
  545. * }
  546. *
  547. * function bark() {
  548. * var variable = 3;
  549. * }
  550. *
  551. * function hawk() {
  552. * var variable = 4;
  553. * }
  554. * })([]);
  555. *
  556. * So, added dead code variable declaration will overwrite a value of function parameter.
  557. * This should never happen.
  558. */
  559. describe('Variant #1', () => {
  560. const functionParameterMatch: string = `` +
  561. `\\(function\\((\\w)\\){` +
  562. ``;
  563. const deadCodeMatch: string = `` +
  564. `function \\w *\\(\\w\\) *{` +
  565. `if *\\(.{0,30}\\) *{` +
  566. `var (\\w).*?;` +
  567. `} *else *{` +
  568. `return *(\\w).*?;` +
  569. `}` +
  570. `}` +
  571. ``;
  572. const functionParameterRegExp: RegExp = new RegExp(functionParameterMatch);
  573. const deadCodeRegExp: RegExp = new RegExp(deadCodeMatch);
  574. let result: boolean = false,
  575. functionIdentifierName: string | null,
  576. returnIdentifierName: string | null,
  577. variableDeclarationIdentifierName: string | null,
  578. obfuscatedCode: string;
  579. before(() => {
  580. const code: string = readFileAsString(__dirname + '/fixtures/unique-names-for-dead-code-identifiers.js');
  581. for (let i: number = 0; i < 100; i++) {
  582. while (true) {
  583. try {
  584. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  585. code,
  586. {
  587. ...NO_ADDITIONAL_NODES_PRESET,
  588. deadCodeInjection: true,
  589. deadCodeInjectionThreshold: 1,
  590. identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
  591. }
  592. ).getObfuscatedCode();
  593. functionIdentifierName = getRegExpMatch(obfuscatedCode, functionParameterRegExp, 0);
  594. variableDeclarationIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 0);
  595. returnIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 1);
  596. break;
  597. } catch {}
  598. }
  599. if (
  600. // variable declaration from dead code is affects original code
  601. functionIdentifierName === variableDeclarationIdentifierName &&
  602. returnIdentifierName === variableDeclarationIdentifierName
  603. ) {
  604. result = false;
  605. break;
  606. }
  607. result = true;
  608. }
  609. });
  610. it('should generate separate identifiers for common AST and dead code', () => {
  611. assert.isOk(result, 'wrong identifier names');
  612. });
  613. });
  614. describe('Variant #2', () => {
  615. const functionParameterMatch: string = `` +
  616. `\\(function\\((\\w)\\){` +
  617. ``;
  618. const deadCodeMatch: string = `` +
  619. `function \\w *\\(\\w\\) *{` +
  620. `if *\\(.{0,30}\\) *{` +
  621. `return *(\\w).{0,40};` +
  622. `} *else *{` +
  623. `var (\\w).*?;` +
  624. `}` +
  625. `}` +
  626. ``;
  627. const functionParameterRegExp: RegExp = new RegExp(functionParameterMatch);
  628. const deadCodeRegExp: RegExp = new RegExp(deadCodeMatch);
  629. let result: boolean = false,
  630. functionIdentifierName: string | null,
  631. returnIdentifierName: string | null,
  632. variableDeclarationIdentifierName: string | null,
  633. obfuscatedCode: string;
  634. before(() => {
  635. const code: string = readFileAsString(__dirname + '/fixtures/unique-names-for-dead-code-identifiers.js');
  636. for (let i: number = 0; i < 100; i++) {
  637. while (true) {
  638. try {
  639. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  640. code,
  641. {
  642. ...NO_ADDITIONAL_NODES_PRESET,
  643. deadCodeInjection: true,
  644. deadCodeInjectionThreshold: 1,
  645. identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
  646. }
  647. ).getObfuscatedCode();
  648. functionIdentifierName = getRegExpMatch(obfuscatedCode, functionParameterRegExp, 0);
  649. returnIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 0);
  650. variableDeclarationIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 1);
  651. break;
  652. } catch {}
  653. }
  654. if (
  655. // variable declaration from dead code is affects original code
  656. functionIdentifierName === variableDeclarationIdentifierName &&
  657. returnIdentifierName === variableDeclarationIdentifierName
  658. ) {
  659. console.log(obfuscatedCode);
  660. result = false;
  661. break;
  662. }
  663. result = true;
  664. }
  665. });
  666. it('should generate separate identifiers for common AST and dead code', () => {
  667. assert.isOk(result, 'wrong identifier names');
  668. });
  669. });
  670. });
  671. describe('Variant #9 - block statements with empty body', () => {
  672. const regExp: RegExp = new RegExp(
  673. `function *${variableMatch} *\\(\\) *{ *} *` +
  674. `${variableMatch} *\\(\\); *`,
  675. 'g'
  676. );
  677. const expectedMatchesLength: number = 5;
  678. let matchesLength: number = 0;
  679. before(() => {
  680. const code: string = readFileAsString(__dirname + '/fixtures/block-statement-empty-body.js');
  681. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  682. code,
  683. {
  684. ...NO_ADDITIONAL_NODES_PRESET,
  685. stringArray: true,
  686. stringArrayThreshold: 1,
  687. deadCodeInjection: true,
  688. deadCodeInjectionThreshold: 1
  689. }
  690. ).getObfuscatedCode();
  691. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
  692. if (functionMatches) {
  693. matchesLength = functionMatches.length;
  694. }
  695. });
  696. it('shouldn\'t add dead code conditions to the block empty block statements', () => {
  697. assert.isAtLeast(matchesLength, expectedMatchesLength);
  698. });
  699. });
  700. describe('Variant #10 - block statement with scope-hoisting', () => {
  701. describe('Variant #1: collecting of block statements', () => {
  702. const regExp: RegExp = new RegExp(
  703. `${variableMatch} *\\(\\); *` +
  704. `var ${variableMatch} *= *0x2; *` +
  705. `function *${variableMatch} *\\(\\) *{ *} *`,
  706. 'g'
  707. );
  708. const expectedMatchesLength: number = 5;
  709. let matchesLength: number = 0;
  710. before(() => {
  711. const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting-1.js');
  712. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  713. code,
  714. {
  715. ...NO_ADDITIONAL_NODES_PRESET,
  716. stringArray: true,
  717. stringArrayThreshold: 1,
  718. deadCodeInjection: true,
  719. deadCodeInjectionThreshold: 1
  720. }
  721. ).getObfuscatedCode();
  722. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
  723. if (functionMatches) {
  724. matchesLength = functionMatches.length;
  725. }
  726. });
  727. it('shouldn\'t collect block statements with scope-hoisting', () => {
  728. assert.equal(matchesLength, expectedMatchesLength);
  729. });
  730. });
  731. describe('Variant #2: wrapping of block statements in dead code conditions', () => {
  732. const regExp: RegExp = new RegExp(
  733. `function *${variableMatch} *\\(\\) *{ *` +
  734. `var ${variableMatch} *= *0x1; *` +
  735. `${variableMatch} *\\(\\); *` +
  736. `var ${variableMatch} *= *0x2; *` +
  737. `function *${variableMatch} *\\(\\) *{ *} *` +
  738. `var ${variableMatch} *= *0x3; *` +
  739. `}`,
  740. 'g'
  741. );
  742. let obfuscatedCode: string;
  743. before(() => {
  744. const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting-2.js');
  745. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  746. code,
  747. {
  748. ...NO_ADDITIONAL_NODES_PRESET,
  749. stringArray: true,
  750. stringArrayThreshold: 1,
  751. deadCodeInjection: true,
  752. deadCodeInjectionThreshold: 1
  753. }
  754. ).getObfuscatedCode();
  755. });
  756. it('shouldn\'t wrap block statements in dead code conditions', () => {
  757. assert.match(obfuscatedCode, regExp);
  758. });
  759. });
  760. });
  761. describe('Variant #11 - prevailing kind of variables of inserted code', () => {
  762. describe('Variant #1: base', () => {
  763. const variableDeclarationsRegExp: RegExp = new RegExp(
  764. `const ${variableMatch} *= *\\[\\]; *` +
  765. `var ${variableMatch} *= *\\[\\]; *`,
  766. 'g'
  767. );
  768. const invalidVariableDeclarationsRegExp: RegExp = new RegExp(
  769. `var ${variableMatch} *= *\\[\\]; *` +
  770. `var ${variableMatch} *= *\\[\\]; *`,
  771. 'g'
  772. );
  773. const forLoopRegExp: RegExp = new RegExp(
  774. `for *\\(const ${variableMatch} of ${variableMatch}\\) *{`,
  775. 'g'
  776. );
  777. const invalidForLoopRegExp: RegExp = new RegExp(
  778. `for *\\(var ${variableMatch} of ${variableMatch}\\) *{`,
  779. 'g'
  780. );
  781. let obfuscatedCode: string;
  782. before(() => {
  783. const code: string = readFileAsString(__dirname + '/fixtures/prevailing-kind-of-variables-1.js');
  784. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  785. code,
  786. {
  787. ...NO_ADDITIONAL_NODES_PRESET,
  788. deadCodeInjection: true,
  789. deadCodeInjectionThreshold: 1
  790. }
  791. ).getObfuscatedCode();
  792. });
  793. it('Match #1: shouldn\'t replace kinds of variables of inserted original code', () => {
  794. assert.match(obfuscatedCode, variableDeclarationsRegExp);
  795. });
  796. it('Match #2: shouldn\'t replace kinds of variables of inserted original code', () => {
  797. assert.notMatch(obfuscatedCode, invalidVariableDeclarationsRegExp);
  798. });
  799. it('Match #3: shouldn\'t replace kinds of variables of inserted original code', () => {
  800. assert.match(obfuscatedCode, forLoopRegExp);
  801. });
  802. it('Match #4: shouldn\'t replace kinds of variables of inserted original code', () => {
  803. assert.notMatch(obfuscatedCode, invalidForLoopRegExp);
  804. });
  805. });
  806. });
  807. });
  808. });