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.
- package/CHANGELOG.md +28 -0
- package/dist/options.js +4 -4
- package/dist/transforms/controlFlowFlattening/controlFlowFlattening.js +16 -2
- package/dist/transforms/identifier/nameRecycling.js +8 -2
- package/dist/transforms/identifier/renameVariables.js +9 -0
- package/dist/transforms/lock/antiDebug.js +1 -1
- package/dist/transforms/lock/integrity.js +6 -2
- package/dist/transforms/lock/lock.js +40 -32
- package/dist/transforms/preparation/preparation.js +0 -7
- package/dist/transforms/rgf.js +32 -3
- package/dist/transforms/string/stringConcealing.js +77 -40
- package/dist/transforms/transform.js +1 -1
- package/package.json +2 -2
- package/src/options.ts +10 -4
- package/src/transforms/controlFlowFlattening/controlFlowFlattening.ts +16 -1
- package/src/transforms/identifier/nameRecycling.ts +14 -3
- package/src/transforms/identifier/renameVariables.ts +19 -0
- package/src/transforms/lock/antiDebug.ts +1 -1
- package/src/transforms/lock/integrity.ts +13 -1
- package/src/transforms/lock/lock.ts +81 -44
- package/src/transforms/preparation/preparation.ts +2 -21
- package/src/transforms/rgf.ts +39 -3
- package/src/transforms/string/stringConcealing.ts +120 -56
- package/src/transforms/transform.ts +1 -1
- package/test/transforms/controlFlowFlattening/controlFlowFlattening.test.ts +36 -0
- package/test/transforms/identifier/nameRecycling.test.ts +39 -0
- package/test/transforms/identifier/renameVariables.test.ts +38 -0
- package/test/transforms/lock/countermeasures.test.ts +18 -0
- 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
|
-
|
|
18
|
-
|
|
14
|
+
ObjectExpression,
|
|
15
|
+
Property,
|
|
19
16
|
ThisExpression,
|
|
20
|
-
UpdateExpression,
|
|
21
17
|
VariableDeclaration,
|
|
22
18
|
VariableDeclarator,
|
|
23
19
|
} from "../../util/gen";
|
|
24
|
-
import { append,
|
|
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(
|
|
78
|
+
var dead = getRandomInteger(5, 15);
|
|
81
79
|
for (var i = 0; i < dead; i++) {
|
|
82
|
-
var str = getRandomString(getRandomInteger(
|
|
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
|
|
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 (
|
|
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
|
-
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
209
|
+
ok(index != -1, "index == -1");
|
|
210
210
|
|
|
211
|
-
|
|
211
|
+
var callExpr = CallExpression(Identifier(fnName), [Literal(index)]);
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
230
|
-
|
|
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
|
-
|
|
233
|
-
|
|
263
|
+
prepend(
|
|
264
|
+
place,
|
|
265
|
+
VariableDeclaration(
|
|
266
|
+
VariableDeclarator(
|
|
267
|
+
place.$stringConcealingArrayName,
|
|
268
|
+
place.$stringConcealingArray
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
}
|
|
234
273
|
|
|
235
|
-
|
|
274
|
+
var arrayIndex = place.$stringConcealingArray.elements.length;
|
|
236
275
|
|
|
237
|
-
|
|
238
|
-
if (!place) {
|
|
239
|
-
this.error(Error("No lexical block to insert code"));
|
|
240
|
-
}
|
|
276
|
+
place.$stringConcealingArray.elements.push(callExpr);
|
|
241
277
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
278
|
+
var memberExpression = MemberExpression(
|
|
279
|
+
Identifier(place.$stringConcealingArrayName),
|
|
280
|
+
Literal(arrayIndex),
|
|
281
|
+
true
|
|
282
|
+
);
|
|
245
283
|
|
|
246
|
-
|
|
284
|
+
newExpr = memberExpression;
|
|
285
|
+
break;
|
|
286
|
+
case "object":
|
|
287
|
+
if (!place.$stringConcealingObject) {
|
|
288
|
+
place.$stringConcealingObject = ObjectExpression([]);
|
|
289
|
+
place.$stringConcealingObjectName = this.getPlaceholder();
|
|
247
290
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
}
|
|
@@ -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", () => {
|