js-confuser 1.5.9 → 1.7.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.
- package/.github/workflows/node.js.yml +2 -2
- package/CHANGELOG.md +55 -0
- package/README.md +346 -165
- package/dist/constants.js +6 -2
- package/dist/index.js +9 -21
- package/dist/obfuscator.js +19 -31
- package/dist/options.js +5 -5
- package/dist/order.js +1 -3
- package/dist/presets.js +6 -7
- package/dist/probability.js +2 -4
- package/dist/templates/bufferToString.js +13 -0
- package/dist/templates/crash.js +3 -3
- package/dist/templates/es5.js +18 -0
- package/dist/templates/functionLength.js +16 -0
- package/dist/transforms/calculator.js +77 -21
- package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +980 -367
- package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +4 -1
- package/dist/transforms/controlFlowFlattening/switchCaseObfuscation.js +25 -26
- package/dist/transforms/deadCode.js +33 -25
- package/dist/transforms/dispatcher.js +8 -4
- package/dist/transforms/es5/antiDestructuring.js +2 -0
- package/dist/transforms/es5/es5.js +31 -34
- package/dist/transforms/extraction/duplicateLiteralsRemoval.js +92 -58
- package/dist/transforms/finalizer.js +82 -0
- package/dist/transforms/flatten.js +229 -148
- package/dist/transforms/identifier/globalAnalysis.js +88 -0
- package/dist/transforms/identifier/globalConcealing.js +10 -83
- package/dist/transforms/identifier/movedDeclarations.js +35 -88
- package/dist/transforms/identifier/renameVariables.js +124 -59
- package/dist/transforms/identifier/variableAnalysis.js +58 -62
- package/dist/transforms/lock/lock.js +0 -37
- package/dist/transforms/minify.js +60 -57
- package/dist/transforms/opaquePredicates.js +1 -1
- package/dist/transforms/preparation/preparation.js +2 -2
- package/dist/transforms/preparation.js +231 -0
- package/dist/transforms/renameLabels.js +1 -1
- package/dist/transforms/rgf.js +139 -247
- package/dist/transforms/stack.js +128 -26
- package/dist/transforms/string/encoding.js +150 -179
- package/dist/transforms/string/stringCompression.js +14 -15
- package/dist/transforms/string/stringConcealing.js +25 -8
- package/dist/transforms/string/stringEncoding.js +13 -24
- package/dist/transforms/transform.js +12 -19
- package/dist/traverse.js +24 -10
- package/dist/util/gen.js +17 -1
- package/dist/util/identifiers.js +37 -3
- package/dist/util/insert.js +35 -4
- package/dist/util/random.js +15 -0
- package/docs/ControlFlowFlattening.md +595 -0
- package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
- package/{Integrity.md → docs/Integrity.md} +2 -2
- package/docs/RGF.md +419 -0
- package/package.json +5 -5
- package/src/constants.ts +3 -0
- package/src/index.ts +2 -2
- package/src/obfuscator.ts +19 -31
- package/src/options.ts +14 -103
- package/src/order.ts +1 -5
- package/src/presets.ts +6 -7
- package/src/probability.ts +2 -3
- package/src/templates/bufferToString.ts +68 -0
- package/src/templates/crash.ts +15 -19
- package/src/templates/es5.ts +131 -0
- package/src/templates/functionLength.ts +14 -0
- package/src/transforms/calculator.ts +122 -59
- package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +1583 -571
- package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +4 -1
- package/src/transforms/deadCode.ts +383 -26
- package/src/transforms/dispatcher.ts +9 -4
- package/src/transforms/es5/antiDestructuring.ts +2 -0
- package/src/transforms/es5/es5.ts +32 -77
- package/src/transforms/extraction/duplicateLiteralsRemoval.ts +133 -129
- package/src/transforms/{hexadecimalNumbers.ts → finalizer.ts} +29 -13
- package/src/transforms/flatten.ts +357 -300
- package/src/transforms/identifier/globalAnalysis.ts +85 -0
- package/src/transforms/identifier/globalConcealing.ts +14 -103
- package/src/transforms/identifier/movedDeclarations.ts +49 -102
- package/src/transforms/identifier/renameVariables.ts +149 -78
- package/src/transforms/identifier/variableAnalysis.ts +66 -73
- package/src/transforms/lock/lock.ts +1 -42
- package/src/transforms/minify.ts +91 -75
- package/src/transforms/opaquePredicates.ts +2 -2
- package/src/transforms/preparation.ts +238 -0
- package/src/transforms/renameLabels.ts +2 -2
- package/src/transforms/rgf.ts +213 -405
- package/src/transforms/stack.ts +156 -36
- package/src/transforms/string/encoding.ts +115 -212
- package/src/transforms/string/stringCompression.ts +27 -18
- package/src/transforms/string/stringConcealing.ts +39 -9
- package/src/transforms/string/stringEncoding.ts +18 -18
- package/src/transforms/transform.ts +21 -23
- package/src/traverse.ts +23 -4
- package/src/types.ts +2 -1
- package/src/util/gen.ts +28 -3
- package/src/util/identifiers.ts +43 -2
- package/src/util/insert.ts +38 -3
- package/src/util/random.ts +13 -0
- package/test/code/Cash.test.ts +1 -1
- package/test/code/Dynamic.test.ts +12 -10
- package/test/code/ES6.src.js +146 -0
- package/test/code/ES6.test.ts +28 -2
- package/test/index.test.ts +2 -1
- package/test/probability.test.ts +44 -0
- package/test/templates/template.test.ts +1 -1
- package/test/transforms/antiTooling.test.ts +22 -0
- package/test/transforms/calculator.test.ts +40 -0
- package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +702 -160
- package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +173 -0
- package/test/transforms/deadCode.test.ts +66 -15
- package/test/transforms/dispatcher.test.ts +20 -1
- package/test/transforms/es5/antiDestructuring.test.ts +16 -0
- package/test/transforms/flatten.test.ts +399 -86
- package/test/transforms/identifier/movedDeclarations.test.ts +63 -8
- package/test/transforms/identifier/renameVariables.test.ts +119 -0
- package/test/transforms/lock/antiDebug.test.ts +2 -2
- package/test/transforms/lock/lock.test.ts +1 -48
- package/test/transforms/minify.test.ts +104 -0
- package/test/transforms/preparation.test.ts +157 -0
- package/test/transforms/rgf.test.ts +261 -381
- package/test/transforms/stack.test.ts +143 -21
- package/test/transforms/string/stringCompression.test.ts +39 -0
- package/test/transforms/string/stringConcealing.test.ts +82 -0
- package/test/transforms/string/stringEncoding.test.ts +53 -2
- package/test/transforms/transform.test.ts +66 -0
- package/test/traverse.test.ts +139 -0
- package/test/util/identifiers.test.ts +113 -1
- package/test/util/insert.test.ts +57 -3
- package/src/transforms/controlFlowFlattening/choiceFlowObfuscation.ts +0 -87
- package/src/transforms/controlFlowFlattening/controlFlowObfuscation.ts +0 -203
- package/src/transforms/controlFlowFlattening/switchCaseObfuscation.ts +0 -130
- package/src/transforms/eval.ts +0 -89
- package/src/transforms/hideInitializingCode.ts +0 -432
- package/src/transforms/identifier/nameRecycling.ts +0 -280
- package/src/transforms/label.ts +0 -64
- package/src/transforms/preparation/nameConflicts.ts +0 -102
- package/src/transforms/preparation/preparation.ts +0 -176
- package/test/transforms/controlFlowFlattening/controlFlowObfuscation.test.ts +0 -101
- package/test/transforms/controlFlowFlattening/switchCaseObfuscation.test.ts +0 -120
- package/test/transforms/eval.test.ts +0 -131
- package/test/transforms/hideInitializingCode.test.ts +0 -336
- package/test/transforms/identifier/nameRecycling.test.ts +0 -205
- package/test/transforms/preparation/nameConflicts.test.ts +0 -52
- package/test/transforms/preparation/preparation.test.ts +0 -62
|
@@ -25,19 +25,23 @@ var _random = require("../../util/random");
|
|
|
25
25
|
|
|
26
26
|
var _transform = _interopRequireDefault(require("../transform"));
|
|
27
27
|
|
|
28
|
-
var _controlFlowObfuscation = _interopRequireDefault(require("./controlFlowObfuscation"));
|
|
29
|
-
|
|
30
28
|
var _expressionObfuscation = _interopRequireDefault(require("./expressionObfuscation"));
|
|
31
29
|
|
|
32
|
-
var _switchCaseObfuscation = _interopRequireDefault(require("./switchCaseObfuscation"));
|
|
33
|
-
|
|
34
30
|
var _stringConcealing = require("../string/stringConcealing");
|
|
35
31
|
|
|
32
|
+
var _constants = require("../../constants");
|
|
33
|
+
|
|
34
|
+
var _compare = require("../../util/compare");
|
|
35
|
+
|
|
36
36
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
37
37
|
|
|
38
38
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
const flattenStructures = new Set(["IfStatement", "ForStatement", "WhileStatement", "DoWhileStatement"]);
|
|
41
|
+
/**
|
|
42
|
+
* A chunk represents a small segment of code
|
|
43
|
+
*/
|
|
44
|
+
|
|
41
45
|
/**
|
|
42
46
|
* Breaks functions into DAGs (Directed Acyclic Graphs)
|
|
43
47
|
*
|
|
@@ -51,178 +55,154 @@ var flattenStructures = new Set(["IfStatement", "ForStatement", "WhileStatement"
|
|
|
51
55
|
* - 2. At the end of each case, the state variable is updated to the next block of code.
|
|
52
56
|
* - 3. The while loop continues until the the state variable is the end state.
|
|
53
57
|
*/
|
|
54
|
-
|
|
55
58
|
class ControlFlowFlattening extends _transform.default {
|
|
59
|
+
// in Debug mode, the output is much easier to read
|
|
60
|
+
// Flatten if-statements, for-loops, etc
|
|
61
|
+
// var control = { str1, num1 }
|
|
62
|
+
// 50 => state + X
|
|
63
|
+
// true => state == X
|
|
64
|
+
// console => (state == X ? console : _)
|
|
65
|
+
// Tries to outline entire chunks
|
|
66
|
+
// Tries to outline expressions found in chunks
|
|
67
|
+
// case s != 49 && s - 10:
|
|
68
|
+
// case 100: case 490: case 510: ...
|
|
69
|
+
// add fakes chunks of code
|
|
70
|
+
// predicate ? REAL : FAKE
|
|
71
|
+
// s=NEXT_STATE,flag=true,break
|
|
72
|
+
// Limit amount of mangling
|
|
73
|
+
// Amount of blocks changed by Control Flow Flattening
|
|
56
74
|
constructor(o) {
|
|
57
75
|
super(o, _order.ObfuscateOrder.ControlFlowFlattening);
|
|
58
76
|
|
|
59
77
|
_defineProperty(this, "isDebug", false);
|
|
60
78
|
|
|
79
|
+
_defineProperty(this, "flattenControlStructures", true);
|
|
80
|
+
|
|
81
|
+
_defineProperty(this, "addToControlObject", true);
|
|
82
|
+
|
|
83
|
+
_defineProperty(this, "mangleNumberLiterals", true);
|
|
84
|
+
|
|
85
|
+
_defineProperty(this, "mangleBooleanLiterals", true);
|
|
86
|
+
|
|
87
|
+
_defineProperty(this, "mangleIdentifiers", true);
|
|
88
|
+
|
|
89
|
+
_defineProperty(this, "outlineStatements", true);
|
|
90
|
+
|
|
91
|
+
_defineProperty(this, "outlineExpressions", true);
|
|
92
|
+
|
|
93
|
+
_defineProperty(this, "addComplexTest", true);
|
|
94
|
+
|
|
95
|
+
_defineProperty(this, "addFakeTest", true);
|
|
96
|
+
|
|
97
|
+
_defineProperty(this, "addDeadCode", true);
|
|
98
|
+
|
|
99
|
+
_defineProperty(this, "addOpaquePredicates", true);
|
|
100
|
+
|
|
101
|
+
_defineProperty(this, "addFlaggedLabels", true);
|
|
102
|
+
|
|
103
|
+
_defineProperty(this, "mangledExpressionsMade", 0);
|
|
104
|
+
|
|
105
|
+
_defineProperty(this, "cffCount", 0);
|
|
106
|
+
|
|
61
107
|
if (!this.isDebug) {
|
|
62
108
|
this.before.push(new _expressionObfuscation.default(o));
|
|
63
|
-
this.after.push(new _controlFlowObfuscation.default(o));
|
|
64
|
-
this.after.push(new _switchCaseObfuscation.default(o));
|
|
65
109
|
} else {
|
|
66
110
|
console.warn("Debug mode enabled");
|
|
67
|
-
}
|
|
68
|
-
|
|
111
|
+
}
|
|
69
112
|
}
|
|
70
113
|
|
|
71
114
|
match(object, parents) {
|
|
72
|
-
return (0, _traverse.isBlock)(object) && (!parents[
|
|
115
|
+
return (0, _traverse.isBlock)(object) && (!parents[0] || !flattenStructures.has(parents[0].type)) && (!parents[1] || !flattenStructures.has(parents[1].type));
|
|
73
116
|
}
|
|
74
117
|
|
|
75
118
|
transform(object, parents) {
|
|
76
119
|
var _this = this;
|
|
77
120
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
121
|
+
// Must be at least 3 statements or more
|
|
122
|
+
if (object.body.length < 3) {
|
|
123
|
+
return;
|
|
124
|
+
} // No 'let'/'const' allowed (These won't work in Switch cases!)
|
|
82
125
|
|
|
83
|
-
if ((0, _identifiers.containsLexicallyBoundVariables)(object, parents)) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
127
|
+
if ((0, _identifiers.containsLexicallyBoundVariables)(object, parents)) {
|
|
128
|
+
return;
|
|
129
|
+
} // Check user's threshold setting
|
|
90
130
|
|
|
91
|
-
var body = (0, _insert.getBlockBody)(object.body);
|
|
92
131
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Fix 1. Bring hoisted functions up to be declared first
|
|
132
|
+
if (!(0, _probability.ComputeProbabilityMap)(this.options.controlFlowFlattening, x => x)) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
97
135
|
|
|
136
|
+
var objectBody = (0, _insert.getBlockBody)(object.body);
|
|
98
137
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* The variable names
|
|
104
|
-
*
|
|
105
|
-
* index -> var name
|
|
106
|
-
*/
|
|
138
|
+
if (!objectBody.length) {
|
|
139
|
+
return;
|
|
140
|
+
} // Purely for naming purposes
|
|
107
141
|
|
|
108
|
-
var stateVars = Array(this.isDebug ? 1 : (0, _random.getRandomInteger)(2, 5)).fill(0).map(() => this.getPlaceholder());
|
|
109
|
-
body.forEach((stmt, i) => {
|
|
110
|
-
if (stmt.type == "FunctionDeclaration") {
|
|
111
|
-
functionDeclarations.add(stmt);
|
|
112
|
-
var name = stmt.id && stmt.id.name;
|
|
113
|
-
fnNames.add(name);
|
|
114
142
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
illegalFnNames.add(name);
|
|
121
|
-
return "EXIT";
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
(0, _traverse.walk)(object, parents, (o, p) => {
|
|
128
|
-
if (o.type == "Identifier" && fnNames.has(o.name)) {
|
|
129
|
-
var info = (0, _identifiers.getIdentifierInfo)(o, p);
|
|
143
|
+
var cffIndex = this.cffCount++; // The controlVar is an object containing:
|
|
144
|
+
// - Strings found in chunks
|
|
145
|
+
// - Numbers found in chunks
|
|
146
|
+
// - Helper functions to adjust the state
|
|
147
|
+
// - Outlined expressions changed into functions
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
149
|
+
var controlVar = this.getPlaceholder() + "_c".concat(cffIndex, "_CONTROL");
|
|
150
|
+
var controlProperties = [];
|
|
151
|
+
var controlConstantMap = new Map();
|
|
152
|
+
var controlGen = this.getGenerator("mangled");
|
|
153
|
+
var controlTestKey = controlGen.generate(); // This 'controlVar' can be accessed by child-nodes
|
|
134
154
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
fnNames.delete(o.name);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!info.spec.isDefined) {
|
|
148
|
-
var b = (0, _traverse.getBlock)(o, p);
|
|
149
|
-
|
|
150
|
-
if (b !== object || !p[0] || p[0].type !== "CallExpression") {
|
|
151
|
-
illegalFnNames.add(o.name);
|
|
152
|
-
} else {
|
|
153
|
-
var isExtractable = false;
|
|
154
|
-
|
|
155
|
-
if (p[1]) {
|
|
156
|
-
if (p[1].type == "ExpressionStatement" && p[1].expression == p[0] && p[2] == object.body) {
|
|
157
|
-
isExtractable = true;
|
|
158
|
-
p[1].$callExpression = "ExpressionStatement";
|
|
159
|
-
p[1].$fnName = o.name;
|
|
160
|
-
} else if (p[1].type == "VariableDeclarator" && p[1].init == p[0] && p[2].length === 1 && p[4] == object.body) {
|
|
161
|
-
isExtractable = true;
|
|
162
|
-
p[3].$callExpression = "VariableDeclarator";
|
|
163
|
-
p[3].$fnName = o.name;
|
|
164
|
-
} else if (p[1].type == "AssignmentExpression" && p[1].operator == "=" && p[1].right === p[0] && p[2] && p[2].type == "ExpressionStatement" && p[3] == object.body) {
|
|
165
|
-
isExtractable = true;
|
|
166
|
-
p[2].$callExpression = "AssignmentExpression";
|
|
167
|
-
p[2].$fnName = o.name;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
155
|
+
object.$controlVar = controlVar;
|
|
156
|
+
object.$controlConstantMap = controlConstantMap;
|
|
157
|
+
object.$controlProperties = controlProperties;
|
|
158
|
+
object.$controlGen = controlGen;
|
|
159
|
+
return () => {
|
|
160
|
+
(0, _assert.ok)(Array.isArray(objectBody)); // The state variable names (and quantity)
|
|
170
161
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}); // redefined function,
|
|
162
|
+
var stateVars = Array(this.isDebug ? 1 : (0, _random.getRandomInteger)(2, 5)).fill(0).map((_, i) => this.getPlaceholder() + "_c".concat(cffIndex, "_S").concat(i)); // How often should chunks be split up?
|
|
163
|
+
// Percentage between 10% and 90% based on block size
|
|
178
164
|
|
|
179
|
-
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
165
|
+
var splitPercent = Math.max(10, 90 - objectBody.length * 5); // Find functions and import declarations
|
|
182
166
|
|
|
183
|
-
illegalFnNames.forEach(illegal => {
|
|
184
|
-
fnNames.delete(illegal);
|
|
185
|
-
});
|
|
186
167
|
var importDeclarations = [];
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
168
|
+
var functionDeclarationNames = new Set();
|
|
169
|
+
var functionDeclarationValues = new Map(); // Find all parent control-nodes
|
|
170
|
+
|
|
171
|
+
const allControlNodes = [object];
|
|
172
|
+
parents.filter(x => x.$controlVar).forEach(node => allControlNodes.push(node));
|
|
173
|
+
|
|
174
|
+
const addControlMapConstant = literalValue => {
|
|
175
|
+
var _selectedControlConst;
|
|
176
|
+
|
|
177
|
+
// Choose a random control node to add to
|
|
178
|
+
var controlNode = (0, _random.choice)(allControlNodes);
|
|
179
|
+
var selectedControlVar = controlNode.$controlVar;
|
|
180
|
+
var selectedControlConstantMap = controlNode.$controlConstantMap;
|
|
181
|
+
var selectedControlProperties = controlNode.$controlProperties;
|
|
182
|
+
var key = (_selectedControlConst = selectedControlConstantMap.get(literalValue)) === null || _selectedControlConst === void 0 ? void 0 : _selectedControlConst.key; // Not found, create
|
|
183
|
+
|
|
184
|
+
if (!key) {
|
|
185
|
+
key = controlNode.$controlGen.generate();
|
|
186
|
+
selectedControlConstantMap.set(literalValue, {
|
|
187
|
+
key: key
|
|
188
|
+
});
|
|
189
|
+
selectedControlProperties.push((0, _gen.Property)((0, _gen.Literal)(key), (0, _gen.Literal)(literalValue), false));
|
|
191
190
|
}
|
|
192
|
-
}
|
|
193
191
|
|
|
194
|
-
|
|
192
|
+
return getControlMember(key, selectedControlVar);
|
|
193
|
+
}; // Helper function to easily make control object accessors
|
|
195
194
|
|
|
196
|
-
if (body.length > 20) {
|
|
197
|
-
fraction /= Math.max(1.2, body.length - 18);
|
|
198
|
-
}
|
|
199
195
|
|
|
200
|
-
|
|
196
|
+
const getControlMember = function (key) {
|
|
197
|
+
let objectName = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : controlVar;
|
|
198
|
+
return (0, _gen.MemberExpression)((0, _gen.Identifier)(objectName), (0, _gen.Literal)(key), true);
|
|
199
|
+
}; // This function recursively calls itself to flatten and split up code into 'chunks'
|
|
201
200
|
|
|
202
|
-
if (isNaN(fraction) || !isFinite(fraction)) {
|
|
203
|
-
fraction = 0.5;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
var resultVar = this.getPlaceholder();
|
|
207
|
-
var argVar = this.getPlaceholder();
|
|
208
|
-
var testVar = this.getPlaceholder();
|
|
209
|
-
var stringBankVar = this.getPlaceholder();
|
|
210
|
-
var stringBank = Object.create(null);
|
|
211
|
-
var stringBankByLabels = Object.create(null);
|
|
212
|
-
let stringBankGen = this.getGenerator();
|
|
213
|
-
var needsTestVar = false;
|
|
214
|
-
var needsResultAndArgVar = false;
|
|
215
|
-
var needsStringBankVar = false;
|
|
216
|
-
var fnToLabel = Object.create(null);
|
|
217
|
-
fnNames.forEach(fnName => {
|
|
218
|
-
fnToLabel[fnName] = this.getPlaceholder();
|
|
219
|
-
});
|
|
220
201
|
|
|
221
|
-
const flattenBody =
|
|
222
|
-
let startingLabel = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.getPlaceholder();
|
|
202
|
+
const flattenBody = (body, startingLabel) => {
|
|
223
203
|
var chunks = [];
|
|
224
204
|
var currentBody = [];
|
|
225
|
-
var currentLabel = startingLabel;
|
|
205
|
+
var currentLabel = startingLabel; // This function ends the current chunk being created ('currentBody')
|
|
226
206
|
|
|
227
207
|
const finishCurrentChunk = function (pointingLabel, newLabel) {
|
|
228
208
|
let addGotoStatement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
|
|
@@ -245,94 +225,94 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
245
225
|
chunks.push({
|
|
246
226
|
label: currentLabel,
|
|
247
227
|
body: [...currentBody]
|
|
248
|
-
});
|
|
249
|
-
(0, _traverse.walk)(currentBody, [], (o, p) => {
|
|
250
|
-
if (o.type == "Literal" && typeof o.value == "string" && !(0, _stringConcealing.isModuleSource)(o, p) && !o.regex && Math.random() / (Object.keys(stringBank).length / 2 + 1) > 0.5) {
|
|
251
|
-
needsStringBankVar = true;
|
|
228
|
+
}); // Random chance of this chunk being flagged (First label cannot be flagged)
|
|
252
229
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
230
|
+
if (!_this.isDebug && _this.addFlaggedLabels && currentLabel !== startLabel && (0, _random.chance)(25)) {
|
|
231
|
+
flaggedLabels[currentLabel] = {
|
|
232
|
+
flagKey: controlGen.generate(),
|
|
233
|
+
flagValue: (0, _random.choice)([true, false])
|
|
234
|
+
};
|
|
235
|
+
}
|
|
256
236
|
|
|
257
|
-
|
|
237
|
+
(0, _traverse.walk)(currentBody, [], (o, p) => {
|
|
238
|
+
if (o.type === "Literal" && !_this.isDebug) {
|
|
239
|
+
// Add strings to the control object
|
|
240
|
+
if (_this.addToControlObject && typeof o.value === "string" && o.value.length >= 3 && o.value.length <= 100 && !(0, _stringConcealing.isModuleSource)(o, p) && !(0, _compare.isDirective)(o, p) && !o.regex && (0, _random.chance)(50 - controlConstantMap.size - _this.mangledExpressionsMade / 100)) {
|
|
241
|
+
return () => {
|
|
242
|
+
_this.replaceIdentifierOrLiteral(o, addControlMapConstant(o.value), p);
|
|
243
|
+
};
|
|
244
|
+
} // Add numbers to the control object
|
|
258
245
|
|
|
259
|
-
if (typeof stringBank[o.value] === "undefined") {
|
|
260
|
-
stringBank[o.value] = stringBankGen.generate();
|
|
261
|
-
}
|
|
262
246
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
247
|
+
if (_this.addToControlObject && typeof o.value === "number" && Math.floor(o.value) === o.value && Math.abs(o.value) < 100000 && (0, _random.chance)(50 - controlConstantMap.size - _this.mangledExpressionsMade / 100)) {
|
|
248
|
+
return () => {
|
|
249
|
+
_this.replaceIdentifierOrLiteral(o, addControlMapConstant(o.value), p);
|
|
250
|
+
};
|
|
251
|
+
}
|
|
266
252
|
}
|
|
267
253
|
});
|
|
268
254
|
currentLabel = newLabel;
|
|
269
255
|
currentBody = [];
|
|
270
256
|
};
|
|
271
257
|
|
|
272
|
-
body
|
|
273
|
-
|
|
274
|
-
|
|
258
|
+
if (body !== objectBody) {
|
|
259
|
+
// This code is nested. Move function declarations up
|
|
260
|
+
var newBody = [];
|
|
261
|
+
|
|
262
|
+
for (var stmt of body) {
|
|
263
|
+
if (stmt.type === "FunctionDeclaration") {
|
|
264
|
+
newBody.unshift(stmt);
|
|
265
|
+
} else {
|
|
266
|
+
newBody.push(stmt);
|
|
267
|
+
}
|
|
275
268
|
}
|
|
276
269
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
270
|
+
body = newBody;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
body.forEach((stmt, i) => {
|
|
274
|
+
if (stmt.type === "ImportDeclaration") {
|
|
275
|
+
// The 'importDeclarations' hold statements that are required to be left untouched at the top of the block
|
|
276
|
+
importDeclarations.push(stmt);
|
|
281
277
|
return;
|
|
282
|
-
}
|
|
278
|
+
} else if (stmt.type === "FunctionDeclaration") {
|
|
279
|
+
var functionName = stmt.id.name;
|
|
280
|
+
stmt.type = "FunctionExpression";
|
|
281
|
+
stmt.id = null;
|
|
282
|
+
functionDeclarationNames.add(functionName);
|
|
283
|
+
|
|
284
|
+
if (objectBody === body) {
|
|
285
|
+
functionDeclarationValues.set(functionName, stmt);
|
|
286
|
+
return;
|
|
287
|
+
} else {
|
|
288
|
+
currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(functionName), stmt)));
|
|
289
|
+
}
|
|
283
290
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
// var a = fn();
|
|
291
|
-
case "VariableDeclarator":
|
|
292
|
-
args = stmt.declarations[0].init.arguments;
|
|
293
|
-
stmt.declarations[0].init = (0, _gen.Identifier)(resultVar);
|
|
294
|
-
break;
|
|
295
|
-
// fn();
|
|
296
|
-
|
|
297
|
-
case "ExpressionStatement":
|
|
298
|
-
args = stmt.expression.arguments;
|
|
299
|
-
stmt.expression = (0, _gen.Identifier)("undefined");
|
|
300
|
-
break;
|
|
301
|
-
// a = fn();
|
|
302
|
-
|
|
303
|
-
case "AssignmentExpression":
|
|
304
|
-
args = stmt.expression.right.arguments;
|
|
305
|
-
stmt.expression.right = (0, _gen.Identifier)(resultVar);
|
|
306
|
-
break;
|
|
291
|
+
return;
|
|
292
|
+
} else if (stmt.directive) {
|
|
293
|
+
if (objectBody === body) {
|
|
294
|
+
importDeclarations.push(stmt);
|
|
295
|
+
} else {
|
|
296
|
+
this.error(new Error("Unimplemented directive support."));
|
|
307
297
|
}
|
|
308
298
|
|
|
309
|
-
|
|
310
|
-
currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(argVar), (0, _gen.ArrayExpression)([{
|
|
311
|
-
type: "StateIdentifier",
|
|
312
|
-
label: afterPath
|
|
313
|
-
}, (0, _gen.ArrayExpression)(args)]))));
|
|
314
|
-
finishCurrentChunk(fnToLabel[stmt.$fnName], afterPath);
|
|
299
|
+
return;
|
|
315
300
|
}
|
|
316
301
|
|
|
317
302
|
if (stmt.type == "GotoStatement" && i !== body.length - 1) {
|
|
318
303
|
finishCurrentChunk(stmt.label);
|
|
319
304
|
return;
|
|
320
|
-
}
|
|
305
|
+
} // The Preparation transform adds labels to every Control-Flow node
|
|
321
306
|
|
|
322
|
-
|
|
307
|
+
|
|
308
|
+
if (this.flattenControlStructures && stmt.type == "LabeledStatement") {
|
|
323
309
|
var lbl = stmt.label.name;
|
|
324
310
|
var control = stmt.body;
|
|
325
311
|
var isSwitchStatement = control.type === "SwitchStatement";
|
|
326
312
|
|
|
327
313
|
if (isSwitchStatement || (control.type == "ForStatement" || control.type == "WhileStatement" || control.type == "DoWhileStatement") && control.body.type == "BlockStatement") {
|
|
328
314
|
if (isSwitchStatement) {
|
|
329
|
-
if (control.cases.length == 0
|
|
330
|
-
control.cases.find(x => !x.test || // cant be default case
|
|
331
|
-
!x.consequent.length || // must have body
|
|
332
|
-
x.consequent.findIndex(node => node.type == "BreakStatement") !== x.consequent.length - 1 || // break statement must be at the end
|
|
333
|
-
x.consequent[x.consequent.length - 1].type !== // must end with break statement
|
|
334
|
-
"BreakStatement" || !x.consequent[x.consequent.length - 1].label || // must be labeled and correct
|
|
335
|
-
x.consequent[x.consequent.length - 1].label.name != lbl)) {
|
|
315
|
+
if (control.cases.length == 0) {
|
|
336
316
|
currentBody.push(stmt);
|
|
337
317
|
return;
|
|
338
318
|
}
|
|
@@ -340,20 +320,20 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
340
320
|
|
|
341
321
|
var isLoop = !isSwitchStatement;
|
|
342
322
|
var supportContinueStatement = isLoop;
|
|
323
|
+
var testPath = this.getPlaceholder();
|
|
324
|
+
var updatePath = this.getPlaceholder();
|
|
325
|
+
var bodyPath = this.getPlaceholder();
|
|
326
|
+
var afterPath = this.getPlaceholder();
|
|
327
|
+
var possible = true;
|
|
328
|
+
var toReplace = []; // Find all break; and continue; statements and change them into 'GotoStatement's
|
|
343
329
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
var afterPath = _this.getPlaceholder();
|
|
330
|
+
(0, _traverse.walk)(control.body || control.cases, [], (o, p) => {
|
|
331
|
+
if (o.type === "BreakStatement" || o.type === "ContinueStatement") {
|
|
332
|
+
var allowedLabels = new Set(p.filter(x => x.type === "LabeledStatement" && x.body.type === "SwitchStatement").map(x => x.label.name));
|
|
333
|
+
var isUnsupportedContinue = !supportContinueStatement && o.type === "ContinueStatement";
|
|
334
|
+
var isInvalidLabel = !o.label || o.label.name !== lbl && !allowedLabels.has(o.label.name); // This seems like the best solution:
|
|
351
335
|
|
|
352
|
-
|
|
353
|
-
var toReplace = [];
|
|
354
|
-
(0, _traverse.walk)(control.body, [], (o, p) => {
|
|
355
|
-
if (o.type == "BreakStatement" || supportContinueStatement && o.type == "ContinueStatement") {
|
|
356
|
-
if (!o.label || o.label.name !== lbl) {
|
|
336
|
+
if (isUnsupportedContinue || isInvalidLabel) {
|
|
357
337
|
possible = false;
|
|
358
338
|
return "EXIT";
|
|
359
339
|
}
|
|
@@ -374,33 +354,57 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
374
354
|
return;
|
|
375
355
|
}
|
|
376
356
|
|
|
377
|
-
toReplace.forEach(v =>
|
|
357
|
+
toReplace.forEach(v => this.replace(v[0], v[1]));
|
|
378
358
|
|
|
379
359
|
if (isSwitchStatement) {
|
|
380
|
-
var
|
|
360
|
+
var switchDiscriminantName = this.getPlaceholder() + "_switchD"; // Stores the value of the discriminant
|
|
381
361
|
|
|
382
|
-
|
|
362
|
+
var switchTestName = this.getPlaceholder() + "_switchT"; // Set to true when a Switch case is matched
|
|
383
363
|
|
|
384
|
-
|
|
364
|
+
currentBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(switchDiscriminantName, control.discriminant)));
|
|
365
|
+
currentBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(switchTestName, (0, _gen.Literal)(false)))); // case labels are:
|
|
366
|
+
// `${caseLabelPrefix}_test_${index}`
|
|
367
|
+
// `${caseLabelPrefix}_entry_${index}`
|
|
385
368
|
|
|
386
|
-
|
|
369
|
+
var caseLabelPrefix = this.getPlaceholder();
|
|
370
|
+
var defaultCaseIndex = control.cases.findIndex(x => x.test === null);
|
|
387
371
|
control.cases.forEach((switchCase, i) => {
|
|
388
|
-
var
|
|
389
|
-
|
|
390
|
-
|
|
372
|
+
var testPath = caseLabelPrefix + "_test_" + i;
|
|
373
|
+
var entryPath = caseLabelPrefix + "_entry_" + i;
|
|
374
|
+
var nextEntryPath = i === control.cases.length - 1 // Last path goes to afterPath
|
|
375
|
+
? afterPath // Else go to next entry path (fall-through behavior)
|
|
376
|
+
: caseLabelPrefix + "_entry_" + (i + 1);
|
|
377
|
+
var nextTestPath = i === control.cases.length - 1 ? afterPath : caseLabelPrefix + "_test_" + (i + 1);
|
|
378
|
+
finishCurrentChunk(testPath, testPath, i == 0);
|
|
379
|
+
|
|
380
|
+
if (switchCase.test) {
|
|
381
|
+
// Check the case condition and goto statement
|
|
382
|
+
currentBody.push((0, _gen.IfStatement)((0, _gen.BinaryExpression)("===", (0, _gen.Identifier)(switchDiscriminantName), switchCase.test), [(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(switchTestName), (0, _gen.Literal)(true))), {
|
|
383
|
+
type: "GotoStatement",
|
|
384
|
+
label: entryPath
|
|
385
|
+
}]));
|
|
386
|
+
} else {// Default case: No test needed.
|
|
387
|
+
} // If default case, on last test, if no case was matched, goto default case
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if (i === control.cases.length - 1 && defaultCaseIndex !== -1) {
|
|
391
|
+
currentBody.push((0, _gen.IfStatement)((0, _gen.UnaryExpression)("!", (0, _gen.Identifier)(switchTestName)), [{
|
|
392
|
+
type: "GotoStatement",
|
|
393
|
+
label: caseLabelPrefix + "_entry_" + defaultCaseIndex
|
|
394
|
+
}]));
|
|
395
|
+
} // Jump to next test
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
currentBody.push({
|
|
391
399
|
type: "GotoStatement",
|
|
392
|
-
label:
|
|
393
|
-
}
|
|
394
|
-
chunks.push(...flattenBody([...switchCase.consequent
|
|
400
|
+
label: nextTestPath
|
|
401
|
+
});
|
|
402
|
+
chunks.push(...flattenBody([...switchCase.consequent, {
|
|
395
403
|
type: "GotoStatement",
|
|
396
|
-
label:
|
|
404
|
+
label: nextEntryPath
|
|
397
405
|
}], entryPath));
|
|
398
|
-
|
|
399
|
-
if (i === control.cases.length - 1) {} else {
|
|
400
|
-
finishCurrentChunk();
|
|
401
|
-
}
|
|
402
406
|
});
|
|
403
|
-
finishCurrentChunk(afterPath, afterPath);
|
|
407
|
+
finishCurrentChunk(afterPath, afterPath, false);
|
|
404
408
|
return;
|
|
405
409
|
} else if (isLoop) {
|
|
406
410
|
var isPostTest = control.type == "DoWhileStatement"; // add initializing section to current chunk
|
|
@@ -415,18 +419,15 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
415
419
|
|
|
416
420
|
|
|
417
421
|
finishCurrentChunk(isPostTest ? bodyPath : testPath, testPath);
|
|
418
|
-
currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (
|
|
419
|
-
needsTestVar = true;
|
|
422
|
+
currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(controlTestKey), control.test || (0, _gen.Literal)(true))));
|
|
420
423
|
finishCurrentChunk();
|
|
421
|
-
currentBody.push((0, _gen.IfStatement)((
|
|
424
|
+
currentBody.push((0, _gen.IfStatement)(getControlMember(controlTestKey), [{
|
|
422
425
|
type: "GotoStatement",
|
|
423
426
|
label: bodyPath
|
|
424
427
|
}])); // create new label called `bodyPath` and have test body point to afterPath (goto afterPath)
|
|
425
428
|
|
|
426
429
|
finishCurrentChunk(afterPath, bodyPath);
|
|
427
|
-
|
|
428
|
-
var innerBothPath = _this.getPlaceholder();
|
|
429
|
-
|
|
430
|
+
var innerBothPath = this.getPlaceholder();
|
|
430
431
|
chunks.push(...flattenBody([...control.body.body, {
|
|
431
432
|
type: "GotoStatement",
|
|
432
433
|
label: updatePath
|
|
@@ -443,21 +444,16 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
443
444
|
}
|
|
444
445
|
}
|
|
445
446
|
|
|
446
|
-
if (stmt.type == "IfStatement" && stmt.consequent.type == "BlockStatement" && (!stmt.alternate || stmt.alternate.type == "BlockStatement")) {
|
|
447
|
+
if (this.flattenControlStructures && stmt.type == "IfStatement" && stmt.consequent.type == "BlockStatement" && (!stmt.alternate || stmt.alternate.type == "BlockStatement")) {
|
|
447
448
|
finishCurrentChunk();
|
|
448
|
-
currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (
|
|
449
|
-
needsTestVar = true;
|
|
449
|
+
currentBody.push((0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(controlTestKey), stmt.test)));
|
|
450
450
|
finishCurrentChunk();
|
|
451
451
|
var hasAlternate = !!stmt.alternate;
|
|
452
452
|
(0, _assert.ok)(!(hasAlternate && stmt.alternate.type !== "BlockStatement"));
|
|
453
|
-
|
|
454
|
-
var
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
var afterPath = _this.getPlaceholder();
|
|
459
|
-
|
|
460
|
-
currentBody.push((0, _gen.IfStatement)((0, _gen.Identifier)(testVar), [{
|
|
453
|
+
var yesPath = this.getPlaceholder();
|
|
454
|
+
var noPath = this.getPlaceholder();
|
|
455
|
+
var afterPath = this.getPlaceholder();
|
|
456
|
+
currentBody.push((0, _gen.IfStatement)(getControlMember(controlTestKey), [{
|
|
461
457
|
type: "GotoStatement",
|
|
462
458
|
label: yesPath
|
|
463
459
|
}]));
|
|
@@ -479,7 +475,7 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
479
475
|
return;
|
|
480
476
|
}
|
|
481
477
|
|
|
482
|
-
if (!currentBody.length ||
|
|
478
|
+
if (!currentBody.length || !(0, _random.chance)(splitPercent)) {
|
|
483
479
|
currentBody.push(stmt);
|
|
484
480
|
} else {
|
|
485
481
|
// Start new chunk
|
|
@@ -491,43 +487,35 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
491
487
|
chunks[chunks.length - 1].body.pop();
|
|
492
488
|
return chunks;
|
|
493
489
|
};
|
|
494
|
-
|
|
495
|
-
var chunks = [];
|
|
496
490
|
/**
|
|
497
|
-
*
|
|
491
|
+
* Executable code segments are broken down into `chunks` typically 1-3 statements each
|
|
492
|
+
*
|
|
493
|
+
* Chunked Code has a special `GotoStatement` node that get processed later on
|
|
494
|
+
* This allows more complex control structures like `IfStatement`s and `ForStatement`s to be converted into basic
|
|
495
|
+
* conditional jumps and flattened in the switch body
|
|
496
|
+
*
|
|
497
|
+
* IfStatement would be converted like this:
|
|
498
|
+
*
|
|
499
|
+
* MAIN:
|
|
500
|
+
* if ( TEST ) {
|
|
501
|
+
* GOTO consequent_label;
|
|
502
|
+
* } else? {
|
|
503
|
+
* GOTO alternate_label;
|
|
504
|
+
* }
|
|
505
|
+
* GOTO NEXT_CHUNK;
|
|
498
506
|
*/
|
|
499
507
|
|
|
500
|
-
var switchLabel = this.getPlaceholder();
|
|
501
|
-
functionDeclarations.forEach(node => {
|
|
502
|
-
if (node.id && fnNames.has(node.id.name)) {
|
|
503
|
-
var exitStateName = this.getPlaceholder();
|
|
504
|
-
var argumentsName = this.getPlaceholder();
|
|
505
|
-
needsResultAndArgVar = true;
|
|
506
|
-
node.body.body.push((0, _gen.ReturnStatement)());
|
|
507
|
-
(0, _traverse.walk)(node.body, [], (o, p) => {
|
|
508
|
-
if (o.type == "ReturnStatement") {
|
|
509
|
-
if (!(0, _insert.getFunction)(o, p)) {
|
|
510
|
-
return () => {
|
|
511
|
-
var exitExpr = (0, _gen.SequenceExpression)([(0, _gen.AssignmentExpression)("=", (0, _gen.ArrayPattern)(stateVars.map(_gen.Identifier)), (0, _gen.Identifier)(exitStateName)), (0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(resultVar), o.argument || (0, _gen.Identifier)("undefined"))]);
|
|
512
|
-
this.replace(o, (0, _gen.ReturnStatement)(exitExpr));
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
});
|
|
517
|
-
var declarations = [(0, _gen.VariableDeclarator)((0, _gen.ArrayPattern)([(0, _gen.Identifier)(exitStateName), (0, _gen.Identifier)(argumentsName)]), (0, _gen.Identifier)(argVar))];
|
|
518
508
|
|
|
519
|
-
|
|
520
|
-
declarations.push((0, _gen.VariableDeclarator)((0, _gen.ArrayPattern)(node.params), (0, _gen.Identifier)(argumentsName)));
|
|
521
|
-
}
|
|
509
|
+
const chunks = []; // Flagged labels have addition code protecting the control state
|
|
522
510
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
chunks.push(...flattenBody(
|
|
511
|
+
const flaggedLabels = Object.create(null);
|
|
512
|
+
/**
|
|
513
|
+
* label: switch(a+b+c){...break label...}
|
|
514
|
+
*/
|
|
515
|
+
|
|
516
|
+
const switchLabel = this.getPlaceholder();
|
|
517
|
+
const startLabel = this.getPlaceholder();
|
|
518
|
+
chunks.push(...flattenBody(objectBody, startLabel));
|
|
531
519
|
chunks[chunks.length - 1].body.push({
|
|
532
520
|
type: "GotoStatement",
|
|
533
521
|
label: "END_LABEL"
|
|
@@ -536,9 +524,63 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
536
524
|
label: "END_LABEL",
|
|
537
525
|
body: []
|
|
538
526
|
});
|
|
527
|
+
const endLabel = chunks[Object.keys(chunks).length - 1].label;
|
|
528
|
+
|
|
529
|
+
if (!this.isDebug && this.addDeadCode) {
|
|
530
|
+
// DEAD CODE 1/3: Add fake chunks that are never reached
|
|
531
|
+
var fakeChunkCount = (0, _random.getRandomInteger)(1, 5);
|
|
532
|
+
|
|
533
|
+
for (var i = 0; i < fakeChunkCount; i++) {
|
|
534
|
+
// These chunks just jump somewhere random, they are never executed
|
|
535
|
+
// so it could contain any code
|
|
536
|
+
var fakeChunkBody = [// This a fake assignment expression
|
|
537
|
+
(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)((0, _random.choice)(stateVars)), (0, _gen.Literal)((0, _random.getRandomInteger)(-150, 150)))), {
|
|
538
|
+
type: "GotoStatement",
|
|
539
|
+
label: (0, _random.choice)(chunks).label
|
|
540
|
+
}];
|
|
541
|
+
chunks.push({
|
|
542
|
+
label: this.getPlaceholder(),
|
|
543
|
+
body: fakeChunkBody,
|
|
544
|
+
impossible: true
|
|
545
|
+
});
|
|
546
|
+
} // DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
chunks.forEach(chunk => {
|
|
550
|
+
if ((0, _random.chance)(25)) {
|
|
551
|
+
var randomLabel = (0, _random.choice)(chunks).label; // The `false` literal will be mangled
|
|
552
|
+
|
|
553
|
+
chunk.body.unshift((0, _gen.IfStatement)((0, _gen.Literal)(false), [{
|
|
554
|
+
type: "GotoStatement",
|
|
555
|
+
label: randomLabel,
|
|
556
|
+
impossible: true
|
|
557
|
+
}]));
|
|
558
|
+
}
|
|
559
|
+
}); // DEAD CODE 3/3: Clone chunks but these chunks are never ran
|
|
560
|
+
|
|
561
|
+
var cloneChunkCount = (0, _random.getRandomInteger)(1, 5);
|
|
562
|
+
|
|
563
|
+
for (var i = 0; i < cloneChunkCount; i++) {
|
|
564
|
+
var randomChunk = (0, _random.choice)(chunks);
|
|
565
|
+
var clonedChunk = {
|
|
566
|
+
body: (0, _insert.clone)(randomChunk.body),
|
|
567
|
+
label: this.getPlaceholder(),
|
|
568
|
+
impossible: true
|
|
569
|
+
}; // Don't double define functions
|
|
570
|
+
|
|
571
|
+
var hasDeclaration = clonedChunk.body.find(stmt => {
|
|
572
|
+
return stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration";
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
if (!hasDeclaration) {
|
|
576
|
+
chunks.unshift(clonedChunk);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
} // Generate a unique 'state' number for each chunk
|
|
580
|
+
|
|
581
|
+
|
|
539
582
|
var caseSelection = new Set();
|
|
540
583
|
var uniqueStatesNeeded = chunks.length;
|
|
541
|
-
var endLabel = chunks[Object.keys(chunks).length - 1].label;
|
|
542
584
|
|
|
543
585
|
do {
|
|
544
586
|
var newState = (0, _random.getRandomInteger)(1, chunks.length * 15);
|
|
@@ -567,9 +609,11 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
567
609
|
*/
|
|
568
610
|
|
|
569
611
|
var labelToStates = Object.create(null);
|
|
612
|
+
var lastLabel;
|
|
570
613
|
Object.values(chunks).forEach((chunk, i) => {
|
|
571
614
|
var state = caseStates[i];
|
|
572
|
-
var stateValues = Array(stateVars.length).fill(0).map(() => (0, _random.
|
|
615
|
+
var stateValues = Array(stateVars.length).fill(0).map((_, i) => lastLabel && (0, _random.chance)(95) // Try to make state changes not as drastic (If last label, re-use some of it's values)
|
|
616
|
+
? labelToStates[lastLabel][i] : (0, _random.getRandomInteger)(-500, 500));
|
|
573
617
|
|
|
574
618
|
const getCurrentState = () => {
|
|
575
619
|
return stateValues.reduce((a, b) => b + a, 0);
|
|
@@ -578,53 +622,368 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
578
622
|
var correctIndex = (0, _random.getRandomInteger)(0, stateValues.length);
|
|
579
623
|
stateValues[correctIndex] = state - (getCurrentState() - stateValues[correctIndex]);
|
|
580
624
|
labelToStates[chunk.label] = stateValues;
|
|
581
|
-
|
|
582
|
-
|
|
625
|
+
lastLabel = chunk.label;
|
|
626
|
+
});
|
|
583
627
|
var initStateValues = [...labelToStates[startLabel]];
|
|
584
|
-
var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0);
|
|
628
|
+
var endState = labelToStates[endLabel].reduce((a, b) => b + a, 0); // Creates a predicate based on the state-variables and control-object properties
|
|
629
|
+
|
|
630
|
+
const createPredicate = stateValues => {
|
|
631
|
+
this.mangledExpressionsMade++;
|
|
632
|
+
var index = (0, _random.getRandomInteger)(0, stateVars.length);
|
|
633
|
+
var compareValue = (0, _random.choice)([stateValues[index], (0, _random.getRandomInteger)(-100, 100)]); // 'state equality' test
|
|
634
|
+
|
|
635
|
+
var test = (0, _gen.BinaryExpression)("==", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(compareValue, stateValues));
|
|
636
|
+
var testValue = stateValues[index] === compareValue; // 'control' equality test
|
|
637
|
+
|
|
638
|
+
if (controlConstantMap.size && (0, _random.chance)(50)) {
|
|
639
|
+
var _controlConstantMap$g;
|
|
640
|
+
|
|
641
|
+
// The controlMap maps LITERAL-values to STRING property names
|
|
642
|
+
var actualValue = (0, _random.choice)(Array.from(controlConstantMap.keys()));
|
|
643
|
+
var controlKey = (_controlConstantMap$g = controlConstantMap.get(actualValue)) === null || _controlConstantMap$g === void 0 ? void 0 : _controlConstantMap$g.key;
|
|
644
|
+
var controlCompareValue = (0, _random.choice)([actualValue, stateValues[index], (0, _random.getRandomInteger)(-100, 100), controlGen.generate()]); // 'control equality' test
|
|
645
|
+
|
|
646
|
+
test = (0, _gen.BinaryExpression)("==", getControlMember(controlKey), (0, _gen.Literal)(controlCompareValue));
|
|
647
|
+
testValue = actualValue == controlCompareValue; // 'control typeof' test
|
|
585
648
|
|
|
586
|
-
|
|
587
|
-
|
|
649
|
+
if ((0, _random.chance)(10)) {
|
|
650
|
+
var compareTypeofValue = (0, _random.choice)(["number", "string", "object", "function", "undefined"]);
|
|
651
|
+
test = (0, _gen.BinaryExpression)("==", (0, _gen.UnaryExpression)("typeof", getControlMember(controlKey)), (0, _gen.Literal)(compareTypeofValue));
|
|
652
|
+
testValue = typeof actualValue === compareTypeofValue;
|
|
653
|
+
} // 'control hasOwnProperty' test
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
if ((0, _random.chance)(10)) {
|
|
657
|
+
var hasOwnProperty = (0, _random.choice)([controlKey, controlGen.generate()]);
|
|
658
|
+
test = (0, _gen.CallExpression)((0, _gen.MemberExpression)((0, _gen.Identifier)(controlVar), (0, _gen.Literal)("hasOwnProperty"), true), [(0, _gen.Literal)(hasOwnProperty)]);
|
|
659
|
+
testValue = hasOwnProperty === controlKey;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return {
|
|
664
|
+
test,
|
|
665
|
+
testValue
|
|
666
|
+
};
|
|
667
|
+
}; // A "state-less" number literal is a Number Literal that is mangled in with the Control properties.
|
|
668
|
+
// Example: X = CONTROL.Y + Z. These can be used anywhere because control properties are constant (unlike state variables)
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
const createStatelessNumberLiteral = function (num) {
|
|
672
|
+
var _selectedControlConst2;
|
|
673
|
+
|
|
674
|
+
let depth = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
675
|
+
|
|
676
|
+
if (!controlConstantMap.size || depth > 4 || (0, _random.chance)(75 + depth * 5 + _this.mangledExpressionsMade / 25)) {
|
|
677
|
+
// Add to control constant map?
|
|
678
|
+
if ((0, _random.chance)(25 - controlConstantMap.size - _this.mangledExpressionsMade / 100)) {
|
|
679
|
+
return addControlMapConstant(num);
|
|
680
|
+
}
|
|
588
681
|
|
|
589
|
-
if (depth > 10 || Math.random() > 0.8 / (depth * 4)) {
|
|
590
682
|
return (0, _gen.Literal)(num);
|
|
591
683
|
}
|
|
592
684
|
|
|
685
|
+
_this.mangledExpressionsMade++;
|
|
686
|
+
|
|
687
|
+
if (controlConstantMap.has(num)) {
|
|
688
|
+
var _controlConstantMap$g2;
|
|
689
|
+
|
|
690
|
+
return getControlMember((_controlConstantMap$g2 = controlConstantMap.get(num)) === null || _controlConstantMap$g2 === void 0 ? void 0 : _controlConstantMap$g2.key);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
var allControlNodes = [object];
|
|
694
|
+
parents.filter(x => x.$controlVar && x.$controlConstantMap.size > 0).forEach(node => allControlNodes.push(node));
|
|
695
|
+
var controlNode = (0, _random.choice)(allControlNodes);
|
|
696
|
+
var selectedControlConstantMap = controlNode.$controlConstantMap;
|
|
697
|
+
var selectedControlVar = controlNode.$controlVar;
|
|
698
|
+
var actualValue = (0, _random.choice)(Array.from(selectedControlConstantMap.keys()));
|
|
699
|
+
var controlKey = (_selectedControlConst2 = selectedControlConstantMap.get(actualValue)) === null || _selectedControlConst2 === void 0 ? void 0 : _selectedControlConst2.key;
|
|
700
|
+
|
|
701
|
+
if (typeof actualValue === "number") {
|
|
702
|
+
var difference = actualValue - num;
|
|
703
|
+
return (0, _gen.BinaryExpression)("-", getControlMember(controlKey, selectedControlVar), createStatelessNumberLiteral(difference, depth + 1));
|
|
704
|
+
} else if (typeof actualValue === "string") {
|
|
705
|
+
// 'control string length' test
|
|
706
|
+
var compareValue = (0, _random.choice)([actualValue.length, (0, _random.getRandomInteger)(0, 50)]);
|
|
707
|
+
var test = (0, _gen.BinaryExpression)("==", (0, _gen.MemberExpression)(getControlMember(controlKey, selectedControlVar), (0, _gen.Literal)("length"), true), createStatelessNumberLiteral(compareValue, depth + 1));
|
|
708
|
+
var testValue = actualValue.length == compareValue;
|
|
709
|
+
var consequent = createStatelessNumberLiteral(num, depth + 1);
|
|
710
|
+
var alternate = (0, _gen.Literal)((0, _random.getRandomInteger)(-100, 100));
|
|
711
|
+
return (0, _gen.ConditionalExpression)(test, testValue ? consequent : alternate, !testValue ? consequent : alternate);
|
|
712
|
+
} else {
|
|
713
|
+
throw new Error("Unknown: " + typeof actualValue);
|
|
714
|
+
}
|
|
715
|
+
}; // A "state-bound" number literal is a Number Literal that is mangled in with the current state variables
|
|
716
|
+
// Example: X = STATE + Y. This can only be used when the state-values are guaranteed to be known.
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
const createStateBoundNumberLiteral = function (num, stateValues) {
|
|
720
|
+
let depth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
|
|
721
|
+
(0, _assert.ok)(Array.isArray(stateValues)); // Base case: After 4 depth, OR random chance
|
|
722
|
+
|
|
723
|
+
if (depth > 4 || (0, _random.chance)(75 + depth * 5 + _this.mangledExpressionsMade / 25)) {
|
|
724
|
+
// Add this number to the control object?
|
|
725
|
+
// Add to control constant map?
|
|
726
|
+
if ((0, _random.chance)(25 - controlConstantMap.size)) {
|
|
727
|
+
return addControlMapConstant(num);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
return (0, _gen.Literal)(num);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
_this.mangledExpressionsMade++;
|
|
734
|
+
|
|
735
|
+
if ((0, _random.chance)(10)) {
|
|
736
|
+
return createStatelessNumberLiteral(num, depth + 1);
|
|
737
|
+
} // Terminated predicate
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
if ((0, _random.chance)(50)) {
|
|
741
|
+
var {
|
|
742
|
+
test,
|
|
743
|
+
testValue
|
|
744
|
+
} = createPredicate(stateValues);
|
|
745
|
+
var alternateNode = (0, _random.choice)([(0, _gen.Literal)((0, _random.getRandomInteger)(-100, 100)), (0, _gen.Literal)(controlGen.generate()), getControlMember(controlGen.generate())]);
|
|
746
|
+
return (0, _gen.ConditionalExpression)(test, testValue ? (0, _gen.Literal)(num) : alternateNode, !testValue ? (0, _gen.Literal)(num) : alternateNode);
|
|
747
|
+
} // Recursive predicate
|
|
748
|
+
|
|
749
|
+
|
|
593
750
|
var opposing = (0, _random.getRandomInteger)(0, stateVars.length);
|
|
594
751
|
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
var
|
|
598
|
-
var
|
|
599
|
-
var
|
|
600
|
-
|
|
601
|
-
|
|
752
|
+
if ((0, _random.chance)(10)) {
|
|
753
|
+
// state > compare ? real : fake
|
|
754
|
+
var compareValue = (0, _random.choice)([stateValues[opposing], (0, _random.getRandomInteger)(-150, 150)]);
|
|
755
|
+
var operator = (0, _random.choice)(["<", ">", "==", "!="]);
|
|
756
|
+
var answer = {
|
|
757
|
+
">": compareValue > stateValues[opposing],
|
|
758
|
+
"<": compareValue < stateValues[opposing],
|
|
759
|
+
"==": compareValue === stateValues[opposing],
|
|
760
|
+
"!=": compareValue !== stateValues[opposing]
|
|
761
|
+
}[operator];
|
|
762
|
+
var correct = createStateBoundNumberLiteral(num, stateValues, depth + 1);
|
|
763
|
+
var incorrect = createStateBoundNumberLiteral((0, _random.getRandomInteger)(-150, 150), stateValues, depth + 1);
|
|
764
|
+
return (0, _gen.ConditionalExpression)((0, _gen.BinaryExpression)(operator, createStateBoundNumberLiteral(compareValue, stateValues, depth + 1), (0, _gen.Identifier)(stateVars[opposing])), answer ? correct : incorrect, answer ? incorrect : correct);
|
|
765
|
+
} // state + 10 = <REAL>
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
var difference = num - stateValues[opposing];
|
|
769
|
+
|
|
770
|
+
if (difference === 0) {
|
|
771
|
+
return (0, _gen.Identifier)(stateVars[opposing]);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return (0, _gen.BinaryExpression)("+", (0, _gen.Identifier)(stateVars[opposing]), createStateBoundNumberLiteral(difference, stateValues, depth + 1));
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
var outlinesCreated = 0;
|
|
778
|
+
|
|
779
|
+
const isExpression = (object, parents) => {
|
|
780
|
+
var fnIndex = parents.findIndex(x => (0, _insert.isFunction)(x));
|
|
781
|
+
|
|
782
|
+
if (fnIndex != -1) {
|
|
783
|
+
// This does NOT mutate
|
|
784
|
+
parents = parents.slice(0, fnIndex);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
var assignmentIndex = parents.findIndex(x => x.type === "AssignmentExpression"); // Left-hand assignment validation
|
|
788
|
+
|
|
789
|
+
if (assignmentIndex != -1) {
|
|
790
|
+
if (parents[assignmentIndex].left === (parents[assignmentIndex - 1] || object)) {
|
|
791
|
+
return false;
|
|
792
|
+
}
|
|
793
|
+
} // For in/of left validation
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
var forInOfIndex = parents.findIndex(x => x.type === "ForInStatement" || x.type === "ForOfStatement");
|
|
797
|
+
|
|
798
|
+
if (forInOfIndex != -1) {
|
|
799
|
+
if (parents[forInOfIndex].left === (parents[forInOfIndex - 1] || object)) {
|
|
800
|
+
return false;
|
|
801
|
+
}
|
|
802
|
+
} // Bound call-expression validation
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
var callExpressionIndex = parents.findIndex(x => x.type === "CallExpression");
|
|
806
|
+
|
|
807
|
+
if (callExpressionIndex != -1) {
|
|
808
|
+
if (parents[callExpressionIndex].callee == (parents[callExpressionIndex - 1] || object)) {
|
|
809
|
+
var callee = parents[callExpressionIndex].callee; // Detected bound call expression. Not supported.
|
|
810
|
+
|
|
811
|
+
if (callee.type === "MemberExpression") {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
} // Update-expression validation:
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
var updateExpressionIndex = parents.findIndex(x => x.type === "UpdateExpression");
|
|
819
|
+
if (updateExpressionIndex !== -1) return false;
|
|
820
|
+
return true;
|
|
821
|
+
}; // This function checks if the expression or statements is possible to be outlined
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
const canOutline = (object, parents) => {
|
|
825
|
+
var isIllegal = false;
|
|
826
|
+
var breakStatements = [];
|
|
827
|
+
var returnStatements = [];
|
|
828
|
+
|
|
829
|
+
if (!Array.isArray(object) && !isExpression(object, parents)) {
|
|
830
|
+
return {
|
|
831
|
+
isIllegal: true,
|
|
832
|
+
breakStatements: [],
|
|
833
|
+
returnStatements: []
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
(0, _traverse.walk)(object, parents, (o, p) => {
|
|
838
|
+
if (o.type === "ThisExpression" || o.type === "MetaProperty" || o.type === "Super") {
|
|
839
|
+
isIllegal = true;
|
|
840
|
+
return "EXIT";
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (o.type === "BreakStatement") {
|
|
844
|
+
// This can be safely outlined
|
|
845
|
+
if (o.label && o.label.name === switchLabel) {
|
|
846
|
+
breakStatements.push([o, p]);
|
|
847
|
+
} else {
|
|
848
|
+
isIllegal = true;
|
|
849
|
+
return "EXIT";
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
if ((o.type === "ContinueStatement" || o.type === "AwaitExpression" || o.type === "YieldExpression" || o.type === "ReturnStatement" || o.type === "VariableDeclaration" || o.type === "FunctionDeclaration" || o.type === "ClassDeclaration") && !p.find(x => (0, _insert.isVarContext)(x))) {
|
|
854
|
+
// This can be safely outlined
|
|
855
|
+
if (o.type === "ReturnStatement") {
|
|
856
|
+
returnStatements.push([o, p]);
|
|
857
|
+
} else {
|
|
858
|
+
isIllegal = true;
|
|
859
|
+
return "EXIT";
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (o.type === "Identifier") {
|
|
864
|
+
if (o.name === "arguments") {
|
|
865
|
+
isIllegal = true;
|
|
866
|
+
return "EXIT";
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
return {
|
|
871
|
+
isIllegal,
|
|
872
|
+
breakStatements,
|
|
873
|
+
returnStatements
|
|
874
|
+
};
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
const createOutlineFunction = (body, stateValues, label) => {
|
|
878
|
+
var key = controlGen.generate();
|
|
879
|
+
var functionExpression = (0, _gen.FunctionExpression)([], body);
|
|
880
|
+
|
|
881
|
+
if (!this.options.es5 && (0, _random.chance)(50)) {
|
|
882
|
+
functionExpression.type = "ArrowFunctionExpression";
|
|
602
883
|
}
|
|
603
884
|
|
|
604
|
-
|
|
885
|
+
controlProperties.push((0, _gen.Property)((0, _gen.Literal)(key), functionExpression, false)); // Add dead code to function
|
|
886
|
+
|
|
887
|
+
if (!this.isDebug && (0, _random.chance)(25)) {
|
|
888
|
+
var {
|
|
889
|
+
test,
|
|
890
|
+
testValue
|
|
891
|
+
} = createPredicate(stateValues);
|
|
892
|
+
var deadCodeVar = this.getPlaceholder();
|
|
893
|
+
functionExpression.params.push((0, _gen.AssignmentPattern)((0, _gen.Identifier)(deadCodeVar), test));
|
|
894
|
+
var alternate = [(0, _gen.ReturnStatement)((0, _random.choice)([(0, _gen.BinaryExpression)("==", (0, _gen.Identifier)((0, _random.choice)(stateVars)), (0, _gen.Literal)((0, _random.getRandomInteger)(-100, 100))), (0, _gen.Literal)(controlGen.generate()), (0, _gen.Identifier)("arguments"), (0, _gen.Identifier)((0, _random.choice)(stateVars)), (0, _gen.Identifier)(controlVar), (0, _gen.CallExpression)(getControlMember(controlGen.generate()), [])]))];
|
|
895
|
+
functionExpression.body.body.unshift((0, _gen.IfStatement)(testValue ? (0, _gen.UnaryExpression)("!", (0, _gen.Identifier)(deadCodeVar)) : (0, _gen.Identifier)(deadCodeVar), alternate));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
outlinesCreated++;
|
|
899
|
+
return key;
|
|
605
900
|
};
|
|
606
901
|
|
|
607
|
-
const
|
|
902
|
+
const attemptOutlineStatements = (statements, parentBlock, stateValues, label) => {
|
|
903
|
+
if (this.isDebug || !this.outlineStatements || (0, _random.chance)(75 + outlinesCreated - this.mangledExpressionsMade / 25)) {
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
var index = parentBlock.indexOf(statements[0]);
|
|
908
|
+
if (index === -1) return;
|
|
909
|
+
var outlineInfo = canOutline(statements, parentBlock);
|
|
910
|
+
if (outlineInfo.isIllegal) return;
|
|
911
|
+
var breakFlag = controlGen.generate();
|
|
912
|
+
outlineInfo.breakStatements.forEach(_ref => {
|
|
913
|
+
let [breakStatement, p] = _ref;
|
|
914
|
+
this.replace(breakStatement, (0, _gen.ReturnStatement)((0, _gen.Literal)(breakFlag)));
|
|
915
|
+
});
|
|
916
|
+
var returnFlag = controlGen.generate();
|
|
917
|
+
outlineInfo.returnStatements.forEach(_ref2 => {
|
|
918
|
+
let [returnStatement, p] = _ref2;
|
|
919
|
+
var argument = returnStatement.argument || (0, _gen.Identifier)("undefined");
|
|
920
|
+
this.replace(returnStatement, (0, _gen.ReturnStatement)((0, _gen.ObjectExpression)([(0, _gen.Property)((0, _gen.Literal)(returnFlag), argument, false)])));
|
|
921
|
+
}); // Outline these statements!
|
|
922
|
+
|
|
923
|
+
var key = createOutlineFunction((0, _insert.clone)(statements), stateValues, label);
|
|
924
|
+
var callExpression = (0, _gen.CallExpression)(getControlMember(key), []);
|
|
925
|
+
var newStatements = [];
|
|
926
|
+
|
|
927
|
+
if (outlineInfo.breakStatements.length === 0 && outlineInfo.returnStatements.length === 0) {
|
|
928
|
+
newStatements.push((0, _gen.ExpressionStatement)(callExpression));
|
|
929
|
+
} else if (outlineInfo.returnStatements.length === 0) {
|
|
930
|
+
newStatements.push((0, _gen.IfStatement)((0, _gen.BinaryExpression)("==", callExpression, (0, _gen.Literal)(breakFlag)), [(0, _gen.BreakStatement)(switchLabel)]));
|
|
931
|
+
} else {
|
|
932
|
+
var tempVar = this.getPlaceholder();
|
|
933
|
+
newStatements.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(tempVar, callExpression)));
|
|
934
|
+
|
|
935
|
+
const t = str => (0, _template.default)(str).single().expression;
|
|
936
|
+
|
|
937
|
+
newStatements.push((0, _gen.IfStatement)(t("".concat(tempVar, " === \"").concat(breakFlag, "\"")), [(0, _gen.BreakStatement)(switchLabel)], [(0, _gen.IfStatement)(t("typeof ".concat(tempVar, " == \"object\"")), [(0, _gen.ReturnStatement)(t("".concat(tempVar, "[\"").concat(returnFlag, "\"]")))])]));
|
|
938
|
+
} // Remove the original statements from the block and replace it with the call expression
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
parentBlock.splice(index, statements.length, ...newStatements);
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
const attemptOutlineExpression = (expression, expressionParents, stateValues, label) => {
|
|
945
|
+
if (this.isDebug || !this.outlineExpressions || (0, _random.chance)(75 + outlinesCreated - this.mangledExpressionsMade / 25)) {
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
var outlineInfo = canOutline(expression, expressionParents);
|
|
950
|
+
if (outlineInfo.isIllegal || outlineInfo.breakStatements.length || outlineInfo.returnStatements.length) return; // Outline this expression!
|
|
951
|
+
|
|
952
|
+
var key = createOutlineFunction([(0, _gen.ReturnStatement)((0, _insert.clone)(expression))], stateValues, label);
|
|
953
|
+
var callExpression = (0, _gen.CallExpression)(getControlMember(key), []);
|
|
954
|
+
this.replaceIdentifierOrLiteral(expression, callExpression, expressionParents);
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
const createTransitionExpression = (index, add, mutatingStateValues, label) => {
|
|
958
|
+
var beforeStateValues = [...mutatingStateValues];
|
|
608
959
|
var newValue = mutatingStateValues[index] + add;
|
|
609
960
|
var expr = null;
|
|
610
961
|
|
|
611
962
|
if (this.isDebug) {
|
|
963
|
+
// state = NEW_STATE
|
|
612
964
|
expr = (0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(stateVars[index]), (0, _gen.Literal)(newValue));
|
|
613
|
-
} else if (
|
|
614
|
-
|
|
965
|
+
} else if ((0, _random.chance)(90)) {
|
|
966
|
+
// state += (NEW_STATE - CURRENT_STATE)
|
|
967
|
+
expr = (0, _gen.AssignmentExpression)("+=", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(add, mutatingStateValues));
|
|
615
968
|
} else {
|
|
969
|
+
// state *= 2
|
|
970
|
+
// state -= DIFFERENCE
|
|
616
971
|
var double = mutatingStateValues[index] * 2;
|
|
617
972
|
var diff = double - newValue;
|
|
618
|
-
var first = (0, _gen.AssignmentExpression)("*=", (0, _gen.Identifier)(stateVars[index]),
|
|
973
|
+
var first = (0, _gen.AssignmentExpression)("*=", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(2, mutatingStateValues));
|
|
619
974
|
mutatingStateValues[index] = double;
|
|
620
|
-
expr = (0, _gen.SequenceExpression)([first, (0, _gen.AssignmentExpression)("-=", (0, _gen.Identifier)(stateVars[index]),
|
|
975
|
+
expr = (0, _gen.SequenceExpression)([first, (0, _gen.AssignmentExpression)("-=", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(diff, mutatingStateValues))]);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
mutatingStateValues[index] = newValue; // These are lower quality outlines vs. the entire transition outline
|
|
979
|
+
|
|
980
|
+
if ((0, _random.chance)(50)) {
|
|
981
|
+
attemptOutlineExpression(expr, [], [...beforeStateValues], label);
|
|
621
982
|
}
|
|
622
983
|
|
|
623
|
-
mutatingStateValues[index] = newValue;
|
|
624
984
|
return expr;
|
|
625
985
|
};
|
|
626
986
|
|
|
627
|
-
var order = Object.create(null);
|
|
628
987
|
var cases = [];
|
|
629
988
|
chunks.forEach((chunk, i) => {
|
|
630
989
|
// skip last case, its empty and never ran
|
|
@@ -634,18 +993,111 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
634
993
|
|
|
635
994
|
(0, _assert.ok)(labelToStates[chunk.label]);
|
|
636
995
|
var state = caseStates[i];
|
|
637
|
-
var made = 1;
|
|
638
|
-
var breaksInsertion = [];
|
|
639
996
|
var staticStateValues = [...labelToStates[chunk.label]];
|
|
640
997
|
var potentialBranches = new Set();
|
|
641
|
-
chunk.body.forEach(
|
|
642
|
-
var addBreak = false;
|
|
998
|
+
[...chunk.body].forEach(stmt => {
|
|
643
999
|
(0, _traverse.walk)(stmt, [], (o, p) => {
|
|
644
|
-
|
|
645
|
-
|
|
1000
|
+
// This mangles certain literals with the state variables
|
|
1001
|
+
// Ex: A number literal (50) changed to a expression (stateVar + 40), when stateVar = 10
|
|
1002
|
+
if (!this.isDebug && o.type === "Literal" && !p.find(x => (0, _insert.isVarContext)(x))) {
|
|
1003
|
+
if (typeof o.value === "number" && Math.floor(o.value) === o.value && // Only whole numbers
|
|
1004
|
+
Math.abs(o.value) < 100000 && // Hard-coded limit
|
|
1005
|
+
this.mangleNumberLiterals && (0, _random.chance)(50 - this.mangledExpressionsMade / 100)) {
|
|
1006
|
+
// 50 -> state1 - 10, when state1 = 60. The result is still 50
|
|
1007
|
+
return () => {
|
|
1008
|
+
this.replaceIdentifierOrLiteral(o, createStateBoundNumberLiteral(o.value, staticStateValues), p);
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (typeof o.value === "boolean" && this.mangleBooleanLiterals && (0, _random.chance)(50 - this.mangledExpressionsMade / 100)) {
|
|
1013
|
+
// true -> state1 == 10, when state1 = 10. The result is still true
|
|
1014
|
+
// Choose a random state var to compare again
|
|
1015
|
+
var index = (0, _random.getRandomInteger)(0, stateVars.length);
|
|
1016
|
+
var compareValue = staticStateValues[index]; // When false, always choose a different number, so the expression always equals false
|
|
1017
|
+
|
|
1018
|
+
while (!o.value && compareValue === staticStateValues[index]) {
|
|
1019
|
+
compareValue = (0, _random.getRandomInteger)(-150, 150);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
var mangledExpression = (0, _gen.BinaryExpression)("==", (0, _gen.Identifier)(stateVars[index]), createStateBoundNumberLiteral(compareValue, staticStateValues));
|
|
1023
|
+
return () => {
|
|
1024
|
+
this.replaceIdentifierOrLiteral(o, mangledExpression, p);
|
|
1025
|
+
attemptOutlineExpression(o, p, staticStateValues, chunk.label);
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
} // Mangle certain referenced identifiers
|
|
1029
|
+
// console.log("hi") -> (x ? console : window).log("hi"), when is x true. The result is the same
|
|
1030
|
+
|
|
1031
|
+
|
|
1032
|
+
if (!this.isDebug && o.type === "Identifier" && this.mangleIdentifiers && (0, _random.chance)(50 - this.mangledExpressionsMade / 100) && !p.find(x => (0, _insert.isVarContext)(x))) {
|
|
1033
|
+
// ONLY referenced identifiers (like actual variable names) can be changed
|
|
1034
|
+
var info = (0, _identifiers.getIdentifierInfo)(o, p);
|
|
1035
|
+
|
|
1036
|
+
if (!info.spec.isReferenced || info.spec.isDefined || info.spec.isModified || info.spec.isExported) {
|
|
1037
|
+
return;
|
|
1038
|
+
} // TYPEOF expression check
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
if (p[0] && p[0].type === "UnaryExpression" && p[0].operator === "typeof" && p[0].argument === o) {
|
|
1042
|
+
return;
|
|
1043
|
+
} // Update expression check
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
if (p[0] && p[0].type === "UpdateExpression") {
|
|
1047
|
+
return;
|
|
1048
|
+
} // FOR-in/of initializer check
|
|
1049
|
+
|
|
1050
|
+
|
|
1051
|
+
if ((0, _insert.isForInitialize)(o, p) === "left-hand") {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
var {
|
|
1056
|
+
test,
|
|
1057
|
+
testValue
|
|
1058
|
+
} = createPredicate(staticStateValues); // test && real
|
|
1059
|
+
|
|
1060
|
+
var mangledExpression = (0, _gen.LogicalExpression)(testValue ? "&&" : "||", test, (0, _gen.Identifier)(o.name)); // control.fake = real
|
|
1061
|
+
|
|
1062
|
+
if ((0, _random.chance)(50)) {
|
|
1063
|
+
mangledExpression = (0, _gen.AssignmentExpression)("=", getControlMember(controlGen.generate()), (0, _gen.Identifier)(o.name));
|
|
1064
|
+
} // test ? real : fake
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
if ((0, _random.chance)(50)) {
|
|
1068
|
+
var alternateName = (0, _random.choice)([controlVar, ...stateVars, ...this.options.globalVariables, ..._constants.reservedIdentifiers]); // Don't use 'arguments'
|
|
1069
|
+
|
|
1070
|
+
if (alternateName === "arguments") alternateName = "undefined";
|
|
1071
|
+
mangledExpression = (0, _gen.ConditionalExpression)(test, (0, _gen.Identifier)(testValue ? o.name : alternateName), (0, _gen.Identifier)(!testValue ? o.name : alternateName));
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
return () => {
|
|
1075
|
+
this.replaceIdentifierOrLiteral(o, mangledExpression, p);
|
|
1076
|
+
};
|
|
1077
|
+
} // Function outlining: bring out certain expressions
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
if (!this.isDebug && o.type && ["BinaryExpression", "LogicalExpression", "CallExpression", "AssignmentExpression", "MemberExpression", "ObjectExpression", "ConditionalExpression"].includes(o.type) && !(0, _random.chance)(p.length * 5) && // The further down the tree the lower quality of expression
|
|
1081
|
+
!p.find(x => (0, _insert.isContext)(x) || x.$outlining)) {
|
|
1082
|
+
o.$outlining = true;
|
|
646
1083
|
return () => {
|
|
647
|
-
|
|
1084
|
+
attemptOutlineExpression(o, p, staticStateValues, chunk.label);
|
|
648
1085
|
};
|
|
1086
|
+
} // Opaque predicates: If Statements, Conditional Statements, Switch Case test
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
if (!this.isDebug && this.addOpaquePredicates && p[0] && (0, _random.chance)(50 - outlinesCreated - this.mangledExpressionsMade / 100)) {
|
|
1090
|
+
var isTestExpression = p[0].type == "IfStatement" && p[0].test === o || p[0].type === "ConditionalExpression" && p[0].test === o || p[0].type === "SwitchCase" && p[0].test === o;
|
|
1091
|
+
|
|
1092
|
+
if (isTestExpression && !p.find(x => (0, _insert.isContext)(x))) {
|
|
1093
|
+
return () => {
|
|
1094
|
+
var {
|
|
1095
|
+
test,
|
|
1096
|
+
testValue
|
|
1097
|
+
} = createPredicate(staticStateValues);
|
|
1098
|
+
this.replace(o, (0, _gen.LogicalExpression)(testValue ? "&&" : "||", test, (0, _insert.clone)(o)));
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
649
1101
|
}
|
|
650
1102
|
|
|
651
1103
|
if (o.type == "StateIdentifier") {
|
|
@@ -657,104 +1109,265 @@ class ControlFlowFlattening extends _transform.default {
|
|
|
657
1109
|
|
|
658
1110
|
if (o.type == "GotoStatement") {
|
|
659
1111
|
return () => {
|
|
660
|
-
var blockIndex = p.findIndex(node => (0, _traverse.isBlock)(node));
|
|
1112
|
+
var blockIndex = p.findIndex(node => (0, _traverse.isBlock)(node) || node.type === "SwitchCase");
|
|
661
1113
|
|
|
662
1114
|
if (blockIndex === -1) {
|
|
663
|
-
|
|
1115
|
+
var index = chunk.body.indexOf(stmt);
|
|
1116
|
+
(0, _assert.ok)(index != -1); // Top level: Insert break statement in the chunk body
|
|
1117
|
+
// This is OKAY because this forEach uses a cloned version of the body `[...chunk.body]`
|
|
1118
|
+
|
|
1119
|
+
chunk.body.splice(index + 1, 0, (0, _gen.BreakStatement)(switchLabel));
|
|
664
1120
|
} else {
|
|
665
|
-
var
|
|
666
|
-
|
|
667
|
-
|
|
1121
|
+
var block = p[blockIndex];
|
|
1122
|
+
|
|
1123
|
+
if (block.type === "SwitchCase") {
|
|
1124
|
+
// Handle switch case break placement (Important!)
|
|
1125
|
+
block.consequent.splice(block.consequent.indexOf(p[blockIndex - 2] || o) + 1, 0, (0, _gen.BreakStatement)(switchLabel));
|
|
1126
|
+
} else {
|
|
1127
|
+
// Standard block placement
|
|
1128
|
+
var child = p[blockIndex - 2] || o;
|
|
1129
|
+
var childIndex = block.body.indexOf(child);
|
|
1130
|
+
block.body.splice(childIndex + 1, 0, (0, _gen.BreakStatement)(switchLabel));
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
if (!o.impossible) {
|
|
1135
|
+
potentialBranches.add(o.label);
|
|
668
1136
|
}
|
|
669
1137
|
|
|
670
|
-
potentialBranches.add(o.label);
|
|
671
1138
|
var mutatingStateValues = [...labelToStates[chunk.label]];
|
|
672
1139
|
var nextStateValues = labelToStates[o.label];
|
|
673
1140
|
(0, _assert.ok)(nextStateValues, o.label);
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1141
|
+
var transitionExpressions = [];
|
|
1142
|
+
|
|
1143
|
+
for (var stateValueIndex = 0; stateValueIndex < stateVars.length; stateValueIndex++) {
|
|
1144
|
+
var diff = nextStateValues[stateValueIndex] - mutatingStateValues[stateValueIndex]; // Only add if state value changed
|
|
1145
|
+
// If pointing to itself then always add to ensure SequenceExpression isn't empty
|
|
1146
|
+
|
|
1147
|
+
if (diff !== 0 || o.label === chunk.label) {
|
|
1148
|
+
transitionExpressions.push(createTransitionExpression(stateValueIndex, diff, mutatingStateValues, chunk.label));
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
(0, _assert.ok)(transitionExpressions.length !== 0);
|
|
1153
|
+
var sequenceExpression = (0, _gen.SequenceExpression)(transitionExpressions); // Check if flagged and additional code here
|
|
1154
|
+
|
|
1155
|
+
if (typeof flaggedLabels[o.label] === "object") {
|
|
1156
|
+
var {
|
|
1157
|
+
flagKey,
|
|
1158
|
+
flagValue
|
|
1159
|
+
} = flaggedLabels[o.label];
|
|
1160
|
+
sequenceExpression.expressions.push((0, _gen.AssignmentExpression)("=", getControlMember(flagKey), (0, _gen.Literal)(flagValue)));
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
attemptOutlineExpression(sequenceExpression, [], staticStateValues, chunk.label);
|
|
1164
|
+
this.replace(o, (0, _gen.ExpressionStatement)(sequenceExpression));
|
|
678
1165
|
};
|
|
679
1166
|
}
|
|
680
1167
|
});
|
|
681
|
-
|
|
682
|
-
if (addBreak) {
|
|
683
|
-
breaksInsertion.push(stmtIndex);
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
breaksInsertion.sort();
|
|
687
|
-
breaksInsertion.reverse();
|
|
688
|
-
breaksInsertion.forEach(index => {
|
|
689
|
-
chunk.body.splice(index + 1, 0, (0, _gen.BreakStatement)(switchLabel));
|
|
690
1168
|
});
|
|
1169
|
+
attemptOutlineStatements(chunk.body, chunk.body, staticStateValues, chunk.label);
|
|
691
1170
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}))));
|
|
699
|
-
}
|
|
700
|
-
} // var c = Identifier("undefined");
|
|
701
|
-
// this.addComment(c, stateValues.join(", "));
|
|
702
|
-
// transitionStatements.push(c);
|
|
703
|
-
|
|
1171
|
+
if (!chunk.impossible) {// FUTURE OBFUSCATION IDEA: Update controlObject based on 'potentialBranches' code
|
|
1172
|
+
// This idea would require a lot of work but would make some seriously effective obfuscation
|
|
1173
|
+
// for protecting the data. In 'inactive' states the data could be overwritten to fake values
|
|
1174
|
+
// And in the 'active' state the data would brought back just in time. This would require the controlObject
|
|
1175
|
+
// state to be known in all chunks
|
|
1176
|
+
}
|
|
704
1177
|
|
|
705
1178
|
var caseObject = {
|
|
706
1179
|
body: chunk.body,
|
|
707
1180
|
state: state,
|
|
708
|
-
|
|
1181
|
+
label: chunk.label
|
|
709
1182
|
};
|
|
710
|
-
order[i] = caseObject;
|
|
711
1183
|
cases.push(caseObject);
|
|
712
1184
|
});
|
|
713
1185
|
|
|
1186
|
+
if (!this.isDebug && this.addDeadCode) {
|
|
1187
|
+
// Add fake control object updates
|
|
1188
|
+
chunks.forEach(chunk => {
|
|
1189
|
+
if ((0, _random.chance)(10)) {
|
|
1190
|
+
// These deadCode variants can NOT break the state/control variables
|
|
1191
|
+
// They are executed!
|
|
1192
|
+
var deadCodeChoices = [(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(controlGen.generate()), (0, _gen.Literal)(controlGen.generate()))), (0, _gen.ExpressionStatement)((0, _gen.UnaryExpression)("delete", getControlMember(controlGen.generate())))]; // These deadCode variants can make breaking changes
|
|
1193
|
+
// because they are never ran
|
|
1194
|
+
|
|
1195
|
+
if (chunk.impossible) {
|
|
1196
|
+
var randomControlKey = (0, _random.choice)(controlProperties.map(prop => {
|
|
1197
|
+
var _prop$key;
|
|
1198
|
+
|
|
1199
|
+
return (_prop$key = prop.key) === null || _prop$key === void 0 ? void 0 : _prop$key.value;
|
|
1200
|
+
}).filter(x => x && typeof x === "string")) || controlGen.generate();
|
|
1201
|
+
deadCodeChoices = deadCodeChoices.concat([(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(controlVar), (0, _gen.Literal)(false))), (0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(controlVar), (0, _gen.Identifier)("undefined"))), (0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", getControlMember(randomControlKey), (0, _gen.Identifier)("undefined"))), (0, _gen.ExpressionStatement)((0, _gen.UnaryExpression)("delete", getControlMember(randomControlKey)))]);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
chunk.body.unshift((0, _random.choice)(deadCodeChoices));
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
|
|
714
1209
|
if (!this.isDebug) {
|
|
715
1210
|
(0, _random.shuffle)(cases);
|
|
1211
|
+
(0, _random.shuffle)(controlProperties);
|
|
716
1212
|
}
|
|
717
1213
|
|
|
718
1214
|
var discriminant = (0, _template.default)("".concat(stateVars.join("+"))).single().expression;
|
|
719
|
-
|
|
1215
|
+
objectBody.length = 0; // Perverse position of import declarations
|
|
720
1216
|
|
|
721
1217
|
for (var importDeclaration of importDeclarations) {
|
|
722
|
-
|
|
723
|
-
}
|
|
1218
|
+
objectBody.push(importDeclaration);
|
|
1219
|
+
} // As well as functions are brought up
|
|
724
1220
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
body.unshift((0, _insert.clone)(x));
|
|
729
|
-
}
|
|
730
|
-
});
|
|
1221
|
+
|
|
1222
|
+
for (var functionName of functionDeclarationNames) {
|
|
1223
|
+
objectBody.push((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(functionName, functionDeclarationValues.get(functionName))));
|
|
731
1224
|
}
|
|
732
1225
|
|
|
733
|
-
var
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
var
|
|
737
|
-
return (0, _gen.SwitchCase)(test, statements);
|
|
738
|
-
}));
|
|
739
|
-
var declarations = [];
|
|
1226
|
+
var defaultCaseIndex = (0, _random.getRandomInteger)(0, cases.length);
|
|
1227
|
+
var switchCases = [];
|
|
1228
|
+
cases.forEach((caseObject, i) => {
|
|
1229
|
+
var _caseObject$body$0$la;
|
|
740
1230
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1231
|
+
// Empty case OR single break statement is skipped
|
|
1232
|
+
if (caseObject.body.length === 0 || caseObject.body.length === 1 && caseObject.body[0].type === "BreakStatement" && ((_caseObject$body$0$la = caseObject.body[0].label) === null || _caseObject$body$0$la === void 0 ? void 0 : _caseObject$body$0$la.name) === switchLabel) return;
|
|
1233
|
+
var test = (0, _gen.Literal)(caseObject.state);
|
|
1234
|
+
var isEligibleForOutlining = false; // Check if Control Map has this value
|
|
744
1235
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
declarations.push((0, _gen.VariableDeclarator)(argVar));
|
|
748
|
-
}
|
|
1236
|
+
if (!this.isDebug && controlConstantMap.has(caseObject.state)) {
|
|
1237
|
+
var _controlConstantMap$g3;
|
|
749
1238
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1239
|
+
test = getControlMember((_controlConstantMap$g3 = controlConstantMap.get(caseObject.state)) === null || _controlConstantMap$g3 === void 0 ? void 0 : _controlConstantMap$g3.key);
|
|
1240
|
+
} // Create complex test expressions for each switch case
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
if (!this.isDebug && this.addComplexTest && (0, _random.chance)(25)) {
|
|
1244
|
+
isEligibleForOutlining = true; // case STATE+X:
|
|
1245
|
+
|
|
1246
|
+
var stateVarIndex = (0, _random.getRandomInteger)(0, stateVars.length);
|
|
1247
|
+
var stateValues = labelToStates[caseObject.label];
|
|
1248
|
+
var difference = stateValues[stateVarIndex] - caseObject.state;
|
|
1249
|
+
var conditionNodes = [];
|
|
1250
|
+
var alreadyConditionedItems = new Set(); // This code finds clash conditions and adds them to 'conditionNodes' array
|
|
1251
|
+
|
|
1252
|
+
Object.keys(labelToStates).forEach(label => {
|
|
1253
|
+
if (label !== caseObject.label) {
|
|
1254
|
+
var labelStates = labelToStates[label];
|
|
1255
|
+
var totalState = labelStates.reduce((a, b) => a + b, 0);
|
|
1256
|
+
|
|
1257
|
+
if (totalState === labelStates[stateVarIndex] - difference) {
|
|
1258
|
+
var differentIndex = labelStates.findIndex((v, i) => v !== stateValues[i]);
|
|
1259
|
+
|
|
1260
|
+
if (differentIndex !== -1) {
|
|
1261
|
+
var expressionAsString = stateVars[differentIndex] + "!=" + labelStates[differentIndex];
|
|
1262
|
+
|
|
1263
|
+
if (!alreadyConditionedItems.has(expressionAsString)) {
|
|
1264
|
+
alreadyConditionedItems.add(expressionAsString);
|
|
1265
|
+
conditionNodes.push((0, _gen.BinaryExpression)("!=", (0, _gen.Identifier)(stateVars[differentIndex]), (0, _gen.Literal)(labelStates[differentIndex])));
|
|
1266
|
+
}
|
|
1267
|
+
} else {
|
|
1268
|
+
conditionNodes.push((0, _gen.BinaryExpression)("!=", (0, _insert.clone)(discriminant), (0, _gen.Literal)(totalState)));
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}); // case STATE!=Y && STATE+X
|
|
1273
|
+
|
|
1274
|
+
test = (0, _gen.BinaryExpression)("-", (0, _gen.Identifier)(stateVars[stateVarIndex]), (0, _gen.Literal)(difference)); // Use the 'conditionNodes' to not cause state clashing issues
|
|
1275
|
+
|
|
1276
|
+
conditionNodes.forEach(conditionNode => {
|
|
1277
|
+
test = (0, _gen.LogicalExpression)("&&", conditionNode, test);
|
|
1278
|
+
});
|
|
1279
|
+
} // A 'flagged' label has addition 'flagKey' that gets switched before jumped to
|
|
1280
|
+
|
|
1281
|
+
|
|
1282
|
+
if (flaggedLabels[caseObject.label]) {
|
|
1283
|
+
isEligibleForOutlining = true;
|
|
1284
|
+
var {
|
|
1285
|
+
flagKey,
|
|
1286
|
+
flagValue
|
|
1287
|
+
} = flaggedLabels[caseObject.label];
|
|
1288
|
+
var alternateNum;
|
|
1289
|
+
|
|
1290
|
+
do {
|
|
1291
|
+
alternateNum = (0, _random.getRandomInteger)(-1000, 1000 + chunks.length);
|
|
1292
|
+
} while (caseSelection.has(alternateNum));
|
|
1293
|
+
|
|
1294
|
+
var alternate = (0, _gen.Literal)(alternateNum); // case FLAG ? <REAL> : <FAKE>:
|
|
1295
|
+
|
|
1296
|
+
test = (0, _gen.ConditionalExpression)(getControlMember(flagKey), flagValue ? test : alternate, !flagValue ? test : alternate);
|
|
1297
|
+
} // Outline this switch case test
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
if (!this.isDebug && this.outlineExpressions && isEligibleForOutlining && (0, _random.chance)(75 - outlinesCreated - this.mangledExpressionsMade / 25)) {
|
|
1301
|
+
this.mangledExpressionsMade++; // Selected a random parent node (or this node) to insert this function in
|
|
1302
|
+
|
|
1303
|
+
var selectedControlNode = (0, _random.choice)(allControlNodes);
|
|
1304
|
+
var selectedControlProperties = selectedControlNode.$controlProperties;
|
|
1305
|
+
var selectedControlVar = selectedControlNode.$controlVar;
|
|
1306
|
+
var selectedControlGen = selectedControlNode.$controlGen;
|
|
1307
|
+
var fnKey = selectedControlGen.generate(); // Pass in the:
|
|
1308
|
+
// - controlVar for 'flagged labels' code check
|
|
1309
|
+
// - stateVars for 'complex test expressions'
|
|
1310
|
+
// (Check which identifiers are actually needed)
|
|
1311
|
+
|
|
1312
|
+
var argumentList = [],
|
|
1313
|
+
watchingFor = new Set([controlVar, ...stateVars]);
|
|
1314
|
+
(0, _traverse.walk)(test, [], (o, p) => {
|
|
1315
|
+
if (o.type === "Identifier" && watchingFor.has(o.name)) {
|
|
1316
|
+
watchingFor.delete(o.name);
|
|
1317
|
+
argumentList.push((0, _gen.Identifier)(o.name));
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
selectedControlProperties.push((0, _gen.Property)((0, _gen.Literal)(fnKey), (0, _gen.FunctionExpression)(argumentList, [(0, _gen.ReturnStatement)(test)]), true)); // case control.a(control, s1, s2):
|
|
1321
|
+
|
|
1322
|
+
test = (0, _gen.CallExpression)(getControlMember(fnKey, selectedControlVar), (0, _insert.clone)(argumentList));
|
|
1323
|
+
} // One random case gets to be default
|
|
1324
|
+
|
|
1325
|
+
|
|
1326
|
+
if (!this.isDebug && i === defaultCaseIndex) test = null;
|
|
1327
|
+
var testArray = [test];
|
|
1328
|
+
|
|
1329
|
+
if (!this.isDebug && this.addFakeTest && (0, _random.chance)(50)) {
|
|
1330
|
+
// Add fake test
|
|
1331
|
+
// case <FAKE>:
|
|
1332
|
+
// case <REAL>:
|
|
1333
|
+
// case <FAKE>:
|
|
1334
|
+
var fakeTestCount = (0, _random.getRandomInteger)(1, 4);
|
|
1335
|
+
|
|
1336
|
+
for (var i = 0; i < fakeTestCount; i++) {
|
|
1337
|
+
// Create a fake test number that doesn't interfere with the actual states
|
|
1338
|
+
var fakeTestNum;
|
|
1339
|
+
|
|
1340
|
+
do {
|
|
1341
|
+
fakeTestNum = (0, _random.getRandomInteger)(1, 1000 + caseSelection.size);
|
|
1342
|
+
} while (caseSelection.has(fakeTestNum)); // Add this fake test
|
|
1343
|
+
|
|
1344
|
+
|
|
1345
|
+
testArray.push((0, _gen.Literal)(fakeTestNum));
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
(0, _random.shuffle)(testArray);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
testArray.forEach((test, i) => {
|
|
1352
|
+
var body = i === testArray.length - 1 ? caseObject.body : [];
|
|
1353
|
+
switchCases.push((0, _gen.SwitchCase)(test, body));
|
|
1354
|
+
});
|
|
1355
|
+
}); // switch(state) { case ... }
|
|
1356
|
+
|
|
1357
|
+
var switchStatement = (0, _gen.SwitchStatement)(discriminant, switchCases);
|
|
1358
|
+
var declarations = []; // var state = START_STATE
|
|
753
1359
|
|
|
754
1360
|
declarations.push(...stateVars.map((stateVar, i) => {
|
|
755
1361
|
return (0, _gen.VariableDeclarator)(stateVar, (0, _gen.Literal)(initStateValues[i]));
|
|
756
|
-
}));
|
|
757
|
-
|
|
1362
|
+
})); // var control = { strings, numbers, outlined functions, etc... }
|
|
1363
|
+
|
|
1364
|
+
var objectExpression = (0, _gen.ObjectExpression)(controlProperties);
|
|
1365
|
+
declarations.push((0, _gen.VariableDeclarator)(controlVar, objectExpression));
|
|
1366
|
+
objectBody.push( // Use individual variable declarations instead so Stack can apply
|
|
1367
|
+
...declarations.map(declaration => (0, _gen.VariableDeclaration)(declaration, "var"))); // while (state != END_STATE) {...}
|
|
1368
|
+
|
|
1369
|
+
var whileTest = (0, _gen.BinaryExpression)("!=", (0, _insert.clone)(discriminant), (0, _gen.Literal)(endState));
|
|
1370
|
+
objectBody.push((0, _gen.WhileStatement)(whileTest, [(0, _gen.LabeledStatement)(switchLabel, switchStatement)])); // mark this object for switch case obfuscation
|
|
758
1371
|
|
|
759
1372
|
switchStatement.$controlFlowFlattening = true;
|
|
760
1373
|
};
|