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