DeadCodeInjectionTransformer.spec.ts 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791
  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. describe('transformNode', function () {
  11. this.timeout(100000);
  12. describe('Variant #1 - 5 simple block statements', () => {
  13. const regExp: RegExp = new RegExp(
  14. `if *\\(${variableMatch}\\('${hexMatch}'\\) *[=|!]== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{`+
  15. `(?:console|${variableMatch})\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  16. `\\} *else *\\{`+
  17. `(?:console|${variableMatch})\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  18. `\\}`,
  19. 'g'
  20. );
  21. const expectedMatchesLength: number = 5;
  22. let matchesLength: number = 0;
  23. before(() => {
  24. const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
  25. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  26. code,
  27. {
  28. ...NO_ADDITIONAL_NODES_PRESET,
  29. deadCodeInjection: true,
  30. deadCodeInjectionThreshold: 1,
  31. stringArray: true,
  32. stringArrayThreshold: 1
  33. }
  34. ).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 obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  56. code,
  57. {
  58. ...NO_ADDITIONAL_NODES_PRESET,
  59. deadCodeInjection: true,
  60. deadCodeInjectionThreshold: 1,
  61. stringArray: true,
  62. stringArrayThreshold: 1
  63. }
  64. ).getObfuscatedCode();
  65. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  66. if (matches) {
  67. matchesLength = matches.length;
  68. }
  69. });
  70. it('shouldn\'t add dead code', () => {
  71. assert.equal(matchesLength, expectedMatchesLength);
  72. });
  73. });
  74. describe('Variant #3 - deadCodeInjectionThreshold: 0', () => {
  75. const regexp: RegExp = new RegExp(
  76. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  77. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  78. `\\};`,
  79. 'g'
  80. );
  81. const expectedMatchesLength: number = 5;
  82. let matchesLength: number = 0;
  83. before(() => {
  84. const code: string = readFileAsString(__dirname + '/fixtures/input-1.js');
  85. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  86. code,
  87. {
  88. ...NO_ADDITIONAL_NODES_PRESET,
  89. deadCodeInjection: true,
  90. deadCodeInjectionThreshold: 0,
  91. stringArray: true,
  92. stringArrayThreshold: 1
  93. }
  94. ).getObfuscatedCode();
  95. const matches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regexp);
  96. if (matches) {
  97. matchesLength = matches.length;
  98. }
  99. });
  100. it('shouldn\'t add dead code', () => {
  101. assert.equal(matchesLength, expectedMatchesLength);
  102. });
  103. });
  104. describe('Variant #4 - break or continue statement in block statement', () => {
  105. describe('Variant #1', () => {
  106. const functionRegExp: RegExp = new RegExp(
  107. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  108. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  109. `\\};`,
  110. 'g'
  111. );
  112. const loopRegExp: RegExp = new RegExp(
  113. `for *\\(var *${variableMatch} *= *${hexMatch}; *${variableMatch} *< *${hexMatch}; *${variableMatch}\\+\\+\\) *\\{` +
  114. `(?:continue|break);` +
  115. `\\}`,
  116. 'g'
  117. );
  118. const expectedFunctionMatchesLength: number = 4;
  119. const expectedLoopMatchesLength: number = 2;
  120. let functionMatchesLength: number = 0,
  121. loopMatchesLength: number = 0;
  122. before(() => {
  123. const code: string = readFileAsString(__dirname + '/fixtures/break-continue-statement-1.js');
  124. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  125. code,
  126. {
  127. ...NO_ADDITIONAL_NODES_PRESET,
  128. deadCodeInjection: true,
  129. deadCodeInjectionThreshold: 1,
  130. stringArray: true,
  131. stringArrayThreshold: 1
  132. }
  133. ).getObfuscatedCode();
  134. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  135. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
  136. if (functionMatches) {
  137. functionMatchesLength = functionMatches.length;
  138. }
  139. if (loopMatches) {
  140. loopMatchesLength = loopMatches.length;
  141. }
  142. });
  143. it('match #1: shouldn\'t add dead code', () => {
  144. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  145. });
  146. it('match #2: shouldn\'t add dead code', () => {
  147. assert.equal(loopMatchesLength, expectedLoopMatchesLength);
  148. });
  149. });
  150. describe('Variant #2', () => {
  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. 'g'
  161. );
  162. const expectedFunctionMatchesLength: number = 4;
  163. const expectedLoopMatchesLength: number = 2;
  164. let functionMatchesLength: number = 0,
  165. loopMatchesLength: number = 0;
  166. before(() => {
  167. const code: string = readFileAsString(__dirname + '/fixtures/break-continue-statement-2.js');
  168. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  169. code,
  170. {
  171. ...NO_ADDITIONAL_NODES_PRESET,
  172. deadCodeInjection: true,
  173. deadCodeInjectionThreshold: 1,
  174. stringArray: true,
  175. stringArrayThreshold: 1
  176. }
  177. ).getObfuscatedCode();
  178. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  179. const loopMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(loopRegExp);
  180. if (functionMatches) {
  181. functionMatchesLength = functionMatches.length;
  182. }
  183. if (loopMatches) {
  184. loopMatchesLength = loopMatches.length;
  185. }
  186. });
  187. it('match #1: shouldn\'t add dead code', () => {
  188. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  189. });
  190. it('match #2: shouldn\'t add dead code', () => {
  191. assert.equal(loopMatchesLength, expectedLoopMatchesLength);
  192. });
  193. });
  194. });
  195. describe('Variant #5 - await expression in block statement', () => {
  196. const functionRegExp: RegExp = new RegExp(
  197. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  198. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  199. `\\};`,
  200. 'g'
  201. );
  202. const awaitExpressionRegExp: RegExp = new RegExp(
  203. `await *${variableMatch}\\(\\)`,
  204. 'g'
  205. );
  206. const expectedFunctionMatchesLength: number = 4;
  207. const expectedAwaitExpressionMatchesLength: number = 1;
  208. let functionMatchesLength: number = 0,
  209. awaitExpressionMatchesLength: number = 0;
  210. before(() => {
  211. const code: string = readFileAsString(__dirname + '/fixtures/await-expression.js');
  212. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  213. code,
  214. {
  215. ...NO_ADDITIONAL_NODES_PRESET,
  216. deadCodeInjection: true,
  217. deadCodeInjectionThreshold: 1,
  218. stringArray: true,
  219. stringArrayThreshold: 1
  220. }
  221. ).getObfuscatedCode();
  222. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  223. const awaitExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(awaitExpressionRegExp);
  224. if (functionMatches) {
  225. functionMatchesLength = functionMatches.length;
  226. }
  227. if (awaitExpressionMatches) {
  228. awaitExpressionMatchesLength = awaitExpressionMatches.length;
  229. }
  230. });
  231. it('match #1: shouldn\'t add dead code', () => {
  232. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  233. });
  234. it('match #2: shouldn\'t add dead code', () => {
  235. assert.equal(awaitExpressionMatchesLength, expectedAwaitExpressionMatchesLength);
  236. });
  237. });
  238. describe('Variant #6 - super expression in block statement', () => {
  239. const functionRegExp: RegExp = new RegExp(
  240. `var *${variableMatch} *= *function *\\(\\) *\\{` +
  241. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  242. `\\};`,
  243. 'g'
  244. );
  245. const superExpressionRegExp: RegExp = new RegExp(
  246. `super *\\(\\);`,
  247. 'g'
  248. );
  249. const expectedFunctionMatchesLength: number = 4;
  250. const expectedSuperExpressionMatchesLength: number = 1;
  251. let functionMatchesLength: number = 0,
  252. superExpressionMatchesLength: number = 0;
  253. before(() => {
  254. const code: string = readFileAsString(__dirname + '/fixtures/super-expression.js');
  255. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  256. code,
  257. {
  258. ...NO_ADDITIONAL_NODES_PRESET,
  259. deadCodeInjection: true,
  260. deadCodeInjectionThreshold: 1,
  261. stringArray: true,
  262. stringArrayThreshold: 1
  263. }
  264. ).getObfuscatedCode();
  265. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(functionRegExp);
  266. const superExpressionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(superExpressionRegExp);
  267. if (functionMatches) {
  268. functionMatchesLength = functionMatches.length;
  269. }
  270. if (superExpressionMatches) {
  271. superExpressionMatchesLength = superExpressionMatches.length;
  272. }
  273. });
  274. it('match #1: shouldn\'t add dead code', () => {
  275. assert.equal(functionMatchesLength, expectedFunctionMatchesLength);
  276. });
  277. it('match #2: shouldn\'t add dead code', () => {
  278. assert.equal(superExpressionMatchesLength, expectedSuperExpressionMatchesLength);
  279. });
  280. });
  281. describe('Variant #7 - chance of `IfStatement` variant', () => {
  282. const samplesCount: number = 1000;
  283. const delta: number = 0.1;
  284. const expectedDistribution: number = 0.25;
  285. const ifMatch: string = `if *\\(!!\\[\\]\\) *\\{`;
  286. const functionMatch: string = `var *${variableMatch} *= *function *\\(\\) *\\{`;
  287. const match1: string = `` +
  288. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  289. `console.*` +
  290. `\\} *else *\\{` +
  291. `alert.*` +
  292. `\\}` +
  293. ``;
  294. const match2: string = `` +
  295. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  296. `console.*` +
  297. `\\} *else *\\{` +
  298. `alert.*` +
  299. `\\}` +
  300. ``;
  301. const match3: string = `` +
  302. `if *\\(${variableMatch}\\('${hexMatch}'\\) *=== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  303. `alert.*` +
  304. `\\} *else *\\{` +
  305. `console.*` +
  306. `\\}` +
  307. ``;
  308. const match4: string = `` +
  309. `if *\\(${variableMatch}\\('${hexMatch}'\\) *!== *${variableMatch}\\('${hexMatch}'\\)\\) *\\{` +
  310. `alert.*` +
  311. `\\} *else *\\{` +
  312. `console.*` +
  313. `\\}` +
  314. ``;
  315. let distribution1: number = 0,
  316. distribution2: number = 0,
  317. distribution3: number = 0,
  318. distribution4: number = 0;
  319. before(() => {
  320. const code: string = readFileAsString(__dirname + '/fixtures/if-statement-variants-distribution.js');
  321. const regExp1: RegExp = new RegExp(`${ifMatch}${functionMatch}${match1}`);
  322. const regExp2: RegExp = new RegExp(`${ifMatch}${functionMatch}${match2}`);
  323. const regExp3: RegExp = new RegExp(`${ifMatch}${functionMatch}${match3}`);
  324. const regExp4: RegExp = new RegExp(`${ifMatch}${functionMatch}${match4}`);
  325. let count1: number = 0;
  326. let count2: number = 0;
  327. let count3: number = 0;
  328. let count4: number = 0;
  329. for (let i = 0; i < samplesCount; i++) {
  330. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  331. code,
  332. {
  333. ...NO_ADDITIONAL_NODES_PRESET,
  334. deadCodeInjection: true,
  335. deadCodeInjectionThreshold: 1,
  336. stringArray: true,
  337. stringArrayThreshold: 1
  338. }
  339. ).getObfuscatedCode();
  340. if (regExp1.test(obfuscatedCode)) {
  341. count1++;
  342. } else if (regExp2.test(obfuscatedCode)) {
  343. count2++;
  344. } else if (regExp3.test(obfuscatedCode)) {
  345. count3++;
  346. } else if (regExp4.test(obfuscatedCode)) {
  347. count4++;
  348. }
  349. }
  350. distribution1 = count1 / samplesCount;
  351. distribution2 = count2 / samplesCount;
  352. distribution3 = count3 / samplesCount;
  353. distribution4 = count4 / samplesCount;
  354. });
  355. it('Variant #1: `IfStatement` variant should have distribution close to `0.25`', () => {
  356. assert.closeTo(distribution1, expectedDistribution, delta);
  357. });
  358. it('Variant #2: `IfStatement` variant should have distribution close to `0.25`', () => {
  359. assert.closeTo(distribution2, expectedDistribution, delta);
  360. });
  361. it('Variant #3: `IfStatement` variant should have distribution close to `0.25`', () => {
  362. assert.closeTo(distribution3, expectedDistribution, delta);
  363. });
  364. it('Variant #4: `IfStatement` variant should have distribution close to `0.25`', () => {
  365. assert.closeTo(distribution4, expectedDistribution, delta);
  366. });
  367. });
  368. describe('Variant #8 - block scope of block statement is `ProgramNode`', () => {
  369. const regExp: RegExp = new RegExp(
  370. `if *\\(!!\\[\\]\\) *{` +
  371. `console\\[${variableMatch}\\('${hexMatch}'\\)\\]\\(${variableMatch}\\('${hexMatch}'\\)\\);` +
  372. `\\}`
  373. );
  374. let obfuscatedCode: string;
  375. before(() => {
  376. const code: string = readFileAsString(__dirname + '/fixtures/block-scope-is-program-node.js');
  377. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  378. code,
  379. {
  380. ...NO_ADDITIONAL_NODES_PRESET,
  381. stringArray: true,
  382. stringArrayThreshold: 1,
  383. deadCodeInjection: true,
  384. deadCodeInjectionThreshold: 1
  385. }
  386. ).getObfuscatedCode();
  387. });
  388. it('shouldn\'t add dead code in block statements with `ProgramNode` block scope', () => {
  389. assert.match(obfuscatedCode, regExp);
  390. });
  391. });
  392. describe('Variant #9 - correct obfuscation of dead-code block statements', () => {
  393. const variableName: string = 'importantVariableName';
  394. let obfuscatedCode: string;
  395. before(() => {
  396. const code: string = readFileAsString(__dirname + '/fixtures/obfuscation-of-dead-code-block-statements.js');
  397. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  398. code,
  399. {
  400. ...NO_ADDITIONAL_NODES_PRESET,
  401. deadCodeInjection: true,
  402. deadCodeInjectionThreshold: 1,
  403. debugProtection: true
  404. }
  405. ).getObfuscatedCode();
  406. });
  407. it('should correctly obfuscate dead-code block statements and prevent any exposing of internal variable names', () => {
  408. assert.notInclude(obfuscatedCode, variableName);
  409. });
  410. });
  411. describe('Variant #10 - unique names for dead code identifiers', () => {
  412. /**
  413. * Code:
  414. *
  415. * (function(variable){
  416. * function foo () {
  417. * return variable.push(1);
  418. * }
  419. *
  420. * function bar () {
  421. * var variable = 1;
  422. * }
  423. *
  424. * function baz() {
  425. * var variable = 2;
  426. * }
  427. *
  428. * function bark() {
  429. * var variable = 3;
  430. * }
  431. *
  432. * function hawk() {
  433. * var variable = 4;
  434. * }
  435. * })([]);
  436. *
  437. * With this code, dead code can be added to the first function `foo`
  438. * If dead code won't be renamed before add - identifier name inside dead code block statement can be
  439. * the same as identifier name inside transformed block statement:
  440. *
  441. * (function(variable){
  442. * function foo () {
  443. * if (1 !== 1) {
  444. * var variable = 1; // <- overwriting value of function parameter
  445. * } else {
  446. * return variable.push(1);
  447. * }
  448. * }
  449. *
  450. * function bar () {
  451. * var variable = 1;
  452. * }
  453. *
  454. * function baz() {
  455. * var variable = 2;
  456. * }
  457. *
  458. * function bark() {
  459. * var variable = 3;
  460. * }
  461. *
  462. * function hawk() {
  463. * var variable = 4;
  464. * }
  465. * })([]);
  466. *
  467. * So, added dead code variable declaration will overwrite a value of function parameter.
  468. * This should never happen.
  469. */
  470. describe('Variant #1', () => {
  471. const functionParameterMatch: string = `` +
  472. `\\(function\\((\\w)\\){` +
  473. ``;
  474. const deadCodeMatch: string = `` +
  475. `function \\w *\\(\\w\\) *{` +
  476. `if *\\(.{0,30}\\) *{` +
  477. `var *(\\w).*?;` +
  478. `} *else *{` +
  479. `return *(\\w).*?;` +
  480. `}` +
  481. `}` +
  482. ``;
  483. const functionParameterRegExp: RegExp = new RegExp(functionParameterMatch);
  484. const deadCodeRegExp: RegExp = new RegExp(deadCodeMatch);
  485. let result: boolean = false,
  486. functionIdentifierName: string | null,
  487. returnIdentifierName: string | null,
  488. variableDeclarationIdentifierName: string | null,
  489. obfuscatedCode: string;
  490. before(() => {
  491. const code: string = readFileAsString(__dirname + '/fixtures/unique-names-for-dead-code-identifiers.js');
  492. for (let i: number = 0; i < 100; i++) {
  493. while (true) {
  494. try {
  495. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  496. code,
  497. {
  498. ...NO_ADDITIONAL_NODES_PRESET,
  499. deadCodeInjection: true,
  500. deadCodeInjectionThreshold: 1,
  501. identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
  502. }
  503. ).getObfuscatedCode();
  504. functionIdentifierName = getRegExpMatch(obfuscatedCode, functionParameterRegExp, 0);
  505. variableDeclarationIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 0);
  506. returnIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 1);
  507. break;
  508. } catch {}
  509. }
  510. if (
  511. // variable declaration from dead code is affects original code
  512. functionIdentifierName === variableDeclarationIdentifierName &&
  513. returnIdentifierName === variableDeclarationIdentifierName
  514. ) {
  515. result = false;
  516. break;
  517. }
  518. result = true;
  519. }
  520. });
  521. it('should generate separate identifiers for common AST and dead code', () => {
  522. assert.isOk(result, 'wrong identifier names');
  523. });
  524. });
  525. describe('Variant #2', () => {
  526. const functionParameterMatch: string = `` +
  527. `\\(function\\((\\w)\\){` +
  528. ``;
  529. const deadCodeMatch: string = `` +
  530. `function \\w *\\(\\w\\) *{` +
  531. `if *\\(.{0,30}\\) *{` +
  532. `return *(\\w).{0,40};` +
  533. `} *else *{` +
  534. `var *(\\w).*?;` +
  535. `}` +
  536. `}` +
  537. ``;
  538. const functionParameterRegExp: RegExp = new RegExp(functionParameterMatch);
  539. const deadCodeRegExp: RegExp = new RegExp(deadCodeMatch);
  540. let result: boolean = false,
  541. functionIdentifierName: string | null,
  542. returnIdentifierName: string | null,
  543. variableDeclarationIdentifierName: string | null,
  544. obfuscatedCode: string;
  545. before(() => {
  546. const code: string = readFileAsString(__dirname + '/fixtures/unique-names-for-dead-code-identifiers.js');
  547. for (let i: number = 0; i < 100; i++) {
  548. while (true) {
  549. try {
  550. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  551. code,
  552. {
  553. ...NO_ADDITIONAL_NODES_PRESET,
  554. deadCodeInjection: true,
  555. deadCodeInjectionThreshold: 1,
  556. identifierNamesGenerator: IdentifierNamesGenerator.MangledIdentifierNamesGenerator
  557. }
  558. ).getObfuscatedCode();
  559. functionIdentifierName = getRegExpMatch(obfuscatedCode, functionParameterRegExp, 0);
  560. returnIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 0);
  561. variableDeclarationIdentifierName = getRegExpMatch(obfuscatedCode, deadCodeRegExp, 1);
  562. break;
  563. } catch {}
  564. }
  565. if (
  566. // variable declaration from dead code is affects original code
  567. functionIdentifierName === variableDeclarationIdentifierName &&
  568. returnIdentifierName === variableDeclarationIdentifierName
  569. ) {
  570. console.log(obfuscatedCode);
  571. result = false;
  572. break;
  573. }
  574. result = true;
  575. }
  576. });
  577. it('should generate separate identifiers for common AST and dead code', () => {
  578. assert.isOk(result, 'wrong identifier names');
  579. });
  580. });
  581. });
  582. describe('Variant #11 - block statements with empty body', () => {
  583. const regExp: RegExp = new RegExp(
  584. `function *${variableMatch} *\\(\\) *{ *} *` +
  585. `${variableMatch} *\\(\\); *`,
  586. 'g'
  587. );
  588. const expectedMatchesLength: number = 5;
  589. let matchesLength: number = 0;
  590. before(() => {
  591. const code: string = readFileAsString(__dirname + '/fixtures/block-statement-empty-body.js');
  592. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  593. code,
  594. {
  595. ...NO_ADDITIONAL_NODES_PRESET,
  596. stringArray: true,
  597. stringArrayThreshold: 1,
  598. deadCodeInjection: true,
  599. deadCodeInjectionThreshold: 1
  600. }
  601. ).getObfuscatedCode();
  602. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
  603. if (functionMatches) {
  604. matchesLength = functionMatches.length;
  605. }
  606. });
  607. it('shouldn\'t add dead code conditions to the block empty block statements', () => {
  608. assert.isAtLeast(matchesLength, expectedMatchesLength);
  609. });
  610. });
  611. describe('Variant #12 - block statement with scope-hoisting', () => {
  612. describe('Variant #1: collecting of block statements', () => {
  613. const regExp: RegExp = new RegExp(
  614. `${variableMatch} *\\(\\); *` +
  615. `var *${variableMatch} *= *0x2; *` +
  616. `function *${variableMatch} *\\(\\) *{ *} *`,
  617. 'g'
  618. );
  619. const expectedMatchesLength: number = 5;
  620. let matchesLength: number = 0;
  621. before(() => {
  622. const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting-1.js');
  623. const obfuscatedCode: string = JavaScriptObfuscator.obfuscate(
  624. code,
  625. {
  626. ...NO_ADDITIONAL_NODES_PRESET,
  627. stringArray: true,
  628. stringArrayThreshold: 1,
  629. deadCodeInjection: true,
  630. deadCodeInjectionThreshold: 1
  631. }
  632. ).getObfuscatedCode();
  633. const functionMatches: RegExpMatchArray = <RegExpMatchArray>obfuscatedCode.match(regExp);
  634. if (functionMatches) {
  635. matchesLength = functionMatches.length;
  636. }
  637. });
  638. it('shouldn\'t collect block statements with scope-hoisting', () => {
  639. assert.equal(matchesLength, expectedMatchesLength);
  640. });
  641. });
  642. describe('Variant #2: wrapping of block statements in dead code conditions', () => {
  643. const regExp: RegExp = new RegExp(
  644. `function *${variableMatch} *\\(\\) *{ *` +
  645. `var *${variableMatch} *= *0x1; *` +
  646. `${variableMatch} *\\(\\); *` +
  647. `var *${variableMatch} *= *0x2; *` +
  648. `function *${variableMatch} *\\(\\) *{ *} *` +
  649. `var *${variableMatch} *= *0x3; *` +
  650. `}`,
  651. 'g'
  652. );
  653. let obfuscatedCode: string;
  654. before(() => {
  655. const code: string = readFileAsString(__dirname + '/fixtures/block-statement-with-scope-hoisting-2.js');
  656. obfuscatedCode = JavaScriptObfuscator.obfuscate(
  657. code,
  658. {
  659. ...NO_ADDITIONAL_NODES_PRESET,
  660. stringArray: true,
  661. stringArrayThreshold: 1,
  662. deadCodeInjection: true,
  663. deadCodeInjectionThreshold: 1
  664. }
  665. ).getObfuscatedCode();
  666. });
  667. it('shouldn\'t wrap block statements in dead code conditions', () => {
  668. assert.match(obfuscatedCode, regExp);
  669. });
  670. });
  671. });
  672. });
  673. });