js-confuser 1.2.2 → 1.4.2

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 (78) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/README.md +4 -1
  3. package/dist/parser.js +1 -2
  4. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +482 -91
  5. package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +4 -0
  6. package/dist/transforms/controlFlowFlattening/{switchCaseObfucation.js → switchCaseObfuscation.js} +2 -2
  7. package/dist/transforms/deadCode.js +1 -1
  8. package/dist/transforms/dispatcher.js +7 -6
  9. package/dist/transforms/eval.js +1 -1
  10. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +4 -2
  11. package/dist/transforms/hideInitializingCode.js +4 -1
  12. package/dist/transforms/identifier/globalConcealing.js +18 -8
  13. package/dist/transforms/identifier/variableAnalysis.js +1 -1
  14. package/dist/transforms/label.js +11 -2
  15. package/dist/transforms/lock/antiDebug.js +32 -13
  16. package/dist/transforms/lock/lock.js +3 -3
  17. package/dist/transforms/minify.js +2 -2
  18. package/dist/transforms/opaquePredicates.js +4 -2
  19. package/dist/transforms/preparation/preparation.js +8 -0
  20. package/dist/transforms/renameLabels.js +17 -3
  21. package/dist/transforms/rgf.js +8 -3
  22. package/dist/transforms/stack.js +1 -1
  23. package/dist/transforms/string/encoding.js +74 -0
  24. package/dist/transforms/string/stringCompression.js +6 -2
  25. package/dist/transforms/string/stringConcealing.js +1 -1
  26. package/dist/transforms/string/stringSplitting.js +6 -0
  27. package/dist/traverse.js +0 -34
  28. package/dist/util/gen.js +3 -1
  29. package/dist/util/identifiers.js +8 -18
  30. package/dist/util/insert.js +4 -38
  31. package/package.json +2 -2
  32. package/src/options.ts +3 -3
  33. package/src/parser.ts +1 -2
  34. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +735 -134
  35. package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +6 -0
  36. package/src/transforms/controlFlowFlattening/{switchCaseObfucation.ts → switchCaseObfuscation.ts} +6 -2
  37. package/src/transforms/deadCode.ts +8 -0
  38. package/src/transforms/dispatcher.ts +16 -6
  39. package/src/transforms/eval.ts +2 -1
  40. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +40 -5
  41. package/src/transforms/hideInitializingCode.ts +432 -425
  42. package/src/transforms/identifier/globalConcealing.ts +102 -38
  43. package/src/transforms/identifier/variableAnalysis.ts +1 -1
  44. package/src/transforms/label.ts +20 -2
  45. package/src/transforms/lock/antiDebug.ts +69 -33
  46. package/src/transforms/lock/lock.ts +4 -5
  47. package/src/transforms/minify.ts +2 -1
  48. package/src/transforms/opaquePredicates.ts +25 -3
  49. package/src/transforms/preparation/preparation.ts +8 -1
  50. package/src/transforms/renameLabels.ts +26 -3
  51. package/src/transforms/rgf.ts +6 -1
  52. package/src/transforms/stack.ts +2 -1
  53. package/src/transforms/string/encoding.ts +107 -1
  54. package/src/transforms/string/stringCompression.ts +28 -3
  55. package/src/transforms/string/stringConcealing.ts +2 -0
  56. package/src/transforms/string/stringSplitting.ts +11 -0
  57. package/src/transforms/transform.ts +1 -2
  58. package/src/traverse.ts +0 -30
  59. package/src/util/gen.ts +5 -3
  60. package/src/util/identifiers.ts +18 -19
  61. package/src/util/insert.ts +10 -76
  62. package/src/util/scope.ts +9 -9
  63. package/test/{transforms/compare.test.ts → compare.test.ts} +2 -2
  64. package/test/index.test.ts +109 -1
  65. package/test/templates/template.test.ts +14 -0
  66. package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +392 -10
  67. package/test/transforms/dispatcher.test.ts +30 -0
  68. package/test/transforms/eval.test.ts +28 -0
  69. package/test/transforms/flatten.test.ts +28 -0
  70. package/test/transforms/hideInitializingCode.test.ts +336 -336
  71. package/test/transforms/identifier/renameVariables.test.ts +31 -0
  72. package/test/transforms/lock/antiDebug.test.ts +43 -0
  73. package/test/transforms/renameLabels.test.ts +33 -0
  74. package/test/transforms/rgf.test.ts +29 -0
  75. package/test/transforms/string/stringSplitting.test.ts +33 -0
  76. package/test/util/identifiers.test.ts +105 -17
  77. package/dist/util/expr.js +0 -60
  78. 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
+ };
193
727
 
194
- var correctIndex = getRandomInteger(0, stateVars.length);
195
- stateValues[correctIndex] =
196
- startState - (getCurrentState() - stateValues[correctIndex]);
728
+ var correctIndex = getRandomInteger(0, stateValues.length);
729
+ stateValues[correctIndex] =
730
+ state - (getCurrentState() - stateValues[correctIndex]);
197
731
 
198
- var initStateValues = [...stateValues];
732
+ labelToStates[chunk.label] = stateValues;
733
+ });
734
+
735
+ // console.log(labelToStates);
736
+
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,66 +773,84 @@ 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) => {
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];
286
844
  var made = 1;
287
845
 
288
- body.forEach((stmt) => {
846
+ var breaksInsertion = [];
847
+ var staticStateValues = [...labelToStates[chunk.label]];
848
+
849
+ chunk.body.forEach((stmt, stmtIndex) => {
850
+ var addBreak = false;
289
851
  walk(stmt, [], (o, p) => {
290
852
  if (
853
+ !this.isDebug &&
291
854
  o.type == "Literal" &&
292
855
  typeof o.value === "number" &&
293
856
  Math.floor(o.value) === o.value &&
@@ -299,63 +862,91 @@ export default class ControlFlowFlattening extends Transform {
299
862
  return () => {
300
863
  this.replaceIdentifierOrLiteral(
301
864
  o,
302
- numberLiteral(o.value, 0),
865
+ numberLiteral(o.value, 0, staticStateValues),
303
866
  p
304
867
  );
305
868
  };
306
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
+ );
895
+ }
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
+ );
917
+ };
918
+ }
307
919
  });
920
+
921
+ if (addBreak) {
922
+ breaksInsertion.push(stmtIndex);
923
+ }
308
924
  });
309
925
 
310
- var state = caseStates[i];
311
- var nextState = caseStates[i + 1];
312
- var diff = nextState - state;
313
- var transitionStatements = [];
314
-
315
- ok(!isNaN(diff));
316
- var modifying = getRandomInteger(0, stateVars.length);
317
- var shift = 0;
318
-
319
- // var c1 = Identifier("undefined");
320
- // this.addComment(c1, stateValues.join(", "));
321
- // transitionStatements.push(c1);
322
-
323
- transitionStatements.push(
324
- ...Array.from(
325
- new Set(
326
- Array(getRandomInteger(0, stateVars.length - 2))
327
- .fill(0)
328
- .map(() => getRandomInteger(0, stateVars.length))
329
- .filter((x) => x != modifying)
330
- )
331
- ).map((x) => {
332
- var randomNumber = getRandomInteger(-250, 250);
926
+ breaksInsertion.sort();
927
+ breaksInsertion.reverse();
333
928
 
334
- shift += randomNumber;
335
- return createTransitionStatement(x, randomNumber);
336
- })
337
- );
338
- transitionStatements.push(
339
- createTransitionStatement(modifying, diff - shift)
340
- );
929
+ breaksInsertion.forEach((index) => {
930
+ chunk.body.splice(index + 1, 0, BreakStatement(switchLabel));
931
+ });
341
932
 
342
933
  // var c = Identifier("undefined");
343
934
  // this.addComment(c, stateValues.join(", "));
344
935
  // transitionStatements.push(c);
345
936
 
346
937
  var caseObject = {
347
- body: body,
938
+ body: chunk.body,
348
939
  state: state,
349
- nextState: nextState,
350
940
  order: i,
351
- transitionStatements: transitionStatements,
352
941
  };
353
942
  order[i] = caseObject;
354
943
 
355
- return caseObject;
944
+ cases.push(caseObject);
356
945
  });
357
946
 
358
- shuffle(cases);
947
+ if (!this.isDebug) {
948
+ shuffle(cases);
949
+ }
359
950
 
360
951
  var discriminant = Template(`${stateVars.join("+")}`).single().expression;
361
952
 
@@ -363,7 +954,9 @@ export default class ControlFlowFlattening extends Transform {
363
954
 
364
955
  if (functionDeclarations.size) {
365
956
  functionDeclarations.forEach((x) => {
366
- body.unshift(clone(x));
957
+ if (!x.id || illegalFnNames.has(x.id.name)) {
958
+ body.unshift(clone(x));
959
+ }
367
960
  });
368
961
  }
369
962
 
@@ -374,28 +967,36 @@ export default class ControlFlowFlattening extends Transform {
374
967
 
375
968
  statements.push(...x.body);
376
969
 
377
- statements.push(...x.transitionStatements);
378
-
379
- statements.push(BreakStatement());
380
-
381
970
  var test = Literal(x.state);
382
971
 
383
972
  return SwitchCase(test, statements);
384
973
  })
385
974
  );
386
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
+
387
989
  body.push(
388
- VariableDeclaration(
389
- stateVars.map((stateVar, i) => {
390
- return VariableDeclarator(stateVar, Literal(initStateValues[i]));
391
- })
392
- ),
990
+ VariableDeclaration(declarations),
393
991
 
394
992
  WhileStatement(
395
993
  BinaryExpression("!=", clone(discriminant), Literal(endState)),
396
- [switchStatement]
994
+ [LabeledStatement(switchLabel, switchStatement)]
397
995
  )
398
996
  );
997
+
998
+ // mark this object for switch case obfuscation
999
+ switchStatement.$controlFlowFlattening = true;
399
1000
  };
400
1001
  }
401
1002
  }