js-confuser 1.2.1 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +7 -6
  3. package/dist/options.js +5 -1
  4. package/dist/parser.js +1 -2
  5. package/dist/presets.js +2 -2
  6. package/dist/transforms/calculator.js +48 -60
  7. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +482 -95
  8. package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +4 -0
  9. package/dist/transforms/controlFlowFlattening/{switchCaseObfucation.js → switchCaseObfuscation.js} +2 -2
  10. package/dist/transforms/deadCode.js +1 -1
  11. package/dist/transforms/dispatcher.js +14 -13
  12. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +5 -10
  13. package/dist/transforms/flatten.js +5 -1
  14. package/dist/transforms/hideInitializingCode.js +17 -2
  15. package/dist/transforms/identifier/globalConcealing.js +46 -25
  16. package/dist/transforms/identifier/movedDeclarations.js +69 -68
  17. package/dist/transforms/identifier/renameVariables.js +22 -98
  18. package/dist/transforms/identifier/variableAnalysis.js +133 -0
  19. package/dist/transforms/label.js +11 -2
  20. package/dist/transforms/lock/antiDebug.js +32 -13
  21. package/dist/transforms/lock/lock.js +13 -2
  22. package/dist/transforms/minify.js +117 -120
  23. package/dist/transforms/opaquePredicates.js +4 -2
  24. package/dist/transforms/preparation/preparation.js +8 -0
  25. package/dist/transforms/renameLabels.js +17 -3
  26. package/dist/transforms/rgf.js +8 -3
  27. package/dist/transforms/shuffle.js +25 -9
  28. package/dist/transforms/stack.js +5 -9
  29. package/dist/transforms/string/encoding.js +209 -0
  30. package/dist/transforms/string/stringCompression.js +10 -10
  31. package/dist/transforms/string/stringConcealing.js +94 -65
  32. package/dist/transforms/string/stringSplitting.js +7 -7
  33. package/dist/transforms/transform.js +10 -0
  34. package/dist/traverse.js +1 -35
  35. package/dist/util/gen.js +3 -1
  36. package/dist/util/identifiers.js +9 -19
  37. package/dist/util/insert.js +6 -40
  38. package/dist/util/scope.js +17 -0
  39. package/package.json +2 -2
  40. package/src/options.ts +19 -3
  41. package/src/parser.ts +1 -2
  42. package/src/presets.ts +2 -2
  43. package/src/transforms/calculator.ts +87 -91
  44. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +742 -142
  45. package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +6 -0
  46. package/src/transforms/controlFlowFlattening/{switchCaseObfucation.ts → switchCaseObfuscation.ts} +6 -2
  47. package/src/transforms/deadCode.ts +8 -0
  48. package/src/transforms/dispatcher.ts +29 -14
  49. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +43 -19
  50. package/src/transforms/flatten.ts +15 -2
  51. package/src/transforms/hideInitializingCode.ts +432 -406
  52. package/src/transforms/identifier/globalConcealing.ts +148 -46
  53. package/src/transforms/identifier/movedDeclarations.ts +78 -101
  54. package/src/transforms/identifier/renameVariables.ts +21 -96
  55. package/src/transforms/identifier/variableAnalysis.ts +124 -0
  56. package/src/transforms/label.ts +20 -2
  57. package/src/transforms/lock/antiDebug.ts +69 -26
  58. package/src/transforms/lock/lock.ts +37 -3
  59. package/src/transforms/minify.ts +154 -130
  60. package/src/transforms/opaquePredicates.ts +25 -3
  61. package/src/transforms/preparation/preparation.ts +8 -1
  62. package/src/transforms/renameLabels.ts +26 -3
  63. package/src/transforms/rgf.ts +6 -1
  64. package/src/transforms/shuffle.ts +87 -29
  65. package/src/transforms/stack.ts +6 -8
  66. package/src/transforms/string/encoding.ts +310 -0
  67. package/src/transforms/string/stringCompression.ts +37 -24
  68. package/src/transforms/string/stringConcealing.ts +157 -160
  69. package/src/transforms/string/stringSplitting.ts +12 -8
  70. package/src/transforms/transform.ts +15 -2
  71. package/src/traverse.ts +1 -31
  72. package/src/util/gen.ts +5 -3
  73. package/src/util/identifiers.ts +20 -20
  74. package/src/util/insert.ts +12 -78
  75. package/src/util/scope.ts +9 -0
  76. package/test/{transforms/compare.test.ts → compare.test.ts} +2 -2
  77. package/test/index.test.ts +109 -1
  78. package/test/templates/template.test.ts +14 -0
  79. package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +392 -10
  80. package/test/transforms/dispatcher.test.ts +30 -0
  81. package/test/transforms/flatten.test.ts +28 -0
  82. package/test/transforms/hideInitializingCode.test.ts +336 -336
  83. package/test/transforms/identifier/globalConcealing.test.ts +1 -2
  84. package/test/transforms/identifier/movedDeclarations.test.ts +137 -112
  85. package/test/transforms/identifier/renameVariables.test.ts +124 -13
  86. package/test/transforms/lock/antiDebug.test.ts +43 -0
  87. package/test/transforms/lock/selfDefending.test.ts +68 -0
  88. package/test/transforms/minify.test.ts +137 -0
  89. package/test/transforms/renameLabels.test.ts +33 -0
  90. package/test/transforms/rgf.test.ts +29 -0
  91. package/test/transforms/string/stringSplitting.test.ts +33 -0
  92. package/test/util/identifiers.test.ts +105 -17
  93. package/dist/util/expr.js +0 -60
  94. package/src/util/expr.ts +0 -56
@@ -1,17 +1,25 @@
1
1
  import { ok } from "assert";
2
+ import { compileJsSync } from "../../compiler";
2
3
  import { ObfuscateOrder } from "../../order";
3
4
  import { ComputeProbabilityMap } from "../../probability";
4
5
  import Template from "../../templates/template";
5
6
  import { getBlock, isBlock, walk } from "../../traverse";
6
7
  import {
8
+ ArrayExpression,
9
+ ArrayPattern,
7
10
  AssignmentExpression,
8
11
  BinaryExpression,
9
12
  BreakStatement,
13
+ CallExpression,
10
14
  ConditionalExpression,
11
15
  ExpressionStatement,
16
+ FunctionDeclaration,
12
17
  Identifier,
18
+ IfStatement,
19
+ LabeledStatement,
13
20
  Literal,
14
21
  Node,
22
+ ReturnStatement,
15
23
  SequenceExpression,
16
24
  SwitchCase,
17
25
  SwitchStatement,
@@ -26,6 +34,7 @@ import {
26
34
  import {
27
35
  clone,
28
36
  getBlockBody,
37
+ getFunction,
29
38
  getVarContext,
30
39
  isVarContext,
31
40
  } from "../../util/insert";
@@ -34,7 +43,14 @@ import Transform from "../transform";
34
43
  import ChoiceFlowObfuscation from "./choiceFlowObfuscation";
35
44
  import ControlFlowObfuscation from "./controlFlowObfuscation";
36
45
  import ExpressionObfuscation from "./expressionObfuscation";
37
- import SwitchCaseObfuscation from "./switchCaseObfucation";
46
+ import SwitchCaseObfuscation from "./switchCaseObfuscation";
47
+
48
+ var flattenStructures = new Set([
49
+ "IfStatement",
50
+ "ForStatement",
51
+ "WhileStatement",
52
+ "DoWhileStatement",
53
+ ]);
38
54
 
39
55
  /**
40
56
  * Breaks functions into DAGs (Directed Acyclic Graphs)
@@ -50,23 +66,36 @@ import SwitchCaseObfuscation from "./switchCaseObfucation";
50
66
  * - 3. The while loop continues until the the state variable is the end state.
51
67
  */
52
68
  export default class ControlFlowFlattening extends Transform {
69
+ isDebug = false;
70
+
53
71
  constructor(o) {
54
72
  super(o, ObfuscateOrder.ControlFlowFlattening);
55
73
 
56
- this.before.push(new ExpressionObfuscation(o));
74
+ if (!this.isDebug) {
75
+ this.before.push(new ExpressionObfuscation(o));
57
76
 
58
- this.before.push(new ControlFlowObfuscation(o));
59
- this.after.push(new SwitchCaseObfuscation(o));
77
+ this.after.push(new ControlFlowObfuscation(o));
78
+ this.after.push(new SwitchCaseObfuscation(o));
79
+ } else {
80
+ console.warn("Debug mode enabled");
81
+ }
60
82
 
61
83
  // this.after.push(new ChoiceFlowObfuscation(o));
62
84
  }
63
85
 
64
86
  match(object, parents) {
65
- return isBlock(object);
87
+ return (
88
+ isBlock(object) &&
89
+ (!parents[1] || !flattenStructures.has(parents[1].type)) &&
90
+ (!parents[2] || !flattenStructures.has(parents[2].type))
91
+ );
66
92
  }
67
93
 
68
94
  transform(object, parents) {
69
95
  return () => {
96
+ if (object.body.length < 3) {
97
+ return;
98
+ }
70
99
  if (containsLexicallyBoundVariables(object, parents)) {
71
100
  return;
72
101
  }
@@ -86,16 +115,42 @@ export default class ControlFlowFlattening extends Transform {
86
115
 
87
116
  var functionDeclarations: Set<Node> = new Set();
88
117
  var fnNames: Set<string> = new Set();
118
+ var illegalFnNames = new Set<string>();
119
+
120
+ /**
121
+ * The variable names
122
+ *
123
+ * index -> var name
124
+ */
125
+ var stateVars = Array(this.isDebug ? 1 : getRandomInteger(2, 5))
126
+ .fill(0)
127
+ .map(() => this.getPlaceholder());
89
128
 
90
129
  body.forEach((stmt, i) => {
91
130
  if (stmt.type == "FunctionDeclaration") {
92
131
  functionDeclarations.add(stmt);
93
- fnNames.add(stmt.id && stmt.id.name);
132
+ var name = stmt.id && stmt.id.name;
133
+ fnNames.add(name);
134
+ if (stmt.body.type !== "BlockStatement") {
135
+ illegalFnNames.add(name);
136
+ } else {
137
+ walk(stmt, [body, object, ...parents], (o, p) => {
138
+ if (
139
+ o.type == "ThisExpression" ||
140
+ o.type == "SuperExpression" ||
141
+ (o.type == "Identifier" &&
142
+ (o.name == "arguments" || o.name == "eval"))
143
+ ) {
144
+ illegalFnNames.add(name);
145
+ return "EXIT";
146
+ }
147
+ });
148
+ }
94
149
  }
95
150
  });
96
151
 
97
152
  walk(object, parents, (o, p) => {
98
- if (o.type == "Identifier") {
153
+ if (o.type == "Identifier" && fnNames.has(o.name)) {
99
154
  var info = getIdentifierInfo(o, p);
100
155
  if (!info.spec.isReferenced) {
101
156
  return;
@@ -112,6 +167,50 @@ export default class ControlFlowFlattening extends Transform {
112
167
  fnNames.delete(o.name);
113
168
  }
114
169
  }
170
+
171
+ if (!info.spec.isDefined) {
172
+ var b = getBlock(o, p);
173
+ if (b !== object || !p[0] || p[0].type !== "CallExpression") {
174
+ illegalFnNames.add(o.name);
175
+ } else {
176
+ var isExtractable = false;
177
+ if (p[1]) {
178
+ if (
179
+ p[1].type == "ExpressionStatement" &&
180
+ p[1].expression == p[0] &&
181
+ p[2] == object.body
182
+ ) {
183
+ isExtractable = true;
184
+ p[1].$callExpression = "ExpressionStatement";
185
+ p[1].$fnName = o.name;
186
+ } else if (
187
+ p[1].type == "VariableDeclarator" &&
188
+ p[1].init == p[0] &&
189
+ p[2].length === 1 &&
190
+ p[4] == object.body
191
+ ) {
192
+ isExtractable = true;
193
+ p[3].$callExpression = "VariableDeclarator";
194
+ p[3].$fnName = o.name;
195
+ } else if (
196
+ p[1].type == "AssignmentExpression" &&
197
+ p[1].operator == "=" &&
198
+ p[1].right === p[0] &&
199
+ p[2] &&
200
+ p[2].type == "ExpressionStatement" &&
201
+ p[3] == object.body
202
+ ) {
203
+ isExtractable = true;
204
+ p[2].$callExpression = "AssignmentExpression";
205
+ p[2].$fnName = o.name;
206
+ }
207
+ }
208
+
209
+ if (!isExtractable) {
210
+ illegalFnNames.add(o.name);
211
+ }
212
+ }
213
+ }
115
214
  }
116
215
  });
117
216
 
@@ -120,7 +219,9 @@ export default class ControlFlowFlattening extends Transform {
120
219
  return;
121
220
  }
122
221
 
123
- var chunks: Node[][] = [[]];
222
+ illegalFnNames.forEach((illegal) => {
223
+ fnNames.delete(illegal);
224
+ });
124
225
 
125
226
  var fraction = 0.9;
126
227
  if (body.length > 20) {
@@ -131,74 +232,514 @@ export default class ControlFlowFlattening extends Transform {
131
232
  fraction = 0.5;
132
233
  }
133
234
 
134
- body.forEach((x, i) => {
135
- if (functionDeclarations.has(x)) {
136
- return;
137
- }
235
+ var resultVar = this.getPlaceholder();
236
+ var argVar = this.getPlaceholder();
237
+ var needsResultAndArgVar = false;
238
+ var fnToLabel: { [fnName: string]: string } = Object.create(null);
138
239
 
139
- var currentChunk = chunks[chunks.length - 1];
240
+ fnNames.forEach((fnName) => {
241
+ fnToLabel[fnName] = this.getPlaceholder();
242
+ });
140
243
 
141
- if (!currentChunk.length || Math.random() < fraction) {
142
- currentChunk.push(x);
143
- } else {
144
- // Start new chunk
145
- chunks.push([x]);
244
+ const flattenBody = (
245
+ body: Node[],
246
+ startingLabel = this.getPlaceholder()
247
+ ): { label: string; body: Node[] }[] => {
248
+ var chunks = [];
249
+ var currentBody = [];
250
+ var currentLabel = startingLabel;
251
+ const finishCurrentChunk = (
252
+ pointingLabel?: string,
253
+ newLabel?: string,
254
+ addGotoStatement = true
255
+ ) => {
256
+ if (!newLabel) {
257
+ newLabel = this.getPlaceholder();
258
+ }
259
+ if (!pointingLabel) {
260
+ pointingLabel = newLabel;
261
+ }
262
+
263
+ if (addGotoStatement) {
264
+ currentBody.push({ type: "GotoStatement", label: pointingLabel });
265
+ }
266
+
267
+ chunks.push({
268
+ label: currentLabel,
269
+ body: [...currentBody],
270
+ });
271
+
272
+ currentLabel = newLabel;
273
+ currentBody = [];
274
+ };
275
+
276
+ body.forEach((stmt, i) => {
277
+ if (functionDeclarations.has(stmt)) {
278
+ return;
279
+ }
280
+
281
+ if (stmt.$exit) {
282
+ currentBody.push(stmt);
283
+ currentBody.push(BreakStatement(switchLabel));
284
+ finishCurrentChunk(null, null, false);
285
+ return;
286
+ }
287
+
288
+ if (stmt.$callExpression && fnToLabel[stmt.$fnName]) {
289
+ var afterPath = this.getPlaceholder();
290
+ var args = [];
291
+
292
+ switch (stmt.$callExpression) {
293
+ // var a = fn();
294
+ case "VariableDeclarator":
295
+ args = stmt.declarations[0].init.arguments;
296
+ stmt.declarations[0].init = Identifier(resultVar);
297
+ break;
298
+
299
+ // fn();
300
+ case "ExpressionStatement":
301
+ args = stmt.expression.arguments;
302
+ stmt.expression = Identifier("undefined");
303
+ break;
304
+
305
+ // a = fn();
306
+ case "AssignmentExpression":
307
+ args = stmt.expression.right.arguments;
308
+ stmt.expression.right = Identifier(resultVar);
309
+ break;
310
+ }
311
+
312
+ needsResultAndArgVar = true;
313
+
314
+ currentBody.push(
315
+ ExpressionStatement(
316
+ AssignmentExpression(
317
+ "=",
318
+ Identifier(argVar),
319
+ ArrayExpression([
320
+ {
321
+ type: "StateIdentifier",
322
+ label: afterPath,
323
+ },
324
+ ArrayExpression(args),
325
+ ])
326
+ )
327
+ )
328
+ );
329
+ finishCurrentChunk(fnToLabel[stmt.$fnName], afterPath);
330
+ }
331
+
332
+ if (stmt.type == "GotoStatement" && i !== body.length - 1) {
333
+ finishCurrentChunk(stmt.label);
334
+ return;
335
+ }
336
+
337
+ if (stmt.type == "LabeledStatement") {
338
+ var lbl = stmt.label.name;
339
+ var control = stmt.body;
340
+
341
+ var isSwitchStatement = control.type === "SwitchStatement";
342
+
343
+ if (
344
+ isSwitchStatement ||
345
+ ((control.type == "ForStatement" ||
346
+ control.type == "WhileStatement" ||
347
+ control.type == "DoWhileStatement") &&
348
+ control.body.type == "BlockStatement")
349
+ ) {
350
+ if (isSwitchStatement) {
351
+ if (
352
+ control.cases.length == 0 || // at least 1 case
353
+ control.cases.find(
354
+ (x) =>
355
+ !x.test || // cant be default case
356
+ !x.consequent.length || // must have body
357
+ x.consequent.findIndex(
358
+ (node) => node.type == "BreakStatement"
359
+ ) !==
360
+ x.consequent.length - 1 || // break statement must be at the end
361
+ x.consequent[x.consequent.length - 1].type !== // must end with break statement
362
+ "BreakStatement" ||
363
+ !x.consequent[x.consequent.length - 1].label || // must be labeled and correct
364
+ x.consequent[x.consequent.length - 1].label.name != lbl
365
+ )
366
+ ) {
367
+ currentBody.push(stmt);
368
+ return;
369
+ }
370
+ }
371
+
372
+ var isLoop = !isSwitchStatement;
373
+ var supportContinueStatement = isLoop;
374
+
375
+ var testPath = this.getPlaceholder();
376
+ var updatePath = this.getPlaceholder();
377
+ var bodyPath = this.getPlaceholder();
378
+ var afterPath = this.getPlaceholder();
379
+ var possible = true;
380
+ var toReplace = [];
381
+
382
+ walk(control.body, [], (o, p) => {
383
+ if (
384
+ o.type == "BreakStatement" ||
385
+ (supportContinueStatement && o.type == "ContinueStatement")
386
+ ) {
387
+ if (!o.label || o.label.name !== lbl) {
388
+ possible = false;
389
+ return "EXIT";
390
+ }
391
+ if (o.label.name === lbl) {
392
+ return () => {
393
+ toReplace.push([
394
+ o,
395
+ {
396
+ type: "GotoStatement",
397
+ label:
398
+ o.type == "BreakStatement" ? afterPath : updatePath,
399
+ },
400
+ ]);
401
+ };
402
+ }
403
+ }
404
+ });
405
+ if (!possible) {
406
+ currentBody.push(stmt);
407
+ return;
408
+ }
409
+ toReplace.forEach((v) => this.replace(v[0], v[1]));
410
+
411
+ if (isSwitchStatement) {
412
+ var switchVarName = this.getPlaceholder();
413
+
414
+ currentBody.push(
415
+ VariableDeclaration(
416
+ VariableDeclarator(switchVarName, control.discriminant)
417
+ )
418
+ );
419
+
420
+ var afterPath = this.getPlaceholder();
421
+ finishCurrentChunk();
422
+ control.cases.forEach((switchCase, i) => {
423
+ var entryPath = this.getPlaceholder();
424
+
425
+ currentBody.push(
426
+ IfStatement(
427
+ BinaryExpression(
428
+ "===",
429
+ Identifier(switchVarName),
430
+ switchCase.test
431
+ ),
432
+ [
433
+ {
434
+ type: "GotoStatement",
435
+ label: entryPath,
436
+ },
437
+ ]
438
+ )
439
+ );
440
+
441
+ chunks.push(
442
+ ...flattenBody(
443
+ [
444
+ ...switchCase.consequent.slice(
445
+ 0,
446
+ switchCase.consequent.length - 1
447
+ ),
448
+ {
449
+ type: "GotoStatement",
450
+ label: afterPath,
451
+ },
452
+ ],
453
+ entryPath
454
+ )
455
+ );
456
+
457
+ if (i === control.cases.length - 1) {
458
+ } else {
459
+ finishCurrentChunk();
460
+ }
461
+ });
462
+
463
+ finishCurrentChunk(afterPath, afterPath);
464
+ return;
465
+ } else if (isLoop) {
466
+ var isPostTest = control.type == "DoWhileStatement";
467
+
468
+ // add initializing section to current chunk
469
+ if (control.init) {
470
+ if (control.init.type == "VariableDeclaration") {
471
+ currentBody.push(control.init);
472
+ } else {
473
+ currentBody.push(ExpressionStatement(control.init));
474
+ }
475
+ }
476
+
477
+ // create new label called `testPath` and have current chunk point to it (goto testPath)
478
+ finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath);
479
+
480
+ currentBody.push(
481
+ IfStatement(control.test || Literal(true), [
482
+ {
483
+ type: "GotoStatement",
484
+ label: bodyPath,
485
+ },
486
+ ])
487
+ );
488
+
489
+ // create new label called `bodyPath` and have test body point to afterPath (goto afterPath)
490
+ finishCurrentChunk(afterPath, bodyPath);
491
+
492
+ var innerBothPath = this.getPlaceholder();
493
+ chunks.push(
494
+ ...flattenBody(
495
+ [
496
+ ...control.body.body,
497
+ {
498
+ type: "GotoStatement",
499
+ label: updatePath,
500
+ },
501
+ ],
502
+ innerBothPath
503
+ )
504
+ );
505
+
506
+ finishCurrentChunk(innerBothPath, updatePath);
507
+
508
+ if (control.update) {
509
+ currentBody.push(ExpressionStatement(control.update));
510
+ }
511
+
512
+ finishCurrentChunk(testPath, afterPath);
513
+ return;
514
+ }
515
+ }
516
+ }
517
+
518
+ if (
519
+ stmt.type == "IfStatement" &&
520
+ stmt.consequent.type == "BlockStatement" &&
521
+ (!stmt.alternate || stmt.alternate.type == "BlockStatement")
522
+ ) {
523
+ finishCurrentChunk();
524
+
525
+ var hasAlternate = !!stmt.alternate;
526
+ ok(!(hasAlternate && stmt.alternate.type !== "BlockStatement"));
527
+
528
+ var yesPath = this.getPlaceholder();
529
+ var noPath = this.getPlaceholder();
530
+ var afterPath = this.getPlaceholder();
531
+
532
+ currentBody.push(
533
+ IfStatement(stmt.test, [
534
+ {
535
+ type: "GotoStatement",
536
+ label: yesPath,
537
+ },
538
+ ])
539
+ );
540
+
541
+ chunks.push(
542
+ ...flattenBody(
543
+ [
544
+ ...stmt.consequent.body,
545
+ {
546
+ type: "GotoStatement",
547
+ label: afterPath,
548
+ },
549
+ ],
550
+ yesPath
551
+ )
552
+ );
553
+
554
+ if (hasAlternate) {
555
+ chunks.push(
556
+ ...flattenBody(
557
+ [
558
+ ...stmt.alternate.body,
559
+ {
560
+ type: "GotoStatement",
561
+ label: afterPath,
562
+ },
563
+ ],
564
+ noPath
565
+ )
566
+ );
567
+
568
+ finishCurrentChunk(noPath, afterPath);
569
+ } else {
570
+ finishCurrentChunk(afterPath, afterPath);
571
+ }
572
+
573
+ return;
574
+ }
575
+
576
+ if (!currentBody.length || Math.random() < fraction) {
577
+ currentBody.push(stmt);
578
+ } else {
579
+ // Start new chunk
580
+ finishCurrentChunk();
581
+ currentBody.push(stmt);
582
+ }
583
+ });
584
+
585
+ finishCurrentChunk();
586
+ chunks[chunks.length - 1].body.pop();
587
+
588
+ return chunks;
589
+ };
590
+
591
+ var chunks = [];
592
+
593
+ /**
594
+ * label: switch(a+b+c){...break label...}
595
+ */
596
+ var switchLabel = this.getPlaceholder();
597
+
598
+ functionDeclarations.forEach((node) => {
599
+ if (node.id && fnNames.has(node.id.name)) {
600
+ var exitStateName = this.getPlaceholder();
601
+ var argumentsName = this.getPlaceholder();
602
+
603
+ needsResultAndArgVar = true;
604
+
605
+ node.body.body.push(ReturnStatement());
606
+
607
+ walk(node.body, [], (o, p) => {
608
+ if (o.type == "ReturnStatement") {
609
+ if (!getFunction(o, p)) {
610
+ return () => {
611
+ var exitExpr = SequenceExpression([
612
+ AssignmentExpression(
613
+ "=",
614
+ ArrayPattern(stateVars.map(Identifier)),
615
+ Identifier(exitStateName)
616
+ ),
617
+ AssignmentExpression(
618
+ "=",
619
+ Identifier(resultVar),
620
+ o.argument || Identifier("undefined")
621
+ ),
622
+ ]);
623
+
624
+ this.replace(o, ReturnStatement(exitExpr));
625
+ };
626
+ }
627
+ }
628
+ });
629
+
630
+ var declarations = [
631
+ VariableDeclarator(
632
+ ArrayPattern([
633
+ Identifier(exitStateName),
634
+ Identifier(argumentsName),
635
+ ]),
636
+ Identifier(argVar)
637
+ ),
638
+ ];
639
+
640
+ if (node.params.length) {
641
+ declarations.push(
642
+ VariableDeclarator(
643
+ ArrayPattern(node.params),
644
+ Identifier(argumentsName)
645
+ )
646
+ );
647
+ }
648
+
649
+ var innerName = this.getPlaceholder();
650
+
651
+ chunks.push(
652
+ ...flattenBody(
653
+ [
654
+ FunctionDeclaration(
655
+ innerName,
656
+ [],
657
+ [VariableDeclaration(declarations), ...node.body.body]
658
+ ),
659
+ this.objectAssign(
660
+ ExpressionStatement(
661
+ CallExpression(Identifier(innerName), [])
662
+ ),
663
+ {
664
+ $exit: true,
665
+ } as any
666
+ ),
667
+ ],
668
+ fnToLabel[node.id.name]
669
+ )
670
+ );
146
671
  }
147
672
  });
148
673
 
149
- if (!chunks[chunks.length - 1].length) {
150
- chunks.pop();
151
- }
152
- if (chunks.length < 2) {
153
- return;
154
- }
674
+ var startLabel = this.getPlaceholder();
155
675
 
156
- // Add empty chunks
157
- Array(getRandomInteger(1, 10))
158
- .fill(0)
159
- .forEach(() => {
160
- chunks.splice(getRandomInteger(0, chunks.length), 0, []);
161
- });
676
+ chunks.push(...flattenBody(body, startLabel));
677
+ chunks[chunks.length - 1].body.push({
678
+ type: "GotoStatement",
679
+ label: "END_LABEL",
680
+ });
681
+ chunks.push({
682
+ label: "END_LABEL",
683
+ body: [],
684
+ });
162
685
 
163
686
  var caseSelection: Set<number> = new Set();
164
687
 
165
- var uniqueStatesNeeded = chunks.length + 1;
166
-
167
- for (var i = 0; i < uniqueStatesNeeded; i++) {
168
- var newState;
169
- do {
170
- newState = getRandomInteger(1, chunks.length * 15);
171
- } while (caseSelection.has(newState));
688
+ var uniqueStatesNeeded = chunks.length;
689
+ var endLabel = chunks[Object.keys(chunks).length - 1].label;
172
690
 
691
+ do {
692
+ var newState = getRandomInteger(1, chunks.length * 15);
693
+ if (this.isDebug) {
694
+ newState = caseSelection.size;
695
+ }
173
696
  caseSelection.add(newState);
174
- }
697
+ } while (caseSelection.size !== uniqueStatesNeeded);
175
698
 
176
699
  ok(caseSelection.size == uniqueStatesNeeded);
177
700
 
701
+ /**
702
+ * The accumulated state values
703
+ *
704
+ * index -> total state value
705
+ */
178
706
  var caseStates = Array.from(caseSelection);
179
707
 
180
- var startState = caseStates[0];
181
- var endState = caseStates[caseStates.length - 1];
708
+ /**
709
+ * The individual state values for each label
710
+ *
711
+ * labels right now are just chunk indexes (numbers)
712
+ *
713
+ * but will expand to if statements and functions when `goto statement` obfuscation is added
714
+ */
715
+ var labelToStates: { [label: string]: number[] } = Object.create(null);
182
716
 
183
- var stateVars = Array(getRandomInteger(2, 7))
184
- .fill(0)
185
- .map(() => this.getPlaceholder());
186
- var stateValues = Array(stateVars.length)
187
- .fill(0)
188
- .map(() => getRandomInteger(-250, 250));
717
+ Object.values(chunks).forEach((chunk, i) => {
718
+ var state = caseStates[i];
189
719
 
190
- const getCurrentState = () => {
191
- return stateValues.reduce((a, b) => b + a, 0);
192
- };
720
+ var stateValues = Array(stateVars.length)
721
+ .fill(0)
722
+ .map(() => getRandomInteger(-250, 250));
723
+
724
+ const getCurrentState = () => {
725
+ return stateValues.reduce((a, b) => b + a, 0);
726
+ };
727
+
728
+ var correctIndex = getRandomInteger(0, stateValues.length);
729
+ stateValues[correctIndex] =
730
+ state - (getCurrentState() - stateValues[correctIndex]);
731
+
732
+ labelToStates[chunk.label] = stateValues;
733
+ });
193
734
 
194
- var correctIndex = getRandomInteger(0, stateVars.length);
195
- stateValues[correctIndex] =
196
- startState - (getCurrentState() - stateValues[correctIndex]);
735
+ // console.log(labelToStates);
197
736
 
198
- var initStateValues = [...stateValues];
737
+ var initStateValues = [...labelToStates[startLabel]];
738
+ var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0);
199
739
 
200
- const numberLiteral = (num, depth) => {
201
- if (depth > 12 || Math.random() > 0.9 / (depth * 4)) {
740
+ const numberLiteral = (num, depth, stateValues) => {
741
+ ok(Array.isArray(stateValues));
742
+ if (depth > 10 || Math.random() > 0.8 / (depth * 4)) {
202
743
  return Literal(num);
203
744
  }
204
745
 
@@ -211,13 +752,17 @@ export default class ControlFlowFlattening extends Transform {
211
752
  operator == "<"
212
753
  ? x < stateValues[opposing]
213
754
  : x > stateValues[opposing];
214
- var correct = numberLiteral(num, depth + 1);
215
- var incorrect = numberLiteral(getRandomInteger(-250, 250), depth + 1);
755
+ var correct = numberLiteral(num, depth + 1, stateValues);
756
+ var incorrect = numberLiteral(
757
+ getRandomInteger(-250, 250),
758
+ depth + 1,
759
+ stateValues
760
+ );
216
761
 
217
762
  return ConditionalExpression(
218
763
  BinaryExpression(
219
764
  operator,
220
- numberLiteral(x, depth + 1),
765
+ numberLiteral(x, depth + 1, stateValues),
221
766
  Identifier(stateVars[opposing])
222
767
  ),
223
768
  answer ? correct : incorrect,
@@ -228,135 +773,180 @@ export default class ControlFlowFlattening extends Transform {
228
773
  return BinaryExpression(
229
774
  "+",
230
775
  Identifier(stateVars[opposing]),
231
- numberLiteral(num - stateValues[opposing], depth + 1)
776
+ numberLiteral(num - stateValues[opposing], depth + 1, stateValues)
232
777
  );
233
778
  };
234
779
 
235
- const createTransitionStatement = (index, add) => {
236
- var newValue = stateValues[index] + add;
780
+ const createTransitionExpression = (
781
+ index: number,
782
+ add: number,
783
+ mutatingStateValues: number[]
784
+ ) => {
785
+ var newValue = mutatingStateValues[index] + add;
237
786
 
238
787
  var expr = null;
239
788
 
240
- if (Math.random() > 0.5) {
241
- expr = ExpressionStatement(
242
- AssignmentExpression(
243
- "+=",
244
- Identifier(stateVars[index]),
245
- numberLiteral(add, 0)
246
- )
789
+ if (this.isDebug) {
790
+ expr = AssignmentExpression(
791
+ "=",
792
+ Identifier(stateVars[index]),
793
+ Literal(newValue)
794
+ );
795
+ } else if (Math.random() > 0.5) {
796
+ expr = AssignmentExpression(
797
+ "+=",
798
+ Identifier(stateVars[index]),
799
+ numberLiteral(add, 0, mutatingStateValues)
247
800
  );
248
801
  } else {
249
- var double = stateValues[index] * 2;
802
+ var double = mutatingStateValues[index] * 2;
250
803
  var diff = double - newValue;
251
804
 
252
805
  var first = AssignmentExpression(
253
806
  "*=",
254
807
  Identifier(stateVars[index]),
255
- numberLiteral(2, 0)
256
- );
257
- stateValues[index] = double;
258
-
259
- expr = ExpressionStatement(
260
- SequenceExpression([
261
- first,
262
- AssignmentExpression(
263
- "-=",
264
- Identifier(stateVars[index]),
265
- numberLiteral(diff, 0)
266
- ),
267
- ])
808
+ numberLiteral(2, 0, mutatingStateValues)
268
809
  );
810
+ mutatingStateValues[index] = double;
811
+
812
+ expr = SequenceExpression([
813
+ first,
814
+ AssignmentExpression(
815
+ "-=",
816
+ Identifier(stateVars[index]),
817
+ numberLiteral(diff, 0, mutatingStateValues)
818
+ ),
819
+ ]);
269
820
  }
270
821
 
271
- stateValues[index] = newValue;
822
+ mutatingStateValues[index] = newValue;
272
823
 
273
824
  return expr;
274
825
  };
275
826
 
276
827
  interface Case {
277
828
  state: number;
278
- nextState: number;
279
829
  body: Node[];
280
830
  order: number;
281
- transitionStatements: Node[];
282
831
  }
283
832
 
284
833
  var order = Object.create(null);
285
- var cases: Case[] = chunks.map((body, i) => {
286
- body.forEach((stmt) => {
834
+ var cases: Case[] = [];
835
+
836
+ chunks.forEach((chunk, i) => {
837
+ // skip last case, its empty and never ran
838
+ if (chunk.label === endLabel) {
839
+ return;
840
+ }
841
+
842
+ ok(labelToStates[chunk.label]);
843
+ var state = caseStates[i];
844
+ var made = 1;
845
+
846
+ var breaksInsertion = [];
847
+ var staticStateValues = [...labelToStates[chunk.label]];
848
+
849
+ chunk.body.forEach((stmt, stmtIndex) => {
850
+ var addBreak = false;
287
851
  walk(stmt, [], (o, p) => {
288
852
  if (
853
+ !this.isDebug &&
289
854
  o.type == "Literal" &&
290
855
  typeof o.value === "number" &&
291
856
  Math.floor(o.value) === o.value &&
292
857
  Math.abs(o.value) < 100_000 &&
293
- Math.random() > 0.5 &&
858
+ Math.random() < 4 / made &&
294
859
  !p.find((x) => isVarContext(x))
295
860
  ) {
861
+ made++;
296
862
  return () => {
297
- if (
298
- p[0].type == "Property" ||
299
- p[0].type == "MethodDefinition"
300
- ) {
301
- if (!p[0].computed) {
302
- p[0].computed = true;
303
- }
863
+ this.replaceIdentifierOrLiteral(
864
+ o,
865
+ numberLiteral(o.value, 0, staticStateValues),
866
+ p
867
+ );
868
+ };
869
+ }
870
+
871
+ if (o.type == "StateIdentifier") {
872
+ return () => {
873
+ ok(labelToStates[o.label]);
874
+ this.replace(
875
+ o,
876
+ ArrayExpression(labelToStates[o.label].map(Literal))
877
+ );
878
+ };
879
+ }
880
+
881
+ if (o.type == "GotoStatement") {
882
+ return () => {
883
+ var blockIndex = p.findIndex((node) => isBlock(node));
884
+ if (blockIndex === -1) {
885
+ addBreak = true;
886
+ } else {
887
+ var child = p[blockIndex - 2] || o;
888
+ var childIndex = p[blockIndex].body.indexOf(child);
889
+
890
+ p[blockIndex].body.splice(
891
+ childIndex + 1,
892
+ 0,
893
+ BreakStatement(switchLabel)
894
+ );
304
895
  }
305
- this.replace(o, numberLiteral(o.value, 0));
896
+
897
+ var mutatingStateValues = [...labelToStates[chunk.label]];
898
+ var nextStateValues = labelToStates[o.label];
899
+ ok(nextStateValues, o.label);
900
+ this.replace(
901
+ o,
902
+ ExpressionStatement(
903
+ SequenceExpression(
904
+ mutatingStateValues.map((_v, stateValueIndex) => {
905
+ var diff =
906
+ nextStateValues[stateValueIndex] -
907
+ mutatingStateValues[stateValueIndex];
908
+ return createTransitionExpression(
909
+ stateValueIndex,
910
+ diff,
911
+ mutatingStateValues
912
+ );
913
+ })
914
+ )
915
+ )
916
+ );
306
917
  };
307
918
  }
308
919
  });
920
+
921
+ if (addBreak) {
922
+ breaksInsertion.push(stmtIndex);
923
+ }
309
924
  });
310
925
 
311
- var state = caseStates[i];
312
- var nextState = caseStates[i + 1];
313
- var diff = nextState - state;
314
- var transitionStatements = [];
315
-
316
- ok(!isNaN(diff));
317
- var modifying = getRandomInteger(0, stateVars.length);
318
- var shift = 0;
319
-
320
- // var c1 = Identifier("undefined");
321
- // this.addComment(c1, stateValues.join(", "));
322
- // transitionStatements.push(c1);
323
-
324
- transitionStatements.push(
325
- ...Array.from(
326
- new Set(
327
- Array(getRandomInteger(0, stateVars.length - 2))
328
- .fill(0)
329
- .map(() => getRandomInteger(0, stateVars.length))
330
- .filter((x) => x != modifying)
331
- )
332
- ).map((x) => {
333
- var randomNumber = getRandomInteger(-250, 250);
926
+ breaksInsertion.sort();
927
+ breaksInsertion.reverse();
334
928
 
335
- shift += randomNumber;
336
- return createTransitionStatement(x, randomNumber);
337
- })
338
- );
339
- transitionStatements.push(
340
- createTransitionStatement(modifying, diff - shift)
341
- );
929
+ breaksInsertion.forEach((index) => {
930
+ chunk.body.splice(index + 1, 0, BreakStatement(switchLabel));
931
+ });
342
932
 
343
933
  // var c = Identifier("undefined");
344
934
  // this.addComment(c, stateValues.join(", "));
345
935
  // transitionStatements.push(c);
346
936
 
347
937
  var caseObject = {
348
- body: body,
938
+ body: chunk.body,
349
939
  state: state,
350
- nextState: nextState,
351
940
  order: i,
352
- transitionStatements: transitionStatements,
353
941
  };
354
942
  order[i] = caseObject;
355
943
 
356
- return caseObject;
944
+ cases.push(caseObject);
357
945
  });
358
946
 
359
- shuffle(cases);
947
+ if (!this.isDebug) {
948
+ shuffle(cases);
949
+ }
360
950
 
361
951
  var discriminant = Template(`${stateVars.join("+")}`).single().expression;
362
952
 
@@ -364,7 +954,9 @@ export default class ControlFlowFlattening extends Transform {
364
954
 
365
955
  if (functionDeclarations.size) {
366
956
  functionDeclarations.forEach((x) => {
367
- body.unshift(clone(x));
957
+ if (!x.id || illegalFnNames.has(x.id.name)) {
958
+ body.unshift(clone(x));
959
+ }
368
960
  });
369
961
  }
370
962
 
@@ -375,28 +967,36 @@ export default class ControlFlowFlattening extends Transform {
375
967
 
376
968
  statements.push(...x.body);
377
969
 
378
- statements.push(...x.transitionStatements);
379
-
380
- statements.push(BreakStatement());
381
-
382
970
  var test = Literal(x.state);
383
971
 
384
972
  return SwitchCase(test, statements);
385
973
  })
386
974
  );
387
975
 
976
+ var declarations = [];
977
+
978
+ if (needsResultAndArgVar) {
979
+ declarations.push(VariableDeclarator(resultVar));
980
+ declarations.push(VariableDeclarator(argVar));
981
+ }
982
+
983
+ declarations.push(
984
+ ...stateVars.map((stateVar, i) => {
985
+ return VariableDeclarator(stateVar, Literal(initStateValues[i]));
986
+ })
987
+ );
988
+
388
989
  body.push(
389
- VariableDeclaration(
390
- stateVars.map((stateVar, i) => {
391
- return VariableDeclarator(stateVar, Literal(initStateValues[i]));
392
- })
393
- ),
990
+ VariableDeclaration(declarations),
394
991
 
395
992
  WhileStatement(
396
993
  BinaryExpression("!=", clone(discriminant), Literal(endState)),
397
- [switchStatement]
994
+ [LabeledStatement(switchLabel, switchStatement)]
398
995
  )
399
996
  );
997
+
998
+ // mark this object for switch case obfuscation
999
+ switchStatement.$controlFlowFlattening = true;
400
1000
  };
401
1001
  }
402
1002
  }