diff --git a/backend/src/services/obfuscator.js b/backend/src/services/obfuscator.js index 9afbbd2..f145c9b 100644 --- a/backend/src/services/obfuscator.js +++ b/backend/src/services/obfuscator.js @@ -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(...); \ No newline at end of file + 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; \ No newline at end of file