simple-javascript-obf 0.1.1

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 (43) hide show
  1. package/.github/workflows/node.js.yml +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/THIRD_PARTY_NOTICES.md +82 -0
  5. package/bin/js-obf +228 -0
  6. package/bin/obf.sh +257 -0
  7. package/package.json +26 -0
  8. package/src/index.js +106 -0
  9. package/src/options.js +128 -0
  10. package/src/pipeline.js +56 -0
  11. package/src/plugins/antiHook.js +123 -0
  12. package/src/plugins/controlFlowFlatten.js +203 -0
  13. package/src/plugins/deadCode.js +82 -0
  14. package/src/plugins/encodeMembers.js +44 -0
  15. package/src/plugins/entry.js +31 -0
  16. package/src/plugins/rename.js +100 -0
  17. package/src/plugins/stringEncode.js +494 -0
  18. package/src/plugins/vm/ast-utils.js +58 -0
  19. package/src/plugins/vm/compiler.js +113 -0
  20. package/src/plugins/vm/constants.js +72 -0
  21. package/src/plugins/vm/emit.js +916 -0
  22. package/src/plugins/vm/encoding.js +252 -0
  23. package/src/plugins/vm/index.js +366 -0
  24. package/src/plugins/vm/mapping.js +24 -0
  25. package/src/plugins/vm/normalize.js +692 -0
  26. package/src/plugins/vm/runtime.js +1145 -0
  27. package/src/plugins/vm.js +1 -0
  28. package/src/utils/names.js +55 -0
  29. package/src/utils/reserved.js +57 -0
  30. package/src/utils/rng.js +55 -0
  31. package/src/utils/stream.js +97 -0
  32. package/src/utils/string.js +13 -0
  33. package/test/bench-runner.js +78 -0
  34. package/test/benchmark-source.js +35 -0
  35. package/test/benchmark-vm.js +160 -0
  36. package/test/dist/bench.obf.js +1 -0
  37. package/test/dist/bench.original.js +35 -0
  38. package/test/dist/bench.vm.js +1 -0
  39. package/test/dist/sample-input.obf.js +1 -0
  40. package/test/dist/sample-input.vm.js +1 -0
  41. package/test/generate-obf.js +38 -0
  42. package/test/obf-smoke.js +129 -0
  43. package/test/sample-input.js +23 -0
@@ -0,0 +1,252 @@
1
+ const crypto = require("crypto");
2
+ const { concatKeys, streamXor } = require("../../utils/stream");
3
+
4
+ function xorBytes(bytes, maskBytes) {
5
+ const mask = Uint8Array.from(maskBytes);
6
+ const out = Uint8Array.from(bytes);
7
+ for (let i = 0; i < out.length; i += 1) {
8
+ out[i] = out[i] ^ mask[i % mask.length];
9
+ }
10
+ return out;
11
+ }
12
+
13
+ function encodeBytecode(code, rng) {
14
+ const keyLength = rng.int(5, 12);
15
+ const keyBytes = Array.from({ length: keyLength }, () => rng.int(0, 255));
16
+ const bytes = new Uint8Array(code.length * 4);
17
+ for (let i = 0; i < code.length; i += 1) {
18
+ const value = code[i] >>> 0;
19
+ const offset = i * 4;
20
+ bytes[offset] = value & 255;
21
+ bytes[offset + 1] = (value >>> 8) & 255;
22
+ bytes[offset + 2] = (value >>> 16) & 255;
23
+ bytes[offset + 3] = (value >>> 24) & 255;
24
+ }
25
+ const encrypted = streamXor(bytes, keyBytes);
26
+ const encoded = encodeBytes(encrypted, rng);
27
+ const parts = splitPayload(encoded.encoded, rng);
28
+ const order = Array.from({ length: parts.length }, (_, i) => i);
29
+ rng.shuffle(order);
30
+ const shuffled = order.map((idx) => parts[idx]);
31
+ const alphabetInfo = splitSecret(Buffer.from(encoded.alphabet, "utf8"), rng);
32
+ const byteLength = encoded.length >>> 0;
33
+ const paramBytes = Uint8Array.from([
34
+ encoded.xor & 255,
35
+ encoded.rot & 255,
36
+ byteLength & 255,
37
+ (byteLength >>> 8) & 255,
38
+ (byteLength >>> 16) & 255,
39
+ (byteLength >>> 24) & 255,
40
+ 0,
41
+ 0,
42
+ ]);
43
+ const paramInfo = splitSecret(paramBytes, rng);
44
+ const keyInfo = splitSecret(keyBytes, rng);
45
+ return {
46
+ parts: shuffled,
47
+ order,
48
+ alphabetMasked: alphabetInfo.masked,
49
+ alphabetMask: alphabetInfo.mask,
50
+ alphabetOrder: alphabetInfo.order,
51
+ alphabetOrderMask: alphabetInfo.orderMask,
52
+ paramMasked: paramInfo.masked,
53
+ paramMask: paramInfo.mask,
54
+ paramOrder: paramInfo.order,
55
+ paramOrderMask: paramInfo.orderMask,
56
+ keyMasked: keyInfo.masked,
57
+ keyMask: keyInfo.mask,
58
+ keyOrder: keyInfo.order,
59
+ keyOrderMask: keyInfo.orderMask,
60
+ };
61
+ }
62
+
63
+ function encodeConstPool(consts, rng) {
64
+ const keyLength = rng.int(5, 12);
65
+ const keyBytes = Array.from({ length: keyLength }, () => rng.int(0, 255));
66
+ const maskLength = rng.int(4, 10);
67
+ const maskBytes = Array.from({ length: maskLength }, () => rng.int(0, 255));
68
+ const entries = consts.map((value) => {
69
+ if (value === undefined) return ["u", 0];
70
+ if (value === null) return ["l", 0];
71
+ if (typeof value === "boolean") return ["b", value ? 1 : 0];
72
+ if (typeof value === "number") {
73
+ const num =
74
+ Number.isNaN(value) ? "NaN" : Object.is(value, -0) ? "-0" : String(value);
75
+ return ["n", num];
76
+ }
77
+ if (typeof value === "string") return ["s", value];
78
+ return ["u", 0];
79
+ });
80
+ const json = JSON.stringify(entries);
81
+ const bytes = Buffer.from(json, "utf8");
82
+ const encrypted = streamXor(bytes, keyBytes);
83
+ const masked = xorBytes(encrypted, maskBytes);
84
+ const encoded = encodeBytes(masked, rng);
85
+ const parts = splitPayload(encoded.encoded, rng);
86
+ const order = Array.from({ length: parts.length }, (_, i) => i);
87
+ rng.shuffle(order);
88
+ const shuffled = order.map((idx) => parts[idx]);
89
+ const alphabetInfo = splitSecret(Buffer.from(encoded.alphabet, "utf8"), rng);
90
+ const byteLength = encoded.length >>> 0;
91
+ const paramBytes = Uint8Array.from([
92
+ encoded.xor & 255,
93
+ encoded.rot & 255,
94
+ byteLength & 255,
95
+ (byteLength >>> 8) & 255,
96
+ (byteLength >>> 16) & 255,
97
+ (byteLength >>> 24) & 255,
98
+ 0,
99
+ 0,
100
+ ]);
101
+ const paramInfo = splitSecret(paramBytes, rng);
102
+ const keyInfo = splitSecret(keyBytes, rng);
103
+ const maskInfo = splitSecret(maskBytes, rng);
104
+ return {
105
+ parts: shuffled,
106
+ order,
107
+ alphabetMasked: alphabetInfo.masked,
108
+ alphabetMask: alphabetInfo.mask,
109
+ alphabetOrder: alphabetInfo.order,
110
+ alphabetOrderMask: alphabetInfo.orderMask,
111
+ paramMasked: paramInfo.masked,
112
+ paramMask: paramInfo.mask,
113
+ paramOrder: paramInfo.order,
114
+ paramOrderMask: paramInfo.orderMask,
115
+ keyMasked: keyInfo.masked,
116
+ keyMask: keyInfo.mask,
117
+ keyOrder: keyInfo.order,
118
+ keyOrderMask: keyInfo.orderMask,
119
+ maskMasked: maskInfo.masked,
120
+ maskMask: maskInfo.mask,
121
+ maskOrder: maskInfo.order,
122
+ maskOrderMask: maskInfo.orderMask,
123
+ };
124
+ }
125
+
126
+ function splitPayload(payload, rng) {
127
+ const parts = [];
128
+ let index = 0;
129
+ while (index < payload.length) {
130
+ const size = rng.int(12, 42);
131
+ parts.push(payload.slice(index, index + size));
132
+ index += size;
133
+ }
134
+ return parts.length ? parts : [payload];
135
+ }
136
+
137
+ function buildAlphabet(rng) {
138
+ const pool =
139
+ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
140
+ const chars = pool.split("");
141
+ rng.shuffle(chars);
142
+ return chars.join("");
143
+ }
144
+
145
+ function encodeBytes(bytes, rng) {
146
+ const alphabet = buildAlphabet(rng);
147
+ const xor = rng.int(1, 63);
148
+ const rot = rng.int(0, 63);
149
+ let out = "";
150
+ for (let i = 0; i < bytes.length; i += 3) {
151
+ const b0 = bytes[i];
152
+ const b1 = i + 1 < bytes.length ? bytes[i + 1] : 0;
153
+ const b2 = i + 2 < bytes.length ? bytes[i + 2] : 0;
154
+ const triple = (b0 << 16) | (b1 << 8) | b2;
155
+ for (let j = 0; j < 4; j += 1) {
156
+ const sextet = (triple >> (18 - j * 6)) & 63;
157
+ const mixed = ((sextet + rot) & 63) ^ xor;
158
+ out += alphabet[mixed];
159
+ }
160
+ }
161
+ return {
162
+ alphabet,
163
+ xor,
164
+ rot,
165
+ encoded: out,
166
+ length: bytes.length,
167
+ };
168
+ }
169
+
170
+ function splitSecret(bytes, rng) {
171
+ const order = Array.from({ length: bytes.length }, (_, i) => i);
172
+ rng.shuffle(order);
173
+ const masked = [];
174
+ const mask = [];
175
+ for (let i = 0; i < order.length; i += 1) {
176
+ const value = bytes[order[i]];
177
+ const m = rng.int(0, 255);
178
+ masked.push(value ^ m);
179
+ mask.push(m);
180
+ }
181
+ const orderMask = rng.int(1, 255);
182
+ const orderEnc = order.map((idx) => idx ^ orderMask);
183
+ return {
184
+ masked,
185
+ mask,
186
+ order: orderEnc,
187
+ orderMask,
188
+ };
189
+ }
190
+
191
+ function encodeOpcodeTable(opcodeDecode, rng) {
192
+ const length = opcodeDecode.length;
193
+ const keyBytes = crypto.randomBytes(12);
194
+ const ivBytes = crypto.randomBytes(8);
195
+ const tag = crypto.randomBytes(10);
196
+ const fullKey = concatKeys(keyBytes, ivBytes, tag);
197
+ const input = Buffer.from(Uint8Array.from(opcodeDecode));
198
+ const ciphertext = streamXor(input, fullKey);
199
+ const encoded = encodeBytes(ciphertext, rng);
200
+ const parts = splitPayload(encoded.encoded, rng);
201
+ const order = Array.from({ length: parts.length }, (_, i) => i);
202
+ rng.shuffle(order);
203
+ const shuffled = order.map((idx) => parts[idx]);
204
+ const keyInfo = splitSecret(keyBytes, rng);
205
+ const ivInfo = splitSecret(ivBytes, rng);
206
+ const tagInfo = splitSecret(tag, rng);
207
+ const alphabetInfo = splitSecret(Buffer.from(encoded.alphabet, "utf8"), rng);
208
+ const cipherLength = encoded.length >>> 0;
209
+ const opsLength = length >>> 0;
210
+ const paramBytes = Uint8Array.from([
211
+ encoded.xor & 255,
212
+ encoded.rot & 255,
213
+ cipherLength & 255,
214
+ (cipherLength >>> 8) & 255,
215
+ (cipherLength >>> 16) & 255,
216
+ (cipherLength >>> 24) & 255,
217
+ opsLength & 255,
218
+ (opsLength >>> 8) & 255,
219
+ ]);
220
+ const paramInfo = splitSecret(paramBytes, rng);
221
+ return {
222
+ parts: shuffled,
223
+ order,
224
+ alphabetMasked: alphabetInfo.masked,
225
+ alphabetMask: alphabetInfo.mask,
226
+ alphabetOrder: alphabetInfo.order,
227
+ alphabetOrderMask: alphabetInfo.orderMask,
228
+ paramMasked: paramInfo.masked,
229
+ paramMask: paramInfo.mask,
230
+ paramOrder: paramInfo.order,
231
+ paramOrderMask: paramInfo.orderMask,
232
+ keyMasked: keyInfo.masked,
233
+ keyMask: keyInfo.mask,
234
+ keyOrder: keyInfo.order,
235
+ keyOrderMask: keyInfo.orderMask,
236
+ ivMasked: ivInfo.masked,
237
+ ivMask: ivInfo.mask,
238
+ ivOrder: ivInfo.order,
239
+ ivOrderMask: ivInfo.orderMask,
240
+ tagMasked: tagInfo.masked,
241
+ tagMask: tagInfo.mask,
242
+ tagOrder: tagInfo.order,
243
+ tagOrderMask: tagInfo.orderMask,
244
+ };
245
+ }
246
+
247
+ module.exports = {
248
+ encodeBytecode,
249
+ encodeConstPool,
250
+ encodeOpcodeTable,
251
+ splitPayload,
252
+ };
@@ -0,0 +1,366 @@
1
+ const { OPCODES } = require("./constants");
2
+ const { buildOpcodeMapping } = require("./mapping");
3
+ const {
4
+ encodeBytecode,
5
+ encodeConstPool,
6
+ encodeOpcodeTable,
7
+ } = require("./encoding");
8
+ const { buildVmRuntime } = require("./runtime");
9
+ const { VmCompiler } = require("./compiler");
10
+ const { makeLiteral } = require("./ast-utils");
11
+ const {
12
+ canVirtualize,
13
+ collectLocals,
14
+ normalizeFunction,
15
+ rewriteClosureRefs,
16
+ } = require("./normalize");
17
+ const { compileStatement } = require("./emit");
18
+
19
+ function createMiniVmInfo(rng) {
20
+ const pool = Array.from({ length: 256 }, (_, i) => i);
21
+ rng.shuffle(pool);
22
+ const mask = rng.int(1, 255);
23
+ const ops = pool.slice(0, 4).map((value) => value ^ mask);
24
+ return { mask, ops };
25
+ }
26
+
27
+ function buildMiniVmProgram(ops, blocks) {
28
+ const [opPush, opRebuild, opToStr, opStore] = ops;
29
+ const program = [];
30
+ for (const block of blocks) {
31
+ for (const idx of block.pool) {
32
+ program.push(opPush, idx);
33
+ }
34
+ program.push(opRebuild);
35
+ if (block.toString === true) {
36
+ program.push(opToStr);
37
+ }
38
+ program.push(opStore, block.store);
39
+ }
40
+ return program;
41
+ }
42
+
43
+ function shouldVirtualize(fnPath, options) {
44
+ if (options.all) {
45
+ return true;
46
+ }
47
+ const name = fnPath.node.id ? fnPath.node.id.name : null;
48
+ if (name && options.include && options.include.includes(name)) {
49
+ return true;
50
+ }
51
+ const comments = fnPath.node.leadingComments || [];
52
+ return comments.some((c) => c.value.includes("@vm"));
53
+ }
54
+
55
+ function applyVmToFunction(fnPath, ctx, runtimeIds) {
56
+ if (!canVirtualize(fnPath, ctx)) {
57
+ return false;
58
+ }
59
+ if (!normalizeFunction(fnPath, ctx)) {
60
+ return false;
61
+ }
62
+
63
+ const envId = ctx.t.identifier(ctx.nameGen.next());
64
+ const envThisKey = `__vm_this_${ctx.rng.int(1, 1e9)}`;
65
+ const envArgsKey = `__vm_args_${ctx.rng.int(1, 1e9)}`;
66
+ const envNewTargetKey = `__vm_new_target_${ctx.rng.int(1, 1e9)}`;
67
+
68
+ rewriteClosureRefs(fnPath, ctx, envId, envThisKey, envArgsKey);
69
+
70
+ const locals = collectLocals(fnPath);
71
+ const opcodeMapping = buildOpcodeMapping(ctx);
72
+ const compiler = new VmCompiler(ctx, locals, opcodeMapping);
73
+ const state = {
74
+ ctx,
75
+ locals,
76
+ breakLabel: null,
77
+ continueLabel: null,
78
+ isAsync: fnPath.node.async,
79
+ newTargetKey: envNewTargetKey,
80
+ withStack: [],
81
+ };
82
+
83
+ for (const stmt of fnPath.node.body.body) {
84
+ if (!compileStatement(stmt, compiler, state)) {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ compiler.emit(OPCODES.PUSH_CONST, compiler.addConst(undefined));
90
+ compiler.emit(OPCODES.RETURN);
91
+
92
+ const codeId = ctx.t.identifier(ctx.nameGen.next());
93
+ const constId = ctx.t.identifier(ctx.nameGen.next());
94
+ const useBytecodeEncrypt = ctx.options.vm.bytecodeEncrypt !== false;
95
+ const useConstEncrypt = ctx.options.vm.constsEncrypt !== false;
96
+ const makeNumArray = (values) =>
97
+ ctx.t.arrayExpression(values.map((value) => ctx.t.numericLiteral(value)));
98
+ const makeStrArray = (values) =>
99
+ ctx.t.arrayExpression(values.map((value) => ctx.t.stringLiteral(value)));
100
+ const makePool = (values) =>
101
+ ctx.t.arrayExpression(
102
+ values.map((value) =>
103
+ Array.isArray(value)
104
+ ? makeNumArray(value)
105
+ : ctx.t.numericLiteral(value)
106
+ )
107
+ );
108
+ let codeInit = ctx.t.arrayExpression(
109
+ compiler.code.map((n) => ctx.t.numericLiteral(n))
110
+ );
111
+ if (useBytecodeEncrypt && runtimeIds.bytecodeDecodeName) {
112
+ const meta = encodeBytecode(compiler.code, ctx.rng);
113
+ const partsExpr = makeStrArray(meta.parts);
114
+ const orderExpr = makeNumArray(meta.order);
115
+ const poolExpr = makePool([
116
+ meta.alphabetMasked,
117
+ meta.alphabetMask,
118
+ meta.alphabetOrder,
119
+ meta.alphabetOrderMask,
120
+ meta.paramMasked,
121
+ meta.paramMask,
122
+ meta.paramOrder,
123
+ meta.paramOrderMask,
124
+ meta.keyMasked,
125
+ meta.keyMask,
126
+ meta.keyOrder,
127
+ meta.keyOrderMask,
128
+ ]);
129
+ codeInit = ctx.t.callExpression(
130
+ ctx.t.identifier(runtimeIds.bytecodeDecodeName),
131
+ [partsExpr, orderExpr, poolExpr]
132
+ );
133
+ }
134
+ let constInit = ctx.t.arrayExpression(
135
+ compiler.consts.map((value) => makeLiteral(ctx.t, value))
136
+ );
137
+ if (useConstEncrypt && runtimeIds.constDecodeName) {
138
+ const meta = encodeConstPool(compiler.consts, ctx.rng);
139
+ const payloadExpr = makeStrArray(meta.parts);
140
+ const orderExpr = makeNumArray(meta.order);
141
+ const poolExpr = makePool([
142
+ meta.alphabetMasked,
143
+ meta.alphabetMask,
144
+ meta.alphabetOrder,
145
+ meta.alphabetOrderMask,
146
+ meta.paramMasked,
147
+ meta.paramMask,
148
+ meta.paramOrder,
149
+ meta.paramOrderMask,
150
+ meta.keyMasked,
151
+ meta.keyMask,
152
+ meta.keyOrder,
153
+ meta.keyOrderMask,
154
+ meta.maskMasked,
155
+ meta.maskMask,
156
+ meta.maskOrder,
157
+ meta.maskOrderMask,
158
+ ]);
159
+ constInit = ctx.t.callExpression(
160
+ ctx.t.identifier(runtimeIds.constDecodeName),
161
+ [payloadExpr, orderExpr, poolExpr]
162
+ );
163
+ }
164
+
165
+ const envProperties = [];
166
+ for (const name of locals) {
167
+ const key = ctx.t.isValidIdentifier(name)
168
+ ? ctx.t.identifier(name)
169
+ : ctx.t.stringLiteral(name);
170
+ const value = fnPath.node.params.some(
171
+ (p) => p.type === "Identifier" && p.name === name
172
+ )
173
+ ? ctx.t.identifier(name)
174
+ : ctx.t.identifier("undefined");
175
+ envProperties.push(ctx.t.objectProperty(key, value));
176
+ }
177
+ envProperties.push(
178
+ ctx.t.objectProperty(
179
+ ctx.t.stringLiteral(envThisKey),
180
+ ctx.t.thisExpression()
181
+ )
182
+ );
183
+ envProperties.push(
184
+ ctx.t.objectProperty(
185
+ ctx.t.stringLiteral(envArgsKey),
186
+ ctx.t.identifier("arguments")
187
+ )
188
+ );
189
+ envProperties.push(
190
+ ctx.t.objectProperty(
191
+ ctx.t.stringLiteral(envNewTargetKey),
192
+ ctx.t.metaProperty(ctx.t.identifier("new"), ctx.t.identifier("target"))
193
+ )
194
+ );
195
+
196
+ const execId = fnPath.node.async
197
+ ? runtimeIds.execAsyncName
198
+ : runtimeIds.execName;
199
+
200
+ const newBody = ctx.t.blockStatement([
201
+ ctx.t.variableDeclaration("const", [
202
+ ctx.t.variableDeclarator(codeId, codeInit),
203
+ ]),
204
+ ctx.t.variableDeclaration("const", [
205
+ ctx.t.variableDeclarator(constId, constInit),
206
+ ]),
207
+ ctx.t.variableDeclaration("const", [
208
+ ctx.t.variableDeclarator(envId, ctx.t.objectExpression(envProperties)),
209
+ ]),
210
+ ctx.t.returnStatement(
211
+ ctx.t.callExpression(ctx.t.identifier(execId), [
212
+ codeId,
213
+ constId,
214
+ envId,
215
+ ctx.t.thisExpression(),
216
+ ])
217
+ ),
218
+ ]);
219
+
220
+ fnPath.get("body").replaceWith(newBody);
221
+ return true;
222
+ }
223
+
224
+ function ensureRuntime(programPath, ctx) {
225
+ if (ctx.state.vmRuntime) {
226
+ return ctx.state.vmRuntime;
227
+ }
228
+ const execName = ctx.nameGen.next();
229
+ const execAsyncName = ctx.nameGen.next();
230
+ const opsName = ctx.nameGen.next();
231
+ const opsLookupName = ctx.nameGen.next();
232
+ const globalsName = ctx.nameGen.next();
233
+ const makeFuncName = ctx.nameGen.next();
234
+ const maskName = ctx.nameGen.next();
235
+ const opcodeB64Name = ctx.nameGen.next();
236
+ const miniVmName = ctx.nameGen.next();
237
+ const bytecodeEnabled = ctx.options.vm.bytecodeEncrypt !== false;
238
+ const bytecodeDecodeName = bytecodeEnabled ? ctx.nameGen.next() : null;
239
+ const bytecodeCacheName = bytecodeEnabled ? ctx.nameGen.next() : null;
240
+ const bytecodeB64Name = bytecodeEnabled ? ctx.nameGen.next() : null;
241
+ const bytecodeRc4Name = bytecodeEnabled ? ctx.nameGen.next() : null;
242
+ const constsEnabled = ctx.options.vm.constsEncrypt !== false;
243
+ const constDecodeName = constsEnabled ? ctx.nameGen.next() : null;
244
+ const constCacheName = constsEnabled ? ctx.nameGen.next() : null;
245
+ const constB64Name = constsEnabled ? ctx.nameGen.next() : null;
246
+ const constRc4Name = constsEnabled ? ctx.nameGen.next() : null;
247
+ const constUtf8Name = constsEnabled ? ctx.nameGen.next() : null;
248
+ const opcodeMapping = buildOpcodeMapping(ctx);
249
+ const opcodeInfo = encodeOpcodeTable(opcodeMapping.decode, ctx.rng);
250
+ const miniVmInfo = createMiniVmInfo(ctx.rng);
251
+ const miniVmPrograms = {
252
+ opcode: buildMiniVmProgram(miniVmInfo.ops, [
253
+ { pool: [0, 1, 2, 3], toString: true, store: 0 },
254
+ { pool: [4, 5, 6, 7], store: 1 },
255
+ { pool: [8, 9, 10, 11], store: 2 },
256
+ { pool: [12, 13, 14, 15], store: 3 },
257
+ { pool: [16, 17, 18, 19], store: 4 },
258
+ ]),
259
+ bytecode: buildMiniVmProgram(miniVmInfo.ops, [
260
+ { pool: [0, 1, 2, 3], toString: true, store: 0 },
261
+ { pool: [4, 5, 6, 7], store: 1 },
262
+ { pool: [8, 9, 10, 11], store: 2 },
263
+ ]),
264
+ consts: buildMiniVmProgram(miniVmInfo.ops, [
265
+ { pool: [0, 1, 2, 3], toString: true, store: 0 },
266
+ { pool: [4, 5, 6, 7], store: 1 },
267
+ { pool: [8, 9, 10, 11], store: 2 },
268
+ { pool: [12, 13, 14, 15], store: 3 },
269
+ ]),
270
+ };
271
+ const runtime = buildVmRuntime(
272
+ {
273
+ execName,
274
+ execAsyncName,
275
+ opsName,
276
+ opsLookupName,
277
+ globalsName,
278
+ makeFuncName,
279
+ maskName,
280
+ opcodeB64Name,
281
+ miniVmName,
282
+ bytecodeDecodeName,
283
+ bytecodeCacheName,
284
+ bytecodeB64Name,
285
+ bytecodeRc4Name,
286
+ constDecodeName,
287
+ constCacheName,
288
+ constB64Name,
289
+ constRc4Name,
290
+ constUtf8Name,
291
+ },
292
+ opcodeInfo,
293
+ opcodeMapping.mask,
294
+ miniVmInfo,
295
+ miniVmPrograms
296
+ );
297
+ const body = programPath.node.body;
298
+ let index = 0;
299
+ while (index < body.length) {
300
+ const stmt = body[index];
301
+ if (stmt.type === "ExpressionStatement" && stmt.directive) {
302
+ index += 1;
303
+ continue;
304
+ }
305
+ break;
306
+ }
307
+ body.splice(index, 0, ...runtime);
308
+ ctx.state.vmRuntime = {
309
+ execName,
310
+ execAsyncName,
311
+ opsName,
312
+ opsLookupName,
313
+ globalsName,
314
+ makeFuncName,
315
+ maskName,
316
+ opcodeB64Name,
317
+ miniVmName,
318
+ bytecodeDecodeName,
319
+ bytecodeCacheName,
320
+ bytecodeB64Name,
321
+ bytecodeRc4Name,
322
+ constDecodeName,
323
+ constCacheName,
324
+ constB64Name,
325
+ constRc4Name,
326
+ constUtf8Name,
327
+ };
328
+ ctx.state.vmRuntimeMeta = {
329
+ programPath,
330
+ index,
331
+ size: runtime.length,
332
+ };
333
+ return ctx.state.vmRuntime;
334
+ }
335
+
336
+ function vmPlugin(ast, ctx) {
337
+ const { traverse, options } = ctx;
338
+ let applied = false;
339
+ traverse(ast, {
340
+ Program(path) {
341
+ let runtimeIds = null;
342
+ path.traverse({
343
+ Function(fnPath) {
344
+ if (!shouldVirtualize(fnPath, options.vm)) {
345
+ return;
346
+ }
347
+ if (!runtimeIds) {
348
+ runtimeIds = ensureRuntime(path, ctx);
349
+ }
350
+ const didApply = applyVmToFunction(fnPath, ctx, runtimeIds);
351
+ if (didApply) {
352
+ applied = true;
353
+ }
354
+ },
355
+ });
356
+ if (!applied && ctx.state.vmRuntimeMeta) {
357
+ const { programPath, index, size } = ctx.state.vmRuntimeMeta;
358
+ programPath.node.body.splice(index, size);
359
+ ctx.state.vmRuntime = null;
360
+ ctx.state.vmRuntimeMeta = null;
361
+ }
362
+ },
363
+ });
364
+ }
365
+
366
+ module.exports = vmPlugin;
@@ -0,0 +1,24 @@
1
+ const { OPCODE_NAMES } = require("./constants");
2
+
3
+ function buildOpcodeMapping(ctx) {
4
+ if (ctx.state.vmOpcodeMapping) {
5
+ return ctx.state.vmOpcodeMapping;
6
+ }
7
+ const count = OPCODE_NAMES.length;
8
+ const base = Array.from({ length: count }, (_, i) => i);
9
+ const shuffled = base.slice();
10
+ if (ctx.options.vm.opcodeShuffle) {
11
+ ctx.rng.shuffle(shuffled);
12
+ }
13
+ const encode = new Array(count);
14
+ const decode = new Array(count);
15
+ for (let i = 0; i < count; i += 1) {
16
+ encode[i] = shuffled[i];
17
+ decode[shuffled[i]] = i;
18
+ }
19
+ const mask = ctx.rng.int(1, 255);
20
+ ctx.state.vmOpcodeMapping = { encode, decode, mask };
21
+ return ctx.state.vmOpcodeMapping;
22
+ }
23
+
24
+ module.exports = { buildOpcodeMapping };