@dacely/toildefender 0.1.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/LICENSE +661 -0
- package/NOTICE.md +16 -0
- package/README.md +380 -0
- package/cli.js +168 -0
- package/defendjs.js +7 -0
- package/docs/all-modes-output.demo.js +673 -0
- package/estest.js +49 -0
- package/esutils.js +107 -0
- package/logger.js +28 -0
- package/obfuscator.js +534 -0
- package/package.json +108 -0
- package/processors/deadCode.js +62 -0
- package/processors/flattener.js +808 -0
- package/processors/health.js +55 -0
- package/processors/identifiers.js +256 -0
- package/processors/literalObfuscator.js +40 -0
- package/processors/literals.js +233 -0
- package/processors/methods.js +332 -0
- package/processors/modules.js +231 -0
- package/processors/normalizer.js +490 -0
- package/processors/numericVm.js +950 -0
- package/processors/postprocessing.js +69 -0
- package/processors/preprocessing.js +248 -0
- package/processors/scopes.js +177 -0
- package/processors/uglifier.js +26 -0
- package/processors/variables.js +185 -0
- package/toildefender.js +7 -0
- package/traverser.js +115 -0
- package/utils.js +135 -0
package/obfuscator.js
ADDED
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
/** @module toildefender */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
var fs = require("fs");
|
|
6
|
+
var assert = require("assert");
|
|
7
|
+
|
|
8
|
+
var _ = require("lodash");
|
|
9
|
+
var legacyBabel = require("babel-core");
|
|
10
|
+
var modernBabel = (() => {
|
|
11
|
+
try {
|
|
12
|
+
return require("@babel/core");
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
})();
|
|
17
|
+
var escodegen = require("escodegen");
|
|
18
|
+
var escope = require("escope");
|
|
19
|
+
var esprima = require("esprima");
|
|
20
|
+
|
|
21
|
+
var traverser = require("./traverser");
|
|
22
|
+
var utils = require("./utils");
|
|
23
|
+
|
|
24
|
+
var Logger = require("./logger");
|
|
25
|
+
|
|
26
|
+
var prDeadCode = require("./processors/deadCode");
|
|
27
|
+
var prModules = require("./processors/modules");
|
|
28
|
+
var prMethods = require("./processors/methods");
|
|
29
|
+
var prVariables = require("./processors/variables");
|
|
30
|
+
var prScopes = require("./processors/scopes");
|
|
31
|
+
var prFlattener = require("./processors/flattener");
|
|
32
|
+
var prNormalizer = require("./processors/normalizer");
|
|
33
|
+
var prPreprocessing = require("./processors/preprocessing");
|
|
34
|
+
var prPostprocessing = require("./processors/postprocessing");
|
|
35
|
+
var prUglifier = require("./processors/uglifier");
|
|
36
|
+
var prIdentifiers = require("./processors/identifiers");
|
|
37
|
+
var prLiterals = require("./processors/literals");
|
|
38
|
+
var prNumericVm = require("./processors/numericVm");
|
|
39
|
+
var prHealth = require("./processors/health");
|
|
40
|
+
|
|
41
|
+
var defaultOptions = {
|
|
42
|
+
babel: true,
|
|
43
|
+
babelTarget: "ie 11",
|
|
44
|
+
features: {
|
|
45
|
+
dead_code: true,
|
|
46
|
+
scope: true,
|
|
47
|
+
control_flow: true,
|
|
48
|
+
identifiers: true,
|
|
49
|
+
numeric_vm: false,
|
|
50
|
+
object_packing: true,
|
|
51
|
+
literals: true,
|
|
52
|
+
mangle: true,
|
|
53
|
+
compress: true
|
|
54
|
+
},
|
|
55
|
+
logLevel: "warn",
|
|
56
|
+
numericVm: {
|
|
57
|
+
enabled: false,
|
|
58
|
+
maxFunctionSize: 120,
|
|
59
|
+
minFunctionSize: 1,
|
|
60
|
+
mode: "balanced",
|
|
61
|
+
perFunctionDialect: true,
|
|
62
|
+
seed: "toildefender-numeric-vm",
|
|
63
|
+
hashMesh: {
|
|
64
|
+
bindToVmState: true,
|
|
65
|
+
chaffRatio: 0.55,
|
|
66
|
+
deriveDialectFromMesh: false,
|
|
67
|
+
enabled: false,
|
|
68
|
+
encodeChaff: true,
|
|
69
|
+
mode: "balanced",
|
|
70
|
+
serverBound: false,
|
|
71
|
+
unlock: "per-function"
|
|
72
|
+
},
|
|
73
|
+
virtualize: "marked"
|
|
74
|
+
},
|
|
75
|
+
protections: {
|
|
76
|
+
virtualMachine: {
|
|
77
|
+
bigintBytecode: true,
|
|
78
|
+
enabled: false,
|
|
79
|
+
encodeConstants: true,
|
|
80
|
+
mode: "balanced",
|
|
81
|
+
perFunctionDialect: true,
|
|
82
|
+
randomizedOpcodes: true,
|
|
83
|
+
virtualize: "marked"
|
|
84
|
+
},
|
|
85
|
+
hashMesh: {
|
|
86
|
+
bindToVmState: true,
|
|
87
|
+
chaffRatio: 0.55,
|
|
88
|
+
deriveDialectFromMesh: false,
|
|
89
|
+
enabled: false,
|
|
90
|
+
encodeChaff: true,
|
|
91
|
+
mode: "balanced",
|
|
92
|
+
serverBound: false,
|
|
93
|
+
unlock: "per-function"
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
preprocessorVariables: {}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
var featureDeps = {
|
|
100
|
+
dead_code: [ "control_flow" ],
|
|
101
|
+
scope: [ "mangle" ],
|
|
102
|
+
control_flow: [ "scope", "mangle" ],
|
|
103
|
+
identifiers: [ "mangle" ],
|
|
104
|
+
numeric_vm: [],
|
|
105
|
+
object_packing: [ "identifiers" ],
|
|
106
|
+
literals: [ "scope", "mangle" ],
|
|
107
|
+
compress: [ "mangle" ]
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
var featureDescs = {
|
|
111
|
+
dead_code: {
|
|
112
|
+
en: "Insert dead code"
|
|
113
|
+
},
|
|
114
|
+
scope: {
|
|
115
|
+
en: "Flatten the scope (method) structure to obfuscate application structure"
|
|
116
|
+
},
|
|
117
|
+
control_flow: {
|
|
118
|
+
en: "Flatten control flow (if, while, for, etc...) structure to obfuscate control flow"
|
|
119
|
+
},
|
|
120
|
+
identifiers: {
|
|
121
|
+
en: "Obfuscate identifiers (variable, object and property names)"
|
|
122
|
+
},
|
|
123
|
+
numeric_vm: {
|
|
124
|
+
en: "Virtualize selected functions into BigInt-packed numeric VM programs"
|
|
125
|
+
},
|
|
126
|
+
object_packing: {
|
|
127
|
+
en: "Pack object literal keys into numeric schemas instead of alternating key/value arrays"
|
|
128
|
+
},
|
|
129
|
+
literals: {
|
|
130
|
+
en: "Obfuscate literals (numbers, strings)"
|
|
131
|
+
},
|
|
132
|
+
mangle: {
|
|
133
|
+
en: "Shorten identifiers (variable names, function names)"
|
|
134
|
+
},
|
|
135
|
+
compress: {
|
|
136
|
+
en: "Remove unneeded whitespace"
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
exports.features = _.fromPairs(
|
|
141
|
+
_.map(defaultOptions.features, (enabled, feature) =>
|
|
142
|
+
[
|
|
143
|
+
feature,
|
|
144
|
+
{
|
|
145
|
+
dependencies: featureDeps[feature] || [],
|
|
146
|
+
descriptions: featureDescs[feature] || {},
|
|
147
|
+
default: enabled
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Logs informational and diagnostic messages onto an output device or object.
|
|
155
|
+
*
|
|
156
|
+
* @callback logAdapterCallback
|
|
157
|
+
* @param {string} level - Message level.
|
|
158
|
+
* @param {string} data - Message data.
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Obfuscates a project.
|
|
163
|
+
* @param {Object} options - Configuration.
|
|
164
|
+
* @param {string} options.code - Code of entry point file to be obfuscated.
|
|
165
|
+
* @param {Object.<string, string>} options.modulesCode - Code of all of options.code's depedencies.
|
|
166
|
+
* @param {boolean} [options.babel = true] - Whether to run babel with ES2015 preset before obfuscating.
|
|
167
|
+
* @param {Object.<string, boolean>} [options.features = All enabled] - Feature configuration.
|
|
168
|
+
* @param {logAdapterCallback} [options.logAdapter = Console] - Logging adapter.
|
|
169
|
+
* @param {string} [options.logLevel = "warn"] - Minimum level of shown log messages.
|
|
170
|
+
* @param {Object.<string, boolean>} [options.preprocessorVariables] - Preprocessor variables.
|
|
171
|
+
* @example
|
|
172
|
+
* toildefender.do({
|
|
173
|
+
* code: "...",
|
|
174
|
+
* modulesCode: {
|
|
175
|
+
* depA: "...",
|
|
176
|
+
* depB: "..."
|
|
177
|
+
* },
|
|
178
|
+
* features: {
|
|
179
|
+
* scope: true,
|
|
180
|
+
* control_flow: true,
|
|
181
|
+
* identifiers: true,
|
|
182
|
+
* literals: true,
|
|
183
|
+
* mangle: true,
|
|
184
|
+
* compress: true
|
|
185
|
+
* }
|
|
186
|
+
* });
|
|
187
|
+
*/
|
|
188
|
+
exports.do = function (options) {
|
|
189
|
+
/**
|
|
190
|
+
* Annotates potentially thrown errors with a label
|
|
191
|
+
*/
|
|
192
|
+
function tryTag(label, task) {
|
|
193
|
+
try {
|
|
194
|
+
return task();
|
|
195
|
+
} catch (e) {
|
|
196
|
+
throw new Error(`[${label}]\t${e.stack}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Adapter for Logger
|
|
202
|
+
*/
|
|
203
|
+
function createConsoleLoggingAdapter(logLevel) {
|
|
204
|
+
const LEVELS = ["log", "error", "warn", "info", "debug"];
|
|
205
|
+
let allowedLevels = [];
|
|
206
|
+
for (let level of LEVELS) {
|
|
207
|
+
allowedLevels.push(level);
|
|
208
|
+
if (level == logLevel) {
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return (level, data) => {
|
|
213
|
+
if (_.includes(allowedLevels, level)) {
|
|
214
|
+
var prefix = "[task]" + Array(taskIndent).join("\t");
|
|
215
|
+
console.log(`${prefix}[${level}]\t${data.join("\t")}`);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
var taskIndent = 1;
|
|
221
|
+
/**
|
|
222
|
+
* Wraps a task, indents its output and measures its duration
|
|
223
|
+
*/
|
|
224
|
+
function doTask(label, condition, task) {
|
|
225
|
+
return tryTag(label, () => {
|
|
226
|
+
taskIndent++;
|
|
227
|
+
var prefix = "[task]" + Array(taskIndent).join("\t");
|
|
228
|
+
try {
|
|
229
|
+
if (condition) {
|
|
230
|
+
logger.info(`${prefix}${label} ...`);
|
|
231
|
+
|
|
232
|
+
var start = Date.now();
|
|
233
|
+
task();
|
|
234
|
+
var duration = Date.now() - start;
|
|
235
|
+
logger.info(`${prefix}${label}: ${duration}ms`);
|
|
236
|
+
return {
|
|
237
|
+
otherwise: function() { }
|
|
238
|
+
};
|
|
239
|
+
} else {
|
|
240
|
+
return {
|
|
241
|
+
otherwise: function (task) { task(); }
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
taskIndent--;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function transformModernSyntax(code, label) {
|
|
251
|
+
if (modernBabel) {
|
|
252
|
+
var result = modernBabel.transformSync(code, {
|
|
253
|
+
babelrc: false,
|
|
254
|
+
comments: false,
|
|
255
|
+
compact: false,
|
|
256
|
+
configFile: false,
|
|
257
|
+
sourceType: "unambiguous",
|
|
258
|
+
presets: [
|
|
259
|
+
[
|
|
260
|
+
require.resolve("@babel/preset-env"),
|
|
261
|
+
{
|
|
262
|
+
modules: "commonjs",
|
|
263
|
+
targets: options.babelTarget,
|
|
264
|
+
useBuiltIns: false
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
]
|
|
268
|
+
});
|
|
269
|
+
return result && result.code || code;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
var babelOptions = {
|
|
273
|
+
"plugins": [
|
|
274
|
+
"babel-plugin-transform-es2015-arrow-functions",
|
|
275
|
+
//"babel-plugin-transform-es2015-block-scoped-functions",
|
|
276
|
+
"babel-plugin-transform-es2015-block-scoping",
|
|
277
|
+
"babel-plugin-transform-es2015-classes",
|
|
278
|
+
"babel-plugin-transform-es2015-computed-properties",
|
|
279
|
+
//"babel-plugin-check-es2015-constants",
|
|
280
|
+
"babel-plugin-transform-es2015-destructuring",
|
|
281
|
+
"babel-plugin-transform-es2015-duplicate-keys",
|
|
282
|
+
"babel-plugin-transform-es2015-for-of",
|
|
283
|
+
"babel-plugin-transform-es2015-function-name",
|
|
284
|
+
"babel-plugin-transform-es2015-literals",
|
|
285
|
+
"babel-plugin-transform-es2015-object-super",
|
|
286
|
+
"babel-plugin-transform-es2015-parameters",
|
|
287
|
+
"babel-plugin-transform-es2015-shorthand-properties",
|
|
288
|
+
"babel-plugin-transform-es2015-spread",
|
|
289
|
+
"babel-plugin-transform-es2015-sticky-regex",
|
|
290
|
+
"babel-plugin-transform-es2015-template-literals",
|
|
291
|
+
//"babel-plugin-transform-es2015-typeof-symbol",
|
|
292
|
+
"babel-plugin-transform-es2015-unicode-regex"
|
|
293
|
+
].map(require.resolve)
|
|
294
|
+
};
|
|
295
|
+
return legacyBabel.transform(code, babelOptions).code;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
options = _.merge({}, defaultOptions, options); // first argument gets mutated
|
|
299
|
+
if (options.protections.virtualMachine.enabled) {
|
|
300
|
+
options.numericVm = _.merge({}, options.numericVm, options.protections.virtualMachine, {
|
|
301
|
+
enabled: true
|
|
302
|
+
});
|
|
303
|
+
options.features.numeric_vm = true;
|
|
304
|
+
}
|
|
305
|
+
if (options.protections.hashMesh.enabled) {
|
|
306
|
+
options.numericVm = _.merge({}, options.numericVm, options.protections.virtualMachine, {
|
|
307
|
+
enabled: true,
|
|
308
|
+
hashMesh: options.protections.hashMesh
|
|
309
|
+
});
|
|
310
|
+
options.features.numeric_vm = true;
|
|
311
|
+
}
|
|
312
|
+
if (!options.logAdapter) {
|
|
313
|
+
options.logAdapter = createConsoleLoggingAdapter(options.logLevel);
|
|
314
|
+
}
|
|
315
|
+
if (!options.forceFeatures) {
|
|
316
|
+
_.map(featureDeps, (deps, feature) => {
|
|
317
|
+
if (options.features[feature]) {
|
|
318
|
+
deps.forEach(dep => options.features[dep] = true);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
} else {
|
|
322
|
+
options.features = options.forceFeatures;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
var parseOptions = {};
|
|
326
|
+
var scopeOptions = {
|
|
327
|
+
optimistic: true // required or things in the global scope just get lost
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
var logger = new Logger(options.logAdapter);
|
|
331
|
+
|
|
332
|
+
var start = Date.now();
|
|
333
|
+
|
|
334
|
+
// Preprocess
|
|
335
|
+
doTask("preprocessing", true, () => {
|
|
336
|
+
var preprocessor = new prPreprocessing(logger);
|
|
337
|
+
options.modulesCode = _.mapValues(
|
|
338
|
+
options.modulesCode,
|
|
339
|
+
(code, key) => tryTag(key, () => preprocessor.process(code, options.preprocessorVariables))
|
|
340
|
+
);
|
|
341
|
+
options.code = tryTag("app", () => preprocessor.process(options.code, options.preprocessorVariables));
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Apply babel
|
|
345
|
+
doTask("babel", options.babel, () => {
|
|
346
|
+
options.modulesCode = _.mapValues(options.modulesCode, (moduleCode, key) => tryTag(key, () => transformModernSyntax(moduleCode, key)));
|
|
347
|
+
options.code = tryTag("app", () => transformModernSyntax(options.code, "app"));
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Parse code
|
|
351
|
+
var ast, modulesAST;
|
|
352
|
+
doTask("parse", true, () => {
|
|
353
|
+
modulesAST = _.mapValues(options.modulesCode, (code, key) => tryTag(key, () => esprima.parse(code, parseOptions)));
|
|
354
|
+
modulesAST.app = tryTag("app", () => esprima.parse(options.code, parseOptions));
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Merge depedencies into main modules
|
|
358
|
+
doTask("merge", true, () => {
|
|
359
|
+
var modules = new prModules(logger);
|
|
360
|
+
ast = modules.merge(modulesAST, "app");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Insert dead code
|
|
364
|
+
doTask("dead_code", options.features.dead_code, () => {
|
|
365
|
+
var deadCode = new prDeadCode();
|
|
366
|
+
ast = deadCode.insert(ast, 1.0);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Simplify graph
|
|
370
|
+
doTask("simplify", true, () => {
|
|
371
|
+
var normalizer = new prNormalizer(logger);
|
|
372
|
+
ast = normalizer.simplify(ast);
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
doTask("numeric_vm", options.features.numeric_vm || options.numericVm.enabled, () => {
|
|
376
|
+
var numericVm = new prNumericVm(logger, _.merge({}, options.numericVm, {
|
|
377
|
+
enabled: options.features.numeric_vm || options.numericVm.enabled
|
|
378
|
+
}));
|
|
379
|
+
ast = numericVm.apply(ast);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// Move identifiers
|
|
383
|
+
doTask("identifiers", options.features.identifiers, () => {
|
|
384
|
+
var identifiers = new prIdentifiers(logger);
|
|
385
|
+
|
|
386
|
+
ast = identifiers.computeProperties(ast);
|
|
387
|
+
ast = identifiers.arrayizeObjects(ast, {
|
|
388
|
+
objectPacking: options.features.object_packing !== false
|
|
389
|
+
});
|
|
390
|
+
//ast = identifiers.moveIdentifiers(ast, escope.analyze(ast, scopeOptions));
|
|
391
|
+
//^ why is this commented out?
|
|
392
|
+
ast = identifiers.moveLiterals(ast, escope.analyze(ast, scopeOptions));
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
doTask("literals", options.features.literals, () => {
|
|
396
|
+
var literals = new prLiterals(logger);
|
|
397
|
+
|
|
398
|
+
literals.generateStrings(ast);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
doTask("scope", options.features.scope, () => {
|
|
402
|
+
var scopes = new prScopes(logger);
|
|
403
|
+
var methods = new prMethods(logger);
|
|
404
|
+
|
|
405
|
+
var rng = new utils.UniqueRandom(32768);
|
|
406
|
+
|
|
407
|
+
// Make identifiers unique
|
|
408
|
+
doTask("obfuscate_identifiers", true, () => {
|
|
409
|
+
var variables = new prVariables(logger);
|
|
410
|
+
variables.removeFunctionExpressionIds(ast);
|
|
411
|
+
variables.functionDeclarationToExpression(ast, escope.analyze(ast, scopeOptions));
|
|
412
|
+
variables.obfuscateIdentifiers(ast, escope.analyze(ast, scopeOptions));
|
|
413
|
+
variables.redefineParameters(ast, escope.analyze(ast, scopeOptions));
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Move identifiers into scope objects
|
|
417
|
+
doTask("create_scope_objects", true, () => {
|
|
418
|
+
scopes.createScopeObjects(ast, escope.analyze(ast, scopeOptions));
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Calculate entry points for all methods
|
|
422
|
+
var methodEntryPoints = {};
|
|
423
|
+
doTask("list_methods", true, () => {
|
|
424
|
+
methods.listMethods(ast).forEach(methodName => {
|
|
425
|
+
methodEntryPoints[methodName] = {
|
|
426
|
+
entry: rng.get()
|
|
427
|
+
};
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// Extract function declarations and expressions
|
|
432
|
+
var fns;
|
|
433
|
+
doTask("extract_methods", true, () => {
|
|
434
|
+
var scopeManager = escope.analyze(ast, scopeOptions);
|
|
435
|
+
fns = methods.extractMethods(ast);
|
|
436
|
+
fns = fns.map(method => {
|
|
437
|
+
var refers = methods.methodRefersToArguments(method, scopeManager);
|
|
438
|
+
methods.removeFirstArguments(method, refers ? method.params.filter(x => x.name.indexOf("$$scope") == 0).length : 0);
|
|
439
|
+
return methods.replaceArgumentReferences(method, true);
|
|
440
|
+
});
|
|
441
|
+
if (options.features.control_flow) {
|
|
442
|
+
methods.replaceFunctionCalls(ast, methodEntryPoints);
|
|
443
|
+
fns.forEach(method => {
|
|
444
|
+
methods.replaceFunctionCalls(method.body, methodEntryPoints);
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
doTask("add_custom_bind", true, () => {
|
|
450
|
+
methods.addCustomBind(ast);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
doTask("control_flow", options.features.control_flow, () => {
|
|
454
|
+
// Apply control flow flattening and merge methods
|
|
455
|
+
var flattener = new prFlattener(logger, rng);
|
|
456
|
+
var entry = rng.get(), exit = rng.get();
|
|
457
|
+
flattener.addMethod(ast, entry, exit);
|
|
458
|
+
fns.forEach(method => {
|
|
459
|
+
methods.bumpArgumentsIndices(method, 1);
|
|
460
|
+
|
|
461
|
+
var entry = methodEntryPoints[method.id.name].entry;
|
|
462
|
+
flattener.addMethod(method.body, entry, exit);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
ast = flattener.getProgram(entry, exit);
|
|
466
|
+
|
|
467
|
+
ast = flattener.unifyPrefixStatements(ast);
|
|
468
|
+
})
|
|
469
|
+
.otherwise(() => {
|
|
470
|
+
if (ast.type == "Program") {
|
|
471
|
+
ast.type = "BlockStatement";
|
|
472
|
+
}
|
|
473
|
+
ast = {
|
|
474
|
+
type: "Program",
|
|
475
|
+
body: fns.concat([ ast ])
|
|
476
|
+
};
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Postprocessing
|
|
481
|
+
doTask("postprocessing", true, () => {
|
|
482
|
+
var postprocessing = new prPostprocessing(logger);
|
|
483
|
+
ast = postprocessing.do(ast);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
doTask("health", options.features.health, () => {
|
|
487
|
+
var health = new prHealth(logger);
|
|
488
|
+
ast = health.check(ast);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
doTask("mangle", options.features.mangle, () => {
|
|
492
|
+
var uglifier = new prUglifier(logger);
|
|
493
|
+
if (ast.type == "Program") {
|
|
494
|
+
ast.type = "BlockStatement";
|
|
495
|
+
}
|
|
496
|
+
ast = uglifier.uglify({
|
|
497
|
+
type: "Program",
|
|
498
|
+
body: [
|
|
499
|
+
{
|
|
500
|
+
type: "CallExpression",
|
|
501
|
+
arguments: [],
|
|
502
|
+
callee: {
|
|
503
|
+
type: "FunctionExpression",
|
|
504
|
+
params: [],
|
|
505
|
+
body: ast
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
]
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
var codegenOptions = {
|
|
513
|
+
sourceMap: false,
|
|
514
|
+
sourceMapWithCode: false
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
doTask("compress", options.features.compress, () => {
|
|
518
|
+
codegenOptions.format = {
|
|
519
|
+
renumber: true,
|
|
520
|
+
hexadecimal: true,
|
|
521
|
+
quotes: "auto",
|
|
522
|
+
compact: true
|
|
523
|
+
};
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
var result = escodegen.generate(ast, codegenOptions);
|
|
527
|
+
|
|
528
|
+
var duration = Date.now() - start;
|
|
529
|
+
|
|
530
|
+
return {
|
|
531
|
+
code: result.code || result,
|
|
532
|
+
map: result.map && result.map.toString()
|
|
533
|
+
};
|
|
534
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dacely/toildefender",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Modern JavaScript code protection, bytecode virtualization, and obfuscation for the Toil tech stack.",
|
|
5
|
+
"author": "Dacely",
|
|
6
|
+
"contributors": [
|
|
7
|
+
{
|
|
8
|
+
"name": "Alexander Horn",
|
|
9
|
+
"url": "https://github.com/alexhorn"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/dacely-cloud/toildefender.git"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/dacely-cloud/toildefender/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/dacely-cloud/toildefender#readme",
|
|
20
|
+
"keywords": [
|
|
21
|
+
"javascript",
|
|
22
|
+
"obfuscation",
|
|
23
|
+
"bytecode",
|
|
24
|
+
"virtual-machine",
|
|
25
|
+
"code-protection",
|
|
26
|
+
"toiljs",
|
|
27
|
+
"dacely"
|
|
28
|
+
],
|
|
29
|
+
"main": "toildefender.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": "./toildefender.js"
|
|
32
|
+
},
|
|
33
|
+
"bin": {
|
|
34
|
+
"toildefender": "toildefender.js"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"test": "node --test test/*.test.js",
|
|
38
|
+
"test:firefox": "node test/firefox-smoke.mjs",
|
|
39
|
+
"pack:dry": "npm pack --dry-run"
|
|
40
|
+
},
|
|
41
|
+
"toildefender": {
|
|
42
|
+
"mainFiles": [
|
|
43
|
+
"toildefender.js"
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
"files": [
|
|
47
|
+
"cli.js",
|
|
48
|
+
"defendjs.js",
|
|
49
|
+
"estest.js",
|
|
50
|
+
"esutils.js",
|
|
51
|
+
"logger.js",
|
|
52
|
+
"obfuscator.js",
|
|
53
|
+
"processors",
|
|
54
|
+
"toildefender.js",
|
|
55
|
+
"traverser.js",
|
|
56
|
+
"utils.js",
|
|
57
|
+
"LICENSE",
|
|
58
|
+
"NOTICE.md",
|
|
59
|
+
"README.md",
|
|
60
|
+
"images/toildefender-logo.svg",
|
|
61
|
+
"docs/all-modes-output.demo.js"
|
|
62
|
+
],
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"@babel/core": "^8.0.1",
|
|
65
|
+
"@babel/preset-env": "^8.0.2",
|
|
66
|
+
"babel-core": "6.10.4",
|
|
67
|
+
"babel-plugin-check-es2015-constants": "^6.3.13",
|
|
68
|
+
"babel-plugin-transform-es2015-arrow-functions": "^6.3.13",
|
|
69
|
+
"babel-plugin-transform-es2015-block-scoped-functions": "^6.3.13",
|
|
70
|
+
"babel-plugin-transform-es2015-block-scoping": "^6.9.0",
|
|
71
|
+
"babel-plugin-transform-es2015-classes": "^6.9.0",
|
|
72
|
+
"babel-plugin-transform-es2015-computed-properties": "^6.3.13",
|
|
73
|
+
"babel-plugin-transform-es2015-destructuring": "^6.9.0",
|
|
74
|
+
"babel-plugin-transform-es2015-duplicate-keys": "^6.6.0",
|
|
75
|
+
"babel-plugin-transform-es2015-for-of": "^6.6.0",
|
|
76
|
+
"babel-plugin-transform-es2015-function-name": "^6.9.0",
|
|
77
|
+
"babel-plugin-transform-es2015-literals": "^6.3.13",
|
|
78
|
+
"babel-plugin-transform-es2015-modules-commonjs": "^6.6.0",
|
|
79
|
+
"babel-plugin-transform-es2015-object-super": "^6.3.13",
|
|
80
|
+
"babel-plugin-transform-es2015-parameters": "^6.9.0",
|
|
81
|
+
"babel-plugin-transform-es2015-shorthand-properties": "^6.3.13",
|
|
82
|
+
"babel-plugin-transform-es2015-spread": "^6.3.13",
|
|
83
|
+
"babel-plugin-transform-es2015-sticky-regex": "^6.3.13",
|
|
84
|
+
"babel-plugin-transform-es2015-template-literals": "^6.6.0",
|
|
85
|
+
"babel-plugin-transform-es2015-typeof-symbol": "^6.6.0",
|
|
86
|
+
"babel-plugin-transform-es2015-unicode-regex": "^6.3.13",
|
|
87
|
+
"babel-plugin-transform-regenerator": "^6.9.0",
|
|
88
|
+
"escodegen": "^2.1.0",
|
|
89
|
+
"escope": "3.6.0",
|
|
90
|
+
"esprima": "^4.0.1",
|
|
91
|
+
"esshorten": "1.1.1",
|
|
92
|
+
"estraverse": "4.2.0",
|
|
93
|
+
"expr-eval": "1.0.0",
|
|
94
|
+
"lodash": "4.13.1",
|
|
95
|
+
"minimist": "1.2.0"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": ">=24.0.0",
|
|
99
|
+
"npm": ">=10.0.0"
|
|
100
|
+
},
|
|
101
|
+
"publishConfig": {
|
|
102
|
+
"access": "public"
|
|
103
|
+
},
|
|
104
|
+
"license": "AGPL-3.0",
|
|
105
|
+
"devDependencies": {
|
|
106
|
+
"playwright": "^1.61.0"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
|
|
5
|
+
var _ = require("lodash");
|
|
6
|
+
var estest = require("../estest");
|
|
7
|
+
var traverser = require("../traverser");
|
|
8
|
+
var utils = require("../utils");
|
|
9
|
+
|
|
10
|
+
const KEYWORDS = ["await","break","case","catch","class","const","continue","debugger","default","delete","do","else","enum","export","extends","finally","for","function","if","implements","import","in","instanceof","interface","let","new","package","private","protected","public","return","static","super","switch","this","throw","try","typeof","var","void","while","with","yield"];
|
|
11
|
+
|
|
12
|
+
module.exports = class DeadCode {
|
|
13
|
+
|
|
14
|
+
constructor (logger) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Insert dead code
|
|
20
|
+
* @param {Node} ast
|
|
21
|
+
* @returns {Node}
|
|
22
|
+
*/
|
|
23
|
+
insert (ast, probability) {
|
|
24
|
+
assert.ok(estest.isNode(ast));
|
|
25
|
+
|
|
26
|
+
var rngAlpha = new utils.UniqueRandomAlpha(3);
|
|
27
|
+
|
|
28
|
+
return traverser.traverse(ast, [], (node, stack) => {
|
|
29
|
+
if (node.type == "BlockStatement") {
|
|
30
|
+
for (var i = 0; i < probability; ++i) {
|
|
31
|
+
if (probability - i < Math.random()) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var pos = utils.random(0, node.body.length - 1);
|
|
36
|
+
var len = utils.random(1, node.body.length - pos);
|
|
37
|
+
|
|
38
|
+
var varValue = _.sample(KEYWORDS);
|
|
39
|
+
|
|
40
|
+
var spliced = node.body.splice(pos, len);
|
|
41
|
+
node.body.splice(pos, 0,
|
|
42
|
+
{
|
|
43
|
+
type: "IfStatement",
|
|
44
|
+
test: {
|
|
45
|
+
type: "BinaryExpression",
|
|
46
|
+
operator: "==",
|
|
47
|
+
left: { type: "Literal", value: varValue },
|
|
48
|
+
right: { type: "Literal", value: varValue }
|
|
49
|
+
},
|
|
50
|
+
consequent: {
|
|
51
|
+
type: "BlockStatement",
|
|
52
|
+
body: spliced
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return node;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
};
|