js-confuser 1.7.1 → 1.7.3

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 (153) hide show
  1. package/.github/workflows/node.js.yml +1 -1
  2. package/CHANGELOG.md +73 -0
  3. package/README.md +32 -31
  4. package/dist/compiler.js +2 -8
  5. package/dist/constants.js +22 -10
  6. package/dist/index.js +15 -30
  7. package/dist/obfuscator.js +15 -62
  8. package/dist/options.js +33 -40
  9. package/dist/order.js +4 -7
  10. package/dist/parser.js +5 -13
  11. package/dist/precedence.js +6 -8
  12. package/dist/presets.js +4 -6
  13. package/dist/probability.js +13 -24
  14. package/dist/templates/bufferToString.js +121 -5
  15. package/dist/templates/core.js +35 -0
  16. package/dist/templates/crash.js +22 -11
  17. package/dist/templates/es5.js +125 -6
  18. package/dist/templates/functionLength.js +24 -6
  19. package/dist/templates/globals.js +9 -0
  20. package/dist/templates/template.js +189 -43
  21. package/dist/transforms/antiTooling.js +26 -22
  22. package/dist/transforms/calculator.js +19 -55
  23. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +242 -333
  24. package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +46 -25
  25. package/dist/transforms/deadCode.js +542 -31
  26. package/dist/transforms/dispatcher.js +112 -112
  27. package/dist/transforms/es5/antiClass.js +70 -44
  28. package/dist/transforms/es5/antiDestructuring.js +14 -38
  29. package/dist/transforms/es5/antiES6Object.js +39 -48
  30. package/dist/transforms/es5/antiSpreadOperator.js +5 -14
  31. package/dist/transforms/es5/antiTemplate.js +10 -19
  32. package/dist/transforms/es5/es5.js +7 -40
  33. package/dist/transforms/extraction/classExtraction.js +83 -0
  34. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +41 -80
  35. package/dist/transforms/extraction/objectExtraction.js +24 -56
  36. package/dist/transforms/finalizer.js +6 -20
  37. package/dist/transforms/flatten.js +51 -99
  38. package/dist/transforms/identifier/globalAnalysis.js +21 -26
  39. package/dist/transforms/identifier/globalConcealing.js +72 -56
  40. package/dist/transforms/identifier/movedDeclarations.js +66 -38
  41. package/dist/transforms/identifier/renameVariables.js +36 -68
  42. package/dist/transforms/identifier/variableAnalysis.js +21 -48
  43. package/dist/transforms/lock/antiDebug.js +20 -25
  44. package/dist/transforms/lock/integrity.js +53 -52
  45. package/dist/transforms/lock/lock.js +161 -126
  46. package/dist/transforms/minify.js +77 -108
  47. package/dist/transforms/opaquePredicates.js +12 -49
  48. package/dist/transforms/preparation.js +28 -49
  49. package/dist/transforms/renameLabels.js +5 -22
  50. package/dist/transforms/rgf.js +125 -72
  51. package/dist/transforms/shuffle.js +42 -47
  52. package/dist/transforms/stack.js +41 -98
  53. package/dist/transforms/string/encoding.js +76 -27
  54. package/dist/transforms/string/stringCompression.js +75 -68
  55. package/dist/transforms/string/stringConcealing.js +127 -135
  56. package/dist/transforms/string/stringEncoding.js +6 -26
  57. package/dist/transforms/string/stringSplitting.js +5 -30
  58. package/dist/transforms/transform.js +76 -104
  59. package/dist/traverse.js +11 -18
  60. package/dist/util/compare.js +27 -29
  61. package/dist/util/gen.js +32 -86
  62. package/dist/util/guard.js +5 -1
  63. package/dist/util/identifiers.js +9 -72
  64. package/dist/util/insert.js +27 -77
  65. package/dist/util/math.js +0 -3
  66. package/dist/util/object.js +3 -7
  67. package/dist/util/random.js +31 -36
  68. package/dist/util/scope.js +6 -3
  69. package/docs/Countermeasures.md +13 -6
  70. package/docs/Integrity.md +35 -28
  71. package/docs/RGF.md +6 -1
  72. package/docs/RenameVariables.md +116 -0
  73. package/docs/TamperProtection.md +100 -0
  74. package/docs/Template.md +117 -0
  75. package/package.json +3 -3
  76. package/src/constants.ts +17 -0
  77. package/src/index.ts +7 -5
  78. package/src/options.ts +60 -7
  79. package/src/order.ts +2 -2
  80. package/src/templates/bufferToString.ts +79 -11
  81. package/src/templates/core.ts +29 -0
  82. package/src/templates/crash.ts +6 -38
  83. package/src/templates/es5.ts +1 -1
  84. package/src/templates/functionLength.ts +21 -3
  85. package/src/templates/globals.ts +3 -0
  86. package/src/templates/template.ts +205 -46
  87. package/src/transforms/antiTooling.ts +33 -11
  88. package/src/transforms/calculator.ts +4 -2
  89. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +12 -5
  90. package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +46 -10
  91. package/src/transforms/deadCode.ts +74 -42
  92. package/src/transforms/dispatcher.ts +99 -73
  93. package/src/transforms/es5/antiClass.ts +25 -12
  94. package/src/transforms/es5/antiDestructuring.ts +1 -1
  95. package/src/transforms/es5/antiES6Object.ts +2 -2
  96. package/src/transforms/es5/antiTemplate.ts +1 -1
  97. package/src/transforms/extraction/classExtraction.ts +168 -0
  98. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +11 -16
  99. package/src/transforms/extraction/objectExtraction.ts +4 -15
  100. package/src/transforms/flatten.ts +20 -5
  101. package/src/transforms/identifier/globalAnalysis.ts +18 -1
  102. package/src/transforms/identifier/globalConcealing.ts +119 -72
  103. package/src/transforms/identifier/movedDeclarations.ts +90 -24
  104. package/src/transforms/identifier/renameVariables.ts +16 -1
  105. package/src/transforms/lock/antiDebug.ts +2 -2
  106. package/src/transforms/lock/integrity.ts +13 -11
  107. package/src/transforms/lock/lock.ts +122 -30
  108. package/src/transforms/minify.ts +28 -13
  109. package/src/transforms/opaquePredicates.ts +2 -2
  110. package/src/transforms/preparation.ts +16 -0
  111. package/src/transforms/rgf.ts +139 -12
  112. package/src/transforms/shuffle.ts +3 -3
  113. package/src/transforms/stack.ts +19 -4
  114. package/src/transforms/string/encoding.ts +88 -51
  115. package/src/transforms/string/stringCompression.ts +86 -17
  116. package/src/transforms/string/stringConcealing.ts +148 -118
  117. package/src/transforms/string/stringEncoding.ts +1 -2
  118. package/src/transforms/string/stringSplitting.ts +1 -2
  119. package/src/transforms/transform.ts +63 -46
  120. package/src/types.ts +2 -0
  121. package/src/util/compare.ts +39 -5
  122. package/src/util/gen.ts +10 -3
  123. package/src/util/guard.ts +10 -0
  124. package/src/util/insert.ts +17 -0
  125. package/src/util/random.ts +81 -1
  126. package/src/util/scope.ts +14 -2
  127. package/test/code/Cash.test.ts +94 -5
  128. package/test/code/StrictMode.src.js +65 -0
  129. package/test/code/StrictMode.test.js +37 -0
  130. package/test/compare.test.ts +62 -2
  131. package/test/options.test.ts +129 -55
  132. package/test/templates/template.test.ts +211 -1
  133. package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +37 -18
  134. package/test/transforms/dispatcher.test.ts +55 -0
  135. package/test/transforms/extraction/classExtraction.test.ts +86 -0
  136. package/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +8 -0
  137. package/test/transforms/extraction/objectExtraction.test.ts +2 -0
  138. package/test/transforms/identifier/globalConcealing.test.ts +89 -0
  139. package/test/transforms/identifier/movedDeclarations.test.ts +61 -0
  140. package/test/transforms/identifier/renameVariables.test.ts +75 -1
  141. package/test/transforms/lock/tamperProtection.test.ts +336 -0
  142. package/test/transforms/minify.test.ts +37 -0
  143. package/test/transforms/rgf.test.ts +50 -0
  144. package/dist/transforms/controlFlowFlattening/choiceFlowObfuscation.js +0 -62
  145. package/dist/transforms/controlFlowFlattening/controlFlowObfuscation.js +0 -159
  146. package/dist/transforms/controlFlowFlattening/switchCaseObfuscation.js +0 -106
  147. package/dist/transforms/eval.js +0 -84
  148. package/dist/transforms/hexadecimalNumbers.js +0 -63
  149. package/dist/transforms/hideInitializingCode.js +0 -270
  150. package/dist/transforms/identifier/nameRecycling.js +0 -218
  151. package/dist/transforms/label.js +0 -67
  152. package/dist/transforms/preparation/nameConflicts.js +0 -116
  153. package/dist/transforms/preparation/preparation.js +0 -188
package/src/options.ts CHANGED
@@ -146,7 +146,7 @@ export interface ObfuscateOptions {
146
146
  *
147
147
  * ⚠️ Significantly impacts performance, use sparingly!
148
148
  *
149
- * [Control-flow Flattening](https://docs.jscrambler.com/code-integrity/documentation/transformations/control-flow-flattening) hinders program comprehension by creating convoluted switch statements. (`true/false/0-1`)
149
+ * Control-flow Flattening hinders program comprehension by creating convoluted switch statements. (`true/false/0-1`)
150
150
  *
151
151
  * Use a number to control the percentage from 0 to 1.
152
152
  *
@@ -187,7 +187,7 @@ export interface ObfuscateOptions {
187
187
  /**
188
188
  * ### `stringConcealing`
189
189
  *
190
- * [String Concealing](https://docs.jscrambler.com/code-integrity/documentation/transformations/string-concealing) involves encoding strings to conceal plain-text values. (`true/false/0-1`)
190
+ * String Concealing involves encoding strings to conceal plain-text values. (`true/false/0-1`)
191
191
  *
192
192
  * `"console"` -> `decrypt('<~@rH7+Dert~>')`
193
193
  *
@@ -200,7 +200,7 @@ export interface ObfuscateOptions {
200
200
  /**
201
201
  * ### `stringEncoding`
202
202
  *
203
- * [String Encoding](https://docs.jscrambler.com/code-integrity/documentation/transformations/string-encoding) transforms a string into an encoded representation. (`true/false/0-1`)
203
+ * String Encoding transforms a string into an encoded representation. (`true/false/0-1`)
204
204
  *
205
205
  * `"console"` -> `'\x63\x6f\x6e\x73\x6f\x6c\x65'`
206
206
  *
@@ -215,7 +215,7 @@ export interface ObfuscateOptions {
215
215
  /**
216
216
  * ### `stringSplitting`
217
217
  *
218
- * [String Splitting](https://docs.jscrambler.com/code-integrity/documentation/transformations/string-splitting) splits your strings into multiple expressions. (`true/false/0-1`)
218
+ * String Splitting splits your strings into multiple expressions. (`true/false/0-1`)
219
219
  *
220
220
  * `"console"` -> `String.fromCharCode(99) + 'ons' + 'ole'`
221
221
  *
@@ -230,7 +230,7 @@ export interface ObfuscateOptions {
230
230
  /**
231
231
  * ### `duplicateLiteralsRemoval`
232
232
  *
233
- * [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a single variable name. (`true/false`)
233
+ * Duplicate Literals Removal replaces duplicate literals with a single variable name. (`true/false`)
234
234
  *
235
235
  * - Potency Medium
236
236
  * - Resilience Low
@@ -406,6 +406,22 @@ export interface ObfuscateOptions {
406
406
  */
407
407
  context?: string[];
408
408
 
409
+ /**
410
+ * ### `lock.tamperProtection`
411
+ *
412
+ * Tamper Protection safeguards the runtime behavior from being altered by JavaScript pitfalls. (`true/false`)
413
+ *
414
+ * **⚠️ Tamper Protection requires eval and ran in a non-strict mode environment!**
415
+ *
416
+ * - **This can break your code.**
417
+ * - **Due to the security concerns of arbitrary code execution, you must enable this yourself.**
418
+ *
419
+ * [Learn more here](https://github.com/MichaelXF/js-confuser/blob/master/TamperProtection.md).
420
+ *
421
+ * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
422
+ */
423
+ tamperProtection?: boolean | ((varName: string) => boolean);
424
+
409
425
  /**
410
426
  * ### `lock.startDate`
411
427
  *
@@ -585,6 +601,15 @@ export interface ObfuscateOptions {
585
601
  * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
586
602
  */
587
603
  debugComments?: boolean;
604
+
605
+ /**
606
+ * ### `preserveFunctionLength`
607
+ *
608
+ * Modified functions will retain the correct `function.length` property. Enabled by default. (`true/false`)
609
+ *
610
+ * [See all settings here](https://github.com/MichaelXF/js-confuser/blob/master/README.md#options)
611
+ */
612
+ preserveFunctionLength?: boolean;
588
613
  }
589
614
 
590
615
  const validProperties = new Set([
@@ -619,6 +644,21 @@ const validProperties = new Set([
619
644
  "verbose",
620
645
  "globalVariables",
621
646
  "debugComments",
647
+ "preserveFunctionLength",
648
+ ]);
649
+
650
+ const validLockProperties = new Set([
651
+ "selfDefending",
652
+ "antiDebug",
653
+ "context",
654
+ "tamperProtection",
655
+ "startDate",
656
+ "endDate",
657
+ "domainLock",
658
+ "osLock",
659
+ "browserLock",
660
+ "integrity",
661
+ "countermeasures",
622
662
  ]);
623
663
 
624
664
  const validOses = new Set(["windows", "linux", "osx", "ios", "android"]);
@@ -654,6 +694,7 @@ export function validateOptions(options: ObfuscateOptions) {
654
694
  options.target,
655
695
  "Missing options.target option (required, must one the following: 'browser' or 'node')"
656
696
  );
697
+
657
698
  ok(
658
699
  ["browser", "node"].includes(options.target),
659
700
  `'${options.target}' is not a valid target mode`
@@ -675,6 +716,13 @@ export function validateOptions(options: ObfuscateOptions) {
675
716
  }
676
717
 
677
718
  if (options.lock) {
719
+ ok(typeof options.lock === "object", "options.lock must be an object");
720
+ Object.keys(options.lock).forEach((key) => {
721
+ if (!validLockProperties.has(key)) {
722
+ throw new TypeError("Invalid lock option: '" + key + "'");
723
+ }
724
+ });
725
+
678
726
  // Validate browser-lock option
679
727
  if (
680
728
  options.lock.browserLock &&
@@ -764,13 +812,18 @@ export async function correctOptions(
764
812
  if (!options.hasOwnProperty("renameGlobals")) {
765
813
  options.renameGlobals = true; // RenameGlobals is on by default
766
814
  }
815
+ if (!options.hasOwnProperty("preserveFunctionLength")) {
816
+ options.preserveFunctionLength = true; // preserveFunctionLength is on by default
817
+ }
767
818
 
768
819
  if (options.globalVariables && !(options.globalVariables instanceof Set)) {
769
820
  options.globalVariables = new Set(Object.keys(options.globalVariables));
770
821
  }
771
822
 
772
- if (options.lock && options.lock.selfDefending) {
773
- options.compact = true; // self defending forcibly enables this
823
+ if (options.lock) {
824
+ if (options.lock.selfDefending) {
825
+ options.compact = true; // self defending forcibly enables this
826
+ }
774
827
  }
775
828
 
776
829
  // options.globalVariables outlines generic globals that should be present in the execution context
package/src/order.ts CHANGED
@@ -46,11 +46,11 @@ export enum ObfuscateOrder {
46
46
 
47
47
  Minify = 28,
48
48
 
49
+ AntiTooling = 29,
50
+
49
51
  RenameVariables = 30,
50
52
 
51
53
  ES5 = 31,
52
54
 
53
- AntiTooling = 34,
54
-
55
55
  Finalizer = 35,
56
56
  }
@@ -1,19 +1,89 @@
1
+ import {
2
+ placeholderVariablePrefix,
3
+ predictableFunctionTag,
4
+ } from "../constants";
5
+ import Transform from "../transforms/transform";
6
+ import { Node } from "../util/gen";
1
7
  import Template from "./template";
2
8
 
3
- export const BufferToStringTemplate = Template(`
4
- function __getGlobal(){
9
+ export const createGetGlobalTemplate = (
10
+ transform: Transform,
11
+ object: Node,
12
+ parents: Node[]
13
+ ) => {
14
+ var options = transform.options;
15
+ if (options.lock?.tamperProtection) {
16
+ return new Template(`
17
+ function {getGlobalFnName}(){
18
+ var localVar = false;
19
+ eval(${transform.jsConfuserVar("localVar")} + " = true")
20
+ if (!localVar) {
21
+ {countermeasures}
22
+ }
23
+
24
+ const root = eval("this");
25
+ return root;
26
+ }
27
+ `).setDefaultVariables({
28
+ countermeasures: transform.lockTransform.getCounterMeasuresCode(
29
+ object,
30
+ parents
31
+ ),
32
+ });
33
+ }
34
+
35
+ return GetGlobalTemplate;
36
+ };
37
+
38
+ const GetGlobalTemplate = new Template(`
39
+ function ${placeholderVariablePrefix}CFG__getGlobalThis${predictableFunctionTag}(){
40
+ return globalThis
41
+ }
42
+
43
+ function ${placeholderVariablePrefix}CFG__getGlobal${predictableFunctionTag}(){
44
+ return global
45
+ }
46
+
47
+ function ${placeholderVariablePrefix}CFG__getWindow${predictableFunctionTag}(){
48
+ return window
49
+ }
50
+
51
+ function ${placeholderVariablePrefix}CFG__getThisFunction${predictableFunctionTag}(){
52
+ return new Function("return this")()
53
+ }
54
+
55
+ function {getGlobalFnName}(array = [
56
+ ${placeholderVariablePrefix}CFG__getGlobalThis${predictableFunctionTag},
57
+ ${placeholderVariablePrefix}CFG__getGlobal${predictableFunctionTag},
58
+ ${placeholderVariablePrefix}CFG__getWindow${predictableFunctionTag},
59
+ ${placeholderVariablePrefix}CFG__getThisFunction${predictableFunctionTag}
60
+ ]){
61
+ var bestMatch
62
+ var itemsToSearch = []
5
63
  try {
6
- return global||window|| ( new Function("return this") )();
7
- } catch ( e ) {
64
+ bestMatch = Object
65
+ itemsToSearch["push"](("")["__proto__"]["constructor"]["name"])
66
+ } catch(e) {
67
+
68
+ }
69
+ A: for(var i = 0; i < array["length"]; i++) {
8
70
  try {
9
- return this;
10
- } catch ( e ) {
11
- return {};
12
- }
71
+ bestMatch = array[i]()
72
+ for(var j = 0; j < itemsToSearch["length"]; j++) {
73
+ if(typeof bestMatch[itemsToSearch[j]] === "undefined") continue A;
74
+ }
75
+ return bestMatch
76
+ } catch(e) {}
13
77
  }
78
+
79
+ return bestMatch || this;
14
80
  }
81
+ `);
15
82
 
16
- var __globalObject = __getGlobal() || {};
83
+ export const BufferToStringTemplate = new Template(`
84
+ {GetGlobalTemplate}
85
+
86
+ var __globalObject = {getGlobalFnName}() || {};
17
87
  var __TextDecoder = __globalObject["TextDecoder"];
18
88
  var __Uint8Array = __globalObject["Uint8Array"];
19
89
  var __Buffer = __globalObject["Buffer"];
@@ -63,6 +133,4 @@ export const BufferToStringTemplate = Template(`
63
133
  return utf8ArrayToStr(buffer);
64
134
  }
65
135
  }
66
-
67
-
68
136
  `);
@@ -0,0 +1,29 @@
1
+ import Template from "./template";
2
+
3
+ export const IndexOfTemplate = new Template(`
4
+ function indexOf(str, substr) {
5
+ const len = str.length;
6
+ const sublen = substr.length;
7
+ let count = 0;
8
+
9
+ if (sublen > len) {
10
+ return -1;
11
+ }
12
+
13
+ for (let i = 0; i <= len - sublen; i++) {
14
+ for (let j = 0; j < sublen; j++) {
15
+ if (str[i + j] === substr[j]) {
16
+ count++;
17
+ if (count === sublen) {
18
+ return i;
19
+ }
20
+ } else {
21
+ count = 0;
22
+ break;
23
+ }
24
+ }
25
+ }
26
+
27
+ return -1;
28
+ }
29
+ `);
@@ -1,13 +1,14 @@
1
1
  import Template from "./template";
2
2
 
3
- export const CrashTemplate1 = Template(`
4
- var {var} = "a";
5
- while(1){
6
- {var} = {var} += "a";
3
+ export const CrashTemplate1 = new Template(`
4
+ var {var} = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,./:;<=>?@[]^_\`{|}~"';
5
+ while(true){
6
+ {var} = {var};
7
+ if(!{var}) break;
7
8
  }
8
9
  `);
9
10
 
10
- export const CrashTemplate2 = Template(`
11
+ export const CrashTemplate2 = new Template(`
11
12
  while(true) {
12
13
  var {var} = 99;
13
14
  for({var} = 99; {var} == {var}; {var} *= {var}) {
@@ -20,36 +21,3 @@ while(true) {
20
21
  {var}--
21
22
  }
22
23
  };`);
23
-
24
- export const CrashTemplate3 = Template(`
25
- try {
26
- function {$2}(y, x){
27
- return x;
28
- }
29
-
30
- var {$1} = {$2}(this, function () {
31
- var {$3} = function () {
32
- var regExp = {$3}
33
- .constructor('return /" + this + "/')()
34
- .constructor('^([^ ]+( +[^ ]+)+)+[^ ]}');
35
-
36
- return !regExp.call({$1});
37
- };
38
-
39
- return {$3}();
40
- });
41
-
42
- {$1}();
43
- } catch ( {$1}e ) {
44
- while({$1}e ? {$1}e : !{$1}e){
45
- var {$1}b;
46
- var {$1}c = 0;
47
- ({$1}e ? !{$1}e : {$1}e) ? (function({$1}e){
48
- {$1}c = {$1}e ? 0 : !{$1}e ? 1 : 0;
49
- })({$1}e) : {$1}b = 1;
50
-
51
- if({$1}b&&{$1}c){break;}
52
- if({$1}b){continue;}
53
- }
54
- }
55
- `);
@@ -5,7 +5,7 @@ import Template from "./template";
5
5
  *
6
6
  * Source: https://vanillajstoolkit.com/polyfills/
7
7
  */
8
- export const ES5Template = Template(`
8
+ export const ES5Template = new Template(`
9
9
  if (!Array.prototype.forEach) {
10
10
  Array.prototype.forEach = function forEach (callback, thisArg) {
11
11
  if (typeof callback !== 'function') {
@@ -3,12 +3,30 @@ import Template from "./template";
3
3
  /**
4
4
  * Helper function to set `function.length` property.
5
5
  */
6
- export const FunctionLengthTemplate = Template(`
6
+ export const FunctionLengthTemplate = new Template(
7
+ `
7
8
  function {name}(functionObject, functionLength){
8
- Object["defineProperty"](functionObject, "length", {
9
+ {ObjectDefineProperty}(functionObject, "length", {
9
10
  "value": functionLength,
10
11
  "configurable": true
11
12
  });
12
13
  return functionObject;
13
14
  }
14
- `);
15
+ `,
16
+ `
17
+ function {name}(functionObject, functionLength){
18
+ return {ObjectDefineProperty}(functionObject, "length", {
19
+ "value": functionLength,
20
+ "configurable": true
21
+ });
22
+ }
23
+ `,
24
+ `
25
+ function {name}(functionObject, functionLength){
26
+ return {ObjectDefineProperty}["call"](null, functionObject, "length", {
27
+ "value": functionLength,
28
+ "configurable": true
29
+ });
30
+ }
31
+ `
32
+ );
@@ -0,0 +1,3 @@
1
+ import Template from "./template";
2
+
3
+ export const ObjectDefineProperty = new Template(`Object["defineProperty"]`);
@@ -1,71 +1,230 @@
1
1
  import { Node } from "../util/gen";
2
2
  import { parseSnippet, parseSync } from "../parser";
3
+ import { ok } from "assert";
4
+ import { choice } from "../util/random";
5
+ import { placeholderVariablePrefix } from "../constants";
6
+ import traverse from "../traverse";
7
+
8
+ export interface TemplateVariables {
9
+ [varName: string]:
10
+ | string
11
+ | (() => Node | Node[] | Template)
12
+ | Node
13
+ | Node[]
14
+ | Template;
15
+ }
3
16
 
4
- export interface ITemplate {
5
- fill(variables?: { [name: string]: string | number }): string;
6
-
7
- compile(variables?: { [name: string]: string | number }): Node[];
8
-
9
- single(variables?: { [name: string]: string | number }): Node;
17
+ /**
18
+ * Templates provides an easy way to parse code snippets into AST subtrees.
19
+ *
20
+ * These AST subtrees can added to the obfuscated code, tailored with variable names.
21
+ *
22
+ * 1. Basic string interpolation
23
+ *
24
+ * ```js
25
+ * var Base64Template = new Template(`
26
+ * function {name}(str){
27
+ * return btoa(str)
28
+ * }
29
+ * `);
30
+ *
31
+ * var functionDeclaration = Base64Template.single({ name: "atob" });
32
+ * ```
33
+ *
34
+ * 2. AST subtree insertion
35
+ *
36
+ * ```js
37
+ * var Base64Template = new Template(`
38
+ * function {name}(str){
39
+ * {getWindow}
40
+ *
41
+ * return {getWindowName}btoa(str)
42
+ * }`)
43
+ *
44
+ * var functionDeclaration = Base64Template.single({
45
+ * name: "atob",
46
+ * getWindowName: "newWindow",
47
+ * getWindow: () => {
48
+ * return acorn.parse("var newWindow = {}").body[0];
49
+ * }
50
+ * });
51
+ * ```
52
+ *
53
+ * Here, the `getWindow` variable is a function that returns an AST subtree. This must be a `Node[]` array or Template.
54
+ * Optionally, the function can be replaced with just the `Node[]` array or Template if it's already computed.
55
+ *
56
+ * 3. Template subtree insertion
57
+ *
58
+ * ```js
59
+ * var NewWindowTemplate = new Template(`
60
+ * var newWindow = {};
61
+ * `);
62
+ *
63
+ * var Base64Template = new Template(`
64
+ * function {name}(str){
65
+ * {NewWindowTemplate}
66
+ *
67
+ * return newWindow.btoa(str)
68
+ * }`)
69
+ *
70
+ * var functionDeclaration = Base64Template.single({
71
+ * name: "atob",
72
+ * NewWindowTemplate: NewWindowTemplate
73
+ * });
74
+ * ```
75
+ */
76
+ export default class Template {
77
+ templates: string[];
78
+ defaultVariables: TemplateVariables;
79
+ requiredVariables: Set<string>;
80
+
81
+ constructor(...templates: string[]) {
82
+ this.templates = templates;
83
+ this.defaultVariables = Object.create(null);
84
+ this.requiredVariables = new Set<string>();
85
+
86
+ this.findRequiredVariables();
87
+ }
10
88
 
11
- source: string;
12
- }
89
+ setDefaultVariables(defaultVariables: TemplateVariables): this {
90
+ this.defaultVariables = defaultVariables;
91
+ return this;
92
+ }
13
93
 
14
- export default function Template(template: string): ITemplate {
15
- var neededVariables = 0;
16
- while (template.includes(`{$${neededVariables + 1}}`)) {
17
- neededVariables++;
94
+ private findRequiredVariables() {
95
+ var matches = this.templates[0].match(/{[$A-z0-9_]+}/g);
96
+ if (matches !== null) {
97
+ matches.forEach((variable) => {
98
+ var name = variable.slice(1, -1);
99
+
100
+ // $ variables are for default variables
101
+ if (name.startsWith("$")) {
102
+ throw new Error("Default variables are no longer supported.");
103
+ } else {
104
+ this.requiredVariables.add(name);
105
+ }
106
+ });
107
+ }
18
108
  }
19
- var vars = Object.create(null);
20
- new Array(neededVariables + 1).fill(0).forEach((x, i) => {
21
- vars["\\$" + i] = "temp_" + i;
22
- });
23
-
24
- function fill(variables?: { [name: string]: string | number }): string {
25
- if (!variables) {
26
- variables = Object.create(null);
109
+
110
+ /**
111
+ * Interpolates the template with the given variables.
112
+ *
113
+ * Prepares the template string for AST parsing.
114
+ *
115
+ * @param variables
116
+ */
117
+ private interpolateTemplate(variables: TemplateVariables = {}) {
118
+ var allVariables = { ...this.defaultVariables, ...variables };
119
+
120
+ // Validate all variables were passed in
121
+ for (var requiredVariable of this.requiredVariables) {
122
+ if (typeof allVariables[requiredVariable] === "undefined") {
123
+ throw new Error(
124
+ this.templates[0] +
125
+ " missing variable: " +
126
+ requiredVariable +
127
+ " from " +
128
+ JSON.stringify(allVariables)
129
+ );
130
+ }
27
131
  }
28
132
 
29
- var cloned = template;
133
+ var template = choice(this.templates);
134
+ var output = template;
30
135
 
31
- var keys = { ...variables, ...vars };
136
+ Object.keys(allVariables).forEach((name) => {
137
+ var bracketName = "{" + name.replace("$", "\\$") + "}";
32
138
 
33
- Object.keys(keys).forEach((name) => {
34
- var bracketName = "{" + name + "}";
35
- var value = keys[name] + "";
139
+ var value = allVariables[name] + "";
140
+ if (typeof allVariables[name] !== "string") {
141
+ value = name;
142
+ }
36
143
 
37
144
  var reg = new RegExp(bracketName, "g");
38
145
 
39
- cloned = cloned.replace(reg, value);
146
+ output = output.replace(reg, value);
40
147
  });
41
148
 
42
- return cloned;
149
+ return { output, template };
43
150
  }
44
151
 
45
- function compile(variables: { [name: string]: string | number }): Node[] {
46
- var code = fill(variables);
47
- try {
48
- var program = parseSnippet(code);
152
+ /**
153
+ * Finds the variables in the AST and replaces them with the given values.
154
+ *
155
+ * Note: Mutates the AST.
156
+ * @param ast
157
+ * @param variables
158
+ */
159
+ private interpolateAST(ast: Node, variables: TemplateVariables) {
160
+ var allVariables = { ...this.defaultVariables, ...variables };
161
+
162
+ var astNames = new Set(
163
+ Object.keys(allVariables).filter((name) => {
164
+ return typeof allVariables[name] !== "string";
165
+ })
166
+ );
167
+
168
+ if (astNames.size === 0) return;
169
+
170
+ traverse(ast, (o, p) => {
171
+ if (o.type === "Identifier" && allVariables[o.name]) {
172
+ return () => {
173
+ var value = allVariables[o.name];
174
+ ok(typeof value !== "string");
175
+
176
+ var insertNodes = typeof value === "function" ? value() : value;
177
+ if (insertNodes instanceof Template) {
178
+ insertNodes = insertNodes.compile(allVariables);
179
+ }
180
+
181
+ if (!Array.isArray(insertNodes)) {
182
+ // Replace with expression
183
+
184
+ Object.assign(o, insertNodes);
185
+ } else {
186
+ // Insert multiple statements/declarations
187
+ var expressionStatement: Node = p[0];
188
+ var body: Node[] = p[1] as any;
189
+
190
+ ok(expressionStatement.type === "ExpressionStatement");
191
+ ok(Array.isArray(body));
192
+
193
+ var index = body.indexOf(expressionStatement);
194
+
195
+ body.splice(index, 1, ...insertNodes);
196
+ }
197
+ };
198
+ }
199
+ });
200
+ }
201
+
202
+ compile(variables: TemplateVariables = {}): Node[] {
203
+ var { output, template } = this.interpolateTemplate(variables);
49
204
 
50
- return program.body;
205
+ var program: Node;
206
+ try {
207
+ program = parseSnippet(output);
51
208
  } catch (e) {
52
- console.error(e);
53
- console.error(template);
54
- throw new Error("Template failed to parse");
209
+ throw new Error(output + "\n" + "Template failed to parse: " + e.message);
55
210
  }
56
- }
57
211
 
58
- function single(variables?: { [name: string]: string | number }): Node {
59
- var nodes = compile(variables);
60
- return nodes[0];
212
+ this.interpolateAST(program, variables);
213
+
214
+ return program.body;
61
215
  }
62
216
 
63
- var obj: ITemplate = {
64
- fill,
65
- compile,
66
- single,
67
- source: template,
68
- };
217
+ single(variables: TemplateVariables = {}): Node {
218
+ var nodes = this.compile(variables);
219
+
220
+ if (nodes.length !== 1) {
221
+ nodes = nodes.filter((node) => node.type !== "EmptyStatement");
222
+ ok(
223
+ nodes.length === 1,
224
+ `Expected single node, got ${nodes.map((node) => node.type).join(", ")}`
225
+ );
226
+ }
69
227
 
70
- return obj;
228
+ return nodes[0];
229
+ }
71
230
  }