js-confuser 2.0.0-alpha.5 → 2.0.1
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 +43 -43
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
- package/.github/workflows/node.js.yml +28 -28
- package/.prettierrc +4 -4
- package/CHANGELOG.md +1015 -989
- package/CODE_OF_CONDUCT.md +131 -131
- package/CONTRIBUTING.md +52 -52
- package/LICENSE +21 -21
- package/Migration.md +72 -71
- package/README.md +86 -78
- package/dist/constants.js +43 -43
- package/dist/index.js +14 -23
- package/dist/obfuscator.js +31 -25
- package/dist/order.js +4 -4
- package/dist/presets.js +31 -31
- package/dist/templates/integrityTemplate.js +4 -4
- package/dist/templates/template.js +1 -2
- package/dist/transforms/astScrambler.js +1 -2
- package/dist/transforms/calculator.js +1 -2
- package/dist/transforms/controlFlowFlattening.js +93 -63
- package/dist/transforms/deadCode.js +1 -2
- package/dist/transforms/dispatcher.js +4 -5
- package/dist/transforms/extraction/duplicateLiteralsRemoval.js +1 -2
- package/dist/transforms/extraction/objectExtraction.js +1 -2
- package/dist/transforms/finalizer.js +1 -2
- package/dist/transforms/flatten.js +1 -2
- package/dist/transforms/identifier/globalConcealing.js +15 -2
- package/dist/transforms/identifier/movedDeclarations.js +8 -7
- package/dist/transforms/identifier/renameVariables.js +7 -7
- package/dist/transforms/lock/integrity.js +11 -10
- package/dist/transforms/lock/lock.js +2 -3
- package/dist/transforms/minify.js +11 -29
- package/dist/transforms/opaquePredicates.js +1 -2
- package/dist/transforms/pack.js +5 -2
- package/dist/transforms/plugin.js +18 -19
- package/dist/transforms/preparation.js +16 -16
- package/dist/transforms/renameLabels.js +1 -2
- package/dist/transforms/rgf.js +8 -9
- package/dist/transforms/shuffle.js +1 -2
- package/dist/transforms/string/encoding.js +1 -2
- package/dist/transforms/string/stringCompression.js +3 -4
- package/dist/transforms/string/stringConcealing.js +8 -3
- package/dist/transforms/string/stringEncoding.js +1 -2
- package/dist/transforms/variableMasking.js +1 -2
- package/dist/utils/NameGen.js +2 -2
- package/dist/utils/PredicateGen.js +1 -2
- package/dist/utils/ast-utils.js +87 -88
- package/dist/utils/function-utils.js +8 -8
- package/dist/utils/node.js +5 -6
- package/dist/utils/object-utils.js +4 -4
- package/dist/utils/random-utils.js +20 -20
- package/dist/utils/static-utils.js +1 -2
- package/dist/validateOptions.js +4 -7
- package/index.d.ts +17 -17
- package/package.json +61 -59
- package/src/constants.ts +168 -168
- package/src/index.ts +118 -118
- package/src/obfuscationResult.ts +49 -49
- package/src/obfuscator.ts +501 -497
- package/src/options.ts +407 -407
- package/src/order.ts +54 -54
- package/src/presets.ts +125 -125
- package/src/templates/bufferToStringTemplate.ts +57 -57
- package/src/templates/deadCodeTemplates.ts +1185 -1185
- package/src/templates/getGlobalTemplate.ts +76 -76
- package/src/templates/integrityTemplate.ts +64 -64
- package/src/templates/setFunctionLengthTemplate.ts +11 -11
- package/src/templates/stringCompressionTemplate.ts +20 -20
- package/src/templates/tamperProtectionTemplates.ts +120 -120
- package/src/templates/template.ts +224 -224
- package/src/transforms/astScrambler.ts +99 -99
- package/src/transforms/calculator.ts +99 -99
- package/src/transforms/controlFlowFlattening.ts +1716 -1664
- package/src/transforms/deadCode.ts +82 -82
- package/src/transforms/dispatcher.ts +450 -450
- package/src/transforms/extraction/duplicateLiteralsRemoval.ts +156 -158
- package/src/transforms/extraction/objectExtraction.ts +186 -186
- package/src/transforms/finalizer.ts +74 -74
- package/src/transforms/flatten.ts +421 -420
- package/src/transforms/identifier/globalConcealing.ts +315 -295
- package/src/transforms/identifier/movedDeclarations.ts +252 -251
- package/src/transforms/identifier/renameVariables.ts +328 -321
- package/src/transforms/lock/integrity.ts +117 -114
- package/src/transforms/lock/lock.ts +418 -425
- package/src/transforms/minify.ts +615 -629
- package/src/transforms/opaquePredicates.ts +100 -100
- package/src/transforms/pack.ts +239 -231
- package/src/transforms/plugin.ts +173 -173
- package/src/transforms/preparation.ts +349 -347
- package/src/transforms/renameLabels.ts +175 -175
- package/src/transforms/rgf.ts +322 -322
- package/src/transforms/shuffle.ts +82 -82
- package/src/transforms/string/encoding.ts +144 -144
- package/src/transforms/string/stringCompression.ts +128 -128
- package/src/transforms/string/stringConcealing.ts +312 -298
- package/src/transforms/string/stringEncoding.ts +80 -80
- package/src/transforms/string/stringSplitting.ts +77 -77
- package/src/transforms/variableMasking.ts +257 -257
- package/src/utils/IntGen.ts +33 -33
- package/src/utils/NameGen.ts +116 -116
- package/src/utils/PredicateGen.ts +61 -61
- package/src/utils/ast-utils.ts +663 -663
- package/src/utils/function-utils.ts +50 -50
- package/src/utils/gen-utils.ts +48 -48
- package/src/utils/node.ts +78 -78
- package/src/utils/object-utils.ts +21 -21
- package/src/utils/random-utils.ts +93 -93
- package/src/utils/static-utils.ts +66 -66
- package/src/validateOptions.ts +256 -259
- package/tsconfig.json +13 -14
- package/dist/probability.js +0 -1
- package/dist/transforms/functionOutlining.js +0 -230
- package/dist/utils/ControlObject.js +0 -125
|
@@ -1,450 +1,450 @@
|
|
|
1
|
-
import { PluginArg, PluginObject } from "./plugin";
|
|
2
|
-
import { NodePath } from "@babel/traverse";
|
|
3
|
-
import * as t from "@babel/types";
|
|
4
|
-
import Template from "../templates/template";
|
|
5
|
-
import { ok } from "assert";
|
|
6
|
-
import { chance, getRandomString } from "../utils/random-utils";
|
|
7
|
-
import { Order } from "../order";
|
|
8
|
-
import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants";
|
|
9
|
-
import {
|
|
10
|
-
computeFunctionLength,
|
|
11
|
-
isVariableFunctionIdentifier,
|
|
12
|
-
} from "../utils/function-utils";
|
|
13
|
-
import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate";
|
|
14
|
-
import { numericLiteral } from "../utils/node";
|
|
15
|
-
import {
|
|
16
|
-
isStrictMode,
|
|
17
|
-
isVariableIdentifier,
|
|
18
|
-
prependProgram,
|
|
19
|
-
} from "../utils/ast-utils";
|
|
20
|
-
|
|
21
|
-
export default ({ Plugin }: PluginArg): PluginObject => {
|
|
22
|
-
const me = Plugin(Order.Dispatcher, {
|
|
23
|
-
changeData: {
|
|
24
|
-
functions: 0,
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
let dispatcherCounter = 0;
|
|
28
|
-
|
|
29
|
-
const setFunctionLength = me.getPlaceholder("d_fnLength");
|
|
30
|
-
|
|
31
|
-
// in Debug mode, function names are preserved
|
|
32
|
-
const isDebug = false;
|
|
33
|
-
|
|
34
|
-
return {
|
|
35
|
-
visitor: {
|
|
36
|
-
"Program|Function": {
|
|
37
|
-
exit(_path) {
|
|
38
|
-
const blockPath = _path as NodePath<t.Program | t.Function>;
|
|
39
|
-
|
|
40
|
-
if (blockPath.isProgram()) {
|
|
41
|
-
blockPath.scope.crawl();
|
|
42
|
-
|
|
43
|
-
// Don't insert function length code when disabled
|
|
44
|
-
// Instead insert empty function as the identifier is still referenced
|
|
45
|
-
var insertNode = t.functionDeclaration(
|
|
46
|
-
t.identifier(setFunctionLength),
|
|
47
|
-
[],
|
|
48
|
-
t.blockStatement([])
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
if (me.options.preserveFunctionLength) {
|
|
52
|
-
// Insert function length code
|
|
53
|
-
insertNode = SetFunctionLengthTemplate.single({
|
|
54
|
-
fnName: setFunctionLength,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
me.skip(prependProgram(_path, insertNode));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if ((blockPath.node as NodeSymbol)[UNSAFE]) return;
|
|
62
|
-
|
|
63
|
-
// For testing
|
|
64
|
-
// if (!blockPath.isProgram()) return;
|
|
65
|
-
|
|
66
|
-
var blockStatement: NodePath<t.Block> = blockPath.isProgram()
|
|
67
|
-
? blockPath
|
|
68
|
-
: (blockPath.get("body") as NodePath<t.BlockStatement>);
|
|
69
|
-
|
|
70
|
-
// Track functions and illegal ones
|
|
71
|
-
// A function is illegal if:
|
|
72
|
-
// - the function is async or generator
|
|
73
|
-
// - the function is redefined
|
|
74
|
-
// - the function uses 'this', 'eval', or 'arguments'
|
|
75
|
-
var functionPaths = new Map<
|
|
76
|
-
string,
|
|
77
|
-
NodePath<t.FunctionDeclaration>
|
|
78
|
-
>();
|
|
79
|
-
var illegalNames = new Set<string>();
|
|
80
|
-
|
|
81
|
-
// Scan for function declarations
|
|
82
|
-
blockPath.traverse({
|
|
83
|
-
// Check for reassigned / redefined functions
|
|
84
|
-
BindingIdentifier: {
|
|
85
|
-
exit(path: NodePath<t.Identifier>) {
|
|
86
|
-
if (!isVariableIdentifier(path)) return;
|
|
87
|
-
|
|
88
|
-
const name = path.node.name;
|
|
89
|
-
if (!path.parentPath?.isFunctionDeclaration()) {
|
|
90
|
-
illegalNames.add(name);
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
// Find functions eligible for dispatching
|
|
95
|
-
FunctionDeclaration: {
|
|
96
|
-
exit(path: NodePath<t.FunctionDeclaration>) {
|
|
97
|
-
const name = path.node.id.name;
|
|
98
|
-
// If the function is not named, we can't dispatch it
|
|
99
|
-
if (!name) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Do not apply to async or generator functions
|
|
104
|
-
if (path.node.async || path.node.generator) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Do not apply to functions in nested scopes
|
|
109
|
-
if (path.parentPath !== blockStatement || me.isSkipped(path)) {
|
|
110
|
-
illegalNames.add(name);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (isStrictMode(path)) {
|
|
115
|
-
illegalNames.add(name);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Do not apply to unsafe functions, redefined functions, or internal obfuscator functions
|
|
120
|
-
if (
|
|
121
|
-
(path.node as NodeSymbol)[UNSAFE] ||
|
|
122
|
-
functionPaths.has(name) ||
|
|
123
|
-
me.obfuscator.isInternalVariable(name)
|
|
124
|
-
) {
|
|
125
|
-
illegalNames.add(name);
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
var hasAssignmentPattern = false;
|
|
130
|
-
|
|
131
|
-
for (var param of path.get("params")) {
|
|
132
|
-
if (param.isAssignmentPattern()) {
|
|
133
|
-
hasAssignmentPattern = true;
|
|
134
|
-
break;
|
|
135
|
-
}
|
|
136
|
-
param.traverse({
|
|
137
|
-
AssignmentPattern(innerPath) {
|
|
138
|
-
var fn = innerPath.getFunctionParent();
|
|
139
|
-
if (fn === path) {
|
|
140
|
-
hasAssignmentPattern = true;
|
|
141
|
-
innerPath.stop();
|
|
142
|
-
} else {
|
|
143
|
-
innerPath.skip();
|
|
144
|
-
}
|
|
145
|
-
},
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
if (hasAssignmentPattern) break;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Functions with default parameters are not fully supported
|
|
152
|
-
// (Could be a Function Expression referencing outer scope)
|
|
153
|
-
if (hasAssignmentPattern) {
|
|
154
|
-
illegalNames.add(name);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
functionPaths.set(name, path);
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
for (let name of illegalNames) {
|
|
164
|
-
functionPaths.delete(name);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
for (var name of functionPaths.keys()) {
|
|
168
|
-
if (!me.computeProbabilityMap(me.options.dispatcher, name)) {
|
|
169
|
-
functionPaths.delete(name);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// No functions here to change
|
|
174
|
-
if (functionPaths.size === 0) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
me.changeData.functions += functionPaths.size;
|
|
179
|
-
|
|
180
|
-
const dispatcherName =
|
|
181
|
-
me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++;
|
|
182
|
-
const payloadName = me.getPlaceholder() + "_payload";
|
|
183
|
-
const cacheName = me.getPlaceholder() + "_cache";
|
|
184
|
-
const newNameMapping = new Map<string, string>();
|
|
185
|
-
|
|
186
|
-
const keys = {
|
|
187
|
-
placeholderNoMeaning: isDebug ? "noMeaning" : getRandomString(10),
|
|
188
|
-
clearPayload: isDebug ? "clearPayload" : getRandomString(10),
|
|
189
|
-
nonCall: isDebug ? "nonCall" : getRandomString(10),
|
|
190
|
-
returnAsObject: isDebug ? "returnAsObject" : getRandomString(10),
|
|
191
|
-
returnAsObjectProperty: isDebug
|
|
192
|
-
? "returnAsObjectProperty"
|
|
193
|
-
: getRandomString(10),
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
for (var name of functionPaths.keys()) {
|
|
197
|
-
newNameMapping.set(
|
|
198
|
-
name,
|
|
199
|
-
isDebug ? "_" + name : getRandomString(6) /** "_" + name */
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Find identifiers calling/referencing the functions
|
|
204
|
-
blockPath.traverse({
|
|
205
|
-
ReferencedIdentifier: {
|
|
206
|
-
exit(path: NodePath<t.Identifier | t.JSXIdentifier>) {
|
|
207
|
-
if (path.isJSX()) return;
|
|
208
|
-
if (isVariableFunctionIdentifier(path)) return;
|
|
209
|
-
const name = path.node.name;
|
|
210
|
-
|
|
211
|
-
var fnPath = functionPaths.get(name);
|
|
212
|
-
if (!fnPath) return;
|
|
213
|
-
|
|
214
|
-
var newName = newNameMapping.get(name);
|
|
215
|
-
|
|
216
|
-
// Do not replace if not referencing the actual function
|
|
217
|
-
if (path.scope.getBinding(name).path !== fnPath) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const createDispatcherCall = (name, flagArg?) => {
|
|
222
|
-
var dispatcherArgs = [t.stringLiteral(name)];
|
|
223
|
-
if (flagArg) {
|
|
224
|
-
dispatcherArgs.push(t.stringLiteral(flagArg));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
var asObject = chance(50);
|
|
228
|
-
|
|
229
|
-
if (asObject) {
|
|
230
|
-
if (dispatcherArgs.length < 2) {
|
|
231
|
-
dispatcherArgs.push(
|
|
232
|
-
t.stringLiteral(keys.placeholderNoMeaning)
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
dispatcherArgs.push(t.stringLiteral(keys.returnAsObject));
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
var callExpression: t.CallExpression | t.NewExpression =
|
|
239
|
-
t.callExpression(
|
|
240
|
-
t.identifier(dispatcherName),
|
|
241
|
-
dispatcherArgs
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
if (!asObject) {
|
|
245
|
-
return callExpression;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (chance(50)) {
|
|
249
|
-
(callExpression as t.Node).type = "NewExpression";
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return t.memberExpression(
|
|
253
|
-
callExpression,
|
|
254
|
-
t.stringLiteral(keys.returnAsObjectProperty),
|
|
255
|
-
true
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
// Replace the identifier with a call to the function
|
|
260
|
-
const parentPath = path.parentPath;
|
|
261
|
-
if (path.key === "callee" && parentPath?.isCallExpression()) {
|
|
262
|
-
var expressions: t.Expression[] = [];
|
|
263
|
-
var callArguments = parentPath.node.arguments;
|
|
264
|
-
|
|
265
|
-
if (callArguments.length === 0) {
|
|
266
|
-
expressions.push(
|
|
267
|
-
// Call the function
|
|
268
|
-
createDispatcherCall(newName, keys.clearPayload)
|
|
269
|
-
);
|
|
270
|
-
} else {
|
|
271
|
-
expressions.push(
|
|
272
|
-
// Prepare the payload arguments
|
|
273
|
-
t.assignmentExpression(
|
|
274
|
-
"=",
|
|
275
|
-
t.identifier(payloadName),
|
|
276
|
-
t.arrayExpression(callArguments as t.Expression[])
|
|
277
|
-
),
|
|
278
|
-
|
|
279
|
-
// Call the function
|
|
280
|
-
createDispatcherCall(newName)
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const output =
|
|
285
|
-
expressions.length === 1
|
|
286
|
-
? expressions[0]
|
|
287
|
-
: t.sequenceExpression(expressions);
|
|
288
|
-
|
|
289
|
-
if (!parentPath.container) return;
|
|
290
|
-
parentPath.replaceWith(output);
|
|
291
|
-
} else {
|
|
292
|
-
if (!path.container) return;
|
|
293
|
-
// Replace non-invocation references with a 'cached' version of the function
|
|
294
|
-
path.replaceWith(createDispatcherCall(newName, keys.nonCall));
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
const fnLengthProperties = [];
|
|
301
|
-
|
|
302
|
-
// Create the dispatcher function
|
|
303
|
-
const objectExpression = t.objectExpression(
|
|
304
|
-
Array.from(newNameMapping).map(([name, newName]) => {
|
|
305
|
-
const originalPath = functionPaths.get(name);
|
|
306
|
-
const originalFn = originalPath.node;
|
|
307
|
-
|
|
308
|
-
if (me.options.preserveFunctionLength) {
|
|
309
|
-
const fnLength = computeFunctionLength(originalPath);
|
|
310
|
-
|
|
311
|
-
if (fnLength >= 1) {
|
|
312
|
-
// 0 is already the default
|
|
313
|
-
fnLengthProperties.push(
|
|
314
|
-
t.objectProperty(
|
|
315
|
-
t.stringLiteral(newName),
|
|
316
|
-
numericLiteral(fnLength)
|
|
317
|
-
)
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const newBody = [...originalFn.body.body];
|
|
323
|
-
ok(Array.isArray(newBody));
|
|
324
|
-
|
|
325
|
-
// Unpack parameters
|
|
326
|
-
if (originalFn.params.length > 0) {
|
|
327
|
-
newBody.unshift(
|
|
328
|
-
t.variableDeclaration("var", [
|
|
329
|
-
t.variableDeclarator(
|
|
330
|
-
t.arrayPattern([...originalFn.params]),
|
|
331
|
-
t.identifier(payloadName)
|
|
332
|
-
),
|
|
333
|
-
])
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Add debug label
|
|
338
|
-
if (isDebug) {
|
|
339
|
-
newBody.unshift(
|
|
340
|
-
t.expressionStatement(
|
|
341
|
-
t.stringLiteral(`Dispatcher: ${name} -> ${newName}`)
|
|
342
|
-
)
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const functionExpression = t.functionExpression(
|
|
347
|
-
null,
|
|
348
|
-
[],
|
|
349
|
-
t.blockStatement(newBody)
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
for (var symbol of Object.getOwnPropertySymbols(originalFn)) {
|
|
353
|
-
(functionExpression as any)[symbol] = (originalFn as any)[
|
|
354
|
-
symbol
|
|
355
|
-
];
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
(functionExpression as NodeSymbol)[PREDICTABLE] = true;
|
|
359
|
-
|
|
360
|
-
return t.objectProperty(
|
|
361
|
-
t.stringLiteral(newName),
|
|
362
|
-
|
|
363
|
-
functionExpression
|
|
364
|
-
);
|
|
365
|
-
})
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
const fnLengths = t.objectExpression(fnLengthProperties);
|
|
369
|
-
|
|
370
|
-
const dispatcher = new Template(`
|
|
371
|
-
function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) {
|
|
372
|
-
var output;
|
|
373
|
-
var fns = {objectExpression};
|
|
374
|
-
|
|
375
|
-
if(flagArg === "${keys.clearPayload}") {
|
|
376
|
-
${payloadName} = [];
|
|
377
|
-
}
|
|
378
|
-
if(flagArg === "${keys.nonCall}") {
|
|
379
|
-
function createFunction(){
|
|
380
|
-
var fn = function(...args){
|
|
381
|
-
${payloadName} = args;
|
|
382
|
-
return fns[name].apply(this);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
var fnLength = fnLengths[name];
|
|
386
|
-
if(fnLength) {
|
|
387
|
-
${setFunctionLength}(fn, fnLength);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
return fn;
|
|
391
|
-
}
|
|
392
|
-
output = ${cacheName}[name] || (${cacheName}[name] = createFunction());
|
|
393
|
-
} else {
|
|
394
|
-
output = fns[name]();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if(returnTypeArg === "${keys.returnAsObject}") {
|
|
398
|
-
return { "${keys.returnAsObjectProperty}": output };
|
|
399
|
-
} else {
|
|
400
|
-
return output;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
`).single({
|
|
404
|
-
objectExpression,
|
|
405
|
-
fnLengthsObjectExpression: fnLengths,
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
(dispatcher as NodeSymbol)[PREDICTABLE] = true;
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Prepends the node into the block. (And registers the declaration)
|
|
412
|
-
* @param node
|
|
413
|
-
*/
|
|
414
|
-
function prepend(node: t.Statement) {
|
|
415
|
-
var newPath = blockStatement.unshiftContainer<any, any, any>(
|
|
416
|
-
"body",
|
|
417
|
-
node
|
|
418
|
-
)[0];
|
|
419
|
-
blockStatement.scope.registerDeclaration(newPath);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Insert the dispatcher function
|
|
423
|
-
prepend(dispatcher);
|
|
424
|
-
|
|
425
|
-
// Insert the payload variable
|
|
426
|
-
prepend(
|
|
427
|
-
t.variableDeclaration("var", [
|
|
428
|
-
t.variableDeclarator(t.identifier(payloadName)),
|
|
429
|
-
])
|
|
430
|
-
);
|
|
431
|
-
|
|
432
|
-
// Insert the cache variable
|
|
433
|
-
prepend(
|
|
434
|
-
t.variableDeclaration("var", [
|
|
435
|
-
t.variableDeclarator(
|
|
436
|
-
t.identifier(cacheName),
|
|
437
|
-
new Template(`Object["create"](null)`).expression()
|
|
438
|
-
),
|
|
439
|
-
])
|
|
440
|
-
);
|
|
441
|
-
|
|
442
|
-
// Remove original functions
|
|
443
|
-
for (let path of functionPaths.values()) {
|
|
444
|
-
path.remove();
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
};
|
|
450
|
-
};
|
|
1
|
+
import { PluginArg, PluginObject } from "./plugin";
|
|
2
|
+
import { NodePath } from "@babel/traverse";
|
|
3
|
+
import * as t from "@babel/types";
|
|
4
|
+
import Template from "../templates/template";
|
|
5
|
+
import { ok } from "assert";
|
|
6
|
+
import { chance, getRandomString } from "../utils/random-utils";
|
|
7
|
+
import { Order } from "../order";
|
|
8
|
+
import { NodeSymbol, PREDICTABLE, UNSAFE } from "../constants";
|
|
9
|
+
import {
|
|
10
|
+
computeFunctionLength,
|
|
11
|
+
isVariableFunctionIdentifier,
|
|
12
|
+
} from "../utils/function-utils";
|
|
13
|
+
import { SetFunctionLengthTemplate } from "../templates/setFunctionLengthTemplate";
|
|
14
|
+
import { numericLiteral } from "../utils/node";
|
|
15
|
+
import {
|
|
16
|
+
isStrictMode,
|
|
17
|
+
isVariableIdentifier,
|
|
18
|
+
prependProgram,
|
|
19
|
+
} from "../utils/ast-utils";
|
|
20
|
+
|
|
21
|
+
export default ({ Plugin }: PluginArg): PluginObject => {
|
|
22
|
+
const me = Plugin(Order.Dispatcher, {
|
|
23
|
+
changeData: {
|
|
24
|
+
functions: 0,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
let dispatcherCounter = 0;
|
|
28
|
+
|
|
29
|
+
const setFunctionLength = me.getPlaceholder("d_fnLength");
|
|
30
|
+
|
|
31
|
+
// in Debug mode, function names are preserved
|
|
32
|
+
const isDebug = false;
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
visitor: {
|
|
36
|
+
"Program|Function": {
|
|
37
|
+
exit(_path) {
|
|
38
|
+
const blockPath = _path as NodePath<t.Program | t.Function>;
|
|
39
|
+
|
|
40
|
+
if (blockPath.isProgram()) {
|
|
41
|
+
blockPath.scope.crawl();
|
|
42
|
+
|
|
43
|
+
// Don't insert function length code when disabled
|
|
44
|
+
// Instead insert empty function as the identifier is still referenced
|
|
45
|
+
var insertNode = t.functionDeclaration(
|
|
46
|
+
t.identifier(setFunctionLength),
|
|
47
|
+
[],
|
|
48
|
+
t.blockStatement([])
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (me.options.preserveFunctionLength) {
|
|
52
|
+
// Insert function length code
|
|
53
|
+
insertNode = SetFunctionLengthTemplate.single({
|
|
54
|
+
fnName: setFunctionLength,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
me.skip(prependProgram(_path, insertNode));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ((blockPath.node as NodeSymbol)[UNSAFE]) return;
|
|
62
|
+
|
|
63
|
+
// For testing
|
|
64
|
+
// if (!blockPath.isProgram()) return;
|
|
65
|
+
|
|
66
|
+
var blockStatement: NodePath<t.Block> = blockPath.isProgram()
|
|
67
|
+
? blockPath
|
|
68
|
+
: (blockPath.get("body") as NodePath<t.BlockStatement>);
|
|
69
|
+
|
|
70
|
+
// Track functions and illegal ones
|
|
71
|
+
// A function is illegal if:
|
|
72
|
+
// - the function is async or generator
|
|
73
|
+
// - the function is redefined
|
|
74
|
+
// - the function uses 'this', 'eval', or 'arguments'
|
|
75
|
+
var functionPaths = new Map<
|
|
76
|
+
string,
|
|
77
|
+
NodePath<t.FunctionDeclaration>
|
|
78
|
+
>();
|
|
79
|
+
var illegalNames = new Set<string>();
|
|
80
|
+
|
|
81
|
+
// Scan for function declarations
|
|
82
|
+
blockPath.traverse({
|
|
83
|
+
// Check for reassigned / redefined functions
|
|
84
|
+
BindingIdentifier: {
|
|
85
|
+
exit(path: NodePath<t.Identifier>) {
|
|
86
|
+
if (!isVariableIdentifier(path)) return;
|
|
87
|
+
|
|
88
|
+
const name = path.node.name;
|
|
89
|
+
if (!path.parentPath?.isFunctionDeclaration()) {
|
|
90
|
+
illegalNames.add(name);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
// Find functions eligible for dispatching
|
|
95
|
+
FunctionDeclaration: {
|
|
96
|
+
exit(path: NodePath<t.FunctionDeclaration>) {
|
|
97
|
+
const name = path.node.id.name;
|
|
98
|
+
// If the function is not named, we can't dispatch it
|
|
99
|
+
if (!name) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Do not apply to async or generator functions
|
|
104
|
+
if (path.node.async || path.node.generator) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Do not apply to functions in nested scopes
|
|
109
|
+
if (path.parentPath !== blockStatement || me.isSkipped(path)) {
|
|
110
|
+
illegalNames.add(name);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (isStrictMode(path)) {
|
|
115
|
+
illegalNames.add(name);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Do not apply to unsafe functions, redefined functions, or internal obfuscator functions
|
|
120
|
+
if (
|
|
121
|
+
(path.node as NodeSymbol)[UNSAFE] ||
|
|
122
|
+
functionPaths.has(name) ||
|
|
123
|
+
me.obfuscator.isInternalVariable(name)
|
|
124
|
+
) {
|
|
125
|
+
illegalNames.add(name);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var hasAssignmentPattern = false;
|
|
130
|
+
|
|
131
|
+
for (var param of path.get("params")) {
|
|
132
|
+
if (param.isAssignmentPattern()) {
|
|
133
|
+
hasAssignmentPattern = true;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
param.traverse({
|
|
137
|
+
AssignmentPattern(innerPath) {
|
|
138
|
+
var fn = innerPath.getFunctionParent();
|
|
139
|
+
if (fn === path) {
|
|
140
|
+
hasAssignmentPattern = true;
|
|
141
|
+
innerPath.stop();
|
|
142
|
+
} else {
|
|
143
|
+
innerPath.skip();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (hasAssignmentPattern) break;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Functions with default parameters are not fully supported
|
|
152
|
+
// (Could be a Function Expression referencing outer scope)
|
|
153
|
+
if (hasAssignmentPattern) {
|
|
154
|
+
illegalNames.add(name);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
functionPaths.set(name, path);
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
for (let name of illegalNames) {
|
|
164
|
+
functionPaths.delete(name);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (var name of functionPaths.keys()) {
|
|
168
|
+
if (!me.computeProbabilityMap(me.options.dispatcher, name)) {
|
|
169
|
+
functionPaths.delete(name);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// No functions here to change
|
|
174
|
+
if (functionPaths.size === 0) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
me.changeData.functions += functionPaths.size;
|
|
179
|
+
|
|
180
|
+
const dispatcherName =
|
|
181
|
+
me.getPlaceholder() + "_dispatcher_" + dispatcherCounter++;
|
|
182
|
+
const payloadName = me.getPlaceholder() + "_payload";
|
|
183
|
+
const cacheName = me.getPlaceholder() + "_cache";
|
|
184
|
+
const newNameMapping = new Map<string, string>();
|
|
185
|
+
|
|
186
|
+
const keys = {
|
|
187
|
+
placeholderNoMeaning: isDebug ? "noMeaning" : getRandomString(10),
|
|
188
|
+
clearPayload: isDebug ? "clearPayload" : getRandomString(10),
|
|
189
|
+
nonCall: isDebug ? "nonCall" : getRandomString(10),
|
|
190
|
+
returnAsObject: isDebug ? "returnAsObject" : getRandomString(10),
|
|
191
|
+
returnAsObjectProperty: isDebug
|
|
192
|
+
? "returnAsObjectProperty"
|
|
193
|
+
: getRandomString(10),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
for (var name of functionPaths.keys()) {
|
|
197
|
+
newNameMapping.set(
|
|
198
|
+
name,
|
|
199
|
+
isDebug ? "_" + name : getRandomString(6) /** "_" + name */
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Find identifiers calling/referencing the functions
|
|
204
|
+
blockPath.traverse({
|
|
205
|
+
ReferencedIdentifier: {
|
|
206
|
+
exit(path: NodePath<t.Identifier | t.JSXIdentifier>) {
|
|
207
|
+
if (path.isJSX()) return;
|
|
208
|
+
if (isVariableFunctionIdentifier(path)) return;
|
|
209
|
+
const name = path.node.name;
|
|
210
|
+
|
|
211
|
+
var fnPath = functionPaths.get(name);
|
|
212
|
+
if (!fnPath) return;
|
|
213
|
+
|
|
214
|
+
var newName = newNameMapping.get(name);
|
|
215
|
+
|
|
216
|
+
// Do not replace if not referencing the actual function
|
|
217
|
+
if (path.scope.getBinding(name).path !== fnPath) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const createDispatcherCall = (name, flagArg?) => {
|
|
222
|
+
var dispatcherArgs = [t.stringLiteral(name)];
|
|
223
|
+
if (flagArg) {
|
|
224
|
+
dispatcherArgs.push(t.stringLiteral(flagArg));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var asObject = chance(50);
|
|
228
|
+
|
|
229
|
+
if (asObject) {
|
|
230
|
+
if (dispatcherArgs.length < 2) {
|
|
231
|
+
dispatcherArgs.push(
|
|
232
|
+
t.stringLiteral(keys.placeholderNoMeaning)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
dispatcherArgs.push(t.stringLiteral(keys.returnAsObject));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
var callExpression: t.CallExpression | t.NewExpression =
|
|
239
|
+
t.callExpression(
|
|
240
|
+
t.identifier(dispatcherName),
|
|
241
|
+
dispatcherArgs
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (!asObject) {
|
|
245
|
+
return callExpression;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (chance(50)) {
|
|
249
|
+
(callExpression as t.Node).type = "NewExpression";
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return t.memberExpression(
|
|
253
|
+
callExpression,
|
|
254
|
+
t.stringLiteral(keys.returnAsObjectProperty),
|
|
255
|
+
true
|
|
256
|
+
);
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// Replace the identifier with a call to the function
|
|
260
|
+
const parentPath = path.parentPath;
|
|
261
|
+
if (path.key === "callee" && parentPath?.isCallExpression()) {
|
|
262
|
+
var expressions: t.Expression[] = [];
|
|
263
|
+
var callArguments = parentPath.node.arguments;
|
|
264
|
+
|
|
265
|
+
if (callArguments.length === 0) {
|
|
266
|
+
expressions.push(
|
|
267
|
+
// Call the function
|
|
268
|
+
createDispatcherCall(newName, keys.clearPayload)
|
|
269
|
+
);
|
|
270
|
+
} else {
|
|
271
|
+
expressions.push(
|
|
272
|
+
// Prepare the payload arguments
|
|
273
|
+
t.assignmentExpression(
|
|
274
|
+
"=",
|
|
275
|
+
t.identifier(payloadName),
|
|
276
|
+
t.arrayExpression(callArguments as t.Expression[])
|
|
277
|
+
),
|
|
278
|
+
|
|
279
|
+
// Call the function
|
|
280
|
+
createDispatcherCall(newName)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const output =
|
|
285
|
+
expressions.length === 1
|
|
286
|
+
? expressions[0]
|
|
287
|
+
: t.sequenceExpression(expressions);
|
|
288
|
+
|
|
289
|
+
if (!parentPath.container) return;
|
|
290
|
+
parentPath.replaceWith(output);
|
|
291
|
+
} else {
|
|
292
|
+
if (!path.container) return;
|
|
293
|
+
// Replace non-invocation references with a 'cached' version of the function
|
|
294
|
+
path.replaceWith(createDispatcherCall(newName, keys.nonCall));
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const fnLengthProperties = [];
|
|
301
|
+
|
|
302
|
+
// Create the dispatcher function
|
|
303
|
+
const objectExpression = t.objectExpression(
|
|
304
|
+
Array.from(newNameMapping).map(([name, newName]) => {
|
|
305
|
+
const originalPath = functionPaths.get(name);
|
|
306
|
+
const originalFn = originalPath.node;
|
|
307
|
+
|
|
308
|
+
if (me.options.preserveFunctionLength) {
|
|
309
|
+
const fnLength = computeFunctionLength(originalPath);
|
|
310
|
+
|
|
311
|
+
if (fnLength >= 1) {
|
|
312
|
+
// 0 is already the default
|
|
313
|
+
fnLengthProperties.push(
|
|
314
|
+
t.objectProperty(
|
|
315
|
+
t.stringLiteral(newName),
|
|
316
|
+
numericLiteral(fnLength)
|
|
317
|
+
)
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const newBody = [...originalFn.body.body];
|
|
323
|
+
ok(Array.isArray(newBody));
|
|
324
|
+
|
|
325
|
+
// Unpack parameters
|
|
326
|
+
if (originalFn.params.length > 0) {
|
|
327
|
+
newBody.unshift(
|
|
328
|
+
t.variableDeclaration("var", [
|
|
329
|
+
t.variableDeclarator(
|
|
330
|
+
t.arrayPattern([...originalFn.params]),
|
|
331
|
+
t.identifier(payloadName)
|
|
332
|
+
),
|
|
333
|
+
])
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Add debug label
|
|
338
|
+
if (isDebug) {
|
|
339
|
+
newBody.unshift(
|
|
340
|
+
t.expressionStatement(
|
|
341
|
+
t.stringLiteral(`Dispatcher: ${name} -> ${newName}`)
|
|
342
|
+
)
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const functionExpression = t.functionExpression(
|
|
347
|
+
null,
|
|
348
|
+
[],
|
|
349
|
+
t.blockStatement(newBody)
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
for (var symbol of Object.getOwnPropertySymbols(originalFn)) {
|
|
353
|
+
(functionExpression as any)[symbol] = (originalFn as any)[
|
|
354
|
+
symbol
|
|
355
|
+
];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
(functionExpression as NodeSymbol)[PREDICTABLE] = true;
|
|
359
|
+
|
|
360
|
+
return t.objectProperty(
|
|
361
|
+
t.stringLiteral(newName),
|
|
362
|
+
|
|
363
|
+
functionExpression
|
|
364
|
+
);
|
|
365
|
+
})
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
const fnLengths = t.objectExpression(fnLengthProperties);
|
|
369
|
+
|
|
370
|
+
const dispatcher = new Template(`
|
|
371
|
+
function ${dispatcherName}(name, flagArg, returnTypeArg, fnLengths = {fnLengthsObjectExpression}) {
|
|
372
|
+
var output;
|
|
373
|
+
var fns = {objectExpression};
|
|
374
|
+
|
|
375
|
+
if(flagArg === "${keys.clearPayload}") {
|
|
376
|
+
${payloadName} = [];
|
|
377
|
+
}
|
|
378
|
+
if(flagArg === "${keys.nonCall}") {
|
|
379
|
+
function createFunction(){
|
|
380
|
+
var fn = function(...args){
|
|
381
|
+
${payloadName} = args;
|
|
382
|
+
return fns[name].apply(this);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
var fnLength = fnLengths[name];
|
|
386
|
+
if(fnLength) {
|
|
387
|
+
${setFunctionLength}(fn, fnLength);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return fn;
|
|
391
|
+
}
|
|
392
|
+
output = ${cacheName}[name] || (${cacheName}[name] = createFunction());
|
|
393
|
+
} else {
|
|
394
|
+
output = fns[name]();
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if(returnTypeArg === "${keys.returnAsObject}") {
|
|
398
|
+
return { "${keys.returnAsObjectProperty}": output };
|
|
399
|
+
} else {
|
|
400
|
+
return output;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
`).single({
|
|
404
|
+
objectExpression,
|
|
405
|
+
fnLengthsObjectExpression: fnLengths,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
(dispatcher as NodeSymbol)[PREDICTABLE] = true;
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Prepends the node into the block. (And registers the declaration)
|
|
412
|
+
* @param node
|
|
413
|
+
*/
|
|
414
|
+
function prepend(node: t.Statement) {
|
|
415
|
+
var newPath = blockStatement.unshiftContainer<any, any, any>(
|
|
416
|
+
"body",
|
|
417
|
+
node
|
|
418
|
+
)[0];
|
|
419
|
+
blockStatement.scope.registerDeclaration(newPath);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Insert the dispatcher function
|
|
423
|
+
prepend(dispatcher);
|
|
424
|
+
|
|
425
|
+
// Insert the payload variable
|
|
426
|
+
prepend(
|
|
427
|
+
t.variableDeclaration("var", [
|
|
428
|
+
t.variableDeclarator(t.identifier(payloadName)),
|
|
429
|
+
])
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Insert the cache variable
|
|
433
|
+
prepend(
|
|
434
|
+
t.variableDeclaration("var", [
|
|
435
|
+
t.variableDeclarator(
|
|
436
|
+
t.identifier(cacheName),
|
|
437
|
+
new Template(`Object["create"](null)`).expression()
|
|
438
|
+
),
|
|
439
|
+
])
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
// Remove original functions
|
|
443
|
+
for (let path of functionPaths.values()) {
|
|
444
|
+
path.remove();
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
};
|
|
450
|
+
};
|