diff --git a/core/__pycache__/obfuscator.cpython-311.pyc b/core/__pycache__/obfuscator.cpython-311.pyc index fce1ba3..b76d8c6 100644 Binary files a/core/__pycache__/obfuscator.cpython-311.pyc and b/core/__pycache__/obfuscator.cpython-311.pyc differ diff --git a/core/obfuscator.py b/core/obfuscator.py index 89fa7b0..316a9dc 100644 --- a/core/obfuscator.py +++ b/core/obfuscator.py @@ -3,11 +3,11 @@ import string import base64 import json import hashlib +import re 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", @@ -16,38 +16,32 @@ class LuauVMObfuscator: "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) + self.k1 = random.randint(100, 500) + self.k2 = random.randint(100, 500) + self.k3 = random.randint(100, 500) - # 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) + if hint in self.var_map: return self.var_map[hint] + chars = "lI" + length = random.randint(8, 16) # Shorter vars for single-line efficiency 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): + if depth > 1 or (depth > 0 and random.random() < 0.5): return str(n) - r = random.randint(1, 2000) - choice = random.choice(['add', 'sub', 'xor', 'mul', 'shl', 'shr', 'not']) + r = random.randint(1, 50) + choice = random.choice(['add', 'sub', 'xor']) if choice == 'add': return f"({self.to_expr(n - r, depth + 1)} + {self.to_expr(r, depth + 1)})" @@ -55,35 +49,9 @@ class LuauVMObfuscator: 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 @@ -93,6 +61,16 @@ class LuauVMObfuscator: res.append(chr(ord(c) ^ k)) return "".join(res) + def minify(self, code): + # Remove comments (though we don't add them, just in case) + code = re.sub(r'--.*', '', code) + # Replace multiple spaces with single space + code = re.sub(r'\s+', ' ', code) + # Remove spaces around operators and delimiters where safe + for op in ['==', '~=', '<=', '>=', '=', r'\+', '-', r'\*', '/', '>', '<', r'\(', r'\)', r'\{', r'\}', r'\[', r'\]', ',', ';', ':']: + code = re.sub(r'\s*' + op + r'\s*', op.replace('\\', ''), code) + return code.strip() + def generate_vm_source(self, bytecode): raw_instructions = bytecode['instructions'] shuffled = [] @@ -104,12 +82,7 @@ class LuauVMObfuscator: 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 + next_shuffled = pos_map[orig_idx + 1] if orig_idx + 1 < len(raw_instructions) else 0 packed = [ inst[0] & 0xFF, (inst[0] >> 8) & 0xFF, @@ -121,11 +94,8 @@ class LuauVMObfuscator: 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) + salt = random.randint(1000, 9999) for i, c in enumerate(bytecode['constants']): if c['type'] == 'string': key = (i * 149 + salt) % 256 @@ -137,8 +107,8 @@ class LuauVMObfuscator: consts_json = json.dumps(encrypted_consts) start_idx = pos_map[0] - # Obfuscated Variable Names - V_ENV = self.get_var("env") + V_LUA_ENV = self.get_var("lua_env") + V_BIT = self.get_var("bit32") V_B64 = self.get_var("b64") V_D = self.get_var("decode") V_INST = self.get_var("inst_raw") @@ -154,137 +124,85 @@ class LuauVMObfuscator: 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") + V_KEY = self.get_var("key") + V_RES = self.get_var("res") + V_LAST = self.get_var("last") - 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 + vm_lua = f"""local {V_LUA_ENV}=getfenv() +local {V_BIT}=bit32 +local {V_B64}='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' +local {V_D}=function(data) + 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 + 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) + 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 + 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 {V_INST}={V_D}('{inst_b64}') +local {V_CONSTS}=game:GetService("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 {V_REGS}={{}} + local {V_CURR}={self.to_expr(start_idx)} + local {V_RUN}=true local function {V_GETC}(idx) - local c = {V_CONSTS}[idx + 1] + 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 + if c.t=={self.to_expr(1)} then + local raw={V_D}(c.v) + local {V_KEY}=(idx*{self.to_expr(149)}+{V_SALT})%256 + local {V_RES}={{}} + local {V_LAST}={V_KEY}%256 + for i=1,#raw do + local k=({V_KEY}+i+{V_LAST}-1)%256 + local b=string.byte(raw,i) + local char_code={V_BIT}.bxor(b,k) + {V_RES}[i]=string.char(char_code) + {V_LAST}=char_code end - return res + return table.concat({V_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 + local {V_PTR}={V_CURR}*7+1 + 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*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.opcodes.index('MOVE')} then + {V_REGS}[{V_A}]={V_REGS}[{V_B}] + elseif {V_OP}=={self.opcodes.index('LOADK')} then + {V_REGS}[{V_A}]={V_GETC}({V_B}) + elseif {V_OP}=={self.opcodes.index('GETGLOBAL')} then + {V_REGS}[{V_A}]={V_LUA_ENV}[{V_GETC}({V_B})] + elseif {V_OP}=={self.opcodes.index('SETGLOBAL')} then + {V_LUA_ENV}[{V_GETC}({V_B})]={V_REGS}[{V_A}] + elseif {V_OP}=={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.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 + return self.minify(vm_lua) def compile_to_bytecode(self, ast): constants = [] @@ -294,14 +212,11 @@ task.spawn({V_EXEC})""" def add_const(val): if isinstance(val, str): - s_q = chr(39) - d_q = chr(34) + 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 @@ -326,16 +241,12 @@ task.spawn({V_EXEC})""" 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 @@ -356,7 +267,7 @@ task.spawn({V_EXEC})""" bytecode = self.compile_to_bytecode(ast) return self.generate_vm_source(bytecode) except Exception: - return "" + return "-- Obfuscation Error" def obfuscate(code): return LuauVMObfuscator().obfuscate(code) \ No newline at end of file