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.
Files changed (113) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +43 -43
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +20 -20
  3. package/.github/workflows/node.js.yml +28 -28
  4. package/.prettierrc +4 -4
  5. package/CHANGELOG.md +1015 -989
  6. package/CODE_OF_CONDUCT.md +131 -131
  7. package/CONTRIBUTING.md +52 -52
  8. package/LICENSE +21 -21
  9. package/Migration.md +72 -71
  10. package/README.md +86 -78
  11. package/dist/constants.js +43 -43
  12. package/dist/index.js +14 -23
  13. package/dist/obfuscator.js +31 -25
  14. package/dist/order.js +4 -4
  15. package/dist/presets.js +31 -31
  16. package/dist/templates/integrityTemplate.js +4 -4
  17. package/dist/templates/template.js +1 -2
  18. package/dist/transforms/astScrambler.js +1 -2
  19. package/dist/transforms/calculator.js +1 -2
  20. package/dist/transforms/controlFlowFlattening.js +93 -63
  21. package/dist/transforms/deadCode.js +1 -2
  22. package/dist/transforms/dispatcher.js +4 -5
  23. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +1 -2
  24. package/dist/transforms/extraction/objectExtraction.js +1 -2
  25. package/dist/transforms/finalizer.js +1 -2
  26. package/dist/transforms/flatten.js +1 -2
  27. package/dist/transforms/identifier/globalConcealing.js +15 -2
  28. package/dist/transforms/identifier/movedDeclarations.js +8 -7
  29. package/dist/transforms/identifier/renameVariables.js +7 -7
  30. package/dist/transforms/lock/integrity.js +11 -10
  31. package/dist/transforms/lock/lock.js +2 -3
  32. package/dist/transforms/minify.js +11 -29
  33. package/dist/transforms/opaquePredicates.js +1 -2
  34. package/dist/transforms/pack.js +5 -2
  35. package/dist/transforms/plugin.js +18 -19
  36. package/dist/transforms/preparation.js +16 -16
  37. package/dist/transforms/renameLabels.js +1 -2
  38. package/dist/transforms/rgf.js +8 -9
  39. package/dist/transforms/shuffle.js +1 -2
  40. package/dist/transforms/string/encoding.js +1 -2
  41. package/dist/transforms/string/stringCompression.js +3 -4
  42. package/dist/transforms/string/stringConcealing.js +8 -3
  43. package/dist/transforms/string/stringEncoding.js +1 -2
  44. package/dist/transforms/variableMasking.js +1 -2
  45. package/dist/utils/NameGen.js +2 -2
  46. package/dist/utils/PredicateGen.js +1 -2
  47. package/dist/utils/ast-utils.js +87 -88
  48. package/dist/utils/function-utils.js +8 -8
  49. package/dist/utils/node.js +5 -6
  50. package/dist/utils/object-utils.js +4 -4
  51. package/dist/utils/random-utils.js +20 -20
  52. package/dist/utils/static-utils.js +1 -2
  53. package/dist/validateOptions.js +4 -7
  54. package/index.d.ts +17 -17
  55. package/package.json +61 -59
  56. package/src/constants.ts +168 -168
  57. package/src/index.ts +118 -118
  58. package/src/obfuscationResult.ts +49 -49
  59. package/src/obfuscator.ts +501 -497
  60. package/src/options.ts +407 -407
  61. package/src/order.ts +54 -54
  62. package/src/presets.ts +125 -125
  63. package/src/templates/bufferToStringTemplate.ts +57 -57
  64. package/src/templates/deadCodeTemplates.ts +1185 -1185
  65. package/src/templates/getGlobalTemplate.ts +76 -76
  66. package/src/templates/integrityTemplate.ts +64 -64
  67. package/src/templates/setFunctionLengthTemplate.ts +11 -11
  68. package/src/templates/stringCompressionTemplate.ts +20 -20
  69. package/src/templates/tamperProtectionTemplates.ts +120 -120
  70. package/src/templates/template.ts +224 -224
  71. package/src/transforms/astScrambler.ts +99 -99
  72. package/src/transforms/calculator.ts +99 -99
  73. package/src/transforms/controlFlowFlattening.ts +1716 -1664
  74. package/src/transforms/deadCode.ts +82 -82
  75. package/src/transforms/dispatcher.ts +450 -450
  76. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +156 -158
  77. package/src/transforms/extraction/objectExtraction.ts +186 -186
  78. package/src/transforms/finalizer.ts +74 -74
  79. package/src/transforms/flatten.ts +421 -420
  80. package/src/transforms/identifier/globalConcealing.ts +315 -295
  81. package/src/transforms/identifier/movedDeclarations.ts +252 -251
  82. package/src/transforms/identifier/renameVariables.ts +328 -321
  83. package/src/transforms/lock/integrity.ts +117 -114
  84. package/src/transforms/lock/lock.ts +418 -425
  85. package/src/transforms/minify.ts +615 -629
  86. package/src/transforms/opaquePredicates.ts +100 -100
  87. package/src/transforms/pack.ts +239 -231
  88. package/src/transforms/plugin.ts +173 -173
  89. package/src/transforms/preparation.ts +349 -347
  90. package/src/transforms/renameLabels.ts +175 -175
  91. package/src/transforms/rgf.ts +322 -322
  92. package/src/transforms/shuffle.ts +82 -82
  93. package/src/transforms/string/encoding.ts +144 -144
  94. package/src/transforms/string/stringCompression.ts +128 -128
  95. package/src/transforms/string/stringConcealing.ts +312 -298
  96. package/src/transforms/string/stringEncoding.ts +80 -80
  97. package/src/transforms/string/stringSplitting.ts +77 -77
  98. package/src/transforms/variableMasking.ts +257 -257
  99. package/src/utils/IntGen.ts +33 -33
  100. package/src/utils/NameGen.ts +116 -116
  101. package/src/utils/PredicateGen.ts +61 -61
  102. package/src/utils/ast-utils.ts +663 -663
  103. package/src/utils/function-utils.ts +50 -50
  104. package/src/utils/gen-utils.ts +48 -48
  105. package/src/utils/node.ts +78 -78
  106. package/src/utils/object-utils.ts +21 -21
  107. package/src/utils/random-utils.ts +93 -93
  108. package/src/utils/static-utils.ts +66 -66
  109. package/src/validateOptions.ts +256 -259
  110. package/tsconfig.json +13 -14
  111. package/dist/probability.js +0 -1
  112. package/dist/transforms/functionOutlining.js +0 -230
  113. package/dist/utils/ControlObject.js +0 -125
package/src/obfuscator.ts CHANGED
@@ -1,497 +1,501 @@
1
- import { ok } from "assert";
2
- import * as t from "@babel/types";
3
- import generate from "@babel/generator";
4
- import traverse from "@babel/traverse";
5
- import { parse } from "@babel/parser";
6
- import { ObfuscateOptions, ProbabilityMap } from "./options";
7
- import { applyDefaultsToOptions, validateOptions } from "./validateOptions";
8
- import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult";
9
- import { NameGen } from "./utils/NameGen";
10
- import { Order } from "./order";
11
- import {
12
- PluginFunction,
13
- PluginInstance,
14
- PluginObject,
15
- } from "./transforms/plugin";
16
- import { createObject } from "./utils/object-utils";
17
-
18
- // Transforms
19
- import preparation from "./transforms/preparation";
20
- import renameVariables from "./transforms/identifier/renameVariables";
21
- import variableMasking from "./transforms/variableMasking";
22
- import dispatcher from "./transforms/dispatcher";
23
- import duplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval";
24
- import objectExtraction from "./transforms/extraction/objectExtraction";
25
- import globalConcealing from "./transforms/identifier/globalConcealing";
26
- import stringCompression from "./transforms/string/stringCompression";
27
- import deadCode from "./transforms/deadCode";
28
- import stringSplitting from "./transforms/string/stringSplitting";
29
- import shuffle from "./transforms/shuffle";
30
- import astScrambler from "./transforms/astScrambler";
31
- import calculator from "./transforms/calculator";
32
- import movedDeclarations from "./transforms/identifier/movedDeclarations";
33
- import renameLabels from "./transforms/renameLabels";
34
- import rgf from "./transforms/rgf";
35
- import flatten from "./transforms/flatten";
36
- import stringConcealing from "./transforms/string/stringConcealing";
37
- import lock from "./transforms/lock/lock";
38
- import controlFlowFlattening from "./transforms/controlFlowFlattening";
39
- import opaquePredicates from "./transforms/opaquePredicates";
40
- import minify from "./transforms/minify";
41
- import finalizer from "./transforms/finalizer";
42
- import integrity from "./transforms/lock/integrity";
43
- import pack, { PackInterface } from "./transforms/pack";
44
-
45
- export const DEFAULT_OPTIONS: ObfuscateOptions = {
46
- target: "node",
47
- compact: true,
48
- };
49
-
50
- export default class Obfuscator {
51
- plugins: {
52
- plugin: PluginObject;
53
- pluginInstance: PluginInstance;
54
- }[] = [];
55
- options: ObfuscateOptions;
56
-
57
- totalPossibleTransforms: number = 0;
58
-
59
- globalState = {
60
- lock: {
61
- integrity: {
62
- sensitivityRegex: / |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g,
63
- },
64
-
65
- createCountermeasuresCode: (): t.Statement[] => {
66
- throw new Error("Not implemented");
67
- },
68
- },
69
-
70
- // After RenameVariables completes, this map will contain the renamed variables
71
- // Most use cases involve grabbing the Program(global) mappings
72
- renamedVariables: new Map<t.Node, Map<string, string>>(),
73
-
74
- // Internal functions, should not be renamed/removed
75
- internals: {
76
- stringCompressionLibraryName: "",
77
- nativeFunctionName: "",
78
- integrityHashName: "",
79
- invokeCountermeasuresFnName: "",
80
- },
81
- };
82
-
83
- // Pack Interface for sharing globals across RGF functions
84
- packInterface: PackInterface;
85
-
86
- isInternalVariable(name: string) {
87
- return Object.values(this.globalState.internals).includes(name);
88
- }
89
-
90
- shouldTransformNativeFunction(nameAndPropertyPath: string[]) {
91
- if (!this.options.lock?.tamperProtection) {
92
- return false;
93
- }
94
-
95
- // Custom implementation for Tamper Protection
96
- if (typeof this.options.lock.tamperProtection === "function") {
97
- return this.options.lock.tamperProtection(nameAndPropertyPath.join("."));
98
- }
99
-
100
- if (
101
- this.options.target === "browser" &&
102
- nameAndPropertyPath.length === 1 &&
103
- nameAndPropertyPath[0] === "fetch"
104
- ) {
105
- return true;
106
- }
107
-
108
- var globalObject = {};
109
- try {
110
- globalObject =
111
- typeof globalThis !== "undefined"
112
- ? globalThis
113
- : typeof window !== "undefined"
114
- ? window
115
- : typeof global !== "undefined"
116
- ? global
117
- : typeof self !== "undefined"
118
- ? self
119
- : new Function("return this")();
120
- } catch (e) {}
121
-
122
- var fn = globalObject;
123
- for (var item of nameAndPropertyPath) {
124
- fn = fn?.[item];
125
- if (typeof fn === "undefined") return false;
126
- }
127
-
128
- var hasNativeCode =
129
- typeof fn === "function" && ("" + fn).includes("[native code]");
130
-
131
- return hasNativeCode;
132
- }
133
-
134
- getStringCompressionLibraryName() {
135
- if (this.parentObfuscator) {
136
- return this.parentObfuscator.getStringCompressionLibraryName();
137
- }
138
-
139
- return this.globalState.internals.stringCompressionLibraryName;
140
- }
141
-
142
- getObfuscatedVariableName(originalName: string, programNode: t.Node) {
143
- const renamedVariables = this.globalState.renamedVariables.get(programNode);
144
-
145
- return renamedVariables?.get(originalName) || originalName;
146
- }
147
-
148
- /**
149
- * The main Name Generator for `Rename Variables`
150
- */
151
- nameGen: NameGen;
152
-
153
- public constructor(
154
- userOptions: ObfuscateOptions,
155
- public parentObfuscator?: Obfuscator
156
- ) {
157
- validateOptions(userOptions);
158
- this.options = applyDefaultsToOptions({ ...userOptions });
159
- this.nameGen = new NameGen(this.options.identifierGenerator);
160
-
161
- const shouldAddLockTransform =
162
- this.options.lock &&
163
- (Object.keys(this.options.lock).filter(
164
- (key) =>
165
- key !== "customLocks" &&
166
- this.isProbabilityMapProbable(this.options.lock[key])
167
- ).length > 0 ||
168
- this.options.lock.customLocks.length > 0);
169
-
170
- const allPlugins: PluginFunction[] = [];
171
-
172
- const push = (probabilityMap, ...pluginFns) => {
173
- this.totalPossibleTransforms += pluginFns.length;
174
- if (!this.isProbabilityMapProbable(probabilityMap)) return;
175
-
176
- allPlugins.push(...pluginFns);
177
- };
178
-
179
- push(true, preparation);
180
- push(this.options.objectExtraction, objectExtraction);
181
- push(this.options.flatten, flatten);
182
- push(shouldAddLockTransform, lock);
183
- push(this.options.rgf, rgf);
184
- push(this.options.dispatcher, dispatcher);
185
- push(this.options.deadCode, deadCode);
186
- push(this.options.controlFlowFlattening, controlFlowFlattening);
187
- push(this.options.calculator, calculator);
188
- push(this.options.globalConcealing, globalConcealing);
189
- push(this.options.opaquePredicates, opaquePredicates);
190
- push(this.options.stringSplitting, stringSplitting);
191
- push(this.options.stringConcealing, stringConcealing);
192
- // String Compression is only applied to the main obfuscator
193
- // Any RGF functions will not have string compression due to the size of the decompression function
194
-
195
- push(
196
- !parentObfuscator && this.options.stringCompression,
197
- stringCompression
198
- );
199
- push(this.options.variableMasking, variableMasking);
200
- push(this.options.duplicateLiteralsRemoval, duplicateLiteralsRemoval);
201
- push(this.options.shuffle, shuffle);
202
- push(this.options.movedDeclarations, movedDeclarations);
203
- push(this.options.renameLabels, renameLabels);
204
- push(this.options.minify, minify);
205
- push(this.options.astScrambler, astScrambler);
206
- push(this.options.renameVariables, renameVariables);
207
-
208
- push(true, finalizer);
209
- push(this.options.pack, pack);
210
- push(this.options.lock?.integrity, integrity);
211
-
212
- allPlugins.map((pluginFunction) => {
213
- var pluginInstance: PluginInstance;
214
- var plugin = pluginFunction({
215
- Plugin: (order: Order, mergeObject?) => {
216
- ok(typeof order === "number");
217
- var pluginOptions = {
218
- order,
219
- name: Order[order],
220
- };
221
-
222
- const newPluginInstance = new PluginInstance(pluginOptions, this);
223
- if (typeof mergeObject === "object" && mergeObject) {
224
- Object.assign(newPluginInstance, mergeObject);
225
- }
226
-
227
- pluginInstance = newPluginInstance;
228
-
229
- // @ts-ignore
230
- return newPluginInstance as any;
231
- },
232
- });
233
-
234
- ok(
235
- pluginInstance,
236
- "Plugin instance not created: " + pluginFunction.toString()
237
- );
238
-
239
- this.plugins.push({
240
- plugin,
241
- pluginInstance,
242
- });
243
- });
244
-
245
- this.plugins = this.plugins.sort(
246
- (a, b) => a.pluginInstance.order - b.pluginInstance.order
247
- );
248
-
249
- if (!parentObfuscator && this.hasPlugin(Order.StringCompression)) {
250
- this.globalState.internals.stringCompressionLibraryName =
251
- this.nameGen.generate(false);
252
- }
253
- }
254
-
255
- index: number = 0;
256
-
257
- obfuscateAST(
258
- ast: babel.types.File,
259
- options?: {
260
- profiler?: ProfilerCallback;
261
- }
262
- ): babel.types.File {
263
- let finalASTHandler: PluginObject["finalASTHandler"][] = [];
264
-
265
- for (let i = 0; i < this.plugins.length; i++) {
266
- this.index = i;
267
- const { plugin, pluginInstance } = this.plugins[i];
268
-
269
- if (this.options.verbose) {
270
- console.log(
271
- `Applying ${pluginInstance.name} (${i + 1}/${this.plugins.length})`
272
- );
273
- }
274
-
275
- traverse(ast, plugin.visitor);
276
- plugin.post?.();
277
-
278
- if (plugin.finalASTHandler) {
279
- finalASTHandler.push(plugin.finalASTHandler);
280
- }
281
-
282
- if (options?.profiler) {
283
- options?.profiler({
284
- index: i,
285
- currentTransform: pluginInstance.name,
286
- nextTransform: this.plugins[i + 1]?.pluginInstance?.name,
287
- totalTransforms: this.plugins.length,
288
- });
289
- }
290
- }
291
-
292
- for (const handler of finalASTHandler) {
293
- ast = handler(ast);
294
- }
295
-
296
- return ast;
297
- }
298
-
299
- async obfuscate(sourceCode: string): Promise<ObfuscationResult> {
300
- // Parse the source code into an AST
301
- let ast = Obfuscator.parseCode(sourceCode);
302
-
303
- ast = this.obfuscateAST(ast);
304
-
305
- // Generate the transformed code from the modified AST with comments removed and compacted output
306
- const code = this.generateCode(ast);
307
-
308
- return {
309
- code: code,
310
- };
311
- }
312
-
313
- getPlugin(order: Order) {
314
- return this.plugins.find((x) => x.pluginInstance.order === order);
315
- }
316
-
317
- hasPlugin(order: Order) {
318
- return !!this.getPlugin(order);
319
- }
320
-
321
- /**
322
- * Calls `Obfuscator.generateCode` with the current instance options
323
- */
324
- generateCode<T extends t.Node = t.File>(ast: T): string {
325
- return Obfuscator.generateCode(ast, this.options);
326
- }
327
-
328
- /**
329
- * Generates code from an AST using `@babel/generator`
330
- */
331
- static generateCode<T extends t.Node = t.File>(
332
- ast: T,
333
- options: ObfuscateOptions = DEFAULT_OPTIONS
334
- ): string {
335
- const compact = !!options.compact;
336
-
337
- const { code } = generate(ast, {
338
- comments: false, // Remove comments
339
- minified: compact,
340
- // jsescOption: {
341
- // String Encoding using Babel
342
- // escapeEverything: true,
343
- // },
344
- });
345
-
346
- return code;
347
- }
348
-
349
- /**
350
- * Parses the source code into an AST using `babel.parseSync`
351
- */
352
- static parseCode(sourceCode: string): babel.types.File {
353
- // Parse the source code into an AST
354
- let ast = parse(sourceCode, {
355
- sourceType: "unambiguous",
356
- });
357
-
358
- return ast;
359
- }
360
-
361
- probabilityMapCounter = new WeakMap<Object, number>();
362
-
363
- /**
364
- * Evaluates a ProbabilityMap.
365
- * @param map The setting object.
366
- * @param customFnArgs Args given to user-implemented function, such as a variable name.
367
- */
368
- computeProbabilityMap<
369
- T,
370
- F extends (...args: any[]) => any = (...args: any[]) => any
371
- >(
372
- map: ProbabilityMap<T, F>,
373
- ...customImplementationArgs: F extends (...args: infer P) => any ? P : never
374
- ): boolean | string {
375
- // Check if this probability map uses the {value: ..., limit: ...} format
376
- if (typeof map === "object" && map && "value" in map) {
377
- // Check for the limit property
378
- if ("limit" in map && typeof map.limit === "number" && map.limit >= 0) {
379
- // Check if the limit has been reached
380
- if (this.probabilityMapCounter.get(map) >= map.limit) {
381
- return false;
382
- }
383
- }
384
-
385
- var value = this.computeProbabilityMap(
386
- map.value as ProbabilityMap<T, F>,
387
- ...customImplementationArgs
388
- );
389
-
390
- if (value) {
391
- // Increment the counter for this map
392
- this.probabilityMapCounter.set(
393
- map,
394
- this.probabilityMapCounter.get(map) + 1 || 1
395
- );
396
- }
397
-
398
- return value;
399
- }
400
-
401
- if (!map) {
402
- return false;
403
- }
404
- if (map === true || map === 1) {
405
- return true;
406
- }
407
- if (typeof map === "number") {
408
- return Math.random() < map;
409
- }
410
-
411
- if (typeof map === "function") {
412
- return (map as Function)(...customImplementationArgs);
413
- }
414
-
415
- if (typeof map === "string") {
416
- return map;
417
- }
418
-
419
- var asObject: { [mode: string]: number } = {};
420
- if (Array.isArray(map)) {
421
- map.forEach((x: any) => {
422
- asObject[x.toString()] = 1;
423
- });
424
- } else {
425
- asObject = map as any;
426
- }
427
-
428
- var total = Object.values(asObject).reduce((a, b) => a + b);
429
- var percentages = createObject(
430
- Object.keys(asObject),
431
- Object.values(asObject).map((x) => x / total)
432
- );
433
-
434
- var ticket = Math.random();
435
-
436
- var count = 0;
437
- var winner = null;
438
- Object.keys(percentages).forEach((key) => {
439
- var x = Number(percentages[key]);
440
-
441
- if (ticket >= count && ticket < count + x) {
442
- winner = key;
443
- }
444
- count += x;
445
- });
446
-
447
- return winner;
448
- }
449
-
450
- /**
451
- * Determines if a probability map can return a positive result (true, or some string mode).
452
- * - Negative probability maps are used to remove transformations from running entirely.
453
- * @param map
454
- */
455
- isProbabilityMapProbable<T>(map: ProbabilityMap<T>): boolean {
456
- ok(!Number.isNaN(map), "Numbers cannot be NaN");
457
-
458
- if (!map || typeof map === "undefined") {
459
- return false;
460
- }
461
- if (typeof map === "function") {
462
- return true;
463
- }
464
- if (typeof map === "number") {
465
- if (map > 1 || map < 0) {
466
- throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`);
467
- }
468
- }
469
- if (Array.isArray(map)) {
470
- ok(
471
- map.length != 0,
472
- "Empty arrays are not allowed for options. Use false instead."
473
- );
474
-
475
- if (map.length == 1) {
476
- return !!map[0];
477
- }
478
- }
479
- if (typeof map === "object") {
480
- if (map instanceof Date) return true;
481
- if (map instanceof RegExp) return true;
482
- if ("value" in map && !map.value) return false;
483
- if ("limit" in map && map.limit === 0) return false;
484
-
485
- var keys = Object.keys(map);
486
- ok(
487
- keys.length != 0,
488
- "Empty objects are not allowed for options. Use false instead."
489
- );
490
-
491
- if (keys.length == 1) {
492
- return !!map[keys[0]];
493
- }
494
- }
495
- return true;
496
- }
497
- }
1
+ import { ok } from "assert";
2
+ import * as t from "@babel/types";
3
+ import generate from "@babel/generator";
4
+ import traverse from "@babel/traverse";
5
+ import { parse } from "@babel/parser";
6
+ import { ObfuscateOptions, ProbabilityMap } from "./options";
7
+ import { applyDefaultsToOptions, validateOptions } from "./validateOptions";
8
+ import { ObfuscationResult, ProfilerCallback } from "./obfuscationResult";
9
+ import { NameGen } from "./utils/NameGen";
10
+ import { Order } from "./order";
11
+ import {
12
+ PluginFunction,
13
+ PluginInstance,
14
+ PluginObject,
15
+ } from "./transforms/plugin";
16
+ import { createObject } from "./utils/object-utils";
17
+
18
+ // Transforms
19
+ import preparation from "./transforms/preparation";
20
+ import renameVariables from "./transforms/identifier/renameVariables";
21
+ import variableMasking from "./transforms/variableMasking";
22
+ import dispatcher from "./transforms/dispatcher";
23
+ import duplicateLiteralsRemoval from "./transforms/extraction/duplicateLiteralsRemoval";
24
+ import objectExtraction from "./transforms/extraction/objectExtraction";
25
+ import globalConcealing from "./transforms/identifier/globalConcealing";
26
+ import stringCompression from "./transforms/string/stringCompression";
27
+ import deadCode from "./transforms/deadCode";
28
+ import stringSplitting from "./transforms/string/stringSplitting";
29
+ import shuffle from "./transforms/shuffle";
30
+ import astScrambler from "./transforms/astScrambler";
31
+ import calculator from "./transforms/calculator";
32
+ import movedDeclarations from "./transforms/identifier/movedDeclarations";
33
+ import renameLabels from "./transforms/renameLabels";
34
+ import rgf from "./transforms/rgf";
35
+ import flatten from "./transforms/flatten";
36
+ import stringConcealing from "./transforms/string/stringConcealing";
37
+ import lock from "./transforms/lock/lock";
38
+ import controlFlowFlattening from "./transforms/controlFlowFlattening";
39
+ import opaquePredicates from "./transforms/opaquePredicates";
40
+ import minify from "./transforms/minify";
41
+ import finalizer from "./transforms/finalizer";
42
+ import integrity from "./transforms/lock/integrity";
43
+ import pack, { PackInterface } from "./transforms/pack";
44
+
45
+ export const DEFAULT_OPTIONS: ObfuscateOptions = {
46
+ target: "node",
47
+ compact: true,
48
+ };
49
+
50
+ export default class Obfuscator {
51
+ plugins: {
52
+ plugin: PluginObject;
53
+ pluginInstance: PluginInstance;
54
+ }[] = [];
55
+ options: ObfuscateOptions;
56
+
57
+ totalPossibleTransforms: number = 0;
58
+
59
+ globalState = {
60
+ lock: {
61
+ integrity: {
62
+ sensitivityRegex: / |\n|;|,|\{|\}|\(|\)|\.|\[|\]/g,
63
+ },
64
+
65
+ createCountermeasuresCode: (): t.Statement[] => {
66
+ throw new Error("Not implemented");
67
+ },
68
+ },
69
+
70
+ // After RenameVariables completes, this map will contain the renamed variables
71
+ // Most use cases involve grabbing the Program(global) mappings
72
+ renamedVariables: new Map<t.Node, Map<string, string>>(),
73
+
74
+ // Internal functions, should not be renamed/removed
75
+ internals: {
76
+ stringCompressionLibraryName: "",
77
+ nativeFunctionName: "",
78
+ integrityHashName: "",
79
+ invokeCountermeasuresFnName: "",
80
+ },
81
+ };
82
+
83
+ // Pack Interface for sharing globals across RGF functions
84
+ packInterface: PackInterface;
85
+
86
+ isInternalVariable(name: string) {
87
+ return Object.values(this.globalState.internals).includes(name);
88
+ }
89
+
90
+ shouldTransformNativeFunction(nameAndPropertyPath: string[]) {
91
+ if (!this.options.lock?.tamperProtection) {
92
+ return false;
93
+ }
94
+
95
+ // Custom implementation for Tamper Protection
96
+ if (typeof this.options.lock.tamperProtection === "function") {
97
+ return this.options.lock.tamperProtection(nameAndPropertyPath.join("."));
98
+ }
99
+
100
+ if (
101
+ this.options.target === "browser" &&
102
+ nameAndPropertyPath.length === 1 &&
103
+ nameAndPropertyPath[0] === "fetch"
104
+ ) {
105
+ return true;
106
+ }
107
+
108
+ var globalObject = {};
109
+ try {
110
+ globalObject =
111
+ typeof globalThis !== "undefined"
112
+ ? globalThis
113
+ : // @ts-ignore
114
+ typeof window !== "undefined"
115
+ ? // @ts-ignore
116
+ window
117
+ : typeof global !== "undefined"
118
+ ? global
119
+ : // @ts-ignore
120
+ typeof self !== "undefined"
121
+ ? // @ts-ignore
122
+ self
123
+ : new Function("return this")();
124
+ } catch (e) {}
125
+
126
+ var fn = globalObject;
127
+ for (var item of nameAndPropertyPath) {
128
+ fn = fn?.[item];
129
+ if (typeof fn === "undefined") return false;
130
+ }
131
+
132
+ var hasNativeCode =
133
+ typeof fn === "function" && ("" + fn).includes("[native code]");
134
+
135
+ return hasNativeCode;
136
+ }
137
+
138
+ getStringCompressionLibraryName() {
139
+ if (this.parentObfuscator) {
140
+ return this.parentObfuscator.getStringCompressionLibraryName();
141
+ }
142
+
143
+ return this.globalState.internals.stringCompressionLibraryName;
144
+ }
145
+
146
+ getObfuscatedVariableName(originalName: string, programNode: t.Node) {
147
+ const renamedVariables = this.globalState.renamedVariables.get(programNode);
148
+
149
+ return renamedVariables?.get(originalName) || originalName;
150
+ }
151
+
152
+ /**
153
+ * The main Name Generator for `Rename Variables`
154
+ */
155
+ nameGen: NameGen;
156
+
157
+ public constructor(
158
+ userOptions: ObfuscateOptions,
159
+ public parentObfuscator?: Obfuscator,
160
+ ) {
161
+ validateOptions(userOptions);
162
+ this.options = applyDefaultsToOptions({ ...userOptions });
163
+ this.nameGen = new NameGen(this.options.identifierGenerator);
164
+
165
+ const shouldAddLockTransform =
166
+ this.options.lock &&
167
+ (Object.keys(this.options.lock).filter(
168
+ (key) =>
169
+ key !== "customLocks" &&
170
+ this.isProbabilityMapProbable(this.options.lock[key]),
171
+ ).length > 0 ||
172
+ this.options.lock.customLocks.length > 0);
173
+
174
+ const allPlugins: PluginFunction[] = [];
175
+
176
+ const push = (probabilityMap, ...pluginFns) => {
177
+ this.totalPossibleTransforms += pluginFns.length;
178
+ if (!this.isProbabilityMapProbable(probabilityMap)) return;
179
+
180
+ allPlugins.push(...pluginFns);
181
+ };
182
+
183
+ push(true, preparation);
184
+ push(this.options.objectExtraction, objectExtraction);
185
+ push(this.options.flatten, flatten);
186
+ push(shouldAddLockTransform, lock);
187
+ push(this.options.rgf, rgf);
188
+ push(this.options.dispatcher, dispatcher);
189
+ push(this.options.deadCode, deadCode);
190
+ push(this.options.controlFlowFlattening, controlFlowFlattening);
191
+ push(this.options.calculator, calculator);
192
+ push(this.options.globalConcealing, globalConcealing);
193
+ push(this.options.opaquePredicates, opaquePredicates);
194
+ push(this.options.stringSplitting, stringSplitting);
195
+ push(this.options.stringConcealing, stringConcealing);
196
+ // String Compression is only applied to the main obfuscator
197
+ // Any RGF functions will not have string compression due to the size of the decompression function
198
+
199
+ push(
200
+ !parentObfuscator && this.options.stringCompression,
201
+ stringCompression,
202
+ );
203
+ push(this.options.variableMasking, variableMasking);
204
+ push(this.options.duplicateLiteralsRemoval, duplicateLiteralsRemoval);
205
+ push(this.options.shuffle, shuffle);
206
+ push(this.options.movedDeclarations, movedDeclarations);
207
+ push(this.options.renameLabels, renameLabels);
208
+ push(this.options.minify, minify);
209
+ push(this.options.astScrambler, astScrambler);
210
+ push(this.options.renameVariables, renameVariables);
211
+
212
+ push(true, finalizer);
213
+ push(this.options.pack, pack);
214
+ push(this.options.lock?.integrity, integrity);
215
+
216
+ allPlugins.map((pluginFunction) => {
217
+ var pluginInstance: PluginInstance;
218
+ var plugin = pluginFunction({
219
+ Plugin: (order: Order, mergeObject?) => {
220
+ ok(typeof order === "number");
221
+ var pluginOptions = {
222
+ order,
223
+ name: Order[order],
224
+ };
225
+
226
+ const newPluginInstance = new PluginInstance(pluginOptions, this);
227
+ if (typeof mergeObject === "object" && mergeObject) {
228
+ Object.assign(newPluginInstance, mergeObject);
229
+ }
230
+
231
+ pluginInstance = newPluginInstance;
232
+
233
+ // @ts-ignore
234
+ return newPluginInstance as any;
235
+ },
236
+ });
237
+
238
+ ok(
239
+ pluginInstance,
240
+ "Plugin instance not created: " + pluginFunction.toString(),
241
+ );
242
+
243
+ this.plugins.push({
244
+ plugin,
245
+ pluginInstance,
246
+ });
247
+ });
248
+
249
+ this.plugins = this.plugins.sort(
250
+ (a, b) => a.pluginInstance.order - b.pluginInstance.order,
251
+ );
252
+
253
+ if (!parentObfuscator && this.hasPlugin(Order.StringCompression)) {
254
+ this.globalState.internals.stringCompressionLibraryName =
255
+ this.nameGen.generate(false);
256
+ }
257
+ }
258
+
259
+ index: number = 0;
260
+
261
+ obfuscateAST(
262
+ ast: t.File,
263
+ options?: {
264
+ profiler?: ProfilerCallback;
265
+ },
266
+ ): t.File {
267
+ let finalASTHandler: PluginObject["finalASTHandler"][] = [];
268
+
269
+ for (let i = 0; i < this.plugins.length; i++) {
270
+ this.index = i;
271
+ const { plugin, pluginInstance } = this.plugins[i];
272
+
273
+ if (this.options.verbose) {
274
+ console.log(
275
+ `Applying ${pluginInstance.name} (${i + 1}/${this.plugins.length})`,
276
+ );
277
+ }
278
+
279
+ traverse(ast, plugin.visitor);
280
+ plugin.post?.();
281
+
282
+ if (plugin.finalASTHandler) {
283
+ finalASTHandler.push(plugin.finalASTHandler);
284
+ }
285
+
286
+ if (options?.profiler) {
287
+ options?.profiler({
288
+ index: i,
289
+ currentTransform: pluginInstance.name,
290
+ nextTransform: this.plugins[i + 1]?.pluginInstance?.name,
291
+ totalTransforms: this.plugins.length,
292
+ });
293
+ }
294
+ }
295
+
296
+ for (const handler of finalASTHandler) {
297
+ ast = handler(ast);
298
+ }
299
+
300
+ return ast;
301
+ }
302
+
303
+ async obfuscate(sourceCode: string): Promise<ObfuscationResult> {
304
+ // Parse the source code into an AST
305
+ let ast = Obfuscator.parseCode(sourceCode);
306
+
307
+ ast = this.obfuscateAST(ast);
308
+
309
+ // Generate the transformed code from the modified AST with comments removed and compacted output
310
+ const code = this.generateCode(ast);
311
+
312
+ return {
313
+ code: code,
314
+ };
315
+ }
316
+
317
+ getPlugin(order: Order) {
318
+ return this.plugins.find((x) => x.pluginInstance.order === order);
319
+ }
320
+
321
+ hasPlugin(order: Order) {
322
+ return !!this.getPlugin(order);
323
+ }
324
+
325
+ /**
326
+ * Calls `Obfuscator.generateCode` with the current instance options
327
+ */
328
+ generateCode<T extends t.Node = t.File>(ast: T): string {
329
+ return Obfuscator.generateCode(ast, this.options);
330
+ }
331
+
332
+ /**
333
+ * Generates code from an AST using `@babel/generator`
334
+ */
335
+ static generateCode<T extends t.Node = t.File>(
336
+ ast: T,
337
+ options: ObfuscateOptions = DEFAULT_OPTIONS,
338
+ ): string {
339
+ const compact = !!options.compact;
340
+
341
+ const { code } = generate(ast, {
342
+ comments: false, // Remove comments
343
+ minified: compact,
344
+ // jsescOption: {
345
+ // String Encoding using Babel
346
+ // escapeEverything: true,
347
+ // },
348
+ });
349
+
350
+ return code;
351
+ }
352
+
353
+ /**
354
+ * Parses the source code into an AST using `t.parseSync`
355
+ */
356
+ static parseCode(sourceCode: string): t.File {
357
+ // Parse the source code into an AST
358
+ let ast = parse(sourceCode, {
359
+ sourceType: "unambiguous",
360
+ });
361
+
362
+ return ast;
363
+ }
364
+
365
+ probabilityMapCounter = new WeakMap<Object, number>();
366
+
367
+ /**
368
+ * Evaluates a ProbabilityMap.
369
+ * @param map The setting object.
370
+ * @param customFnArgs Args given to user-implemented function, such as a variable name.
371
+ */
372
+ computeProbabilityMap<
373
+ T,
374
+ F extends (...args: any[]) => any = (...args: any[]) => any,
375
+ >(
376
+ map: ProbabilityMap<T, F>,
377
+ ...customImplementationArgs: F extends (...args: infer P) => any ? P : never
378
+ ): boolean | string {
379
+ // Check if this probability map uses the {value: ..., limit: ...} format
380
+ if (typeof map === "object" && map && "value" in map) {
381
+ // Check for the limit property
382
+ if ("limit" in map && typeof map.limit === "number" && map.limit >= 0) {
383
+ // Check if the limit has been reached
384
+ if (this.probabilityMapCounter.get(map) >= map.limit) {
385
+ return false;
386
+ }
387
+ }
388
+
389
+ var value = this.computeProbabilityMap(
390
+ map.value as ProbabilityMap<T, F>,
391
+ ...customImplementationArgs,
392
+ );
393
+
394
+ if (value) {
395
+ // Increment the counter for this map
396
+ this.probabilityMapCounter.set(
397
+ map,
398
+ this.probabilityMapCounter.get(map) + 1 || 1,
399
+ );
400
+ }
401
+
402
+ return value;
403
+ }
404
+
405
+ if (!map) {
406
+ return false;
407
+ }
408
+ if (map === true || map === 1) {
409
+ return true;
410
+ }
411
+ if (typeof map === "number") {
412
+ return Math.random() < map;
413
+ }
414
+
415
+ if (typeof map === "function") {
416
+ return (map as Function)(...customImplementationArgs);
417
+ }
418
+
419
+ if (typeof map === "string") {
420
+ return map;
421
+ }
422
+
423
+ var asObject: { [mode: string]: number } = {};
424
+ if (Array.isArray(map)) {
425
+ map.forEach((x: any) => {
426
+ asObject[x.toString()] = 1;
427
+ });
428
+ } else {
429
+ asObject = map as any;
430
+ }
431
+
432
+ var total = Object.values(asObject).reduce((a, b) => a + b);
433
+ var percentages = createObject(
434
+ Object.keys(asObject),
435
+ Object.values(asObject).map((x) => x / total),
436
+ );
437
+
438
+ var ticket = Math.random();
439
+
440
+ var count = 0;
441
+ var winner = null;
442
+ Object.keys(percentages).forEach((key) => {
443
+ var x = Number(percentages[key]);
444
+
445
+ if (ticket >= count && ticket < count + x) {
446
+ winner = key;
447
+ }
448
+ count += x;
449
+ });
450
+
451
+ return winner;
452
+ }
453
+
454
+ /**
455
+ * Determines if a probability map can return a positive result (true, or some string mode).
456
+ * - Negative probability maps are used to remove transformations from running entirely.
457
+ * @param map
458
+ */
459
+ isProbabilityMapProbable<T>(map: ProbabilityMap<T>): boolean {
460
+ ok(!Number.isNaN(map), "Numbers cannot be NaN");
461
+
462
+ if (!map || typeof map === "undefined") {
463
+ return false;
464
+ }
465
+ if (typeof map === "function") {
466
+ return true;
467
+ }
468
+ if (typeof map === "number") {
469
+ if (map > 1 || map < 0) {
470
+ throw new Error(`Numbers must be between 0 and 1 for 0% - 100%`);
471
+ }
472
+ }
473
+ if (Array.isArray(map)) {
474
+ ok(
475
+ map.length != 0,
476
+ "Empty arrays are not allowed for options. Use false instead.",
477
+ );
478
+
479
+ if (map.length == 1) {
480
+ return !!map[0];
481
+ }
482
+ }
483
+ if (typeof map === "object") {
484
+ if (map instanceof Date) return true;
485
+ if (map instanceof RegExp) return true;
486
+ if ("value" in map && !map.value) return false;
487
+ if ("limit" in map && map.limit === 0) return false;
488
+
489
+ var keys = Object.keys(map);
490
+ ok(
491
+ keys.length != 0,
492
+ "Empty objects are not allowed for options. Use false instead.",
493
+ );
494
+
495
+ if (keys.length == 1) {
496
+ return !!map[keys[0]];
497
+ }
498
+ }
499
+ return true;
500
+ }
501
+ }