Autosave: 20260125-152826
This commit is contained in:
parent
6628329bbd
commit
6d9e2d7c97
@ -3,214 +3,168 @@ const crypto = require('crypto');
|
||||
class ObfuscatorService {
|
||||
static obfuscate(sourceCode, config = {}) {
|
||||
const seed = crypto.randomBytes(4).readUInt32BE(0);
|
||||
const opcodeMap = this.generateRandomOpcodeMap(seed);
|
||||
const { bytecode, constants } = this.compile(sourceCode, opcodeMap);
|
||||
return this.generateVMRunner(bytecode, constants, opcodeMap, seed, config);
|
||||
const tokens = this.tokenize(sourceCode);
|
||||
const { bc, constants, ops } = this.compile(tokens);
|
||||
return this.generateVM(bc, constants, ops, seed);
|
||||
}
|
||||
|
||||
static generateRandomOpcodeMap(seed) {
|
||||
const baseOpcodes = [
|
||||
'LOADK', 'GETGLOBAL', 'SETGLOBAL', 'CALL', 'RETURN',
|
||||
'MOVE', 'ADD', 'SUB', 'MUL', 'DIV', 'MOD', 'POW',
|
||||
'UNM', 'NOT', 'LEN', 'CONCAT', 'JMP', 'EQ', 'LT', 'LE',
|
||||
'FORLOOP', 'FORPREP', 'TFORCALL', 'TFORLOOP', 'SETLIST',
|
||||
'CLOSE', 'CLOSURE', 'VARARG', 'FAKE'
|
||||
];
|
||||
|
||||
// Add many fake opcodes for "noise"
|
||||
for (let i = 0; i < 30; i++) {
|
||||
baseOpcodes.push(`NOISE_${i}`);
|
||||
}
|
||||
|
||||
const shuffled = [...baseOpcodes].sort(() => this.seededRandom(seed++) - 0.5);
|
||||
const map = {};
|
||||
shuffled.forEach((op, index) => {
|
||||
map[op] = index + 5; // Offset by 5
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
static seededRandom(seed) {
|
||||
const x = Math.sin(seed) * 10000;
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
static compile(code, opcodeMap) {
|
||||
const constants = [];
|
||||
const bytecode = [];
|
||||
|
||||
const getConstant = (val) => {
|
||||
let idx = constants.indexOf(val);
|
||||
if (idx === -1) {
|
||||
idx = constants.length;
|
||||
constants.push(val);
|
||||
static tokenize(code) {
|
||||
const tokens = [];
|
||||
let i = 0;
|
||||
while (i < code.length) {
|
||||
let c = code[i];
|
||||
if (/\\s/.test(c)) { i++; continue; }
|
||||
if (c === '-' && code[i + 1] === '-') {
|
||||
i += 2;
|
||||
if (code[i] === '[' && code[i + 1] === '[') {
|
||||
i += 2;
|
||||
while (i < code.length && !(code[i] === ']' && code[i + 1] === ']')) i++;
|
||||
i += 2;
|
||||
} else {
|
||||
while (i < code.length && code[i] !== '\n') i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
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; }
|
||||
else { s += code[i]; i++; }
|
||||
}
|
||||
tokens.push({ type: 'string', value: s });
|
||||
i++; continue;
|
||||
}
|
||||
if (/\\d/.test(c)) {
|
||||
let n = "";
|
||||
while (i < code.length && /[\\d\.xX]/.test(code[i])) { n += code[i]; i++; }
|
||||
tokens.push({ type: 'number', value: parseFloat(n) });
|
||||
continue;
|
||||
}
|
||||
if (/[a-zA-Z_]/.test(c)) {
|
||||
let id = "";
|
||||
while (i < code.length && /[a-zA-Z0-9_]/.test(code[i])) { id += code[i]; i++; }
|
||||
tokens.push({ type: 'identifier', value: id });
|
||||
continue;
|
||||
}
|
||||
tokens.push({ type: 'operator', value: c }); i++;
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
static compile(tokens) {
|
||||
const constants = [];
|
||||
const getC = (v) => {
|
||||
let idx = constants.findIndex(c => c.value === v);
|
||||
if (idx === -1) { idx = constants.length; constants.push({ value: v, type: typeof v }); }
|
||||
return idx;
|
||||
};
|
||||
|
||||
const lines = code.split('\n').map(l => l.trim()).filter(l => l.length > 0 && !l.startsWith('--'));
|
||||
|
||||
lines.forEach(line => {
|
||||
// Basic call: print("hello")
|
||||
const callMatch = line.match(/^(\w+)\s*\(.*\)$/);
|
||||
if (callMatch) {
|
||||
const funcName = callMatch[1];
|
||||
const argsStr = line.match(/\((.*)\)/)?.[1] || "";
|
||||
const args = argsStr ? this.splitArgs(argsStr) : [];
|
||||
|
||||
bytecode.push(opcodeMap['GETGLOBAL'], 0, getConstant(funcName));
|
||||
args.forEach((arg, i) => {
|
||||
const reg = i + 1;
|
||||
this.pushValueToReg(arg, reg, bytecode, opcodeMap, getConstant);
|
||||
});
|
||||
bytecode.push(opcodeMap['CALL'], 0, args.length + 1, 1);
|
||||
return;
|
||||
const ops = {LOADK:0, GETG:1, SETG:2, CALL:3, RET:4, GETT:5, GETS:6};
|
||||
const bc = [];
|
||||
let ti = 0;
|
||||
const pExpr = (reg) => {
|
||||
let t = tokens[ti]; if (!t) return;
|
||||
if (t.type === 'number' || t.type === 'string') {
|
||||
bc.push(ops.LOADK, reg, getC(t.value)); ti++;
|
||||
} 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));
|
||||
}
|
||||
} else { bc.push(ops.GETG, reg, getC(name)); }
|
||||
}
|
||||
|
||||
// Basic assignment: x = 10
|
||||
const assignMatch = line.match(/^(\w+)\s*=\s*(.*)$/);
|
||||
if (assignMatch) {
|
||||
const varName = assignMatch[1];
|
||||
const val = assignMatch[2].trim();
|
||||
this.pushValueToReg(val, 0, bytecode, opcodeMap, getConstant);
|
||||
bytecode.push(opcodeMap['SETGLOBAL'], 0, getConstant(varName));
|
||||
return;
|
||||
};
|
||||
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);
|
||||
}
|
||||
|
||||
// Add "opaque predicates" simulation
|
||||
if (Math.random() > 0.8) {
|
||||
bytecode.push(opcodeMap['JMP'], 1);
|
||||
bytecode.push(opcodeMap[`NOISE_${Math.floor(Math.random() * 10)}`], 0xFF);
|
||||
bc.push(ops.CALL, reg, ac + 1, 1);
|
||||
if (tokens[ti]?.value === ')') ti++;
|
||||
};
|
||||
const pCall = (reg) => {
|
||||
const fn = tokens[ti].value; ti++;
|
||||
bc.push(ops.GETG, reg, getC(fn)); pArgs(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;
|
||||
}
|
||||
});
|
||||
|
||||
bytecode.push(opcodeMap['RETURN'], 0, 1);
|
||||
return { bytecode, constants };
|
||||
}
|
||||
|
||||
static splitArgs(argsStr) {
|
||||
const args = [];
|
||||
let current = "";
|
||||
let inString = false;
|
||||
for (let i = 0; i < argsStr.length; i++) {
|
||||
const c = argsStr[i];
|
||||
if (c === '"' || c === "'") inString = !inString;
|
||||
if (c === ',' && !inString) {
|
||||
args.push(current.trim());
|
||||
current = "";
|
||||
} else {
|
||||
current += c;
|
||||
if (t.type === 'identifier' && (tokens[ti+1]?.value === '(' || tokens[ti+1]?.value === ':')) {
|
||||
pExpr(0); continue;
|
||||
}
|
||||
ti++;
|
||||
}
|
||||
args.push(current.trim());
|
||||
return args.filter(a => a.length > 0);
|
||||
bc.push(ops.RET, 0, 1);
|
||||
return { bc, constants, ops };
|
||||
}
|
||||
|
||||
static pushValueToReg(val, reg, bytecode, opcodeMap, getConstant) {
|
||||
if (val.startsWith('"') || val.startsWith("'")) {
|
||||
bytecode.push(opcodeMap['LOADK'], reg, getConstant(val.substring(1, val.length - 1)));
|
||||
} else if (!isNaN(val)) {
|
||||
bytecode.push(opcodeMap['LOADK'], reg, getConstant(Number(val)));
|
||||
} else if (val === "true") {
|
||||
bytecode.push(opcodeMap['LOADK'], reg, getConstant(true));
|
||||
} else if (val === "false") {
|
||||
bytecode.push(opcodeMap['LOADK'], reg, getConstant(false));
|
||||
} else {
|
||||
bytecode.push(opcodeMap['GETGLOBAL'], reg, getConstant(val));
|
||||
}
|
||||
}
|
||||
|
||||
static generateVMRunner(bytecode, constants, opcodeMap, seed, config) {
|
||||
const k1 = (seed % 250) + 1;
|
||||
const k2 = ((seed >> 8) % 250) + 1;
|
||||
|
||||
const encBytecode = [];
|
||||
let rolling = k1;
|
||||
bytecode.forEach((b) => {
|
||||
const enc = (b ^ rolling) ^ k2;
|
||||
encBytecode.push(enc);
|
||||
rolling = (rolling + enc) % 256;
|
||||
});
|
||||
|
||||
const encConstants = constants.map(c => {
|
||||
if (typeof c === 'string') {
|
||||
const chars = c.split('').map(char => {
|
||||
let v = char.charCodeAt(0) ^ k1;
|
||||
v = (v ^ 0xAA) ^ k2;
|
||||
return v;
|
||||
});
|
||||
return { t: 's', d: chars };
|
||||
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 encC = constants.map(c => {
|
||||
if (c.type === 'string') {
|
||||
const e = Buffer.from(c.value).map(b => b ^ (seed & 0xFF)).toString('base64');
|
||||
return `{t=1,v="${e}"}`;
|
||||
}
|
||||
if (typeof c === 'number') {
|
||||
return { t: 'n', d: (c ^ k1) ^ 0x55 };
|
||||
if (c.type === 'number') {
|
||||
const o = Math.floor(Math.random() * 1000);
|
||||
return `{t=2,v=${(c.value+o)^(seed&0xFFFF)},o=${o}}`;
|
||||
}
|
||||
if (typeof c === 'boolean') {
|
||||
return { t: 'b', d: c ? 1 : 0 };
|
||||
}
|
||||
return { t: 'z', d: 0 };
|
||||
});
|
||||
|
||||
const opsMapping = Object.entries(opcodeMap).map(([k, v]) => `[${v}] = "${k}"`).join(',');
|
||||
const varL = "L_" + crypto.randomBytes(2).toString('hex');
|
||||
const varC = "C_" + crypto.randomBytes(2).toString('hex');
|
||||
const varK = "K_" + crypto.randomBytes(2).toString('hex');
|
||||
const varO = "O_" + crypto.randomBytes(2).toString('hex');
|
||||
const varB = "B_" + crypto.randomBytes(2).toString('hex');
|
||||
|
||||
const constantsStr = encConstants.map(c => {
|
||||
const dataStr = Array.isArray(c.d) ? `{${c.d.join(',')}}` : c.d;
|
||||
return `{t="${c.t}",d=${dataStr}}`;
|
||||
return `{t=0}`;
|
||||
}).join(',');
|
||||
|
||||
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`;
|
||||
d[ops.SETG] = `function() _G[${nD}(${nL}[${nPC}+1])] = ${nR}[${nL}[${nPC}]]; ${nPC}=${nPC}+2 end`;
|
||||
d[ops.GETT] = `function() local r,t,k = ${nL}[${nPC}],${nL}[${nPC}+1],${nL}[${nPC}+2]; ${nPC}=${nPC}+3; ${nR}[r] = ${nR}[t][${nD}(k)] end`;
|
||||
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`;
|
||||
|
||||
const lua = `--[[ Luartex VM Protection ]]
|
||||
local function Luartex_VM(...)
|
||||
local ${varL} = {${encBytecode.join(',')}}
|
||||
local ${varC} = {${constantsStr}}
|
||||
local ${varK} = {${k1}, ${k2}}
|
||||
local ${varO} = {${opsMapping}}
|
||||
local ${varB} = bit32 or {bxor = function(a, b) local r, m = 0, 1 while a > 0 or b > 0 do if a % 2 ~= b % 2 then r = r + m end a, b, m = math.floor(a / 2), math.floor(b / 2), m * 2 end return r end}
|
||||
local function _DECODE(v, k, k2) if v.t == "s" then local s = "" for i = 1, #v.d do local val = ${varB}.bxor(v.d[i], k2) s = s .. string.char(${varB}.bxor(val, k)) end return s elseif v.t == "n" then return ${varB}.bxor(${varB}.bxor(v.d, k), 0x55) elseif v.t == "b" then return v.d == 1 end return nil end
|
||||
local function _EXECUTE()
|
||||
local _ENV = getfenv() local _REG = {} local _PC = 1 local _RUNNING = true local _ROLLING = ${varK}[1]
|
||||
while _RUNNING and _PC <= #${varL} do
|
||||
local _RAW = ${varL}[_PC] local _OP = ${varB}.bxor(${varB}.bxor(_RAW, _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + _RAW) % 256 _PC = _PC + 1
|
||||
local _MN = ${varO}[_OP]
|
||||
if _MN == "LOADK" then
|
||||
local _R = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
local _CI = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
_REG[_R] = _DECODE(${varC}[_CI + 1], ${varK}[1], ${varK}[2])
|
||||
elseif _MN == "GETGLOBAL" then
|
||||
local _R = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
local _CI = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
_REG[_R] = _ENV[_DECODE(${varC}[_CI + 1], ${varK}[1], ${varK}[2])]
|
||||
elseif _MN == "SETGLOBAL" then
|
||||
local _R = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
local _CI = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
_ENV[_DECODE(${varC}[_CI + 1], ${varK}[1], ${varK}[2])] = _REG[_R]
|
||||
elseif _MN == "CALL" then
|
||||
local _R = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
local _NA = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
local _NR = ${varB}.bxor(${varB}.bxor(${varL}[_PC], _ROLLING), ${varK}[2])
|
||||
_ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
local _AS = {}
|
||||
if _NA > 1 then for i = 1, _NA - 1 do _AS[i] = _REG[_R + i] end end
|
||||
local _F = _REG[_R]
|
||||
local _S, _RE = pcall(_F, unpack(_AS))
|
||||
if _NR > 1 then for i = 1, _NR - 1 do _REG[_R + i - 1] = _RE end end
|
||||
elseif _MN == "RETURN" then _RUNNING = false
|
||||
elseif _MN and _MN:find("NOISE") then _ROLLING = (_ROLLING + ${varL}[_PC]) % 256 _PC = _PC + 1
|
||||
end
|
||||
const xorKey = seed % 255;
|
||||
|
||||
return `local function VM(...)
|
||||
local ${nL} = {${bc.join(',')}}
|
||||
local ${nC} = {${encC}}
|
||||
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 function ${nD}(i)
|
||||
local c = ${nC}[i+1]; if not c then return end
|
||||
if c.t == 1 then
|
||||
local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
local s = c.v:gsub("[^"..b.."=]", ""); 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(${nK},255))) end end
|
||||
end
|
||||
return res
|
||||
elseif c.t == 2 then return bit32.bxor(c.v, bit32.band(${nK}, 65535)) - c.o end
|
||||
end
|
||||
pcall(_EXECUTE)
|
||||
end
|
||||
Luartex_VM(...);
|
||||
local ${nDS} = {${d.join(',')}}
|
||||
while ${nL}[${nPC}] do
|
||||
local op = ${nL}[${nPC}]; ${nPC} = ${nPC} + 1
|
||||
local f = ${nDS}[op+1]
|
||||
if f then if f() == "EXIT" then break end else break end
|
||||
end
|
||||
end; VM(...)`
|
||||
}
|
||||
}
|
||||
module.exports = ObfuscatorService;
|
||||
Loading…
x
Reference in New Issue
Block a user