import random import string import base64 import json import hashlib from .parser import Lexer, Parser class LuauVMObfuscator: def __init__(self): # Full Luau-style Opcode list self.opcodes = [ "MOVE", "LOADK", "LOADBOOL", "LOADNIL", "GETGLOBAL", "SETGLOBAL", "GETTABLE", "SETTABLE", "NEWTABLE", "SELF", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", "UNM", "NOT", "LEN", "CONCAT", "JMP", "EQ", "LT", "LE", "TEST", "TESTSET", "CALL", "TAILCALL", "RETURN", "FORLOOP", "FORPREP", "TFORLOOP", "SETLIST", "CLOSE", "CLOSURE", "VARARG" ] # Arithmetic keys for opcode decoding self.k1 = random.randint(100, 1000) self.k2 = random.randint(100, 1000) self.k3 = random.randint(100, 1000) # Opcode to encoded ID: ((real_index + k1) ^ k2) + k3 self.op_to_id = {name: (((self.opcodes.index(name) + self.k1) ^ self.k2) + self.k3) % 4096 for name in self.opcodes} # Variable Renaming Map self.var_map = {} self.used_vars = set() def get_var(self, hint="var"): if hint in self.var_map: return self.var_map[hint] chars = "lI1" length = random.randint(32, 64) new_var = "_" + "".join(random.choice(chars) for _ in range(length)) while new_var in self.used_vars: new_var = "_" + "".join(random.choice(chars) for _ in range(length)) self.used_vars.add(new_var) self.var_map[hint] = new_var return new_var def to_expr(self, n, depth=0): if depth > 3 or (depth > 0 and random.random() < 0.15): return str(n) r = random.randint(1, 2000) choice = random.choice(['add', 'sub', 'xor', 'mul', 'shl', 'shr', 'not']) if choice == 'add': return f"({self.to_expr(n - r, depth + 1)} + {self.to_expr(r, depth + 1)})" elif choice == 'sub': return f"({self.to_expr(n + r, depth + 1)} - {self.to_expr(r, depth + 1)})" elif choice == 'xor': return f"bit32.bxor({self.to_expr(n ^ r, depth + 1)}, {self.to_expr(r, depth + 1)})" elif choice == 'mul': if n != 0 and n % 2 == 0: return f"({self.to_expr(n // 2, depth + 1)} * {self.to_expr(2, depth + 1)})" return f"({self.to_expr(n, depth + 1)})" elif choice == 'shl': # n = (n >> 1) << 1 + (n & 1) return f"bit32.bor(bit32.lshift({self.to_expr(n >> 1, depth + 1)}, {self.to_expr(1, depth + 1)}), {self.to_expr(n & 1, depth + 1)})" elif choice == 'shr': return f"bit32.rshift({self.to_expr(n << 1, depth + 1)}, {self.to_expr(1, depth + 1)})" elif choice == 'not': return f"bit32.bnot(bit32.bnot({self.to_expr(n, depth + 1)}))" return str(n) def opaque_predicate(self): v1 = random.randint(100, 1000) v2 = random.randint(100, 1000) preds = [ f"({self.to_expr(v1)} == {self.to_expr(v1)})", f"({self.to_expr(v1)} + {self.to_expr(v2)} > {self.to_expr(v1)})", f"(math.floor(math.pi) == {self.to_expr(3)})", f"(string.len('SUPREME') == {self.to_expr(7)})", f"(bit32.bor({v1}, {v1}) == {v1})", f"(bit32.band({v1}, {v1}) == {v1})", f"(bit32.bxor({v1}, 0) == {v1})", f"({self.to_expr(v2)} * 0 == 0)" ] return random.choice(preds) def encrypt_string(self, s, key): res = [] last = key % 256 for i, c in enumerate(s): k = (key + i + last) % 256 last = ord(c) res.append(chr(ord(c) ^ k)) return "".join(res) def generate_vm_source(self, bytecode): raw_instructions = bytecode['instructions'] shuffled = [] indices = list(range(len(raw_instructions))) random.shuffle(indices) pos_map = {orig: shuffled_idx for shuffled_idx, orig in enumerate(indices)} final_insts = [None] * len(raw_instructions) for i, orig_idx in enumerate(indices): inst = raw_instructions[orig_idx] if orig_idx + 1 < len(raw_instructions): next_orig = orig_idx + 1 next_shuffled = pos_map[next_orig] else: next_shuffled = 0 packed = [ inst[0] & 0xFF, (inst[0] >> 8) & 0xFF, inst[1], inst[2], inst[3], next_shuffled & 0xFF, (next_shuffled >> 8) & 0xFF ] final_insts[i] = packed inst_str = "".join(chr(i) for inst in final_insts for i in inst) inst_b64 = base64.b64encode(inst_str.encode('latin-1')).decode() # Bytecode Checksum for Integrity checksum = hashlib.md5(inst_str.encode('latin-1')).hexdigest() encrypted_consts = [] salt = random.randint(10000, 99999) for i, c in enumerate(bytecode['constants']): if c['type'] == 'string': key = (i * 149 + salt) % 256 enc_val = self.encrypt_string(c['value'], key) encrypted_consts.append({"t": 1, "v": base64.b64encode(enc_val.encode('latin-1')).decode()}) else: encrypted_consts.append({"t": 2, "v": c['value']}) consts_json = json.dumps(encrypted_consts) start_idx = pos_map[0] # Obfuscated Variable Names V_ENV = self.get_var("env") V_B64 = self.get_var("b64") V_D = self.get_var("decode") V_INST = self.get_var("inst_raw") V_CONSTS = self.get_var("consts") V_SALT = self.get_var("salt") V_EXEC = self.get_var("execute") V_REGS = self.get_var("registers") V_CURR = self.get_var("current") V_RUN = self.get_var("running") V_GETC = self.get_var("get_const") V_PTR = self.get_var("ptr") V_OP = self.get_var("op") V_A = self.get_var("a") V_B = self.get_var("b") V_C = self.get_var("c") V_BIT = self.get_var("bit32") V_GS = self.get_var("getservice") V_TRAP = self.get_var("trap") V_CHECK = self.get_var("check") V_IS_HOOKED = self.get_var("is_hooked") V_PROTECT = self.get_var("protect") V_METH = self.get_var("meth") V_LUA_ENV = self.get_var("lua_env") vm_lua = f"""local {V_LUA_ENV} = getfenv() local {V_BIT} = bit32 local {V_GS} = game.GetService local {V_B64} = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' local {V_D} = function(data) if not ({self.opaque_predicate()}) then return "" end data = string.gsub(data, '[^'..{V_B64}..'=]', '') return (data:gsub('.', function(x) if (x == '=') then return '' end local r,f='',({V_B64}:find(x)-1) for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end return r; end):gsub('%d%d%d%d%d%d%d%d', function(x) local r=0 for i=1,8 do r=r+(x:sub(i,i)=='1' and 2^(8-i) or 0) end return string.char(r) end)) end local function {V_IS_HOOKED}(f) if type(f) ~= "function" then return false end local s = tostring(f) if string.find(s, "hook") or string.find(s, "proxy") then return true end return false end local function {V_TRAP}() local _ = function() while true do end end task.spawn(_) end local function {V_CHECK}() local d = {{ "getgenv", "getrenv", "getreg", "debug", "Drawing", "hookfunction", "setreadonly", "make_writeable" }} for _, v in ipairs(d) do if {V_LUA_ENV}[v] then if {V_IS_HOOKED}({V_LUA_ENV}[v]) then return false end end end if not {V_LUA_ENV}["game"] or not {V_LUA_ENV}["task"] then return false end return true end local {V_INST} = {V_D}('{inst_b64}') local {V_CONSTS} = {V_GS}(game, "HttpService"):JSONDecode('{consts_json}') local {V_SALT} = {self.to_expr(salt)} local function {V_EXEC}() if not {V_CHECK}() then {V_TRAP}() return end local {V_REGS} = {{}} local {V_CURR} = {self.to_expr(start_idx)} local {V_RUN} = true local {V_PROTECT} = {{}} setmetatable({V_PROTECT}, {{ __index = {V_LUA_ENV}, __newindex = function(_, k, v) {V_LUA_ENV}[k] = v end, __metatable = "Locked" }}) local function {V_GETC}(idx) local c = {V_CONSTS}[idx + 1] if not c then return nil end if c.t == {self.to_expr(1)} then local raw = {V_D}(c.v) local key = (idx * {self.to_expr(149)} + {V_SALT}) % {self.to_expr(256)} local res = "" local last = key % 256 for i=1, #raw do local k = (key + i + last - 1) % 256 local b = string.byte(raw, i) local char_code = {V_BIT}.bxor(b, k) res = res .. string.char(char_code) last = char_code end return res end return c.v end while {V_RUN} do if not ({self.opaque_predicate()}) then break end local {V_PTR} = {V_CURR} * {self.to_expr(7)} + 1 if {V_PTR} > #{V_INST} then break end local op_l = string.byte({V_INST}, {V_PTR}) local op_h = string.byte({V_INST}, {V_PTR} + 1) local {V_A} = string.byte({V_INST}, {V_PTR} + 2) local {V_B} = string.byte({V_INST}, {V_PTR} + 3) local {V_C} = string.byte({V_INST}, {V_PTR} + 4) local next_l = string.byte({V_INST}, {V_PTR} + 5) local next_h = string.byte({V_INST}, {V_PTR} + 6) {V_CURR} = next_l + (next_h * {self.to_expr(256)}) local op_raw = op_l + (op_h * 256) local {V_OP} = ({V_BIT}.bxor(op_raw, {self.to_expr(self.k2)}) - {self.to_expr(self.k1)}) - {self.to_expr(self.k3)} if {V_OP} == {self.to_expr(self.opcodes.index('MOVE'))} then {V_REGS}[{V_A}] = {V_REGS}[{V_B}] elseif {V_OP} == {self.to_expr(self.opcodes.index('LOADK'))} then {V_REGS}[{V_A}] = {V_GETC}({V_B}) elseif {V_OP} == {self.to_expr(self.opcodes.index('GETGLOBAL'))} then {V_REGS}[{V_A}] = {V_PROTECT}[{V_GETC}({V_B})] elseif {V_OP} == {self.to_expr(self.opcodes.index('SETGLOBAL'))} then {V_PROTECT}[{V_GETC}({V_B})] = {V_REGS}[{V_A}] elseif {V_OP} == {self.to_expr(self.opcodes.index('CALL'))} then local f = {V_REGS}[{V_A}] local args = {{}} if {V_B} > 1 then for i=1, {V_B}-1 do args[i] = {V_REGS}[{V_A}+i] end end local res = {{f(unpack(args))}} if {V_C} > 1 then for i=1, {V_C}-1 do {V_REGS}[{V_A}+i-1] = res[i] end end elseif {V_OP} == {self.to_expr(self.opcodes.index('RETURN'))} then {V_RUN} = false end if not ({self.opaque_predicate()}) then {V_RUN} = false end end end task.spawn({V_EXEC})""" return vm_lua def compile_to_bytecode(self, ast): constants = [] instructions = [] locals_map = {} next_reg = 0 def add_const(val): if isinstance(val, str): s_q = chr(39) d_q = chr(34) if (val.startswith(s_q) and val.endswith(s_q)) or (val.startswith(d_q) and val.endswith(d_q)): val = val[1:-1] for i, c in enumerate(constants): if c['value'] == val: return i t = 'string' if isinstance(val, str) else 'number' constants.append({'type': t, 'value': val}) return len(constants) - 1 def load_expr_to_reg(expr, reg): if expr['type'] == 'IDENT': if expr['value'] in locals_map: instructions.append([self.op_to_id["MOVE"], reg, locals_map[expr['value']], 0]) else: instructions.append([self.op_to_id["GETGLOBAL"], reg, add_const(expr['value']), 0]) elif expr['type'] in ['STRING', 'NUMBER']: val = expr['value'] if expr['type'] == 'NUMBER': try: val = float(val) except: pass instructions.append([self.op_to_id["LOADK"], reg, add_const(val), 0]) for node in ast: if node['type'] == 'call': func_reg = next_reg if node['name'] in locals_map: instructions.append([self.op_to_id["MOVE"], func_reg, locals_map[node['name']], 0]) else: instructions.append([self.op_to_id["GETGLOBAL"], func_reg, add_const(node['name']), 0]) for i, arg_expr in enumerate(node['args']): load_expr_to_reg(arg_expr, func_reg + 1 + i) instructions.append([self.op_to_id["CALL"], func_reg, len(node['args']) + 1, 1]) elif node['type'] == 'assign': val_reg = next_reg load_expr_to_reg(node['value'], val_reg) if node.get('local'): locals_map[node['name']] = val_reg next_reg += 1 else: instructions.append([self.op_to_id["SETGLOBAL"], val_reg, add_const(node['name']), 0]) instructions.append([self.op_to_id["RETURN"], 0, 0, 0]) return {"instructions": instructions, "constants": constants} def obfuscate(self, code): if not code.strip(): return "" try: lexer = Lexer(code) tokens = lexer.tokenize() parser = Parser(tokens) ast = parser.parse() if not ast: return "" bytecode = self.compile_to_bytecode(ast) return self.generate_vm_source(bytecode) except Exception: return "" def obfuscate(code): return LuauVMObfuscator().obfuscate(code)