JavaScriptObfuscator.spec.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. import { assert } from 'chai';
  2. import { IObfuscationResult } from '../../../src/interfaces/IObfuscationResult';
  3. import { JavaScriptObfuscator } from '../../../src/JavaScriptObfuscator';
  4. import { NO_CUSTOM_NODES_PRESET } from '../../../src/options/presets/NoCustomNodes';
  5. import { buildLargeCode } from '../../helpers/buildLargeCode';
  6. import { getRegExpMatch } from '../../helpers/getRegExpMatch';
  7. import { readFileAsString } from '../../helpers/readFileAsString';
  8. describe('JavaScriptObfuscator', () => {
  9. describe('obfuscate (sourceCode: string, customOptions?: IObfuscatorOptions): IObfuscationResult', () => {
  10. describe('correct source code', () => {
  11. let obfuscatedCode: string,
  12. sourceMap: string;
  13. beforeEach(() => {
  14. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  15. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  16. code,
  17. {
  18. ...NO_CUSTOM_NODES_PRESET
  19. }
  20. );
  21. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  22. sourceMap = obfuscationResult.getSourceMap();
  23. });
  24. it('should return correct obfuscated code', () => {
  25. assert.isOk(obfuscatedCode);
  26. });
  27. it('should return empty source map', () => {
  28. assert.isNotOk(sourceMap);
  29. });
  30. });
  31. describe('empty source code', () => {
  32. let obfuscatedCode: string;
  33. beforeEach(() => {
  34. const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
  35. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  36. code,
  37. );
  38. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  39. });
  40. it('should return an empty obfuscated code', () => {
  41. assert.isNotOk(obfuscatedCode);
  42. });
  43. });
  44. describe('empty source code with comments', () => {
  45. let obfuscatedCode: string;
  46. beforeEach(() => {
  47. const code: string = readFileAsString(__dirname + '/fixtures/comments-only.js');
  48. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  49. code,
  50. );
  51. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  52. });
  53. it('should return an empty obfuscated code', () => {
  54. assert.isNotOk(obfuscatedCode);
  55. });
  56. });
  57. describe('`sourceMap` option is `true`', () => {
  58. describe('`sourceMapMode` is `separate`', () => {
  59. let obfuscatedCode: string,
  60. sourceMap: string;
  61. beforeEach(() => {
  62. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  63. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  64. code,
  65. {
  66. ...NO_CUSTOM_NODES_PRESET,
  67. sourceMap: true
  68. }
  69. );
  70. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  71. sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
  72. });
  73. it('should return correct obfuscated code', () => {
  74. assert.isOk(obfuscatedCode);
  75. });
  76. it('should return correct source map', () => {
  77. assert.isOk(sourceMap);
  78. });
  79. });
  80. describe('`sourceMapMode` is `inline`', () => {
  81. const regExp: RegExp = /sourceMappingURL=data:application\/json;base64/;
  82. let obfuscatedCode: string,
  83. sourceMap: string;
  84. beforeEach(() => {
  85. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  86. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  87. code,
  88. {
  89. ...NO_CUSTOM_NODES_PRESET,
  90. sourceMap: true,
  91. sourceMapMode: 'inline'
  92. }
  93. );
  94. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  95. sourceMap = JSON.parse(obfuscationResult.getSourceMap()).mappings;
  96. });
  97. it('should return correct obfuscated code', () => {
  98. assert.isOk(obfuscatedCode);
  99. });
  100. it('should return obfuscated code with inline source map as Base64 string', () => {
  101. assert.match(obfuscatedCode, regExp);
  102. });
  103. it('should return correct source map', () => {
  104. assert.isOk(sourceMap);
  105. });
  106. });
  107. describe('empty source code', () => {
  108. let obfuscatedCode: string,
  109. sourceMapNames: string[],
  110. sourceMapSources: string[],
  111. sourceMapMappings: string;
  112. beforeEach(() => {
  113. const code: string = readFileAsString(__dirname + '/fixtures/empty-input.js');
  114. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  115. code,
  116. {
  117. sourceMap: true
  118. }
  119. );
  120. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  121. const sourceMapObject: any = JSON.parse(obfuscationResult.getSourceMap());
  122. sourceMapNames = sourceMapObject.names;
  123. sourceMapSources = sourceMapObject.sources;
  124. sourceMapMappings = sourceMapObject.mappings;
  125. });
  126. it('should return empty obfuscated code', () => {
  127. assert.isNotOk(obfuscatedCode);
  128. });
  129. it('should return empty source map property `names`', () => {
  130. assert.deepEqual(sourceMapNames, []);
  131. });
  132. it('should return empty source map property `sources`', () => {
  133. assert.deepEqual(sourceMapSources, []);
  134. });
  135. it('should return empty source map property `mappings`', () => {
  136. assert.isNotOk(sourceMapMappings);
  137. });
  138. });
  139. });
  140. describe('variable inside global scope', () => {
  141. const regExp: RegExp = /^var *test *= *0x\d+;$/;
  142. let obfuscatedCode: string;
  143. beforeEach(() => {
  144. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-1.js');
  145. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  146. code,
  147. {
  148. ...NO_CUSTOM_NODES_PRESET
  149. }
  150. );
  151. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  152. });
  153. it('should return correct obfuscated code', () => {
  154. assert.match(obfuscatedCode, regExp);
  155. });
  156. });
  157. describe('variable inside global scope', () => {
  158. const regExp: RegExp = /^\(function *\(\) *\{ *var *_0x[\w]+ *= *0x\d+; *\}(\(\)\)|\)\(\));?$/;
  159. let obfuscatedCode: string;
  160. beforeEach(() => {
  161. const code: string = readFileAsString(__dirname + '/fixtures/block-scope.js');
  162. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  163. code,
  164. {
  165. ...NO_CUSTOM_NODES_PRESET
  166. }
  167. );
  168. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  169. });
  170. it('should return correct obfuscated code', () => {
  171. assert.match(obfuscatedCode, regExp);
  172. });
  173. });
  174. describe('latin literal variable value', () => {
  175. const stringArrayLatinRegExp: RegExp = /^var _0x(\w){4} *= *\['abc'\];/;
  176. const stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/;
  177. let obfuscatedCode: string;
  178. beforeEach(() => {
  179. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
  180. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  181. code,
  182. {
  183. ...NO_CUSTOM_NODES_PRESET,
  184. stringArray: true,
  185. stringArrayThreshold: 1
  186. }
  187. );
  188. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  189. });
  190. it('match #1: should return correct obfuscated code', () => {
  191. assert.match(obfuscatedCode, stringArrayLatinRegExp);
  192. });
  193. it('match #2: should return correct obfuscated code', () => {
  194. assert.match(obfuscatedCode, stringArrayCallRegExp);
  195. });
  196. });
  197. describe('cyrillic literal variable value', () => {
  198. const stringArrayCyrillicRegExp: RegExp = /^var _0x(\w){4} *= *\['абц'\];/;
  199. const stringArrayCallRegExp: RegExp = /var *test *= *_0x(\w){4}\('0x0'\);$/;
  200. let obfuscatedCode: string;
  201. beforeEach(() => {
  202. const code: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
  203. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  204. code,
  205. {
  206. ...NO_CUSTOM_NODES_PRESET,
  207. stringArray: true,
  208. stringArrayThreshold: 1
  209. }
  210. );
  211. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  212. });
  213. it('match #1: should return correct obfuscated code', () => {
  214. assert.match(obfuscatedCode, stringArrayCyrillicRegExp);
  215. });
  216. it('match #2: should return correct obfuscated code', () => {
  217. assert.match(obfuscatedCode, stringArrayCallRegExp);
  218. });
  219. });
  220. describe('seed', function () {
  221. this.timeout(60000);
  222. describe('same seed on each run', () => {
  223. const code: string = readFileAsString('./test/fixtures/sample.js');
  224. const samples: number = 100;
  225. let obfuscatedCode1: string,
  226. obfuscatedCode2: string,
  227. seed: number = 12345,
  228. equalsCount: number = 0;
  229. beforeEach(() => {
  230. for (let i: number = 0; i < samples; i++) {
  231. if (i % 20 === 0) {
  232. seed++;
  233. }
  234. const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  235. code,
  236. {
  237. seed: seed
  238. }
  239. );
  240. const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  241. code,
  242. {
  243. seed: seed
  244. }
  245. );
  246. obfuscatedCode1 = obfuscationResult1.getObfuscatedCode();
  247. obfuscatedCode2 = obfuscationResult2.getObfuscatedCode();
  248. if (obfuscatedCode1 === obfuscatedCode2) {
  249. equalsCount++;
  250. }
  251. }
  252. });
  253. it('should return same code every time with same `seed`', () => {
  254. assert.equal(equalsCount, samples);
  255. });
  256. });
  257. describe('variant #1: different seed on each run', () => {
  258. const code: string = readFileAsString('./test/fixtures/sample.js');
  259. let obfuscatedCode1: string,
  260. obfuscatedCode2: string;
  261. beforeEach(() => {
  262. const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  263. code,
  264. {
  265. seed: 12345
  266. }
  267. );
  268. const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  269. code,
  270. {
  271. seed: 12346
  272. }
  273. );
  274. obfuscatedCode1 = obfuscationResult1.getObfuscatedCode();
  275. obfuscatedCode2 = obfuscationResult2.getObfuscatedCode();
  276. });
  277. it('should return different obfuscated code with different `seed` option value', () => {
  278. assert.notEqual(obfuscatedCode1, obfuscatedCode2);
  279. });
  280. });
  281. describe('variant #2: different seed on each run', () => {
  282. const code: string = readFileAsString('./test/fixtures/sample.js');
  283. let obfuscatedCode1: string,
  284. obfuscatedCode2: string;
  285. beforeEach(() => {
  286. const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  287. code,
  288. {
  289. seed: 0
  290. }
  291. );
  292. const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  293. code,
  294. {
  295. seed: 0
  296. }
  297. );
  298. obfuscatedCode1 = obfuscationResult1.getObfuscatedCode();
  299. obfuscatedCode2 = obfuscationResult2.getObfuscatedCode();
  300. });
  301. it('should return different obfuscated code with different `seed` option value', () => {
  302. assert.notEqual(obfuscatedCode1, obfuscatedCode2);
  303. });
  304. });
  305. describe('variant #3: same seed for different source code', () => {
  306. const code1: string = readFileAsString(__dirname + '/fixtures/simple-input-cyrillic.js');
  307. const code2: string = readFileAsString(__dirname + '/fixtures/simple-input-2.js');
  308. const regExp: RegExp = /var (_0x(\w){4}) *= *\['.*'\];/;
  309. let match1: string,
  310. match2: string;
  311. beforeEach(() => {
  312. const obfuscationResult1: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  313. code1,
  314. {
  315. seed: 123,
  316. stringArrayThreshold: 1
  317. }
  318. );
  319. const obfuscationResult2: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  320. code2,
  321. {
  322. seed: 123,
  323. stringArrayThreshold: 1
  324. }
  325. );
  326. const obfuscatedCode1: string = obfuscationResult1.getObfuscatedCode();
  327. const obfuscatedCode2: string = obfuscationResult2.getObfuscatedCode();
  328. match1 = getRegExpMatch(obfuscatedCode1, regExp);
  329. match2 = getRegExpMatch(obfuscatedCode2, regExp);
  330. });
  331. it('should return different String Array names for different source code with same seed', () => {
  332. assert.notEqual(match1, match2);
  333. });
  334. });
  335. });
  336. describe('new.target MetaProperty', () => {
  337. const regExp: RegExp = /new\.target *=== *Foo/;
  338. let obfuscatedCode: string;
  339. beforeEach(() => {
  340. const code: string = readFileAsString(__dirname + '/fixtures/new-target.js');
  341. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  342. code,
  343. {
  344. ...NO_CUSTOM_NODES_PRESET
  345. }
  346. );
  347. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  348. });
  349. it('should keep new.target MetaProperty', () => {
  350. assert.match(obfuscatedCode, regExp);
  351. });
  352. });
  353. describe('mangle', () => {
  354. const regExp: RegExp = /var *a *= *0x1/;
  355. let obfuscatedCode: string;
  356. beforeEach(() => {
  357. const code: string = readFileAsString(__dirname + '/fixtures/mangle.js');
  358. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  359. code,
  360. {
  361. mangle: true
  362. }
  363. );
  364. obfuscatedCode = obfuscationResult.getObfuscatedCode();
  365. });
  366. it('should mangle obfuscated code', () => {
  367. assert.match(obfuscatedCode, regExp);
  368. });
  369. });
  370. describe('3.5k variables', function () {
  371. this.timeout(200000);
  372. const expectedValue: number = 3500;
  373. let result: number;
  374. beforeEach(() => {
  375. const code: string = buildLargeCode(expectedValue);
  376. const obfuscationResult: IObfuscationResult = JavaScriptObfuscator.obfuscate(
  377. code,
  378. {
  379. compact: true,
  380. controlFlowFlattening: true,
  381. controlFlowFlatteningThreshold: 1,
  382. deadCodeInjection: true,
  383. deadCodeInjectionThreshold: 1,
  384. disableConsoleOutput: false,
  385. rotateStringArray: true,
  386. stringArray: true,
  387. stringArrayEncoding: 'rc4',
  388. stringArrayThreshold: 1,
  389. unicodeEscapeSequence: false
  390. }
  391. );
  392. const obfuscatedCode: string = obfuscationResult.getObfuscatedCode();
  393. result = eval(obfuscatedCode);
  394. });
  395. it('should correctly obfuscate 3.5k variables', () => {
  396. assert.equal(result, expectedValue);
  397. });
  398. });
  399. });
  400. });