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
@@ -3,7 +3,7 @@ import Template from "./template";
3
3
  export const CrashTemplate1 = Template(`
4
4
  var {var} = "a";
5
5
  while(1){
6
- {var} = {var} += "a"; //add as much as the browser can handle
6
+ {var} = {var} += "a";
7
7
  }
8
8
  `);
9
9
 
@@ -40,16 +40,16 @@ try {
40
40
  });
41
41
 
42
42
  {$1}();
43
- } catch ( e ) {
44
- while(e ? e : !e){
45
- var b;
46
- var c = 0;
47
- (e ? !e : e) ? (function(e){
48
- c = e ? 0 : !e ? 1 : 0;
49
- })(e) : b = 1;
43
+ } catch ( {$1}e ) {
44
+ while({$1}e ? {$1}e : !{$1}e){
45
+ var {$1}b;
46
+ var {$1}c = 0;
47
+ ({$1}e ? !{$1}e : {$1}e) ? (function({$1}e){
48
+ {$1}c = {$1}e ? 0 : !{$1}e ? 1 : 0;
49
+ })({$1}e) : {$1}b = 1;
50
50
 
51
- if(b&&c){break;}
52
- if(b){continue;}
51
+ if({$1}b&&{$1}c){break;}
52
+ if({$1}b){continue;}
53
53
  }
54
54
  }
55
55
  `);
@@ -0,0 +1,14 @@
1
+ import Template from "./template";
2
+
3
+ /**
4
+ * Helper function to set `function.length` property.
5
+ */
6
+ export const FunctionLengthTemplate = Template(`
7
+ function {name}(functionObject, functionLength){
8
+ Object["defineProperty"](functionObject, "length", {
9
+ "value": functionLength,
10
+ "configurable": true
11
+ });
12
+ return functionObject;
13
+ }
14
+ `);
@@ -68,6 +68,8 @@ import Template from "../templates/template";
68
68
  * 3. using `this`
69
69
  */
70
70
  export default class Dispatcher extends Transform {
71
+ // Debug mode preserves function names
72
+ isDebug = false;
71
73
  count: number;
72
74
 
73
75
  constructor(o) {
@@ -196,7 +198,9 @@ export default class Dispatcher extends Transform {
196
198
  // map original name->new game
197
199
  var gen = this.getGenerator();
198
200
  Object.keys(functionDeclarations).forEach((name) => {
199
- newFnNames[name] = gen.generate();
201
+ newFnNames[name] = this.isDebug
202
+ ? "_dispatcher_" + this.count + "_" + name
203
+ : gen.generate();
200
204
  });
201
205
  // set containing new name
202
206
  var set = new Set(Object.keys(newFnNames));
@@ -13,15 +13,9 @@ import {
13
13
  BinaryExpression,
14
14
  FunctionDeclaration,
15
15
  ThisExpression,
16
- FunctionExpression,
16
+ ConditionalExpression,
17
17
  } from "../../util/gen";
18
- import {
19
- append,
20
- clone,
21
- getLexContext,
22
- isLexContext,
23
- prepend,
24
- } from "../../util/insert";
18
+ import { append, clone, prepend } from "../../util/insert";
25
19
  import { isDirective, isPrimitive } from "../../util/compare";
26
20
 
27
21
  import { ObfuscateOrder } from "../../order";
@@ -29,6 +23,7 @@ import { isModuleSource } from "../string/stringConcealing";
29
23
  import { ComputeProbabilityMap } from "../../probability";
30
24
  import { ok } from "assert";
31
25
  import { chance, choice, getRandomInteger } from "../../util/random";
26
+ import { getBlock } from "../../traverse";
32
27
 
33
28
  /**
34
29
  * [Duplicate Literals Removal](https://docs.jscrambler.com/code-integrity/documentation/transformations/duplicate-literals-removal) replaces duplicate literals with a variable name.
@@ -49,20 +44,25 @@ import { chance, choice, getRandomInteger } from "../../util/random";
49
44
  * ```
50
45
  */
51
46
  export default class DuplicateLiteralsRemoval extends Transform {
47
+ // The array holding all the duplicate literals
52
48
  arrayName: string;
49
+ // The array expression node to be inserted into the program
53
50
  arrayExpression: Node;
51
+
52
+ /**
53
+ * Literals in the array
54
+ */
54
55
  map: Map<string, number>;
55
- first: Map<string, Location | null>;
56
56
 
57
57
  /**
58
- * getter fn name -> accumulative shift
58
+ * Literals are saved here the first time they are seen.
59
59
  */
60
- fnShifts: Map<string, number>;
60
+ first: Map<string, Location>;
61
61
 
62
62
  /**
63
- * lex context -> getter fn name
63
+ * Block -> { functionName, indexShift }
64
64
  */
65
- fnGetters: Map<Node, string>;
65
+ functions: Map<Node, { functionName: string; indexShift: number }>;
66
66
 
67
67
  constructor(o) {
68
68
  super(o, ObfuscateOrder.DuplicateLiteralsRemoval);
@@ -70,14 +70,14 @@ export default class DuplicateLiteralsRemoval extends Transform {
70
70
  this.map = new Map();
71
71
  this.first = new Map();
72
72
 
73
- this.fnShifts = new Map();
74
- this.fnGetters = new Map();
73
+ this.functions = new Map();
75
74
  }
76
75
 
77
76
  apply(tree) {
78
77
  super.apply(tree);
79
78
 
80
- if (this.arrayName && this.arrayExpression.elements.length) {
79
+ if (this.arrayName && this.arrayExpression.elements.length > 0) {
80
+ // This function simply returns the array
81
81
  var getArrayFn = this.getPlaceholder();
82
82
  append(
83
83
  tree,
@@ -88,22 +88,79 @@ export default class DuplicateLiteralsRemoval extends Transform {
88
88
  )
89
89
  );
90
90
 
91
+ // This variable holds the array
91
92
  prepend(
92
93
  tree,
93
94
  VariableDeclaration(
94
95
  VariableDeclarator(
95
96
  this.arrayName,
96
97
  CallExpression(
97
- MemberExpression(
98
- Identifier(getArrayFn),
99
- Identifier("call"),
100
- false
101
- ),
98
+ MemberExpression(Identifier(getArrayFn), Literal("call"), true),
102
99
  [ThisExpression()]
103
100
  )
104
101
  )
105
102
  )
106
103
  );
104
+
105
+ // Create all the functions needed
106
+ for (var blockNode of this.functions.keys()) {
107
+ var { functionName, indexShift } = this.functions.get(blockNode);
108
+
109
+ var propertyNode: Node = BinaryExpression(
110
+ "-",
111
+ Identifier("index_param"),
112
+ Literal(indexShift)
113
+ );
114
+
115
+ var indexRangeInclusive = [
116
+ 0 + indexShift - 1,
117
+ this.map.size + indexShift,
118
+ ];
119
+
120
+ // The function uses mangling to hide the index being accessed
121
+ var mangleCount = getRandomInteger(1, 10);
122
+ for (var i = 0; i < mangleCount; i++) {
123
+ var operator = choice([">", "<"]);
124
+ var compareValue = choice(indexRangeInclusive);
125
+
126
+ var test = BinaryExpression(
127
+ operator,
128
+ Identifier("index_param"),
129
+ Literal(compareValue)
130
+ );
131
+
132
+ var alternate = BinaryExpression(
133
+ "-",
134
+ Identifier("index_param"),
135
+ Literal(getRandomInteger(-100, 100))
136
+ );
137
+
138
+ var testValue =
139
+ (operator === ">" && compareValue === indexRangeInclusive[0]) ||
140
+ (operator === "<" && compareValue === indexRangeInclusive[1]);
141
+
142
+ propertyNode = ConditionalExpression(
143
+ test,
144
+ testValue ? propertyNode : alternate,
145
+ !testValue ? propertyNode : alternate
146
+ );
147
+ }
148
+
149
+ var returnArgument = MemberExpression(
150
+ Identifier(this.arrayName),
151
+ propertyNode,
152
+ true
153
+ );
154
+
155
+ prepend(
156
+ blockNode,
157
+ FunctionDeclaration(
158
+ functionName,
159
+ [Identifier("index_param")],
160
+ [ReturnStatement(returnArgument)]
161
+ )
162
+ );
163
+ }
107
164
  }
108
165
  }
109
166
 
@@ -122,104 +179,44 @@ export default class DuplicateLiteralsRemoval extends Transform {
122
179
  * @param parents
123
180
  * @param index
124
181
  */
125
- toCaller(object: Node, parents: Node[], index: number) {
126
- // get all the getters defined here or higher
127
- var getterNames = [object, ...parents]
128
- .map((x) => this.fnGetters.get(x))
129
- .filter((x) => x);
130
-
131
- // use random getter function
132
- var getterName = choice(getterNames);
133
-
134
- // get this literals context
135
- var lexContext = getLexContext(object, parents);
136
-
137
- var hasGetterHere = this.fnGetters.has(lexContext);
138
-
139
- // create one if none are available (or by random chance if none are here locally)
140
- var shouldCreateNew =
141
- !getterName || (!hasGetterHere && Math.random() > 0.9);
142
-
143
- if (shouldCreateNew) {
144
- ok(!this.fnGetters.has(lexContext));
145
-
146
- var lexContextIndex = parents.findIndex(
147
- (x) => x !== lexContext && isLexContext(x)
148
- );
149
- var basedOn =
150
- lexContextIndex !== -1
151
- ? choice(
152
- parents
153
- .slice(lexContextIndex + 1)
154
- .map((x) => this.fnGetters.get(x))
155
- .filter((x) => x)
156
- )
157
- : null;
158
-
159
- var body = [];
160
- var thisShift = getRandomInteger(-250, 250);
161
- // the name of the getter
162
- getterName = this.getPlaceholder() + "_dLR_" + this.fnGetters.size;
163
-
164
- if (basedOn) {
165
- var shift = this.fnShifts.get(basedOn);
166
- ok(typeof shift === "number");
167
-
168
- body = [
169
- ReturnStatement(
170
- CallExpression(Identifier(basedOn), [
171
- BinaryExpression("+", Identifier("index"), Literal(thisShift)),
172
- ])
173
- ),
174
- ];
175
-
176
- this.fnShifts.set(getterName, shift + thisShift);
177
- } else {
178
- // from scratch
179
-
180
- body = [
181
- ReturnStatement(
182
- MemberExpression(
183
- Identifier(this.arrayName),
184
- BinaryExpression("+", Identifier("index"), Literal(thisShift)),
185
- true
186
- )
187
- ),
188
- ];
182
+ transformLiteral(object: Node, parents: Node[], index: number) {
183
+ var blockNode = choice(parents.filter((x) => this.functions.has(x)));
184
+
185
+ // Create initial function if none exist
186
+ if (this.functions.size === 0) {
187
+ var root = parents[parents.length - 1];
188
+ var rootFunctionName = this.getPlaceholder() + "_dLR_0";
189
+ this.functions.set(root, {
190
+ functionName: rootFunctionName,
191
+ indexShift: getRandomInteger(-100, 100),
192
+ });
193
+
194
+ blockNode = root;
195
+ }
189
196
 
190
- this.fnShifts.set(getterName, thisShift);
191
- }
197
+ // If no function here exist, possibly create new chained function
198
+ var block = getBlock(object, parents);
199
+ if (!this.functions.has(block) && chance(50 - this.functions.size)) {
200
+ var newFunctionName =
201
+ this.getPlaceholder() + "_dLR_" + this.functions.size;
192
202
 
193
- this.fnGetters.set(lexContext, getterName);
203
+ this.functions.set(block, {
204
+ functionName: newFunctionName,
205
+ indexShift: getRandomInteger(-100, 100),
206
+ });
194
207
 
195
- prepend(
196
- lexContext,
197
- VariableDeclaration(
198
- VariableDeclarator(
199
- getterName,
200
- CallExpression(
201
- FunctionExpression(
202
- [],
203
- [
204
- ReturnStatement(
205
- FunctionExpression([Identifier("index")], body)
206
- ),
207
- ]
208
- ),
209
- []
210
- )
211
- )
212
- )
213
- );
208
+ blockNode = block;
214
209
  }
215
210
 
216
- var theShift = this.fnShifts.get(getterName);
211
+ // Derive the function to call from the selected blockNode
212
+ var { functionName, indexShift } = this.functions.get(blockNode);
217
213
 
218
- this.replaceIdentifierOrLiteral(
219
- object,
220
- CallExpression(Identifier(getterName), [Literal(index - theShift)]),
221
- parents
222
- );
214
+ // Call the function given it's indexShift
215
+ var callExpression = CallExpression(Identifier(functionName), [
216
+ Literal(index + indexShift),
217
+ ]);
218
+
219
+ this.replaceIdentifierOrLiteral(object, callExpression, parents);
223
220
  }
224
221
 
225
222
  transform(object: Node, parents: Node[]) {
@@ -234,7 +231,7 @@ export default class DuplicateLiteralsRemoval extends Transform {
234
231
  }
235
232
 
236
233
  // HARD CODED LIMIT of 10,000 (after 1,000 elements)
237
- if (this.map.size > 1000 && !chance(this.map.size / 100)) return;
234
+ if (this.map.size > 1000 && chance(this.map.size / 100)) return;
238
235
 
239
236
  if (
240
237
  this.arrayName &&
@@ -244,11 +241,11 @@ export default class DuplicateLiteralsRemoval extends Transform {
244
241
  return;
245
242
  }
246
243
 
247
- var value;
244
+ var stringValue;
248
245
  if (object.type == "Literal") {
249
- value = typeof object.value + ":" + object.value;
246
+ stringValue = typeof object.value + ":" + object.value;
250
247
  if (object.value === null) {
251
- value = "null:null";
248
+ stringValue = "null:null";
252
249
  } else {
253
250
  // Skip empty strings
254
251
  if (typeof object.value === "string" && !object.value) {
@@ -256,42 +253,46 @@ export default class DuplicateLiteralsRemoval extends Transform {
256
253
  }
257
254
  }
258
255
  } else if (object.type == "Identifier") {
259
- value = "identifier:" + object.name;
256
+ stringValue = "identifier:" + object.name;
260
257
  } else {
261
258
  throw new Error("Unsupported primitive type: " + object.type);
262
259
  }
263
260
 
264
- ok(value);
261
+ ok(stringValue);
265
262
 
266
- if (!this.first.has(value) && !this.map.has(value)) {
267
- this.first.set(value, [object, parents]);
268
- } else {
263
+ if (this.map.has(stringValue) || this.first.has(stringValue)) {
264
+ // Create the array if not already made
269
265
  if (!this.arrayName) {
270
266
  this.arrayName = this.getPlaceholder();
271
267
  this.arrayExpression = ArrayExpression([]);
272
268
  }
273
269
 
274
- var firstLocation = this.first.get(value);
270
+ // Delete with first location
271
+ var firstLocation = this.first.get(stringValue);
275
272
  if (firstLocation) {
276
- this.first.set(value, null);
277
273
  var index = this.map.size;
278
274
 
279
- ok(!this.map.has(value));
280
- this.map.set(value, index);
275
+ ok(!this.map.has(stringValue));
276
+ this.map.set(stringValue, index);
277
+ this.first.delete(stringValue);
281
278
 
282
279
  var pushing = clone(object);
283
280
  this.arrayExpression.elements.push(pushing);
284
281
 
285
282
  ok(this.arrayExpression.elements[index] === pushing);
286
283
 
287
- this.toCaller(firstLocation[0], firstLocation[1], index);
284
+ this.transformLiteral(firstLocation[0], firstLocation[1], index);
288
285
  }
289
286
 
290
- var index = this.map.get(value);
287
+ var index = this.map.get(stringValue);
291
288
  ok(typeof index === "number");
292
289
 
293
- this.toCaller(object, parents, index);
290
+ this.transformLiteral(object, parents, index);
291
+ return;
294
292
  }
293
+
294
+ // Save this, maybe a duplicate will be found.
295
+ this.first.set(stringValue, [object, parents]);
295
296
  };
296
297
  }
297
298
  }