@dacely/toildefender 0.1.0 → 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.
- package/LICENSE +661 -661
- package/NOTICE.md +16 -16
- package/README.md +380 -380
- package/cli.js +168 -168
- package/defendjs.js +6 -6
- package/docs/all-modes-output.demo.js +673 -673
- package/estest.js +49 -49
- package/esutils.js +107 -107
- package/logger.js +28 -28
- package/obfuscator.js +534 -534
- package/package.json +108 -108
- package/processors/deadCode.js +62 -62
- package/processors/flattener.js +808 -808
- package/processors/health.js +55 -55
- package/processors/identifiers.js +256 -256
- package/processors/literalObfuscator.js +40 -40
- package/processors/literals.js +233 -233
- package/processors/methods.js +332 -332
- package/processors/modules.js +231 -231
- package/processors/normalizer.js +490 -490
- package/processors/numericVm.js +950 -950
- package/processors/postprocessing.js +69 -69
- package/processors/preprocessing.js +248 -248
- package/processors/scopes.js +177 -177
- package/processors/uglifier.js +25 -25
- package/processors/variables.js +185 -185
- package/toildefender.js +7 -7
- package/traverser.js +115 -115
- package/utils.js +135 -135
package/processors/numericVm.js
CHANGED
|
@@ -1,950 +1,950 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var assert = require("assert");
|
|
4
|
-
var crypto = require("crypto");
|
|
5
|
-
var esprima = require("esprima");
|
|
6
|
-
|
|
7
|
-
var estest = require("../estest");
|
|
8
|
-
var traverser = require("../traverser");
|
|
9
|
-
|
|
10
|
-
var RUNTIME = `
|
|
11
|
-
function veilmark$numericVmString(program, length, salt) {
|
|
12
|
-
var out = "";
|
|
13
|
-
var i = 0;
|
|
14
|
-
while (i < length) {
|
|
15
|
-
var encoded = Number(program % BigInt(65537));
|
|
16
|
-
program = program / BigInt(65537);
|
|
17
|
-
out += String.fromCharCode(encoded ^ ((salt + i * 97) & 65535));
|
|
18
|
-
i += 1;
|
|
19
|
-
}
|
|
20
|
-
return out;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function veilmark$numericVmPow(a, b) {
|
|
24
|
-
if (typeof a === "bigint" && typeof b === "bigint") {
|
|
25
|
-
if (b < BigInt(0)) throw new RangeError("BigInt exponent must be positive");
|
|
26
|
-
var out = BigInt(1);
|
|
27
|
-
var base = a;
|
|
28
|
-
var exp = b;
|
|
29
|
-
while (exp > BigInt(0)) {
|
|
30
|
-
if (exp % BigInt(2) === BigInt(1)) out *= base;
|
|
31
|
-
base *= base;
|
|
32
|
-
exp = exp / BigInt(2);
|
|
33
|
-
}
|
|
34
|
-
return out;
|
|
35
|
-
}
|
|
36
|
-
return Math.pow(a, b);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function veilmark$hashMeshMix(current, value) {
|
|
40
|
-
var h = (current ^ value) >>> 0;
|
|
41
|
-
h = Math.imul(h ^ (h >>> 16), 2246822507) >>> 0;
|
|
42
|
-
h = Math.imul(h ^ (h >>> 13), 3266489909) >>> 0;
|
|
43
|
-
return (h ^ (h >>> 16)) >>> 0;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function veilmark$hashMeshValue(hash, value) {
|
|
47
|
-
if (typeof value === "number") return veilmark$hashMeshMix(hash, value >>> 0);
|
|
48
|
-
if (typeof value === "string") {
|
|
49
|
-
hash = veilmark$hashMeshMix(hash, value.length >>> 0);
|
|
50
|
-
var j = 0;
|
|
51
|
-
while (j < value.length) {
|
|
52
|
-
hash = veilmark$hashMeshMix(hash, value.charCodeAt(j));
|
|
53
|
-
j += 1;
|
|
54
|
-
}
|
|
55
|
-
return hash;
|
|
56
|
-
}
|
|
57
|
-
if (value && typeof value.length === "number") {
|
|
58
|
-
hash = veilmark$hashMeshMix(hash, value.length >>> 0);
|
|
59
|
-
var i = 0;
|
|
60
|
-
while (i < value.length) {
|
|
61
|
-
hash = veilmark$hashMeshValue(hash, value[i]);
|
|
62
|
-
i += 1;
|
|
63
|
-
}
|
|
64
|
-
return hash;
|
|
65
|
-
}
|
|
66
|
-
return veilmark$hashMeshMix(hash, 3735928559);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function veilmark$hashMeshKey(mesh, base, tokenCount, seed, tag, ops) {
|
|
70
|
-
var hash = 2166136261;
|
|
71
|
-
hash = veilmark$hashMeshMix(hash, 1145713480);
|
|
72
|
-
hash = veilmark$hashMeshMix(hash, 1296388936);
|
|
73
|
-
hash = veilmark$hashMeshValue(hash, mesh);
|
|
74
|
-
hash = veilmark$hashMeshMix(hash, base >>> 0);
|
|
75
|
-
hash = veilmark$hashMeshMix(hash, tokenCount >>> 0);
|
|
76
|
-
hash = veilmark$hashMeshMix(hash, seed >>> 0);
|
|
77
|
-
hash = veilmark$hashMeshMix(hash, tag >>> 0);
|
|
78
|
-
hash = veilmark$hashMeshValue(hash, ops);
|
|
79
|
-
return hash >>> 0;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function veilmark$hashMeshStream(key, index, base, salt) {
|
|
83
|
-
var hash = veilmark$hashMeshMix(key >>> 0, 1398035796);
|
|
84
|
-
hash = veilmark$hashMeshMix(hash, salt >>> 0);
|
|
85
|
-
hash = veilmark$hashMeshMix(hash, index >>> 0);
|
|
86
|
-
hash = veilmark$hashMeshMix(hash, Math.imul(index + 1, 2654435761) >>> 0);
|
|
87
|
-
return hash % base;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function veilmark$hashMeshUnlock(program, base, tokenCount, seed, tag, ops, mesh) {
|
|
91
|
-
var key = veilmark$hashMeshKey(mesh, base, tokenCount, seed, tag, ops);
|
|
92
|
-
var salt = mesh[5] >>> 0;
|
|
93
|
-
var baseBig = BigInt(base);
|
|
94
|
-
var out = BigInt(0);
|
|
95
|
-
var pow = BigInt(1);
|
|
96
|
-
var i = 0;
|
|
97
|
-
while (i < tokenCount) {
|
|
98
|
-
var cipher = Number(program % baseBig);
|
|
99
|
-
program = program / baseBig;
|
|
100
|
-
var plain = (cipher - veilmark$hashMeshStream(key, i, base, salt) + base) % base;
|
|
101
|
-
out += BigInt(plain) * pow;
|
|
102
|
-
pow *= baseBig;
|
|
103
|
-
i += 1;
|
|
104
|
-
}
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants, argsLike, self, ops, mesh) {
|
|
109
|
-
if (mesh) {
|
|
110
|
-
program = veilmark$hashMeshUnlock(program, base, tokenCount, seed, tag, ops, mesh);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
var tokens = [];
|
|
114
|
-
var state = seed >>> 0;
|
|
115
|
-
var seen = seed >>> 0;
|
|
116
|
-
var baseBig = BigInt(base);
|
|
117
|
-
|
|
118
|
-
function inverse(value, modulo) {
|
|
119
|
-
var t = 0, nt = 1;
|
|
120
|
-
var r = modulo, nr = value % modulo;
|
|
121
|
-
while (nr !== 0) {
|
|
122
|
-
var q = Math.floor(r / nr);
|
|
123
|
-
var ot = t;
|
|
124
|
-
t = nt;
|
|
125
|
-
nt = ot - q * nt;
|
|
126
|
-
var or = r;
|
|
127
|
-
r = nr;
|
|
128
|
-
nr = or - q * nr;
|
|
129
|
-
}
|
|
130
|
-
return t < 0 ? t + modulo : t;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function mix(current, encrypted, index) {
|
|
134
|
-
var mixed = (current ^ (encrypted + 2654435769 + ((current << 6) >>> 0) + (current >>> 2) + index)) >>> 0;
|
|
135
|
-
mixed = Math.imul(mixed ^ (mixed >>> 16), 2246822507) >>> 0;
|
|
136
|
-
return (mixed ^ (mixed >>> 13)) >>> 0;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
var i = 0;
|
|
140
|
-
while (i < tokenCount) {
|
|
141
|
-
var encrypted = Number(program % baseBig);
|
|
142
|
-
program = program / baseBig;
|
|
143
|
-
var mul = 1 + ((state >>> 5) % (base - 1));
|
|
144
|
-
var add = state % base;
|
|
145
|
-
var plain = (((encrypted - add + base) % base) * inverse(mul, base)) % base;
|
|
146
|
-
tokens.push(plain);
|
|
147
|
-
seen = mix(seen, encrypted, i);
|
|
148
|
-
state = mix(state, encrypted, i);
|
|
149
|
-
i += 1;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if ((seen >>> 0) !== (tag >>> 0)) throw new Error("invalid numeric vm program");
|
|
153
|
-
|
|
154
|
-
var stack = [];
|
|
155
|
-
var locals = [];
|
|
156
|
-
var frameArgs = Array.prototype.slice.call(argsLike);
|
|
157
|
-
var ip = 0;
|
|
158
|
-
|
|
159
|
-
function read() {
|
|
160
|
-
return tokens[ip++];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function readUnsigned() {
|
|
164
|
-
var shift = 0;
|
|
165
|
-
var value = 0;
|
|
166
|
-
for (;;) {
|
|
167
|
-
var part = read();
|
|
168
|
-
value += (part & 127) * Math.pow(2, shift);
|
|
169
|
-
if ((part & 128) === 0) return value;
|
|
170
|
-
shift += 7;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function readSigned() {
|
|
175
|
-
var raw = readUnsigned();
|
|
176
|
-
return (raw & 1) === 0 ? raw / 2 : -((raw + 1) / 2);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function popArgs(count) {
|
|
180
|
-
var out = new Array(count);
|
|
181
|
-
var i = count;
|
|
182
|
-
while (i > 0) {
|
|
183
|
-
i -= 1;
|
|
184
|
-
out[i] = stack.pop();
|
|
185
|
-
}
|
|
186
|
-
return out;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
while (true) {
|
|
190
|
-
var op = read();
|
|
191
|
-
if (op === ops[0]) continue;
|
|
192
|
-
if (op === ops[1]) { stack.push(undefined); continue; }
|
|
193
|
-
if (op === ops[2]) { stack.push(null); continue; }
|
|
194
|
-
if (op === ops[3]) { stack.push(true); continue; }
|
|
195
|
-
if (op === ops[4]) { stack.push(false); continue; }
|
|
196
|
-
if (op === ops[5]) { stack.push(readUnsigned()); continue; }
|
|
197
|
-
if (op === ops[6]) { stack.push(constants[readUnsigned()]); continue; }
|
|
198
|
-
if (op === ops[7]) { stack.push(frameArgs[readUnsigned()]); continue; }
|
|
199
|
-
if (op === ops[8]) { stack.push(locals[readUnsigned()]); continue; }
|
|
200
|
-
if (op === ops[9]) { locals[readUnsigned()] = stack.pop(); continue; }
|
|
201
|
-
if (op === ops[10]) { stack.push(stack[stack.length - 1]); continue; }
|
|
202
|
-
if (op === ops[11]) { stack.pop(); continue; }
|
|
203
|
-
if (op === ops[12]) { var addB = stack.pop(); var addA = stack.pop(); stack.push(addA + addB); continue; }
|
|
204
|
-
if (op === ops[13]) { var subB = stack.pop(); var subA = stack.pop(); stack.push(subA - subB); continue; }
|
|
205
|
-
if (op === ops[14]) { var mulB = stack.pop(); var mulA = stack.pop(); stack.push(mulA * mulB); continue; }
|
|
206
|
-
if (op === ops[15]) { var divB = stack.pop(); var divA = stack.pop(); stack.push(divA / divB); continue; }
|
|
207
|
-
if (op === ops[16]) { var modB = stack.pop(); var modA = stack.pop(); stack.push(modA % modB); continue; }
|
|
208
|
-
if (op === ops[17]) { var powB = stack.pop(); var powA = stack.pop(); stack.push(veilmark$numericVmPow(powA, powB)); continue; }
|
|
209
|
-
if (op === ops[18]) { stack.push(-stack.pop()); continue; }
|
|
210
|
-
if (op === ops[19]) { stack.push(!stack.pop()); continue; }
|
|
211
|
-
if (op === ops[20]) { stack.push(~stack.pop()); continue; }
|
|
212
|
-
if (op === ops[21]) { var eqB = stack.pop(); var eqA = stack.pop(); stack.push(eqA == eqB); continue; }
|
|
213
|
-
if (op === ops[22]) { var neqB = stack.pop(); var neqA = stack.pop(); stack.push(neqA != neqB); continue; }
|
|
214
|
-
if (op === ops[23]) { var seqB = stack.pop(); var seqA = stack.pop(); stack.push(seqA === seqB); continue; }
|
|
215
|
-
if (op === ops[24]) { var sneB = stack.pop(); var sneA = stack.pop(); stack.push(sneA !== sneB); continue; }
|
|
216
|
-
if (op === ops[25]) { var ltB = stack.pop(); var ltA = stack.pop(); stack.push(ltA < ltB); continue; }
|
|
217
|
-
if (op === ops[26]) { var lteB = stack.pop(); var lteA = stack.pop(); stack.push(lteA <= lteB); continue; }
|
|
218
|
-
if (op === ops[27]) { var gtB = stack.pop(); var gtA = stack.pop(); stack.push(gtA > gtB); continue; }
|
|
219
|
-
if (op === ops[28]) { var gteB = stack.pop(); var gteA = stack.pop(); stack.push(gteA >= gteB); continue; }
|
|
220
|
-
if (op === ops[29]) { var jmp = readSigned(); ip += jmp; continue; }
|
|
221
|
-
if (op === ops[30]) { var jf = readSigned(); if (!stack.pop()) ip += jf; continue; }
|
|
222
|
-
if (op === ops[31]) { var jt = readSigned(); if (stack.pop()) ip += jt; continue; }
|
|
223
|
-
if (op === ops[32]) { readUnsigned(); var argc = readUnsigned(); var ca = popArgs(argc); var fn = stack.pop(); stack.push(fn.apply(undefined, ca)); continue; }
|
|
224
|
-
if (op === ops[33]) { readUnsigned(); var largc = readUnsigned(); var la = popArgs(largc); var lfn = constants[readUnsigned()]; stack.push(lfn.apply(undefined, la)); continue; }
|
|
225
|
-
if (op === ops[34]) { var gpKey = stack.pop(); var gpObj = stack.pop(); stack.push(gpObj[gpKey]); continue; }
|
|
226
|
-
if (op === ops[35]) { var spValue = stack.pop(); var spKey = stack.pop(); var spObj = stack.pop(); spObj[spKey] = spValue; stack.push(spValue); continue; }
|
|
227
|
-
if (op === ops[36]) { var ac = readUnsigned(); var arr = new Array(ac); var ai = ac; while (ai > 0) { ai -= 1; arr[ai] = stack.pop(); } stack.push(arr); continue; }
|
|
228
|
-
if (op === ops[37]) { var oc = readUnsigned(); var pairs = new Array(oc); var oi = oc; while (oi > 0) { oi -= 1; var ov = stack.pop(); var ok = stack.pop(); pairs[oi] = [ok, ov]; } var obj = {}; var pi = 0; while (pi < oc) { obj[pairs[pi][0]] = pairs[pi][1]; pi += 1; } stack.push(obj); continue; }
|
|
229
|
-
if (op === ops[38]) return stack.pop();
|
|
230
|
-
if (op === ops[39]) throw stack.pop();
|
|
231
|
-
if (op === ops[40]) { stack.push(self); continue; }
|
|
232
|
-
if (op === ops[41]) { stack.push(argsLike); continue; }
|
|
233
|
-
if (op === ops[42]) { stack.push(typeof stack.pop()); continue; }
|
|
234
|
-
if (op === ops[43]) { var mc = readUnsigned(); var ma = popArgs(mc); var mk = stack.pop(); var mo = stack.pop(); stack.push(mo[mk].apply(mo, ma)); continue; }
|
|
235
|
-
throw new Error("invalid virtual opcode");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
`;
|
|
239
|
-
|
|
240
|
-
var OP_NAMES = [
|
|
241
|
-
"NOP", "PUSH_UNDEFINED", "PUSH_NULL", "PUSH_TRUE", "PUSH_FALSE", "PUSH_SMALL",
|
|
242
|
-
"PUSH_CONST", "LOAD_ARG", "LOAD_LOCAL", "STORE_LOCAL", "DUP", "POP", "ADD",
|
|
243
|
-
"SUB", "MUL", "DIV", "MOD", "POW", "NEG", "NOT", "BIT_NOT", "EQ", "NEQ",
|
|
244
|
-
"STRICT_EQ", "STRICT_NEQ", "LT", "LTE", "GT", "GTE", "JMP", "JMP_FALSE",
|
|
245
|
-
"JMP_TRUE", "CALL_EXT", "CALL_LOCAL", "GET_PROP", "SET_PROP", "MAKE_ARRAY",
|
|
246
|
-
"MAKE_OBJECT", "RETURN", "THROW", "PUSH_THIS", "PUSH_ARGUMENTS", "TYPEOF",
|
|
247
|
-
"CALL_METHOD"
|
|
248
|
-
];
|
|
249
|
-
|
|
250
|
-
var BASES = [257, 263, 269, 521, 1031, 4099, 65537];
|
|
251
|
-
var SMALL_LIMIT = 128;
|
|
252
|
-
|
|
253
|
-
function literal(value) { return { type: "Literal", value: value }; }
|
|
254
|
-
function identifier(name) { return { type: "Identifier", name: name }; }
|
|
255
|
-
function call(callee, args) { return { type: "CallExpression", callee: callee, arguments: args }; }
|
|
256
|
-
function binary(operator, left, right) { return { type: "BinaryExpression", operator: operator, left: left, right: right }; }
|
|
257
|
-
function unary(operator, argument) { return { type: "UnaryExpression", operator: operator, prefix: true, argument: argument }; }
|
|
258
|
-
function member(object, property) { return { type: "MemberExpression", object: object, property: property, computed: true }; }
|
|
259
|
-
function arrayExpression(values) { return { type: "ArrayExpression", elements: values }; }
|
|
260
|
-
function returnStatement(argument) { return { type: "ReturnStatement", argument: argument }; }
|
|
261
|
-
function functionName(node) { return node.id && node.id.name ? node.id.name : ""; }
|
|
262
|
-
|
|
263
|
-
function hashSeed(seed) {
|
|
264
|
-
return crypto.createHash("sha256").update(String(seed)).digest().readUInt32LE(0) || 1;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function makeRng(seed) {
|
|
268
|
-
var state = seed >>> 0;
|
|
269
|
-
return function () {
|
|
270
|
-
state ^= state << 13; state >>>= 0;
|
|
271
|
-
state ^= state >>> 17; state >>>= 0;
|
|
272
|
-
state ^= state << 5; state >>>= 0;
|
|
273
|
-
return state >>> 0;
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function shuffle(values, next) {
|
|
278
|
-
var copy = values.slice();
|
|
279
|
-
for (var i = copy.length - 1; i > 0; i -= 1) {
|
|
280
|
-
var j = next() % (i + 1);
|
|
281
|
-
var tmp = copy[i];
|
|
282
|
-
copy[i] = copy[j];
|
|
283
|
-
copy[j] = tmp;
|
|
284
|
-
}
|
|
285
|
-
return copy;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
function bigintLiteral(value) {
|
|
289
|
-
var bigint = typeof value === "bigint" ? value : BigInt(value);
|
|
290
|
-
var raw = bigint.toString();
|
|
291
|
-
return { type: "Literal", value: bigint, bigint: raw, raw: raw + "n" };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function replaceStaticBigIntCalls(ast) {
|
|
295
|
-
return traverser.traverse(ast, [], function (node) {
|
|
296
|
-
if (node.type === "CallExpression"
|
|
297
|
-
&& node.callee.type === "Identifier"
|
|
298
|
-
&& node.callee.name === "BigInt"
|
|
299
|
-
&& node.arguments.length === 1
|
|
300
|
-
&& node.arguments[0].type === "Literal"
|
|
301
|
-
&& typeof node.arguments[0].value === "number"
|
|
302
|
-
&& Number.isInteger(node.arguments[0].value)
|
|
303
|
-
) {
|
|
304
|
-
return bigintLiteral(node.arguments[0].value);
|
|
305
|
-
}
|
|
306
|
-
return node;
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function bigintExpression(value, next) {
|
|
311
|
-
var radixBits = 26n;
|
|
312
|
-
var radix = 1n << radixBits;
|
|
313
|
-
var chunks = [];
|
|
314
|
-
var work = value < 0n ? -value : value;
|
|
315
|
-
if (work === 0n) chunks.push(0n);
|
|
316
|
-
while (work > 0n) {
|
|
317
|
-
chunks.push(work % radix);
|
|
318
|
-
work = work / radix;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
var expr = bigintLiteral(chunks[chunks.length - 1]);
|
|
322
|
-
for (var i = chunks.length - 2; i >= 0; i -= 1) {
|
|
323
|
-
expr = binary("+", binary("<<", expr, bigintLiteral(radixBits)), bigintLiteral(chunks[i]));
|
|
324
|
-
}
|
|
325
|
-
if (value < 0n) expr = { type: "UnaryExpression", operator: "-", prefix: true, argument: expr };
|
|
326
|
-
|
|
327
|
-
var xorKey = BigInt((next() & 65535) + 1);
|
|
328
|
-
var addKey = BigInt((next() & 65535) + 1);
|
|
329
|
-
return binary("^", binary("-", binary("+", binary("^", expr, bigintLiteral(xorKey)), bigintLiteral(addKey)), bigintLiteral(addKey)), bigintLiteral(xorKey));
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function stringBlob(value, salt) {
|
|
333
|
-
var base = 65537n;
|
|
334
|
-
var pow = 1n;
|
|
335
|
-
var out = 0n;
|
|
336
|
-
for (var i = 0; i < value.length; i += 1) {
|
|
337
|
-
out += BigInt(value.charCodeAt(i) ^ ((salt + i * 97) & 65535)) * pow;
|
|
338
|
-
pow *= base;
|
|
339
|
-
}
|
|
340
|
-
return out;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function encodeUnsigned(value) {
|
|
344
|
-
var out = [];
|
|
345
|
-
var current = value >>> 0;
|
|
346
|
-
do {
|
|
347
|
-
var part = current & 127;
|
|
348
|
-
current = Math.floor(current / 128);
|
|
349
|
-
out.push(current > 0 ? part | 128 : part);
|
|
350
|
-
} while (current > 0);
|
|
351
|
-
return out;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function encodeSigned(value) {
|
|
355
|
-
return encodeUnsigned(value >= 0 ? value * 2 : (-value * 2) - 1);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
function signedLengthFor(target, start, beforeOperand) {
|
|
359
|
-
var len = 1;
|
|
360
|
-
for (;;) {
|
|
361
|
-
var next = encodeSigned(target - (start + beforeOperand + len)).length;
|
|
362
|
-
if (next === len) return len;
|
|
363
|
-
len = next;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function mix(current, encrypted, index) {
|
|
368
|
-
var mixed = (current ^ (encrypted + 2654435769 + ((current << 6) >>> 0) + (current >>> 2) + index)) >>> 0;
|
|
369
|
-
mixed = Math.imul(mixed ^ (mixed >>> 16), 2246822507) >>> 0;
|
|
370
|
-
return (mixed ^ (mixed >>> 13)) >>> 0;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function meshMix(current, value) {
|
|
374
|
-
var h = (current ^ value) >>> 0;
|
|
375
|
-
h = Math.imul(h ^ (h >>> 16), 2246822507) >>> 0;
|
|
376
|
-
h = Math.imul(h ^ (h >>> 13), 3266489909) >>> 0;
|
|
377
|
-
return (h ^ (h >>> 16)) >>> 0;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function meshValue(hash, value) {
|
|
381
|
-
if (typeof value === "number") return meshMix(hash, value >>> 0);
|
|
382
|
-
if (Array.isArray(value)) {
|
|
383
|
-
hash = meshMix(hash, value.length >>> 0);
|
|
384
|
-
for (var i = 0; i < value.length; i += 1) {
|
|
385
|
-
hash = meshValue(hash, value[i]);
|
|
386
|
-
}
|
|
387
|
-
return hash;
|
|
388
|
-
}
|
|
389
|
-
return meshMix(hash, 3735928559);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function meshKey(mesh, base, tokenCount, seed, tag, ops) {
|
|
393
|
-
var hash = 2166136261;
|
|
394
|
-
hash = meshMix(hash, 1145713480);
|
|
395
|
-
hash = meshMix(hash, 1296388936);
|
|
396
|
-
hash = meshValue(hash, mesh);
|
|
397
|
-
hash = meshMix(hash, base >>> 0);
|
|
398
|
-
hash = meshMix(hash, tokenCount >>> 0);
|
|
399
|
-
hash = meshMix(hash, seed >>> 0);
|
|
400
|
-
hash = meshMix(hash, tag >>> 0);
|
|
401
|
-
hash = meshValue(hash, ops);
|
|
402
|
-
return hash >>> 0;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function meshStream(key, index, base, salt) {
|
|
406
|
-
var hash = meshMix(key >>> 0, 1398035796);
|
|
407
|
-
hash = meshMix(hash, salt >>> 0);
|
|
408
|
-
hash = meshMix(hash, index >>> 0);
|
|
409
|
-
hash = meshMix(hash, Math.imul(index + 1, 2654435761) >>> 0);
|
|
410
|
-
return hash % base;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function textDigest(value) {
|
|
414
|
-
var hash = 2166136261;
|
|
415
|
-
for (var i = 0; i < value.length; i += 1) {
|
|
416
|
-
hash = meshMix(hash, value.charCodeAt(i));
|
|
417
|
-
}
|
|
418
|
-
return hash >>> 0;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
function constantDigest(constants) {
|
|
422
|
-
var hash = textDigest("DJS-HMESH/constants/v1");
|
|
423
|
-
for (var i = 0; i < constants.length; i += 1) {
|
|
424
|
-
var constant = constants[i];
|
|
425
|
-
hash = meshMix(hash, textDigest(constant.kind));
|
|
426
|
-
hash = meshMix(hash, textDigest(String(constant.value)));
|
|
427
|
-
}
|
|
428
|
-
return hash >>> 0;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
function meshExpression(value) {
|
|
432
|
-
if (Array.isArray(value)) {
|
|
433
|
-
return arrayExpression(value.map(meshExpression));
|
|
434
|
-
}
|
|
435
|
-
return literal(value >>> 0);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function encryptedStream(tokens, base, seed) {
|
|
439
|
-
var state = seed >>> 0;
|
|
440
|
-
var tag = seed >>> 0;
|
|
441
|
-
var encrypted = [];
|
|
442
|
-
for (var i = 0; i < tokens.length; i += 1) {
|
|
443
|
-
var mul = 1 + ((state >>> 5) % (base - 1));
|
|
444
|
-
var add = state % base;
|
|
445
|
-
var value = (tokens[i] * mul + add) % base;
|
|
446
|
-
encrypted.push(value);
|
|
447
|
-
state = mix(state, value, i);
|
|
448
|
-
tag = mix(tag, value, i);
|
|
449
|
-
}
|
|
450
|
-
return { encrypted: encrypted, tag: tag >>> 0 };
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function packTokens(tokens, base) {
|
|
454
|
-
var out = 0n;
|
|
455
|
-
var pow = 1n;
|
|
456
|
-
var bigBase = BigInt(base);
|
|
457
|
-
tokens.forEach(function (token) {
|
|
458
|
-
out += BigInt(token) * pow;
|
|
459
|
-
pow *= bigBase;
|
|
460
|
-
});
|
|
461
|
-
return out;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function makeChaff(next, tokenCount, ratio) {
|
|
465
|
-
var length = Math.max(4, Math.min(32, Math.ceil(tokenCount * ratio / 16)));
|
|
466
|
-
var chaff = [];
|
|
467
|
-
for (var i = 0; i < length; i += 1) {
|
|
468
|
-
chaff.push(next() >>> 0);
|
|
469
|
-
}
|
|
470
|
-
return chaff;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function buildHashMeshRecord(record, encryptedTokens, opValues, constants, dialect, options) {
|
|
474
|
-
var ratio = typeof options.chaffRatio === "number" ? options.chaffRatio : 0.55;
|
|
475
|
-
var buildSalt = hashSeed(String(options.seed || "toildefender-hmesh") + ":DJS-HMESH/build/v1");
|
|
476
|
-
var functionId = meshMix(buildSalt, dialect.seed);
|
|
477
|
-
var chunkId = dialect.next() >>> 0;
|
|
478
|
-
var constDigest = constantDigest(constants);
|
|
479
|
-
var previousDigest = meshMix(meshMix(buildSalt, functionId), chunkId);
|
|
480
|
-
var streamSalt = dialect.next() >>> 0;
|
|
481
|
-
var flags = 0;
|
|
482
|
-
if (options.bindToVmState !== false) flags |= 1;
|
|
483
|
-
if (options.deriveDialectFromMesh) flags |= 2;
|
|
484
|
-
if (options.encodeChaff !== false) flags |= 4;
|
|
485
|
-
var chaff = options.encodeChaff === false ? [] : makeChaff(dialect.next, record.tokenCount, ratio);
|
|
486
|
-
var mesh = [
|
|
487
|
-
buildSalt >>> 0,
|
|
488
|
-
functionId >>> 0,
|
|
489
|
-
chunkId >>> 0,
|
|
490
|
-
constDigest >>> 0,
|
|
491
|
-
previousDigest >>> 0,
|
|
492
|
-
streamSalt >>> 0,
|
|
493
|
-
flags >>> 0,
|
|
494
|
-
textDigest("DJS-HMESH/chunk-key/v1") >>> 0,
|
|
495
|
-
chaff
|
|
496
|
-
];
|
|
497
|
-
var key = meshKey(mesh, record.base, record.tokenCount, record.seed, record.tag, opValues);
|
|
498
|
-
var cipher = encryptedTokens.map(function (token, index) {
|
|
499
|
-
return (token + meshStream(key, index, record.base, streamSalt)) % record.base;
|
|
500
|
-
});
|
|
501
|
-
record.blob = packTokens(cipher, record.base);
|
|
502
|
-
record.mesh = mesh;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function isSimplePattern(node) {
|
|
506
|
-
return node && node.type === "Identifier";
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function containsNestedFunction(node) {
|
|
510
|
-
var found = false;
|
|
511
|
-
traverser.traverseEx(node, [], function (child) {
|
|
512
|
-
if (child !== node && estest.isFunction(child)) {
|
|
513
|
-
found = true;
|
|
514
|
-
this.abort();
|
|
515
|
-
}
|
|
516
|
-
return child;
|
|
517
|
-
});
|
|
518
|
-
return found;
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
function Compiler(fn, dialect, options) {
|
|
522
|
-
this.fn = fn;
|
|
523
|
-
this.dialect = dialect;
|
|
524
|
-
this.options = options || {};
|
|
525
|
-
this.instructions = [];
|
|
526
|
-
this.labelId = 0;
|
|
527
|
-
this.params = {};
|
|
528
|
-
this.locals = {};
|
|
529
|
-
this.localCount = 0;
|
|
530
|
-
this.constants = [];
|
|
531
|
-
this.constantKeys = {};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
Compiler.prototype.label = function () { return "L" + this.labelId++; };
|
|
535
|
-
Compiler.prototype.mark = function (name) { this.instructions.push({ label: name }); };
|
|
536
|
-
Compiler.prototype.emit = function (op) { this.instructions.push({ op: op, args: Array.prototype.slice.call(arguments, 1) }); };
|
|
537
|
-
Compiler.prototype.localSlot = function (name) {
|
|
538
|
-
if (!Object.prototype.hasOwnProperty.call(this.locals, name)) this.locals[name] = this.localCount++;
|
|
539
|
-
return this.locals[name];
|
|
540
|
-
};
|
|
541
|
-
Compiler.prototype.addConstant = function (kind, value) {
|
|
542
|
-
var key = kind + ":" + String(value);
|
|
543
|
-
if (Object.prototype.hasOwnProperty.call(this.constantKeys, key)) return this.constantKeys[key];
|
|
544
|
-
var index = this.constants.length;
|
|
545
|
-
this.constantKeys[key] = index;
|
|
546
|
-
this.constants.push({ kind: kind, value: value });
|
|
547
|
-
return index;
|
|
548
|
-
};
|
|
549
|
-
Compiler.prototype.collectLocals = function () {
|
|
550
|
-
var self = this;
|
|
551
|
-
this.fn.params.forEach(function (param, index) {
|
|
552
|
-
if (!isSimplePattern(param)) throw new Error("unsupported parameter pattern");
|
|
553
|
-
self.params[param.name] = index;
|
|
554
|
-
});
|
|
555
|
-
traverser.traverseEx(this.fn.body, [], function (node) {
|
|
556
|
-
if (node !== self.fn.body && estest.isFunction(node)) {
|
|
557
|
-
this.abort();
|
|
558
|
-
return node;
|
|
559
|
-
}
|
|
560
|
-
if (node.type === "VariableDeclarator") {
|
|
561
|
-
if (!isSimplePattern(node.id)) throw new Error("unsupported declaration pattern");
|
|
562
|
-
self.localSlot(node.id.name);
|
|
563
|
-
}
|
|
564
|
-
return node;
|
|
565
|
-
});
|
|
566
|
-
};
|
|
567
|
-
Compiler.prototype.compile = function () {
|
|
568
|
-
if (!this.fn.body || this.fn.body.type !== "BlockStatement") throw new Error("unsupported function body");
|
|
569
|
-
if (containsNestedFunction(this.fn.body)) throw new Error("nested functions are not virtualized");
|
|
570
|
-
this.collectLocals();
|
|
571
|
-
this.compileBlock(this.fn.body);
|
|
572
|
-
this.emit("PUSH_UNDEFINED");
|
|
573
|
-
this.emit("RETURN");
|
|
574
|
-
return this.finish();
|
|
575
|
-
};
|
|
576
|
-
Compiler.prototype.compileBlock = function (block) {
|
|
577
|
-
var self = this;
|
|
578
|
-
block.body.forEach(function (stmt) { self.compileStatement(stmt); });
|
|
579
|
-
};
|
|
580
|
-
Compiler.prototype.compileStatement = function (stmt) {
|
|
581
|
-
switch (stmt.type) {
|
|
582
|
-
case "BlockStatement": this.compileBlock(stmt); return;
|
|
583
|
-
case "VariableDeclaration":
|
|
584
|
-
for (var i = 0; i < stmt.declarations.length; i += 1) {
|
|
585
|
-
var decl = stmt.declarations[i];
|
|
586
|
-
var slot = this.localSlot(decl.id.name);
|
|
587
|
-
if (decl.init) this.compileExpression(decl.init); else this.emit("PUSH_UNDEFINED");
|
|
588
|
-
this.emit("STORE_LOCAL", slot);
|
|
589
|
-
}
|
|
590
|
-
return;
|
|
591
|
-
case "ExpressionStatement": this.compileExpression(stmt.expression); this.emit("POP"); return;
|
|
592
|
-
case "ReturnStatement":
|
|
593
|
-
if (stmt.argument) this.compileExpression(stmt.argument); else this.emit("PUSH_UNDEFINED");
|
|
594
|
-
this.emit("RETURN");
|
|
595
|
-
return;
|
|
596
|
-
case "IfStatement": {
|
|
597
|
-
var elseLabel = this.label();
|
|
598
|
-
var endLabel = this.label();
|
|
599
|
-
this.compileExpression(stmt.test);
|
|
600
|
-
this.emit("JMP_FALSE", elseLabel);
|
|
601
|
-
this.compileStatement(stmt.consequent);
|
|
602
|
-
this.emit("JMP", endLabel);
|
|
603
|
-
this.mark(elseLabel);
|
|
604
|
-
if (stmt.alternate) this.compileStatement(stmt.alternate);
|
|
605
|
-
this.mark(endLabel);
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
case "WhileStatement": {
|
|
609
|
-
var start = this.label();
|
|
610
|
-
var end = this.label();
|
|
611
|
-
this.mark(start);
|
|
612
|
-
this.compileExpression(stmt.test);
|
|
613
|
-
this.emit("JMP_FALSE", end);
|
|
614
|
-
this.compileStatement(stmt.body);
|
|
615
|
-
this.emit("JMP", start);
|
|
616
|
-
this.mark(end);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
case "EmptyStatement": return;
|
|
620
|
-
default: throw new Error("unsupported statement " + stmt.type);
|
|
621
|
-
}
|
|
622
|
-
};
|
|
623
|
-
Compiler.prototype.compileExpression = function (expr) {
|
|
624
|
-
switch (expr.type) {
|
|
625
|
-
case "Literal": this.compileLiteral(expr); return;
|
|
626
|
-
case "Identifier": this.compileIdentifier(expr.name); return;
|
|
627
|
-
case "ThisExpression": this.emit("PUSH_THIS"); return;
|
|
628
|
-
case "ArrayExpression": this.compileArray(expr); return;
|
|
629
|
-
case "ObjectExpression": this.compileObject(expr); return;
|
|
630
|
-
case "UnaryExpression": this.compileUnary(expr); return;
|
|
631
|
-
case "BinaryExpression": this.compileBinary(expr); return;
|
|
632
|
-
case "LogicalExpression": this.compileLogical(expr); return;
|
|
633
|
-
case "AssignmentExpression": this.compileAssignment(expr); return;
|
|
634
|
-
case "MemberExpression": this.compileMember(expr); return;
|
|
635
|
-
case "CallExpression": this.compileCall(expr); return;
|
|
636
|
-
case "ConditionalExpression": this.compileConditional(expr); return;
|
|
637
|
-
case "SequenceExpression":
|
|
638
|
-
for (var i = 0; i < expr.expressions.length; i += 1) {
|
|
639
|
-
this.compileExpression(expr.expressions[i]);
|
|
640
|
-
if (i + 1 < expr.expressions.length) this.emit("POP");
|
|
641
|
-
}
|
|
642
|
-
return;
|
|
643
|
-
default: throw new Error("unsupported expression " + expr.type);
|
|
644
|
-
}
|
|
645
|
-
};
|
|
646
|
-
Compiler.prototype.compileLiteral = function (expr) {
|
|
647
|
-
if (expr.regex) throw new Error("regex literals are unsupported");
|
|
648
|
-
if (expr.value === null) this.emit("PUSH_NULL");
|
|
649
|
-
else if (expr.value === true) this.emit("PUSH_TRUE");
|
|
650
|
-
else if (expr.value === false) this.emit("PUSH_FALSE");
|
|
651
|
-
else if (typeof expr.value === "number" && Number.isInteger(expr.value) && expr.value >= 0 && expr.value < SMALL_LIMIT) this.emit("PUSH_SMALL", expr.value);
|
|
652
|
-
else this.emit("PUSH_CONST", this.addConstant(typeof expr.value, expr.value));
|
|
653
|
-
};
|
|
654
|
-
Compiler.prototype.compileIdentifier = function (name) {
|
|
655
|
-
if (name === "undefined") this.emit("PUSH_UNDEFINED");
|
|
656
|
-
else if (name === "arguments") this.emit("PUSH_ARGUMENTS");
|
|
657
|
-
else if (Object.prototype.hasOwnProperty.call(this.params, name)) this.emit("LOAD_ARG", this.params[name]);
|
|
658
|
-
else if (Object.prototype.hasOwnProperty.call(this.locals, name)) this.emit("LOAD_LOCAL", this.locals[name]);
|
|
659
|
-
else this.emit("PUSH_CONST", this.addConstant("reference", name));
|
|
660
|
-
};
|
|
661
|
-
Compiler.prototype.compileArray = function (expr) {
|
|
662
|
-
for (var i = 0; i < expr.elements.length; i += 1) {
|
|
663
|
-
if (expr.elements[i] === null) this.emit("PUSH_UNDEFINED");
|
|
664
|
-
else this.compileExpression(expr.elements[i]);
|
|
665
|
-
}
|
|
666
|
-
this.emit("MAKE_ARRAY", expr.elements.length);
|
|
667
|
-
};
|
|
668
|
-
Compiler.prototype.compileObject = function (expr) {
|
|
669
|
-
for (var i = 0; i < expr.properties.length; i += 1) {
|
|
670
|
-
var prop = expr.properties[i];
|
|
671
|
-
if (prop.kind && prop.kind !== "init") throw new Error("unsupported object property kind");
|
|
672
|
-
if (prop.type === "SpreadElement") throw new Error("unsupported object spread");
|
|
673
|
-
var key = prop.computed ? null : prop.key.name || prop.key.value;
|
|
674
|
-
if (key === null) this.compileExpression(prop.key); else this.emit("PUSH_CONST", this.addConstant("string", String(key)));
|
|
675
|
-
this.compileExpression(prop.value);
|
|
676
|
-
}
|
|
677
|
-
this.emit("MAKE_OBJECT", expr.properties.length);
|
|
678
|
-
};
|
|
679
|
-
Compiler.prototype.compileUnary = function (expr) {
|
|
680
|
-
if (expr.operator === "void") {
|
|
681
|
-
this.compileExpression(expr.argument);
|
|
682
|
-
this.emit("POP");
|
|
683
|
-
this.emit("PUSH_UNDEFINED");
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
this.compileExpression(expr.argument);
|
|
687
|
-
if (expr.operator === "-") this.emit("NEG");
|
|
688
|
-
else if (expr.operator === "!") this.emit("NOT");
|
|
689
|
-
else if (expr.operator === "~") this.emit("BIT_NOT");
|
|
690
|
-
else if (expr.operator === "typeof") this.emit("TYPEOF");
|
|
691
|
-
else if (expr.operator !== "+") throw new Error("unsupported unary operator " + expr.operator);
|
|
692
|
-
};
|
|
693
|
-
Compiler.prototype.compileBinary = function (expr) {
|
|
694
|
-
this.compileExpression(expr.left);
|
|
695
|
-
this.compileExpression(expr.right);
|
|
696
|
-
var map = { "+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV", "%": "MOD", "**": "POW", "==": "EQ", "!=": "NEQ", "===": "STRICT_EQ", "!==": "STRICT_NEQ", "<": "LT", "<=": "LTE", ">": "GT", ">=": "GTE" };
|
|
697
|
-
if (!map[expr.operator]) throw new Error("unsupported binary operator " + expr.operator);
|
|
698
|
-
this.emit(map[expr.operator]);
|
|
699
|
-
};
|
|
700
|
-
Compiler.prototype.compileLogical = function (expr) {
|
|
701
|
-
var end = this.label();
|
|
702
|
-
this.compileExpression(expr.left);
|
|
703
|
-
this.emit("DUP");
|
|
704
|
-
this.emit(expr.operator === "&&" ? "JMP_FALSE" : "JMP_TRUE", end);
|
|
705
|
-
this.emit("POP");
|
|
706
|
-
this.compileExpression(expr.right);
|
|
707
|
-
this.mark(end);
|
|
708
|
-
};
|
|
709
|
-
Compiler.prototype.compileAssignment = function (expr) {
|
|
710
|
-
if (expr.left.type === "Identifier") {
|
|
711
|
-
if (!Object.prototype.hasOwnProperty.call(this.locals, expr.left.name)) throw new Error("unsupported assignment target " + expr.left.name);
|
|
712
|
-
if (expr.operator === "=") this.compileExpression(expr.right);
|
|
713
|
-
else {
|
|
714
|
-
var map = { "+=": "ADD", "-=": "SUB", "*=": "MUL", "/=": "DIV", "%=": "MOD" };
|
|
715
|
-
if (!map[expr.operator]) throw new Error("unsupported assignment operator " + expr.operator);
|
|
716
|
-
this.compileIdentifier(expr.left.name);
|
|
717
|
-
this.compileExpression(expr.right);
|
|
718
|
-
this.emit(map[expr.operator]);
|
|
719
|
-
}
|
|
720
|
-
this.emit("DUP");
|
|
721
|
-
this.emit("STORE_LOCAL", this.locals[expr.left.name]);
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
if (expr.left.type === "MemberExpression" && expr.operator === "=") {
|
|
725
|
-
this.compileExpression(expr.left.object);
|
|
726
|
-
this.compilePropertyKey(expr.left);
|
|
727
|
-
this.compileExpression(expr.right);
|
|
728
|
-
this.emit("SET_PROP");
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
731
|
-
throw new Error("unsupported assignment expression");
|
|
732
|
-
};
|
|
733
|
-
Compiler.prototype.compilePropertyKey = function (expr) {
|
|
734
|
-
if (expr.computed) this.compileExpression(expr.property);
|
|
735
|
-
else this.emit("PUSH_CONST", this.addConstant("string", expr.property.name));
|
|
736
|
-
};
|
|
737
|
-
Compiler.prototype.compileMember = function (expr) {
|
|
738
|
-
this.compileExpression(expr.object);
|
|
739
|
-
this.compilePropertyKey(expr);
|
|
740
|
-
this.emit("GET_PROP");
|
|
741
|
-
};
|
|
742
|
-
Compiler.prototype.compileCall = function (expr) {
|
|
743
|
-
if (expr.callee.type === "MemberExpression") {
|
|
744
|
-
this.compileExpression(expr.callee.object);
|
|
745
|
-
this.compilePropertyKey(expr.callee);
|
|
746
|
-
for (var i = 0; i < expr.arguments.length; i += 1) this.compileExpression(expr.arguments[i]);
|
|
747
|
-
this.emit("CALL_METHOD", expr.arguments.length);
|
|
748
|
-
return;
|
|
749
|
-
}
|
|
750
|
-
this.compileExpression(expr.callee);
|
|
751
|
-
for (var j = 0; j < expr.arguments.length; j += 1) this.compileExpression(expr.arguments[j]);
|
|
752
|
-
this.emit("CALL_EXT", 0, expr.arguments.length);
|
|
753
|
-
};
|
|
754
|
-
Compiler.prototype.compileConditional = function (expr) {
|
|
755
|
-
var alternate = this.label();
|
|
756
|
-
var end = this.label();
|
|
757
|
-
this.compileExpression(expr.test);
|
|
758
|
-
this.emit("JMP_FALSE", alternate);
|
|
759
|
-
this.compileExpression(expr.consequent);
|
|
760
|
-
this.emit("JMP", end);
|
|
761
|
-
this.mark(alternate);
|
|
762
|
-
this.compileExpression(expr.alternate);
|
|
763
|
-
this.mark(end);
|
|
764
|
-
};
|
|
765
|
-
Compiler.prototype.instructionSize = function (instr, positions) {
|
|
766
|
-
if (instr.label) return 0;
|
|
767
|
-
var start = positions.get(instr) || 0;
|
|
768
|
-
var size = 1;
|
|
769
|
-
for (var i = 0; i < instr.args.length; i += 1) {
|
|
770
|
-
var arg = instr.args[i];
|
|
771
|
-
if (typeof arg === "string") size += signedLengthFor(positions.get(arg) || 0, start, size);
|
|
772
|
-
else size += encodeUnsigned(arg).length;
|
|
773
|
-
}
|
|
774
|
-
return size;
|
|
775
|
-
};
|
|
776
|
-
Compiler.prototype.assemble = function () {
|
|
777
|
-
var positions = new Map();
|
|
778
|
-
var stable = false;
|
|
779
|
-
while (!stable) {
|
|
780
|
-
stable = true;
|
|
781
|
-
var cursor = 0;
|
|
782
|
-
for (var i = 0; i < this.instructions.length; i += 1) {
|
|
783
|
-
var instr = this.instructions[i];
|
|
784
|
-
if (instr.label) {
|
|
785
|
-
if (positions.get(instr.label) !== cursor) stable = false;
|
|
786
|
-
positions.set(instr.label, cursor);
|
|
787
|
-
} else {
|
|
788
|
-
positions.set(instr, cursor);
|
|
789
|
-
cursor += this.instructionSize(instr, positions);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
var tokens = [];
|
|
794
|
-
for (var j = 0; j < this.instructions.length; j += 1) {
|
|
795
|
-
var op = this.instructions[j];
|
|
796
|
-
if (op.label) continue;
|
|
797
|
-
var start = tokens.length;
|
|
798
|
-
tokens.push(this.dialect.opcodes[op.op]);
|
|
799
|
-
for (var k = 0; k < op.args.length; k += 1) {
|
|
800
|
-
var arg = op.args[k];
|
|
801
|
-
if (typeof arg === "string") {
|
|
802
|
-
var before = tokens.length - start;
|
|
803
|
-
var len = signedLengthFor(positions.get(arg), start, before);
|
|
804
|
-
var rel = positions.get(arg) - (start + before + len);
|
|
805
|
-
encodeSigned(rel).forEach(function (value) { tokens.push(value); });
|
|
806
|
-
} else {
|
|
807
|
-
encodeUnsigned(arg).forEach(function (value) { tokens.push(value); });
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
return tokens;
|
|
812
|
-
};
|
|
813
|
-
Compiler.prototype.constantExpression = function (constant) {
|
|
814
|
-
var next = this.dialect.next;
|
|
815
|
-
if (constant.kind === "number") {
|
|
816
|
-
if (Number.isNaN(constant.value)) return binary("/", literal(0), literal(0));
|
|
817
|
-
if (constant.value === Infinity) return binary("/", literal(1), literal(0));
|
|
818
|
-
if (constant.value === -Infinity) return { type: "UnaryExpression", operator: "-", prefix: true, argument: binary("/", literal(1), literal(0)) };
|
|
819
|
-
return literal(constant.value);
|
|
820
|
-
}
|
|
821
|
-
if (constant.kind === "string" || constant.kind === "reference") {
|
|
822
|
-
var value = String(constant.value);
|
|
823
|
-
var salt = (next() & 65535) || 1;
|
|
824
|
-
var decoded = call(identifier("veilmark$numericVmString"), [ bigintExpression(stringBlob(value, salt), next), literal(value.length), literal(salt) ]);
|
|
825
|
-
if (constant.kind === "reference") {
|
|
826
|
-
return {
|
|
827
|
-
type: "ConditionalExpression",
|
|
828
|
-
test: binary("===", unary("typeof", identifier(value)), literal("undefined")),
|
|
829
|
-
consequent: member(identifier("globalThis"), decoded),
|
|
830
|
-
alternate: identifier(value)
|
|
831
|
-
};
|
|
832
|
-
}
|
|
833
|
-
return decoded;
|
|
834
|
-
}
|
|
835
|
-
if (constant.kind === "boolean") return literal(!!constant.value);
|
|
836
|
-
if (constant.kind === "undefined") return { type: "UnaryExpression", operator: "void", prefix: true, argument: literal(0) };
|
|
837
|
-
throw new Error("unsupported constant " + constant.kind);
|
|
838
|
-
};
|
|
839
|
-
Compiler.prototype.finish = function () {
|
|
840
|
-
var tokens = this.assemble();
|
|
841
|
-
var encrypted = encryptedStream(tokens, this.dialect.base, this.dialect.seed);
|
|
842
|
-
var opValues = OP_NAMES.map(name => this.dialect.opcodes[name]);
|
|
843
|
-
var record = {
|
|
844
|
-
base: this.dialect.base,
|
|
845
|
-
blob: packTokens(encrypted.encrypted, this.dialect.base),
|
|
846
|
-
constants: this.constants.map(this.constantExpression.bind(this)),
|
|
847
|
-
opValues: opValues.map(literal),
|
|
848
|
-
seed: this.dialect.seed,
|
|
849
|
-
tag: encrypted.tag,
|
|
850
|
-
tokenCount: tokens.length
|
|
851
|
-
};
|
|
852
|
-
if (this.options.hashMesh && this.options.hashMesh.enabled) {
|
|
853
|
-
buildHashMeshRecord(record, encrypted.encrypted, opValues, this.constants, this.dialect, Object.assign({}, this.options.hashMesh, {
|
|
854
|
-
seed: this.options.seed
|
|
855
|
-
}));
|
|
856
|
-
}
|
|
857
|
-
return record;
|
|
858
|
-
};
|
|
859
|
-
|
|
860
|
-
function makeDialect(seedText) {
|
|
861
|
-
var seed = hashSeed(seedText);
|
|
862
|
-
var next = makeRng(seed);
|
|
863
|
-
var values = shuffle(Array.from({ length: OP_NAMES.length }, function (_, index) { return index + 1; }), next);
|
|
864
|
-
var opcodes = {};
|
|
865
|
-
OP_NAMES.forEach(function (name, index) { opcodes[name] = values[index]; });
|
|
866
|
-
return { base: BASES[next() % BASES.length], next: next, opcodes: opcodes, seed: seed };
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function vmCall(record, next) {
|
|
870
|
-
return call(identifier("veilmark$numericVmRun"), [
|
|
871
|
-
bigintExpression(record.blob, next),
|
|
872
|
-
literal(record.base),
|
|
873
|
-
literal(record.tokenCount),
|
|
874
|
-
literal(record.seed),
|
|
875
|
-
literal(record.tag),
|
|
876
|
-
arrayExpression(record.constants),
|
|
877
|
-
identifier("arguments"),
|
|
878
|
-
{ type: "ThisExpression" },
|
|
879
|
-
arrayExpression(record.opValues),
|
|
880
|
-
record.mesh ? meshExpression(record.mesh) : literal(null)
|
|
881
|
-
]);
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
function resolveOptions(options) {
|
|
885
|
-
return Object.assign({
|
|
886
|
-
enabled: false,
|
|
887
|
-
maxFunctionSize: 120,
|
|
888
|
-
minFunctionSize: 1,
|
|
889
|
-
mode: "balanced",
|
|
890
|
-
seed: "toildefender-numeric-vm",
|
|
891
|
-
hashMesh: {
|
|
892
|
-
bindToVmState: true,
|
|
893
|
-
chaffRatio: 0.55,
|
|
894
|
-
deriveDialectFromMesh: false,
|
|
895
|
-
enabled: false,
|
|
896
|
-
encodeChaff: true,
|
|
897
|
-
mode: "balanced",
|
|
898
|
-
serverBound: false,
|
|
899
|
-
unlock: "per-function"
|
|
900
|
-
},
|
|
901
|
-
virtualize: "marked"
|
|
902
|
-
}, options || {});
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
module.exports = class NumericVm {
|
|
906
|
-
constructor(logger, options) {
|
|
907
|
-
this.logger = logger;
|
|
908
|
-
this.options = resolveOptions(options);
|
|
909
|
-
this.count = 0;
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
shouldTry(node) {
|
|
913
|
-
if (!this.options.enabled || !estest.isFunction(node) || node.generator || node.async) return false;
|
|
914
|
-
if (!node.body || node.body.type !== "BlockStatement") return false;
|
|
915
|
-
if (functionName(node).indexOf("veilmark$numericVm") === 0) return false;
|
|
916
|
-
var bodySize = node.body.body.length;
|
|
917
|
-
if (bodySize < this.options.minFunctionSize || bodySize > this.options.maxFunctionSize) return false;
|
|
918
|
-
if (this.options.virtualize === "all-supported") return true;
|
|
919
|
-
if (this.options.virtualize === "heuristic") return bodySize >= this.options.minFunctionSize;
|
|
920
|
-
return false;
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
apply(ast) {
|
|
924
|
-
assert.ok(estest.isNode(ast));
|
|
925
|
-
if (!this.options.enabled) return ast;
|
|
926
|
-
|
|
927
|
-
var runtime = replaceStaticBigIntCalls(esprima.parse(RUNTIME));
|
|
928
|
-
var self = this;
|
|
929
|
-
var transformed = 0;
|
|
930
|
-
|
|
931
|
-
ast = traverser.traverse(ast, [], function (node) {
|
|
932
|
-
if (!self.shouldTry(node)) return node;
|
|
933
|
-
try {
|
|
934
|
-
var dialect = makeDialect(self.options.seed + ":" + transformed + ":" + functionName(node));
|
|
935
|
-
var record = new Compiler(node, dialect, self.options).compile();
|
|
936
|
-
node.body = { type: "BlockStatement", body: [ returnStatement(vmCall(record, dialect.next)) ] };
|
|
937
|
-
transformed += 1;
|
|
938
|
-
} catch (error) {
|
|
939
|
-
if (self.options.virtualize === "all-supported") self.logger.warn("numeric_vm skipped " + functionName(node) + ": " + error.message);
|
|
940
|
-
}
|
|
941
|
-
return node;
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
if (transformed > 0) {
|
|
945
|
-
runtime.body.reverse().forEach(function (node) { ast.body.unshift(node); });
|
|
946
|
-
}
|
|
947
|
-
this.count = transformed;
|
|
948
|
-
return ast;
|
|
949
|
-
}
|
|
950
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var assert = require("assert");
|
|
4
|
+
var crypto = require("crypto");
|
|
5
|
+
var esprima = require("esprima");
|
|
6
|
+
|
|
7
|
+
var estest = require("../estest");
|
|
8
|
+
var traverser = require("../traverser");
|
|
9
|
+
|
|
10
|
+
var RUNTIME = `
|
|
11
|
+
function veilmark$numericVmString(program, length, salt) {
|
|
12
|
+
var out = "";
|
|
13
|
+
var i = 0;
|
|
14
|
+
while (i < length) {
|
|
15
|
+
var encoded = Number(program % BigInt(65537));
|
|
16
|
+
program = program / BigInt(65537);
|
|
17
|
+
out += String.fromCharCode(encoded ^ ((salt + i * 97) & 65535));
|
|
18
|
+
i += 1;
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function veilmark$numericVmPow(a, b) {
|
|
24
|
+
if (typeof a === "bigint" && typeof b === "bigint") {
|
|
25
|
+
if (b < BigInt(0)) throw new RangeError("BigInt exponent must be positive");
|
|
26
|
+
var out = BigInt(1);
|
|
27
|
+
var base = a;
|
|
28
|
+
var exp = b;
|
|
29
|
+
while (exp > BigInt(0)) {
|
|
30
|
+
if (exp % BigInt(2) === BigInt(1)) out *= base;
|
|
31
|
+
base *= base;
|
|
32
|
+
exp = exp / BigInt(2);
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
return Math.pow(a, b);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function veilmark$hashMeshMix(current, value) {
|
|
40
|
+
var h = (current ^ value) >>> 0;
|
|
41
|
+
h = Math.imul(h ^ (h >>> 16), 2246822507) >>> 0;
|
|
42
|
+
h = Math.imul(h ^ (h >>> 13), 3266489909) >>> 0;
|
|
43
|
+
return (h ^ (h >>> 16)) >>> 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function veilmark$hashMeshValue(hash, value) {
|
|
47
|
+
if (typeof value === "number") return veilmark$hashMeshMix(hash, value >>> 0);
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
hash = veilmark$hashMeshMix(hash, value.length >>> 0);
|
|
50
|
+
var j = 0;
|
|
51
|
+
while (j < value.length) {
|
|
52
|
+
hash = veilmark$hashMeshMix(hash, value.charCodeAt(j));
|
|
53
|
+
j += 1;
|
|
54
|
+
}
|
|
55
|
+
return hash;
|
|
56
|
+
}
|
|
57
|
+
if (value && typeof value.length === "number") {
|
|
58
|
+
hash = veilmark$hashMeshMix(hash, value.length >>> 0);
|
|
59
|
+
var i = 0;
|
|
60
|
+
while (i < value.length) {
|
|
61
|
+
hash = veilmark$hashMeshValue(hash, value[i]);
|
|
62
|
+
i += 1;
|
|
63
|
+
}
|
|
64
|
+
return hash;
|
|
65
|
+
}
|
|
66
|
+
return veilmark$hashMeshMix(hash, 3735928559);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function veilmark$hashMeshKey(mesh, base, tokenCount, seed, tag, ops) {
|
|
70
|
+
var hash = 2166136261;
|
|
71
|
+
hash = veilmark$hashMeshMix(hash, 1145713480);
|
|
72
|
+
hash = veilmark$hashMeshMix(hash, 1296388936);
|
|
73
|
+
hash = veilmark$hashMeshValue(hash, mesh);
|
|
74
|
+
hash = veilmark$hashMeshMix(hash, base >>> 0);
|
|
75
|
+
hash = veilmark$hashMeshMix(hash, tokenCount >>> 0);
|
|
76
|
+
hash = veilmark$hashMeshMix(hash, seed >>> 0);
|
|
77
|
+
hash = veilmark$hashMeshMix(hash, tag >>> 0);
|
|
78
|
+
hash = veilmark$hashMeshValue(hash, ops);
|
|
79
|
+
return hash >>> 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function veilmark$hashMeshStream(key, index, base, salt) {
|
|
83
|
+
var hash = veilmark$hashMeshMix(key >>> 0, 1398035796);
|
|
84
|
+
hash = veilmark$hashMeshMix(hash, salt >>> 0);
|
|
85
|
+
hash = veilmark$hashMeshMix(hash, index >>> 0);
|
|
86
|
+
hash = veilmark$hashMeshMix(hash, Math.imul(index + 1, 2654435761) >>> 0);
|
|
87
|
+
return hash % base;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function veilmark$hashMeshUnlock(program, base, tokenCount, seed, tag, ops, mesh) {
|
|
91
|
+
var key = veilmark$hashMeshKey(mesh, base, tokenCount, seed, tag, ops);
|
|
92
|
+
var salt = mesh[5] >>> 0;
|
|
93
|
+
var baseBig = BigInt(base);
|
|
94
|
+
var out = BigInt(0);
|
|
95
|
+
var pow = BigInt(1);
|
|
96
|
+
var i = 0;
|
|
97
|
+
while (i < tokenCount) {
|
|
98
|
+
var cipher = Number(program % baseBig);
|
|
99
|
+
program = program / baseBig;
|
|
100
|
+
var plain = (cipher - veilmark$hashMeshStream(key, i, base, salt) + base) % base;
|
|
101
|
+
out += BigInt(plain) * pow;
|
|
102
|
+
pow *= baseBig;
|
|
103
|
+
i += 1;
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function veilmark$numericVmRun(program, base, tokenCount, seed, tag, constants, argsLike, self, ops, mesh) {
|
|
109
|
+
if (mesh) {
|
|
110
|
+
program = veilmark$hashMeshUnlock(program, base, tokenCount, seed, tag, ops, mesh);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
var tokens = [];
|
|
114
|
+
var state = seed >>> 0;
|
|
115
|
+
var seen = seed >>> 0;
|
|
116
|
+
var baseBig = BigInt(base);
|
|
117
|
+
|
|
118
|
+
function inverse(value, modulo) {
|
|
119
|
+
var t = 0, nt = 1;
|
|
120
|
+
var r = modulo, nr = value % modulo;
|
|
121
|
+
while (nr !== 0) {
|
|
122
|
+
var q = Math.floor(r / nr);
|
|
123
|
+
var ot = t;
|
|
124
|
+
t = nt;
|
|
125
|
+
nt = ot - q * nt;
|
|
126
|
+
var or = r;
|
|
127
|
+
r = nr;
|
|
128
|
+
nr = or - q * nr;
|
|
129
|
+
}
|
|
130
|
+
return t < 0 ? t + modulo : t;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function mix(current, encrypted, index) {
|
|
134
|
+
var mixed = (current ^ (encrypted + 2654435769 + ((current << 6) >>> 0) + (current >>> 2) + index)) >>> 0;
|
|
135
|
+
mixed = Math.imul(mixed ^ (mixed >>> 16), 2246822507) >>> 0;
|
|
136
|
+
return (mixed ^ (mixed >>> 13)) >>> 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var i = 0;
|
|
140
|
+
while (i < tokenCount) {
|
|
141
|
+
var encrypted = Number(program % baseBig);
|
|
142
|
+
program = program / baseBig;
|
|
143
|
+
var mul = 1 + ((state >>> 5) % (base - 1));
|
|
144
|
+
var add = state % base;
|
|
145
|
+
var plain = (((encrypted - add + base) % base) * inverse(mul, base)) % base;
|
|
146
|
+
tokens.push(plain);
|
|
147
|
+
seen = mix(seen, encrypted, i);
|
|
148
|
+
state = mix(state, encrypted, i);
|
|
149
|
+
i += 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if ((seen >>> 0) !== (tag >>> 0)) throw new Error("invalid numeric vm program");
|
|
153
|
+
|
|
154
|
+
var stack = [];
|
|
155
|
+
var locals = [];
|
|
156
|
+
var frameArgs = Array.prototype.slice.call(argsLike);
|
|
157
|
+
var ip = 0;
|
|
158
|
+
|
|
159
|
+
function read() {
|
|
160
|
+
return tokens[ip++];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readUnsigned() {
|
|
164
|
+
var shift = 0;
|
|
165
|
+
var value = 0;
|
|
166
|
+
for (;;) {
|
|
167
|
+
var part = read();
|
|
168
|
+
value += (part & 127) * Math.pow(2, shift);
|
|
169
|
+
if ((part & 128) === 0) return value;
|
|
170
|
+
shift += 7;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function readSigned() {
|
|
175
|
+
var raw = readUnsigned();
|
|
176
|
+
return (raw & 1) === 0 ? raw / 2 : -((raw + 1) / 2);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function popArgs(count) {
|
|
180
|
+
var out = new Array(count);
|
|
181
|
+
var i = count;
|
|
182
|
+
while (i > 0) {
|
|
183
|
+
i -= 1;
|
|
184
|
+
out[i] = stack.pop();
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
while (true) {
|
|
190
|
+
var op = read();
|
|
191
|
+
if (op === ops[0]) continue;
|
|
192
|
+
if (op === ops[1]) { stack.push(undefined); continue; }
|
|
193
|
+
if (op === ops[2]) { stack.push(null); continue; }
|
|
194
|
+
if (op === ops[3]) { stack.push(true); continue; }
|
|
195
|
+
if (op === ops[4]) { stack.push(false); continue; }
|
|
196
|
+
if (op === ops[5]) { stack.push(readUnsigned()); continue; }
|
|
197
|
+
if (op === ops[6]) { stack.push(constants[readUnsigned()]); continue; }
|
|
198
|
+
if (op === ops[7]) { stack.push(frameArgs[readUnsigned()]); continue; }
|
|
199
|
+
if (op === ops[8]) { stack.push(locals[readUnsigned()]); continue; }
|
|
200
|
+
if (op === ops[9]) { locals[readUnsigned()] = stack.pop(); continue; }
|
|
201
|
+
if (op === ops[10]) { stack.push(stack[stack.length - 1]); continue; }
|
|
202
|
+
if (op === ops[11]) { stack.pop(); continue; }
|
|
203
|
+
if (op === ops[12]) { var addB = stack.pop(); var addA = stack.pop(); stack.push(addA + addB); continue; }
|
|
204
|
+
if (op === ops[13]) { var subB = stack.pop(); var subA = stack.pop(); stack.push(subA - subB); continue; }
|
|
205
|
+
if (op === ops[14]) { var mulB = stack.pop(); var mulA = stack.pop(); stack.push(mulA * mulB); continue; }
|
|
206
|
+
if (op === ops[15]) { var divB = stack.pop(); var divA = stack.pop(); stack.push(divA / divB); continue; }
|
|
207
|
+
if (op === ops[16]) { var modB = stack.pop(); var modA = stack.pop(); stack.push(modA % modB); continue; }
|
|
208
|
+
if (op === ops[17]) { var powB = stack.pop(); var powA = stack.pop(); stack.push(veilmark$numericVmPow(powA, powB)); continue; }
|
|
209
|
+
if (op === ops[18]) { stack.push(-stack.pop()); continue; }
|
|
210
|
+
if (op === ops[19]) { stack.push(!stack.pop()); continue; }
|
|
211
|
+
if (op === ops[20]) { stack.push(~stack.pop()); continue; }
|
|
212
|
+
if (op === ops[21]) { var eqB = stack.pop(); var eqA = stack.pop(); stack.push(eqA == eqB); continue; }
|
|
213
|
+
if (op === ops[22]) { var neqB = stack.pop(); var neqA = stack.pop(); stack.push(neqA != neqB); continue; }
|
|
214
|
+
if (op === ops[23]) { var seqB = stack.pop(); var seqA = stack.pop(); stack.push(seqA === seqB); continue; }
|
|
215
|
+
if (op === ops[24]) { var sneB = stack.pop(); var sneA = stack.pop(); stack.push(sneA !== sneB); continue; }
|
|
216
|
+
if (op === ops[25]) { var ltB = stack.pop(); var ltA = stack.pop(); stack.push(ltA < ltB); continue; }
|
|
217
|
+
if (op === ops[26]) { var lteB = stack.pop(); var lteA = stack.pop(); stack.push(lteA <= lteB); continue; }
|
|
218
|
+
if (op === ops[27]) { var gtB = stack.pop(); var gtA = stack.pop(); stack.push(gtA > gtB); continue; }
|
|
219
|
+
if (op === ops[28]) { var gteB = stack.pop(); var gteA = stack.pop(); stack.push(gteA >= gteB); continue; }
|
|
220
|
+
if (op === ops[29]) { var jmp = readSigned(); ip += jmp; continue; }
|
|
221
|
+
if (op === ops[30]) { var jf = readSigned(); if (!stack.pop()) ip += jf; continue; }
|
|
222
|
+
if (op === ops[31]) { var jt = readSigned(); if (stack.pop()) ip += jt; continue; }
|
|
223
|
+
if (op === ops[32]) { readUnsigned(); var argc = readUnsigned(); var ca = popArgs(argc); var fn = stack.pop(); stack.push(fn.apply(undefined, ca)); continue; }
|
|
224
|
+
if (op === ops[33]) { readUnsigned(); var largc = readUnsigned(); var la = popArgs(largc); var lfn = constants[readUnsigned()]; stack.push(lfn.apply(undefined, la)); continue; }
|
|
225
|
+
if (op === ops[34]) { var gpKey = stack.pop(); var gpObj = stack.pop(); stack.push(gpObj[gpKey]); continue; }
|
|
226
|
+
if (op === ops[35]) { var spValue = stack.pop(); var spKey = stack.pop(); var spObj = stack.pop(); spObj[spKey] = spValue; stack.push(spValue); continue; }
|
|
227
|
+
if (op === ops[36]) { var ac = readUnsigned(); var arr = new Array(ac); var ai = ac; while (ai > 0) { ai -= 1; arr[ai] = stack.pop(); } stack.push(arr); continue; }
|
|
228
|
+
if (op === ops[37]) { var oc = readUnsigned(); var pairs = new Array(oc); var oi = oc; while (oi > 0) { oi -= 1; var ov = stack.pop(); var ok = stack.pop(); pairs[oi] = [ok, ov]; } var obj = {}; var pi = 0; while (pi < oc) { obj[pairs[pi][0]] = pairs[pi][1]; pi += 1; } stack.push(obj); continue; }
|
|
229
|
+
if (op === ops[38]) return stack.pop();
|
|
230
|
+
if (op === ops[39]) throw stack.pop();
|
|
231
|
+
if (op === ops[40]) { stack.push(self); continue; }
|
|
232
|
+
if (op === ops[41]) { stack.push(argsLike); continue; }
|
|
233
|
+
if (op === ops[42]) { stack.push(typeof stack.pop()); continue; }
|
|
234
|
+
if (op === ops[43]) { var mc = readUnsigned(); var ma = popArgs(mc); var mk = stack.pop(); var mo = stack.pop(); stack.push(mo[mk].apply(mo, ma)); continue; }
|
|
235
|
+
throw new Error("invalid virtual opcode");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
var OP_NAMES = [
|
|
241
|
+
"NOP", "PUSH_UNDEFINED", "PUSH_NULL", "PUSH_TRUE", "PUSH_FALSE", "PUSH_SMALL",
|
|
242
|
+
"PUSH_CONST", "LOAD_ARG", "LOAD_LOCAL", "STORE_LOCAL", "DUP", "POP", "ADD",
|
|
243
|
+
"SUB", "MUL", "DIV", "MOD", "POW", "NEG", "NOT", "BIT_NOT", "EQ", "NEQ",
|
|
244
|
+
"STRICT_EQ", "STRICT_NEQ", "LT", "LTE", "GT", "GTE", "JMP", "JMP_FALSE",
|
|
245
|
+
"JMP_TRUE", "CALL_EXT", "CALL_LOCAL", "GET_PROP", "SET_PROP", "MAKE_ARRAY",
|
|
246
|
+
"MAKE_OBJECT", "RETURN", "THROW", "PUSH_THIS", "PUSH_ARGUMENTS", "TYPEOF",
|
|
247
|
+
"CALL_METHOD"
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
var BASES = [257, 263, 269, 521, 1031, 4099, 65537];
|
|
251
|
+
var SMALL_LIMIT = 128;
|
|
252
|
+
|
|
253
|
+
function literal(value) { return { type: "Literal", value: value }; }
|
|
254
|
+
function identifier(name) { return { type: "Identifier", name: name }; }
|
|
255
|
+
function call(callee, args) { return { type: "CallExpression", callee: callee, arguments: args }; }
|
|
256
|
+
function binary(operator, left, right) { return { type: "BinaryExpression", operator: operator, left: left, right: right }; }
|
|
257
|
+
function unary(operator, argument) { return { type: "UnaryExpression", operator: operator, prefix: true, argument: argument }; }
|
|
258
|
+
function member(object, property) { return { type: "MemberExpression", object: object, property: property, computed: true }; }
|
|
259
|
+
function arrayExpression(values) { return { type: "ArrayExpression", elements: values }; }
|
|
260
|
+
function returnStatement(argument) { return { type: "ReturnStatement", argument: argument }; }
|
|
261
|
+
function functionName(node) { return node.id && node.id.name ? node.id.name : ""; }
|
|
262
|
+
|
|
263
|
+
function hashSeed(seed) {
|
|
264
|
+
return crypto.createHash("sha256").update(String(seed)).digest().readUInt32LE(0) || 1;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function makeRng(seed) {
|
|
268
|
+
var state = seed >>> 0;
|
|
269
|
+
return function () {
|
|
270
|
+
state ^= state << 13; state >>>= 0;
|
|
271
|
+
state ^= state >>> 17; state >>>= 0;
|
|
272
|
+
state ^= state << 5; state >>>= 0;
|
|
273
|
+
return state >>> 0;
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function shuffle(values, next) {
|
|
278
|
+
var copy = values.slice();
|
|
279
|
+
for (var i = copy.length - 1; i > 0; i -= 1) {
|
|
280
|
+
var j = next() % (i + 1);
|
|
281
|
+
var tmp = copy[i];
|
|
282
|
+
copy[i] = copy[j];
|
|
283
|
+
copy[j] = tmp;
|
|
284
|
+
}
|
|
285
|
+
return copy;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function bigintLiteral(value) {
|
|
289
|
+
var bigint = typeof value === "bigint" ? value : BigInt(value);
|
|
290
|
+
var raw = bigint.toString();
|
|
291
|
+
return { type: "Literal", value: bigint, bigint: raw, raw: raw + "n" };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function replaceStaticBigIntCalls(ast) {
|
|
295
|
+
return traverser.traverse(ast, [], function (node) {
|
|
296
|
+
if (node.type === "CallExpression"
|
|
297
|
+
&& node.callee.type === "Identifier"
|
|
298
|
+
&& node.callee.name === "BigInt"
|
|
299
|
+
&& node.arguments.length === 1
|
|
300
|
+
&& node.arguments[0].type === "Literal"
|
|
301
|
+
&& typeof node.arguments[0].value === "number"
|
|
302
|
+
&& Number.isInteger(node.arguments[0].value)
|
|
303
|
+
) {
|
|
304
|
+
return bigintLiteral(node.arguments[0].value);
|
|
305
|
+
}
|
|
306
|
+
return node;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function bigintExpression(value, next) {
|
|
311
|
+
var radixBits = 26n;
|
|
312
|
+
var radix = 1n << radixBits;
|
|
313
|
+
var chunks = [];
|
|
314
|
+
var work = value < 0n ? -value : value;
|
|
315
|
+
if (work === 0n) chunks.push(0n);
|
|
316
|
+
while (work > 0n) {
|
|
317
|
+
chunks.push(work % radix);
|
|
318
|
+
work = work / radix;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
var expr = bigintLiteral(chunks[chunks.length - 1]);
|
|
322
|
+
for (var i = chunks.length - 2; i >= 0; i -= 1) {
|
|
323
|
+
expr = binary("+", binary("<<", expr, bigintLiteral(radixBits)), bigintLiteral(chunks[i]));
|
|
324
|
+
}
|
|
325
|
+
if (value < 0n) expr = { type: "UnaryExpression", operator: "-", prefix: true, argument: expr };
|
|
326
|
+
|
|
327
|
+
var xorKey = BigInt((next() & 65535) + 1);
|
|
328
|
+
var addKey = BigInt((next() & 65535) + 1);
|
|
329
|
+
return binary("^", binary("-", binary("+", binary("^", expr, bigintLiteral(xorKey)), bigintLiteral(addKey)), bigintLiteral(addKey)), bigintLiteral(xorKey));
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function stringBlob(value, salt) {
|
|
333
|
+
var base = 65537n;
|
|
334
|
+
var pow = 1n;
|
|
335
|
+
var out = 0n;
|
|
336
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
337
|
+
out += BigInt(value.charCodeAt(i) ^ ((salt + i * 97) & 65535)) * pow;
|
|
338
|
+
pow *= base;
|
|
339
|
+
}
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function encodeUnsigned(value) {
|
|
344
|
+
var out = [];
|
|
345
|
+
var current = value >>> 0;
|
|
346
|
+
do {
|
|
347
|
+
var part = current & 127;
|
|
348
|
+
current = Math.floor(current / 128);
|
|
349
|
+
out.push(current > 0 ? part | 128 : part);
|
|
350
|
+
} while (current > 0);
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function encodeSigned(value) {
|
|
355
|
+
return encodeUnsigned(value >= 0 ? value * 2 : (-value * 2) - 1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function signedLengthFor(target, start, beforeOperand) {
|
|
359
|
+
var len = 1;
|
|
360
|
+
for (;;) {
|
|
361
|
+
var next = encodeSigned(target - (start + beforeOperand + len)).length;
|
|
362
|
+
if (next === len) return len;
|
|
363
|
+
len = next;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function mix(current, encrypted, index) {
|
|
368
|
+
var mixed = (current ^ (encrypted + 2654435769 + ((current << 6) >>> 0) + (current >>> 2) + index)) >>> 0;
|
|
369
|
+
mixed = Math.imul(mixed ^ (mixed >>> 16), 2246822507) >>> 0;
|
|
370
|
+
return (mixed ^ (mixed >>> 13)) >>> 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function meshMix(current, value) {
|
|
374
|
+
var h = (current ^ value) >>> 0;
|
|
375
|
+
h = Math.imul(h ^ (h >>> 16), 2246822507) >>> 0;
|
|
376
|
+
h = Math.imul(h ^ (h >>> 13), 3266489909) >>> 0;
|
|
377
|
+
return (h ^ (h >>> 16)) >>> 0;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function meshValue(hash, value) {
|
|
381
|
+
if (typeof value === "number") return meshMix(hash, value >>> 0);
|
|
382
|
+
if (Array.isArray(value)) {
|
|
383
|
+
hash = meshMix(hash, value.length >>> 0);
|
|
384
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
385
|
+
hash = meshValue(hash, value[i]);
|
|
386
|
+
}
|
|
387
|
+
return hash;
|
|
388
|
+
}
|
|
389
|
+
return meshMix(hash, 3735928559);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function meshKey(mesh, base, tokenCount, seed, tag, ops) {
|
|
393
|
+
var hash = 2166136261;
|
|
394
|
+
hash = meshMix(hash, 1145713480);
|
|
395
|
+
hash = meshMix(hash, 1296388936);
|
|
396
|
+
hash = meshValue(hash, mesh);
|
|
397
|
+
hash = meshMix(hash, base >>> 0);
|
|
398
|
+
hash = meshMix(hash, tokenCount >>> 0);
|
|
399
|
+
hash = meshMix(hash, seed >>> 0);
|
|
400
|
+
hash = meshMix(hash, tag >>> 0);
|
|
401
|
+
hash = meshValue(hash, ops);
|
|
402
|
+
return hash >>> 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function meshStream(key, index, base, salt) {
|
|
406
|
+
var hash = meshMix(key >>> 0, 1398035796);
|
|
407
|
+
hash = meshMix(hash, salt >>> 0);
|
|
408
|
+
hash = meshMix(hash, index >>> 0);
|
|
409
|
+
hash = meshMix(hash, Math.imul(index + 1, 2654435761) >>> 0);
|
|
410
|
+
return hash % base;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function textDigest(value) {
|
|
414
|
+
var hash = 2166136261;
|
|
415
|
+
for (var i = 0; i < value.length; i += 1) {
|
|
416
|
+
hash = meshMix(hash, value.charCodeAt(i));
|
|
417
|
+
}
|
|
418
|
+
return hash >>> 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function constantDigest(constants) {
|
|
422
|
+
var hash = textDigest("DJS-HMESH/constants/v1");
|
|
423
|
+
for (var i = 0; i < constants.length; i += 1) {
|
|
424
|
+
var constant = constants[i];
|
|
425
|
+
hash = meshMix(hash, textDigest(constant.kind));
|
|
426
|
+
hash = meshMix(hash, textDigest(String(constant.value)));
|
|
427
|
+
}
|
|
428
|
+
return hash >>> 0;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function meshExpression(value) {
|
|
432
|
+
if (Array.isArray(value)) {
|
|
433
|
+
return arrayExpression(value.map(meshExpression));
|
|
434
|
+
}
|
|
435
|
+
return literal(value >>> 0);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function encryptedStream(tokens, base, seed) {
|
|
439
|
+
var state = seed >>> 0;
|
|
440
|
+
var tag = seed >>> 0;
|
|
441
|
+
var encrypted = [];
|
|
442
|
+
for (var i = 0; i < tokens.length; i += 1) {
|
|
443
|
+
var mul = 1 + ((state >>> 5) % (base - 1));
|
|
444
|
+
var add = state % base;
|
|
445
|
+
var value = (tokens[i] * mul + add) % base;
|
|
446
|
+
encrypted.push(value);
|
|
447
|
+
state = mix(state, value, i);
|
|
448
|
+
tag = mix(tag, value, i);
|
|
449
|
+
}
|
|
450
|
+
return { encrypted: encrypted, tag: tag >>> 0 };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function packTokens(tokens, base) {
|
|
454
|
+
var out = 0n;
|
|
455
|
+
var pow = 1n;
|
|
456
|
+
var bigBase = BigInt(base);
|
|
457
|
+
tokens.forEach(function (token) {
|
|
458
|
+
out += BigInt(token) * pow;
|
|
459
|
+
pow *= bigBase;
|
|
460
|
+
});
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function makeChaff(next, tokenCount, ratio) {
|
|
465
|
+
var length = Math.max(4, Math.min(32, Math.ceil(tokenCount * ratio / 16)));
|
|
466
|
+
var chaff = [];
|
|
467
|
+
for (var i = 0; i < length; i += 1) {
|
|
468
|
+
chaff.push(next() >>> 0);
|
|
469
|
+
}
|
|
470
|
+
return chaff;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function buildHashMeshRecord(record, encryptedTokens, opValues, constants, dialect, options) {
|
|
474
|
+
var ratio = typeof options.chaffRatio === "number" ? options.chaffRatio : 0.55;
|
|
475
|
+
var buildSalt = hashSeed(String(options.seed || "toildefender-hmesh") + ":DJS-HMESH/build/v1");
|
|
476
|
+
var functionId = meshMix(buildSalt, dialect.seed);
|
|
477
|
+
var chunkId = dialect.next() >>> 0;
|
|
478
|
+
var constDigest = constantDigest(constants);
|
|
479
|
+
var previousDigest = meshMix(meshMix(buildSalt, functionId), chunkId);
|
|
480
|
+
var streamSalt = dialect.next() >>> 0;
|
|
481
|
+
var flags = 0;
|
|
482
|
+
if (options.bindToVmState !== false) flags |= 1;
|
|
483
|
+
if (options.deriveDialectFromMesh) flags |= 2;
|
|
484
|
+
if (options.encodeChaff !== false) flags |= 4;
|
|
485
|
+
var chaff = options.encodeChaff === false ? [] : makeChaff(dialect.next, record.tokenCount, ratio);
|
|
486
|
+
var mesh = [
|
|
487
|
+
buildSalt >>> 0,
|
|
488
|
+
functionId >>> 0,
|
|
489
|
+
chunkId >>> 0,
|
|
490
|
+
constDigest >>> 0,
|
|
491
|
+
previousDigest >>> 0,
|
|
492
|
+
streamSalt >>> 0,
|
|
493
|
+
flags >>> 0,
|
|
494
|
+
textDigest("DJS-HMESH/chunk-key/v1") >>> 0,
|
|
495
|
+
chaff
|
|
496
|
+
];
|
|
497
|
+
var key = meshKey(mesh, record.base, record.tokenCount, record.seed, record.tag, opValues);
|
|
498
|
+
var cipher = encryptedTokens.map(function (token, index) {
|
|
499
|
+
return (token + meshStream(key, index, record.base, streamSalt)) % record.base;
|
|
500
|
+
});
|
|
501
|
+
record.blob = packTokens(cipher, record.base);
|
|
502
|
+
record.mesh = mesh;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function isSimplePattern(node) {
|
|
506
|
+
return node && node.type === "Identifier";
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function containsNestedFunction(node) {
|
|
510
|
+
var found = false;
|
|
511
|
+
traverser.traverseEx(node, [], function (child) {
|
|
512
|
+
if (child !== node && estest.isFunction(child)) {
|
|
513
|
+
found = true;
|
|
514
|
+
this.abort();
|
|
515
|
+
}
|
|
516
|
+
return child;
|
|
517
|
+
});
|
|
518
|
+
return found;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function Compiler(fn, dialect, options) {
|
|
522
|
+
this.fn = fn;
|
|
523
|
+
this.dialect = dialect;
|
|
524
|
+
this.options = options || {};
|
|
525
|
+
this.instructions = [];
|
|
526
|
+
this.labelId = 0;
|
|
527
|
+
this.params = {};
|
|
528
|
+
this.locals = {};
|
|
529
|
+
this.localCount = 0;
|
|
530
|
+
this.constants = [];
|
|
531
|
+
this.constantKeys = {};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
Compiler.prototype.label = function () { return "L" + this.labelId++; };
|
|
535
|
+
Compiler.prototype.mark = function (name) { this.instructions.push({ label: name }); };
|
|
536
|
+
Compiler.prototype.emit = function (op) { this.instructions.push({ op: op, args: Array.prototype.slice.call(arguments, 1) }); };
|
|
537
|
+
Compiler.prototype.localSlot = function (name) {
|
|
538
|
+
if (!Object.prototype.hasOwnProperty.call(this.locals, name)) this.locals[name] = this.localCount++;
|
|
539
|
+
return this.locals[name];
|
|
540
|
+
};
|
|
541
|
+
Compiler.prototype.addConstant = function (kind, value) {
|
|
542
|
+
var key = kind + ":" + String(value);
|
|
543
|
+
if (Object.prototype.hasOwnProperty.call(this.constantKeys, key)) return this.constantKeys[key];
|
|
544
|
+
var index = this.constants.length;
|
|
545
|
+
this.constantKeys[key] = index;
|
|
546
|
+
this.constants.push({ kind: kind, value: value });
|
|
547
|
+
return index;
|
|
548
|
+
};
|
|
549
|
+
Compiler.prototype.collectLocals = function () {
|
|
550
|
+
var self = this;
|
|
551
|
+
this.fn.params.forEach(function (param, index) {
|
|
552
|
+
if (!isSimplePattern(param)) throw new Error("unsupported parameter pattern");
|
|
553
|
+
self.params[param.name] = index;
|
|
554
|
+
});
|
|
555
|
+
traverser.traverseEx(this.fn.body, [], function (node) {
|
|
556
|
+
if (node !== self.fn.body && estest.isFunction(node)) {
|
|
557
|
+
this.abort();
|
|
558
|
+
return node;
|
|
559
|
+
}
|
|
560
|
+
if (node.type === "VariableDeclarator") {
|
|
561
|
+
if (!isSimplePattern(node.id)) throw new Error("unsupported declaration pattern");
|
|
562
|
+
self.localSlot(node.id.name);
|
|
563
|
+
}
|
|
564
|
+
return node;
|
|
565
|
+
});
|
|
566
|
+
};
|
|
567
|
+
Compiler.prototype.compile = function () {
|
|
568
|
+
if (!this.fn.body || this.fn.body.type !== "BlockStatement") throw new Error("unsupported function body");
|
|
569
|
+
if (containsNestedFunction(this.fn.body)) throw new Error("nested functions are not virtualized");
|
|
570
|
+
this.collectLocals();
|
|
571
|
+
this.compileBlock(this.fn.body);
|
|
572
|
+
this.emit("PUSH_UNDEFINED");
|
|
573
|
+
this.emit("RETURN");
|
|
574
|
+
return this.finish();
|
|
575
|
+
};
|
|
576
|
+
Compiler.prototype.compileBlock = function (block) {
|
|
577
|
+
var self = this;
|
|
578
|
+
block.body.forEach(function (stmt) { self.compileStatement(stmt); });
|
|
579
|
+
};
|
|
580
|
+
Compiler.prototype.compileStatement = function (stmt) {
|
|
581
|
+
switch (stmt.type) {
|
|
582
|
+
case "BlockStatement": this.compileBlock(stmt); return;
|
|
583
|
+
case "VariableDeclaration":
|
|
584
|
+
for (var i = 0; i < stmt.declarations.length; i += 1) {
|
|
585
|
+
var decl = stmt.declarations[i];
|
|
586
|
+
var slot = this.localSlot(decl.id.name);
|
|
587
|
+
if (decl.init) this.compileExpression(decl.init); else this.emit("PUSH_UNDEFINED");
|
|
588
|
+
this.emit("STORE_LOCAL", slot);
|
|
589
|
+
}
|
|
590
|
+
return;
|
|
591
|
+
case "ExpressionStatement": this.compileExpression(stmt.expression); this.emit("POP"); return;
|
|
592
|
+
case "ReturnStatement":
|
|
593
|
+
if (stmt.argument) this.compileExpression(stmt.argument); else this.emit("PUSH_UNDEFINED");
|
|
594
|
+
this.emit("RETURN");
|
|
595
|
+
return;
|
|
596
|
+
case "IfStatement": {
|
|
597
|
+
var elseLabel = this.label();
|
|
598
|
+
var endLabel = this.label();
|
|
599
|
+
this.compileExpression(stmt.test);
|
|
600
|
+
this.emit("JMP_FALSE", elseLabel);
|
|
601
|
+
this.compileStatement(stmt.consequent);
|
|
602
|
+
this.emit("JMP", endLabel);
|
|
603
|
+
this.mark(elseLabel);
|
|
604
|
+
if (stmt.alternate) this.compileStatement(stmt.alternate);
|
|
605
|
+
this.mark(endLabel);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
case "WhileStatement": {
|
|
609
|
+
var start = this.label();
|
|
610
|
+
var end = this.label();
|
|
611
|
+
this.mark(start);
|
|
612
|
+
this.compileExpression(stmt.test);
|
|
613
|
+
this.emit("JMP_FALSE", end);
|
|
614
|
+
this.compileStatement(stmt.body);
|
|
615
|
+
this.emit("JMP", start);
|
|
616
|
+
this.mark(end);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
case "EmptyStatement": return;
|
|
620
|
+
default: throw new Error("unsupported statement " + stmt.type);
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
Compiler.prototype.compileExpression = function (expr) {
|
|
624
|
+
switch (expr.type) {
|
|
625
|
+
case "Literal": this.compileLiteral(expr); return;
|
|
626
|
+
case "Identifier": this.compileIdentifier(expr.name); return;
|
|
627
|
+
case "ThisExpression": this.emit("PUSH_THIS"); return;
|
|
628
|
+
case "ArrayExpression": this.compileArray(expr); return;
|
|
629
|
+
case "ObjectExpression": this.compileObject(expr); return;
|
|
630
|
+
case "UnaryExpression": this.compileUnary(expr); return;
|
|
631
|
+
case "BinaryExpression": this.compileBinary(expr); return;
|
|
632
|
+
case "LogicalExpression": this.compileLogical(expr); return;
|
|
633
|
+
case "AssignmentExpression": this.compileAssignment(expr); return;
|
|
634
|
+
case "MemberExpression": this.compileMember(expr); return;
|
|
635
|
+
case "CallExpression": this.compileCall(expr); return;
|
|
636
|
+
case "ConditionalExpression": this.compileConditional(expr); return;
|
|
637
|
+
case "SequenceExpression":
|
|
638
|
+
for (var i = 0; i < expr.expressions.length; i += 1) {
|
|
639
|
+
this.compileExpression(expr.expressions[i]);
|
|
640
|
+
if (i + 1 < expr.expressions.length) this.emit("POP");
|
|
641
|
+
}
|
|
642
|
+
return;
|
|
643
|
+
default: throw new Error("unsupported expression " + expr.type);
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
Compiler.prototype.compileLiteral = function (expr) {
|
|
647
|
+
if (expr.regex) throw new Error("regex literals are unsupported");
|
|
648
|
+
if (expr.value === null) this.emit("PUSH_NULL");
|
|
649
|
+
else if (expr.value === true) this.emit("PUSH_TRUE");
|
|
650
|
+
else if (expr.value === false) this.emit("PUSH_FALSE");
|
|
651
|
+
else if (typeof expr.value === "number" && Number.isInteger(expr.value) && expr.value >= 0 && expr.value < SMALL_LIMIT) this.emit("PUSH_SMALL", expr.value);
|
|
652
|
+
else this.emit("PUSH_CONST", this.addConstant(typeof expr.value, expr.value));
|
|
653
|
+
};
|
|
654
|
+
Compiler.prototype.compileIdentifier = function (name) {
|
|
655
|
+
if (name === "undefined") this.emit("PUSH_UNDEFINED");
|
|
656
|
+
else if (name === "arguments") this.emit("PUSH_ARGUMENTS");
|
|
657
|
+
else if (Object.prototype.hasOwnProperty.call(this.params, name)) this.emit("LOAD_ARG", this.params[name]);
|
|
658
|
+
else if (Object.prototype.hasOwnProperty.call(this.locals, name)) this.emit("LOAD_LOCAL", this.locals[name]);
|
|
659
|
+
else this.emit("PUSH_CONST", this.addConstant("reference", name));
|
|
660
|
+
};
|
|
661
|
+
Compiler.prototype.compileArray = function (expr) {
|
|
662
|
+
for (var i = 0; i < expr.elements.length; i += 1) {
|
|
663
|
+
if (expr.elements[i] === null) this.emit("PUSH_UNDEFINED");
|
|
664
|
+
else this.compileExpression(expr.elements[i]);
|
|
665
|
+
}
|
|
666
|
+
this.emit("MAKE_ARRAY", expr.elements.length);
|
|
667
|
+
};
|
|
668
|
+
Compiler.prototype.compileObject = function (expr) {
|
|
669
|
+
for (var i = 0; i < expr.properties.length; i += 1) {
|
|
670
|
+
var prop = expr.properties[i];
|
|
671
|
+
if (prop.kind && prop.kind !== "init") throw new Error("unsupported object property kind");
|
|
672
|
+
if (prop.type === "SpreadElement") throw new Error("unsupported object spread");
|
|
673
|
+
var key = prop.computed ? null : prop.key.name || prop.key.value;
|
|
674
|
+
if (key === null) this.compileExpression(prop.key); else this.emit("PUSH_CONST", this.addConstant("string", String(key)));
|
|
675
|
+
this.compileExpression(prop.value);
|
|
676
|
+
}
|
|
677
|
+
this.emit("MAKE_OBJECT", expr.properties.length);
|
|
678
|
+
};
|
|
679
|
+
Compiler.prototype.compileUnary = function (expr) {
|
|
680
|
+
if (expr.operator === "void") {
|
|
681
|
+
this.compileExpression(expr.argument);
|
|
682
|
+
this.emit("POP");
|
|
683
|
+
this.emit("PUSH_UNDEFINED");
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
this.compileExpression(expr.argument);
|
|
687
|
+
if (expr.operator === "-") this.emit("NEG");
|
|
688
|
+
else if (expr.operator === "!") this.emit("NOT");
|
|
689
|
+
else if (expr.operator === "~") this.emit("BIT_NOT");
|
|
690
|
+
else if (expr.operator === "typeof") this.emit("TYPEOF");
|
|
691
|
+
else if (expr.operator !== "+") throw new Error("unsupported unary operator " + expr.operator);
|
|
692
|
+
};
|
|
693
|
+
Compiler.prototype.compileBinary = function (expr) {
|
|
694
|
+
this.compileExpression(expr.left);
|
|
695
|
+
this.compileExpression(expr.right);
|
|
696
|
+
var map = { "+": "ADD", "-": "SUB", "*": "MUL", "/": "DIV", "%": "MOD", "**": "POW", "==": "EQ", "!=": "NEQ", "===": "STRICT_EQ", "!==": "STRICT_NEQ", "<": "LT", "<=": "LTE", ">": "GT", ">=": "GTE" };
|
|
697
|
+
if (!map[expr.operator]) throw new Error("unsupported binary operator " + expr.operator);
|
|
698
|
+
this.emit(map[expr.operator]);
|
|
699
|
+
};
|
|
700
|
+
Compiler.prototype.compileLogical = function (expr) {
|
|
701
|
+
var end = this.label();
|
|
702
|
+
this.compileExpression(expr.left);
|
|
703
|
+
this.emit("DUP");
|
|
704
|
+
this.emit(expr.operator === "&&" ? "JMP_FALSE" : "JMP_TRUE", end);
|
|
705
|
+
this.emit("POP");
|
|
706
|
+
this.compileExpression(expr.right);
|
|
707
|
+
this.mark(end);
|
|
708
|
+
};
|
|
709
|
+
Compiler.prototype.compileAssignment = function (expr) {
|
|
710
|
+
if (expr.left.type === "Identifier") {
|
|
711
|
+
if (!Object.prototype.hasOwnProperty.call(this.locals, expr.left.name)) throw new Error("unsupported assignment target " + expr.left.name);
|
|
712
|
+
if (expr.operator === "=") this.compileExpression(expr.right);
|
|
713
|
+
else {
|
|
714
|
+
var map = { "+=": "ADD", "-=": "SUB", "*=": "MUL", "/=": "DIV", "%=": "MOD" };
|
|
715
|
+
if (!map[expr.operator]) throw new Error("unsupported assignment operator " + expr.operator);
|
|
716
|
+
this.compileIdentifier(expr.left.name);
|
|
717
|
+
this.compileExpression(expr.right);
|
|
718
|
+
this.emit(map[expr.operator]);
|
|
719
|
+
}
|
|
720
|
+
this.emit("DUP");
|
|
721
|
+
this.emit("STORE_LOCAL", this.locals[expr.left.name]);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (expr.left.type === "MemberExpression" && expr.operator === "=") {
|
|
725
|
+
this.compileExpression(expr.left.object);
|
|
726
|
+
this.compilePropertyKey(expr.left);
|
|
727
|
+
this.compileExpression(expr.right);
|
|
728
|
+
this.emit("SET_PROP");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
throw new Error("unsupported assignment expression");
|
|
732
|
+
};
|
|
733
|
+
Compiler.prototype.compilePropertyKey = function (expr) {
|
|
734
|
+
if (expr.computed) this.compileExpression(expr.property);
|
|
735
|
+
else this.emit("PUSH_CONST", this.addConstant("string", expr.property.name));
|
|
736
|
+
};
|
|
737
|
+
Compiler.prototype.compileMember = function (expr) {
|
|
738
|
+
this.compileExpression(expr.object);
|
|
739
|
+
this.compilePropertyKey(expr);
|
|
740
|
+
this.emit("GET_PROP");
|
|
741
|
+
};
|
|
742
|
+
Compiler.prototype.compileCall = function (expr) {
|
|
743
|
+
if (expr.callee.type === "MemberExpression") {
|
|
744
|
+
this.compileExpression(expr.callee.object);
|
|
745
|
+
this.compilePropertyKey(expr.callee);
|
|
746
|
+
for (var i = 0; i < expr.arguments.length; i += 1) this.compileExpression(expr.arguments[i]);
|
|
747
|
+
this.emit("CALL_METHOD", expr.arguments.length);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
this.compileExpression(expr.callee);
|
|
751
|
+
for (var j = 0; j < expr.arguments.length; j += 1) this.compileExpression(expr.arguments[j]);
|
|
752
|
+
this.emit("CALL_EXT", 0, expr.arguments.length);
|
|
753
|
+
};
|
|
754
|
+
Compiler.prototype.compileConditional = function (expr) {
|
|
755
|
+
var alternate = this.label();
|
|
756
|
+
var end = this.label();
|
|
757
|
+
this.compileExpression(expr.test);
|
|
758
|
+
this.emit("JMP_FALSE", alternate);
|
|
759
|
+
this.compileExpression(expr.consequent);
|
|
760
|
+
this.emit("JMP", end);
|
|
761
|
+
this.mark(alternate);
|
|
762
|
+
this.compileExpression(expr.alternate);
|
|
763
|
+
this.mark(end);
|
|
764
|
+
};
|
|
765
|
+
Compiler.prototype.instructionSize = function (instr, positions) {
|
|
766
|
+
if (instr.label) return 0;
|
|
767
|
+
var start = positions.get(instr) || 0;
|
|
768
|
+
var size = 1;
|
|
769
|
+
for (var i = 0; i < instr.args.length; i += 1) {
|
|
770
|
+
var arg = instr.args[i];
|
|
771
|
+
if (typeof arg === "string") size += signedLengthFor(positions.get(arg) || 0, start, size);
|
|
772
|
+
else size += encodeUnsigned(arg).length;
|
|
773
|
+
}
|
|
774
|
+
return size;
|
|
775
|
+
};
|
|
776
|
+
Compiler.prototype.assemble = function () {
|
|
777
|
+
var positions = new Map();
|
|
778
|
+
var stable = false;
|
|
779
|
+
while (!stable) {
|
|
780
|
+
stable = true;
|
|
781
|
+
var cursor = 0;
|
|
782
|
+
for (var i = 0; i < this.instructions.length; i += 1) {
|
|
783
|
+
var instr = this.instructions[i];
|
|
784
|
+
if (instr.label) {
|
|
785
|
+
if (positions.get(instr.label) !== cursor) stable = false;
|
|
786
|
+
positions.set(instr.label, cursor);
|
|
787
|
+
} else {
|
|
788
|
+
positions.set(instr, cursor);
|
|
789
|
+
cursor += this.instructionSize(instr, positions);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
var tokens = [];
|
|
794
|
+
for (var j = 0; j < this.instructions.length; j += 1) {
|
|
795
|
+
var op = this.instructions[j];
|
|
796
|
+
if (op.label) continue;
|
|
797
|
+
var start = tokens.length;
|
|
798
|
+
tokens.push(this.dialect.opcodes[op.op]);
|
|
799
|
+
for (var k = 0; k < op.args.length; k += 1) {
|
|
800
|
+
var arg = op.args[k];
|
|
801
|
+
if (typeof arg === "string") {
|
|
802
|
+
var before = tokens.length - start;
|
|
803
|
+
var len = signedLengthFor(positions.get(arg), start, before);
|
|
804
|
+
var rel = positions.get(arg) - (start + before + len);
|
|
805
|
+
encodeSigned(rel).forEach(function (value) { tokens.push(value); });
|
|
806
|
+
} else {
|
|
807
|
+
encodeUnsigned(arg).forEach(function (value) { tokens.push(value); });
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
return tokens;
|
|
812
|
+
};
|
|
813
|
+
Compiler.prototype.constantExpression = function (constant) {
|
|
814
|
+
var next = this.dialect.next;
|
|
815
|
+
if (constant.kind === "number") {
|
|
816
|
+
if (Number.isNaN(constant.value)) return binary("/", literal(0), literal(0));
|
|
817
|
+
if (constant.value === Infinity) return binary("/", literal(1), literal(0));
|
|
818
|
+
if (constant.value === -Infinity) return { type: "UnaryExpression", operator: "-", prefix: true, argument: binary("/", literal(1), literal(0)) };
|
|
819
|
+
return literal(constant.value);
|
|
820
|
+
}
|
|
821
|
+
if (constant.kind === "string" || constant.kind === "reference") {
|
|
822
|
+
var value = String(constant.value);
|
|
823
|
+
var salt = (next() & 65535) || 1;
|
|
824
|
+
var decoded = call(identifier("veilmark$numericVmString"), [ bigintExpression(stringBlob(value, salt), next), literal(value.length), literal(salt) ]);
|
|
825
|
+
if (constant.kind === "reference") {
|
|
826
|
+
return {
|
|
827
|
+
type: "ConditionalExpression",
|
|
828
|
+
test: binary("===", unary("typeof", identifier(value)), literal("undefined")),
|
|
829
|
+
consequent: member(identifier("globalThis"), decoded),
|
|
830
|
+
alternate: identifier(value)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
return decoded;
|
|
834
|
+
}
|
|
835
|
+
if (constant.kind === "boolean") return literal(!!constant.value);
|
|
836
|
+
if (constant.kind === "undefined") return { type: "UnaryExpression", operator: "void", prefix: true, argument: literal(0) };
|
|
837
|
+
throw new Error("unsupported constant " + constant.kind);
|
|
838
|
+
};
|
|
839
|
+
Compiler.prototype.finish = function () {
|
|
840
|
+
var tokens = this.assemble();
|
|
841
|
+
var encrypted = encryptedStream(tokens, this.dialect.base, this.dialect.seed);
|
|
842
|
+
var opValues = OP_NAMES.map(name => this.dialect.opcodes[name]);
|
|
843
|
+
var record = {
|
|
844
|
+
base: this.dialect.base,
|
|
845
|
+
blob: packTokens(encrypted.encrypted, this.dialect.base),
|
|
846
|
+
constants: this.constants.map(this.constantExpression.bind(this)),
|
|
847
|
+
opValues: opValues.map(literal),
|
|
848
|
+
seed: this.dialect.seed,
|
|
849
|
+
tag: encrypted.tag,
|
|
850
|
+
tokenCount: tokens.length
|
|
851
|
+
};
|
|
852
|
+
if (this.options.hashMesh && this.options.hashMesh.enabled) {
|
|
853
|
+
buildHashMeshRecord(record, encrypted.encrypted, opValues, this.constants, this.dialect, Object.assign({}, this.options.hashMesh, {
|
|
854
|
+
seed: this.options.seed
|
|
855
|
+
}));
|
|
856
|
+
}
|
|
857
|
+
return record;
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
function makeDialect(seedText) {
|
|
861
|
+
var seed = hashSeed(seedText);
|
|
862
|
+
var next = makeRng(seed);
|
|
863
|
+
var values = shuffle(Array.from({ length: OP_NAMES.length }, function (_, index) { return index + 1; }), next);
|
|
864
|
+
var opcodes = {};
|
|
865
|
+
OP_NAMES.forEach(function (name, index) { opcodes[name] = values[index]; });
|
|
866
|
+
return { base: BASES[next() % BASES.length], next: next, opcodes: opcodes, seed: seed };
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function vmCall(record, next) {
|
|
870
|
+
return call(identifier("veilmark$numericVmRun"), [
|
|
871
|
+
bigintExpression(record.blob, next),
|
|
872
|
+
literal(record.base),
|
|
873
|
+
literal(record.tokenCount),
|
|
874
|
+
literal(record.seed),
|
|
875
|
+
literal(record.tag),
|
|
876
|
+
arrayExpression(record.constants),
|
|
877
|
+
identifier("arguments"),
|
|
878
|
+
{ type: "ThisExpression" },
|
|
879
|
+
arrayExpression(record.opValues),
|
|
880
|
+
record.mesh ? meshExpression(record.mesh) : literal(null)
|
|
881
|
+
]);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function resolveOptions(options) {
|
|
885
|
+
return Object.assign({
|
|
886
|
+
enabled: false,
|
|
887
|
+
maxFunctionSize: 120,
|
|
888
|
+
minFunctionSize: 1,
|
|
889
|
+
mode: "balanced",
|
|
890
|
+
seed: "toildefender-numeric-vm",
|
|
891
|
+
hashMesh: {
|
|
892
|
+
bindToVmState: true,
|
|
893
|
+
chaffRatio: 0.55,
|
|
894
|
+
deriveDialectFromMesh: false,
|
|
895
|
+
enabled: false,
|
|
896
|
+
encodeChaff: true,
|
|
897
|
+
mode: "balanced",
|
|
898
|
+
serverBound: false,
|
|
899
|
+
unlock: "per-function"
|
|
900
|
+
},
|
|
901
|
+
virtualize: "marked"
|
|
902
|
+
}, options || {});
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
module.exports = class NumericVm {
|
|
906
|
+
constructor(logger, options) {
|
|
907
|
+
this.logger = logger;
|
|
908
|
+
this.options = resolveOptions(options);
|
|
909
|
+
this.count = 0;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
shouldTry(node) {
|
|
913
|
+
if (!this.options.enabled || !estest.isFunction(node) || node.generator || node.async) return false;
|
|
914
|
+
if (!node.body || node.body.type !== "BlockStatement") return false;
|
|
915
|
+
if (functionName(node).indexOf("veilmark$numericVm") === 0) return false;
|
|
916
|
+
var bodySize = node.body.body.length;
|
|
917
|
+
if (bodySize < this.options.minFunctionSize || bodySize > this.options.maxFunctionSize) return false;
|
|
918
|
+
if (this.options.virtualize === "all-supported") return true;
|
|
919
|
+
if (this.options.virtualize === "heuristic") return bodySize >= this.options.minFunctionSize;
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
apply(ast) {
|
|
924
|
+
assert.ok(estest.isNode(ast));
|
|
925
|
+
if (!this.options.enabled) return ast;
|
|
926
|
+
|
|
927
|
+
var runtime = replaceStaticBigIntCalls(esprima.parse(RUNTIME));
|
|
928
|
+
var self = this;
|
|
929
|
+
var transformed = 0;
|
|
930
|
+
|
|
931
|
+
ast = traverser.traverse(ast, [], function (node) {
|
|
932
|
+
if (!self.shouldTry(node)) return node;
|
|
933
|
+
try {
|
|
934
|
+
var dialect = makeDialect(self.options.seed + ":" + transformed + ":" + functionName(node));
|
|
935
|
+
var record = new Compiler(node, dialect, self.options).compile();
|
|
936
|
+
node.body = { type: "BlockStatement", body: [ returnStatement(vmCall(record, dialect.next)) ] };
|
|
937
|
+
transformed += 1;
|
|
938
|
+
} catch (error) {
|
|
939
|
+
if (self.options.virtualize === "all-supported") self.logger.warn("numeric_vm skipped " + functionName(node) + ": " + error.message);
|
|
940
|
+
}
|
|
941
|
+
return node;
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
if (transformed > 0) {
|
|
945
|
+
runtime.body.reverse().forEach(function (node) { ast.body.unshift(node); });
|
|
946
|
+
}
|
|
947
|
+
this.count = transformed;
|
|
948
|
+
return ast;
|
|
949
|
+
}
|
|
950
|
+
};
|