38029-vm/core/obfuscator.py
Flatlogic Bot a998fae926 14:48
2026-01-31 13:48:46 +00:00

196 lines
7.4 KiB
Python

import random
import string
import base64
import json
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"
]
# Randomize opcode mapping
self.op_order = list(range(len(self.opcodes)))
random.shuffle(self.op_order)
# Arithmetic keys for opcode decoding
self.k1 = random.randint(10, 100)
self.k2 = random.randint(10, 100)
# Opcode to encoded ID: (real_index + k1) ^ k2
self.op_to_id = {name: ((self.opcodes.index(name) + self.k1) ^ self.k2) % 256 for name in self.opcodes}
def encrypt_string(self, s, key):
return "".join(chr(ord(c) ^ ((key + i) % 256)) for i, c in enumerate(s))
def generate_vm_source(self, bytecode):
# Pack instructions into a string
inst_str = "".join(chr(i) for inst in bytecode['instructions'] for i in inst)
inst_b64 = base64.b64encode(inst_str.encode('latin-1')).decode()
# Prepare constants
encrypted_consts = []
for i, c in enumerate(bytecode['constants']):
if c['type'] == 'string':
# Encrypt with index-based key
key = (i * 137 + 42) % 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)
vm_lua = f"""
-- [[ LUAU-VM PROTECTED ]]
local _ENV = getfenv()
local _B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
local _D = function(data)
data = string.gsub(data, '[^'.._B64..'=]', '')
return (data:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(_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 _INST_RAW = _D('{inst_b64}')
local _CONSTS = game:GetService("HttpService"):JSONDecode('{consts_json}')
local function _EXECUTE()
local registers = {{}}
local vip = 1
local running = true
local function get_const(idx)
local c = _CONSTS[idx + 1]
if not c then return nil end
if c.t == 1 then
local raw = _D(c.v)
local key = (idx * 137 + 42) % 256
local res = ""
for i=1, #raw do
res = res .. string.char(bit32.bxor(string.byte(raw, i), (key + i - 1) % 256))
end
return res
end
return c.v
end
while running and vip <= #_INST_RAW do
local op_raw = string.byte(_INST_RAW, vip)
local a = string.byte(_INST_RAW, vip + 1)
local b = string.byte(_INST_RAW, vip + 2)
local c = string.byte(_INST_RAW, vip + 3)
vip = vip + 4
-- Arithmetic Opcode Decoding
local op = bit32.bxor(op_raw, {self.k2}) - {self.k1}
if op == {self.opcodes.index('MOVE')} then
registers[a] = registers[b]
elseif op == {self.opcodes.index('LOADK')} then
registers[a] = get_const(b)
elseif op == {self.opcodes.index('GETGLOBAL')} then
registers[a] = _ENV[get_const(b)]
elseif op == {self.opcodes.index('SETGLOBAL')} then
_ENV[get_const(b)] = registers[a]
elseif op == {self.opcodes.index('CALL')} then
local f = registers[a]
local args = {{}}
if b > 1 then for i=1, b-1 do args[i] = registers[a+i] end end
local res = {{f(unpack(args))}}
if c > 1 then for i=1, c-1 do registers[a+i-1] = res[i] end end
elseif op == {self.opcodes.index('RETURN')} then
running = false
end
end
end
task.spawn(_EXECUTE)
"""
return vm_lua
def compile_to_bytecode(self, ast):
constants = []
instructions = []
locals_map = {}
next_reg = 0
def add_const(val):
if isinstance(val, str):
if (val.startswith('"') and val.endswith('"')) or (val.startswith("'" ) and val.endswith("'" )):
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 "-- No input"
try:
lexer = Lexer(code)
tokens = lexer.tokenize()
parser = Parser(tokens)
ast = parser.parse()
if not ast: return "-- VM Parser: No valid structures found."
bytecode = self.compile_to_bytecode(ast)
return self.generate_vm_source(bytecode)
except Exception as e:
import traceback
return f"-- Error: {str(e)}\n{traceback.format_exc()}"
def obfuscate(code):
return LuauVMObfuscator().obfuscate(code)