simple-javascript-obf 0.1.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.
Files changed (43) hide show
  1. package/.github/workflows/node.js.yml +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/THIRD_PARTY_NOTICES.md +82 -0
  5. package/bin/js-obf +228 -0
  6. package/bin/obf.sh +257 -0
  7. package/package.json +26 -0
  8. package/src/index.js +106 -0
  9. package/src/options.js +128 -0
  10. package/src/pipeline.js +56 -0
  11. package/src/plugins/antiHook.js +123 -0
  12. package/src/plugins/controlFlowFlatten.js +203 -0
  13. package/src/plugins/deadCode.js +82 -0
  14. package/src/plugins/encodeMembers.js +44 -0
  15. package/src/plugins/entry.js +31 -0
  16. package/src/plugins/rename.js +100 -0
  17. package/src/plugins/stringEncode.js +494 -0
  18. package/src/plugins/vm/ast-utils.js +58 -0
  19. package/src/plugins/vm/compiler.js +113 -0
  20. package/src/plugins/vm/constants.js +72 -0
  21. package/src/plugins/vm/emit.js +916 -0
  22. package/src/plugins/vm/encoding.js +252 -0
  23. package/src/plugins/vm/index.js +366 -0
  24. package/src/plugins/vm/mapping.js +24 -0
  25. package/src/plugins/vm/normalize.js +692 -0
  26. package/src/plugins/vm/runtime.js +1145 -0
  27. package/src/plugins/vm.js +1 -0
  28. package/src/utils/names.js +55 -0
  29. package/src/utils/reserved.js +57 -0
  30. package/src/utils/rng.js +55 -0
  31. package/src/utils/stream.js +97 -0
  32. package/src/utils/string.js +13 -0
  33. package/test/bench-runner.js +78 -0
  34. package/test/benchmark-source.js +35 -0
  35. package/test/benchmark-vm.js +160 -0
  36. package/test/dist/bench.obf.js +1 -0
  37. package/test/dist/bench.original.js +35 -0
  38. package/test/dist/bench.vm.js +1 -0
  39. package/test/dist/sample-input.obf.js +1 -0
  40. package/test/dist/sample-input.vm.js +1 -0
  41. package/test/generate-obf.js +38 -0
  42. package/test/obf-smoke.js +129 -0
  43. package/test/sample-input.js +23 -0
@@ -0,0 +1,692 @@
1
+ function hasUnsupported(fnPath) {
2
+ let unsupported = false;
3
+ fnPath.traverse({
4
+ Function(path) {
5
+ if (path !== fnPath) {
6
+ path.skip();
7
+ }
8
+ },
9
+ MetaProperty(path) {
10
+ const { meta, property } = path.node;
11
+ if (
12
+ !meta ||
13
+ !property ||
14
+ meta.type !== "Identifier" ||
15
+ property.type !== "Identifier" ||
16
+ meta.name !== "new" ||
17
+ property.name !== "target"
18
+ ) {
19
+ unsupported = true;
20
+ path.stop();
21
+ }
22
+ },
23
+ Super(path) {
24
+ if (
25
+ !path.findParent(
26
+ (parent) =>
27
+ parent.isClassDeclaration() ||
28
+ parent.isClassExpression() ||
29
+ parent.isObjectMethod()
30
+ )
31
+ ) {
32
+ unsupported = true;
33
+ path.stop();
34
+ }
35
+ },
36
+ Import(path) {
37
+ unsupported = true;
38
+ path.stop();
39
+ },
40
+ ExportNamedDeclaration(path) {
41
+ unsupported = true;
42
+ path.stop();
43
+ },
44
+ ExportDefaultDeclaration(path) {
45
+ unsupported = true;
46
+ path.stop();
47
+ },
48
+ });
49
+ return unsupported;
50
+ }
51
+
52
+ function expandPattern(pattern, valueExpr, ctx, declarators) {
53
+ const { t } = ctx;
54
+ if (pattern.type === "Identifier") {
55
+ declarators.push(t.variableDeclarator(pattern, valueExpr));
56
+ return true;
57
+ }
58
+ if (pattern.type === "AssignmentPattern") {
59
+ const temp = t.identifier(ctx.nameGen.next());
60
+ declarators.push(t.variableDeclarator(temp, valueExpr));
61
+ const resolved = t.conditionalExpression(
62
+ t.binaryExpression("===", temp, t.identifier("undefined")),
63
+ pattern.right,
64
+ temp
65
+ );
66
+ return expandPattern(pattern.left, resolved, ctx, declarators);
67
+ }
68
+ if (pattern.type === "ArrayPattern") {
69
+ const temp = t.identifier(ctx.nameGen.next());
70
+ declarators.push(t.variableDeclarator(temp, valueExpr));
71
+ for (let i = 0; i < pattern.elements.length; i += 1) {
72
+ const elem = pattern.elements[i];
73
+ if (!elem) {
74
+ continue;
75
+ }
76
+ if (elem.type === "RestElement") {
77
+ if (elem.argument.type !== "Identifier") {
78
+ return false;
79
+ }
80
+ const sliceCall = t.callExpression(
81
+ t.memberExpression(
82
+ t.memberExpression(
83
+ t.memberExpression(t.identifier("Array"), t.identifier("prototype")),
84
+ t.identifier("slice")
85
+ ),
86
+ t.identifier("call")
87
+ ),
88
+ [temp, t.numericLiteral(i)]
89
+ );
90
+ declarators.push(t.variableDeclarator(elem.argument, sliceCall));
91
+ continue;
92
+ }
93
+ const access = t.memberExpression(temp, t.numericLiteral(i), true);
94
+ if (!expandPattern(elem, access, ctx, declarators)) {
95
+ return false;
96
+ }
97
+ }
98
+ return true;
99
+ }
100
+ if (pattern.type === "ObjectPattern") {
101
+ const temp = t.identifier(ctx.nameGen.next());
102
+ declarators.push(t.variableDeclarator(temp, valueExpr));
103
+ for (const prop of pattern.properties) {
104
+ if (prop.type === "RestElement") {
105
+ return false;
106
+ }
107
+ if (prop.computed) {
108
+ return false;
109
+ }
110
+ const key = prop.key;
111
+ let access = null;
112
+ if (key.type === "Identifier") {
113
+ access = t.memberExpression(temp, t.identifier(key.name));
114
+ } else if (key.type === "StringLiteral" || key.type === "NumericLiteral") {
115
+ access = t.memberExpression(temp, t.stringLiteral(String(key.value)), true);
116
+ } else {
117
+ return false;
118
+ }
119
+ if (!expandPattern(prop.value, access, ctx, declarators)) {
120
+ return false;
121
+ }
122
+ }
123
+ return true;
124
+ }
125
+ return false;
126
+ }
127
+
128
+ function rewriteParams(fnPath, ctx) {
129
+ const { t } = ctx;
130
+ const prologue = [];
131
+ const newParams = [];
132
+ const params = fnPath.node.params;
133
+
134
+ for (let i = 0; i < params.length; i += 1) {
135
+ const param = params[i];
136
+ if (param.type === "Identifier") {
137
+ newParams.push(param);
138
+ continue;
139
+ }
140
+ if (param.type === "AssignmentPattern") {
141
+ if (param.left.type === "Identifier") {
142
+ newParams.push(param.left);
143
+ prologue.push(
144
+ t.ifStatement(
145
+ t.binaryExpression(
146
+ "===",
147
+ param.left,
148
+ t.identifier("undefined")
149
+ ),
150
+ t.blockStatement([
151
+ t.expressionStatement(
152
+ t.assignmentExpression("=", param.left, param.right)
153
+ ),
154
+ ])
155
+ )
156
+ );
157
+ } else {
158
+ const temp = t.identifier(ctx.nameGen.next());
159
+ newParams.push(temp);
160
+ prologue.push(
161
+ t.ifStatement(
162
+ t.binaryExpression("===", temp, t.identifier("undefined")),
163
+ t.blockStatement([
164
+ t.expressionStatement(
165
+ t.assignmentExpression("=", temp, param.right)
166
+ ),
167
+ ])
168
+ )
169
+ );
170
+ const decls = [];
171
+ if (!expandPattern(param.left, temp, ctx, decls)) {
172
+ return null;
173
+ }
174
+ if (decls.length) {
175
+ prologue.push(t.variableDeclaration("var", decls));
176
+ }
177
+ }
178
+ continue;
179
+ }
180
+ if (param.type === "RestElement") {
181
+ if (param.argument.type !== "Identifier") {
182
+ return null;
183
+ }
184
+ const restId = param.argument;
185
+ const sliceCall = t.callExpression(
186
+ t.memberExpression(
187
+ t.memberExpression(
188
+ t.memberExpression(t.identifier("Array"), t.identifier("prototype")),
189
+ t.identifier("slice")
190
+ ),
191
+ t.identifier("call")
192
+ ),
193
+ [t.identifier("arguments"), t.numericLiteral(i)]
194
+ );
195
+ prologue.push(
196
+ t.variableDeclaration("var", [
197
+ t.variableDeclarator(restId, sliceCall),
198
+ ])
199
+ );
200
+ break;
201
+ }
202
+ if (param.type === "ObjectPattern" || param.type === "ArrayPattern") {
203
+ const temp = t.identifier(ctx.nameGen.next());
204
+ newParams.push(temp);
205
+ const decls = [];
206
+ if (!expandPattern(param, temp, ctx, decls)) {
207
+ return null;
208
+ }
209
+ if (decls.length) {
210
+ prologue.push(t.variableDeclaration("var", decls));
211
+ }
212
+ continue;
213
+ }
214
+ return null;
215
+ }
216
+
217
+ fnPath.node.params = newParams;
218
+ return prologue;
219
+ }
220
+
221
+ function createTempIdentifier(path, ctx, used) {
222
+ const { nameGen, t } = ctx;
223
+ let name = nameGen.next();
224
+ while (path.scope.hasBinding(name) || (used && used.has(name))) {
225
+ name = nameGen.next();
226
+ }
227
+ if (used) {
228
+ used.add(name);
229
+ }
230
+ return t.identifier(name);
231
+ }
232
+
233
+ function rewriteForOf(fnPath, ctx) {
234
+ const { t } = ctx;
235
+ let ok = true;
236
+ const usedNames = new Set();
237
+ fnPath.traverse({
238
+ Function(path) {
239
+ if (path !== fnPath) {
240
+ path.skip();
241
+ }
242
+ },
243
+ ForOfStatement(path) {
244
+ if (!ok) return;
245
+ if (path.node.await) {
246
+ ok = false;
247
+ path.stop();
248
+ return;
249
+ }
250
+ const left = path.node.left;
251
+ const right = t.cloneNode(path.node.right, true);
252
+ const body = path.node.body;
253
+
254
+ const iterId = createTempIdentifier(path, ctx, usedNames);
255
+ const stepId = createTempIdentifier(path, ctx, usedNames);
256
+
257
+ const iteratorKey = t.memberExpression(
258
+ t.identifier("Symbol"),
259
+ t.identifier("iterator")
260
+ );
261
+ const iteratorCall = t.callExpression(
262
+ t.memberExpression(right, iteratorKey, true),
263
+ []
264
+ );
265
+ const init = t.variableDeclaration("var", [
266
+ t.variableDeclarator(iterId, iteratorCall),
267
+ t.variableDeclarator(stepId, null),
268
+ ]);
269
+
270
+ const stepAssign = t.assignmentExpression(
271
+ "=",
272
+ stepId,
273
+ t.callExpression(t.memberExpression(iterId, t.identifier("next")), [])
274
+ );
275
+ const test = t.unaryExpression(
276
+ "!",
277
+ t.memberExpression(stepAssign, t.identifier("done"))
278
+ );
279
+
280
+ const valueExpr = t.memberExpression(stepId, t.identifier("value"));
281
+ const prologue = [];
282
+
283
+ if (left.type === "VariableDeclaration") {
284
+ if (left.declarations.length !== 1) {
285
+ ok = false;
286
+ path.stop();
287
+ return;
288
+ }
289
+ const decl = left.declarations[0];
290
+ prologue.push(
291
+ t.variableDeclaration("var", [
292
+ t.variableDeclarator(t.cloneNode(decl.id, true), valueExpr),
293
+ ])
294
+ );
295
+ } else if (left.type === "Identifier" || left.type === "MemberExpression") {
296
+ prologue.push(
297
+ t.expressionStatement(
298
+ t.assignmentExpression("=", t.cloneNode(left, true), valueExpr)
299
+ )
300
+ );
301
+ } else if (
302
+ left.type === "ObjectPattern" ||
303
+ left.type === "ArrayPattern" ||
304
+ left.type === "AssignmentPattern"
305
+ ) {
306
+ prologue.push(
307
+ t.variableDeclaration("var", [
308
+ t.variableDeclarator(t.cloneNode(left, true), valueExpr),
309
+ ])
310
+ );
311
+ } else {
312
+ ok = false;
313
+ path.stop();
314
+ return;
315
+ }
316
+
317
+ let loopBody;
318
+ if (body.type === "BlockStatement") {
319
+ loopBody = t.blockStatement([...prologue, ...body.body]);
320
+ } else {
321
+ loopBody = t.blockStatement([...prologue, body]);
322
+ }
323
+
324
+ const forStmt = t.forStatement(init, test, null, loopBody);
325
+ path.replaceWith(forStmt);
326
+ },
327
+ });
328
+ return ok;
329
+ }
330
+
331
+ function rewriteBlockScoped(fnPath, ctx) {
332
+ const { nameGen } = ctx;
333
+ fnPath.traverse({
334
+ VariableDeclaration(path) {
335
+ if (path.node.kind === "var") {
336
+ return;
337
+ }
338
+ const names = [];
339
+ for (const decl of path.node.declarations) {
340
+ if (decl.id.type === "Identifier") {
341
+ names.push(decl.id.name);
342
+ }
343
+ }
344
+ for (const name of names) {
345
+ const binding = path.scope.getBinding(name);
346
+ if (!binding) {
347
+ continue;
348
+ }
349
+ let newName = nameGen.next();
350
+ while (path.scope.hasBinding(newName)) {
351
+ newName = nameGen.next();
352
+ }
353
+ binding.scope.rename(name, newName);
354
+ }
355
+ path.node.kind = "var";
356
+ },
357
+ ClassDeclaration(path) {
358
+ if (!path.node.id) {
359
+ return;
360
+ }
361
+ const name = path.node.id.name;
362
+ const binding = path.scope.getBinding(name);
363
+ if (!binding) {
364
+ return;
365
+ }
366
+ let newName = nameGen.next();
367
+ while (path.scope.hasBinding(newName)) {
368
+ newName = nameGen.next();
369
+ }
370
+ binding.scope.rename(name, newName);
371
+ },
372
+ ForStatement(path) {
373
+ if (path.node.init && path.node.init.type === "VariableDeclaration") {
374
+ path.node.init.kind = "var";
375
+ }
376
+ },
377
+ CatchClause(path) {
378
+ if (path.node.param && path.node.param.type === "Identifier") {
379
+ const name = path.node.param.name;
380
+ if (fnPath.scope.hasBinding(name)) {
381
+ let newName = nameGen.next();
382
+ while (fnPath.scope.hasBinding(newName)) {
383
+ newName = nameGen.next();
384
+ }
385
+ path.scope.rename(name, newName);
386
+ }
387
+ }
388
+ },
389
+ });
390
+ }
391
+
392
+ function rewriteDestructuringDecls(fnPath, ctx) {
393
+ const { t } = ctx;
394
+ let ok = true;
395
+ fnPath.traverse({
396
+ VariableDeclaration(path) {
397
+ if (!ok) return;
398
+ const newDeclarators = [];
399
+ for (const decl of path.node.declarations) {
400
+ if (decl.id.type === "Identifier") {
401
+ newDeclarators.push(decl);
402
+ continue;
403
+ }
404
+ const init = decl.init || t.identifier("undefined");
405
+ const decls = [];
406
+ if (!expandPattern(decl.id, init, ctx, decls)) {
407
+ ok = false;
408
+ return;
409
+ }
410
+ newDeclarators.push(...decls);
411
+ }
412
+ if (!ok) return;
413
+ path.node.declarations = newDeclarators;
414
+ path.node.kind = "var";
415
+ },
416
+ });
417
+ return ok;
418
+ }
419
+
420
+ function rewriteDestructuringAssignments(fnPath, ctx) {
421
+ let ok = true;
422
+ fnPath.traverse({
423
+ ExpressionStatement(path) {
424
+ const expr = path.node.expression;
425
+ if (!expr || expr.type !== "AssignmentExpression") {
426
+ return;
427
+ }
428
+ if (expr.left.type !== "ObjectPattern" && expr.left.type !== "ArrayPattern") {
429
+ return;
430
+ }
431
+ ok = false;
432
+ },
433
+ });
434
+ return ok;
435
+ }
436
+
437
+ function hoistFunctionDeclarations(fnPath, ctx) {
438
+ const { t } = ctx;
439
+ const body = fnPath.node.body;
440
+ if (!body || body.type !== "BlockStatement") {
441
+ return [];
442
+ }
443
+ const prologue = [];
444
+ const rest = [];
445
+ for (const stmt of body.body) {
446
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
447
+ const funcExpr = t.functionExpression(
448
+ stmt.id,
449
+ stmt.params,
450
+ stmt.body,
451
+ stmt.generator,
452
+ stmt.async
453
+ );
454
+ prologue.push(
455
+ t.variableDeclaration("var", [
456
+ t.variableDeclarator(stmt.id, funcExpr),
457
+ ])
458
+ );
459
+ } else {
460
+ rest.push(stmt);
461
+ }
462
+ }
463
+ body.body = rest;
464
+ return prologue;
465
+ }
466
+
467
+ function rewriteClosureRefs(fnPath, ctx, envId, envThisKey, envArgsKey) {
468
+ const { t } = ctx;
469
+ const rootScope = fnPath.scope;
470
+
471
+ fnPath.traverse({
472
+ Function(innerPath) {
473
+ if (innerPath === fnPath) {
474
+ return;
475
+ }
476
+ innerPath.traverse({
477
+ Identifier(idPath) {
478
+ if (!idPath.isReferencedIdentifier()) {
479
+ return;
480
+ }
481
+ const name = idPath.node.name;
482
+ if (name === "undefined") {
483
+ return;
484
+ }
485
+ const binding = innerPath.scope.getBinding(name);
486
+ if (binding && binding.scope === rootScope) {
487
+ idPath.replaceWith(
488
+ t.memberExpression(envId, t.identifier(name))
489
+ );
490
+ return;
491
+ }
492
+ if (innerPath.isArrowFunctionExpression() && name === "arguments") {
493
+ if (!binding) {
494
+ idPath.replaceWith(
495
+ t.memberExpression(
496
+ envId,
497
+ t.stringLiteral(envArgsKey),
498
+ true
499
+ )
500
+ );
501
+ }
502
+ return;
503
+ }
504
+ },
505
+ ThisExpression(thisPath) {
506
+ if (innerPath.isArrowFunctionExpression()) {
507
+ thisPath.replaceWith(
508
+ t.memberExpression(
509
+ envId,
510
+ t.stringLiteral(envThisKey),
511
+ true
512
+ )
513
+ );
514
+ }
515
+ },
516
+ });
517
+ },
518
+ });
519
+ }
520
+
521
+ function normalizeFunction(fnPath, ctx) {
522
+ if (fnPath.node.generator) {
523
+ return null;
524
+ }
525
+ if (hasUnsupported(fnPath)) {
526
+ return null;
527
+ }
528
+ if (!rewriteForOf(fnPath, ctx)) {
529
+ return null;
530
+ }
531
+ rewriteBlockScoped(fnPath, ctx);
532
+ const paramPrologue = rewriteParams(fnPath, ctx);
533
+ if (!paramPrologue) {
534
+ return null;
535
+ }
536
+ const funcDeclPrologue = hoistFunctionDeclarations(fnPath, ctx);
537
+ if (!rewriteDestructuringDecls(fnPath, ctx)) {
538
+ return null;
539
+ }
540
+ if (!rewriteDestructuringAssignments(fnPath, ctx)) {
541
+ return null;
542
+ }
543
+
544
+ const body = fnPath.node.body;
545
+ if (!body || body.type !== "BlockStatement") {
546
+ return null;
547
+ }
548
+ const directives = [];
549
+ const rest = [];
550
+ for (const stmt of body.body) {
551
+ if (stmt.type === "ExpressionStatement" && stmt.directive) {
552
+ directives.push(stmt);
553
+ } else {
554
+ rest.push(stmt);
555
+ }
556
+ }
557
+ body.body = [...directives, ...paramPrologue, ...funcDeclPrologue, ...rest];
558
+ return body;
559
+ }
560
+
561
+ function hasBlockScoped(fnPath) {
562
+ let found = false;
563
+ fnPath.traverse({
564
+ Function(path) {
565
+ if (path !== fnPath) {
566
+ path.skip();
567
+ }
568
+ },
569
+ VariableDeclaration(path) {
570
+ if (path.node.kind !== "var") {
571
+ found = true;
572
+ path.stop();
573
+ }
574
+ },
575
+ });
576
+ return found;
577
+ }
578
+
579
+ function canVirtualize(fnPath, ctx) {
580
+ let ok = true;
581
+ if (!ctx.options.vm.downlevel && hasBlockScoped(fnPath)) {
582
+ return false;
583
+ }
584
+ fnPath.traverse({
585
+ Function(path) {
586
+ if (path !== fnPath) {
587
+ path.skip();
588
+ }
589
+ },
590
+ ForInStatement(path) {
591
+ ok = false;
592
+ path.stop();
593
+ },
594
+ LabeledStatement(path) {
595
+ ok = false;
596
+ path.stop();
597
+ },
598
+ BreakStatement(path) {
599
+ if (path.node.label) {
600
+ ok = false;
601
+ path.stop();
602
+ }
603
+ },
604
+ ContinueStatement(path) {
605
+ if (path.node.label) {
606
+ ok = false;
607
+ path.stop();
608
+ }
609
+ },
610
+ SpreadElement(path) {
611
+ ok = false;
612
+ path.stop();
613
+ },
614
+ AssignmentExpression(path) {
615
+ if (path.node.left.type === "ObjectPattern" || path.node.left.type === "ArrayPattern") {
616
+ ok = false;
617
+ path.stop();
618
+ }
619
+ },
620
+ UpdateExpression() {
621
+ },
622
+ ObjectPattern(path) {
623
+ for (const prop of path.node.properties) {
624
+ if (prop.type === "RestElement" || prop.computed) {
625
+ ok = false;
626
+ path.stop();
627
+ break;
628
+ }
629
+ }
630
+ },
631
+ LogicalExpression(path) {
632
+ if (path.node.operator === "??") {
633
+ ok = false;
634
+ path.stop();
635
+ }
636
+ },
637
+ OptionalMemberExpression(path) {
638
+ ok = false;
639
+ path.stop();
640
+ },
641
+ OptionalCallExpression(path) {
642
+ ok = false;
643
+ path.stop();
644
+ },
645
+ UnaryExpression(path) {
646
+ if (path.node.operator === "delete") {
647
+ ok = false;
648
+ path.stop();
649
+ }
650
+ },
651
+ });
652
+ return ok;
653
+ }
654
+
655
+ function collectLocals(fnPath) {
656
+ const locals = new Set();
657
+ for (const param of fnPath.node.params) {
658
+ if (param.type === "Identifier") {
659
+ locals.add(param.name);
660
+ }
661
+ }
662
+ fnPath.traverse({
663
+ Function(path) {
664
+ if (path !== fnPath) {
665
+ path.skip();
666
+ }
667
+ },
668
+ VariableDeclarator(path) {
669
+ if (path.node.id.type === "Identifier") {
670
+ locals.add(path.node.id.name);
671
+ }
672
+ },
673
+ ClassDeclaration(path) {
674
+ if (path.node.id) {
675
+ locals.add(path.node.id.name);
676
+ }
677
+ },
678
+ CatchClause(path) {
679
+ if (path.node.param && path.node.param.type === "Identifier") {
680
+ locals.add(path.node.param.name);
681
+ }
682
+ },
683
+ });
684
+ return locals;
685
+ }
686
+
687
+ module.exports = {
688
+ canVirtualize,
689
+ collectLocals,
690
+ normalizeFunction,
691
+ rewriteClosureRefs,
692
+ };