Newest 16:33
This commit is contained in:
parent
6d9e2d7c97
commit
6eb4a6e0d6
@ -13,7 +13,7 @@ class ObfuscatorService {
|
||||
let i = 0;
|
||||
while (i < code.length) {
|
||||
let c = code[i];
|
||||
if (/\\s/.test(c)) { i++; continue; }
|
||||
if (/\s/.test(c)) { i++; continue; }
|
||||
if (c === '-' && code[i + 1] === '-') {
|
||||
i += 2;
|
||||
if (code[i] === '[' && code[i + 1] === '[') {
|
||||
@ -28,15 +28,15 @@ class ObfuscatorService {
|
||||
if (c === '"' || c === "'") {
|
||||
const q = c; let s = ""; i++;
|
||||
while (i < code.length && code[i] !== q) {
|
||||
if (code[i] === '\\') { s += code[i] + (code[i + 1] || ""); i += 2; }
|
||||
if (code[i] === '\\') { s += code[i] + (code[i + 1] || ""); i += 2; }
|
||||
else { s += code[i]; i++; }
|
||||
}
|
||||
tokens.push({ type: 'string', value: s });
|
||||
i++; continue;
|
||||
}
|
||||
if (/\\d/.test(c)) {
|
||||
if (/\d/.test(c)) {
|
||||
let n = "";
|
||||
while (i < code.length && /[\\d\.xX]/.test(code[i])) { n += code[i]; i++; }
|
||||
while (i < code.length && /[\d\.\xX]/.test(code[i])) { n += code[i]; i++; }
|
||||
tokens.push({ type: 'number', value: parseFloat(n) });
|
||||
continue;
|
||||
}
|
||||
@ -51,6 +51,32 @@ class ObfuscatorService {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static numberToExpression(n) {
|
||||
if (typeof n !== 'number') return n;
|
||||
const ops = ['+', '-', '*'];
|
||||
let current = Math.floor(Math.random() * 200) - 100;
|
||||
let expr = `(${current})`;
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const op = ops[Math.floor(Math.random() * ops.length)];
|
||||
const val = Math.floor(Math.random() * 100) + 1;
|
||||
if (op === '+') { current += val; expr = `(${expr} + ${val})`; }
|
||||
else if (op === '-') { current -= val; expr = `(${expr} - ${val})`; }
|
||||
else if (op === '*') {
|
||||
if (Math.abs(current * val) < 1000000) {
|
||||
current *= val; expr = `(${expr} * ${val})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const diff = n - current;
|
||||
if (diff >= 0) expr = `(${expr} + ${diff})`;
|
||||
else expr = `(${expr} - ${Math.abs(diff)})`;
|
||||
|
||||
const noise = Math.floor(Math.random() * 255);
|
||||
return `bit32.bxor(bit32.bxor(${expr}, ${noise}), ${noise})`;
|
||||
}
|
||||
|
||||
static compile(tokens) {
|
||||
const constants = [];
|
||||
const getC = (v) => {
|
||||
@ -58,65 +84,119 @@ class ObfuscatorService {
|
||||
if (idx === -1) { idx = constants.length; constants.push({ value: v, type: typeof v }); }
|
||||
return idx;
|
||||
};
|
||||
const ops = {LOADK:0, GETG:1, SETG:2, CALL:3, RET:4, GETT:5, GETS:6};
|
||||
const ops = {
|
||||
LOADK: 0, GETG: 1, SETG: 2, CALL: 3, RET: 4, GETT: 5, GETS: 6,
|
||||
NEWTABLE: 7, SETTABLE: 8, GETLOCAL: 9, SETLOCAL: 10
|
||||
};
|
||||
const bc = [];
|
||||
let ti = 0;
|
||||
const pExpr = (reg) => {
|
||||
let t = tokens[ti]; if (!t) return;
|
||||
const locals = {};
|
||||
let nextLocal = 0;
|
||||
|
||||
const peek = () => tokens[ti];
|
||||
const consume = () => tokens[ti++];
|
||||
|
||||
const parseExpr = (reg) => {
|
||||
let t = peek(); if (!t) return;
|
||||
if (t.type === 'number' || t.type === 'string') {
|
||||
bc.push(ops.LOADK, reg, getC(t.value)); ti++;
|
||||
bc.push(ops.LOADK, reg, getC(consume().value));
|
||||
} else if (t.type === 'identifier') {
|
||||
const name = t.value; ti++;
|
||||
if (tokens[ti]?.value === '(') { ti--; pCall(reg); }
|
||||
else if (tokens[ti]?.value === ':') {
|
||||
ti++; const m = tokens[ti].value; ti++;
|
||||
bc.push(ops.GETG, reg, getC(name));
|
||||
bc.push(ops.GETS, reg, reg, getC(m));
|
||||
pArgs(reg, true);
|
||||
} else if (tokens[ti]?.value === '.') {
|
||||
bc.push(ops.GETG, reg, getC(name));
|
||||
while (tokens[ti]?.value === '.') {
|
||||
ti++; const k = tokens[ti].value; ti++;
|
||||
bc.push(ops.GETT, reg, reg, getC(k));
|
||||
let name = consume().value;
|
||||
if (peek()?.value === '(') {
|
||||
ti--; parseCall(reg);
|
||||
} else {
|
||||
emitGet(reg, name);
|
||||
while (peek()?.value === '.' || peek()?.value === ':') {
|
||||
let op = consume().value;
|
||||
let key = consume().value;
|
||||
if (op === ':') {
|
||||
bc.push(ops.GETS, reg, reg, getC(key));
|
||||
parseArgs(reg, true);
|
||||
} else {
|
||||
bc.push(ops.GETT, reg, reg, getC(key));
|
||||
if (peek()?.value === '(') parseArgs(reg);
|
||||
}
|
||||
}
|
||||
} else { bc.push(ops.GETG, reg, getC(name)); }
|
||||
}
|
||||
} else if (t.value === '{') {
|
||||
parseTable(reg);
|
||||
}
|
||||
};
|
||||
const pArgs = (reg, isM) => {
|
||||
if (tokens[ti]?.value !== '(') return;
|
||||
ti++; let ac = isM ? 1 : 0;
|
||||
while (ti < tokens.length && tokens[ti].value !== ')') {
|
||||
if (tokens[ti].value === ',') { ti++; continue; }
|
||||
ac++; pExpr(reg + ac);
|
||||
|
||||
const parseTable = (reg) => {
|
||||
bc.push(ops.NEWTABLE, reg); consume(); // {
|
||||
while (ti < tokens.length && peek().value !== '}') {
|
||||
if (peek().value === ',') { consume(); continue; }
|
||||
let t = peek();
|
||||
if (tokens[ti+1]?.value === '=') {
|
||||
let key = consume().value; consume(); // =
|
||||
parseExpr(reg + 1);
|
||||
bc.push(ops.SETTABLE, reg, getC(key), reg + 1);
|
||||
} else {
|
||||
parseExpr(reg + 1);
|
||||
}
|
||||
if (peek()?.value === ',') consume();
|
||||
}
|
||||
if (peek()?.value === '}') consume();
|
||||
};
|
||||
|
||||
const emitGet = (reg, name) => {
|
||||
if (locals[name] !== undefined) bc.push(ops.GETLOCAL, reg, locals[name]);
|
||||
else bc.push(ops.GETG, reg, getC(name));
|
||||
};
|
||||
|
||||
const parseArgs = (reg, isM) => {
|
||||
if (peek()?.value !== '(') return;
|
||||
consume(); let ac = isM ? 1 : 0;
|
||||
while (ti < tokens.length && peek().value !== ')') {
|
||||
if (peek().value === ',') { consume(); continue; }
|
||||
ac++; parseExpr(reg + ac);
|
||||
}
|
||||
bc.push(ops.CALL, reg, ac + 1, 1);
|
||||
if (tokens[ti]?.value === ')') ti++;
|
||||
if (peek()?.value === ')') consume();
|
||||
};
|
||||
const pCall = (reg) => {
|
||||
const fn = tokens[ti].value; ti++;
|
||||
bc.push(ops.GETG, reg, getC(fn)); pArgs(reg);
|
||||
|
||||
const parseCall = (reg) => {
|
||||
let fn = consume().value;
|
||||
emitGet(reg, fn);
|
||||
parseArgs(reg);
|
||||
};
|
||||
|
||||
while (ti < tokens.length) {
|
||||
let t = tokens[ti];
|
||||
if (t.type === 'identifier' && tokens[ti+1]?.value === '=') {
|
||||
const vn = t.value; ti += 2; pExpr(0);
|
||||
bc.push(ops.SETG, 0, getC(vn)); continue;
|
||||
let t = peek();
|
||||
if (t.value === 'local') {
|
||||
consume();
|
||||
let name = consume().value;
|
||||
if (peek()?.value === '=') {
|
||||
consume(); parseExpr(nextLocal);
|
||||
locals[name] = nextLocal++;
|
||||
} else {
|
||||
locals[name] = nextLocal++;
|
||||
}
|
||||
} else if (t.type === 'identifier' && tokens[ti+1]?.value === '=') {
|
||||
let vn = consume().value; consume();
|
||||
parseExpr(nextLocal);
|
||||
if (locals[vn] !== undefined) bc.push(ops.SETLOCAL, locals[vn], nextLocal);
|
||||
else bc.push(ops.SETG, nextLocal, getC(vn));
|
||||
} else if (t.type === 'identifier' && (tokens[ti+1]?.value === '(' || tokens[ti+1]?.value === ':')) {
|
||||
parseExpr(nextLocal);
|
||||
} else {
|
||||
ti++;
|
||||
}
|
||||
if (t.type === 'identifier' && (tokens[ti+1]?.value === '(' || tokens[ti+1]?.value === ':')) {
|
||||
pExpr(0); continue;
|
||||
}
|
||||
ti++;
|
||||
}
|
||||
bc.push(ops.RET, 0, 1);
|
||||
return { bc, constants, ops };
|
||||
}
|
||||
|
||||
static generateVM(bc, constants, ops, seed) {
|
||||
const r = () => "_" + Math.random().toString(36).substring(7);
|
||||
const nL = r(), nC = r(), nR = r(), nPC = r(), nK = r(), nD = r(), nDS = r();
|
||||
const r = () => "_" + Math.random().toString(36).substring(7) + "_" + Math.floor(Math.random() * 10000);
|
||||
const nL = r(), nC = r(), nR = r(), nPC = r(), nK = r(), nD = r(), nDS = r(), nLOC = r();
|
||||
const nENV = r(), nTIME = r(), nX = r(), nY = r();
|
||||
|
||||
const encC = constants.map(c => {
|
||||
if (c.type === 'string') {
|
||||
const e = Buffer.from(c.value).map(b => b ^ (seed & 0xFF)).toString('base64');
|
||||
const key = seed & 0xFF;
|
||||
const e = Buffer.from(c.value).map(b => b ^ key).toString('base64');
|
||||
return `{t=1,v="${e}"}`;
|
||||
}
|
||||
if (c.type === 'number') {
|
||||
@ -125,7 +205,33 @@ class ObfuscatorService {
|
||||
}
|
||||
return `{t=0}`;
|
||||
}).join(',');
|
||||
|
||||
const xorKey = seed % 255;
|
||||
|
||||
const antiTamper = `
|
||||
local ${nENV} = getfenv and getfenv() or _G
|
||||
local ${nTIME} = tick()
|
||||
local function check()
|
||||
if ${nENV}.debug and ${nENV}.debug.getinfo then
|
||||
local i = ${nENV}.debug.getinfo(check)
|
||||
if i.what ~= "Lua" then while true do end end
|
||||
end
|
||||
local s = tick()
|
||||
for i=1,1000 do end
|
||||
if tick() - s > 1 then while true do end end
|
||||
end
|
||||
check()
|
||||
task.spawn(function()
|
||||
while true do
|
||||
if tick() - ${nTIME} > 4 then
|
||||
local ${nX} = nil; ${nX}.crash()
|
||||
end
|
||||
${nTIME} = tick()
|
||||
task.wait(3)
|
||||
end
|
||||
end)
|
||||
`;
|
||||
|
||||
const d = [];
|
||||
d[ops.LOADK] = `function() ${nR}[${nL}[${nPC}]] = ${nD}(${nL}[${nPC}+1]); ${nPC}=${nPC}+2 end`;
|
||||
d[ops.GETG] = `function() ${nR}[${nL}[${nPC}]] = _G[${nD}(${nL}[${nPC}+1])]; ${nPC}=${nPC}+2 end`;
|
||||
@ -134,21 +240,49 @@ class ObfuscatorService {
|
||||
d[ops.GETS] = `function() local r,t,k = ${nL}[${nPC}],${nL}[${nPC}+1],${nL}[${nPC}+2]; ${nPC}=${nPC}+3; ${nR}[r+1] = ${nR}[t]; ${nR}[r] = ${nR}[t][${nD}(k)] end`;
|
||||
d[ops.CALL] = `function() local r,na,nr = ${nL}[${nPC}],${nL}[${nPC}+1],${nL}[${nPC}+2]; ${nPC}=${nPC}+3; local a={}; for i=1,na-1 do a[i]=${nR}[r+i] end; ${nR}[r]=${nR}[r](table.unpack(a)) end`;
|
||||
d[ops.RET] = `function() return "EXIT" end`;
|
||||
d[ops.NEWTABLE] = `function() ${nR}[${nL}[${nPC}]] = {}; ${nPC}=${nPC}+1 end`;
|
||||
d[ops.SETTABLE] = `function() local t,k,v = ${nL}[${nPC}],${nL}[${nPC}+1],${nL}[${nPC}+2]; ${nPC}=${nPC}+3; ${nR}[t][${nD}(k)] = ${nR}[v] end`;
|
||||
d[ops.GETLOCAL] = `function() ${nR}[${nL}[${nPC}]] = ${nLOC}[${nL}[${nPC}+1]+1]; ${nPC}=${nPC}+2 end`;
|
||||
d[ops.SETLOCAL] = `function() ${nLOC}[${nL}[${nPC}]+1] = ${nR}[${nL}[${nPC}+1]]; ${nPC}=${nPC}+2 end`;
|
||||
|
||||
const xorKey = seed % 255;
|
||||
const argCounts = {
|
||||
[ops.LOADK]: 2, [ops.GETG]: 2, [ops.SETG]: 2, [ops.GETT]: 3,
|
||||
[ops.GETS]: 3, [ops.CALL]: 3, [ops.RET]: 2, [ops.NEWTABLE]: 1,
|
||||
[ops.SETTABLE]: 3, [ops.GETLOCAL]: 2, [ops.SETLOCAL]: 2
|
||||
};
|
||||
|
||||
return `local function VM(...)
|
||||
local ${nL} = {${bc.join(',')}}
|
||||
const shuffled = Object.keys(ops).sort(() => Math.random() - 0.5);
|
||||
const opMap = {};
|
||||
shuffled.forEach((k, i) => opMap[ops[k]] = i);
|
||||
|
||||
const finalBC = [];
|
||||
for (let i = 0; i < bc.length; ) {
|
||||
const op = bc[i];
|
||||
finalBC.push(opMap[op]);
|
||||
i++;
|
||||
const count = argCounts[op] || 0;
|
||||
for (let j = 0; j < count; j++) {
|
||||
finalBC.push(bc[i] || 0);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
const dispatchFunctions = shuffled.map(k => d[ops[k]] || "function() end").join(',');
|
||||
|
||||
const vmCode = `local function VM(...)
|
||||
${antiTamper}
|
||||
local ${nL} = {${finalBC.join(',')}}
|
||||
local ${nC} = {${encC}}
|
||||
local ${nLOC} = {}
|
||||
local ${nR} = setmetatable({}, {
|
||||
__index = function(t, k) return rawget(t, bit32.bxor(k, ${xorKey})) end,
|
||||
__newindex = function(t, k, v) rawset(t, bit32.bxor(k, ${xorKey}), v) end
|
||||
})
|
||||
local ${nPC} = 1; local ${nK} = ${seed}
|
||||
local ${nPC} = ${this.numberToExpression(1)}; local ${nK} = ${this.numberToExpression(seed)}
|
||||
local function ${nD}(i)
|
||||
local c = ${nC}[i+1]; if not c then return end
|
||||
if c.t == 1 then
|
||||
local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
local s = c.v:gsub("[^"..b.."=]", ""); local res = ""
|
||||
for j=1,#s,4 do
|
||||
local v = 0
|
||||
@ -158,13 +292,39 @@ class ObfuscatorService {
|
||||
return res
|
||||
elseif c.t == 2 then return bit32.bxor(c.v, bit32.band(${nK}, 65535)) - c.o end
|
||||
end
|
||||
local ${nDS} = {${d.join(',')}}
|
||||
while ${nL}[${nPC}] do
|
||||
local op = ${nL}[${nPC}]; ${nPC} = ${nPC} + 1
|
||||
local ${nDS} = {${dispatchFunctions}}
|
||||
while true do
|
||||
local op = ${nL}[${nPC}]; if not op then break end
|
||||
${nPC} = ${nPC} + 1
|
||||
local f = ${nDS}[op+1]
|
||||
if f then if f() == "EXIT" then break end else break end
|
||||
end
|
||||
end; VM(...)`
|
||||
end; VM(...)`;
|
||||
|
||||
const wrapLayers = (code, layers) => {
|
||||
let current = code;
|
||||
for (let i = 0; i < layers; i++) {
|
||||
const layerSeed = crypto.randomBytes(4).readUInt32BE(0);
|
||||
const encoded = Buffer.from(current).map(b => b ^ (layerSeed & 0xFF)).toString('base64');
|
||||
current = `local _v = "${encoded}"
|
||||
local _s = ${this.numberToExpression(layerSeed)}
|
||||
local function _d(s)
|
||||
local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
local res = ""
|
||||
for j=1,#s,4 do
|
||||
local v = 0
|
||||
for k=0,3 do local char = s:sub(j+k,j+k); v = v*64 + (char=="=" and 0 or (b:find(char,1,true)-1)) end
|
||||
for k=2,0,-1 do if j+3-k<=#s and s:sub(j+3-k,j+3-k)~="=" then res = res..string.char(bit32.bxor(bit32.extract(v,k*8,8),bit32.band(_s,255))) end end
|
||||
end
|
||||
return res
|
||||
end
|
||||
local _f = loadstring(_d(_v))
|
||||
if _f then _f() end`;
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
return wrapLayers(vmCode, 2);
|
||||
}
|
||||
}
|
||||
module.exports = ObfuscatorService;
|
||||
Loading…
x
Reference in New Issue
Block a user