js-confuser 1.5.7 → 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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # `1.5.8`
2
+ Several fixes
3
+
4
+ - Fixed [#46](https://github.com/MichaelXF/js-confuser/issues/46)
5
+ - - Updated the validation on `lock` options
6
+
7
+ - Fixed [#68](https://github.com/MichaelXF/js-confuser/issues/68)
8
+ - - Name Recycling fixed to not break certain function declarations
9
+
10
+ - Fixed [#69](https://github.com/MichaelXF/js-confuser/issues/69), [#70](https://github.com/MichaelXF/js-confuser/issues/70) and [#71](https://github.com/MichaelXF/js-confuser/issues/71)
11
+ - - Import statements to be properly handled
12
+
13
+ - Slight improvements to String Concealing
14
+
1
15
  # `1.5.7`
2
16
  Countermeasures function fixes
3
17
 
package/dist/options.js CHANGED
@@ -40,24 +40,24 @@ function validateOptions(options) {
40
40
 
41
41
  if (options.lock) {
42
42
  // Validate browser-lock option
43
- if (typeof options.lock.browserLock !== "undefined") {
43
+ if (options.lock.browserLock && typeof options.lock.browserLock !== "undefined") {
44
44
  (0, _assert.ok)(Array.isArray(options.lock.browserLock), "browserLock must be an array");
45
45
  (0, _assert.ok)(!options.lock.browserLock.find(browserName => !validBrowsers.has(browserName)), 'Invalid browser name. Allowed: "firefox", "chrome", "iexplorer", "edge", "safari", "opera"');
46
46
  } // Validate os-lock option
47
47
 
48
48
 
49
- if (typeof options.lock.osLock !== "undefined") {
49
+ if (options.lock.osLock && typeof options.lock.osLock !== "undefined") {
50
50
  (0, _assert.ok)(Array.isArray(options.lock.osLock), "osLock must be an array");
51
51
  (0, _assert.ok)(!options.lock.osLock.find(osName => !validOses.has(osName)), 'Invalid OS name. Allowed: "windows", "linux", "osx", "ios", "android"');
52
52
  } // Validate domain-lock option
53
53
 
54
54
 
55
- if (typeof options.lock.domainLock !== "undefined") {
55
+ if (options.lock.domainLock && typeof options.lock.domainLock !== "undefined") {
56
56
  (0, _assert.ok)(Array.isArray(options.lock.domainLock), "domainLock must be an array");
57
57
  } // Validate context option
58
58
 
59
59
 
60
- if (typeof options.lock.context !== "undefined") {
60
+ if (options.lock.context && typeof options.lock.context !== "undefined") {
61
61
  (0, _assert.ok)(Array.isArray(options.lock.context), "context must be an array");
62
62
  } // Validate start-date option
63
63
 
@@ -31,6 +31,8 @@ var _expressionObfuscation = _interopRequireDefault(require("./expressionObfusca
31
31
 
32
32
  var _switchCaseObfuscation = _interopRequireDefault(require("./switchCaseObfuscation"));
33
33
 
34
+ var _stringConcealing = require("../string/stringConcealing");
35
+
34
36
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
35
37
 
36
38
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
@@ -181,6 +183,14 @@ class ControlFlowFlattening extends _transform.default {
181
183
  illegalFnNames.forEach(illegal => {
182
184
  fnNames.delete(illegal);
183
185
  });
186
+ var importDeclarations = [];
187
+
188
+ for (var stmt of body) {
189
+ if (stmt.type === "ImportDeclaration") {
190
+ importDeclarations.push(stmt);
191
+ }
192
+ }
193
+
184
194
  var fraction = 0.9;
185
195
 
186
196
  if (body.length > 20) {
@@ -237,7 +247,7 @@ class ControlFlowFlattening extends _transform.default {
237
247
  body: [...currentBody]
238
248
  });
239
249
  (0, _traverse.walk)(currentBody, [], (o, p) => {
240
- if (o.type == "Literal" && typeof o.value == "string" && !o.regex && Math.random() / (Object.keys(stringBank).length / 2 + 1) > 0.5) {
250
+ if (o.type == "Literal" && typeof o.value == "string" && !(0, _stringConcealing.isModuleSource)(o, p) && !o.regex && Math.random() / (Object.keys(stringBank).length / 2 + 1) > 0.5) {
241
251
  needsStringBankVar = true;
242
252
 
243
253
  if (!stringBankByLabels[currentLabel]) {
@@ -260,7 +270,7 @@ class ControlFlowFlattening extends _transform.default {
260
270
  };
261
271
 
262
272
  body.forEach((stmt, i) => {
263
- if (functionDeclarations.has(stmt)) {
273
+ if (functionDeclarations.has(stmt) || stmt.type === "ImportDeclaration") {
264
274
  return;
265
275
  }
266
276
 
@@ -708,6 +718,10 @@ class ControlFlowFlattening extends _transform.default {
708
718
  var discriminant = (0, _template.default)("".concat(stateVars.join("+"))).single().expression;
709
719
  body.length = 0;
710
720
 
721
+ for (var importDeclaration of importDeclarations) {
722
+ body.push(importDeclaration);
723
+ }
724
+
711
725
  if (functionDeclarations.size) {
712
726
  functionDeclarations.forEach(x => {
713
727
  if (!x.id || illegalFnNames.has(x.id.name)) {
@@ -87,7 +87,6 @@ class NameRecycling extends _transform.default {
87
87
  return;
88
88
  }
89
89
 
90
- lastReferenceMap.set(o.name, i);
91
90
  var comparingContext = info.spec.isDefined ? (0, _insert.getDefiningContext)(o, p) : (0, _insert.getReferencingContexts)(o, p).find(x => (0, _insert.isVarContext)(x));
92
91
 
93
92
  if (comparingContext !== context) {
@@ -101,7 +100,12 @@ class NameRecycling extends _transform.default {
101
100
  }
102
101
 
103
102
  if (info.spec.isDefined) {
104
- if (defined.has(o.name) || (0, _traverse.getBlock)(o, p) !== object) {
103
+ // Function Declarations can be used before they're defined, if so, don't change this
104
+ if (info.isFunctionDeclaration && lastReferenceMap.has(o.name)) {
105
+ illegal.add(o.name);
106
+ }
107
+
108
+ if (defined.has(o.name) || (0, _traverse.getBlock)(o, p) !== object || info.isImportSpecifier) {
105
109
  illegal.add(o.name);
106
110
  }
107
111
 
@@ -111,6 +115,8 @@ class NameRecycling extends _transform.default {
111
115
  referencedHere.add(o.name);
112
116
  }
113
117
  }
118
+
119
+ lastReferenceMap.set(o.name, i);
114
120
  };
115
121
  }
116
122
  }); // console.log(i, definedHere);
@@ -165,6 +165,15 @@ class RenameVariables extends _transform.default {
165
165
  if (newName && typeof newName === "string") {
166
166
  if (o.$renamed) {
167
167
  return;
168
+ } // Strange behavior where the `local` and `imported` objects are the same
169
+
170
+
171
+ if (info.isImportSpecifier) {
172
+ var importSpecifierIndex = p.findIndex(x => x.type === "ImportSpecifier");
173
+
174
+ if (importSpecifierIndex != -1 && p[importSpecifierIndex].imported === (p[importSpecifierIndex - 1] || o) && p[importSpecifierIndex].imported && p[importSpecifierIndex].imported.type === "Identifier") {
175
+ p[importSpecifierIndex].imported = (0, _insert.clone)(p[importSpecifierIndex - 1] || o);
176
+ }
168
177
  } // console.log(o.name, "->", newName);
169
178
 
170
179
 
@@ -43,7 +43,7 @@ class AntiDebug extends _transform.default {
43
43
  var startTimeName = this.getPlaceholder();
44
44
  var endTimeName = this.getPlaceholder();
45
45
  var isDevName = this.getPlaceholder();
46
- var functionDeclaration = (0, _gen.FunctionDeclaration)(fnName, [], [...(0, _template.default)("\n var ".concat(startTimeName, " = new Date();\n debugger;\n var ").concat(endTimeName, " = new Date();\n var ").concat(isDevName, " = ").concat(endTimeName, "-").concat(startTimeName, " > 1000;\n ")).compile(), (0, _gen.IfStatement)((0, _gen.Identifier)(isDevName), this.options.lock.countermeasures ? this.lock.getCounterMeasuresCode() : [(0, _gen.WhileStatement)((0, _gen.Identifier)(isDevName), [(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(startTimeName), (0, _gen.Identifier)(endTimeName)))])], null)]);
46
+ var functionDeclaration = (0, _gen.FunctionDeclaration)(fnName, [], [...(0, _template.default)("\n var ".concat(startTimeName, " = new Date();\n debugger;\n var ").concat(endTimeName, " = new Date();\n var ").concat(isDevName, " = ").concat(endTimeName, "-").concat(startTimeName, " > 1000;\n ")).compile(), (0, _gen.IfStatement)((0, _gen.Identifier)(isDevName), this.options.lock.countermeasures ? this.lock.getCounterMeasuresCode(tree.body, [tree]) : [(0, _gen.WhileStatement)((0, _gen.Identifier)(isDevName), [(0, _gen.ExpressionStatement)((0, _gen.AssignmentExpression)("=", (0, _gen.Identifier)(startTimeName), (0, _gen.Identifier)(endTimeName)))])], null)]);
47
47
  tree.body.unshift(...DevToolsDetection.compile({
48
48
  functionName: fnName
49
49
  }));
@@ -70,17 +70,20 @@ class StringConcealing extends _transform.default {
70
70
 
71
71
  _defineProperty(this, "encoding", Object.create(null));
72
72
 
73
+ _defineProperty(this, "gen", void 0);
74
+
73
75
  _defineProperty(this, "hasAllEncodings", void 0);
74
76
 
75
77
  this.set = new Set();
76
78
  this.index = Object.create(null);
77
79
  this.arrayExpression = (0, _gen.ArrayExpression)([]);
78
- this.hasAllEncodings = false; // Pad array with useless strings
80
+ this.hasAllEncodings = false;
81
+ this.gen = this.getGenerator(); // Pad array with useless strings
79
82
 
80
- var dead = (0, _random.getRandomInteger)(4, 10);
83
+ var dead = (0, _random.getRandomInteger)(5, 15);
81
84
 
82
85
  for (var i = 0; i < dead; i++) {
83
- var str = (0, _random.getRandomString)((0, _random.getRandomInteger)(4, 20));
86
+ var str = (0, _random.getRandomString)((0, _random.getRandomInteger)(5, 40));
84
87
  var fn = this.transform((0, _gen.Literal)(str), []);
85
88
 
86
89
  if (fn) {
@@ -104,7 +107,7 @@ class StringConcealing extends _transform.default {
104
107
  (0, _insert.append)(tree, (0, _template.default)("\n \n function ".concat(getterFn, "(x, y, z, a = ").concat(decodeFn, ", b = ").concat(cacheName, "){\n if ( z ) {\n return y[").concat(cacheName, "[z]] = ").concat(getterFn, "(x, y);\n } else if ( y ) {\n [b, y] = [a(b), x || z]\n }\n \n return y ? x[b[y]] : ").concat(cacheName, "[x] || (z=(b[x], a), ").concat(cacheName, "[x] = z(").concat(this.arrayName, "[x]))\n }\n \n ")).single());
105
108
  });
106
109
  var flowIntegrity = this.getPlaceholder();
107
- (0, _insert.prepend)(tree, (0, _gen.VariableDeclaration)([(0, _gen.VariableDeclarator)(cacheName, (0, _gen.ArrayExpression)([])), (0, _gen.VariableDeclarator)(flowIntegrity, (0, _gen.Literal)(0)), (0, _gen.VariableDeclarator)(this.arrayName, (0, _gen.CallExpression)((0, _gen.FunctionExpression)([], [(0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)("a", this.arrayExpression)), (0, _template.default)("return (".concat(flowIntegrity, " ? a.pop() : ").concat(flowIntegrity, "++, a)")).single()]), []))]));
110
+ (0, _insert.prepend)(tree, (0, _gen.VariableDeclaration)([(0, _gen.VariableDeclarator)(cacheName, (0, _gen.ArrayExpression)([])), (0, _gen.VariableDeclarator)(flowIntegrity, (0, _gen.Literal)(0)), (0, _gen.VariableDeclarator)(this.arrayName, (0, _gen.CallExpression)((0, _gen.FunctionExpression)([], [(0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)("a", this.arrayExpression)), (0, _template.default)("return (".concat(flowIntegrity, " ? a[\"pop\"]() : ").concat(flowIntegrity, "++, a)")).single()]), []))]));
108
111
  }
109
112
 
110
113
  match(object, parents) {
@@ -117,7 +120,7 @@ class StringConcealing extends _transform.default {
117
120
  transform(object, parents) {
118
121
  return () => {
119
122
  // Empty strings are discarded
120
- if (!object.value || this.ignore.has(object.value)) {
123
+ if (!object.value || this.ignore.has(object.value) || object.value.length == 0) {
121
124
  return;
122
125
  }
123
126
 
@@ -143,53 +146,87 @@ class StringConcealing extends _transform.default {
143
146
 
144
147
  if (encoder.decode(encoded) != object.value) {
145
148
  this.ignore.add(object.value);
146
- this.warn(object.value.slice(0, 100));
149
+ this.warn(type, object.value.slice(0, 100));
147
150
  return;
148
- } // Fix 1. weird undefined error
151
+ }
152
+
153
+ var index = -1;
149
154
 
155
+ if (!this.set.has(object.value)) {
156
+ this.arrayExpression.elements.push((0, _gen.Literal)(encoded));
157
+ index = this.arrayExpression.elements.length - 1;
158
+ this.index[object.value] = [index, fnName];
159
+ this.set.add(object.value);
160
+ } else {
161
+ [index, fnName] = this.index[object.value];
162
+ (0, _assert.ok)(typeof index === "number");
163
+ }
150
164
 
151
- if (object.value && object.value.length > 0) {
152
- var index = -1;
165
+ (0, _assert.ok)(index != -1, "index == -1");
166
+ var callExpr = (0, _gen.CallExpression)((0, _gen.Identifier)(fnName), [(0, _gen.Literal)(index)]); // use `.apply` to fool automated de-obfuscators
153
167
 
154
- if (!this.set.has(object.value)) {
155
- this.arrayExpression.elements.push((0, _gen.Literal)(encoded));
156
- index = this.arrayExpression.elements.length - 1;
157
- this.index[object.value] = [index, fnName];
158
- this.set.add(object.value);
159
- } else {
160
- [index, fnName] = this.index[object.value];
161
- (0, _assert.ok)(typeof index === "number");
162
- }
168
+ if (Math.random() > 0.5) {
169
+ callExpr = (0, _gen.CallExpression)((0, _gen.MemberExpression)((0, _gen.Identifier)(fnName), (0, _gen.Identifier)("apply"), false), [(0, _gen.ThisExpression)(), (0, _gen.ArrayExpression)([(0, _gen.Literal)(index)])]);
170
+ } // use `.call`
171
+ else if (Math.random() > 0.5) {
172
+ callExpr = (0, _gen.CallExpression)((0, _gen.MemberExpression)((0, _gen.Identifier)(fnName), (0, _gen.Identifier)("call"), false), [(0, _gen.ThisExpression)(), (0, _gen.Literal)(index)]);
173
+ }
163
174
 
164
- (0, _assert.ok)(index != -1, "index == -1");
165
- var callExpr = (0, _gen.CallExpression)((0, _gen.Identifier)(fnName), [(0, _gen.Literal)(index)]); // use `.apply` to fool automated de-obfuscators
175
+ var referenceType = "call";
166
176
 
167
- if (Math.random() > 0.5) {
168
- callExpr = (0, _gen.CallExpression)((0, _gen.MemberExpression)((0, _gen.Identifier)(fnName), (0, _gen.Identifier)("apply"), false), [(0, _gen.ThisExpression)(), (0, _gen.ArrayExpression)([(0, _gen.Literal)(index)])]);
169
- } // use `.call`
170
- else if (Math.random() > 0.5) {
171
- callExpr = (0, _gen.CallExpression)((0, _gen.MemberExpression)((0, _gen.Identifier)(fnName), (0, _gen.Identifier)("call"), false), [(0, _gen.ThisExpression)(), (0, _gen.Literal)(index)]);
172
- }
177
+ if (parents.length && Math.random() < 0.5 / this.variablesMade) {
178
+ referenceType = "constantReference";
179
+ }
173
180
 
174
- var constantReference = parents.length && Math.random() > 0.5 / this.variablesMade;
181
+ var newExpr = callExpr;
175
182
 
176
- if (constantReference) {
177
- // Define the string earlier, reference the name here
178
- var name = this.getPlaceholder();
179
- var place = (0, _random.choice)(parents.filter(node => (0, _traverse.isBlock)(node)));
183
+ if (referenceType === "constantReference") {
184
+ // Define the string earlier, reference the name here
185
+ this.variablesMade++;
186
+ var constantReferenceType = (0, _random.choice)(["variable", "array", "object"]);
187
+ var place = (0, _random.choice)(parents.filter(node => (0, _traverse.isBlock)(node)));
180
188
 
181
- if (!place) {
182
- this.error(Error("No lexical block to insert code"));
183
- }
189
+ if (!place) {
190
+ this.error(new Error("No lexical block to insert code"));
191
+ }
184
192
 
185
- place.body.unshift((0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(name, callExpr)));
186
- this.replaceIdentifierOrLiteral(object, (0, _gen.Identifier)(name), parents);
187
- this.variablesMade++;
188
- } else {
189
- // Direct call to the getter function
190
- this.replaceIdentifierOrLiteral(object, callExpr, parents);
193
+ switch (constantReferenceType) {
194
+ case "variable":
195
+ var name = this.getPlaceholder();
196
+ (0, _insert.prepend)(place, (0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(name, callExpr)));
197
+ newExpr = (0, _gen.Identifier)(name);
198
+ break;
199
+
200
+ case "array":
201
+ if (!place.$stringConcealingArray) {
202
+ place.$stringConcealingArray = (0, _gen.ArrayExpression)([]);
203
+ place.$stringConcealingArrayName = this.getPlaceholder();
204
+ (0, _insert.prepend)(place, (0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(place.$stringConcealingArrayName, place.$stringConcealingArray)));
205
+ }
206
+
207
+ var arrayIndex = place.$stringConcealingArray.elements.length;
208
+ place.$stringConcealingArray.elements.push(callExpr);
209
+ var memberExpression = (0, _gen.MemberExpression)((0, _gen.Identifier)(place.$stringConcealingArrayName), (0, _gen.Literal)(arrayIndex), true);
210
+ newExpr = memberExpression;
211
+ break;
212
+
213
+ case "object":
214
+ if (!place.$stringConcealingObject) {
215
+ place.$stringConcealingObject = (0, _gen.ObjectExpression)([]);
216
+ place.$stringConcealingObjectName = this.getPlaceholder();
217
+ (0, _insert.prepend)(place, (0, _gen.VariableDeclaration)((0, _gen.VariableDeclarator)(place.$stringConcealingObjectName, place.$stringConcealingObject)));
218
+ }
219
+
220
+ var propName = this.gen.generate();
221
+ var property = (0, _gen.Property)((0, _gen.Literal)(propName), callExpr, true);
222
+ place.$stringConcealingObject.properties.push(property);
223
+ var memberExpression = (0, _gen.MemberExpression)((0, _gen.Identifier)(place.$stringConcealingObjectName), (0, _gen.Literal)(propName), true);
224
+ newExpr = memberExpression;
225
+ break;
191
226
  }
192
227
  }
228
+
229
+ this.replaceIdentifierOrLiteral(object, newExpr, parents);
193
230
  };
194
231
  }
195
232
 
@@ -378,7 +378,7 @@ class Transform {
378
378
  }
379
379
  }
380
380
  /**
381
- * Verbose logging for warning/importing messages.
381
+ * Verbose logging for warning/important messages.
382
382
  * @param messages
383
383
  */
384
384
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-confuser",
3
- "version": "1.5.7",
3
+ "version": "1.5.8",
4
4
  "description": "JavaScript Obfuscation Tool.",
5
5
  "main": "dist/index.js",
6
6
  "types": "index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "author": "MichaelXF",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "acorn": "^8.7.0",
25
+ "acorn": "^8.8.2",
26
26
  "escodegen": "^2.0.0"
27
27
  },
28
28
  "devDependencies": {
package/src/options.ts CHANGED
@@ -770,7 +770,10 @@ export function validateOptions(options: ObfuscateOptions) {
770
770
 
771
771
  if (options.lock) {
772
772
  // Validate browser-lock option
773
- if (typeof options.lock.browserLock !== "undefined") {
773
+ if (
774
+ options.lock.browserLock &&
775
+ typeof options.lock.browserLock !== "undefined"
776
+ ) {
774
777
  ok(
775
778
  Array.isArray(options.lock.browserLock),
776
779
  "browserLock must be an array"
@@ -783,7 +786,7 @@ export function validateOptions(options: ObfuscateOptions) {
783
786
  );
784
787
  }
785
788
  // Validate os-lock option
786
- if (typeof options.lock.osLock !== "undefined") {
789
+ if (options.lock.osLock && typeof options.lock.osLock !== "undefined") {
787
790
  ok(Array.isArray(options.lock.osLock), "osLock must be an array");
788
791
  ok(
789
792
  !options.lock.osLock.find((osName) => !validOses.has(osName)),
@@ -791,12 +794,15 @@ export function validateOptions(options: ObfuscateOptions) {
791
794
  );
792
795
  }
793
796
  // Validate domain-lock option
794
- if (typeof options.lock.domainLock !== "undefined") {
797
+ if (
798
+ options.lock.domainLock &&
799
+ typeof options.lock.domainLock !== "undefined"
800
+ ) {
795
801
  ok(Array.isArray(options.lock.domainLock), "domainLock must be an array");
796
802
  }
797
803
 
798
804
  // Validate context option
799
- if (typeof options.lock.context !== "undefined") {
805
+ if (options.lock.context && typeof options.lock.context !== "undefined") {
800
806
  ok(Array.isArray(options.lock.context), "context must be an array");
801
807
  }
802
808
 
@@ -47,6 +47,7 @@ import ChoiceFlowObfuscation from "./choiceFlowObfuscation";
47
47
  import ControlFlowObfuscation from "./controlFlowObfuscation";
48
48
  import ExpressionObfuscation from "./expressionObfuscation";
49
49
  import SwitchCaseObfuscation from "./switchCaseObfuscation";
50
+ import { isModuleSource } from "../string/stringConcealing";
50
51
 
51
52
  var flattenStructures = new Set([
52
53
  "IfStatement",
@@ -226,6 +227,13 @@ export default class ControlFlowFlattening extends Transform {
226
227
  fnNames.delete(illegal);
227
228
  });
228
229
 
230
+ var importDeclarations = [];
231
+ for (var stmt of body) {
232
+ if (stmt.type === "ImportDeclaration") {
233
+ importDeclarations.push(stmt);
234
+ }
235
+ }
236
+
229
237
  var fraction = 0.9;
230
238
  if (body.length > 20) {
231
239
  fraction /= Math.max(1.2, body.length - 18);
@@ -285,6 +293,7 @@ export default class ControlFlowFlattening extends Transform {
285
293
  if (
286
294
  o.type == "Literal" &&
287
295
  typeof o.value == "string" &&
296
+ !isModuleSource(o, p) &&
288
297
  !o.regex &&
289
298
  Math.random() / (Object.keys(stringBank).length / 2 + 1) > 0.5
290
299
  ) {
@@ -318,7 +327,10 @@ export default class ControlFlowFlattening extends Transform {
318
327
  };
319
328
 
320
329
  body.forEach((stmt, i) => {
321
- if (functionDeclarations.has(stmt)) {
330
+ if (
331
+ functionDeclarations.has(stmt) ||
332
+ stmt.type === "ImportDeclaration"
333
+ ) {
322
334
  return;
323
335
  }
324
336
 
@@ -1045,6 +1057,9 @@ export default class ControlFlowFlattening extends Transform {
1045
1057
  var discriminant = Template(`${stateVars.join("+")}`).single().expression;
1046
1058
 
1047
1059
  body.length = 0;
1060
+ for (var importDeclaration of importDeclarations) {
1061
+ body.push(importDeclaration);
1062
+ }
1048
1063
 
1049
1064
  if (functionDeclarations.size) {
1050
1065
  functionDeclarations.forEach((x) => {
@@ -97,8 +97,6 @@ export default class NameRecycling extends Transform {
97
97
  return;
98
98
  }
99
99
 
100
- lastReferenceMap.set(o.name, i);
101
-
102
100
  var comparingContext = info.spec.isDefined
103
101
  ? getDefiningContext(o, p)
104
102
  : getReferencingContexts(o, p).find((x) => isVarContext(x));
@@ -114,7 +112,18 @@ export default class NameRecycling extends Transform {
114
112
  }
115
113
 
116
114
  if (info.spec.isDefined) {
117
- if (defined.has(o.name) || getBlock(o, p) !== object) {
115
+ // Function Declarations can be used before they're defined, if so, don't change this
116
+ if (
117
+ info.isFunctionDeclaration &&
118
+ lastReferenceMap.has(o.name)
119
+ ) {
120
+ illegal.add(o.name);
121
+ }
122
+ if (
123
+ defined.has(o.name) ||
124
+ getBlock(o, p) !== object ||
125
+ info.isImportSpecifier
126
+ ) {
118
127
  illegal.add(o.name);
119
128
  }
120
129
  defined.add(o.name);
@@ -123,6 +132,8 @@ export default class NameRecycling extends Transform {
123
132
  referencedHere.add(o.name);
124
133
  }
125
134
  }
135
+
136
+ lastReferenceMap.set(o.name, i);
126
137
  };
127
138
  }
128
139
  });
@@ -10,6 +10,7 @@ import {
10
10
  isContext,
11
11
  isLexContext,
12
12
  getDefiningContext,
13
+ clone,
13
14
  } from "../../util/insert";
14
15
  import { isValidIdentifier } from "../../util/compare";
15
16
  import Transform from "../transform";
@@ -185,6 +186,24 @@ export default class RenameVariables extends Transform {
185
186
  return;
186
187
  }
187
188
 
189
+ // Strange behavior where the `local` and `imported` objects are the same
190
+ if (info.isImportSpecifier) {
191
+ var importSpecifierIndex = p.findIndex(
192
+ (x) => x.type === "ImportSpecifier"
193
+ );
194
+ if (
195
+ importSpecifierIndex != -1 &&
196
+ p[importSpecifierIndex].imported ===
197
+ (p[importSpecifierIndex - 1] || o) &&
198
+ p[importSpecifierIndex].imported &&
199
+ p[importSpecifierIndex].imported.type === "Identifier"
200
+ ) {
201
+ p[importSpecifierIndex].imported = clone(
202
+ p[importSpecifierIndex - 1] || o
203
+ );
204
+ }
205
+ }
206
+
188
207
  // console.log(o.name, "->", newName);
189
208
  o.name = newName;
190
209
  o.$renamed = true;
@@ -62,7 +62,7 @@ export default class AntiDebug extends Transform {
62
62
  IfStatement(
63
63
  Identifier(isDevName),
64
64
  this.options.lock.countermeasures
65
- ? this.lock.getCounterMeasuresCode()
65
+ ? this.lock.getCounterMeasuresCode(tree.body, [tree])
66
66
  : [
67
67
  WhileStatement(Identifier(isDevName), [
68
68
  ExpressionStatement(
@@ -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
+ });