@dacely/toildefender 0.1.4 → 0.1.6

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/esutils.js CHANGED
@@ -32,12 +32,22 @@ module.exports = function (logger) {
32
32
  });
33
33
  };
34
34
 
35
+ this.canInsertIntoScope = function (scope) {
36
+ if (!scope || !scope.block) {
37
+ return false;
38
+ }
39
+ if (scope.block.body && (scope.block.body.type == "Program" || scope.block.body.type == "BlockStatement")) {
40
+ return true;
41
+ }
42
+ return scope.block.type == "Program" || scope.block.type == "BlockStatement";
43
+ };
44
+
35
45
  this.insertIntoScope = function (scope, node, idx) {
36
46
  assert.ok(estest.isNode(node));
37
47
 
38
48
  idx = idx || 0;
39
49
 
40
- if (scope.block.body.type == "Program" || scope.block.body.type == "BlockStatement") {
50
+ if (scope.block.body && (scope.block.body.type == "Program" || scope.block.body.type == "BlockStatement")) {
41
51
  scope.block.body.body.splice(idx, 0, node);
42
52
 
43
53
  Object.defineProperty(node, "veilmark$parent", {
package/obfuscator.js CHANGED
@@ -6,14 +6,15 @@ var fs = require("fs");
6
6
  var assert = require("assert");
7
7
 
8
8
  var _ = require("lodash");
9
- var legacyBabel = require("babel-core");
10
- var modernBabel = (() => {
9
+ function requireOptional(name) {
11
10
  try {
12
- return require("@babel/core");
11
+ return require(name);
13
12
  } catch (e) {
14
13
  return null;
15
14
  }
16
- })();
15
+ }
16
+
17
+ var modernParser = requireOptional("@babel/parser");
17
18
  var escodegen = require("escodegen");
18
19
  var escope = require("escope");
19
20
  var esprima = require("esprima");
@@ -39,8 +40,11 @@ var prNumericVm = require("./processors/numericVm");
39
40
  var prHealth = require("./processors/health");
40
41
 
41
42
  var defaultOptions = {
42
- babel: true,
43
+ babel: false,
43
44
  babelTarget: "ie 11",
45
+ babelPreserveAsync: true,
46
+ runtimeHelpers: true,
47
+ simplify: true,
44
48
  features: {
45
49
  dead_code: true,
46
50
  scope: true,
@@ -163,7 +167,8 @@ exports.features = _.fromPairs(
163
167
  * @param {Object} options - Configuration.
164
168
  * @param {string} options.code - Code of entry point file to be obfuscated.
165
169
  * @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.
170
+ * @param {boolean} [options.babel = false] - Whether to run the optional Babel transform before obfuscating.
171
+ * @param {boolean} [options.babelPreserveAsync = true] - Whether Babel should leave async/generator syntax for async-aware flattening instead of emitting regenerator helpers.
167
172
  * @param {Object.<string, boolean>} [options.features = All enabled] - Feature configuration.
168
173
  * @param {logAdapterCallback} [options.logAdapter = Console] - Logging adapter.
169
174
  * @param {string} [options.logLevel = "warn"] - Minimum level of shown log messages.
@@ -248,7 +253,26 @@ exports.do = function (options) {
248
253
  }
249
254
 
250
255
  function transformModernSyntax(code, label) {
256
+ var modernBabel = requireOptional("@babel/core");
251
257
  if (modernBabel) {
258
+ var presetEnvPath;
259
+ try {
260
+ presetEnvPath = require.resolve("@babel/preset-env");
261
+ } catch (e) {
262
+ throw new Error("Babel transform requested, but @babel/preset-env is not installed");
263
+ }
264
+
265
+ var presetOptions = {
266
+ modules: "commonjs",
267
+ targets: options.babelTarget,
268
+ useBuiltIns: false
269
+ };
270
+ if (options.babelPreserveAsync !== false) {
271
+ presetOptions.exclude = [
272
+ "@babel/plugin-transform-async-to-generator",
273
+ "@babel/plugin-transform-regenerator"
274
+ ];
275
+ }
252
276
  var result = modernBabel.transformSync(code, {
253
277
  babelrc: false,
254
278
  comments: false,
@@ -257,18 +281,19 @@ exports.do = function (options) {
257
281
  sourceType: "unambiguous",
258
282
  presets: [
259
283
  [
260
- require.resolve("@babel/preset-env"),
261
- {
262
- modules: "commonjs",
263
- targets: options.babelTarget,
264
- useBuiltIns: false
265
- }
284
+ presetEnvPath,
285
+ presetOptions
266
286
  ]
267
287
  ]
268
288
  });
269
289
  return result && result.code || code;
270
290
  }
271
291
 
292
+ var legacyBabel = requireOptional("babel-core");
293
+ if (!legacyBabel) {
294
+ throw new Error("Babel transform requested, but neither @babel/core nor babel-core is installed");
295
+ }
296
+
272
297
  var babelOptions = {
273
298
  "plugins": [
274
299
  "babel-plugin-transform-es2015-arrow-functions",
@@ -294,6 +319,67 @@ exports.do = function (options) {
294
319
  };
295
320
  return legacyBabel.transform(code, babelOptions).code;
296
321
  }
322
+
323
+ function parseSource(code, options) {
324
+ try {
325
+ return esprima.parse(code, options);
326
+ } catch (esprimaError) {
327
+ if (!modernParser) {
328
+ throw esprimaError;
329
+ }
330
+
331
+ var parsed = modernParser.parse(code, {
332
+ sourceType: "unambiguous",
333
+ plugins: [
334
+ "estree",
335
+ "classProperties",
336
+ "classPrivateProperties",
337
+ "classPrivateMethods",
338
+ "optionalChaining",
339
+ "nullishCoalescingOperator",
340
+ "objectRestSpread"
341
+ ]
342
+ });
343
+ return {
344
+ type: "Program",
345
+ body: parsed.program.body,
346
+ sourceType: parsed.program.sourceType
347
+ };
348
+ }
349
+ }
350
+
351
+ function containsNodeType(root, names) {
352
+ var found = false;
353
+ var lookup = {};
354
+ names.forEach(name => {
355
+ lookup[name] = true;
356
+ });
357
+ traverser.traverseEx(root, [], function (node) {
358
+ if (lookup[node.type]) {
359
+ found = true;
360
+ this.abort();
361
+ }
362
+ return node;
363
+ });
364
+ return found;
365
+ }
366
+
367
+ function hasMangleUnsupportedSyntax(root) {
368
+ return containsNodeType(root, [ "Super" ]);
369
+ }
370
+
371
+ function dispatcherForMethod(method) {
372
+ if (method.async === true && method.generator === true) {
373
+ return "main$asyncGenerator";
374
+ }
375
+ if (method.async === true) {
376
+ return "main$async";
377
+ }
378
+ if (method.generator === true) {
379
+ return "main$generator";
380
+ }
381
+ return "main";
382
+ }
297
383
 
298
384
  options = _.merge({}, defaultOptions, options); // first argument gets mutated
299
385
  if (options.protections.virtualMachine.enabled) {
@@ -328,6 +414,7 @@ exports.do = function (options) {
328
414
  };
329
415
 
330
416
  var logger = new Logger(options.logAdapter);
417
+ var customBindAdded = false;
331
418
 
332
419
  var start = Date.now();
333
420
 
@@ -349,9 +436,17 @@ exports.do = function (options) {
349
436
 
350
437
  // Parse code
351
438
  var ast, modulesAST;
439
+ function addCustomBindOnce() {
440
+ if (!customBindAdded) {
441
+ var methods = new prMethods(logger);
442
+ methods.addCustomBind(ast);
443
+ customBindAdded = true;
444
+ }
445
+ }
446
+
352
447
  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));
448
+ modulesAST = _.mapValues(options.modulesCode, (code, key) => tryTag(key, () => parseSource(code, parseOptions)));
449
+ modulesAST.app = tryTag("app", () => parseSource(options.code, parseOptions));
355
450
  });
356
451
 
357
452
  // Merge depedencies into main modules
@@ -367,7 +462,7 @@ exports.do = function (options) {
367
462
  });
368
463
 
369
464
  // Simplify graph
370
- doTask("simplify", true, () => {
465
+ doTask("simplify", options.simplify !== false, () => {
371
466
  var normalizer = new prNormalizer(logger);
372
467
  ast = normalizer.simplify(ast);
373
468
  });
@@ -438,6 +533,11 @@ exports.do = function (options) {
438
533
  methods.removeFirstArguments(method, refers ? method.params.filter(x => x.name.indexOf("$$scope") == 0).length : 0);
439
534
  return methods.replaceArgumentReferences(method, true);
440
535
  });
536
+ fns.forEach(method => {
537
+ if (method && method.id && methodEntryPoints[method.id.name]) {
538
+ methodEntryPoints[method.id.name].dispatcher = dispatcherForMethod(method);
539
+ }
540
+ });
441
541
  if (options.features.control_flow) {
442
542
  methods.replaceFunctionCalls(ast, methodEntryPoints);
443
543
  fns.forEach(method => {
@@ -446,25 +546,88 @@ exports.do = function (options) {
446
546
  }
447
547
  });
448
548
 
449
- doTask("add_custom_bind", true, () => {
450
- methods.addCustomBind(ast);
451
- });
452
-
453
549
  doTask("control_flow", options.features.control_flow, () => {
454
550
  // Apply control flow flattening and merge methods
455
551
  var flattener = new prFlattener(logger, rng);
456
552
  var entry = rng.get(), exit = rng.get();
457
- flattener.addMethod(ast, entry, exit);
553
+ var dispatcherGroups = {
554
+ "main$async": {
555
+ async: true,
556
+ fns: []
557
+ },
558
+ "main$generator": {
559
+ generator: true,
560
+ fns: []
561
+ },
562
+ "main$asyncGenerator": {
563
+ async: true,
564
+ generator: true,
565
+ fns: []
566
+ }
567
+ };
568
+ var syncFns = [];
569
+
458
570
  fns.forEach(method => {
571
+ var dispatcher = dispatcherForMethod(method);
572
+ if (dispatcher == "main") {
573
+ syncFns.push(method);
574
+ } else {
575
+ dispatcherGroups[dispatcher].fns.push(method);
576
+ }
577
+ });
578
+
579
+ flattener.addMethod(ast, entry, exit);
580
+ syncFns.forEach(method => {
459
581
  methods.bumpArgumentsIndices(method, 1);
460
582
 
461
583
  var entry = methodEntryPoints[method.id.name].entry;
462
584
  flattener.addMethod(method.body, entry, exit);
463
585
  });
464
586
 
465
- ast = flattener.getProgram(entry, exit);
466
-
467
- ast = flattener.unifyPrefixStatements(ast);
587
+ var syncAst = flattener.getProgram(entry, exit, {
588
+ name: "main",
589
+ invoke: true
590
+ });
591
+
592
+ syncAst = flattener.unifyPrefixStatements(syncAst);
593
+
594
+ var asyncPrograms = [];
595
+ Object.keys(dispatcherGroups).forEach(name => {
596
+ var group = dispatcherGroups[name];
597
+ if (group.fns.length == 0) {
598
+ return;
599
+ }
600
+
601
+ var groupFlattener = new prFlattener(logger, rng);
602
+ var groupEntry = methodEntryPoints[group.fns[0].id.name].entry;
603
+ var groupExit = rng.get();
604
+
605
+ group.fns.forEach(method => {
606
+ methods.bumpArgumentsIndices(method, 1);
607
+
608
+ var entry = methodEntryPoints[method.id.name].entry;
609
+ groupFlattener.addMethod(method.body, entry, groupExit);
610
+ });
611
+
612
+ var groupAst = groupFlattener.getProgram(groupEntry, groupExit, {
613
+ name: name,
614
+ async: group.async === true,
615
+ generator: group.generator === true,
616
+ invoke: false
617
+ });
618
+
619
+ groupAst = groupFlattener.unifyPrefixStatements(groupAst);
620
+ asyncPrograms.push(groupAst);
621
+ });
622
+
623
+ if (asyncPrograms.length > 0) {
624
+ ast = {
625
+ type: "Program",
626
+ body: Array.prototype.concat.apply([], asyncPrograms.map(program => program.body)).concat(syncAst.body)
627
+ };
628
+ } else {
629
+ ast = syncAst;
630
+ }
468
631
  })
469
632
  .otherwise(() => {
470
633
  if (ast.type == "Program") {
@@ -476,6 +639,10 @@ exports.do = function (options) {
476
639
  };
477
640
  });
478
641
  });
642
+
643
+ doTask("add_runtime_helpers", options.runtimeHelpers !== false && (options.features.scope || options.features.object_packing || options.features.literals || options.babel === false), () => {
644
+ addCustomBindOnce();
645
+ });
479
646
 
480
647
  // Postprocessing
481
648
  doTask("postprocessing", true, () => {
@@ -489,6 +656,10 @@ exports.do = function (options) {
489
656
  });
490
657
 
491
658
  doTask("mangle", options.features.mangle, () => {
659
+ if (hasMangleUnsupportedSyntax(ast)) {
660
+ logger.warn("Skipping mangle because native modern syntax is not supported by the legacy mangler");
661
+ return;
662
+ }
492
663
  var uglifier = new prUglifier(logger);
493
664
  if (ast.type == "Program") {
494
665
  ast.type = "BlockStatement";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dacely/toildefender",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Modern JavaScript code protection, bytecode virtualization, and obfuscation for the Toil tech stack.",
5
5
  "author": "Dacely",
6
6
  "contributors": [
@@ -61,30 +61,7 @@
61
61
  "docs/all-modes-output.demo.js"
62
62
  ],
63
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",
64
+ "@babel/parser": "^8.0.0",
88
65
  "escodegen": "^2.1.0",
89
66
  "escope": "3.6.0",
90
67
  "esprima": "^4.0.1",
@@ -95,7 +72,7 @@
95
72
  "minimist": "1.2.0"
96
73
  },
97
74
  "engines": {
98
- "node": ">=24.0.0",
75
+ "node": ">=24.11.0",
99
76
  "npm": ">=10.0.0"
100
77
  },
101
78
  "publishConfig": {
@@ -9,6 +9,10 @@ var utils = require("../utils");
9
9
 
10
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
11
 
12
+ function isClassMethodBody(stack) {
13
+ return stack.some(frame => frame.node.type == "MethodDefinition" || frame.node.type == "ClassBody");
14
+ }
15
+
12
16
  module.exports = class DeadCode {
13
17
 
14
18
  constructor (logger) {
@@ -26,7 +30,7 @@ module.exports = class DeadCode {
26
30
  var rngAlpha = new utils.UniqueRandomAlpha(3);
27
31
 
28
32
  return traverser.traverse(ast, [], (node, stack) => {
29
- if (node.type == "BlockStatement") {
33
+ if (node.type == "BlockStatement" && !isClassMethodBody(stack)) {
30
34
  for (var i = 0; i < probability; ++i) {
31
35
  if (probability - i < Math.random()) {
32
36
  continue;
@@ -201,45 +201,69 @@ module.exports = class Flattener {
201
201
  * Get output switch construct program
202
202
  * @param {number} entry Entry point
203
203
  * @param {number} exit Exit point
204
+ * @param {Object} options Program options
204
205
  * @returns {Program} Switch construct program
205
206
  */
206
- getProgram (entry, exit) {
207
+ getProgram (entry, exit, options) {
207
208
  assert.equal(typeof entry, "number");
208
209
  assert.equal(typeof exit, "number");
210
+
211
+ options = options || {};
212
+ var name = options.name || "main";
213
+ var invoke = options.invoke !== false;
209
214
 
210
- return {
211
- type: "Program",
212
- body: [
213
- {
214
- type: "FunctionDeclaration",
215
- id: { type: "Identifier", name: "main" },
216
- params: [
217
- { type: "Identifier", name: "state" },
218
- { type: "Identifier", name: "scope" }
219
- ],
220
- body: {
221
- type: "BlockStatement",
222
- body: [
223
- {
224
- type: "WhileStatement",
225
- test: { type: "Literal", value: true },
226
- body: this.getCases(entry, exit)
227
- }
228
- ]
229
- }
215
+ var body = [
216
+ {
217
+ type: "FunctionDeclaration",
218
+ id: { type: "Identifier", name: name },
219
+ params: [
220
+ { type: "Identifier", name: "state" },
221
+ { type: "Identifier", name: "scope" }
222
+ ],
223
+ body: {
224
+ type: "BlockStatement",
225
+ body: [
226
+ {
227
+ type: "VariableDeclaration",
228
+ kind: "var",
229
+ declarations: [
230
+ {
231
+ type: "VariableDeclarator",
232
+ id: { type: "Identifier", name: "veilmark$tobethrown" },
233
+ init: null
234
+ }
235
+ ]
236
+ },
237
+ {
238
+ type: "WhileStatement",
239
+ test: { type: "Literal", value: true },
240
+ body: this.getCases(entry, exit)
241
+ }
242
+ ]
230
243
  },
231
- {
232
- type: "ExpressionStatement",
233
- expression: {
234
- type: "CallExpression",
235
- callee: { type: "Identifier", name: "main" },
236
- arguments: [
237
- { type: "Literal", value: entry },
238
- { type: "ObjectExpression", properties: [] }
239
- ]
240
- }
244
+ generator: options.generator === true,
245
+ expression: false,
246
+ async: options.async === true
247
+ }
248
+ ];
249
+
250
+ if (invoke) {
251
+ body.push({
252
+ type: "ExpressionStatement",
253
+ expression: {
254
+ type: "CallExpression",
255
+ callee: { type: "Identifier", name: name },
256
+ arguments: [
257
+ { type: "Literal", value: entry },
258
+ { type: "ObjectExpression", properties: [] }
259
+ ]
241
260
  }
242
- ]
261
+ });
262
+ }
263
+
264
+ return {
265
+ type: "Program",
266
+ body: body
243
267
  };
244
268
  }
245
269
 
@@ -25,6 +25,10 @@ function objectKey(prop) {
25
25
  return prop.key.name || prop.key.value;
26
26
  }
27
27
 
28
+ function canPackObjectExpression(node) {
29
+ return node.properties.every(prop => prop.type != "SpreadElement" && prop.key);
30
+ }
31
+
28
32
  function isBigIntLiteral(node) {
29
33
  return node.type == "Literal" && typeof node.value == "bigint";
30
34
  }
@@ -86,21 +90,10 @@ module.exports = class Identifiers {
86
90
  ast = traverser.traverse(ast, [], (node, stack) => {
87
91
  if (node.type == "ObjectExpression") {
88
92
  if (options.objectPacking === false) {
89
- var arr = [];
90
- node.properties.forEach(prop => {
91
- arr.push(literal(objectKey(prop)));
92
- arr.push(prop.value);
93
- });
94
- return {
95
- type: "CallExpression",
96
- callee: { type: "Identifier", name: "veilmark$toObject" },
97
- arguments: [
98
- {
99
- type: "ArrayExpression",
100
- elements: arr
101
- }
102
- ]
103
- };
93
+ return node;
94
+ }
95
+ if (!canPackObjectExpression(node)) {
96
+ return node;
104
97
  }
105
98
 
106
99
  var salt = utils.random(1, 65535);
@@ -117,6 +110,7 @@ module.exports = class Identifiers {
117
110
  type: "CallExpression",
118
111
  callee: { type: "Identifier", name: "veilmark$toObject" },
119
112
  arguments: [
113
+ literal(String(utils.hash(schema.join(",")))),
120
114
  {
121
115
  type: "ArrayExpression",
122
116
  elements: schema.map(literal)