js-confuser 1.5.6 → 1.5.8

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 (29) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/options.js +4 -4
  3. package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +16 -2
  4. package/dist/transforms/identifier/nameRecycling.js +8 -2
  5. package/dist/transforms/identifier/renameVariables.js +9 -0
  6. package/dist/transforms/lock/antiDebug.js +1 -1
  7. package/dist/transforms/lock/integrity.js +6 -2
  8. package/dist/transforms/lock/lock.js +40 -32
  9. package/dist/transforms/preparation/preparation.js +0 -7
  10. package/dist/transforms/rgf.js +32 -3
  11. package/dist/transforms/string/stringConcealing.js +77 -40
  12. package/dist/transforms/transform.js +1 -1
  13. package/package.json +2 -2
  14. package/src/options.ts +10 -4
  15. package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +16 -1
  16. package/src/transforms/identifier/nameRecycling.ts +14 -3
  17. package/src/transforms/identifier/renameVariables.ts +19 -0
  18. package/src/transforms/lock/antiDebug.ts +1 -1
  19. package/src/transforms/lock/integrity.ts +13 -1
  20. package/src/transforms/lock/lock.ts +81 -44
  21. package/src/transforms/preparation/preparation.ts +2 -21
  22. package/src/transforms/rgf.ts +39 -3
  23. package/src/transforms/string/stringConcealing.ts +120 -56
  24. package/src/transforms/transform.ts +1 -1
  25. package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +36 -0
  26. package/test/transforms/identifier/nameRecycling.test.ts +39 -0
  27. package/test/transforms/identifier/renameVariables.test.ts +38 -0
  28. package/test/transforms/lock/countermeasures.test.ts +18 -0
  29. package/test/transforms/rgf.test.ts +37 -0
@@ -6,22 +6,18 @@ import { isDirective } from "../../util/compare";
6
6
  import {
7
7
  ArrayExpression,
8
8
  CallExpression,
9
- ConditionalExpression,
10
- FunctionDeclaration,
11
9
  FunctionExpression,
12
10
  Identifier,
13
- IfStatement,
14
11
  Literal,
15
12
  MemberExpression,
16
13
  Node,
17
- ReturnStatement,
18
- SequenceExpression,
14
+ ObjectExpression,
15
+ Property,
19
16
  ThisExpression,
20
- UpdateExpression,
21
17
  VariableDeclaration,
22
18
  VariableDeclarator,
23
19
  } from "../../util/gen";
24
- import { append, isLexContext, isVarContext, prepend } from "../../util/insert";
20
+ import { append, prepend } from "../../util/insert";
25
21
  import { choice, getRandomInteger, getRandomString } from "../../util/random";
26
22
  import Transform from "../transform";
27
23
  import Encoding from "./encoding";
@@ -65,6 +61,7 @@ export default class StringConcealing extends Transform {
65
61
  ignore = new Set<string>();
66
62
  variablesMade = 1;
67
63
  encoding: { [type: string]: string } = Object.create(null);
64
+ gen: ReturnType<Transform["getGenerator"]>;
68
65
 
69
66
  hasAllEncodings: boolean;
70
67
 
@@ -75,11 +72,12 @@ export default class StringConcealing extends Transform {
75
72
  this.index = Object.create(null);
76
73
  this.arrayExpression = ArrayExpression([]);
77
74
  this.hasAllEncodings = false;
75
+ this.gen = this.getGenerator();
78
76
 
79
77
  // Pad array with useless strings
80
- var dead = getRandomInteger(4, 10);
78
+ var dead = getRandomInteger(5, 15);
81
79
  for (var i = 0; i < dead; i++) {
82
- var str = getRandomString(getRandomInteger(4, 20));
80
+ var str = getRandomString(getRandomInteger(5, 40));
83
81
  var fn = this.transform(Literal(str), []);
84
82
  if (fn) {
85
83
  fn();
@@ -134,7 +132,7 @@ export default class StringConcealing extends Transform {
134
132
  VariableDeclarator("a", this.arrayExpression)
135
133
  ),
136
134
  Template(
137
- `return (${flowIntegrity} ? a.pop() : ${flowIntegrity}++, a)`
135
+ `return (${flowIntegrity} ? a["pop"]() : ${flowIntegrity}++, a)`
138
136
  ).single(),
139
137
  ]
140
138
  ),
@@ -159,7 +157,11 @@ export default class StringConcealing extends Transform {
159
157
  transform(object: Node, parents: Node[]) {
160
158
  return () => {
161
159
  // Empty strings are discarded
162
- if (!object.value || this.ignore.has(object.value)) {
160
+ if (
161
+ !object.value ||
162
+ this.ignore.has(object.value) ||
163
+ object.value.length == 0
164
+ ) {
163
165
  return;
164
166
  }
165
167
 
@@ -188,69 +190,131 @@ export default class StringConcealing extends Transform {
188
190
  var encoded = encoder.encode(object.value);
189
191
  if (encoder.decode(encoded) != object.value) {
190
192
  this.ignore.add(object.value);
191
- this.warn(object.value.slice(0, 100));
193
+ this.warn(type, object.value.slice(0, 100));
192
194
  return;
193
195
  }
194
196
 
195
- // Fix 1. weird undefined error
196
- if (object.value && object.value.length > 0) {
197
- var index = -1;
198
- if (!this.set.has(object.value)) {
199
- this.arrayExpression.elements.push(Literal(encoded));
200
- index = this.arrayExpression.elements.length - 1;
201
- this.index[object.value] = [index, fnName];
197
+ var index = -1;
198
+ if (!this.set.has(object.value)) {
199
+ this.arrayExpression.elements.push(Literal(encoded));
200
+ index = this.arrayExpression.elements.length - 1;
201
+ this.index[object.value] = [index, fnName];
202
202
 
203
- this.set.add(object.value);
204
- } else {
205
- [index, fnName] = this.index[object.value];
206
- ok(typeof index === "number");
207
- }
203
+ this.set.add(object.value);
204
+ } else {
205
+ [index, fnName] = this.index[object.value];
206
+ ok(typeof index === "number");
207
+ }
208
208
 
209
- ok(index != -1, "index == -1");
209
+ ok(index != -1, "index == -1");
210
210
 
211
- var callExpr = CallExpression(Identifier(fnName), [Literal(index)]);
211
+ var callExpr = CallExpression(Identifier(fnName), [Literal(index)]);
212
212
 
213
- // use `.apply` to fool automated de-obfuscators
214
- if (Math.random() > 0.5) {
215
- callExpr = CallExpression(
216
- MemberExpression(Identifier(fnName), Identifier("apply"), false),
217
- [ThisExpression(), ArrayExpression([Literal(index)])]
218
- );
219
- }
213
+ // use `.apply` to fool automated de-obfuscators
214
+ if (Math.random() > 0.5) {
215
+ callExpr = CallExpression(
216
+ MemberExpression(Identifier(fnName), Identifier("apply"), false),
217
+ [ThisExpression(), ArrayExpression([Literal(index)])]
218
+ );
219
+ }
220
+
221
+ // use `.call`
222
+ else if (Math.random() > 0.5) {
223
+ callExpr = CallExpression(
224
+ MemberExpression(Identifier(fnName), Identifier("call"), false),
225
+ [ThisExpression(), Literal(index)]
226
+ );
227
+ }
228
+
229
+ var referenceType = "call";
230
+ if (parents.length && Math.random() < 0.5 / this.variablesMade) {
231
+ referenceType = "constantReference";
232
+ }
233
+
234
+ var newExpr: Node = callExpr;
235
+
236
+ if (referenceType === "constantReference") {
237
+ // Define the string earlier, reference the name here
238
+ this.variablesMade++;
220
239
 
221
- // use `.call`
222
- else if (Math.random() > 0.5) {
223
- callExpr = CallExpression(
224
- MemberExpression(Identifier(fnName), Identifier("call"), false),
225
- [ThisExpression(), Literal(index)]
226
- );
240
+ var constantReferenceType = choice(["variable", "array", "object"]);
241
+
242
+ var place = choice(parents.filter((node) => isBlock(node)));
243
+ if (!place) {
244
+ this.error(new Error("No lexical block to insert code"));
227
245
  }
228
246
 
229
- var constantReference =
230
- parents.length && Math.random() > 0.5 / this.variablesMade;
247
+ switch (constantReferenceType) {
248
+ case "variable":
249
+ var name = this.getPlaceholder();
250
+
251
+ prepend(
252
+ place,
253
+ VariableDeclaration(VariableDeclarator(name, callExpr))
254
+ );
255
+
256
+ newExpr = Identifier(name);
257
+ break;
258
+ case "array":
259
+ if (!place.$stringConcealingArray) {
260
+ place.$stringConcealingArray = ArrayExpression([]);
261
+ place.$stringConcealingArrayName = this.getPlaceholder();
231
262
 
232
- if (constantReference) {
233
- // Define the string earlier, reference the name here
263
+ prepend(
264
+ place,
265
+ VariableDeclaration(
266
+ VariableDeclarator(
267
+ place.$stringConcealingArrayName,
268
+ place.$stringConcealingArray
269
+ )
270
+ )
271
+ );
272
+ }
234
273
 
235
- var name = this.getPlaceholder();
274
+ var arrayIndex = place.$stringConcealingArray.elements.length;
236
275
 
237
- var place = choice(parents.filter((node) => isBlock(node)));
238
- if (!place) {
239
- this.error(Error("No lexical block to insert code"));
240
- }
276
+ place.$stringConcealingArray.elements.push(callExpr);
241
277
 
242
- place.body.unshift(
243
- VariableDeclaration(VariableDeclarator(name, callExpr))
244
- );
278
+ var memberExpression = MemberExpression(
279
+ Identifier(place.$stringConcealingArrayName),
280
+ Literal(arrayIndex),
281
+ true
282
+ );
245
283
 
246
- this.replaceIdentifierOrLiteral(object, Identifier(name), parents);
284
+ newExpr = memberExpression;
285
+ break;
286
+ case "object":
287
+ if (!place.$stringConcealingObject) {
288
+ place.$stringConcealingObject = ObjectExpression([]);
289
+ place.$stringConcealingObjectName = this.getPlaceholder();
247
290
 
248
- this.variablesMade++;
249
- } else {
250
- // Direct call to the getter function
251
- this.replaceIdentifierOrLiteral(object, callExpr, parents);
291
+ prepend(
292
+ place,
293
+ VariableDeclaration(
294
+ VariableDeclarator(
295
+ place.$stringConcealingObjectName,
296
+ place.$stringConcealingObject
297
+ )
298
+ )
299
+ );
300
+ }
301
+
302
+ var propName = this.gen.generate();
303
+ var property = Property(Literal(propName), callExpr, true);
304
+ place.$stringConcealingObject.properties.push(property);
305
+
306
+ var memberExpression = MemberExpression(
307
+ Identifier(place.$stringConcealingObjectName),
308
+ Literal(propName),
309
+ true
310
+ );
311
+
312
+ newExpr = memberExpression;
313
+ break;
252
314
  }
253
315
  }
316
+
317
+ this.replaceIdentifierOrLiteral(object, newExpr, parents);
254
318
  };
255
319
  }
256
320
  }
@@ -407,7 +407,7 @@ export default class Transform {
407
407
  }
408
408
 
409
409
  /**
410
- * Verbose logging for warning/importing messages.
410
+ * Verbose logging for warning/important messages.
411
411
  * @param messages
412
412
  */
413
413
  warn(...messages: any[]) {
@@ -672,3 +672,39 @@ test("Variant #20: Don't apply when functions are redefined", async () => {
672
672
  eval(output);
673
673
  expect(TEST_ARRAY).toStrictEqual([0, 0, 0]);
674
674
  });
675
+
676
+ // https://github.com/MichaelXF/js-confuser/issues/70
677
+ test("Variant #21: Don't move Import Declarations", async () => {
678
+ var output = await JsConfuser(
679
+ `
680
+ import {createHash} from "crypto";
681
+ var inputString = "Hash this string";
682
+ var hashed = createHash("sha256").update(inputString).digest("hex");
683
+ TEST_OUTPUT = hashed;
684
+ `,
685
+ {
686
+ target: "node",
687
+ controlFlowFlattening: true,
688
+ }
689
+ );
690
+
691
+ // Ensure Control Flow FLattening was applied
692
+ expect(output).toContain("switch");
693
+
694
+ // Ensure the import declaration wasn't moved
695
+ expect(output.startsWith("import")).toStrictEqual(true);
696
+
697
+ // Convert to runnable code
698
+ output = output.replace(
699
+ `import{createHash}from'crypto';`,
700
+ "const {createHash}=require('crypto');"
701
+ );
702
+
703
+ var TEST_OUTPUT = "";
704
+
705
+ eval(output);
706
+
707
+ expect(TEST_OUTPUT).toStrictEqual(
708
+ "1cac63f39fd68d8c531f27b807610fb3d50f0fc3f186995767fb6316e7200a3e"
709
+ );
710
+ });
@@ -164,3 +164,42 @@ it("should convert variable declarations in for loop initializers properly", asy
164
164
  expect(TEST_VAR_1).toStrictEqual("Hello World");
165
165
  expect(TEST_VAR_2).toStrictEqual("Number: 0");
166
166
  });
167
+
168
+ // https://github.com/MichaelXF/js-confuser/issues/68
169
+ it("should work on Function Declarations that are defined later in the code", async () => {
170
+ var output = await JsConfuser(
171
+ `
172
+ var result = MyFunction();
173
+ TEST_VAR = result;
174
+
175
+ function MyFunction(b) {
176
+ return "Hello World";
177
+ }
178
+ `,
179
+ { target: "node", nameRecycling: true }
180
+ );
181
+
182
+ var TEST_VAR;
183
+ eval(output);
184
+
185
+ expect(TEST_VAR).toStrictEqual("Hello World");
186
+ });
187
+
188
+ // https://github.com/MichaelXF/js-confuser/issues/71
189
+ it("should work on Import Declarations", async () => {
190
+ var output = await JsConfuser(
191
+ `
192
+ import crypto from 'node:crypto'
193
+
194
+ var x = 1;
195
+
196
+ console.log(x);
197
+ `,
198
+ {
199
+ target: "node",
200
+ nameRecycling: true,
201
+ }
202
+ );
203
+
204
+ expect(output).not.toContain("crypto=");
205
+ });
@@ -436,3 +436,41 @@ test("Variant #18: Catch parameter and lexical variable clash", async () => {
436
436
 
437
437
  eval(output);
438
438
  });
439
+
440
+ // https://github.com/MichaelXF/js-confuser/issues/69
441
+ test("Variant #19: Don't break Import Declarations", async () => {
442
+ var output = await JsConfuser(
443
+ `
444
+ import { createHash } from 'node:crypto'
445
+
446
+ function sha256(content) {
447
+ return createHash('sha256').update(content).digest('hex')
448
+ }
449
+
450
+ TEST_OUTPUT = sha256("Hash this string");
451
+ `,
452
+ {
453
+ target: "node",
454
+ renameVariables: true,
455
+ }
456
+ );
457
+
458
+ // Ensure the createHash got renamed
459
+ expect(output).toContain("createHash as ");
460
+
461
+ // Convert to runnable code
462
+ // This smartly changes the `import` statement to a require call, keeping the new variable name intact
463
+ var newVarName = output.split("createHash as ")[1].split("}")[0];
464
+ output = output
465
+ .split(";")
466
+ .filter((s) => !s.startsWith("import"))
467
+ .join(";");
468
+ output = `var {createHash: ${newVarName}}=require('crypto');` + output;
469
+
470
+ var TEST_OUTPUT;
471
+ eval(output);
472
+
473
+ expect(TEST_OUTPUT).toStrictEqual(
474
+ "1cac63f39fd68d8c531f27b807610fb3d50f0fc3f186995767fb6316e7200a3e"
475
+ );
476
+ });
@@ -80,3 +80,21 @@ test("Variant #4: Should work when countermeasures is variable declaration", asy
80
80
  }
81
81
  );
82
82
  });
83
+
84
+ // https://github.com/MichaelXF/js-confuser/issues/66
85
+ test("Variant #5: Should work with RGF enabled", async () => {
86
+ await JsConfuser.obfuscate(
87
+ `
88
+ function myCountermeasuresFunction(){
89
+
90
+ }
91
+ `,
92
+ {
93
+ target: "node",
94
+ lock: {
95
+ countermeasures: "myCountermeasuresFunction",
96
+ },
97
+ rgf: true,
98
+ }
99
+ );
100
+ });
@@ -251,6 +251,43 @@ input(console.log, result)
251
251
  eval(output);
252
252
  expect(TEST_VALUE).toStrictEqual("undefined");
253
253
  });
254
+
255
+ it("should not apply to functions that reference their parent scope in the parameters", async () => {
256
+ var output = await JsConfuser.obfuscate(
257
+ `
258
+ var outsideVar = 0;
259
+ function myFunction(insideVar = outsideVar){
260
+ }
261
+ `,
262
+ {
263
+ target: "node",
264
+ rgf: true,
265
+ }
266
+ );
267
+
268
+ expect(output).not.toContain("new Function");
269
+ });
270
+
271
+ it("should not apply to functions that reference their parent scope in previously defined names", async () => {
272
+ var output = await JsConfuser.obfuscate(
273
+ `
274
+ var outsideVar = 0;
275
+ function myFunction(){
276
+ (function (){
277
+ var outsideVar;
278
+ })();
279
+
280
+ console.log(outsideVar);
281
+ }
282
+ `,
283
+ {
284
+ target: "node",
285
+ rgf: true,
286
+ }
287
+ );
288
+
289
+ expect(output).not.toContain("new Function");
290
+ });
254
291
  });
255
292
 
256
293
  describe("RGF with the 'all' mode", () => {