js-confuser 1.6.0 → 1.7.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +215 -170
  3. package/dist/constants.js +6 -2
  4. package/dist/obfuscator.js +0 -6
  5. package/dist/options.js +4 -4
  6. package/dist/presets.js +6 -7
  7. package/dist/templates/crash.js +2 -2
  8. package/dist/templates/functionLength.js +16 -0
  9. package/dist/transforms/dispatcher.js +4 -1
  10. package/dist/transforms/extraction/duplicateLiteralsRemoval.js +89 -58
  11. package/dist/transforms/flatten.js +224 -147
  12. package/dist/transforms/identifier/movedDeclarations.js +38 -85
  13. package/dist/transforms/identifier/renameVariables.js +94 -41
  14. package/dist/transforms/lock/lock.js +0 -37
  15. package/dist/transforms/minify.js +2 -2
  16. package/dist/transforms/rgf.js +139 -246
  17. package/dist/transforms/stack.js +42 -1
  18. package/dist/transforms/transform.js +1 -1
  19. package/dist/util/gen.js +2 -1
  20. package/dist/util/identifiers.js +37 -3
  21. package/dist/util/insert.js +24 -3
  22. package/docs/ControlFlowFlattening.md +595 -0
  23. package/{Countermeasures.md → docs/Countermeasures.md} +1 -15
  24. package/{Integrity.md → docs/Integrity.md} +2 -2
  25. package/docs/RGF.md +419 -0
  26. package/package.json +1 -1
  27. package/src/constants.ts +3 -0
  28. package/src/obfuscator.ts +0 -4
  29. package/src/options.ts +9 -86
  30. package/src/presets.ts +6 -7
  31. package/src/templates/crash.ts +10 -10
  32. package/src/templates/functionLength.ts +14 -0
  33. package/src/transforms/dispatcher.ts +5 -1
  34. package/src/transforms/extraction/duplicateLiteralsRemoval.ts +130 -129
  35. package/src/transforms/flatten.ts +357 -290
  36. package/src/transforms/identifier/movedDeclarations.ts +50 -96
  37. package/src/transforms/identifier/renameVariables.ts +120 -56
  38. package/src/transforms/lock/lock.ts +1 -42
  39. package/src/transforms/minify.ts +11 -2
  40. package/src/transforms/rgf.ts +214 -404
  41. package/src/transforms/stack.ts +62 -0
  42. package/src/transforms/transform.ts +6 -2
  43. package/src/util/gen.ts +7 -2
  44. package/src/util/identifiers.ts +43 -2
  45. package/src/util/insert.ts +26 -2
  46. package/test/code/ES6.src.js +24 -0
  47. package/test/transforms/flatten.test.ts +352 -88
  48. package/test/transforms/identifier/movedDeclarations.test.ts +37 -9
  49. package/test/transforms/identifier/renameVariables.test.ts +37 -0
  50. package/test/transforms/lock/lock.test.ts +1 -48
  51. package/test/transforms/minify.test.ts +19 -0
  52. package/test/transforms/rgf.test.ts +262 -353
  53. package/test/transforms/stack.test.ts +52 -0
  54. package/test/util/identifiers.test.ts +113 -1
  55. package/test/util/insert.test.ts +57 -3
  56. package/src/transforms/eval.ts +0 -89
  57. package/src/transforms/identifier/nameRecycling.ts +0 -280
  58. package/test/transforms/eval.test.ts +0 -131
  59. package/test/transforms/identifier/nameRecycling.test.ts +0 -205
@@ -1,19 +1,15 @@
1
1
  import Transform from "../transform";
2
- import { isBlock, walk } from "../../traverse";
2
+ import { isBlock } from "../../traverse";
3
3
  import {
4
- Location,
5
4
  ExpressionStatement,
6
5
  AssignmentExpression,
7
6
  Identifier,
8
7
  Node,
9
8
  VariableDeclarator,
10
- VariableDeclaration,
11
9
  } from "../../util/gen";
12
10
  import { isForInitialize, prepend } from "../../util/insert";
13
11
  import { ok } from "assert";
14
12
  import { ObfuscateOrder } from "../../order";
15
- import { getIdentifierInfo } from "../../util/identifiers";
16
- import { isLexicalScope, getLexicalScope } from "../../util/scope";
17
13
 
18
14
  /**
19
15
  * Defines all the names at the top of every lexical block.
@@ -24,110 +20,68 @@ export default class MovedDeclarations extends Transform {
24
20
  }
25
21
 
26
22
  match(object, parents) {
27
- return isLexicalScope(object);
23
+ return (
24
+ object.type === "VariableDeclaration" &&
25
+ object.kind === "var" &&
26
+ object.declarations.length === 1 &&
27
+ object.declarations[0].id.type === "Identifier"
28
+ );
28
29
  }
29
30
 
30
31
  transform(object: Node, parents: Node[]) {
31
32
  return () => {
32
- var body = isBlock(object) ? object.body : object.consequent;
33
- ok(Array.isArray(body));
34
-
35
- var illegal = new Set<string>();
36
- var defined = new Set<string>();
37
- var variableDeclarations: {
38
- [name: string]: {
39
- location: Location;
40
- replace: Node;
33
+ var forInitializeType = isForInitialize(object, parents);
34
+
35
+ // Get the block statement or Program node
36
+ var blockIndex = parents.findIndex((x) => isBlock(x));
37
+ var block = parents[blockIndex];
38
+ var body = block.body;
39
+ var bodyObject = parents[blockIndex - 2] || object;
40
+
41
+ // Make sure in the block statement, and not already at the top of it
42
+ var index = body.indexOf(bodyObject);
43
+ if (index === -1 || index === 0) return;
44
+
45
+ var topVariableDeclaration;
46
+ if (body[0].type === "VariableDeclaration" && body[0].kind === "var") {
47
+ topVariableDeclaration = body[0];
48
+ } else {
49
+ topVariableDeclaration = {
50
+ type: "VariableDeclaration",
51
+ declarations: [],
52
+ kind: "var",
41
53
  };
42
- } = Object.create(null);
43
-
44
- walk(object, parents, (o, p) => {
45
- if (o.type == "Identifier") {
46
- if (o.hidden || getLexicalScope(o, p) !== object) {
47
- illegal.add(o.name);
48
- } else {
49
- var info = getIdentifierInfo(o, p);
50
- if (!info.spec.isReferenced) {
51
- return;
52
- }
53
54
 
54
- if (info.spec.isDefined) {
55
- if (info.isFunctionDeclaration || info.isClassDeclaration) {
56
- illegal.add(o.name);
57
- } else {
58
- if (defined.has(o.name)) {
59
- illegal.add(o.name);
60
- } else {
61
- defined.add(o.name);
62
- }
63
- }
64
- }
65
- }
66
- }
55
+ prepend(block, topVariableDeclaration);
56
+ }
67
57
 
68
- if (o.type == "VariableDeclaration") {
69
- return () => {
70
- if (
71
- o.declarations.length === 1 &&
72
- o.declarations[0].id.type === "Identifier"
73
- ) {
74
- var name = o.declarations[0].id.name;
58
+ var varName = object.declarations[0].id.name;
59
+ ok(typeof varName === "string");
75
60
 
76
- // Check if duplicate
77
- if (variableDeclarations[name] || o.kind !== "var") {
78
- illegal.add(name);
79
- return;
80
- }
61
+ // Add `var x` at the top of the block
62
+ topVariableDeclaration.declarations.push(
63
+ VariableDeclarator(Identifier(varName))
64
+ );
81
65
 
82
- // Check if already at top
83
- if (body[0] === o) {
84
- illegal.add(name);
85
- return;
86
- }
66
+ var assignmentExpression = AssignmentExpression(
67
+ "=",
68
+ Identifier(varName),
69
+ object.declarations[0].init || Identifier(varName)
70
+ );
87
71
 
88
- var replace: Node = AssignmentExpression(
89
- "=",
90
- Identifier(name),
91
- o.declarations[0].init || Identifier("undefined")
92
- );
72
+ if (forInitializeType) {
73
+ if (forInitializeType === "initializer") {
74
+ // Replace `for (var i = 0...)` to `for (i = 0...)`
75
+ this.replace(object, assignmentExpression);
76
+ } else if (forInitializeType === "left-hand") {
77
+ // Replace `for (var k in...)` to `for (k in ...)`
93
78
 
94
- var forType = isForInitialize(o, p);
95
- if (forType === "left-hand") {
96
- replace = Identifier(name);
97
- } else if (!forType) {
98
- replace = ExpressionStatement(replace);
99
- }
100
- variableDeclarations[name] = {
101
- location: [o, p],
102
- replace: replace,
103
- };
104
- }
105
- };
79
+ this.replace(object, Identifier(varName));
106
80
  }
107
- });
108
-
109
- illegal.forEach((name) => {
110
- delete variableDeclarations[name];
111
- });
112
-
113
- var movingNames = Object.keys(variableDeclarations);
114
-
115
- if (movingNames.length === 0) {
116
- return;
81
+ } else {
82
+ // Replace `var x = value` to `x = value`
83
+ this.replace(object, ExpressionStatement(assignmentExpression));
117
84
  }
118
-
119
- var variableDeclaration = VariableDeclaration(
120
- movingNames.map((name) => {
121
- return VariableDeclarator(name);
122
- })
123
- );
124
-
125
- prepend(object, variableDeclaration);
126
-
127
- movingNames.forEach((name) => {
128
- var { location, replace } = variableDeclarations[name];
129
- this.replace(location[0], replace);
130
- });
131
85
  };
132
86
  }
133
87
  }
@@ -8,9 +8,14 @@ import {
8
8
  isContext,
9
9
  isLexContext,
10
10
  clone,
11
+ isFunction,
11
12
  } from "../../util/insert";
12
13
  import Transform from "../transform";
13
- import { reservedIdentifiers } from "../../constants";
14
+ import {
15
+ noRenameVariablePrefix,
16
+ placeholderVariablePrefix,
17
+ reservedIdentifiers,
18
+ } from "../../constants";
14
19
  import { ComputeProbabilityMap } from "../../probability";
15
20
  import VariableAnalysis from "./variableAnalysis";
16
21
 
@@ -33,6 +38,9 @@ export default class RenameVariables extends Transform {
33
38
  // Ref to VariableAnalysis data
34
39
  variableAnalysis: VariableAnalysis;
35
40
 
41
+ // Option to re-use previously generated names
42
+ reusePreviousNames = true;
43
+
36
44
  constructor(o) {
37
45
  super(o, ObfuscateOrder.RenameVariables);
38
46
 
@@ -45,10 +53,10 @@ export default class RenameVariables extends Transform {
45
53
  }
46
54
 
47
55
  match(object: Node, parents: Node[]) {
48
- return isContext(object);
56
+ return isContext(object) || object.type === "Identifier";
49
57
  }
50
58
 
51
- transform(object: Node, parents: Node[]) {
59
+ transformContext(object: Node, parents: Node[]) {
52
60
  // 2. Notice this is on 'onEnter' (top-down)
53
61
  var isGlobal = object.type == "Program";
54
62
  var type = isGlobal
@@ -76,7 +84,7 @@ export default class RenameVariables extends Transform {
76
84
  var possible = new Set<string>();
77
85
 
78
86
  // 3. Try to re-use names when possible
79
- if (this.generated.length && !isGlobal) {
87
+ if (this.reusePreviousNames && this.generated.length && !isGlobal) {
80
88
  var allReferences = new Set<string>();
81
89
  var nope = new Set(defined);
82
90
  walk(object, [], (o, p) => {
@@ -115,8 +123,8 @@ export default class RenameVariables extends Transform {
115
123
  // 4. Defined names to new names
116
124
  for (var name of defined) {
117
125
  if (
118
- !name.startsWith("__NO_JS_CONFUSER_RENAME__") && // Variables prefixed with '__NO_JS_CONFUSER_RENAME__' are never renamed
119
- (isGlobal && !name.startsWith("__p_") // Variables prefixed with '__p_' are created by the obfuscator, always renamed
126
+ !name.startsWith(noRenameVariablePrefix) && // Variables prefixed with '__NO_JS_CONFUSER_RENAME__' are never renamed
127
+ (isGlobal && !name.startsWith(placeholderVariablePrefix) // Variables prefixed with '__p_' are created by the obfuscator, always renamed
120
128
  ? ComputeProbabilityMap(this.options.renameGlobals, (x) => x, name)
121
129
  : true) &&
122
130
  ComputeProbabilityMap(
@@ -151,71 +159,127 @@ export default class RenameVariables extends Transform {
151
159
  }
152
160
  }
153
161
 
162
+ // console.log(object.type, newNames);
154
163
  this.changed.set(object, newNames);
164
+ }
155
165
 
156
- // 5. Update Identifier node's 'name' property
157
- walk(object, parents, (o, p) => {
158
- if (o.type == "Identifier") {
159
- if (
160
- reservedIdentifiers.has(o.name) ||
161
- this.options.globalVariables.has(o.name)
162
- ) {
163
- return;
164
- }
166
+ transformIdentifier(object: Node, parents: Node[]) {
167
+ const identifierName = object.name;
168
+ if (
169
+ reservedIdentifiers.has(identifierName) ||
170
+ this.options.globalVariables.has(identifierName)
171
+ ) {
172
+ return;
173
+ }
165
174
 
166
- if (o.$renamed) {
167
- return;
168
- }
175
+ if (object.$renamed) {
176
+ return;
177
+ }
169
178
 
170
- var info = getIdentifierInfo(o, p);
179
+ var info = getIdentifierInfo(object, parents);
171
180
 
172
- if (info.spec.isExported) {
173
- return;
174
- }
181
+ if (info.spec.isExported) {
182
+ return;
183
+ }
175
184
 
176
- if (!info.spec.isReferenced) {
177
- return;
178
- }
185
+ if (!info.spec.isReferenced) {
186
+ return;
187
+ }
188
+
189
+ var contexts = [object, ...parents].filter((x) => isContext(x));
190
+ var newName = null;
191
+
192
+ // Function default parameter check!
193
+ var functionIndices = [];
194
+ for (var i in parents) {
195
+ if (isFunction(parents[i])) {
196
+ functionIndices.push(i);
197
+ }
198
+ }
199
+
200
+ for (var functionIndex of functionIndices) {
201
+ if (parents[functionIndex].id === object) {
202
+ // This context is not referenced, so remove it
203
+ contexts = contexts.filter(
204
+ (context) => context != parents[functionIndex]
205
+ );
206
+ continue;
207
+ }
208
+ if (parents[functionIndex].params === parents[functionIndex - 1]) {
209
+ var isReferencedHere = true;
179
210
 
180
- var contexts = [o, ...p].filter((x) => isContext(x));
181
- var newName = null;
211
+ var slicedParents = parents.slice(0, functionIndex);
212
+ var forIndex = 0;
213
+ for (var parent of slicedParents) {
214
+ var childNode = slicedParents[forIndex - 1] || object;
182
215
 
183
- for (var check of contexts) {
184
216
  if (
185
- this.variableAnalysis.defined.has(check) &&
186
- this.variableAnalysis.defined.get(check).has(o.name)
217
+ parent.type === "AssignmentPattern" &&
218
+ parent.right === childNode
187
219
  ) {
188
- if (this.changed.has(check) && this.changed.get(check)[o.name]) {
189
- newName = this.changed.get(check)[o.name];
190
- break;
191
- }
220
+ isReferencedHere = false;
221
+ break;
192
222
  }
223
+
224
+ forIndex++;
193
225
  }
194
226
 
195
- if (newName && typeof newName === "string") {
196
- // Strange behavior where the `local` and `imported` objects are the same
197
- if (info.isImportSpecifier) {
198
- var importSpecifierIndex = p.findIndex(
199
- (x) => x.type === "ImportSpecifier"
200
- );
201
- if (
202
- importSpecifierIndex != -1 &&
203
- p[importSpecifierIndex].imported ===
204
- (p[importSpecifierIndex - 1] || o) &&
205
- p[importSpecifierIndex].imported &&
206
- p[importSpecifierIndex].imported.type === "Identifier"
207
- ) {
208
- p[importSpecifierIndex].imported = clone(
209
- p[importSpecifierIndex - 1] || o
210
- );
211
- }
212
- }
227
+ if (!isReferencedHere) {
228
+ // This context is not referenced, so remove it
229
+ contexts = contexts.filter(
230
+ (context) => context != parents[functionIndex]
231
+ );
232
+ }
233
+ }
234
+ }
213
235
 
214
- // console.log(o.name, "->", newName);
215
- o.name = newName;
216
- o.$renamed = true;
236
+ for (var check of contexts) {
237
+ if (
238
+ this.variableAnalysis.defined.has(check) &&
239
+ this.variableAnalysis.defined.get(check).has(identifierName)
240
+ ) {
241
+ if (
242
+ this.changed.has(check) &&
243
+ this.changed.get(check)[identifierName]
244
+ ) {
245
+ newName = this.changed.get(check)[identifierName];
246
+ break;
217
247
  }
218
248
  }
219
- });
249
+ }
250
+
251
+ if (newName && typeof newName === "string") {
252
+ // Strange behavior where the `local` and `imported` objects are the same
253
+ if (info.isImportSpecifier) {
254
+ var importSpecifierIndex = parents.findIndex(
255
+ (x) => x.type === "ImportSpecifier"
256
+ );
257
+ if (
258
+ importSpecifierIndex != -1 &&
259
+ parents[importSpecifierIndex].imported ===
260
+ (parents[importSpecifierIndex - 1] || object) &&
261
+ parents[importSpecifierIndex].imported &&
262
+ parents[importSpecifierIndex].imported.type === "Identifier"
263
+ ) {
264
+ parents[importSpecifierIndex].imported = clone(
265
+ parents[importSpecifierIndex - 1] || object
266
+ );
267
+ }
268
+ }
269
+
270
+ // console.log(o.name, "->", newName);
271
+ // 5. Update Identifier node's 'name' property
272
+ object.name = newName;
273
+ object.$renamed = true;
274
+ }
275
+ }
276
+
277
+ transform(object: Node, parents: Node[]) {
278
+ var matchType = object.type === "Identifier" ? "Identifier" : "Context";
279
+ if (matchType === "Identifier") {
280
+ this.transformIdentifier(object, parents);
281
+ } else {
282
+ this.transformContext(object, parents);
283
+ }
220
284
  }
221
285
  }
@@ -219,9 +219,7 @@ export default class Lock extends Transform {
219
219
  if (this.options.lock.domainLock && this.options.lock.domainLock.length) {
220
220
  choices.push("domainLock");
221
221
  }
222
- if (this.options.lock.nativeFunctions) {
223
- choices.push("nativeFunction");
224
- }
222
+
225
223
  if (this.options.lock.context && this.options.lock.context.length) {
226
224
  choices.push("context");
227
225
  }
@@ -312,45 +310,6 @@ export default class Lock extends Transform {
312
310
 
313
311
  break;
314
312
 
315
- case "nativeFunction":
316
- var set = this.options.lock.nativeFunctions;
317
- if (set === true) {
318
- if (this.options.target == "node") {
319
- set = new Set(["Function", "String"]);
320
- } else {
321
- set = new Set(["Function", "String", "fetch"]);
322
- }
323
- }
324
- if (Array.isArray(set)) {
325
- set = new Set(set);
326
- }
327
- if (!set) {
328
- set = new Set();
329
- }
330
-
331
- var fn = choice(Array.from(set));
332
- if (fn) {
333
- test = Template(
334
- `(${fn}+"").indexOf("[native code]") == -1`
335
- ).single().expression;
336
-
337
- if (Math.random() > 0.5) {
338
- test = Template(
339
- `${fn}.toString().split("{ [native code] }").length <= 1`
340
- ).single().expression;
341
- }
342
-
343
- nodes.push(
344
- IfStatement(
345
- test,
346
- this.getCounterMeasuresCode(object, parents) || [],
347
- null
348
- )
349
- );
350
- }
351
-
352
- break;
353
-
354
313
  case "startDate":
355
314
  test = BinaryExpression(
356
315
  "<",
@@ -22,6 +22,7 @@ import {
22
22
  isForInitialize,
23
23
  append,
24
24
  isVarContext,
25
+ computeFunctionLength,
25
26
  } from "../util/insert";
26
27
  import { isValidIdentifier, isEquivalent } from "../util/compare";
27
28
  import { walk, isBlock } from "../traverse";
@@ -258,8 +259,15 @@ export default class Minify extends Transform {
258
259
  append(
259
260
  parents[parents.length - 1] || object,
260
261
  Template(`
261
- function ${this.arrowFunctionName}(arrowFn){
262
- return function(){ return arrowFn(...arguments) }
262
+ function ${this.arrowFunctionName}(arrowFn, functionLength){
263
+ var functionObject = function(){ return arrowFn(...arguments) };
264
+
265
+ Object["defineProperty"](functionObject, "length", {
266
+ "value": functionLength,
267
+ "configurable": true
268
+ });
269
+
270
+ return functionObject;
263
271
  }
264
272
  `).single()
265
273
  );
@@ -268,6 +276,7 @@ export default class Minify extends Transform {
268
276
  const wrap = (object: Node) => {
269
277
  return CallExpression(Identifier(this.arrowFunctionName), [
270
278
  clone(object),
279
+ Literal(computeFunctionLength(object.params)),
271
280
  ]);
272
281
  };
273
282