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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +6 -4
- package/.github/workflows/node.js.yml +1 -1
- package/CHANGELOG.md +105 -0
- package/Migration.md +57 -0
- package/README.md +23 -913
- package/dist/constants.js +69 -13
- package/dist/index.js +108 -152
- package/dist/obfuscator.js +316 -118
- package/dist/options.js +1 -109
- package/dist/order.js +30 -30
- package/dist/presets.js +47 -45
- package/dist/probability.js +25 -32
- package/dist/templates/bufferToStringTemplate.js +9 -0
- package/dist/templates/deadCodeTemplates.js +9 -0
- package/dist/templates/getGlobalTemplate.js +19 -0
- package/dist/templates/integrityTemplate.js +30 -0
- package/dist/templates/setFunctionLengthTemplate.js +9 -0
- package/dist/templates/stringCompressionTemplate.js +10 -0
- package/dist/templates/tamperProtectionTemplates.js +21 -0
- package/dist/templates/template.js +213 -93
- package/dist/transforms/astScrambler.js +100 -0
- package/dist/transforms/calculator.js +70 -127
- package/dist/transforms/controlFlowFlattening.js +1182 -0
- package/dist/transforms/deadCode.js +62 -577
- package/dist/transforms/dispatcher.js +300 -309
- package/dist/transforms/extraction/duplicateLiteralsRemoval.js +88 -189
- package/dist/transforms/extraction/objectExtraction.js +131 -215
- package/dist/transforms/finalizer.js +56 -59
- package/dist/transforms/flatten.js +275 -276
- package/dist/transforms/functionOutlining.js +230 -0
- package/dist/transforms/identifier/globalConcealing.js +217 -103
- package/dist/transforms/identifier/movedDeclarations.js +167 -91
- package/dist/transforms/identifier/renameVariables.js +240 -187
- package/dist/transforms/lock/integrity.js +61 -184
- package/dist/transforms/lock/lock.js +263 -303
- package/dist/transforms/minify.js +431 -436
- package/dist/transforms/opaquePredicates.js +65 -118
- package/dist/transforms/pack.js +160 -0
- package/dist/transforms/plugin.js +179 -0
- package/dist/transforms/preparation.js +263 -163
- package/dist/transforms/renameLabels.js +132 -56
- package/dist/transforms/rgf.js +142 -240
- package/dist/transforms/shuffle.js +52 -145
- package/dist/transforms/string/encoding.js +45 -173
- package/dist/transforms/string/stringCompression.js +81 -126
- package/dist/transforms/string/stringConcealing.js +189 -224
- package/dist/transforms/string/stringEncoding.js +32 -40
- package/dist/transforms/string/stringSplitting.js +54 -55
- package/dist/transforms/variableMasking.js +232 -0
- package/dist/utils/ControlObject.js +125 -0
- package/dist/utils/IntGen.js +46 -0
- package/dist/utils/NameGen.js +106 -0
- package/dist/utils/ast-utils.js +560 -0
- package/dist/utils/function-utils.js +56 -0
- package/dist/utils/gen-utils.js +48 -0
- package/dist/utils/node.js +77 -0
- package/dist/utils/object-utils.js +21 -0
- package/dist/utils/random-utils.js +91 -0
- package/dist/utils/static-utils.js +64 -0
- package/dist/validateOptions.js +122 -0
- package/index.d.ts +1 -17
- package/package.json +27 -22
- package/src/constants.ts +139 -77
- package/src/index.ts +70 -163
- package/src/obfuscationResult.ts +43 -0
- package/src/obfuscator.ts +328 -135
- package/src/options.ts +154 -623
- package/src/order.ts +14 -14
- package/src/presets.ts +39 -34
- package/src/probability.ts +21 -36
- package/src/templates/{bufferToString.ts → bufferToStringTemplate.ts} +5 -54
- package/src/templates/deadCodeTemplates.ts +1185 -0
- package/src/templates/getGlobalTemplate.ts +72 -0
- package/src/templates/integrityTemplate.ts +69 -0
- package/src/templates/setFunctionLengthTemplate.ts +11 -0
- package/src/templates/stringCompressionTemplate.ts +42 -0
- package/src/templates/tamperProtectionTemplates.ts +116 -0
- package/src/templates/template.ts +183 -92
- package/src/transforms/astScrambler.ts +99 -0
- package/src/transforms/calculator.ts +96 -224
- package/src/transforms/controlFlowFlattening.ts +1594 -0
- package/src/transforms/deadCode.ts +85 -628
- package/src/transforms/dispatcher.ts +431 -636
- package/src/transforms/extraction/duplicateLiteralsRemoval.ts +147 -299
- package/src/transforms/extraction/objectExtraction.ts +160 -333
- package/src/transforms/finalizer.ts +63 -64
- package/src/transforms/flatten.ts +439 -557
- package/src/transforms/functionOutlining.ts +225 -0
- package/src/transforms/identifier/globalConcealing.ts +261 -189
- package/src/transforms/identifier/movedDeclarations.ts +228 -142
- package/src/transforms/identifier/renameVariables.ts +252 -258
- package/src/transforms/lock/integrity.ts +84 -260
- package/src/transforms/lock/lock.ts +342 -491
- package/src/transforms/minify.ts +523 -663
- package/src/transforms/opaquePredicates.ts +90 -229
- package/src/transforms/pack.ts +195 -0
- package/src/transforms/plugin.ts +185 -0
- package/src/transforms/preparation.ts +337 -215
- package/src/transforms/renameLabels.ts +176 -77
- package/src/transforms/rgf.ts +293 -386
- package/src/transforms/shuffle.ts +80 -254
- package/src/transforms/string/encoding.ts +26 -129
- package/src/transforms/string/stringCompression.ts +118 -236
- package/src/transforms/string/stringConcealing.ts +255 -339
- package/src/transforms/string/stringEncoding.ts +28 -47
- package/src/transforms/string/stringSplitting.ts +61 -75
- package/src/transforms/variableMasking.ts +257 -0
- package/src/utils/ControlObject.ts +141 -0
- package/src/utils/IntGen.ts +33 -0
- package/src/utils/NameGen.ts +106 -0
- package/src/utils/ast-utils.ts +667 -0
- package/src/utils/function-utils.ts +50 -0
- package/src/utils/gen-utils.ts +48 -0
- package/src/utils/node.ts +78 -0
- package/src/utils/object-utils.ts +21 -0
- package/src/utils/random-utils.ts +79 -0
- package/src/utils/static-utils.ts +66 -0
- package/src/validateOptions.ts +256 -0
- package/tsconfig.json +13 -8
- package/babel.config.js +0 -12
- package/dev.js +0 -8
- package/dist/compiler.js +0 -34
- package/dist/parser.js +0 -59
- package/dist/precedence.js +0 -66
- package/dist/templates/bufferToString.js +0 -108
- package/dist/templates/crash.js +0 -59
- package/dist/templates/es5.js +0 -137
- package/dist/templates/functionLength.js +0 -34
- package/dist/templates/globals.js +0 -9
- package/dist/transforms/antiTooling.js +0 -88
- package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +0 -1281
- package/dist/transforms/controlFlowFlattening/expressionObfuscation.js +0 -131
- package/dist/transforms/es5/antiClass.js +0 -164
- package/dist/transforms/es5/antiDestructuring.js +0 -193
- package/dist/transforms/es5/antiES6Object.js +0 -185
- package/dist/transforms/es5/antiSpreadOperator.js +0 -35
- package/dist/transforms/es5/antiTemplate.js +0 -66
- package/dist/transforms/es5/es5.js +0 -123
- package/dist/transforms/extraction/classExtraction.js +0 -83
- package/dist/transforms/identifier/globalAnalysis.js +0 -70
- package/dist/transforms/identifier/variableAnalysis.js +0 -104
- package/dist/transforms/lock/antiDebug.js +0 -76
- package/dist/transforms/stack.js +0 -343
- package/dist/transforms/transform.js +0 -350
- package/dist/traverse.js +0 -110
- package/dist/util/compare.js +0 -145
- package/dist/util/gen.js +0 -564
- package/dist/util/guard.js +0 -9
- package/dist/util/identifiers.js +0 -355
- package/dist/util/insert.js +0 -362
- package/dist/util/math.js +0 -19
- package/dist/util/object.js +0 -40
- package/dist/util/random.js +0 -130
- package/dist/util/scope.js +0 -20
- package/docs/ControlFlowFlattening.md +0 -595
- package/docs/Countermeasures.md +0 -63
- package/docs/ES5.md +0 -197
- package/docs/Integrity.md +0 -75
- package/docs/RGF.md +0 -419
- package/samples/example.js +0 -15
- package/samples/high.js +0 -1
- package/samples/input.js +0 -3
- package/samples/javascriptobfuscator.com.js +0 -8
- package/samples/jscrambler_advanced.js +0 -1894
- package/samples/jscrambler_light.js +0 -1134
- package/samples/low.js +0 -1
- package/samples/medium.js +0 -1
- package/samples/obfuscator.io.js +0 -1686
- package/samples/preemptive.com.js +0 -16
- package/src/compiler.ts +0 -35
- package/src/parser.ts +0 -49
- package/src/precedence.ts +0 -61
- package/src/templates/crash.ts +0 -55
- package/src/templates/es5.ts +0 -131
- package/src/templates/functionLength.ts +0 -32
- package/src/templates/globals.ts +0 -3
- package/src/transforms/antiTooling.ts +0 -102
- package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +0 -2146
- package/src/transforms/controlFlowFlattening/expressionObfuscation.ts +0 -179
- package/src/transforms/es5/antiClass.ts +0 -272
- package/src/transforms/es5/antiDestructuring.ts +0 -294
- package/src/transforms/es5/antiES6Object.ts +0 -267
- package/src/transforms/es5/antiSpreadOperator.ts +0 -56
- package/src/transforms/es5/antiTemplate.ts +0 -98
- package/src/transforms/es5/es5.ts +0 -149
- package/src/transforms/extraction/classExtraction.ts +0 -168
- package/src/transforms/identifier/globalAnalysis.ts +0 -85
- package/src/transforms/identifier/variableAnalysis.ts +0 -118
- package/src/transforms/lock/antiDebug.ts +0 -112
- package/src/transforms/stack.ts +0 -551
- package/src/transforms/transform.ts +0 -453
- package/src/traverse.ts +0 -120
- package/src/types.ts +0 -131
- package/src/util/compare.ts +0 -181
- package/src/util/gen.ts +0 -651
- package/src/util/guard.ts +0 -7
- package/src/util/identifiers.ts +0 -494
- package/src/util/insert.ts +0 -419
- package/src/util/math.ts +0 -15
- package/src/util/object.ts +0 -39
- package/src/util/random.ts +0 -141
- package/src/util/scope.ts +0 -21
- package/test/code/Cash.src.js +0 -1011
- package/test/code/Cash.test.ts +0 -49
- package/test/code/Dynamic.src.js +0 -118
- package/test/code/Dynamic.test.ts +0 -49
- package/test/code/ES6.src.js +0 -235
- package/test/code/ES6.test.ts +0 -42
- package/test/code/NewFeatures.test.ts +0 -19
- package/test/code/StrictMode.src.js +0 -65
- package/test/code/StrictMode.test.js +0 -37
- package/test/compare.test.ts +0 -104
- package/test/index.test.ts +0 -249
- package/test/options.test.ts +0 -132
- package/test/presets.test.ts +0 -22
- package/test/probability.test.ts +0 -44
- package/test/templates/template.test.ts +0 -14
- package/test/transforms/antiTooling.test.ts +0 -52
- package/test/transforms/calculator.test.ts +0 -78
- package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +0 -1274
- package/test/transforms/controlFlowFlattening/expressionObfuscation.test.ts +0 -192
- package/test/transforms/deadCode.test.ts +0 -85
- package/test/transforms/dispatcher.test.ts +0 -457
- package/test/transforms/es5/antiClass.test.ts +0 -427
- package/test/transforms/es5/antiDestructuring.test.ts +0 -157
- package/test/transforms/es5/antiES6Object.test.ts +0 -245
- package/test/transforms/es5/antiTemplate.test.ts +0 -116
- package/test/transforms/es5/es5.test.ts +0 -110
- package/test/transforms/extraction/classExtraction.test.ts +0 -86
- package/test/transforms/extraction/duplicateLiteralsRemoval.test.ts +0 -200
- package/test/transforms/extraction/objectExtraction.test.ts +0 -491
- package/test/transforms/flatten.test.ts +0 -721
- package/test/transforms/hexadecimalNumbers.test.ts +0 -62
- package/test/transforms/identifier/globalConcealing.test.ts +0 -72
- package/test/transforms/identifier/movedDeclarations.test.ts +0 -275
- package/test/transforms/identifier/renameVariables.test.ts +0 -621
- package/test/transforms/lock/antiDebug.test.ts +0 -66
- package/test/transforms/lock/browserLock.test.ts +0 -129
- package/test/transforms/lock/countermeasures.test.ts +0 -100
- package/test/transforms/lock/integrity.test.ts +0 -161
- package/test/transforms/lock/lock.test.ts +0 -204
- package/test/transforms/lock/osLock.test.ts +0 -312
- package/test/transforms/lock/selfDefending.test.ts +0 -68
- package/test/transforms/minify.test.ts +0 -575
- package/test/transforms/opaquePredicates.test.ts +0 -43
- package/test/transforms/preparation.test.ts +0 -157
- package/test/transforms/renameLabels.test.ts +0 -95
- package/test/transforms/rgf.test.ts +0 -378
- package/test/transforms/shuffle.test.ts +0 -135
- package/test/transforms/stack.test.ts +0 -573
- package/test/transforms/string/stringCompression.test.ts +0 -120
- package/test/transforms/string/stringConcealing.test.ts +0 -299
- package/test/transforms/string/stringEncoding.test.ts +0 -95
- package/test/transforms/string/stringSplitting.test.ts +0 -135
- package/test/transforms/transform.test.ts +0 -66
- package/test/traverse.test.ts +0 -139
- package/test/util/compare.test.ts +0 -34
- package/test/util/gen.test.ts +0 -121
- package/test/util/identifiers.test.ts +0 -253
- package/test/util/insert.test.ts +0 -142
- package/test/util/math.test.ts +0 -5
- package/test/util/random.test.ts +0 -71
- /package/dist/{types.js → obfuscationResult.js} +0 -0
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
import traverse, { NodePath, Scope, Visitor } from "@babel/traverse";
|
|
2
|
+
import { PluginArg, PluginObject } from "./plugin";
|
|
3
|
+
import { Order } from "../order";
|
|
4
|
+
import { computeProbabilityMap } from "../probability";
|
|
5
|
+
import {
|
|
6
|
+
ensureComputedExpression,
|
|
7
|
+
getParentFunctionOrProgram,
|
|
8
|
+
isDefiningIdentifier,
|
|
9
|
+
isModifiedIdentifier,
|
|
10
|
+
isStrictMode,
|
|
11
|
+
isVariableIdentifier,
|
|
12
|
+
replaceDefiningIdentifierToMemberExpression,
|
|
13
|
+
} from "../utils/ast-utils";
|
|
14
|
+
import * as t from "@babel/types";
|
|
15
|
+
import { numericLiteral, deepClone } from "../utils/node";
|
|
16
|
+
import Template from "../templates/template";
|
|
17
|
+
import {
|
|
18
|
+
chance,
|
|
19
|
+
choice,
|
|
20
|
+
getRandomInteger,
|
|
21
|
+
shuffle,
|
|
22
|
+
} from "../utils/random-utils";
|
|
23
|
+
import { IntGen } from "../utils/IntGen";
|
|
24
|
+
import { ok } from "assert";
|
|
25
|
+
import { NameGen } from "../utils/NameGen";
|
|
26
|
+
import {
|
|
27
|
+
NodeSymbol,
|
|
28
|
+
UNSAFE,
|
|
29
|
+
NO_RENAME,
|
|
30
|
+
PREDICTABLE,
|
|
31
|
+
variableFunctionName,
|
|
32
|
+
WITH_STATEMENT,
|
|
33
|
+
CONTROL_OBJECTS,
|
|
34
|
+
} from "../constants";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Breaks functions into DAGs (Directed Acyclic Graphs)
|
|
38
|
+
*
|
|
39
|
+
* - 1. Break functions into chunks
|
|
40
|
+
* - 2. Shuffle chunks but remember their original position
|
|
41
|
+
* - 3. Create a Switch statement inside a While loop, each case is a chunk, and the while loops exits on the last transition.
|
|
42
|
+
*
|
|
43
|
+
* The Switch statement:
|
|
44
|
+
*
|
|
45
|
+
* - 1. The state variable controls which case will run next
|
|
46
|
+
* - 2. At the end of each case, the state variable is updated to the next block of code.
|
|
47
|
+
* - 3. The while loop continues until the the state variable is the end state.
|
|
48
|
+
*/
|
|
49
|
+
export default ({ Plugin }: PluginArg): PluginObject => {
|
|
50
|
+
const me = Plugin(Order.ControlFlowFlattening, {
|
|
51
|
+
changeData: {
|
|
52
|
+
functions: 0,
|
|
53
|
+
blocks: 0,
|
|
54
|
+
ifStatements: 0,
|
|
55
|
+
deadCode: 0,
|
|
56
|
+
variables: 0,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// in Debug mode, the output is much easier to read
|
|
61
|
+
const isDebug = false;
|
|
62
|
+
const flattenIfStatements = true; // Converts IF-statements into equivalent 'goto style of code'
|
|
63
|
+
const flattenFunctionDeclarations = true; // Converts Function Declarations into equivalent 'goto style of code'
|
|
64
|
+
const addRelativeAssignments = true; // state += (NEW_STATE - CURRENT_STATE)
|
|
65
|
+
const addDeadCode = true; // add fakes chunks of code
|
|
66
|
+
const addFakeTests = true; // case 100: case 490: case 510: ...
|
|
67
|
+
const addComplexTests = true; // case s != 49 && s - 10:
|
|
68
|
+
const addPredicateTests = true; // case scope.A + 10: ...
|
|
69
|
+
const mangleNumericalLiterals = true; // 50 => state + X
|
|
70
|
+
const mangleBooleanLiterals = true; // true => state == X
|
|
71
|
+
const addWithStatement = true; // Disabling not supported yet
|
|
72
|
+
|
|
73
|
+
const cffPrefix = me.getPlaceholder();
|
|
74
|
+
|
|
75
|
+
// Amount of blocks changed by Control Flow Flattening
|
|
76
|
+
let cffCounter = 0;
|
|
77
|
+
|
|
78
|
+
const functionsModified = new Set<t.Node>();
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
post: () => {
|
|
82
|
+
functionsModified.forEach((node) => {
|
|
83
|
+
(node as NodeSymbol)[UNSAFE] = true;
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
visitor: {
|
|
87
|
+
"Program|Function": {
|
|
88
|
+
exit(_path) {
|
|
89
|
+
let programOrFunctionPath = _path as NodePath<t.Program | t.Function>;
|
|
90
|
+
|
|
91
|
+
// Exclude loops
|
|
92
|
+
if (
|
|
93
|
+
programOrFunctionPath.find((p) => p.isForStatement() || p.isWhile())
|
|
94
|
+
)
|
|
95
|
+
return;
|
|
96
|
+
|
|
97
|
+
let programPath = _path.isProgram() ? _path : null;
|
|
98
|
+
let functionPath = _path.isFunction() ? _path : null;
|
|
99
|
+
|
|
100
|
+
let blockPath: NodePath<t.Block>;
|
|
101
|
+
if (programPath) {
|
|
102
|
+
blockPath = programPath;
|
|
103
|
+
} else {
|
|
104
|
+
let fnBlockPath = functionPath.get("body");
|
|
105
|
+
if (!fnBlockPath.isBlock()) return;
|
|
106
|
+
blockPath = fnBlockPath;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Don't apply to strict mode blocks
|
|
110
|
+
const strictModeEnforcingBlock = programOrFunctionPath.find((path) =>
|
|
111
|
+
isStrictMode(path as NodePath<t.Block>)
|
|
112
|
+
);
|
|
113
|
+
if (strictModeEnforcingBlock) return;
|
|
114
|
+
|
|
115
|
+
// Must be at least 3 statements or more
|
|
116
|
+
if (blockPath.node.body.length < 3) return;
|
|
117
|
+
|
|
118
|
+
// Check user's threshold setting
|
|
119
|
+
if (!computeProbabilityMap(me.options.controlFlowFlattening)) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Avoid unsafe functions
|
|
124
|
+
if (functionPath && (functionPath.node as NodeSymbol)[UNSAFE]) return;
|
|
125
|
+
|
|
126
|
+
programOrFunctionPath.scope.crawl();
|
|
127
|
+
|
|
128
|
+
const blockFnParent = getParentFunctionOrProgram(blockPath);
|
|
129
|
+
|
|
130
|
+
let hasIllegalNode = false;
|
|
131
|
+
const bindingNames = new Set<string>();
|
|
132
|
+
blockPath.traverse({
|
|
133
|
+
"Super|MetaProperty|AwaitExpression|YieldExpression"(path) {
|
|
134
|
+
if (
|
|
135
|
+
getParentFunctionOrProgram(path).node === blockFnParent.node
|
|
136
|
+
) {
|
|
137
|
+
hasIllegalNode = true;
|
|
138
|
+
path.stop();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
VariableDeclaration(path) {
|
|
142
|
+
if (path.node.declarations.length !== 1) {
|
|
143
|
+
hasIllegalNode = true;
|
|
144
|
+
path.stop();
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
Identifier(path) {
|
|
148
|
+
if (
|
|
149
|
+
path.node.name === variableFunctionName ||
|
|
150
|
+
path.node.name === "arguments"
|
|
151
|
+
) {
|
|
152
|
+
hasIllegalNode = true;
|
|
153
|
+
path.stop();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!path.isBindingIdentifier()) return;
|
|
158
|
+
const binding = path.scope.getBinding(path.node.name);
|
|
159
|
+
if (!binding) return;
|
|
160
|
+
|
|
161
|
+
let fnParent = path.getFunctionParent();
|
|
162
|
+
if (
|
|
163
|
+
path.key === "id" &&
|
|
164
|
+
path.parentPath.isFunctionDeclaration()
|
|
165
|
+
) {
|
|
166
|
+
fnParent = path.parentPath.getFunctionParent();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (fnParent !== functionPath) return;
|
|
170
|
+
|
|
171
|
+
if (!isDefiningIdentifier(path)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (bindingNames.has(path.node.name)) {
|
|
176
|
+
hasIllegalNode = true;
|
|
177
|
+
path.stop();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
bindingNames.add(path.node.name);
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (hasIllegalNode) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
me.changeData.blocks++;
|
|
189
|
+
|
|
190
|
+
// Limit how many numbers get entangled
|
|
191
|
+
let mangledLiteralsCreated = 0;
|
|
192
|
+
|
|
193
|
+
const cffIndex = ++cffCounter; // Start from 1
|
|
194
|
+
const prefix = cffPrefix + "_" + cffIndex;
|
|
195
|
+
|
|
196
|
+
const withIdentifier = (suffix) => {
|
|
197
|
+
var name;
|
|
198
|
+
if (isDebug) {
|
|
199
|
+
name = prefix + "_" + suffix;
|
|
200
|
+
} else {
|
|
201
|
+
name = me.obfuscator.nameGen.generate(false);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
var id = t.identifier(name);
|
|
205
|
+
|
|
206
|
+
(id as NodeSymbol)[NO_RENAME] = cffIndex;
|
|
207
|
+
return id;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const mainFnName = withIdentifier("main");
|
|
211
|
+
|
|
212
|
+
const scopeVar = withIdentifier("scope");
|
|
213
|
+
|
|
214
|
+
const stateVars = new Array(isDebug ? 1 : getRandomInteger(2, 5))
|
|
215
|
+
.fill("")
|
|
216
|
+
.map((_, i) => withIdentifier(`state_${i}`));
|
|
217
|
+
|
|
218
|
+
const argVar = withIdentifier("_arg");
|
|
219
|
+
|
|
220
|
+
const didReturnVar = withIdentifier("return");
|
|
221
|
+
|
|
222
|
+
const basicBlocks = new Map<string, BasicBlock>();
|
|
223
|
+
|
|
224
|
+
// Map labels to states
|
|
225
|
+
const stateIntGen = new IntGen();
|
|
226
|
+
|
|
227
|
+
const defaultBlockPath = blockPath;
|
|
228
|
+
|
|
229
|
+
let scopeCounter = 0;
|
|
230
|
+
|
|
231
|
+
let scopeNameGen = new NameGen(me.options.identifierGenerator);
|
|
232
|
+
if (!isDebug) {
|
|
233
|
+
scopeNameGen = me.obfuscator.nameGen;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Create 'with' object - Determines which scope gets top-level variable access
|
|
237
|
+
const withProperty = isDebug ? "with" : scopeNameGen.generate(false);
|
|
238
|
+
const withMemberExpression = new Template(
|
|
239
|
+
`${scopeVar.name}["${withProperty}"]`
|
|
240
|
+
).expression<t.MemberExpression>();
|
|
241
|
+
withMemberExpression.object[NO_RENAME] = cffIndex;
|
|
242
|
+
|
|
243
|
+
// Create 'resetWith' function - Safely resets the 'with' object to none
|
|
244
|
+
const resetWithProperty = isDebug
|
|
245
|
+
? "resetWith"
|
|
246
|
+
: scopeNameGen.generate(false);
|
|
247
|
+
|
|
248
|
+
const resetWithMemberExpression = new Template(
|
|
249
|
+
`${scopeVar.name}["${resetWithProperty}"]`
|
|
250
|
+
).expression<t.MemberExpression>();
|
|
251
|
+
resetWithMemberExpression.object[NO_RENAME] = cffIndex;
|
|
252
|
+
|
|
253
|
+
class ScopeManager {
|
|
254
|
+
isNotUsed = true;
|
|
255
|
+
requiresInitializing = true;
|
|
256
|
+
|
|
257
|
+
nameMap = new Map<string, string>();
|
|
258
|
+
nameGen = addWithStatement
|
|
259
|
+
? me.obfuscator.nameGen
|
|
260
|
+
: new NameGen(me.options.identifierGenerator);
|
|
261
|
+
|
|
262
|
+
findBestWithDiscriminant(basicBlock: BasicBlock): ScopeManager {
|
|
263
|
+
if (basicBlock !== this.initializingBasicBlock) {
|
|
264
|
+
if (this.nameMap.size > 0) return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return this.parent?.findBestWithDiscriminant(basicBlock);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
getNewName(name: string, originalNode?: t.Node) {
|
|
271
|
+
if (!this.nameMap.has(name)) {
|
|
272
|
+
let newName = this.nameGen.generate(false);
|
|
273
|
+
if (isDebug) {
|
|
274
|
+
newName = "_" + name;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// console.log(name, newName);
|
|
278
|
+
|
|
279
|
+
this.nameMap.set(name, newName);
|
|
280
|
+
|
|
281
|
+
me.changeData.variables++;
|
|
282
|
+
|
|
283
|
+
// console.log(
|
|
284
|
+
// "Renaming " +
|
|
285
|
+
// name +
|
|
286
|
+
// " to " +
|
|
287
|
+
// newName +
|
|
288
|
+
// " : " +
|
|
289
|
+
// this.scope.path.type
|
|
290
|
+
// );
|
|
291
|
+
|
|
292
|
+
return newName;
|
|
293
|
+
}
|
|
294
|
+
return this.nameMap.get(name);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
getScopeObject() {
|
|
298
|
+
return t.memberExpression(
|
|
299
|
+
deepClone(scopeVar),
|
|
300
|
+
t.stringLiteral(this.propertyName),
|
|
301
|
+
true
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
getInitializingStatement() {
|
|
306
|
+
return t.expressionStatement(
|
|
307
|
+
t.assignmentExpression(
|
|
308
|
+
"=",
|
|
309
|
+
this.getScopeObject(),
|
|
310
|
+
this.getInitializingObjectExpression()
|
|
311
|
+
)
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
getInitializingObjectExpression() {
|
|
316
|
+
return isDebug
|
|
317
|
+
? new Template(`
|
|
318
|
+
({
|
|
319
|
+
identity: "${this.propertyName}"
|
|
320
|
+
})
|
|
321
|
+
`).expression()
|
|
322
|
+
: new Template(`Object["create"](null)`).expression();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
getMemberExpression(name: string) {
|
|
326
|
+
const memberExpression = t.memberExpression(
|
|
327
|
+
this.getScopeObject(),
|
|
328
|
+
t.stringLiteral(name),
|
|
329
|
+
true
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
return memberExpression;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
propertyName: string;
|
|
336
|
+
constructor(
|
|
337
|
+
public scope: Scope,
|
|
338
|
+
public initializingBasicBlock: BasicBlock
|
|
339
|
+
) {
|
|
340
|
+
this.propertyName = isDebug
|
|
341
|
+
? "_" + scopeCounter++
|
|
342
|
+
: scopeNameGen.generate();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
get parent() {
|
|
346
|
+
return scopeToScopeManager.get(this.scope.parent);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
getObjectExpression(refreshLabel: string) {
|
|
350
|
+
let refreshScope = basicBlocks.get(refreshLabel).scopeManager;
|
|
351
|
+
let propertyMap: { [property: string]: t.Expression } = {};
|
|
352
|
+
|
|
353
|
+
let cursor = this.scope;
|
|
354
|
+
while (cursor) {
|
|
355
|
+
let parentScopeManager = scopeToScopeManager.get(cursor);
|
|
356
|
+
if (parentScopeManager) {
|
|
357
|
+
propertyMap[parentScopeManager.propertyName] =
|
|
358
|
+
t.memberExpression(
|
|
359
|
+
deepClone(scopeVar),
|
|
360
|
+
t.stringLiteral(parentScopeManager.propertyName),
|
|
361
|
+
true
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
cursor = cursor.parent;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
propertyMap[refreshScope.propertyName] =
|
|
369
|
+
refreshScope.getInitializingObjectExpression();
|
|
370
|
+
|
|
371
|
+
const properties: t.ObjectProperty[] = [];
|
|
372
|
+
for (const key in propertyMap) {
|
|
373
|
+
properties.push(
|
|
374
|
+
t.objectProperty(t.stringLiteral(key), propertyMap[key], true)
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return t.objectExpression(properties);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
hasOwnName(name: string) {
|
|
382
|
+
return this.nameMap.has(name);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const getImpossibleBasicBlocks = () => {
|
|
387
|
+
return Array.from(basicBlocks.values()).filter(
|
|
388
|
+
(block) => block.options.impossible
|
|
389
|
+
);
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const scopeToScopeManager = new Map<Scope, ScopeManager>();
|
|
393
|
+
/**
|
|
394
|
+
* A Basic Block is a sequence of instructions with no diversion except at the entry and exit points.
|
|
395
|
+
*/
|
|
396
|
+
class BasicBlock {
|
|
397
|
+
totalState: number;
|
|
398
|
+
stateValues: number[];
|
|
399
|
+
allowWithDiscriminant = true;
|
|
400
|
+
bestWithDiscriminant: ScopeManager;
|
|
401
|
+
|
|
402
|
+
get withDiscriminant() {
|
|
403
|
+
if (!this.allowWithDiscriminant) return null;
|
|
404
|
+
|
|
405
|
+
return this.bestWithDiscriminant;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private createPath() {
|
|
409
|
+
const newPath = NodePath.get<t.BlockStatement, any>({
|
|
410
|
+
hub: this.parentPath.hub,
|
|
411
|
+
parentPath: this.parentPath,
|
|
412
|
+
parent: this.parentPath.node,
|
|
413
|
+
container: this.parentPath.node.body,
|
|
414
|
+
listKey: "body", // Set the correct list key
|
|
415
|
+
key: "virtual", // Set the index of the new node
|
|
416
|
+
} as any);
|
|
417
|
+
|
|
418
|
+
newPath.scope = this.parentPath.scope;
|
|
419
|
+
newPath.parentPath = this.parentPath;
|
|
420
|
+
newPath.node = t.blockStatement([]);
|
|
421
|
+
|
|
422
|
+
this.thisPath = newPath;
|
|
423
|
+
this.thisNode = newPath.node;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
insertAfter(newNode: t.Statement) {
|
|
427
|
+
this.body.push(newNode);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
get scope() {
|
|
431
|
+
return this.parentPath.scope;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
get scopeManager() {
|
|
435
|
+
return scopeToScopeManager.get(this.scope);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
thisPath: NodePath<t.BlockStatement>;
|
|
439
|
+
thisNode: t.BlockStatement;
|
|
440
|
+
|
|
441
|
+
get body(): t.Statement[] {
|
|
442
|
+
return this.thisPath.node.body;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
createFalsePredicate(): t.Expression {
|
|
446
|
+
var predicate = this.createPredicate();
|
|
447
|
+
if (predicate.value) {
|
|
448
|
+
// Make predicate false
|
|
449
|
+
return t.unaryExpression("!", predicate.node);
|
|
450
|
+
}
|
|
451
|
+
return predicate.node;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
createTruePredicate(): t.Expression {
|
|
455
|
+
var predicate = this.createPredicate();
|
|
456
|
+
if (!predicate.value) {
|
|
457
|
+
// Make predicate true
|
|
458
|
+
return t.unaryExpression("!", predicate.node);
|
|
459
|
+
}
|
|
460
|
+
return predicate.node;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
createPredicate() {
|
|
464
|
+
var stateVarIndex = getRandomInteger(0, stateVars.length);
|
|
465
|
+
var stateValue = this.stateValues[stateVarIndex];
|
|
466
|
+
var compareValue = choice([
|
|
467
|
+
stateValue,
|
|
468
|
+
getRandomInteger(-250, 250),
|
|
469
|
+
]);
|
|
470
|
+
|
|
471
|
+
var operator: t.BinaryExpression["operator"] = choice([
|
|
472
|
+
"==",
|
|
473
|
+
"!=",
|
|
474
|
+
"<",
|
|
475
|
+
">",
|
|
476
|
+
]);
|
|
477
|
+
var compareResult;
|
|
478
|
+
switch (operator) {
|
|
479
|
+
case "==":
|
|
480
|
+
compareResult = stateValue === compareValue;
|
|
481
|
+
break;
|
|
482
|
+
case "!=":
|
|
483
|
+
compareResult = stateValue !== compareValue;
|
|
484
|
+
break;
|
|
485
|
+
case "<":
|
|
486
|
+
compareResult = stateValue < compareValue;
|
|
487
|
+
break;
|
|
488
|
+
case ">":
|
|
489
|
+
compareResult = stateValue > compareValue;
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
node: t.binaryExpression(
|
|
495
|
+
operator,
|
|
496
|
+
deepClone(stateVars[stateVarIndex]),
|
|
497
|
+
numericLiteral(compareValue)
|
|
498
|
+
),
|
|
499
|
+
value: compareResult,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
identifier(identifierName: string, scopeManager: ScopeManager) {
|
|
504
|
+
if (
|
|
505
|
+
this.withDiscriminant &&
|
|
506
|
+
this.withDiscriminant === scopeManager
|
|
507
|
+
) {
|
|
508
|
+
var id = t.identifier(identifierName);
|
|
509
|
+
(id as NodeSymbol)[NO_RENAME] = cffIndex;
|
|
510
|
+
(id as NodeSymbol)[WITH_STATEMENT] = true;
|
|
511
|
+
return id;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return scopeManager.getMemberExpression(identifierName);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
initializedScope: ScopeManager;
|
|
518
|
+
|
|
519
|
+
constructor(
|
|
520
|
+
public label: string,
|
|
521
|
+
public parentPath: NodePath<t.Block>,
|
|
522
|
+
public options: { impossible?: boolean } = {}
|
|
523
|
+
) {
|
|
524
|
+
this.createPath();
|
|
525
|
+
|
|
526
|
+
if (isDebug) {
|
|
527
|
+
// States in debug mode are just 1, 2, 3, ...
|
|
528
|
+
this.totalState = basicBlocks.size + 1;
|
|
529
|
+
} else {
|
|
530
|
+
this.totalState = stateIntGen.generate();
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Correct state values
|
|
534
|
+
// Start with random numbers
|
|
535
|
+
this.stateValues = stateVars.map(() =>
|
|
536
|
+
getRandomInteger(-250, 250)
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
// Try to re-use old state values to make diffs smaller
|
|
540
|
+
if (basicBlocks.size > 1) {
|
|
541
|
+
const lastBlock = [...basicBlocks.values()].at(-1);
|
|
542
|
+
this.stateValues = lastBlock.stateValues.map((oldValue, i) => {
|
|
543
|
+
return choice([oldValue, this.stateValues[i]]);
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Correct one of the values so that the accumulated sum is equal to the state
|
|
548
|
+
const correctIndex = getRandomInteger(0, this.stateValues.length);
|
|
549
|
+
|
|
550
|
+
const getCurrentState = () =>
|
|
551
|
+
this.stateValues.reduce((a, b) => a + b, 0);
|
|
552
|
+
|
|
553
|
+
// Correct the value
|
|
554
|
+
this.stateValues[correctIndex] =
|
|
555
|
+
this.totalState -
|
|
556
|
+
(getCurrentState() - this.stateValues[correctIndex]);
|
|
557
|
+
|
|
558
|
+
ok(getCurrentState() === this.totalState);
|
|
559
|
+
|
|
560
|
+
// Store basic block
|
|
561
|
+
basicBlocks.set(label, this);
|
|
562
|
+
|
|
563
|
+
// Create a new scope manager if it doesn't exist
|
|
564
|
+
if (!scopeToScopeManager.has(this.scope)) {
|
|
565
|
+
scopeToScopeManager.set(
|
|
566
|
+
this.scope,
|
|
567
|
+
new ScopeManager(this.scope, this)
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
this.initializedScope = this.scopeManager;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Stage 1: Flatten the code into Basic Blocks
|
|
577
|
+
*
|
|
578
|
+
* This involves transforming the Control Flow / Scopes into blocks with 'goto' statements
|
|
579
|
+
*
|
|
580
|
+
* - A block is simply a sequence of statements
|
|
581
|
+
* - A block can have a 'goto' statement to another block
|
|
582
|
+
* - A block original scope is preserved
|
|
583
|
+
*
|
|
584
|
+
* 'goto' & Scopes are transformed in Stage 2
|
|
585
|
+
*/
|
|
586
|
+
|
|
587
|
+
const switchLabel = me.getPlaceholder();
|
|
588
|
+
const breakStatement = () => {
|
|
589
|
+
return t.breakStatement(t.identifier(switchLabel));
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const startLabel = me.getPlaceholder();
|
|
593
|
+
const endLabel = me.getPlaceholder();
|
|
594
|
+
|
|
595
|
+
let currentBasicBlock = new BasicBlock(startLabel, blockPath);
|
|
596
|
+
currentBasicBlock.allowWithDiscriminant = false;
|
|
597
|
+
|
|
598
|
+
const gotoFunctionName =
|
|
599
|
+
"GOTO__" +
|
|
600
|
+
me.getPlaceholder() +
|
|
601
|
+
"__IF_YOU_CAN_READ_THIS_THERE_IS_A_BUG";
|
|
602
|
+
|
|
603
|
+
function GotoControlStatement(label: string) {
|
|
604
|
+
return new Template(`
|
|
605
|
+
${gotoFunctionName}("${label}");
|
|
606
|
+
`).single();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Ends the current block and starts a new one
|
|
610
|
+
function endCurrentBasicBlock({
|
|
611
|
+
jumpToNext = true,
|
|
612
|
+
nextLabel = me.getPlaceholder(),
|
|
613
|
+
prevJumpTo = null,
|
|
614
|
+
nextBlockPath = null,
|
|
615
|
+
} = {}) {
|
|
616
|
+
ok(nextBlockPath);
|
|
617
|
+
|
|
618
|
+
if (prevJumpTo) {
|
|
619
|
+
currentBasicBlock.insertAfter(GotoControlStatement(prevJumpTo));
|
|
620
|
+
} else if (jumpToNext) {
|
|
621
|
+
currentBasicBlock.insertAfter(GotoControlStatement(nextLabel));
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
currentBasicBlock = new BasicBlock(nextLabel, nextBlockPath);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const prependNodes: t.Statement[] = [];
|
|
628
|
+
const functionExpressions: [
|
|
629
|
+
string,
|
|
630
|
+
string,
|
|
631
|
+
BasicBlock,
|
|
632
|
+
t.FunctionExpression
|
|
633
|
+
][] = [];
|
|
634
|
+
|
|
635
|
+
function flattenIntoBasicBlocks(
|
|
636
|
+
bodyIn: NodePath<t.Statement>[] | NodePath<t.Block>
|
|
637
|
+
) {
|
|
638
|
+
// if (!Array.isArray(bodyIn) && bodyIn.isBlock()) {
|
|
639
|
+
// currentBasicBlock.parentPath = bodyIn;
|
|
640
|
+
// }
|
|
641
|
+
const body = Array.isArray(bodyIn) ? bodyIn : bodyIn.get("body");
|
|
642
|
+
const nextBlockPath = Array.isArray(bodyIn)
|
|
643
|
+
? currentBasicBlock.parentPath
|
|
644
|
+
: bodyIn;
|
|
645
|
+
|
|
646
|
+
for (const index in body) {
|
|
647
|
+
const statement = body[index];
|
|
648
|
+
|
|
649
|
+
// Keep Imports before everything else
|
|
650
|
+
if (statement.isImportDeclaration()) {
|
|
651
|
+
prependNodes.push(statement.node);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (statement.isFunctionDeclaration()) {
|
|
656
|
+
const fnName = statement.node.id.name;
|
|
657
|
+
let isIllegal = false;
|
|
658
|
+
|
|
659
|
+
if (
|
|
660
|
+
!flattenFunctionDeclarations ||
|
|
661
|
+
statement.node.async ||
|
|
662
|
+
statement.node.generator ||
|
|
663
|
+
(statement.node as NodeSymbol)[UNSAFE]
|
|
664
|
+
) {
|
|
665
|
+
isIllegal = true;
|
|
666
|
+
}
|
|
667
|
+
let oldBasicBlock = currentBasicBlock;
|
|
668
|
+
let fnLabel = me.getPlaceholder();
|
|
669
|
+
|
|
670
|
+
let sm = currentBasicBlock.scopeManager;
|
|
671
|
+
let rename = sm.getNewName(fnName);
|
|
672
|
+
|
|
673
|
+
sm.scope.bindings[fnName].kind = "var";
|
|
674
|
+
|
|
675
|
+
const hoistedBasicBlock = Array.from(basicBlocks.values()).find(
|
|
676
|
+
(block) => block.parentPath === currentBasicBlock.parentPath
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
if (isIllegal) {
|
|
680
|
+
hoistedBasicBlock.body.unshift(statement.node);
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
me.changeData.functions++;
|
|
685
|
+
|
|
686
|
+
const functionExpression = t.functionExpression(
|
|
687
|
+
null,
|
|
688
|
+
[],
|
|
689
|
+
t.blockStatement([])
|
|
690
|
+
);
|
|
691
|
+
functionExpressions.push([
|
|
692
|
+
fnName,
|
|
693
|
+
fnLabel,
|
|
694
|
+
currentBasicBlock,
|
|
695
|
+
functionExpression,
|
|
696
|
+
]);
|
|
697
|
+
|
|
698
|
+
// Change the function declaration to a variable declaration
|
|
699
|
+
hoistedBasicBlock.body.unshift(
|
|
700
|
+
t.variableDeclaration("var", [
|
|
701
|
+
t.variableDeclarator(
|
|
702
|
+
t.identifier(fnName),
|
|
703
|
+
functionExpression
|
|
704
|
+
),
|
|
705
|
+
])
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
const blockStatement = statement.get("body");
|
|
709
|
+
|
|
710
|
+
endCurrentBasicBlock({
|
|
711
|
+
nextLabel: fnLabel,
|
|
712
|
+
nextBlockPath: blockStatement,
|
|
713
|
+
jumpToNext: false,
|
|
714
|
+
});
|
|
715
|
+
let fnTopBlock = currentBasicBlock;
|
|
716
|
+
|
|
717
|
+
// Implicit return
|
|
718
|
+
blockStatement.node.body.push(
|
|
719
|
+
t.returnStatement(t.identifier("undefined"))
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
flattenIntoBasicBlocks(blockStatement);
|
|
723
|
+
scopeToScopeManager.get(statement.scope).requiresInitializing =
|
|
724
|
+
false;
|
|
725
|
+
basicBlocks.get(fnLabel).allowWithDiscriminant = false;
|
|
726
|
+
|
|
727
|
+
// Debug label
|
|
728
|
+
if (isDebug) {
|
|
729
|
+
fnTopBlock.body.unshift(
|
|
730
|
+
t.expressionStatement(
|
|
731
|
+
t.stringLiteral(
|
|
732
|
+
"Function " +
|
|
733
|
+
statement.node.id.name +
|
|
734
|
+
" -> Renamed to " +
|
|
735
|
+
rename
|
|
736
|
+
)
|
|
737
|
+
)
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Unpack parameters
|
|
742
|
+
if (statement.node.params.length > 0) {
|
|
743
|
+
fnTopBlock.body.unshift(
|
|
744
|
+
t.variableDeclaration("var", [
|
|
745
|
+
t.variableDeclarator(
|
|
746
|
+
t.arrayPattern(statement.node.params),
|
|
747
|
+
deepClone(argVar)
|
|
748
|
+
),
|
|
749
|
+
])
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
// Change bindings from 'param' to 'var'
|
|
753
|
+
statement.get("params").forEach((param) => {
|
|
754
|
+
let ids = param.getBindingIdentifierPaths();
|
|
755
|
+
// Loop over the record of binding identifiers
|
|
756
|
+
for (const identifierName in ids) {
|
|
757
|
+
const identifierPath = ids[identifierName];
|
|
758
|
+
if (identifierPath.getFunctionParent() === statement) {
|
|
759
|
+
const binding =
|
|
760
|
+
statement.scope.getBinding(identifierName);
|
|
761
|
+
|
|
762
|
+
if (binding) {
|
|
763
|
+
binding.kind = "var";
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
currentBasicBlock = oldBasicBlock;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Convert IF statements into Basic Blocks
|
|
775
|
+
if (statement.isIfStatement() && flattenIfStatements) {
|
|
776
|
+
const test = statement.get("test");
|
|
777
|
+
const consequent = statement.get("consequent");
|
|
778
|
+
const alternate = statement.get("alternate");
|
|
779
|
+
|
|
780
|
+
// Both consequent and alternate are blocks
|
|
781
|
+
if (
|
|
782
|
+
consequent.isBlockStatement() &&
|
|
783
|
+
(!alternate.node || alternate.isBlockStatement())
|
|
784
|
+
) {
|
|
785
|
+
me.changeData.ifStatements++;
|
|
786
|
+
|
|
787
|
+
const consequentLabel = me.getPlaceholder();
|
|
788
|
+
const alternateLabel = alternate.node
|
|
789
|
+
? me.getPlaceholder()
|
|
790
|
+
: null;
|
|
791
|
+
const afterPath = me.getPlaceholder();
|
|
792
|
+
|
|
793
|
+
currentBasicBlock.insertAfter(
|
|
794
|
+
t.ifStatement(
|
|
795
|
+
test.node,
|
|
796
|
+
GotoControlStatement(consequentLabel),
|
|
797
|
+
alternateLabel
|
|
798
|
+
? GotoControlStatement(alternateLabel)
|
|
799
|
+
: GotoControlStatement(afterPath)
|
|
800
|
+
)
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
const oldBasicBlock = currentBasicBlock;
|
|
804
|
+
|
|
805
|
+
endCurrentBasicBlock({
|
|
806
|
+
jumpToNext: false,
|
|
807
|
+
nextLabel: consequentLabel,
|
|
808
|
+
nextBlockPath: consequent,
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
flattenIntoBasicBlocks(consequent);
|
|
812
|
+
currentBasicBlock.initializedScope =
|
|
813
|
+
oldBasicBlock.scopeManager;
|
|
814
|
+
|
|
815
|
+
if (alternate.isBlockStatement()) {
|
|
816
|
+
endCurrentBasicBlock({
|
|
817
|
+
prevJumpTo: afterPath,
|
|
818
|
+
nextLabel: alternateLabel,
|
|
819
|
+
nextBlockPath: alternate,
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
flattenIntoBasicBlocks(alternate);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
endCurrentBasicBlock({
|
|
826
|
+
prevJumpTo: afterPath,
|
|
827
|
+
nextLabel: afterPath,
|
|
828
|
+
nextBlockPath: oldBasicBlock.parentPath,
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (
|
|
836
|
+
Number(index) === body.length - 1 &&
|
|
837
|
+
statement.isExpressionStatement() &&
|
|
838
|
+
statement.findParent((p) => p.isBlock()) === blockPath
|
|
839
|
+
) {
|
|
840
|
+
// Return the result of the last expression for eval() purposes
|
|
841
|
+
currentBasicBlock.insertAfter(
|
|
842
|
+
t.returnStatement(statement.get("expression").node)
|
|
843
|
+
);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 3 or more statements should be split more
|
|
848
|
+
if (
|
|
849
|
+
currentBasicBlock.body.length > 1 &&
|
|
850
|
+
chance(50 + currentBasicBlock.body.length)
|
|
851
|
+
) {
|
|
852
|
+
endCurrentBasicBlock({
|
|
853
|
+
nextBlockPath: nextBlockPath,
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// console.log(currentBasicBlock.thisPath.type);
|
|
858
|
+
// console.log(currentBasicBlock.body);
|
|
859
|
+
currentBasicBlock.body.push(statement.node);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Convert our code into Basic Blocks
|
|
864
|
+
flattenIntoBasicBlocks(blockPath.get("body"));
|
|
865
|
+
|
|
866
|
+
// Ensure always jumped to the Program end
|
|
867
|
+
endCurrentBasicBlock({
|
|
868
|
+
jumpToNext: true,
|
|
869
|
+
nextLabel: endLabel,
|
|
870
|
+
nextBlockPath: defaultBlockPath,
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
basicBlocks.get(endLabel).allowWithDiscriminant = false;
|
|
874
|
+
|
|
875
|
+
// Add with / reset with logic
|
|
876
|
+
basicBlocks.get(startLabel).body.unshift(
|
|
877
|
+
new Template(`
|
|
878
|
+
{resetWithMemberExpression} = function(newStateValues){
|
|
879
|
+
{withMemberExpression} = undefined;
|
|
880
|
+
{arrayPattern} = newStateValues
|
|
881
|
+
}
|
|
882
|
+
`).single({
|
|
883
|
+
arrayPattern: t.arrayPattern(deepClone(stateVars)),
|
|
884
|
+
resetWithMemberExpression: deepClone(resetWithMemberExpression),
|
|
885
|
+
withMemberExpression: deepClone(withMemberExpression),
|
|
886
|
+
})
|
|
887
|
+
);
|
|
888
|
+
|
|
889
|
+
if (!isDebug && addDeadCode) {
|
|
890
|
+
// DEAD CODE 1/3: Add fake chunks that are never reached
|
|
891
|
+
const fakeChunkCount = getRandomInteger(1, 5);
|
|
892
|
+
for (let i = 0; i < fakeChunkCount; i++) {
|
|
893
|
+
// These chunks just jump somewhere random, they are never executed
|
|
894
|
+
// so it could contain any code
|
|
895
|
+
const fakeBlock = new BasicBlock(me.getPlaceholder(), blockPath, {
|
|
896
|
+
impossible: true,
|
|
897
|
+
});
|
|
898
|
+
let fakeJump;
|
|
899
|
+
do {
|
|
900
|
+
fakeJump = choice(Array.from(basicBlocks.keys()));
|
|
901
|
+
} while (fakeJump === fakeBlock.label);
|
|
902
|
+
|
|
903
|
+
fakeBlock.insertAfter(GotoControlStatement(fakeJump));
|
|
904
|
+
me.changeData.deadCode++;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// DEAD CODE 2/3: Add fake jumps to really mess with deobfuscators
|
|
908
|
+
// "irreducible control flow"
|
|
909
|
+
basicBlocks.forEach((basicBlock) => {
|
|
910
|
+
if (chance(30 - basicBlocks.size)) {
|
|
911
|
+
let randomLabel = choice(Array.from(basicBlocks.keys()));
|
|
912
|
+
|
|
913
|
+
// The `false` literal will be mangled
|
|
914
|
+
basicBlock.insertAfter(
|
|
915
|
+
new Template(`
|
|
916
|
+
if({predicate}){
|
|
917
|
+
{goto}
|
|
918
|
+
}
|
|
919
|
+
`).single({
|
|
920
|
+
goto: GotoControlStatement(randomLabel),
|
|
921
|
+
predicate: basicBlock.createFalsePredicate(),
|
|
922
|
+
})
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
me.changeData.deadCode++;
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
// DEAD CODE 3/3: Clone chunks but these chunks are never ran
|
|
929
|
+
const cloneChunkCount = getRandomInteger(1, 5);
|
|
930
|
+
for (let i = 0; i < cloneChunkCount; i++) {
|
|
931
|
+
let randomChunk = choice(Array.from(basicBlocks.values()));
|
|
932
|
+
|
|
933
|
+
// Don't double define functions
|
|
934
|
+
let hasDeclaration = randomChunk.body.find((stmt) => {
|
|
935
|
+
return t.isDeclaration(stmt);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
if (!hasDeclaration) {
|
|
939
|
+
let clonedChunk = new BasicBlock(
|
|
940
|
+
me.getPlaceholder(),
|
|
941
|
+
randomChunk.parentPath,
|
|
942
|
+
{
|
|
943
|
+
impossible: true,
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
randomChunk.thisNode.body
|
|
948
|
+
.map((x) => deepClone(x))
|
|
949
|
+
.forEach((node) => {
|
|
950
|
+
if (node.type === "EmptyStatement") return;
|
|
951
|
+
clonedChunk.insertAfter(node);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
me.changeData.deadCode++;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// Select scope managers for the with statement
|
|
960
|
+
for (const basicBlock of basicBlocks.values()) {
|
|
961
|
+
basicBlock.bestWithDiscriminant =
|
|
962
|
+
basicBlock.initializedScope?.findBestWithDiscriminant(basicBlock);
|
|
963
|
+
|
|
964
|
+
if (isDebug && basicBlock.withDiscriminant) {
|
|
965
|
+
basicBlock.body.unshift(
|
|
966
|
+
t.expressionStatement(
|
|
967
|
+
t.stringLiteral(
|
|
968
|
+
"With " + basicBlock.withDiscriminant.propertyName
|
|
969
|
+
)
|
|
970
|
+
)
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Stage 2: Transform 'goto' statements into valid JavaScript
|
|
977
|
+
*
|
|
978
|
+
* - 'goto' is replaced with equivalent state updates and break statements
|
|
979
|
+
* - Original identifiers are converted into member expressions
|
|
980
|
+
*/
|
|
981
|
+
|
|
982
|
+
// Remap 'GotoStatement' to actual state assignments and Break statements
|
|
983
|
+
for (const basicBlock of basicBlocks.values()) {
|
|
984
|
+
const { stateValues: currentStateValues } = basicBlock;
|
|
985
|
+
// Wrap the statement in a Babel path to allow traversal
|
|
986
|
+
|
|
987
|
+
const outerFn = getParentFunctionOrProgram(basicBlock.parentPath);
|
|
988
|
+
|
|
989
|
+
function isWithinSameFunction(path: NodePath) {
|
|
990
|
+
let fn = getParentFunctionOrProgram(path);
|
|
991
|
+
return fn.node === outerFn.node;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
let visitor: Visitor = {
|
|
995
|
+
BooleanLiteral: {
|
|
996
|
+
exit(boolPath) {
|
|
997
|
+
// Don't mangle booleans in debug mode
|
|
998
|
+
if (
|
|
999
|
+
isDebug ||
|
|
1000
|
+
!mangleBooleanLiterals ||
|
|
1001
|
+
me.isSkipped(boolPath)
|
|
1002
|
+
)
|
|
1003
|
+
return;
|
|
1004
|
+
|
|
1005
|
+
if (!isWithinSameFunction(boolPath)) return;
|
|
1006
|
+
if (chance(50 + mangledLiteralsCreated)) return;
|
|
1007
|
+
|
|
1008
|
+
mangledLiteralsCreated++;
|
|
1009
|
+
|
|
1010
|
+
const index = getRandomInteger(0, stateVars.length - 1);
|
|
1011
|
+
const stateVar = stateVars[index];
|
|
1012
|
+
const stateVarValue = currentStateValues[index];
|
|
1013
|
+
|
|
1014
|
+
const compareValue = choice([
|
|
1015
|
+
getRandomInteger(-250, 250),
|
|
1016
|
+
stateVarValue,
|
|
1017
|
+
]);
|
|
1018
|
+
const compareResult = stateVarValue === compareValue;
|
|
1019
|
+
|
|
1020
|
+
const newExpression = t.binaryExpression(
|
|
1021
|
+
boolPath.node.value === compareResult ? "==" : "!=",
|
|
1022
|
+
deepClone(stateVar),
|
|
1023
|
+
numericLiteral(compareValue)
|
|
1024
|
+
);
|
|
1025
|
+
|
|
1026
|
+
ensureComputedExpression(boolPath);
|
|
1027
|
+
boolPath.replaceWith(newExpression);
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
// Mangle numbers with the state values
|
|
1031
|
+
NumericLiteral: {
|
|
1032
|
+
exit(numPath) {
|
|
1033
|
+
// Don't mangle numbers in debug mode
|
|
1034
|
+
if (
|
|
1035
|
+
isDebug ||
|
|
1036
|
+
!mangleNumericalLiterals ||
|
|
1037
|
+
me.isSkipped(numPath)
|
|
1038
|
+
)
|
|
1039
|
+
return;
|
|
1040
|
+
|
|
1041
|
+
const num = numPath.node.value;
|
|
1042
|
+
if (
|
|
1043
|
+
Math.floor(num) !== num ||
|
|
1044
|
+
Math.abs(num) > 100_000 ||
|
|
1045
|
+
!Number.isFinite(num) ||
|
|
1046
|
+
Number.isNaN(num)
|
|
1047
|
+
)
|
|
1048
|
+
return;
|
|
1049
|
+
|
|
1050
|
+
if (!isWithinSameFunction(numPath)) return;
|
|
1051
|
+
if (chance(50 + mangledLiteralsCreated)) return;
|
|
1052
|
+
|
|
1053
|
+
mangledLiteralsCreated++;
|
|
1054
|
+
|
|
1055
|
+
const index = getRandomInteger(0, stateVars.length - 1);
|
|
1056
|
+
const stateVar = stateVars[index];
|
|
1057
|
+
|
|
1058
|
+
// num = 50
|
|
1059
|
+
// stateVar = 30
|
|
1060
|
+
// stateVar + 30
|
|
1061
|
+
|
|
1062
|
+
const diff = t.binaryExpression(
|
|
1063
|
+
"+",
|
|
1064
|
+
deepClone(stateVar),
|
|
1065
|
+
me.skip(numericLiteral(num - currentStateValues[index]))
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
ensureComputedExpression(numPath);
|
|
1069
|
+
|
|
1070
|
+
numPath.replaceWith(diff);
|
|
1071
|
+
numPath.skip();
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
|
|
1075
|
+
Identifier: {
|
|
1076
|
+
exit(path: NodePath<t.Identifier>) {
|
|
1077
|
+
if (!isVariableIdentifier(path)) return;
|
|
1078
|
+
if (me.isSkipped(path)) return;
|
|
1079
|
+
if ((path.node as NodeSymbol)[NO_RENAME] === cffIndex) return;
|
|
1080
|
+
|
|
1081
|
+
const identifierName = path.node.name;
|
|
1082
|
+
if (identifierName === gotoFunctionName) return;
|
|
1083
|
+
|
|
1084
|
+
var binding = basicBlock.scope.getBinding(identifierName);
|
|
1085
|
+
if (!binding) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (
|
|
1090
|
+
binding.kind === "var" ||
|
|
1091
|
+
binding.kind === "let" ||
|
|
1092
|
+
binding.kind === "const"
|
|
1093
|
+
) {
|
|
1094
|
+
} else {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// console.log("No binding found for " + identifierName);
|
|
1099
|
+
|
|
1100
|
+
var scopeManager = scopeToScopeManager.get(binding.scope);
|
|
1101
|
+
if (!scopeManager) return;
|
|
1102
|
+
|
|
1103
|
+
let newName = scopeManager.getNewName(
|
|
1104
|
+
identifierName,
|
|
1105
|
+
path.node
|
|
1106
|
+
);
|
|
1107
|
+
|
|
1108
|
+
let memberExpression: t.MemberExpression | t.Identifier =
|
|
1109
|
+
scopeManager.getMemberExpression(newName);
|
|
1110
|
+
|
|
1111
|
+
scopeManager.isNotUsed = false;
|
|
1112
|
+
|
|
1113
|
+
if (isDefiningIdentifier(path)) {
|
|
1114
|
+
replaceDefiningIdentifierToMemberExpression(
|
|
1115
|
+
path,
|
|
1116
|
+
memberExpression
|
|
1117
|
+
);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (!path.container) return;
|
|
1122
|
+
|
|
1123
|
+
var isModified = isModifiedIdentifier(path);
|
|
1124
|
+
|
|
1125
|
+
if (
|
|
1126
|
+
basicBlock.withDiscriminant &&
|
|
1127
|
+
basicBlock.withDiscriminant === scopeManager &&
|
|
1128
|
+
basicBlock.withDiscriminant.hasOwnName(identifierName)
|
|
1129
|
+
) {
|
|
1130
|
+
if (!isModified) {
|
|
1131
|
+
memberExpression = basicBlock.identifier(
|
|
1132
|
+
newName,
|
|
1133
|
+
scopeManager
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
me.skip(memberExpression);
|
|
1139
|
+
|
|
1140
|
+
path.replaceWith(memberExpression);
|
|
1141
|
+
path.skip();
|
|
1142
|
+
},
|
|
1143
|
+
},
|
|
1144
|
+
|
|
1145
|
+
// Top-level returns set additional flag to indicate that the function has returned
|
|
1146
|
+
ReturnStatement: {
|
|
1147
|
+
exit(path) {
|
|
1148
|
+
var functionParent = path.getFunctionParent();
|
|
1149
|
+
if (
|
|
1150
|
+
!functionParent ||
|
|
1151
|
+
functionParent.get("body") !== blockPath
|
|
1152
|
+
)
|
|
1153
|
+
return;
|
|
1154
|
+
|
|
1155
|
+
const returnArgument =
|
|
1156
|
+
path.node.argument || t.identifier("undefined");
|
|
1157
|
+
|
|
1158
|
+
path.node.argument = new Template(`
|
|
1159
|
+
({didReturnVar} = true, {returnArgument})
|
|
1160
|
+
`).expression({
|
|
1161
|
+
returnArgument,
|
|
1162
|
+
didReturnVar: deepClone(didReturnVar),
|
|
1163
|
+
});
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
|
|
1167
|
+
// goto() calls are replaced with state updates and break statements
|
|
1168
|
+
CallExpression: {
|
|
1169
|
+
exit(path) {
|
|
1170
|
+
if (
|
|
1171
|
+
t.isIdentifier(path.node.callee) &&
|
|
1172
|
+
path.node.callee.name === gotoFunctionName
|
|
1173
|
+
) {
|
|
1174
|
+
const [labelNode] = path.node.arguments;
|
|
1175
|
+
|
|
1176
|
+
ok(t.isStringLiteral(labelNode));
|
|
1177
|
+
const label = labelNode.value;
|
|
1178
|
+
|
|
1179
|
+
const jumpBlock = basicBlocks.get(label);
|
|
1180
|
+
ok(jumpBlock, "Label not found: " + label);
|
|
1181
|
+
|
|
1182
|
+
const {
|
|
1183
|
+
stateValues: newStateValues,
|
|
1184
|
+
totalState: newTotalState,
|
|
1185
|
+
} = jumpBlock;
|
|
1186
|
+
|
|
1187
|
+
const assignments: t.Expression[] = [];
|
|
1188
|
+
let needsIndividualAssignments = true;
|
|
1189
|
+
|
|
1190
|
+
if (jumpBlock.withDiscriminant) {
|
|
1191
|
+
assignments.push(
|
|
1192
|
+
t.assignmentExpression(
|
|
1193
|
+
"=",
|
|
1194
|
+
deepClone(withMemberExpression),
|
|
1195
|
+
jumpBlock.withDiscriminant.getScopeObject()
|
|
1196
|
+
)
|
|
1197
|
+
);
|
|
1198
|
+
} else if (basicBlock.withDiscriminant) {
|
|
1199
|
+
assignments.push(
|
|
1200
|
+
t.callExpression(deepClone(resetWithMemberExpression), [
|
|
1201
|
+
t.arrayExpression(newStateValues.map(numericLiteral)),
|
|
1202
|
+
])
|
|
1203
|
+
);
|
|
1204
|
+
needsIndividualAssignments = false;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
if (needsIndividualAssignments) {
|
|
1208
|
+
for (let i = 0; i < stateVars.length; i++) {
|
|
1209
|
+
const oldValue = currentStateValues[i];
|
|
1210
|
+
const newValue = newStateValues[i];
|
|
1211
|
+
|
|
1212
|
+
// console.log(oldValue, newValue);
|
|
1213
|
+
if (oldValue === newValue) continue; // No diff needed if the value doesn't change
|
|
1214
|
+
|
|
1215
|
+
const leftValue = jumpBlock.withDiscriminant
|
|
1216
|
+
? jumpBlock.withDiscriminant.getMemberExpression(
|
|
1217
|
+
stateVars[i].name
|
|
1218
|
+
)
|
|
1219
|
+
: deepClone(stateVars[i]);
|
|
1220
|
+
|
|
1221
|
+
let assignment = t.assignmentExpression(
|
|
1222
|
+
"=",
|
|
1223
|
+
leftValue,
|
|
1224
|
+
numericLiteral(newValue)
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
if (!isDebug && addRelativeAssignments) {
|
|
1228
|
+
// Use diffs to create confusing code
|
|
1229
|
+
assignment = t.assignmentExpression(
|
|
1230
|
+
"+=",
|
|
1231
|
+
deepClone(stateVars[i]),
|
|
1232
|
+
numericLiteral(newValue - oldValue)
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
assignments.push(assignment);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// Add debug label
|
|
1241
|
+
if (isDebug) {
|
|
1242
|
+
assignments.unshift(
|
|
1243
|
+
t.stringLiteral("Goto " + newTotalState)
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
path.parentPath
|
|
1248
|
+
.replaceWith(
|
|
1249
|
+
t.expressionStatement(t.sequenceExpression(assignments))
|
|
1250
|
+
)[0]
|
|
1251
|
+
.skip();
|
|
1252
|
+
|
|
1253
|
+
// Add break after updating state variables
|
|
1254
|
+
path.insertAfter(breakStatement());
|
|
1255
|
+
}
|
|
1256
|
+
},
|
|
1257
|
+
},
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
basicBlock.thisPath.traverse(visitor);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* Stage 3: Create a switch statement to handle the control flow
|
|
1265
|
+
*
|
|
1266
|
+
* - Add fake / impossible blocks
|
|
1267
|
+
* - Add fake / predicates to the switch cases tests
|
|
1268
|
+
*/
|
|
1269
|
+
|
|
1270
|
+
// Create global numbers for predicates
|
|
1271
|
+
const mainScope = basicBlocks.get(startLabel).scopeManager;
|
|
1272
|
+
const predicateNumbers = new Map<string, number>();
|
|
1273
|
+
const predicateNumberCount =
|
|
1274
|
+
isDebug || !addPredicateTests ? 0 : getRandomInteger(2, 5);
|
|
1275
|
+
for (let i = 0; i < predicateNumberCount; i++) {
|
|
1276
|
+
const name = mainScope.getNewName(
|
|
1277
|
+
me.getPlaceholder("predicate_" + i)
|
|
1278
|
+
);
|
|
1279
|
+
|
|
1280
|
+
const number = getRandomInteger(-250, 250);
|
|
1281
|
+
predicateNumbers.set(name, number);
|
|
1282
|
+
|
|
1283
|
+
const createAssignment = (value: number) => {
|
|
1284
|
+
return new Template(`
|
|
1285
|
+
{memberExpression} = {number}
|
|
1286
|
+
`).single({
|
|
1287
|
+
memberExpression: mainScope.getMemberExpression(name),
|
|
1288
|
+
number: numericLiteral(number),
|
|
1289
|
+
});
|
|
1290
|
+
};
|
|
1291
|
+
|
|
1292
|
+
basicBlocks.get(startLabel).body.unshift(createAssignment(number));
|
|
1293
|
+
|
|
1294
|
+
// Add random assignments to impossible blocks
|
|
1295
|
+
var fakeAssignmentCount = getRandomInteger(0, 3);
|
|
1296
|
+
for (let i = 0; i < fakeAssignmentCount; i++) {
|
|
1297
|
+
var impossibleBlock = choice(getImpossibleBasicBlocks());
|
|
1298
|
+
if (impossibleBlock) {
|
|
1299
|
+
impossibleBlock.body.unshift(
|
|
1300
|
+
createAssignment(getRandomInteger(-250, 250))
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// Add scope initializations: scope["_0"] = {identity: "_0"}
|
|
1307
|
+
for (const scopeManager of scopeToScopeManager.values()) {
|
|
1308
|
+
if (scopeManager.isNotUsed) continue;
|
|
1309
|
+
if (!scopeManager.requiresInitializing) continue;
|
|
1310
|
+
if (scopeManager.initializingBasicBlock.label === startLabel)
|
|
1311
|
+
continue;
|
|
1312
|
+
|
|
1313
|
+
scopeManager.initializingBasicBlock.body.unshift(
|
|
1314
|
+
scopeManager.getInitializingStatement()
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
let switchCases: t.SwitchCase[] = [];
|
|
1319
|
+
let blocks = Array.from(basicBlocks.values());
|
|
1320
|
+
if (!isDebug && addFakeTests) {
|
|
1321
|
+
shuffle(blocks);
|
|
1322
|
+
}
|
|
1323
|
+
for (const block of blocks) {
|
|
1324
|
+
if (block.label === endLabel) {
|
|
1325
|
+
// ok(block.body.length === 0);
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
let test: t.Expression = numericLiteral(block.totalState);
|
|
1330
|
+
|
|
1331
|
+
// Predicate tests cannot apply to the start label
|
|
1332
|
+
// As that's when the numbers are initialized
|
|
1333
|
+
if (
|
|
1334
|
+
!isDebug &&
|
|
1335
|
+
addPredicateTests &&
|
|
1336
|
+
block.label !== startLabel &&
|
|
1337
|
+
chance(50)
|
|
1338
|
+
) {
|
|
1339
|
+
let predicateName = choice(Array.from(predicateNumbers.keys()));
|
|
1340
|
+
if (predicateName) {
|
|
1341
|
+
let number = predicateNumbers.get(predicateName);
|
|
1342
|
+
let diff = block.totalState - number;
|
|
1343
|
+
|
|
1344
|
+
test = t.binaryExpression(
|
|
1345
|
+
"+",
|
|
1346
|
+
mainScope.getMemberExpression(predicateName),
|
|
1347
|
+
numericLiteral(diff)
|
|
1348
|
+
);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// Add complex tests
|
|
1353
|
+
if (!isDebug && addComplexTests && chance(50)) {
|
|
1354
|
+
// Create complex test expressions for each switch case
|
|
1355
|
+
|
|
1356
|
+
// case STATE+X:
|
|
1357
|
+
let stateVarIndex = getRandomInteger(0, stateVars.length);
|
|
1358
|
+
|
|
1359
|
+
let stateValues = block.stateValues;
|
|
1360
|
+
let difference = stateValues[stateVarIndex] - block.totalState;
|
|
1361
|
+
|
|
1362
|
+
let conditionNodes: t.Expression[] = [];
|
|
1363
|
+
let alreadyConditionedItems = new Set<string>();
|
|
1364
|
+
|
|
1365
|
+
// This code finds clash conditions and adds them to 'conditionNodes' array
|
|
1366
|
+
Array.from(basicBlocks.keys()).forEach((label) => {
|
|
1367
|
+
if (label !== block.label) {
|
|
1368
|
+
let labelStates = basicBlocks.get(label).stateValues;
|
|
1369
|
+
let totalState = labelStates.reduce((a, b) => a + b, 0);
|
|
1370
|
+
|
|
1371
|
+
if (totalState === labelStates[stateVarIndex] - difference) {
|
|
1372
|
+
let differentIndex = labelStates.findIndex(
|
|
1373
|
+
(v, i) => v !== stateValues[i]
|
|
1374
|
+
);
|
|
1375
|
+
if (differentIndex !== -1) {
|
|
1376
|
+
let expressionAsString =
|
|
1377
|
+
stateVars[differentIndex].name +
|
|
1378
|
+
"!=" +
|
|
1379
|
+
labelStates[differentIndex];
|
|
1380
|
+
if (!alreadyConditionedItems.has(expressionAsString)) {
|
|
1381
|
+
alreadyConditionedItems.add(expressionAsString);
|
|
1382
|
+
|
|
1383
|
+
conditionNodes.push(
|
|
1384
|
+
t.binaryExpression(
|
|
1385
|
+
"!=",
|
|
1386
|
+
deepClone(stateVars[differentIndex]),
|
|
1387
|
+
numericLiteral(labelStates[differentIndex])
|
|
1388
|
+
)
|
|
1389
|
+
);
|
|
1390
|
+
}
|
|
1391
|
+
} else {
|
|
1392
|
+
conditionNodes.push(
|
|
1393
|
+
t.binaryExpression(
|
|
1394
|
+
"!=",
|
|
1395
|
+
deepClone(discriminant),
|
|
1396
|
+
numericLiteral(totalState)
|
|
1397
|
+
)
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
// case STATE!=Y && STATE+X
|
|
1405
|
+
test = t.binaryExpression(
|
|
1406
|
+
"-",
|
|
1407
|
+
deepClone(stateVars[stateVarIndex]),
|
|
1408
|
+
numericLiteral(difference)
|
|
1409
|
+
);
|
|
1410
|
+
|
|
1411
|
+
// Use the 'conditionNodes' to not cause state clashing issues
|
|
1412
|
+
conditionNodes.forEach((conditionNode) => {
|
|
1413
|
+
test = t.logicalExpression("&&", conditionNode, test);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const tests = [test];
|
|
1418
|
+
|
|
1419
|
+
if (!isDebug && addFakeTests && chance(50)) {
|
|
1420
|
+
// Add fake tests
|
|
1421
|
+
let fakeTestCount = getRandomInteger(1, 3);
|
|
1422
|
+
for (let i = 0; i < fakeTestCount; i++) {
|
|
1423
|
+
tests.push(numericLiteral(stateIntGen.generate()));
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
shuffle(tests);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
const lastTest = tests.pop();
|
|
1430
|
+
|
|
1431
|
+
for (const test of tests) {
|
|
1432
|
+
switchCases.push(t.switchCase(test, []));
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
switchCases.push(t.switchCase(lastTest, block.thisPath.node.body));
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
if (!isDebug && addFakeTests) {
|
|
1439
|
+
// A random test can be 'default'
|
|
1440
|
+
choice(switchCases).test = null;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const discriminant = new Template(`
|
|
1444
|
+
${stateVars.map((x) => x.name).join(" + ")}
|
|
1445
|
+
`).expression<t.Expression>();
|
|
1446
|
+
|
|
1447
|
+
traverse(t.program([t.expressionStatement(discriminant)]), {
|
|
1448
|
+
Identifier(path) {
|
|
1449
|
+
(path.node as NodeSymbol)[NO_RENAME] = cffIndex;
|
|
1450
|
+
},
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
// Create a new SwitchStatement
|
|
1454
|
+
const switchStatement = t.labeledStatement(
|
|
1455
|
+
t.identifier(switchLabel),
|
|
1456
|
+
t.switchStatement(discriminant, switchCases)
|
|
1457
|
+
);
|
|
1458
|
+
|
|
1459
|
+
const startStateValues = basicBlocks.get(startLabel).stateValues;
|
|
1460
|
+
const endTotalState = basicBlocks.get(endLabel).totalState;
|
|
1461
|
+
|
|
1462
|
+
const whileStatement = t.whileStatement(
|
|
1463
|
+
t.binaryExpression(
|
|
1464
|
+
"!==",
|
|
1465
|
+
deepClone(discriminant),
|
|
1466
|
+
numericLiteral(endTotalState)
|
|
1467
|
+
),
|
|
1468
|
+
t.blockStatement([
|
|
1469
|
+
t.withStatement(
|
|
1470
|
+
new Template(
|
|
1471
|
+
`{withDiscriminant} || Object["create"](null)`
|
|
1472
|
+
).expression({
|
|
1473
|
+
withDiscriminant: deepClone(withMemberExpression),
|
|
1474
|
+
}),
|
|
1475
|
+
t.blockStatement([switchStatement])
|
|
1476
|
+
),
|
|
1477
|
+
])
|
|
1478
|
+
);
|
|
1479
|
+
|
|
1480
|
+
const parameters: t.Identifier[] = [
|
|
1481
|
+
...stateVars,
|
|
1482
|
+
argVar,
|
|
1483
|
+
scopeVar,
|
|
1484
|
+
].map((id) => deepClone(id));
|
|
1485
|
+
|
|
1486
|
+
const parametersNames: string[] = parameters.map((id) => id.name);
|
|
1487
|
+
|
|
1488
|
+
for (var [
|
|
1489
|
+
originalFnName,
|
|
1490
|
+
fnLabel,
|
|
1491
|
+
basicBlock,
|
|
1492
|
+
fn,
|
|
1493
|
+
] of functionExpressions) {
|
|
1494
|
+
const { scopeManager } = basicBlock;
|
|
1495
|
+
const { stateValues } = basicBlocks.get(fnLabel);
|
|
1496
|
+
|
|
1497
|
+
const argumentsRestName = me.getPlaceholder();
|
|
1498
|
+
|
|
1499
|
+
const argumentsNodes = [];
|
|
1500
|
+
for (const parameterName of parametersNames) {
|
|
1501
|
+
const stateIndex = stateVars
|
|
1502
|
+
.map((x) => x.name)
|
|
1503
|
+
.indexOf(parameterName);
|
|
1504
|
+
if (stateIndex !== -1) {
|
|
1505
|
+
argumentsNodes.push(numericLiteral(stateValues[stateIndex]));
|
|
1506
|
+
} else if (parameterName === argVar.name) {
|
|
1507
|
+
argumentsNodes.push(t.identifier(argumentsRestName));
|
|
1508
|
+
} else if (parameterName === scopeVar.name) {
|
|
1509
|
+
argumentsNodes.push(scopeManager.getObjectExpression(fnLabel));
|
|
1510
|
+
} else {
|
|
1511
|
+
ok(false);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
Object.assign(
|
|
1516
|
+
fn,
|
|
1517
|
+
new Template(`
|
|
1518
|
+
(function (...${argumentsRestName}){
|
|
1519
|
+
${
|
|
1520
|
+
isDebug
|
|
1521
|
+
? `"Calling ${originalFnName}, Label: ${fnLabel}";`
|
|
1522
|
+
: ""
|
|
1523
|
+
}
|
|
1524
|
+
return {callExpression}
|
|
1525
|
+
})
|
|
1526
|
+
|
|
1527
|
+
`).expression({
|
|
1528
|
+
callExpression: t.callExpression(
|
|
1529
|
+
deepClone(mainFnName),
|
|
1530
|
+
argumentsNodes
|
|
1531
|
+
),
|
|
1532
|
+
})
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const mainFnDeclaration = t.functionDeclaration(
|
|
1537
|
+
deepClone(mainFnName),
|
|
1538
|
+
parameters,
|
|
1539
|
+
t.blockStatement([whileStatement])
|
|
1540
|
+
);
|
|
1541
|
+
|
|
1542
|
+
(mainFnDeclaration as NodeSymbol)[PREDICTABLE] = true;
|
|
1543
|
+
|
|
1544
|
+
var startProgramExpression = t.callExpression(deepClone(mainFnName), [
|
|
1545
|
+
...startStateValues.map((stateValue) => numericLiteral(stateValue)),
|
|
1546
|
+
t.identifier("undefined"),
|
|
1547
|
+
basicBlocks
|
|
1548
|
+
.get(startLabel)
|
|
1549
|
+
.scopeManager.getObjectExpression(startLabel),
|
|
1550
|
+
]);
|
|
1551
|
+
|
|
1552
|
+
var resultVar = withIdentifier("result");
|
|
1553
|
+
var allowReturns = blockPath.find((p) => p.isFunction());
|
|
1554
|
+
|
|
1555
|
+
const startProgramStatements = new Template(`
|
|
1556
|
+
${allowReturns ? `var {didReturnVar};` : ""}
|
|
1557
|
+
var {resultVar} = {startProgramExpression};
|
|
1558
|
+
${
|
|
1559
|
+
allowReturns
|
|
1560
|
+
? `
|
|
1561
|
+
if({didReturnVar}){
|
|
1562
|
+
return {resultVar};
|
|
1563
|
+
}`
|
|
1564
|
+
: ""
|
|
1565
|
+
}
|
|
1566
|
+
`).compile({
|
|
1567
|
+
startProgramExpression,
|
|
1568
|
+
didReturnVar: () => deepClone(didReturnVar),
|
|
1569
|
+
resultVar: () => deepClone(resultVar),
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
blockPath.node.body = [
|
|
1573
|
+
...prependNodes,
|
|
1574
|
+
mainFnDeclaration,
|
|
1575
|
+
...startProgramStatements,
|
|
1576
|
+
];
|
|
1577
|
+
|
|
1578
|
+
functionsModified.add(programOrFunctionPath.node);
|
|
1579
|
+
|
|
1580
|
+
// Reset all bindings here
|
|
1581
|
+
blockPath.scope.bindings = Object.create(null);
|
|
1582
|
+
|
|
1583
|
+
// Bindings changed - breaking control objects
|
|
1584
|
+
delete (blockPath.node as NodeSymbol)[CONTROL_OBJECTS];
|
|
1585
|
+
|
|
1586
|
+
// Register new declarations
|
|
1587
|
+
for (var node of blockPath.get("body")) {
|
|
1588
|
+
blockPath.scope.registerDeclaration(node);
|
|
1589
|
+
}
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
},
|
|
1593
|
+
};
|
|
1594
|
+
};
|