js-confuser 1.7.3 → 2.0.0-alpha.0

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 (269) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +6 -4
  2. package/CHANGELOG.md +70 -0
  3. package/Migration.md +57 -0
  4. package/README.md +23 -929
  5. package/dist/constants.js +65 -14
  6. package/dist/index.js +108 -160
  7. package/dist/obfuscator.js +316 -118
  8. package/dist/options.js +1 -119
  9. package/dist/order.js +30 -30
  10. package/dist/presets.js +47 -45
  11. package/dist/probability.js +25 -32
  12. package/dist/templates/bufferToStringTemplate.js +9 -0
  13. package/dist/templates/deadCodeTemplates.js +9 -0
  14. package/dist/templates/getGlobalTemplate.js +19 -0
  15. package/dist/templates/integrityTemplate.js +30 -0
  16. package/dist/templates/setFunctionLengthTemplate.js +9 -0
  17. package/dist/templates/stringCompressionTemplate.js +10 -0
  18. package/dist/templates/tamperProtectionTemplates.js +21 -0
  19. package/dist/templates/template.js +199 -184
  20. package/dist/transforms/astScrambler.js +100 -0
  21. package/dist/transforms/calculator.js +70 -127
  22. package/dist/transforms/controlFlowFlattening.js +1182 -0
  23. package/dist/transforms/deadCode.js +62 -587
  24. package/dist/transforms/dispatcher.js +300 -313
  25. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +88 -189
  26. package/dist/transforms/extraction/objectExtraction.js +131 -215
  27. package/dist/transforms/finalizer.js +56 -59
  28. package/dist/transforms/flatten.js +275 -276
  29. package/dist/transforms/functionOutlining.js +230 -0
  30. package/dist/transforms/identifier/globalConcealing.js +214 -135
  31. package/dist/transforms/identifier/movedDeclarations.js +167 -91
  32. package/dist/transforms/identifier/renameVariables.js +239 -193
  33. package/dist/transforms/lock/integrity.js +61 -184
  34. package/dist/transforms/lock/lock.js +261 -387
  35. package/dist/transforms/minify.js +431 -436
  36. package/dist/transforms/opaquePredicates.js +65 -118
  37. package/dist/transforms/pack.js +160 -0
  38. package/dist/transforms/plugin.js +179 -0
  39. package/dist/transforms/preparation.js +261 -173
  40. package/dist/transforms/renameLabels.js +132 -56
  41. package/dist/transforms/rgf.js +140 -267
  42. package/dist/transforms/shuffle.js +52 -145
  43. package/dist/transforms/string/encoding.js +44 -175
  44. package/dist/transforms/string/stringCompression.js +79 -155
  45. package/dist/transforms/string/stringConcealing.js +189 -225
  46. package/dist/transforms/string/stringEncoding.js +32 -40
  47. package/dist/transforms/string/stringSplitting.js +54 -55
  48. package/dist/transforms/variableMasking.js +232 -0
  49. package/dist/utils/ControlObject.js +125 -0
  50. package/dist/utils/IntGen.js +46 -0
  51. package/dist/utils/NameGen.js +106 -0
  52. package/dist/utils/ast-utils.js +560 -0
  53. package/dist/utils/function-utils.js +56 -0
  54. package/dist/utils/gen-utils.js +48 -0
  55. package/dist/utils/node.js +77 -0
  56. package/dist/utils/object-utils.js +21 -0
  57. package/dist/utils/random-utils.js +91 -0
  58. package/dist/utils/static-utils.js +64 -0
  59. package/dist/validateOptions.js +122 -0
  60. package/index.d.ts +1 -17
  61. package/package.json +27 -22
  62. package/src/constants.ts +139 -82
  63. package/src/index.ts +70 -165
  64. package/src/obfuscationResult.ts +43 -0
  65. package/src/obfuscator.ts +328 -135
  66. package/src/options.ts +149 -658
  67. package/src/order.ts +14 -14
  68. package/src/presets.ts +39 -34
  69. package/src/probability.ts +21 -36
  70. package/src/templates/bufferToStringTemplate.ts +57 -0
  71. package/src/templates/deadCodeTemplates.ts +1185 -0
  72. package/src/templates/getGlobalTemplate.ts +72 -0
  73. package/src/templates/integrityTemplate.ts +69 -0
  74. package/src/templates/setFunctionLengthTemplate.ts +11 -0
  75. package/src/templates/stringCompressionTemplate.ts +42 -0
  76. package/src/templates/tamperProtectionTemplates.ts +116 -0
  77. package/src/templates/template.ts +149 -157
  78. package/src/transforms/astScrambler.ts +99 -0
  79. package/src/transforms/calculator.ts +96 -226
  80. package/src/transforms/controlFlowFlattening.ts +1594 -0
  81. package/src/transforms/deadCode.ts +85 -676
  82. package/src/transforms/dispatcher.ts +431 -640
  83. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +147 -295
  84. package/src/transforms/extraction/objectExtraction.ts +160 -333
  85. package/src/transforms/finalizer.ts +63 -64
  86. package/src/transforms/flatten.ts +439 -557
  87. package/src/transforms/functionOutlining.ts +225 -0
  88. package/src/transforms/identifier/globalConcealing.ts +255 -266
  89. package/src/transforms/identifier/movedDeclarations.ts +228 -142
  90. package/src/transforms/identifier/renameVariables.ts +250 -271
  91. package/src/transforms/lock/integrity.ts +85 -263
  92. package/src/transforms/lock/lock.ts +338 -579
  93. package/src/transforms/minify.ts +523 -663
  94. package/src/transforms/opaquePredicates.ts +90 -229
  95. package/src/transforms/pack.ts +195 -0
  96. package/src/transforms/plugin.ts +185 -0
  97. package/src/transforms/preparation.ts +337 -231
  98. package/src/transforms/renameLabels.ts +176 -77
  99. package/src/transforms/rgf.ts +293 -424
  100. package/src/transforms/shuffle.ts +80 -254
  101. package/src/transforms/string/encoding.ts +20 -126
  102. package/src/transforms/string/stringCompression.ts +117 -307
  103. package/src/transforms/string/stringConcealing.ts +254 -342
  104. package/src/transforms/string/stringEncoding.ts +28 -47
  105. package/src/transforms/string/stringSplitting.ts +61 -75
  106. package/src/transforms/variableMasking.ts +257 -0
  107. package/src/utils/ControlObject.ts +141 -0
  108. package/src/utils/IntGen.ts +33 -0
  109. package/src/utils/NameGen.ts +106 -0
  110. package/src/utils/ast-utils.ts +667 -0
  111. package/src/utils/function-utils.ts +50 -0
  112. package/src/utils/gen-utils.ts +48 -0
  113. package/src/utils/node.ts +78 -0
  114. package/src/utils/object-utils.ts +21 -0
  115. package/src/utils/random-utils.ts +79 -0
  116. package/src/utils/static-utils.ts +66 -0
  117. package/src/validateOptions.ts +256 -0
  118. package/tsconfig.json +13 -8
  119. package/babel.config.js +0 -12
  120. package/dev.js +0 -8
  121. package/dist/compiler.js +0 -34
  122. package/dist/parser.js +0 -59
  123. package/dist/precedence.js +0 -66
  124. package/dist/templates/bufferToString.js +0 -129
  125. package/dist/templates/core.js +0 -35
  126. package/dist/templates/crash.js +0 -28
  127. package/dist/templates/es5.js +0 -137
  128. package/dist/templates/functionLength.js +0 -34
  129. package/dist/templates/globals.js +0 -9
  130. package/dist/transforms/antiTooling.js +0 -88
  131. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +0 -1287
  132. package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +0 -131
  133. package/dist/transforms/es5/antiClass.js +0 -164
  134. package/dist/transforms/es5/antiDestructuring.js +0 -193
  135. package/dist/transforms/es5/antiES6Object.js +0 -185
  136. package/dist/transforms/es5/antiSpreadOperator.js +0 -35
  137. package/dist/transforms/es5/antiTemplate.js +0 -66
  138. package/dist/transforms/es5/es5.js +0 -123
  139. package/dist/transforms/extraction/classExtraction.js +0 -83
  140. package/dist/transforms/identifier/globalAnalysis.js +0 -83
  141. package/dist/transforms/identifier/variableAnalysis.js +0 -104
  142. package/dist/transforms/lock/antiDebug.js +0 -76
  143. package/dist/transforms/stack.js +0 -349
  144. package/dist/transforms/transform.js +0 -372
  145. package/dist/traverse.js +0 -110
  146. package/dist/util/compare.js +0 -145
  147. package/dist/util/gen.js +0 -564
  148. package/dist/util/guard.js +0 -14
  149. package/dist/util/identifiers.js +0 -355
  150. package/dist/util/insert.js +0 -362
  151. package/dist/util/math.js +0 -19
  152. package/dist/util/object.js +0 -40
  153. package/dist/util/random.js +0 -156
  154. package/dist/util/scope.js +0 -20
  155. package/docs/ControlFlowFlattening.md +0 -595
  156. package/docs/Countermeasures.md +0 -70
  157. package/docs/ES5.md +0 -197
  158. package/docs/Integrity.md +0 -82
  159. package/docs/RGF.md +0 -424
  160. package/docs/RenameVariables.md +0 -116
  161. package/docs/TamperProtection.md +0 -100
  162. package/docs/Template.md +0 -117
  163. package/samples/example.js +0 -15
  164. package/samples/high.js +0 -1
  165. package/samples/input.js +0 -3
  166. package/samples/javascriptobfuscator.com.js +0 -8
  167. package/samples/jscrambler_advanced.js +0 -1894
  168. package/samples/jscrambler_light.js +0 -1134
  169. package/samples/low.js +0 -1
  170. package/samples/medium.js +0 -1
  171. package/samples/obfuscator.io.js +0 -1686
  172. package/samples/preemptive.com.js +0 -16
  173. package/src/compiler.ts +0 -35
  174. package/src/parser.ts +0 -49
  175. package/src/precedence.ts +0 -61
  176. package/src/templates/bufferToString.ts +0 -136
  177. package/src/templates/core.ts +0 -29
  178. package/src/templates/crash.ts +0 -23
  179. package/src/templates/es5.ts +0 -131
  180. package/src/templates/functionLength.ts +0 -32
  181. package/src/templates/globals.ts +0 -3
  182. package/src/transforms/antiTooling.ts +0 -102
  183. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +0 -2153
  184. package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +0 -179
  185. package/src/transforms/es5/antiClass.ts +0 -276
  186. package/src/transforms/es5/antiDestructuring.ts +0 -294
  187. package/src/transforms/es5/antiES6Object.ts +0 -267
  188. package/src/transforms/es5/antiSpreadOperator.ts +0 -56
  189. package/src/transforms/es5/antiTemplate.ts +0 -98
  190. package/src/transforms/es5/es5.ts +0 -149
  191. package/src/transforms/extraction/classExtraction.ts +0 -168
  192. package/src/transforms/identifier/globalAnalysis.ts +0 -102
  193. package/src/transforms/identifier/variableAnalysis.ts +0 -118
  194. package/src/transforms/lock/antiDebug.ts +0 -112
  195. package/src/transforms/stack.ts +0 -557
  196. package/src/transforms/transform.ts +0 -441
  197. package/src/traverse.ts +0 -120
  198. package/src/types.ts +0 -133
  199. package/src/util/compare.ts +0 -181
  200. package/src/util/gen.ts +0 -651
  201. package/src/util/guard.ts +0 -17
  202. package/src/util/identifiers.ts +0 -494
  203. package/src/util/insert.ts +0 -419
  204. package/src/util/math.ts +0 -15
  205. package/src/util/object.ts +0 -39
  206. package/src/util/random.ts +0 -221
  207. package/src/util/scope.ts +0 -21
  208. package/test/code/Cash.src.js +0 -1011
  209. package/test/code/Cash.test.ts +0 -132
  210. package/test/code/Dynamic.src.js +0 -118
  211. package/test/code/Dynamic.test.ts +0 -49
  212. package/test/code/ES6.src.js +0 -235
  213. package/test/code/ES6.test.ts +0 -42
  214. package/test/code/NewFeatures.test.ts +0 -19
  215. package/test/code/StrictMode.src.js +0 -65
  216. package/test/code/StrictMode.test.js +0 -37
  217. package/test/compare.test.ts +0 -104
  218. package/test/index.test.ts +0 -249
  219. package/test/options.test.ts +0 -150
  220. package/test/presets.test.ts +0 -22
  221. package/test/probability.test.ts +0 -44
  222. package/test/templates/template.test.ts +0 -224
  223. package/test/transforms/antiTooling.test.ts +0 -52
  224. package/test/transforms/calculator.test.ts +0 -78
  225. package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +0 -1274
  226. package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +0 -192
  227. package/test/transforms/deadCode.test.ts +0 -85
  228. package/test/transforms/dispatcher.test.ts +0 -457
  229. package/test/transforms/es5/antiClass.test.ts +0 -427
  230. package/test/transforms/es5/antiDestructuring.test.ts +0 -157
  231. package/test/transforms/es5/antiES6Object.test.ts +0 -245
  232. package/test/transforms/es5/antiTemplate.test.ts +0 -116
  233. package/test/transforms/es5/es5.test.ts +0 -110
  234. package/test/transforms/extraction/classExtraction.test.ts +0 -86
  235. package/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +0 -200
  236. package/test/transforms/extraction/objectExtraction.test.ts +0 -491
  237. package/test/transforms/flatten.test.ts +0 -721
  238. package/test/transforms/hexadecimalNumbers.test.ts +0 -62
  239. package/test/transforms/identifier/globalConcealing.test.ts +0 -142
  240. package/test/transforms/identifier/movedDeclarations.test.ts +0 -275
  241. package/test/transforms/identifier/renameVariables.test.ts +0 -695
  242. package/test/transforms/lock/antiDebug.test.ts +0 -66
  243. package/test/transforms/lock/browserLock.test.ts +0 -129
  244. package/test/transforms/lock/countermeasures.test.ts +0 -100
  245. package/test/transforms/lock/integrity.test.ts +0 -161
  246. package/test/transforms/lock/lock.test.ts +0 -204
  247. package/test/transforms/lock/osLock.test.ts +0 -312
  248. package/test/transforms/lock/selfDefending.test.ts +0 -68
  249. package/test/transforms/lock/tamperProtection.test.ts +0 -336
  250. package/test/transforms/minify.test.ts +0 -575
  251. package/test/transforms/opaquePredicates.test.ts +0 -43
  252. package/test/transforms/preparation.test.ts +0 -157
  253. package/test/transforms/renameLabels.test.ts +0 -95
  254. package/test/transforms/rgf.test.ts +0 -378
  255. package/test/transforms/shuffle.test.ts +0 -135
  256. package/test/transforms/stack.test.ts +0 -573
  257. package/test/transforms/string/stringCompression.test.ts +0 -120
  258. package/test/transforms/string/stringConcealing.test.ts +0 -299
  259. package/test/transforms/string/stringEncoding.test.ts +0 -95
  260. package/test/transforms/string/stringSplitting.test.ts +0 -135
  261. package/test/transforms/transform.test.ts +0 -66
  262. package/test/traverse.test.ts +0 -139
  263. package/test/util/compare.test.ts +0 -34
  264. package/test/util/gen.test.ts +0 -121
  265. package/test/util/identifiers.test.ts +0 -253
  266. package/test/util/insert.test.ts +0 -142
  267. package/test/util/math.test.ts +0 -5
  268. package/test/util/random.test.ts +0 -71
  269. /package/dist/{types.js → obfuscationResult.js} +0 -0
@@ -1,2153 +0,0 @@
1
- import { ok } from "assert";
2
- import { ObfuscateOrder } from "../../order";
3
- import { ComputeProbabilityMap } from "../../probability";
4
- import Template from "../../templates/template";
5
- import { isBlock, walk } from "../../traverse";
6
- import {
7
- ArrayExpression,
8
- AssignmentExpression,
9
- AssignmentPattern,
10
- BinaryExpression,
11
- BreakStatement,
12
- CallExpression,
13
- ConditionalExpression,
14
- ExpressionStatement,
15
- FunctionExpression,
16
- Identifier,
17
- IfStatement,
18
- LabeledStatement,
19
- Literal,
20
- Location,
21
- LogicalExpression,
22
- MemberExpression,
23
- Node,
24
- ObjectExpression,
25
- Property,
26
- ReturnStatement,
27
- SequenceExpression,
28
- SwitchCase,
29
- SwitchStatement,
30
- UnaryExpression,
31
- VariableDeclaration,
32
- VariableDeclarator,
33
- WhileStatement,
34
- } from "../../util/gen";
35
- import {
36
- containsLexicallyBoundVariables,
37
- getIdentifierInfo,
38
- } from "../../util/identifiers";
39
- import {
40
- clone,
41
- getBlockBody,
42
- isContext,
43
- isForInitialize,
44
- isFunction,
45
- isVarContext,
46
- } from "../../util/insert";
47
- import { chance, choice, getRandomInteger, shuffle } from "../../util/random";
48
- import Transform from "../transform";
49
- import ExpressionObfuscation from "./expressionObfuscation";
50
- import { reservedIdentifiers, variableFunctionName } from "../../constants";
51
- import { isDirective, isModuleSource } from "../../util/compare";
52
- import { isJSConfuserVar } from "../../util/guard";
53
-
54
- const flattenStructures = new Set([
55
- "IfStatement",
56
- "ForStatement",
57
- "WhileStatement",
58
- "DoWhileStatement",
59
- ]);
60
-
61
- /**
62
- * A chunk represents a small segment of code
63
- */
64
- interface Chunk {
65
- label: string;
66
- body: Node[];
67
-
68
- impossible?: boolean;
69
- }
70
-
71
- /**
72
- * Breaks functions into DAGs (Directed Acyclic Graphs)
73
- *
74
- * - 1. Break functions into chunks
75
- * - 2. Shuffle chunks but remember their original position
76
- * - 3. Create a Switch statement inside a While loop, each case is a chunk, and the while loops exits on the last transition.
77
- *
78
- * The Switch statement:
79
- *
80
- * - 1. The state variable controls which case will run next
81
- * - 2. At the end of each case, the state variable is updated to the next block of code.
82
- * - 3. The while loop continues until the the state variable is the end state.
83
- */
84
- export default class ControlFlowFlattening extends Transform {
85
- // in Debug mode, the output is much easier to read
86
- isDebug = false;
87
- flattenControlStructures = true; // Flatten if-statements, for-loops, etc
88
- addToControlObject = true; // var control = { str1, num1 }
89
- mangleNumberLiterals = true; // 50 => state + X
90
- mangleBooleanLiterals = true; // true => state == X
91
- mangleIdentifiers = true; // console => (state == X ? console : _)
92
- outlineStatements = true; // Tries to outline entire chunks
93
- outlineExpressions = true; // Tries to outline expressions found in chunks
94
- addComplexTest = true; // case s != 49 && s - 10:
95
- addFakeTest = true; // case 100: case 490: case 510: ...
96
- addDeadCode = true; // add fakes chunks of code
97
- addOpaquePredicates = true; // predicate ? REAL : FAKE
98
- addFlaggedLabels = true; // s=NEXT_STATE,flag=true,break
99
-
100
- // Limit amount of mangling
101
- mangledExpressionsMade = 0;
102
-
103
- // Amount of blocks changed by Control Flow Flattening
104
- cffCount = 0;
105
-
106
- constructor(o) {
107
- super(o, ObfuscateOrder.ControlFlowFlattening);
108
-
109
- if (!this.isDebug) {
110
- this.before.push(new ExpressionObfuscation(o));
111
- } else {
112
- console.warn("Debug mode enabled");
113
- }
114
- }
115
-
116
- match(object, parents) {
117
- return (
118
- isBlock(object) &&
119
- (!parents[0] || !flattenStructures.has(parents[0].type)) &&
120
- (!parents[1] || !flattenStructures.has(parents[1].type))
121
- );
122
- }
123
-
124
- transform(object, parents) {
125
- // Must be at least 3 statements or more
126
- if (object.body.length < 3) {
127
- return;
128
- }
129
- // No 'let'/'const' allowed (These won't work in Switch cases!)
130
- if (containsLexicallyBoundVariables(object, parents)) {
131
- return;
132
- }
133
- // Check user's threshold setting
134
- if (!ComputeProbabilityMap(this.options.controlFlowFlattening, (x) => x)) {
135
- return;
136
- }
137
-
138
- var objectBody = getBlockBody(object.body);
139
- if (!objectBody.length) {
140
- return;
141
- }
142
-
143
- // Purely for naming purposes
144
- var cffIndex = this.cffCount++;
145
-
146
- // The controlVar is an object containing:
147
- // - Strings found in chunks
148
- // - Numbers found in chunks
149
- // - Helper functions to adjust the state
150
- // - Outlined expressions changed into functions
151
- var controlVar = this.getPlaceholder() + `_c${cffIndex}_CONTROL`;
152
- var controlProperties: Node[] = [];
153
- var controlConstantMap = new Map<string | number, { key: string }>();
154
- var controlGen = this.getGenerator("mangled");
155
- var controlTestKey = controlGen.generate();
156
-
157
- // This 'controlVar' can be accessed by child-nodes
158
- object.$controlVar = controlVar;
159
- object.$controlConstantMap = controlConstantMap;
160
- object.$controlProperties = controlProperties;
161
- object.$controlGen = controlGen;
162
-
163
- return () => {
164
- ok(Array.isArray(objectBody));
165
-
166
- // The state variable names (and quantity)
167
- var stateVars = Array(this.isDebug ? 1 : getRandomInteger(2, 5))
168
- .fill(0)
169
- .map((_, i) => this.getPlaceholder() + `_c${cffIndex}_S${i}`);
170
-
171
- // How often should chunks be split up?
172
- // Percentage between 10% and 90% based on block size
173
- var splitPercent = Math.max(10, 90 - objectBody.length * 5);
174
-
175
- // Find functions and import declarations
176
- var importDeclarations: Node[] = [];
177
- var functionDeclarationNames = new Set<string>();
178
- var functionDeclarationValues = new Map<string, Node>();
179
-
180
- // Find all parent control-nodes
181
- const allControlNodes = [object];
182
- parents
183
- .filter((x) => x.$controlVar)
184
- .forEach((node) => allControlNodes.push(node));
185
-
186
- const addControlMapConstant = (literalValue: number | string) => {
187
- // Choose a random control node to add to
188
- var controlNode = choice(allControlNodes);
189
- var selectedControlVar = controlNode.$controlVar;
190
- var selectedControlConstantMap = controlNode.$controlConstantMap;
191
- var selectedControlProperties = controlNode.$controlProperties;
192
-
193
- var key = selectedControlConstantMap.get(literalValue)?.key;
194
-
195
- // Not found, create
196
- if (!key) {
197
- key = controlNode.$controlGen.generate();
198
- selectedControlConstantMap.set(literalValue, { key: key });
199
-
200
- selectedControlProperties.push(
201
- Property(Literal(key), Literal(literalValue), false)
202
- );
203
- }
204
-
205
- return getControlMember(key, selectedControlVar);
206
- };
207
-
208
- // Helper function to easily make control object accessors
209
- const getControlMember = (key: string, objectName = controlVar) =>
210
- MemberExpression(Identifier(objectName), Literal(key), true);
211
-
212
- // This function recursively calls itself to flatten and split up code into 'chunks'
213
- const flattenBody = (body: Node[], startingLabel: string): Chunk[] => {
214
- var chunks: Chunk[] = [];
215
- var currentBody: Node[] = [];
216
- var currentLabel = startingLabel;
217
-
218
- // This function ends the current chunk being created ('currentBody')
219
- const finishCurrentChunk = (
220
- pointingLabel?: string,
221
- newLabel?: string,
222
- addGotoStatement = true
223
- ) => {
224
- if (!newLabel) {
225
- newLabel = this.getPlaceholder();
226
- }
227
- if (!pointingLabel) {
228
- pointingLabel = newLabel;
229
- }
230
-
231
- if (addGotoStatement) {
232
- currentBody.push({ type: "GotoStatement", label: pointingLabel });
233
- }
234
-
235
- chunks.push({
236
- label: currentLabel,
237
- body: [...currentBody],
238
- });
239
-
240
- // Random chance of this chunk being flagged (First label cannot be flagged)
241
- if (
242
- !this.isDebug &&
243
- this.addFlaggedLabels &&
244
- currentLabel !== startLabel &&
245
- chance(25)
246
- ) {
247
- flaggedLabels[currentLabel] = {
248
- flagKey: controlGen.generate(),
249
- flagValue: choice([true, false]),
250
- };
251
- }
252
-
253
- walk(currentBody, [], (o, p) => {
254
- if (o.type === "Literal" && !this.isDebug) {
255
- // Add strings to the control object
256
- if (
257
- this.addToControlObject &&
258
- typeof o.value === "string" &&
259
- o.value.length >= 3 &&
260
- o.value.length <= 100 &&
261
- !isModuleSource(o, p) &&
262
- !isDirective(o, p) &&
263
- !o.regex &&
264
- chance(
265
- 50 -
266
- controlConstantMap.size -
267
- this.mangledExpressionsMade / 100
268
- )
269
- ) {
270
- return () => {
271
- this.replaceIdentifierOrLiteral(
272
- o,
273
- addControlMapConstant(o.value),
274
- p
275
- );
276
- };
277
- }
278
-
279
- // Add numbers to the control object
280
- if (
281
- this.addToControlObject &&
282
- typeof o.value === "number" &&
283
- Math.floor(o.value) === o.value &&
284
- Math.abs(o.value) < 100_000 &&
285
- chance(
286
- 50 -
287
- controlConstantMap.size -
288
- this.mangledExpressionsMade / 100
289
- )
290
- ) {
291
- return () => {
292
- this.replaceIdentifierOrLiteral(
293
- o,
294
- addControlMapConstant(o.value),
295
- p
296
- );
297
- };
298
- }
299
- }
300
- });
301
-
302
- currentLabel = newLabel;
303
- currentBody = [];
304
- };
305
-
306
- if (body !== objectBody) {
307
- // This code is nested. Move function declarations up
308
-
309
- var newBody = [];
310
- for (var stmt of body) {
311
- if (stmt.type === "FunctionDeclaration") {
312
- newBody.unshift(stmt);
313
- } else {
314
- newBody.push(stmt);
315
- }
316
- }
317
-
318
- body = newBody;
319
- }
320
-
321
- body.forEach((stmt, i) => {
322
- if (stmt.type === "ImportDeclaration") {
323
- // The 'importDeclarations' hold statements that are required to be left untouched at the top of the block
324
- importDeclarations.push(stmt);
325
- return;
326
- } else if (stmt.type === "FunctionDeclaration") {
327
- var functionName = stmt.id.name;
328
-
329
- stmt.type = "FunctionExpression";
330
- stmt.id = null;
331
-
332
- functionDeclarationNames.add(functionName);
333
- if (objectBody === body) {
334
- functionDeclarationValues.set(functionName, stmt);
335
- return;
336
- } else {
337
- currentBody.push(
338
- ExpressionStatement(
339
- AssignmentExpression("=", Identifier(functionName), stmt)
340
- )
341
- );
342
- }
343
-
344
- return;
345
- } else if (stmt.directive) {
346
- if (objectBody === body) {
347
- importDeclarations.push(stmt);
348
- } else {
349
- this.error(new Error("Unimplemented directive support."));
350
- }
351
- return;
352
- }
353
-
354
- if (stmt.type == "GotoStatement" && i !== body.length - 1) {
355
- finishCurrentChunk(stmt.label);
356
- return;
357
- }
358
-
359
- // The Preparation transform adds labels to every Control-Flow node
360
- if (
361
- this.flattenControlStructures &&
362
- stmt.type == "LabeledStatement"
363
- ) {
364
- var lbl = stmt.label.name;
365
- var control: Node = stmt.body;
366
-
367
- var isSwitchStatement = control.type === "SwitchStatement";
368
-
369
- if (
370
- isSwitchStatement ||
371
- ((control.type == "ForStatement" ||
372
- control.type == "WhileStatement" ||
373
- control.type == "DoWhileStatement") &&
374
- control.body.type == "BlockStatement")
375
- ) {
376
- if (isSwitchStatement) {
377
- if (control.cases.length == 0) {
378
- currentBody.push(stmt);
379
- return;
380
- }
381
- }
382
-
383
- var isLoop = !isSwitchStatement;
384
- var supportContinueStatement = isLoop;
385
-
386
- var testPath = this.getPlaceholder();
387
- var updatePath = this.getPlaceholder();
388
- var bodyPath = this.getPlaceholder();
389
- var afterPath = this.getPlaceholder();
390
- var possible = true;
391
- var toReplace = [];
392
-
393
- // Find all break; and continue; statements and change them into 'GotoStatement's
394
- walk(control.body || control.cases, [], (o, p) => {
395
- if (
396
- o.type === "BreakStatement" ||
397
- o.type === "ContinueStatement"
398
- ) {
399
- var allowedLabels = new Set(
400
- p
401
- .filter(
402
- (x) =>
403
- x.type === "LabeledStatement" &&
404
- x.body.type === "SwitchStatement"
405
- )
406
- .map((x) => x.label.name)
407
- );
408
-
409
- var isUnsupportedContinue =
410
- !supportContinueStatement && o.type === "ContinueStatement";
411
-
412
- var isInvalidLabel =
413
- !o.label ||
414
- (o.label.name !== lbl && !allowedLabels.has(o.label.name));
415
-
416
- // This seems like the best solution:
417
- if (isUnsupportedContinue || isInvalidLabel) {
418
- possible = false;
419
- return "EXIT";
420
- }
421
- if (o.label.name === lbl) {
422
- return () => {
423
- toReplace.push([
424
- o,
425
- {
426
- type: "GotoStatement",
427
- label:
428
- o.type == "BreakStatement" ? afterPath : updatePath,
429
- },
430
- ]);
431
- };
432
- }
433
- }
434
- });
435
- if (!possible) {
436
- currentBody.push(stmt);
437
- return;
438
- }
439
- toReplace.forEach((v) => this.replace(v[0], v[1]));
440
-
441
- if (isSwitchStatement) {
442
- var switchDiscriminantName = this.getPlaceholder() + "_switchD"; // Stores the value of the discriminant
443
- var switchTestName = this.getPlaceholder() + "_switchT"; // Set to true when a Switch case is matched
444
-
445
- currentBody.push(
446
- VariableDeclaration(
447
- VariableDeclarator(
448
- switchDiscriminantName,
449
- control.discriminant
450
- )
451
- )
452
- );
453
-
454
- currentBody.push(
455
- VariableDeclaration(
456
- VariableDeclarator(switchTestName, Literal(false))
457
- )
458
- );
459
-
460
- // case labels are:
461
- // `${caseLabelPrefix}_test_${index}`
462
- // `${caseLabelPrefix}_entry_${index}`
463
- var caseLabelPrefix = this.getPlaceholder();
464
- var defaultCaseIndex = control.cases.findIndex(
465
- (x) => x.test === null
466
- );
467
-
468
- control.cases.forEach((switchCase, i) => {
469
- var testPath = caseLabelPrefix + "_test_" + i;
470
- var entryPath = caseLabelPrefix + "_entry_" + i;
471
- var nextEntryPath =
472
- i === control.cases.length - 1 // Last path goes to afterPath
473
- ? afterPath // Else go to next entry path (fall-through behavior)
474
- : caseLabelPrefix + "_entry_" + (i + 1);
475
- var nextTestPath =
476
- i === control.cases.length - 1
477
- ? afterPath
478
- : caseLabelPrefix + "_test_" + (i + 1);
479
-
480
- finishCurrentChunk(testPath, testPath, i == 0);
481
-
482
- if (switchCase.test) {
483
- // Check the case condition and goto statement
484
- currentBody.push(
485
- IfStatement(
486
- BinaryExpression(
487
- "===",
488
- Identifier(switchDiscriminantName),
489
- switchCase.test
490
- ),
491
- [
492
- ExpressionStatement(
493
- AssignmentExpression(
494
- "=",
495
- Identifier(switchTestName),
496
- Literal(true)
497
- )
498
- ),
499
- {
500
- type: "GotoStatement",
501
- label: entryPath,
502
- },
503
- ]
504
- )
505
- );
506
- } else {
507
- // Default case: No test needed.
508
- }
509
-
510
- // If default case, on last test, if no case was matched, goto default case
511
- if (
512
- i === control.cases.length - 1 &&
513
- defaultCaseIndex !== -1
514
- ) {
515
- currentBody.push(
516
- IfStatement(
517
- UnaryExpression("!", Identifier(switchTestName)),
518
- [
519
- {
520
- type: "GotoStatement",
521
- label:
522
- caseLabelPrefix + "_entry_" + defaultCaseIndex,
523
- },
524
- ]
525
- )
526
- );
527
- }
528
-
529
- // Jump to next test
530
- currentBody.push({
531
- type: "GotoStatement",
532
- label: nextTestPath,
533
- });
534
-
535
- chunks.push(
536
- ...flattenBody(
537
- [
538
- ...switchCase.consequent,
539
- {
540
- type: "GotoStatement",
541
- label: nextEntryPath,
542
- },
543
- ],
544
- entryPath
545
- )
546
- );
547
- });
548
-
549
- finishCurrentChunk(afterPath, afterPath, false);
550
- return;
551
- } else if (isLoop) {
552
- var isPostTest = control.type == "DoWhileStatement";
553
-
554
- // add initializing section to current chunk
555
- if (control.init) {
556
- if (control.init.type == "VariableDeclaration") {
557
- currentBody.push(control.init);
558
- } else {
559
- currentBody.push(ExpressionStatement(control.init));
560
- }
561
- }
562
-
563
- // create new label called `testPath` and have current chunk point to it (goto testPath)
564
- finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath);
565
-
566
- currentBody.push(
567
- ExpressionStatement(
568
- AssignmentExpression(
569
- "=",
570
- getControlMember(controlTestKey),
571
- control.test || Literal(true)
572
- )
573
- )
574
- );
575
-
576
- finishCurrentChunk();
577
-
578
- currentBody.push(
579
- IfStatement(getControlMember(controlTestKey), [
580
- {
581
- type: "GotoStatement",
582
- label: bodyPath,
583
- },
584
- ])
585
- );
586
-
587
- // create new label called `bodyPath` and have test body point to afterPath (goto afterPath)
588
- finishCurrentChunk(afterPath, bodyPath);
589
-
590
- var innerBothPath = this.getPlaceholder();
591
- chunks.push(
592
- ...flattenBody(
593
- [
594
- ...control.body.body,
595
- {
596
- type: "GotoStatement",
597
- label: updatePath,
598
- },
599
- ],
600
- innerBothPath
601
- )
602
- );
603
-
604
- finishCurrentChunk(innerBothPath, updatePath);
605
-
606
- if (control.update) {
607
- currentBody.push(ExpressionStatement(control.update));
608
- }
609
-
610
- finishCurrentChunk(testPath, afterPath);
611
- return;
612
- }
613
- }
614
- }
615
-
616
- if (
617
- this.flattenControlStructures &&
618
- stmt.type == "IfStatement" &&
619
- stmt.consequent.type == "BlockStatement" &&
620
- (!stmt.alternate || stmt.alternate.type == "BlockStatement")
621
- ) {
622
- finishCurrentChunk();
623
-
624
- currentBody.push(
625
- ExpressionStatement(
626
- AssignmentExpression(
627
- "=",
628
- getControlMember(controlTestKey),
629
- stmt.test
630
- )
631
- )
632
- );
633
-
634
- finishCurrentChunk();
635
-
636
- var hasAlternate = !!stmt.alternate;
637
- ok(!(hasAlternate && stmt.alternate.type !== "BlockStatement"));
638
-
639
- var yesPath = this.getPlaceholder();
640
- var noPath = this.getPlaceholder();
641
- var afterPath = this.getPlaceholder();
642
-
643
- currentBody.push(
644
- IfStatement(getControlMember(controlTestKey), [
645
- {
646
- type: "GotoStatement",
647
- label: yesPath,
648
- },
649
- ])
650
- );
651
-
652
- chunks.push(
653
- ...flattenBody(
654
- [
655
- ...stmt.consequent.body,
656
- {
657
- type: "GotoStatement",
658
- label: afterPath,
659
- },
660
- ],
661
- yesPath
662
- )
663
- );
664
-
665
- if (hasAlternate) {
666
- chunks.push(
667
- ...flattenBody(
668
- [
669
- ...stmt.alternate.body,
670
- {
671
- type: "GotoStatement",
672
- label: afterPath,
673
- },
674
- ],
675
- noPath
676
- )
677
- );
678
-
679
- finishCurrentChunk(noPath, afterPath);
680
- } else {
681
- finishCurrentChunk(afterPath, afterPath);
682
- }
683
-
684
- return;
685
- }
686
-
687
- if (!currentBody.length || !chance(splitPercent)) {
688
- currentBody.push(stmt);
689
- } else {
690
- // Start new chunk
691
- finishCurrentChunk();
692
- currentBody.push(stmt);
693
- }
694
- });
695
-
696
- finishCurrentChunk();
697
- chunks[chunks.length - 1].body.pop();
698
-
699
- return chunks;
700
- };
701
-
702
- /**
703
- * Executable code segments are broken down into `chunks` typically 1-3 statements each
704
- *
705
- * Chunked Code has a special `GotoStatement` node that get processed later on
706
- * This allows more complex control structures like `IfStatement`s and `ForStatement`s to be converted into basic
707
- * conditional jumps and flattened in the switch body
708
- *
709
- * IfStatement would be converted like this:
710
- *
711
- * MAIN:
712
- * if ( TEST ) {
713
- * GOTO consequent_label;
714
- * } else? {
715
- * GOTO alternate_label;
716
- * }
717
- * GOTO NEXT_CHUNK;
718
- */
719
- const chunks: Chunk[] = [];
720
-
721
- // Flagged labels have addition code protecting the control state
722
- const flaggedLabels: {
723
- [label: string]: { flagKey: string; flagValue: boolean };
724
- } = Object.create(null);
725
-
726
- /**
727
- * label: switch(a+b+c){...break label...}
728
- */
729
- const switchLabel = this.getPlaceholder();
730
-
731
- const startLabel = this.getPlaceholder();
732
-
733
- chunks.push(...flattenBody(objectBody, startLabel));
734
- chunks[chunks.length - 1].body.push({
735
- type: "GotoStatement",
736
- label: "END_LABEL",
737
- });
738
- chunks.push({
739
- label: "END_LABEL",
740
- body: [],
741
- });
742
-
743
- const endLabel = chunks[Object.keys(chunks).length - 1].label;
744
-
745
- if (!this.isDebug && this.addDeadCode) {
746
- // DEAD CODE 1/3: Add fake chunks that are never reached
747
- var fakeChunkCount = getRandomInteger(1, 5);
748
- for (var i = 0; i < fakeChunkCount; i++) {
749
- // These chunks just jump somewhere random, they are never executed
750
- // so it could contain any code
751
- var fakeChunkBody = [
752
- // This a fake assignment expression
753
- ExpressionStatement(
754
- AssignmentExpression(
755
- "=",
756
- Identifier(choice(stateVars)),
757
- Literal(getRandomInteger(-150, 150))
758
- )
759
- ),
760
-
761
- {
762
- type: "GotoStatement",
763
- label: choice(chunks).label,
764
- },
765
- ];
766
-
767
- chunks.push({
768
- label: this.getPlaceholder(),
769
- body: fakeChunkBody,
770
- impossible: true,
771
- });
772
- }
773
-
774
- // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators
775
- chunks.forEach((chunk) => {
776
- if (chance(25)) {
777
- var randomLabel = choice(chunks).label;
778
-
779
- // The `false` literal will be mangled
780
- chunk.body.unshift(
781
- IfStatement(Literal(false), [
782
- {
783
- type: "GotoStatement",
784
- label: randomLabel,
785
- impossible: true,
786
- },
787
- ])
788
- );
789
- }
790
- });
791
-
792
- // DEAD CODE 3/3: Clone chunks but these chunks are never ran
793
- var cloneChunkCount = getRandomInteger(1, 5);
794
- for (var i = 0; i < cloneChunkCount; i++) {
795
- var randomChunk = choice(chunks);
796
- var clonedChunk = {
797
- body: clone(randomChunk.body),
798
- label: this.getPlaceholder(),
799
- impossible: true,
800
- };
801
-
802
- // Don't double define functions
803
- var hasDeclaration = clonedChunk.body.find((stmt) => {
804
- return (
805
- stmt.type === "FunctionDeclaration" ||
806
- stmt.type === "ClassDeclaration"
807
- );
808
- });
809
-
810
- if (!hasDeclaration) {
811
- chunks.unshift(clonedChunk);
812
- }
813
- }
814
- }
815
-
816
- // Generate a unique 'state' number for each chunk
817
- var caseSelection: Set<number> = new Set();
818
- var uniqueStatesNeeded = chunks.length;
819
-
820
- do {
821
- var newState = getRandomInteger(1, chunks.length * 15);
822
- if (this.isDebug) {
823
- newState = caseSelection.size;
824
- }
825
- caseSelection.add(newState);
826
- } while (caseSelection.size !== uniqueStatesNeeded);
827
-
828
- ok(caseSelection.size == uniqueStatesNeeded);
829
-
830
- /**
831
- * The accumulated state values
832
- *
833
- * index -> total state value
834
- */
835
- var caseStates = Array.from(caseSelection);
836
-
837
- /**
838
- * The individual state values for each label
839
- *
840
- * labels right now are just chunk indexes (numbers)
841
- *
842
- * but will expand to if statements and functions when `goto statement` obfuscation is added
843
- */
844
- var labelToStates: { [label: string]: number[] } = Object.create(null);
845
-
846
- var lastLabel;
847
-
848
- Object.values(chunks).forEach((chunk, i) => {
849
- var state = caseStates[i];
850
-
851
- var stateValues = Array(stateVars.length)
852
- .fill(0)
853
- .map((_, i) =>
854
- lastLabel && chance(95) // Try to make state changes not as drastic (If last label, re-use some of it's values)
855
- ? labelToStates[lastLabel][i]
856
- : getRandomInteger(-500, 500)
857
- );
858
-
859
- const getCurrentState = () => {
860
- return stateValues.reduce((a, b) => b + a, 0);
861
- };
862
-
863
- var correctIndex = getRandomInteger(0, stateValues.length);
864
- stateValues[correctIndex] =
865
- state - (getCurrentState() - stateValues[correctIndex]);
866
-
867
- labelToStates[chunk.label] = stateValues;
868
- lastLabel = chunk.label;
869
- });
870
-
871
- var initStateValues = [...labelToStates[startLabel]];
872
- var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0);
873
-
874
- // Creates a predicate based on the state-variables and control-object properties
875
- const createPredicate = (
876
- stateValues: number[]
877
- ): { test: Node; testValue: boolean } => {
878
- this.mangledExpressionsMade++;
879
-
880
- var index = getRandomInteger(0, stateVars.length);
881
-
882
- var compareValue = choice([
883
- stateValues[index],
884
- getRandomInteger(-100, 100),
885
- ]);
886
-
887
- // 'state equality' test
888
- var test: Node = BinaryExpression(
889
- "==",
890
- Identifier(stateVars[index]),
891
- createStateBoundNumberLiteral(compareValue, stateValues)
892
- );
893
- var testValue = stateValues[index] === compareValue;
894
-
895
- // 'control' equality test
896
- if (controlConstantMap.size && chance(50)) {
897
- // The controlMap maps LITERAL-values to STRING property names
898
- var actualValue = choice(Array.from(controlConstantMap.keys()));
899
- var controlKey = controlConstantMap.get(actualValue)?.key;
900
-
901
- var controlCompareValue = choice([
902
- actualValue,
903
- stateValues[index],
904
- getRandomInteger(-100, 100),
905
- controlGen.generate(),
906
- ]);
907
-
908
- // 'control equality' test
909
- test = BinaryExpression(
910
- "==",
911
- getControlMember(controlKey),
912
- Literal(controlCompareValue)
913
- );
914
- testValue = actualValue == controlCompareValue;
915
-
916
- // 'control typeof' test
917
- if (chance(10)) {
918
- var compareTypeofValue = choice([
919
- "number",
920
- "string",
921
- "object",
922
- "function",
923
- "undefined",
924
- ]);
925
-
926
- test = BinaryExpression(
927
- "==",
928
- UnaryExpression("typeof", getControlMember(controlKey)),
929
- Literal(compareTypeofValue)
930
- );
931
- testValue = typeof actualValue === compareTypeofValue;
932
- }
933
-
934
- // 'control hasOwnProperty' test
935
- if (chance(10)) {
936
- var hasOwnProperty = choice([controlKey, controlGen.generate()]);
937
- test = CallExpression(
938
- MemberExpression(
939
- Identifier(controlVar),
940
- Literal("hasOwnProperty"),
941
- true
942
- ),
943
- [Literal(hasOwnProperty)]
944
- );
945
- testValue = hasOwnProperty === controlKey;
946
- }
947
- }
948
-
949
- return { test, testValue };
950
- };
951
-
952
- // A "state-less" number literal is a Number Literal that is mangled in with the Control properties.
953
- // Example: X = CONTROL.Y + Z. These can be used anywhere because control properties are constant (unlike state variables)
954
- const createStatelessNumberLiteral = (num: number, depth = 0) => {
955
- if (
956
- !controlConstantMap.size ||
957
- depth > 4 ||
958
- chance(75 + depth * 5 + this.mangledExpressionsMade / 25)
959
- ) {
960
- // Add to control constant map?
961
- if (
962
- chance(
963
- 25 - controlConstantMap.size - this.mangledExpressionsMade / 100
964
- )
965
- ) {
966
- return addControlMapConstant(num);
967
- }
968
- return Literal(num);
969
- }
970
- this.mangledExpressionsMade++;
971
-
972
- if (controlConstantMap.has(num)) {
973
- return getControlMember(controlConstantMap.get(num)?.key);
974
- }
975
-
976
- var allControlNodes = [object];
977
- parents
978
- .filter((x) => x.$controlVar && x.$controlConstantMap.size > 0)
979
- .forEach((node) => allControlNodes.push(node));
980
-
981
- var controlNode = choice(allControlNodes);
982
- var selectedControlConstantMap = controlNode.$controlConstantMap;
983
- var selectedControlVar = controlNode.$controlVar;
984
-
985
- var actualValue = choice(Array.from(selectedControlConstantMap.keys()));
986
- var controlKey = selectedControlConstantMap.get(actualValue)?.key;
987
-
988
- if (typeof actualValue === "number") {
989
- var difference = actualValue - num;
990
-
991
- return BinaryExpression(
992
- "-",
993
- getControlMember(controlKey, selectedControlVar),
994
- createStatelessNumberLiteral(difference, depth + 1)
995
- );
996
- } else if (typeof actualValue === "string") {
997
- // 'control string length' test
998
- var compareValue = choice([
999
- actualValue.length,
1000
- getRandomInteger(0, 50),
1001
- ]);
1002
-
1003
- var test = BinaryExpression(
1004
- "==",
1005
- MemberExpression(
1006
- getControlMember(controlKey, selectedControlVar),
1007
- Literal("length"),
1008
- true
1009
- ),
1010
- createStatelessNumberLiteral(compareValue, depth + 1)
1011
- );
1012
- var testValue = actualValue.length == compareValue;
1013
-
1014
- var consequent: Node = createStatelessNumberLiteral(num, depth + 1);
1015
- var alternate: Node = Literal(getRandomInteger(-100, 100));
1016
-
1017
- return ConditionalExpression(
1018
- test,
1019
- testValue ? consequent : alternate,
1020
- !testValue ? consequent : alternate
1021
- );
1022
- } else {
1023
- throw new Error("Unknown: " + typeof actualValue);
1024
- }
1025
- };
1026
-
1027
- // A "state-bound" number literal is a Number Literal that is mangled in with the current state variables
1028
- // Example: X = STATE + Y. This can only be used when the state-values are guaranteed to be known.
1029
- const createStateBoundNumberLiteral = (
1030
- num: number,
1031
- stateValues: number[],
1032
- depth = 0
1033
- ): Node => {
1034
- ok(Array.isArray(stateValues));
1035
-
1036
- // Base case: After 4 depth, OR random chance
1037
- if (
1038
- depth > 4 ||
1039
- chance(75 + depth * 5 + this.mangledExpressionsMade / 25)
1040
- ) {
1041
- // Add this number to the control object?
1042
- // Add to control constant map?
1043
- if (chance(25 - controlConstantMap.size)) {
1044
- return addControlMapConstant(num);
1045
- }
1046
-
1047
- return Literal(num);
1048
- }
1049
- this.mangledExpressionsMade++;
1050
-
1051
- if (chance(10)) {
1052
- return createStatelessNumberLiteral(num, depth + 1);
1053
- }
1054
-
1055
- // Terminated predicate
1056
- if (chance(50)) {
1057
- var { test, testValue } = createPredicate(stateValues);
1058
-
1059
- var alternateNode = choice([
1060
- Literal(getRandomInteger(-100, 100)),
1061
- Literal(controlGen.generate()),
1062
- getControlMember(controlGen.generate()),
1063
- ]);
1064
-
1065
- return ConditionalExpression(
1066
- test,
1067
- testValue ? Literal(num) : alternateNode,
1068
- !testValue ? Literal(num) : alternateNode
1069
- );
1070
- }
1071
-
1072
- // Recursive predicate
1073
- var opposing = getRandomInteger(0, stateVars.length);
1074
-
1075
- if (chance(10)) {
1076
- // state > compare ? real : fake
1077
-
1078
- var compareValue: number = choice([
1079
- stateValues[opposing],
1080
- getRandomInteger(-150, 150),
1081
- ]);
1082
-
1083
- var operator = choice(["<", ">", "==", "!="]);
1084
- var answer: boolean = {
1085
- ">": compareValue > stateValues[opposing],
1086
- "<": compareValue < stateValues[opposing],
1087
- "==": compareValue === stateValues[opposing],
1088
- "!=": compareValue !== stateValues[opposing],
1089
- }[operator];
1090
-
1091
- var correct = createStateBoundNumberLiteral(
1092
- num,
1093
- stateValues,
1094
- depth + 1
1095
- );
1096
- var incorrect = createStateBoundNumberLiteral(
1097
- getRandomInteger(-150, 150),
1098
- stateValues,
1099
- depth + 1
1100
- );
1101
-
1102
- return ConditionalExpression(
1103
- BinaryExpression(
1104
- operator,
1105
- createStateBoundNumberLiteral(
1106
- compareValue,
1107
- stateValues,
1108
- depth + 1
1109
- ),
1110
- Identifier(stateVars[opposing])
1111
- ),
1112
- answer ? correct : incorrect,
1113
- answer ? incorrect : correct
1114
- );
1115
- }
1116
-
1117
- // state + 10 = <REAL>
1118
- var difference = num - stateValues[opposing];
1119
-
1120
- if (difference === 0) {
1121
- return Identifier(stateVars[opposing]);
1122
- }
1123
-
1124
- return BinaryExpression(
1125
- "+",
1126
- Identifier(stateVars[opposing]),
1127
- createStateBoundNumberLiteral(difference, stateValues, depth + 1)
1128
- );
1129
- };
1130
-
1131
- var outlinesCreated = 0;
1132
-
1133
- const isExpression = (object: Node, parents: Node[]) => {
1134
- var fnIndex = parents.findIndex((x) => isFunction(x));
1135
- if (fnIndex != -1) {
1136
- // This does NOT mutate
1137
- parents = parents.slice(0, fnIndex);
1138
- }
1139
- var assignmentIndex = parents.findIndex(
1140
- (x) => x.type === "AssignmentExpression"
1141
- );
1142
-
1143
- // Left-hand assignment validation
1144
- if (assignmentIndex != -1) {
1145
- if (
1146
- parents[assignmentIndex].left ===
1147
- (parents[assignmentIndex - 1] || object)
1148
- ) {
1149
- return false;
1150
- }
1151
- }
1152
-
1153
- // For in/of left validation
1154
- var forInOfIndex = parents.findIndex(
1155
- (x) => x.type === "ForInStatement" || x.type === "ForOfStatement"
1156
- );
1157
- if (forInOfIndex != -1) {
1158
- if (
1159
- parents[forInOfIndex].left === (parents[forInOfIndex - 1] || object)
1160
- ) {
1161
- return false;
1162
- }
1163
- }
1164
-
1165
- // Bound call-expression validation
1166
- var callExpressionIndex = parents.findIndex(
1167
- (x) => x.type === "CallExpression"
1168
- );
1169
- if (callExpressionIndex != -1) {
1170
- if (
1171
- parents[callExpressionIndex].callee ==
1172
- (parents[callExpressionIndex - 1] || object)
1173
- ) {
1174
- var callee = parents[callExpressionIndex].callee;
1175
-
1176
- // Detected bound call expression. Not supported.
1177
- if (callee.type === "MemberExpression") {
1178
- return false;
1179
- }
1180
- }
1181
- }
1182
-
1183
- // Update-expression validation:
1184
- var updateExpressionIndex = parents.findIndex(
1185
- (x) => x.type === "UpdateExpression"
1186
- );
1187
- if (updateExpressionIndex !== -1) return false;
1188
-
1189
- return true;
1190
- };
1191
-
1192
- // This function checks if the expression or statements is possible to be outlined
1193
- const canOutline = (object: Node | Node[], parents: Node[]) => {
1194
- var isIllegal = false;
1195
-
1196
- var breakStatements: Location[] = [];
1197
- var returnStatements: Location[] = [];
1198
-
1199
- if (!Array.isArray(object) && !isExpression(object, parents)) {
1200
- return { isIllegal: true, breakStatements: [], returnStatements: [] };
1201
- }
1202
-
1203
- walk(object, parents, (o, p) => {
1204
- if (
1205
- o.type === "ThisExpression" ||
1206
- o.type === "MetaProperty" ||
1207
- o.type === "Super"
1208
- ) {
1209
- isIllegal = true;
1210
- return "EXIT";
1211
- }
1212
-
1213
- if (o.type === "BreakStatement") {
1214
- // This can be safely outlined
1215
- if (o.label && o.label.name === switchLabel) {
1216
- breakStatements.push([o, p]);
1217
- } else {
1218
- isIllegal = true;
1219
- return "EXIT";
1220
- }
1221
- }
1222
-
1223
- if (
1224
- (o.type === "ContinueStatement" ||
1225
- o.type === "AwaitExpression" ||
1226
- o.type === "YieldExpression" ||
1227
- o.type === "ReturnStatement" ||
1228
- o.type === "VariableDeclaration" ||
1229
- o.type === "FunctionDeclaration" ||
1230
- o.type === "ClassDeclaration") &&
1231
- !p.find((x) => isVarContext(x))
1232
- ) {
1233
- // This can be safely outlined
1234
- if (o.type === "ReturnStatement") {
1235
- returnStatements.push([o, p]);
1236
- } else {
1237
- isIllegal = true;
1238
- return "EXIT";
1239
- }
1240
- }
1241
-
1242
- if (o.type === "Identifier") {
1243
- if (o.name === "arguments") {
1244
- isIllegal = true;
1245
- return "EXIT";
1246
- }
1247
- }
1248
- });
1249
-
1250
- return { isIllegal, breakStatements, returnStatements };
1251
- };
1252
-
1253
- const createOutlineFunction = (
1254
- body: Node[],
1255
- stateValues: number[],
1256
- label: string
1257
- ) => {
1258
- var key = controlGen.generate();
1259
-
1260
- var functionExpression = FunctionExpression([], body);
1261
- if (!this.options.es5 && chance(50)) {
1262
- functionExpression.type = "ArrowFunctionExpression";
1263
- }
1264
-
1265
- controlProperties.push(
1266
- Property(Literal(key), functionExpression, false)
1267
- );
1268
-
1269
- // Add dead code to function
1270
- if (!this.isDebug && chance(25)) {
1271
- var { test, testValue } = createPredicate(stateValues);
1272
- var deadCodeVar = this.getPlaceholder();
1273
- functionExpression.params.push(
1274
- AssignmentPattern(Identifier(deadCodeVar), test)
1275
- );
1276
- var alternate = [
1277
- ReturnStatement(
1278
- choice([
1279
- BinaryExpression(
1280
- "==",
1281
- Identifier(choice(stateVars)),
1282
- Literal(getRandomInteger(-100, 100))
1283
- ),
1284
- Literal(controlGen.generate()),
1285
- Identifier("arguments"),
1286
- Identifier(choice(stateVars)),
1287
- Identifier(controlVar),
1288
- CallExpression(getControlMember(controlGen.generate()), []),
1289
- ])
1290
- ),
1291
- ];
1292
-
1293
- functionExpression.body.body.unshift(
1294
- IfStatement(
1295
- testValue
1296
- ? UnaryExpression("!", Identifier(deadCodeVar))
1297
- : Identifier(deadCodeVar),
1298
- alternate
1299
- )
1300
- );
1301
- }
1302
-
1303
- outlinesCreated++;
1304
-
1305
- return key;
1306
- };
1307
-
1308
- const attemptOutlineStatements = (
1309
- statements: Node[],
1310
- parentBlock: Node[],
1311
- stateValues: number[],
1312
- label: string
1313
- ) => {
1314
- if (
1315
- this.isDebug ||
1316
- !this.outlineStatements ||
1317
- chance(75 + outlinesCreated - this.mangledExpressionsMade / 25)
1318
- ) {
1319
- return;
1320
- }
1321
-
1322
- var index = parentBlock.indexOf(statements[0]);
1323
- if (index === -1) return;
1324
-
1325
- var outlineInfo = canOutline(statements, parentBlock);
1326
- if (outlineInfo.isIllegal) return;
1327
-
1328
- var breakFlag = controlGen.generate();
1329
-
1330
- outlineInfo.breakStatements.forEach(([breakStatement, p]) => {
1331
- this.replace(breakStatement, ReturnStatement(Literal(breakFlag)));
1332
- });
1333
-
1334
- var returnFlag = controlGen.generate();
1335
-
1336
- outlineInfo.returnStatements.forEach(([returnStatement, p]) => {
1337
- var argument = returnStatement.argument || Identifier("undefined");
1338
-
1339
- this.replace(
1340
- returnStatement,
1341
- ReturnStatement(
1342
- ObjectExpression([Property(Literal(returnFlag), argument, false)])
1343
- )
1344
- );
1345
- });
1346
-
1347
- // Outline these statements!
1348
- var key = createOutlineFunction(clone(statements), stateValues, label);
1349
- var callExpression = CallExpression(getControlMember(key), []);
1350
-
1351
- var newStatements: Node[] = [];
1352
- if (
1353
- outlineInfo.breakStatements.length === 0 &&
1354
- outlineInfo.returnStatements.length === 0
1355
- ) {
1356
- newStatements.push(ExpressionStatement(callExpression));
1357
- } else if (outlineInfo.returnStatements.length === 0) {
1358
- newStatements.push(
1359
- IfStatement(
1360
- BinaryExpression("==", callExpression, Literal(breakFlag)),
1361
- [BreakStatement(switchLabel)]
1362
- )
1363
- );
1364
- } else {
1365
- var tempVar = this.getPlaceholder();
1366
- newStatements.push(
1367
- VariableDeclaration(VariableDeclarator(tempVar, callExpression))
1368
- );
1369
-
1370
- const t = (str): Node => new Template(str).single().expression;
1371
-
1372
- newStatements.push(
1373
- IfStatement(
1374
- t(`${tempVar} === "${breakFlag}"`),
1375
- [BreakStatement(switchLabel)],
1376
- [
1377
- IfStatement(t(`typeof ${tempVar} == "object"`), [
1378
- ReturnStatement(t(`${tempVar}["${returnFlag}"]`)),
1379
- ]),
1380
- ]
1381
- )
1382
- );
1383
- }
1384
-
1385
- // Remove the original statements from the block and replace it with the call expression
1386
- parentBlock.splice(index, statements.length, ...newStatements);
1387
- };
1388
-
1389
- const attemptOutlineExpression = (
1390
- expression: Node,
1391
- expressionParents: Node[],
1392
- stateValues: number[],
1393
- label: string
1394
- ) => {
1395
- if (
1396
- this.isDebug ||
1397
- !this.outlineExpressions ||
1398
- chance(75 + outlinesCreated - this.mangledExpressionsMade / 25)
1399
- ) {
1400
- return;
1401
- }
1402
-
1403
- var outlineInfo = canOutline(expression, expressionParents);
1404
- if (
1405
- outlineInfo.isIllegal ||
1406
- outlineInfo.breakStatements.length ||
1407
- outlineInfo.returnStatements.length
1408
- )
1409
- return;
1410
-
1411
- // Outline this expression!
1412
- var key = createOutlineFunction(
1413
- [ReturnStatement(clone(expression))],
1414
- stateValues,
1415
- label
1416
- );
1417
-
1418
- var callExpression = CallExpression(getControlMember(key), []);
1419
-
1420
- this.replaceIdentifierOrLiteral(
1421
- expression,
1422
- callExpression,
1423
- expressionParents
1424
- );
1425
- };
1426
-
1427
- const createTransitionExpression = (
1428
- index: number,
1429
- add: number,
1430
- mutatingStateValues: number[],
1431
- label: string
1432
- ) => {
1433
- var beforeStateValues = [...mutatingStateValues];
1434
- var newValue = mutatingStateValues[index] + add;
1435
-
1436
- var expr = null;
1437
-
1438
- if (this.isDebug) {
1439
- // state = NEW_STATE
1440
- expr = AssignmentExpression(
1441
- "=",
1442
- Identifier(stateVars[index]),
1443
- Literal(newValue)
1444
- );
1445
- } else if (chance(90)) {
1446
- // state += (NEW_STATE - CURRENT_STATE)
1447
- expr = AssignmentExpression(
1448
- "+=",
1449
- Identifier(stateVars[index]),
1450
- createStateBoundNumberLiteral(add, mutatingStateValues)
1451
- );
1452
- } else {
1453
- // state *= 2
1454
- // state -= DIFFERENCE
1455
- var double = mutatingStateValues[index] * 2;
1456
- var diff = double - newValue;
1457
-
1458
- var first = AssignmentExpression(
1459
- "*=",
1460
- Identifier(stateVars[index]),
1461
- createStateBoundNumberLiteral(2, mutatingStateValues)
1462
- );
1463
- mutatingStateValues[index] = double;
1464
-
1465
- expr = SequenceExpression([
1466
- first,
1467
- AssignmentExpression(
1468
- "-=",
1469
- Identifier(stateVars[index]),
1470
- createStateBoundNumberLiteral(diff, mutatingStateValues)
1471
- ),
1472
- ]);
1473
- }
1474
-
1475
- mutatingStateValues[index] = newValue;
1476
-
1477
- // These are lower quality outlines vs. the entire transition outline
1478
- if (chance(50)) {
1479
- attemptOutlineExpression(expr, [], [...beforeStateValues], label);
1480
- }
1481
-
1482
- return expr;
1483
- };
1484
-
1485
- interface Case {
1486
- state: number;
1487
- body: Node[];
1488
- label: string;
1489
- }
1490
-
1491
- var cases: Case[] = [];
1492
-
1493
- chunks.forEach((chunk, i) => {
1494
- // skip last case, its empty and never ran
1495
- if (chunk.label === endLabel) {
1496
- return;
1497
- }
1498
-
1499
- ok(labelToStates[chunk.label]);
1500
- var state = caseStates[i];
1501
-
1502
- var staticStateValues = [...labelToStates[chunk.label]];
1503
- var potentialBranches = new Set<string>();
1504
-
1505
- [...chunk.body].forEach((stmt) => {
1506
- walk(stmt, [], (o, p) => {
1507
- // This mangles certain literals with the state variables
1508
- // Ex: A number literal (50) changed to a expression (stateVar + 40), when stateVar = 10
1509
- if (
1510
- !this.isDebug &&
1511
- o.type === "Literal" &&
1512
- !p.find((x) => isVarContext(x))
1513
- ) {
1514
- if (
1515
- typeof o.value === "number" &&
1516
- Math.floor(o.value) === o.value && // Only whole numbers
1517
- Math.abs(o.value) < 100_000 && // Hard-coded limit
1518
- this.mangleNumberLiterals &&
1519
- chance(50 - this.mangledExpressionsMade / 100)
1520
- ) {
1521
- // 50 -> state1 - 10, when state1 = 60. The result is still 50
1522
-
1523
- return () => {
1524
- this.replaceIdentifierOrLiteral(
1525
- o,
1526
- createStateBoundNumberLiteral(o.value, staticStateValues),
1527
- p
1528
- );
1529
- };
1530
- }
1531
-
1532
- if (
1533
- typeof o.value === "boolean" &&
1534
- this.mangleBooleanLiterals &&
1535
- chance(50 - this.mangledExpressionsMade / 100)
1536
- ) {
1537
- // true -> state1 == 10, when state1 = 10. The result is still true
1538
-
1539
- // Choose a random state var to compare again
1540
- var index = getRandomInteger(0, stateVars.length);
1541
-
1542
- var compareValue = staticStateValues[index];
1543
-
1544
- // When false, always choose a different number, so the expression always equals false
1545
- while (!o.value && compareValue === staticStateValues[index]) {
1546
- compareValue = getRandomInteger(-150, 150);
1547
- }
1548
-
1549
- var mangledExpression: Node = BinaryExpression(
1550
- "==",
1551
- Identifier(stateVars[index]),
1552
- createStateBoundNumberLiteral(compareValue, staticStateValues)
1553
- );
1554
-
1555
- return () => {
1556
- this.replaceIdentifierOrLiteral(o, mangledExpression, p);
1557
-
1558
- attemptOutlineExpression(
1559
- o,
1560
- p,
1561
- staticStateValues,
1562
- chunk.label
1563
- );
1564
- };
1565
- }
1566
- }
1567
-
1568
- // Mangle certain referenced identifiers
1569
- // console.log("hi") -> (x ? console : window).log("hi"), when is x true. The result is the same
1570
- if (
1571
- !this.isDebug &&
1572
- o.type === "Identifier" &&
1573
- this.mangleIdentifiers &&
1574
- !reservedIdentifiers.has(o.name) &&
1575
- chance(50 - this.mangledExpressionsMade / 100) &&
1576
- !p.find((x) => isVarContext(x))
1577
- ) {
1578
- // ONLY referenced identifiers (like actual variable names) can be changed
1579
- var info = getIdentifierInfo(o, p);
1580
- if (
1581
- !info.spec.isReferenced ||
1582
- info.spec.isDefined ||
1583
- info.spec.isModified ||
1584
- info.spec.isExported
1585
- ) {
1586
- return;
1587
- }
1588
-
1589
- // Ignore __JS_CONFUSER_VAR__()
1590
- if (isJSConfuserVar(p)) {
1591
- return;
1592
- }
1593
-
1594
- // TYPEOF expression check
1595
- if (
1596
- p[0] &&
1597
- p[0].type === "UnaryExpression" &&
1598
- p[0].operator === "typeof" &&
1599
- p[0].argument === o
1600
- ) {
1601
- return;
1602
- }
1603
-
1604
- // Update expression check
1605
- if (p[0] && p[0].type === "UpdateExpression") {
1606
- return;
1607
- }
1608
-
1609
- // FOR-in/of initializer check
1610
- if (isForInitialize(o, p) === "left-hand") {
1611
- return;
1612
- }
1613
-
1614
- var { test, testValue } = createPredicate(staticStateValues);
1615
-
1616
- // test && real
1617
- var mangledExpression: Node = LogicalExpression(
1618
- testValue ? "&&" : "||",
1619
- test,
1620
- Identifier(o.name)
1621
- );
1622
-
1623
- // control.fake = real
1624
- if (chance(50)) {
1625
- mangledExpression = AssignmentExpression(
1626
- "=",
1627
- getControlMember(controlGen.generate()),
1628
- Identifier(o.name)
1629
- );
1630
- }
1631
-
1632
- // test ? real : fake
1633
- if (chance(50)) {
1634
- var alternateName = choice([
1635
- controlVar,
1636
- ...stateVars,
1637
- ...this.options.globalVariables,
1638
- ...reservedIdentifiers,
1639
- ]);
1640
-
1641
- // Don't use 'arguments'
1642
- if (alternateName === "arguments") alternateName = "undefined";
1643
-
1644
- mangledExpression = ConditionalExpression(
1645
- test,
1646
- Identifier(testValue ? o.name : alternateName),
1647
- Identifier(!testValue ? o.name : alternateName)
1648
- );
1649
- }
1650
-
1651
- return () => {
1652
- this.replaceIdentifierOrLiteral(o, mangledExpression, p);
1653
- };
1654
- }
1655
-
1656
- // Function outlining: bring out certain expressions
1657
- if (
1658
- !this.isDebug &&
1659
- o.type &&
1660
- [
1661
- "BinaryExpression",
1662
- "LogicalExpression",
1663
- "CallExpression",
1664
- "AssignmentExpression",
1665
- "MemberExpression",
1666
- "ObjectExpression",
1667
- "ConditionalExpression",
1668
- ].includes(o.type) &&
1669
- !chance(p.length * 5) && // The further down the tree the lower quality of expression
1670
- !p.find((x) => isContext(x) || x.$outlining)
1671
- ) {
1672
- o.$outlining = true;
1673
- return () => {
1674
- attemptOutlineExpression(o, p, staticStateValues, chunk.label);
1675
- };
1676
- }
1677
-
1678
- // Opaque predicates: If Statements, Conditional Statements, Switch Case test
1679
- if (
1680
- !this.isDebug &&
1681
- this.addOpaquePredicates &&
1682
- p[0] &&
1683
- chance(50 - outlinesCreated - this.mangledExpressionsMade / 100)
1684
- ) {
1685
- var isTestExpression =
1686
- (p[0].type == "IfStatement" && p[0].test === o) ||
1687
- (p[0].type === "ConditionalExpression" && p[0].test === o) ||
1688
- (p[0].type === "SwitchCase" && p[0].test === o);
1689
-
1690
- if (isTestExpression && !p.find((x) => isContext(x))) {
1691
- return () => {
1692
- var { test, testValue } = createPredicate(staticStateValues);
1693
-
1694
- this.replace(
1695
- o,
1696
- LogicalExpression(testValue ? "&&" : "||", test, clone(o))
1697
- );
1698
- };
1699
- }
1700
- }
1701
-
1702
- if (o.type == "StateIdentifier") {
1703
- return () => {
1704
- ok(labelToStates[o.label]);
1705
- this.replace(
1706
- o,
1707
- ArrayExpression(labelToStates[o.label].map(Literal))
1708
- );
1709
- };
1710
- }
1711
-
1712
- if (o.type == "GotoStatement") {
1713
- return () => {
1714
- var blockIndex = p.findIndex(
1715
- (node) => isBlock(node) || node.type === "SwitchCase"
1716
- );
1717
- if (blockIndex === -1) {
1718
- var index = chunk.body.indexOf(stmt);
1719
- ok(index != -1);
1720
-
1721
- // Top level: Insert break statement in the chunk body
1722
- // This is OKAY because this forEach uses a cloned version of the body `[...chunk.body]`
1723
- chunk.body.splice(index + 1, 0, BreakStatement(switchLabel));
1724
- } else {
1725
- var block = p[blockIndex];
1726
-
1727
- if (block.type === "SwitchCase") {
1728
- // Handle switch case break placement (Important!)
1729
- block.consequent.splice(
1730
- block.consequent.indexOf(p[blockIndex - 2] || o) + 1,
1731
- 0,
1732
- BreakStatement(switchLabel)
1733
- );
1734
- } else {
1735
- // Standard block placement
1736
- var child = p[blockIndex - 2] || o;
1737
- var childIndex = block.body.indexOf(child);
1738
-
1739
- block.body.splice(
1740
- childIndex + 1,
1741
- 0,
1742
- BreakStatement(switchLabel)
1743
- );
1744
- }
1745
- }
1746
-
1747
- if (!o.impossible) {
1748
- potentialBranches.add(o.label);
1749
- }
1750
-
1751
- var mutatingStateValues = [...labelToStates[chunk.label]];
1752
- var nextStateValues = labelToStates[o.label];
1753
- ok(nextStateValues, o.label);
1754
-
1755
- var transitionExpressions: Node[] = [];
1756
- for (
1757
- var stateValueIndex = 0;
1758
- stateValueIndex < stateVars.length;
1759
- stateValueIndex++
1760
- ) {
1761
- var diff =
1762
- nextStateValues[stateValueIndex] -
1763
- mutatingStateValues[stateValueIndex];
1764
-
1765
- // Only add if state value changed
1766
- // If pointing to itself then always add to ensure SequenceExpression isn't empty
1767
- if (diff !== 0 || o.label === chunk.label) {
1768
- transitionExpressions.push(
1769
- createTransitionExpression(
1770
- stateValueIndex,
1771
- diff,
1772
- mutatingStateValues,
1773
- chunk.label
1774
- )
1775
- );
1776
- }
1777
- }
1778
-
1779
- ok(transitionExpressions.length !== 0);
1780
-
1781
- var sequenceExpression = SequenceExpression(
1782
- transitionExpressions
1783
- );
1784
-
1785
- // Check if flagged and additional code here
1786
- if (typeof flaggedLabels[o.label] === "object") {
1787
- var { flagKey, flagValue } = flaggedLabels[o.label];
1788
-
1789
- sequenceExpression.expressions.push(
1790
- AssignmentExpression(
1791
- "=",
1792
- getControlMember(flagKey),
1793
- Literal(flagValue)
1794
- )
1795
- );
1796
- }
1797
-
1798
- attemptOutlineExpression(
1799
- sequenceExpression,
1800
- [],
1801
- staticStateValues,
1802
- chunk.label
1803
- );
1804
-
1805
- this.replace(o, ExpressionStatement(sequenceExpression));
1806
- };
1807
- }
1808
- });
1809
- });
1810
-
1811
- attemptOutlineStatements(
1812
- chunk.body,
1813
- chunk.body,
1814
- staticStateValues,
1815
- chunk.label
1816
- );
1817
-
1818
- if (!chunk.impossible) {
1819
- // FUTURE OBFUSCATION IDEA: Update controlObject based on 'potentialBranches' code
1820
- // This idea would require a lot of work but would make some seriously effective obfuscation
1821
- // for protecting the data. In 'inactive' states the data could be overwritten to fake values
1822
- // And in the 'active' state the data would brought back just in time. This would require the controlObject
1823
- // state to be known in all chunks
1824
- }
1825
-
1826
- var caseObject: Case = {
1827
- body: chunk.body,
1828
- state: state,
1829
- label: chunk.label,
1830
- };
1831
-
1832
- cases.push(caseObject);
1833
- });
1834
-
1835
- if (!this.isDebug && this.addDeadCode) {
1836
- // Add fake control object updates
1837
- chunks.forEach((chunk) => {
1838
- if (chance(10)) {
1839
- // These deadCode variants can NOT break the state/control variables
1840
- // They are executed!
1841
- var deadCodeChoices = [
1842
- ExpressionStatement(
1843
- AssignmentExpression(
1844
- "=",
1845
- getControlMember(controlGen.generate()),
1846
- Literal(controlGen.generate())
1847
- )
1848
- ),
1849
- ExpressionStatement(
1850
- UnaryExpression(
1851
- "delete",
1852
- getControlMember(controlGen.generate())
1853
- )
1854
- ),
1855
- ];
1856
-
1857
- // These deadCode variants can make breaking changes
1858
- // because they are never ran
1859
- if (chunk.impossible) {
1860
- var randomControlKey =
1861
- choice(
1862
- controlProperties
1863
- .map((prop) => prop.key?.value)
1864
- .filter((x) => x && typeof x === "string")
1865
- ) || controlGen.generate();
1866
-
1867
- deadCodeChoices = deadCodeChoices.concat([
1868
- ExpressionStatement(
1869
- AssignmentExpression(
1870
- "=",
1871
- Identifier(controlVar),
1872
- Literal(false)
1873
- )
1874
- ),
1875
- ExpressionStatement(
1876
- AssignmentExpression(
1877
- "=",
1878
- Identifier(controlVar),
1879
- Identifier("undefined")
1880
- )
1881
- ),
1882
- ExpressionStatement(
1883
- AssignmentExpression(
1884
- "=",
1885
- getControlMember(randomControlKey),
1886
- Identifier("undefined")
1887
- )
1888
- ),
1889
- ExpressionStatement(
1890
- UnaryExpression("delete", getControlMember(randomControlKey))
1891
- ),
1892
- ]);
1893
- }
1894
-
1895
- chunk.body.unshift(choice(deadCodeChoices));
1896
- }
1897
- });
1898
- }
1899
-
1900
- if (!this.isDebug) {
1901
- shuffle(cases);
1902
- shuffle(controlProperties);
1903
- }
1904
-
1905
- var discriminant = new Template(`${stateVars.join("+")}`).single()
1906
- .expression;
1907
-
1908
- objectBody.length = 0;
1909
- // Perverse position of import declarations
1910
- for (var importDeclaration of importDeclarations) {
1911
- objectBody.push(importDeclaration);
1912
- }
1913
-
1914
- // As well as functions are brought up
1915
- for (var functionName of functionDeclarationNames) {
1916
- objectBody.push(
1917
- VariableDeclaration(
1918
- VariableDeclarator(
1919
- functionName,
1920
- functionDeclarationValues.get(functionName)
1921
- )
1922
- )
1923
- );
1924
- }
1925
-
1926
- var defaultCaseIndex = getRandomInteger(0, cases.length);
1927
- var switchCases: Node[] = [];
1928
-
1929
- cases.forEach((caseObject, i) => {
1930
- // Empty case OR single break statement is skipped
1931
- if (
1932
- caseObject.body.length === 0 ||
1933
- (caseObject.body.length === 1 &&
1934
- caseObject.body[0].type === "BreakStatement" &&
1935
- caseObject.body[0].label?.name === switchLabel)
1936
- )
1937
- return;
1938
-
1939
- var test = Literal(caseObject.state);
1940
- var isEligibleForOutlining = false;
1941
-
1942
- // Check if Control Map has this value
1943
- if (!this.isDebug && controlConstantMap.has(caseObject.state)) {
1944
- test = getControlMember(
1945
- controlConstantMap.get(caseObject.state)?.key
1946
- );
1947
- }
1948
-
1949
- // Create complex test expressions for each switch case
1950
- if (!this.isDebug && this.addComplexTest && chance(25)) {
1951
- isEligibleForOutlining = true;
1952
-
1953
- // case STATE+X:
1954
- var stateVarIndex = getRandomInteger(0, stateVars.length);
1955
-
1956
- var stateValues = labelToStates[caseObject.label];
1957
- var difference = stateValues[stateVarIndex] - caseObject.state;
1958
-
1959
- var conditionNodes: Node[] = [];
1960
- var alreadyConditionedItems = new Set<string>();
1961
-
1962
- // This code finds clash conditions and adds them to 'conditionNodes' array
1963
- Object.keys(labelToStates).forEach((label) => {
1964
- if (label !== caseObject.label) {
1965
- var labelStates = labelToStates[label];
1966
- var totalState = labelStates.reduce((a, b) => a + b, 0);
1967
-
1968
- if (totalState === labelStates[stateVarIndex] - difference) {
1969
- var differentIndex = labelStates.findIndex(
1970
- (v, i) => v !== stateValues[i]
1971
- );
1972
- if (differentIndex !== -1) {
1973
- var expressionAsString =
1974
- stateVars[differentIndex] +
1975
- "!=" +
1976
- labelStates[differentIndex];
1977
- if (!alreadyConditionedItems.has(expressionAsString)) {
1978
- alreadyConditionedItems.add(expressionAsString);
1979
-
1980
- conditionNodes.push(
1981
- BinaryExpression(
1982
- "!=",
1983
- Identifier(stateVars[differentIndex]),
1984
- Literal(labelStates[differentIndex])
1985
- )
1986
- );
1987
- }
1988
- } else {
1989
- conditionNodes.push(
1990
- BinaryExpression(
1991
- "!=",
1992
- clone(discriminant),
1993
- Literal(totalState)
1994
- )
1995
- );
1996
- }
1997
- }
1998
- }
1999
- });
2000
-
2001
- // case STATE!=Y && STATE+X
2002
- test = BinaryExpression(
2003
- "-",
2004
- Identifier(stateVars[stateVarIndex]),
2005
- Literal(difference)
2006
- );
2007
-
2008
- // Use the 'conditionNodes' to not cause state clashing issues
2009
- conditionNodes.forEach((conditionNode) => {
2010
- test = LogicalExpression("&&", conditionNode, test);
2011
- });
2012
- }
2013
-
2014
- // A 'flagged' label has addition 'flagKey' that gets switched before jumped to
2015
- if (flaggedLabels[caseObject.label]) {
2016
- isEligibleForOutlining = true;
2017
-
2018
- var { flagKey, flagValue } = flaggedLabels[caseObject.label];
2019
-
2020
- var alternateNum: number;
2021
- do {
2022
- alternateNum = getRandomInteger(-1000, 1000 + chunks.length);
2023
- } while (caseSelection.has(alternateNum));
2024
-
2025
- var alternate = Literal(alternateNum);
2026
-
2027
- // case FLAG ? <REAL> : <FAKE>:
2028
- test = ConditionalExpression(
2029
- getControlMember(flagKey),
2030
-
2031
- flagValue ? test : alternate,
2032
- !flagValue ? test : alternate
2033
- );
2034
- }
2035
-
2036
- // Outline this switch case test
2037
- if (
2038
- !this.isDebug &&
2039
- this.outlineExpressions &&
2040
- isEligibleForOutlining &&
2041
- chance(75 - outlinesCreated - this.mangledExpressionsMade / 25)
2042
- ) {
2043
- this.mangledExpressionsMade++;
2044
-
2045
- // Selected a random parent node (or this node) to insert this function in
2046
- var selectedControlNode = choice(allControlNodes);
2047
- var selectedControlProperties =
2048
- selectedControlNode.$controlProperties;
2049
- var selectedControlVar = selectedControlNode.$controlVar;
2050
- var selectedControlGen = selectedControlNode.$controlGen;
2051
-
2052
- var fnKey = selectedControlGen.generate();
2053
-
2054
- // Pass in the:
2055
- // - controlVar for 'flagged labels' code check
2056
- // - stateVars for 'complex test expressions'
2057
- // (Check which identifiers are actually needed)
2058
- var argumentList = [],
2059
- watchingFor = new Set([controlVar, ...stateVars]);
2060
- walk(test, [], (o, p) => {
2061
- if (o.type === "Identifier" && watchingFor.has(o.name)) {
2062
- watchingFor.delete(o.name);
2063
- argumentList.push(Identifier(o.name));
2064
- }
2065
- });
2066
-
2067
- selectedControlProperties.push(
2068
- Property(
2069
- Literal(fnKey),
2070
- FunctionExpression(argumentList, [ReturnStatement(test)]),
2071
- true
2072
- )
2073
- );
2074
-
2075
- // case control.a(control, s1, s2):
2076
- test = CallExpression(
2077
- getControlMember(fnKey, selectedControlVar),
2078
- clone(argumentList)
2079
- );
2080
- }
2081
-
2082
- // One random case gets to be default
2083
- if (!this.isDebug && i === defaultCaseIndex) test = null;
2084
-
2085
- var testArray: Node[] = [test];
2086
- if (!this.isDebug && this.addFakeTest && chance(50)) {
2087
- // Add fake test
2088
- // case <FAKE>:
2089
- // case <REAL>:
2090
- // case <FAKE>:
2091
- var fakeTestCount = getRandomInteger(1, 4);
2092
- for (var i = 0; i < fakeTestCount; i++) {
2093
- // Create a fake test number that doesn't interfere with the actual states
2094
- var fakeTestNum;
2095
- do {
2096
- fakeTestNum = getRandomInteger(1, 1000 + caseSelection.size);
2097
- } while (caseSelection.has(fakeTestNum));
2098
-
2099
- // Add this fake test
2100
- testArray.push(Literal(fakeTestNum));
2101
- }
2102
-
2103
- shuffle(testArray);
2104
- }
2105
-
2106
- testArray.forEach((test, i) => {
2107
- var body = i === testArray.length - 1 ? caseObject.body : [];
2108
-
2109
- switchCases.push(SwitchCase(test, body));
2110
- });
2111
- });
2112
-
2113
- // switch(state) { case ... }
2114
- var switchStatement: Node = SwitchStatement(discriminant, switchCases);
2115
-
2116
- var declarations: Node[] = [];
2117
-
2118
- // var state = START_STATE
2119
- declarations.push(
2120
- ...stateVars.map((stateVar, i) => {
2121
- return VariableDeclarator(stateVar, Literal(initStateValues[i]));
2122
- })
2123
- );
2124
-
2125
- // var control = { strings, numbers, outlined functions, etc... }
2126
- var objectExpression = ObjectExpression(controlProperties);
2127
- declarations.push(VariableDeclarator(controlVar, objectExpression));
2128
-
2129
- objectBody.push(
2130
- // Use individual variable declarations instead so Stack can apply
2131
- ...declarations.map((declaration) =>
2132
- VariableDeclaration(declaration, "var")
2133
- )
2134
- );
2135
-
2136
- // while (state != END_STATE) {...}
2137
- var whileTest = BinaryExpression(
2138
- "!=",
2139
- clone(discriminant),
2140
- Literal(endState)
2141
- );
2142
-
2143
- objectBody.push(
2144
- WhileStatement(whileTest, [
2145
- LabeledStatement(switchLabel, switchStatement),
2146
- ])
2147
- );
2148
-
2149
- // mark this object for switch case obfuscation
2150
- switchStatement.$controlFlowFlattening = true;
2151
- };
2152
- }
2153
- }