15:54
This commit is contained in:
parent
ca3f7c81d2
commit
60d9aabf8d
Binary file not shown.
Binary file not shown.
@ -16,44 +16,27 @@ class LuauVMObfuscator:
|
|||||||
"FORLOOP", "FORPREP", "TFORLOOP", "SETLIST", "CLOSE", "CLOSURE", "VARARG"
|
"FORLOOP", "FORPREP", "TFORLOOP", "SETLIST", "CLOSE", "CLOSURE", "VARARG"
|
||||||
]
|
]
|
||||||
|
|
||||||
self.k1 = random.randint(1000, 5000)
|
self.k1, self.k2, self.k3 = random.randint(500, 2000), random.randint(500, 2000), random.randint(500, 2000)
|
||||||
self.k2 = random.randint(1000, 5000)
|
self.op_to_id = {name: ((self.opcodes.index(name) + self.k1) ^ self.k2) + self.k3 for name in self.opcodes}
|
||||||
|
self.var_map, self.used_vars = {}, set()
|
||||||
self.op_to_id = {name: (self.opcodes.index(name) + self.k1) ^ self.k2 for name in self.opcodes}
|
|
||||||
|
|
||||||
self.var_map = {}
|
|
||||||
self.used_vars = set()
|
|
||||||
|
|
||||||
def get_var(self, hint="var"):
|
def get_var(self, hint="var"):
|
||||||
if hint in self.var_map: return self.var_map[hint]
|
if hint in self.var_map: return self.var_map[hint]
|
||||||
chars = "lI1"
|
c = "Il1"; l = random.randint(24, 32)
|
||||||
length = random.randint(32, 48)
|
v = "_" + "".join(random.choice(c) for _ in range(l))
|
||||||
new_var = "_" + "".join(random.choice(chars) for _ in range(length))
|
while v in self.used_vars: v = "_" + "".join(random.choice(c) for _ in range(l))
|
||||||
while new_var in self.used_vars:
|
self.used_vars.add(v); self.var_map[hint] = v
|
||||||
new_var = "_" + "".join(random.choice(chars) for _ in range(length))
|
return v
|
||||||
self.used_vars.add(new_var)
|
|
||||||
self.var_map[hint] = new_var
|
|
||||||
return new_var
|
|
||||||
|
|
||||||
def to_expr(self, n, bit32_var="bit32", depth=0):
|
def to_expr(self, n):
|
||||||
if depth > 2 or (depth > 0 and random.random() < 0.2):
|
r = random.randint(1, 1000)
|
||||||
return str(n)
|
ch = random.choice(['add', 'sub', 'xor'])
|
||||||
|
if ch == 'add': return f"({n-r}+{r})"
|
||||||
r = random.randint(1, 100000)
|
if ch == 'sub': return f"({n+r}-{r})"
|
||||||
choice = random.choice(['add', 'sub', 'xor'])
|
return f"bit32.bxor({n^r},{r})"
|
||||||
|
|
||||||
if choice == 'add':
|
|
||||||
return f"({self.to_expr(n - r, bit32_var, depth + 1)} + {self.to_expr(r, bit32_var, depth + 1)})"
|
|
||||||
elif choice == 'sub':
|
|
||||||
return f"({self.to_expr(n + r, bit32_var, depth + 1)} - {self.to_expr(r, bit32_var, depth + 1)})"
|
|
||||||
elif choice == 'xor':
|
|
||||||
return f"{bit32_var}.bxor({self.to_expr(n ^ r, bit32_var, depth + 1)}, {self.to_expr(r, bit32_var, depth + 1)})"
|
|
||||||
|
|
||||||
return str(n)
|
|
||||||
|
|
||||||
def encrypt_string(self, s, key):
|
def encrypt_string(self, s, key):
|
||||||
res = []
|
res, last = [], key % 256
|
||||||
last = key % 256
|
|
||||||
for i, c in enumerate(s):
|
for i, c in enumerate(s):
|
||||||
k = (key + i + last) % 256
|
k = (key + i + last) % 256
|
||||||
last = ord(c)
|
last = ord(c)
|
||||||
@ -68,200 +51,168 @@ class LuauVMObfuscator:
|
|||||||
return code.strip()
|
return code.strip()
|
||||||
|
|
||||||
def generate_vm_source(self, bytecode):
|
def generate_vm_source(self, bytecode):
|
||||||
raw_instructions = bytecode['instructions']
|
raw_ins = bytecode['instructions']
|
||||||
indices = list(range(len(raw_instructions)))
|
indices = list(range(len(raw_ins)))
|
||||||
random.shuffle(indices)
|
random.shuffle(indices); pos_map = {orig: i for i, orig in enumerate(indices)}
|
||||||
|
final_insts = []
|
||||||
pos_map = {orig: shuffled_idx for shuffled_idx, orig in enumerate(indices)}
|
for orig_idx in indices:
|
||||||
|
inst = raw_ins[orig_idx]
|
||||||
final_insts = [None] * len(raw_instructions)
|
next_sh = pos_map[orig_idx + 1] if orig_idx + 1 < len(raw_ins) else 0
|
||||||
for i, orig_idx in enumerate(indices):
|
final_insts.append([inst[0]&0xFF,(inst[0]>>8)&0xFF,inst[1]&0xFF,inst[2]&0xFF,inst[3]&0xFF,next_sh&0xFF,(next_sh>>8)&0xFF])
|
||||||
inst = raw_instructions[orig_idx]
|
|
||||||
next_shuffled = pos_map[orig_idx + 1] if orig_idx + 1 < len(raw_instructions) else 0
|
|
||||||
|
|
||||||
packed = [
|
|
||||||
inst[0] & 0xFF, (inst[0] >> 8) & 0xFF,
|
|
||||||
inst[1] & 0xFF, inst[2] & 0xFF, inst[3] & 0xFF,
|
|
||||||
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_str = "".join(chr(i) for inst in final_insts for i in inst)
|
||||||
inst_b64 = base64.b64encode(inst_str.encode('latin-1')).decode()
|
inst_b64 = base64.b64encode(inst_str.encode('latin-1')).decode()
|
||||||
|
|
||||||
encrypted_consts = []
|
consts = []
|
||||||
salt = random.randint(100000, 999999)
|
salt = random.randint(100000, 999999)
|
||||||
for i, c in enumerate(bytecode['constants']):
|
for i, c in enumerate(bytecode['constants']):
|
||||||
if c['type'] == 'string':
|
if c['type'] == 'string':
|
||||||
key = (i * 149 + salt) % 256
|
k = (i * 149 + salt) % 256; enc = self.encrypt_string(c['value'], k)
|
||||||
enc_val = self.encrypt_string(c['value'], key)
|
consts.append({"t": 1, "v": base64.b64encode(enc.encode('latin-1')).decode()})
|
||||||
encrypted_consts.append({"t": 1, "v": base64.b64encode(enc_val.encode('latin-1')).decode()})
|
elif c['type'] == 'number': consts.append({"t": 2, "v": c['value']})
|
||||||
else:
|
else: consts.append({"t": 3, "v": c['value']})
|
||||||
encrypted_consts.append({"t": 2, "v": c['value']})
|
|
||||||
|
|
||||||
consts_json = json.dumps(encrypted_consts)
|
v = {
|
||||||
start_idx = pos_map[0]
|
"BIT": "bit32", "ENV": self.get_var("env"), "DEC": self.get_var("dec"),
|
||||||
|
"INST": self.get_var("inst"), "CONSTS": self.get_var("consts"), "SALT": self.to_expr(salt),
|
||||||
|
"EXEC": self.get_var("exec"), "REGS": self.get_var("regs"), "CURR": self.get_var("curr"),
|
||||||
|
"PTR": self.get_var("ptr"), "OP": self.get_var("op"), "A": self.get_var("a"),
|
||||||
|
"B": self.get_var("b"), "C": self.get_var("c"), "UNP": "unpack or table.unpack",
|
||||||
|
"SPW": "task and task.spawn or spawn", "JSON": "game:GetService('HttpService')",
|
||||||
|
"CHARS": self.get_var("chars"), "LOOKUP": self.get_var("lookup"), "GETC": self.get_var("getc")
|
||||||
|
}
|
||||||
|
|
||||||
V_ENV = self.get_var("env")
|
vm_lua = f"""local {v['ENV']}=setmetatable({{}},{{__index=getfenv()}})
|
||||||
V_BIT = self.get_var("bit")
|
local {v['CHARS']}='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||||
V_B64 = self.get_var("b64")
|
local {v['LOOKUP']}={{}}
|
||||||
V_DEC = self.get_var("dec")
|
for i=1,64 do {v['LOOKUP']}[{v['CHARS']}:sub(i,i)]=i-1 end
|
||||||
V_INST = self.get_var("inst")
|
local function {v['DEC']}(data)
|
||||||
V_CONSTS = self.get_var("consts")
|
data=data:gsub('[^%a%d%+/]','')
|
||||||
V_SALT = self.get_var("salt")
|
local res={{}}
|
||||||
V_EXEC = self.get_var("exec")
|
for i=1,#data,4 do
|
||||||
V_REGS = self.get_var("regs")
|
local a,b,c,d={v['LOOKUP']}[data:sub(i,i)],{v['LOOKUP']}[data:sub(i+1,i+1)],{v['LOOKUP']}[data:sub(i+2,i+2)],{v['LOOKUP']}[data:sub(i+3,i+3)]
|
||||||
V_CURR = self.get_var("curr")
|
local n={v['BIT']}.lshift(a or 0,18)+{v['BIT']}.lshift(b or 0,12)+{v['BIT']}.lshift(c or 0,6)+(d or 0)
|
||||||
V_RUN = self.get_var("run")
|
res[#res+1]=string.char({v['BIT']}.extract(n,16,8))
|
||||||
V_GETC = self.get_var("getc")
|
if c then res[#res+1]=string.char({v['BIT']}.extract(n,8,8)) end
|
||||||
V_PTR = self.get_var("ptr")
|
if d then res[#res+1]=string.char({v['BIT']}.extract(n,0,8)) end
|
||||||
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_UNP = self.get_var("unp")
|
|
||||||
V_SPW = self.get_var("spw")
|
|
||||||
V_RAW = self.get_var("raw")
|
|
||||||
V_KEY = self.get_var("key")
|
|
||||||
V_RES = self.get_var("res")
|
|
||||||
V_LST = self.get_var("lst")
|
|
||||||
|
|
||||||
vm_lua = f"""local {V_BIT}=bit32
|
|
||||||
local {V_ENV}=setmetatable({{}},{{__index=getfenv()}})
|
|
||||||
local {V_UNP}=unpack or table.unpack
|
|
||||||
local {V_SPW}=task and task.spawn or spawn
|
|
||||||
local {V_B64}='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
||||||
local {V_DEC}=function(data)
|
|
||||||
data=data:gsub('[^'..{V_B64}..'=]','')
|
|
||||||
local res,bits={{}},''
|
|
||||||
for i=1,#data do
|
|
||||||
local c=data:sub(i,i)
|
|
||||||
if c=='=' then break end
|
|
||||||
local f={V_B64}:find(c)-1
|
|
||||||
for j=5,0,-1 do bits=bits..({V_BIT}.extract(f,j,1)) end
|
|
||||||
end
|
|
||||||
for i=1,#bits-7,8 do
|
|
||||||
local b=0
|
|
||||||
for j=1,8 do b=b+(bits:sub(i+j-1,i+j-1)=='1' and 2^(8-j) or 0) end
|
|
||||||
res[#res+1]=string.char(b)
|
|
||||||
end
|
end
|
||||||
return table.concat(res)
|
return table.concat(res)
|
||||||
end
|
end
|
||||||
local {V_INST}={V_DEC}('{inst_b64}')
|
local {v['INST']}={v['DEC']}('{inst_b64}')
|
||||||
local {V_CONSTS}=game:GetService("HttpService"):JSONDecode([=[{consts_json}]=])
|
local {v['CONSTS']}={v['JSON']}:JSONDecode([=[{json.dumps(consts)}]=])
|
||||||
local {V_SALT}={self.to_expr(salt, V_BIT)}
|
local {v['EXEC']}=function()
|
||||||
local function {V_EXEC}()
|
local {v['REGS']}={{}}
|
||||||
local {V_REGS}={{}}
|
local {v['CURR']}={self.to_expr(pos_map[0])}
|
||||||
local {V_CURR}={self.to_expr(start_idx, V_BIT)}
|
local function {v['GETC']}(idx)
|
||||||
local {V_RUN}=true
|
local c={v['CONSTS']}[idx+1]
|
||||||
local function {V_GETC}(idx)
|
if not c then return end
|
||||||
if not idx then return nil end
|
if c.t==1 then
|
||||||
local c={V_CONSTS}[idx+1]
|
local r,k={v['DEC']}(c.v),(idx*149+{v['SALT']})%256
|
||||||
if not c then return nil end
|
local res,lst={{}},k%256
|
||||||
if c.t=={self.to_expr(1, V_BIT)} then
|
for i=1,#r do
|
||||||
local {V_RAW}={V_DEC}(c.v)
|
local char={v['BIT']}.bxor(string.byte(r,i),(k+i+lst-1)%256)
|
||||||
local {V_KEY}=(idx*{self.to_expr(149, V_BIT)}+{V_SALT})%256
|
res[i]=string.char(char)
|
||||||
local {V_RES}={{}}
|
lst=char
|
||||||
local {V_LST}={V_KEY}%256
|
|
||||||
for i=1,#{V_RAW} do
|
|
||||||
local k=({V_KEY}+i+{V_LST}-1)%256
|
|
||||||
local b=string.byte({V_RAW},i)
|
|
||||||
local char={V_BIT}.bxor(b,k)
|
|
||||||
{V_RES}[i]=string.char(char)
|
|
||||||
{V_LST}=char
|
|
||||||
end
|
end
|
||||||
return table.concat({V_RES})
|
return table.concat(res)
|
||||||
end
|
elseif c.t==3 then return c.v=='true' and true or (c.v=='false' and false or nil) end
|
||||||
return c.v
|
return c.v
|
||||||
end
|
end
|
||||||
while {V_RUN} do
|
while true do
|
||||||
local {V_PTR}={V_CURR}*7+1
|
local {v['PTR']}={v['CURR']}*7+1
|
||||||
local op_l=string.byte({V_INST},{V_PTR}) or 0
|
local b1,b2,b3,b4,b5,b6,b7=string.byte({v['INST']},{v['PTR']},{v['PTR']}+6)
|
||||||
local op_h=string.byte({V_INST},{V_PTR}+1) or 0
|
if not b1 then break end
|
||||||
local {V_A}=string.byte({V_INST},{V_PTR}+2) or 0
|
{v['CURR']}=b6+({v['BIT']}.lshift(b7,8))
|
||||||
local {V_B}=string.byte({V_INST},{V_PTR}+3) or 0
|
local {v['OP']}={v['BIT']}.bxor(b1+{v['BIT']}.lshift(b2,8),{self.to_expr(self.k2)})-{self.to_expr(self.k1)}
|
||||||
local {V_C}=string.byte({V_INST},{V_PTR}+4) or 0
|
local {v['A']},{v['B']},{v['C']}=b3,b4,b5
|
||||||
local n_l=string.byte({V_INST},{V_PTR}+5) or 0
|
if {v['OP']}=={self.opcodes.index('MOVE')} then {v['REGS']}[{v['A']}]={v['REGS']}[{v['B']}]
|
||||||
local n_h=string.byte({V_INST},{V_PTR}+6) or 0
|
elseif {v['OP']}=={self.opcodes.index('LOADK')} then {v['REGS']}[{v['A']}]={v['GETC']}({v['B']})
|
||||||
{V_CURR}=n_l+({V_BIT}.lshift(n_h,8))
|
elseif {v['OP']}=={self.opcodes.index('GETGLOBAL')} then {v['REGS']}[{v['A']}]={v['ENV']}[{v['GETC']}({v['B']})]
|
||||||
local op_raw=op_l+({V_BIT}.lshift(op_h,8))
|
elseif {v['OP']}=={self.opcodes.index('SETGLOBAL')} then {v['ENV']}[{v['GETC']}({v['B']})]={v['REGS']}[{v['A']}]
|
||||||
local {V_OP}={V_BIT}.bxor(op_raw,{self.to_expr(self.k2, V_BIT)})-{self.to_expr(self.k1, V_BIT)}
|
elseif {v['OP']}=={self.opcodes.index('GETTABLE')} then
|
||||||
if {V_OP}=={self.opcodes.index('MOVE')} then
|
local b={v['REGS']}[{v['B']}]
|
||||||
{V_REGS}[{V_A}]={V_REGS}[{V_B}]
|
if b then {v['REGS']}[{v['A']}]=b[{v['REGS']}[{v['C']}] or {v['GETC']}({v['C']})] end
|
||||||
elseif {V_OP}=={self.opcodes.index('LOADK')} then
|
elseif {v['OP']}=={self.opcodes.index('SETTABLE')} then
|
||||||
{V_REGS}[{V_A}]={V_GETC}({V_B})
|
local a={v['REGS']}[{v['A']}]
|
||||||
elseif {V_OP}=={self.opcodes.index('GETGLOBAL')} then
|
if a then a[{v['REGS']}[{v['B']}] or {v['GETC']}({v['B']})]={v['REGS']}[{v['C']}] end
|
||||||
local n={V_GETC}({V_B})
|
elseif {v['OP']}=={self.opcodes.index('ADD')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)+({v['REGS']}[{v['C']}] or 0)
|
||||||
if n then {V_REGS}[{V_A}]={V_ENV}[n] end
|
elseif {v['OP']}=={self.opcodes.index('SUB')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)-({v['REGS']}[{v['C']}] or 0)
|
||||||
elseif {V_OP}=={self.opcodes.index('SETGLOBAL')} then
|
elseif {v['OP']}=={self.opcodes.index('MUL')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)*({v['REGS']}[{v['C']}] or 0)
|
||||||
local n={V_GETC}({V_B})
|
elseif {v['OP']}=={self.opcodes.index('DIV')} then {v['REGS']}[{v['A']}]=({v['REGS']}[{v['B']}] or 0)/({v['REGS']}[{v['C']}] or 1)
|
||||||
if n then {V_ENV}[n]={V_REGS}[{V_A}] end
|
elseif {v['OP']}=={self.opcodes.index('UNM')} then {v['REGS']}[{v['A']}]=-({v['REGS']}[{v['B']}] or 0)
|
||||||
elseif {V_OP}=={self.opcodes.index('CALL')} then
|
elseif {v['OP']}=={self.opcodes.index('NOT')} then {v['REGS']}[{v['A']}]=not {v['REGS']}[{v['B']}]
|
||||||
local f={V_REGS}[{V_A}]
|
elseif {v['OP']}=={self.opcodes.index('LEN')} then {v['REGS']}[{v['A']}]=#{v['REGS']}[{v['B']}] or 0
|
||||||
|
elseif {v['OP']}=={self.opcodes.index('CALL')} then
|
||||||
|
local f={v['REGS']}[{v['A']}]
|
||||||
if f then
|
if f then
|
||||||
local args={{}}
|
local args={{}}
|
||||||
if {V_B}>1 then for i=1,{V_B}-1 do args[i]={V_REGS}[{V_A}+i] end end
|
if {v['B']}>1 then for i=1,{v['B']}-1 do args[i]={v['REGS']}[{v['A']}+i] end end
|
||||||
local res={{f({V_UNP}(args))}}
|
local res={{f(({v['UNP']})(args))}}
|
||||||
if {V_C}>1 then for i=1,{V_C}-1 do {V_REGS}[{V_A}+i-1]=res[i] end end
|
if {v['C']}>1 then for i=1,{v['C']}-1 do {v['REGS']}[{v['A']}+i-1]=res[i] end end
|
||||||
end
|
end
|
||||||
elseif {V_OP}=={self.opcodes.index('RETURN')} then
|
elseif {v['OP']}=={self.opcodes.index('RETURN')} then break end
|
||||||
{V_RUN}=false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
{V_SPW}({V_EXEC})"""
|
{v['SPW']}({v['EXEC']})"""
|
||||||
return self.minify(vm_lua)
|
return self.minify(vm_lua)
|
||||||
|
|
||||||
def compile_to_bytecode(self, ast):
|
def compile_to_bytecode(self, ast):
|
||||||
constants = []
|
constants, instructions, locals_map = [], [], {}
|
||||||
instructions = []
|
self.next_reg = 0
|
||||||
locals_map = {}
|
|
||||||
next_reg = 0
|
|
||||||
|
|
||||||
def add_const(val):
|
def add_const(val):
|
||||||
if isinstance(val, str):
|
if isinstance(val, str) and ((val.startswith("'") and val.endswith("'")) or (val.startswith('"') and val.endswith('"'))): val = val[1:-1]
|
||||||
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):
|
for i, c in enumerate(constants):
|
||||||
if c['value'] == val: return i
|
if c['value'] == val: return i
|
||||||
t = 'string' if isinstance(val, str) else 'number'
|
t = 'string' if isinstance(val, str) else 'number'
|
||||||
|
if val in ['true', 'false', 'nil']: t = 'keyword'
|
||||||
constants.append({'type': t, 'value': val})
|
constants.append({'type': t, 'value': val})
|
||||||
return len(constants) - 1
|
return len(constants) - 1
|
||||||
|
def emit(op, a=0, b=0, c=0): instructions.append([self.op_to_id[op], a, b, c])
|
||||||
def load_expr_to_reg(expr, reg):
|
def gen_expr(expr, target):
|
||||||
if expr['type'] == 'IDENT':
|
if not expr: return
|
||||||
if expr['value'] in locals_map:
|
if expr['type'] == 'NUMBER': emit("LOADK", target, add_const(float(expr['value'])))
|
||||||
instructions.append([self.op_to_id["MOVE"], reg, locals_map[expr['value']], 0])
|
elif expr['type'] == 'STRING': emit("LOADK", target, add_const(expr['value']))
|
||||||
else:
|
elif expr['type'] == 'KEYWORD': emit("LOADK", target, add_const(expr['value']))
|
||||||
instructions.append([self.op_to_id["GETGLOBAL"], reg, add_const(expr['value']), 0])
|
elif expr['type'] == 'variable':
|
||||||
elif expr['type'] in ['STRING', 'NUMBER']:
|
if expr['name'] in locals_map: emit("MOVE", target, locals_map[expr['name']])
|
||||||
val = expr['value']
|
else: emit("GETGLOBAL", target, add_const(expr['name']))
|
||||||
if expr['type'] == 'NUMBER':
|
elif expr['type'] == 'index':
|
||||||
try: val = float(val)
|
br = self.next_reg; self.next_reg += 1; gen_expr(expr['base'], br)
|
||||||
except: pass
|
kr = self.next_reg; self.next_reg += 1; gen_expr(expr['key'], kr)
|
||||||
instructions.append([self.op_to_id["LOADK"], reg, add_const(val), 0])
|
emit("GETTABLE", target, br, kr); self.next_reg -= 2
|
||||||
|
elif expr['type'] == 'binary':
|
||||||
|
lr = self.next_reg; self.next_reg += 1; gen_expr(expr['left'], lr)
|
||||||
|
rr = self.next_reg; self.next_reg += 1; gen_expr(expr['right'], rr)
|
||||||
|
op_m = {'+': 'ADD', '-': 'SUB', '*': 'MUL', '/': 'DIV'}
|
||||||
|
emit(op_m.get(expr['op'], 'ADD'), target, lr, rr); self.next_reg -= 2
|
||||||
|
elif expr['type'] == 'unary':
|
||||||
|
or_reg = self.next_reg; self.next_reg += 1; gen_expr(expr['operand'], or_reg)
|
||||||
|
op_m = {'-': 'UNM', '#': 'LEN', 'not': 'NOT'}
|
||||||
|
emit(op_m.get(expr['op'], 'UNM'), target, or_reg); self.next_reg -= 1
|
||||||
|
elif expr['type'] == 'call': gen_call(expr, target)
|
||||||
|
def gen_call(node, target):
|
||||||
|
fr = self.next_reg; self.next_reg += 1
|
||||||
|
if node['func']['type'] == 'variable':
|
||||||
|
if node['func']['name'] in locals_map: emit("MOVE", fr, locals_map[node['func']['name']])
|
||||||
|
else: emit("GETGLOBAL", fr, add_const(node['func']['name']))
|
||||||
|
else: gen_expr(node['func'], fr)
|
||||||
|
for i, arg in enumerate(node['args']):
|
||||||
|
ar = self.next_reg; self.next_reg += 1; gen_expr(arg, ar)
|
||||||
|
emit("CALL", fr, len(node['args']) + 1, 2)
|
||||||
|
emit("MOVE", target, fr)
|
||||||
|
self.next_reg = fr
|
||||||
for node in ast:
|
for node in ast:
|
||||||
if node['type'] == 'call':
|
if node['type'] == 'assign':
|
||||||
func_reg = next_reg
|
vr = self.next_reg; self.next_reg += 1; gen_expr(node['value'], vr)
|
||||||
if node['name'] in locals_map:
|
if node.get('local'): locals_map[node['name']] = vr
|
||||||
instructions.append([self.op_to_id["MOVE"], func_reg, locals_map[node['name']], 0])
|
elif 'target' in node:
|
||||||
else:
|
t = node['target']
|
||||||
instructions.append([self.op_to_id["GETGLOBAL"], func_reg, add_const(node['name']), 0])
|
if t['type'] == 'index':
|
||||||
for i, arg_expr in enumerate(node['args']):
|
br = self.next_reg; self.next_reg += 1; gen_expr(t['base'], br)
|
||||||
load_expr_to_reg(arg_expr, func_reg + 1 + i)
|
kr = self.next_reg; self.next_reg += 1; gen_expr(t['key'], kr)
|
||||||
instructions.append([self.op_to_id["CALL"], func_reg, len(node['args']) + 1, 1])
|
emit("SETTABLE", br, kr, vr); self.next_reg -= 2
|
||||||
elif node['type'] == 'assign':
|
else: emit("SETGLOBAL", vr, add_const(node['name']))
|
||||||
val_reg = next_reg
|
elif node['type'] == 'call': gen_call(node, self.next_reg)
|
||||||
load_expr_to_reg(node['value'], val_reg)
|
emit("RETURN")
|
||||||
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}
|
return {"instructions": instructions, "constants": constants}
|
||||||
|
|
||||||
def obfuscate(self, code):
|
def obfuscate(self, code):
|
||||||
|
|||||||
130
core/parser.py
130
core/parser.py
@ -5,102 +5,88 @@ class Lexer:
|
|||||||
self.code = code
|
self.code = code
|
||||||
self.tokens = []
|
self.tokens = []
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
|
LB, RB, DQ, SQ, BS = chr(91), chr(93), chr(34), chr(39), chr(92)
|
||||||
# Using chr() to build regex parts and avoid tool-induced corruption.
|
OP_LIST = r'==|~=|<=|>=|\.\.\.|\.\.|>>|<<|\+|\-|\*|/|%|\^|#|=|\<|\>|\(|\)|\{|\}|' + BS + LB + '|' + BS + RB + '|;|:|,|\.'
|
||||||
LB = chr(91)
|
|
||||||
RB = chr(93)
|
|
||||||
DQ = chr(34)
|
|
||||||
SQ = chr(39)
|
|
||||||
BS = chr(92)
|
|
||||||
|
|
||||||
# List every operator individually to avoid character set issues.
|
|
||||||
OP_LIST = '==|~=|<=|>=|\.\.\.|\.\.|>>|<<|\+|\-|\*|/|%|\^|#|=|\<|\>|\(|\)|\{|\}|' + BS + LB + '|' + BS + RB + '|;|:|,|\.'
|
|
||||||
|
|
||||||
self.rules = [
|
self.rules = [
|
||||||
('COMMENT', re.compile('--' + LB + LB + '.*?' + RB + RB + '|--.*', re.DOTALL)),
|
('COMMENT', re.compile('--' + LB + LB + '.*?' + RB + RB + '|--.*', re.DOTALL)),
|
||||||
('STRING', re.compile(DQ + '(?:' + BS + BS + '.|[^' + DQ + BS + BS + '])*' + DQ + '|' + SQ + '(?:' + BS + BS + '.|[^' + SQ + BS + BS + '])*' + SQ + '|' + LB + LB + '.*?' + RB + RB, re.DOTALL)),
|
('STRING', re.compile(DQ + r'(?:' + BS + BS + r'.|[^' + DQ + BS + BS + r'])*' + DQ + '|' + SQ + r'(?:' + BS + BS + r'.|[^' + SQ + BS + BS + r'])*' + SQ + '|' + LB + LB + '.*?' + RB + RB, re.DOTALL)),
|
||||||
('NUMBER', re.compile(r'\d+\.?\d*')),
|
('NUMBER', re.compile(r'\b\d+\.?\d*(?:[eE][+-]?\d+)?\b|\b0x[0-9a-fA-F]+\b')),
|
||||||
('KEYWORD', re.compile(r'\b(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b')),
|
('KEYWORD', re.compile(r'\b(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b')),
|
||||||
('IDENT', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')),
|
('IDENT', re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*')),
|
||||||
('OP', re.compile(OP_LIST)),
|
('OP', re.compile(OP_LIST)),
|
||||||
('SPACE', re.compile(r'\s+'))
|
('SPACE', re.compile(r'\s+'))
|
||||||
]
|
]
|
||||||
|
|
||||||
def tokenize(self):
|
def tokenize(self):
|
||||||
while self.pos < len(self.code):
|
while self.pos < len(self.code):
|
||||||
match = None
|
match = None
|
||||||
for name, regex in self.rules:
|
for name, regex in self.rules:
|
||||||
match = regex.match(self.code, self.pos)
|
match = regex.match(self.code, self.pos)
|
||||||
if match:
|
if match:
|
||||||
if name != 'SPACE' and name != 'COMMENT':
|
if name != 'SPACE' and name != 'COMMENT': self.tokens.append((name, match.group(0)))
|
||||||
self.tokens.append((name, match.group(0)))
|
self.pos = match.end(); break
|
||||||
self.pos = match.end()
|
if not match: self.pos += 1
|
||||||
break
|
|
||||||
if not match:
|
|
||||||
self.pos += 1
|
|
||||||
return self.tokens
|
return self.tokens
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
def __init__(self, tokens):
|
def __init__(self, tokens): self.tokens = tokens; self.pos = 0
|
||||||
self.tokens = tokens
|
|
||||||
self.pos = 0
|
|
||||||
|
|
||||||
def peek(self, offset=0):
|
def peek(self, offset=0):
|
||||||
index = self.pos + offset
|
idx = self.pos + offset
|
||||||
return self.tokens[index] if index < len(self.tokens) else (None, None)
|
return self.tokens[idx] if idx < len(self.tokens) else (None, None)
|
||||||
|
def consume(self, t=None, v=None):
|
||||||
def consume(self, expected_type=None, expected_value=None):
|
tk = self.peek()
|
||||||
token = self.peek()
|
if not tk or not tk[0]: return None
|
||||||
if not token or not token[0]: return None
|
if t and tk[0] != t: return None
|
||||||
if expected_type and token[0] != expected_type: return None
|
if v and tk[1] != v: return None
|
||||||
if expected_value and token[1] != expected_value: return None
|
self.pos += 1; return tk
|
||||||
self.pos += 1
|
|
||||||
return token
|
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
nodes = []
|
nodes = []
|
||||||
while self.pos < len(self.tokens):
|
while self.pos < len(self.tokens):
|
||||||
node = self.parse_statement()
|
node = self.parse_statement()
|
||||||
if node:
|
if node: nodes.append(node)
|
||||||
nodes.append(node)
|
else: self.pos += 1
|
||||||
else:
|
|
||||||
self.pos += 1
|
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
def parse_statement(self):
|
def parse_statement(self):
|
||||||
token = self.peek()
|
tk = self.peek()
|
||||||
if not token or not token[0]: return None
|
if not tk or not tk[0]: return None
|
||||||
|
if tk[1] == 'local':
|
||||||
if token[1] == 'local':
|
self.consume(); ident = self.consume('IDENT')
|
||||||
self.consume()
|
|
||||||
ident = self.consume('IDENT')
|
|
||||||
if ident:
|
if ident:
|
||||||
if self.peek()[1] == '=':
|
if self.peek()[1] == '=': self.consume(); return {'type': 'assign', 'name': ident[1], 'value': self.parse_expression(), 'local': True}
|
||||||
self.consume()
|
|
||||||
val = self.parse_expression()
|
|
||||||
return {'type': 'assign', 'name': ident[1], 'value': val, 'local': True}
|
|
||||||
return None
|
return None
|
||||||
|
start = self.pos; expr = self.parse_prefix_expression()
|
||||||
if token[0] == 'IDENT':
|
if expr:
|
||||||
ident = self.consume()[1]
|
if self.peek()[1] == '=': self.consume(); return {'type': 'assign', 'target': expr, 'value': self.parse_expression()}
|
||||||
next_token = self.peek()
|
elif expr['type'] == 'call': return expr
|
||||||
if next_token[1] == '(':
|
self.pos = start; return None
|
||||||
self.consume()
|
def parse_prefix_expression(self):
|
||||||
args = []
|
tk = self.peek()
|
||||||
while self.peek()[1] and self.peek()[1] != ')':
|
if not tk or tk[0] != 'IDENT': return None
|
||||||
|
expr = {'type': 'variable', 'name': self.consume()[1]}
|
||||||
|
while True:
|
||||||
|
nt = self.peek()
|
||||||
|
if nt[1] == '.':
|
||||||
|
self.consume(); m = self.consume('IDENT'); expr = {'type': 'index', 'base': expr, 'key': {'type': 'STRING', 'value': '"'+m[1]+'"'}}
|
||||||
|
elif nt[1] == '[': self.consume(); key = self.parse_expression(); self.consume('OP', ']'); expr = {'type': 'index', 'base': expr, 'key': key}
|
||||||
|
elif nt[1] == '(':
|
||||||
|
self.consume(); args = []
|
||||||
|
if self.peek()[1] != ')':
|
||||||
args.append(self.parse_expression())
|
args.append(self.parse_expression())
|
||||||
if self.peek()[1] == ',':
|
while self.peek()[1] == ',': self.consume(); args.append(self.parse_expression())
|
||||||
self.consume()
|
self.consume('OP', ')'); expr = {'type': 'call', 'func': expr, 'args': args}
|
||||||
self.consume('OP', ')')
|
else: break
|
||||||
return {'type': 'call', 'name': ident, 'args': args}
|
return expr
|
||||||
elif next_token[1] == '=':
|
|
||||||
self.consume()
|
|
||||||
val = self.parse_expression()
|
|
||||||
return {'type': 'assign', 'name': ident, 'value': val, 'local': False}
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def parse_expression(self):
|
def parse_expression(self):
|
||||||
token = self.consume()
|
left = self.parse_primary_expression()
|
||||||
if not token: return None
|
while self.peek()[1] in ['+', '-', '*', '/', '..', '==', '<', '>', '<=', '>=', '~=']:
|
||||||
return {'type': token[0], 'value': token[1]}
|
op = self.consume()[1]; right = self.parse_primary_expression()
|
||||||
|
left = {'type': 'binary', 'op': op, 'left': left, 'right': right}
|
||||||
|
return left
|
||||||
|
def parse_primary_expression(self):
|
||||||
|
tk = self.peek()
|
||||||
|
if not tk: return None
|
||||||
|
if tk[1] in ['-', '#', 'not']:
|
||||||
|
op = self.consume()[1]; return {'type': 'unary', 'op': op, 'operand': self.parse_primary_expression()}
|
||||||
|
if tk[0] in ['STRING', 'NUMBER']: v = tk[1]; self.consume(); return {'type': tk[0], 'value': v}
|
||||||
|
if tk[1] in ['true', 'false', 'nil']: return {'type': 'KEYWORD', 'value': self.consume()[1]}
|
||||||
|
if tk[1] == '(': self.consume(); e = self.parse_expression(); self.consume('OP', ')'); return e
|
||||||
|
return self.parse_prefix_expression()
|
||||||
Loading…
x
Reference in New Issue
Block a user