Hi!
I’ve been working on my own Lua obfuscator for quite a while now, and I was wondering if anyone would like to provide some feedback.
Contrary to popular obfuscators, this one isn’t a vm obfuscator.
Instead, it mutates the code in a way that aims to provide a faster execution time, for scripts that need to be as fast as possible, while maintaining an additional level of security, making it difficult for exploiters to reverse your code. In addition, with anti decompile (which is integrated into the obfuscator), it’d be more difficult for exploiters to make scripts due to the randomization of key areas in functions (such as constants, etc)
Practicality
Before we get into an actual test script, I want to describe the practicality of this obfuscator.
I have made a tool to obfuscate all local scripts within a place, with optional settings for module scripts.
Just download an .rbxlx of the place you want to obfuscate, run it through the tool and it obfuscates with the settings you want.
I am still working on extending support for Luau scripts, with types already finished, and i’m working on support for compound operators and other Luau syntax.
Current Features
Features
Control Flow
All functions and do blocks are restructured and randomized using loops
Global Encryption and Caching
example:
print("hi") = env["pawdlpawie"]("hi")
in this case, “pawdlpawie” is cached so that it doesn’t have to constantly decrypt the key.
Important rbx…
Right now I’ve done fireserver and invokeserver, as well as findfirstchild and I plan to extend this to most used roblox functions.
Essentially it takes this line:
DamageEntity:FireServer(targetHumanoid, key, damage)
and transforms it to this line
--at top of script
local f = Instance.new("RemoteEvent").FireServer
...
f(DamageEntity, targetHumanoid, key, damage)
This is important mainly in functions, where exploiters might use the FireServer constant to identify certain functions.
Example:
local function sendDamageRequest(humanoid, amount)
DamageEntity:FireServer(humanoid, key, amount)
end
Example Scripts
The following are scripts with different features enabled/disabled to assist with testing
CFlow enabled for all of them.
Anti decompile disabled on all scripts, filesize becomes huge
Vanilla lua script
Below is a beautified version of Ironbrew 2’s bechmark script, which tests closures, gettable and settable, obviously obfuscated
local a = (getfenv or function()
return _ENV
end)()
local b;
local c = {}
local d = setmetatable({}, {
__index = function(self, e)
local f = c[e]
if f then
return f
else
local g = b(e)
local h = a[g]
if not h then
local i = {}local function j(k)
for l, m in next, k do
if type(m) == "table" and not i[l] then
i[l] = true;
j(m)
elseif l == g then
h = m
end
end
end;
j(a)
end;
c[e] = h;
return h
end
end
})
local n = 5482972044399938;
local o = 3139;
local tonumber = tonumber;
local tostring = tostring;
local p = tostring(tonumber)
local q = p.char;
b = function(r)
local s, t = n, 16384 + o;
return r:gsub("%x%x", function(u)
local v = s % 274877906944;
local w = (s - v) / 274877906944;
local x = w % 128;
u = tonumber(u, 16)
local y = (u + (w - x) / 128) * (2 * x + 1) % 256;
s = v * t + w + u + y;
return q(y)
end)
end;
local function z(A, B)
local C, u = 1, 0;
while A > 0 and B > 0 do
local D, E = A % 2, B % 2;
if D ~= E then
u = u + C
end;
A, B, C = (A - D) / 2, (B - E) / 2, C * 2
end;
if A < B then
A = B
end;
while A > 0 do
local D = A % 2;
if D > 0 then
u = u + C
end;
A, C = (A - D) / 2, C * 2
end;
return u
end;
local F = -#"wait, where is the wrapper?" + 154 - 14;
while not not{} do
if F == 113 then
if true then
do
local G, H, I, J, K = -#"wait, where is the wrapper?" + 64 + 35;
while not not{} do
if G == -#"wait, where is the wrapper?" + 219 - 50 then
d["15704E3CBB"]('GETTABLE testing.')
G = 13
end;
if G == 36 then
d["15704E3CBB"]('CLOSURE testing.')
G = -#"lol ib2 fork" + 226 - 26
end;
if G == -#"yes no vm here" + 222 + 26 then
for L = 1, H do
K[d["098B744A2FEE1F95"](L)] = 'EPIC GAMER ' .. d["098B744A2FEE1F95"](L)
end;
G = -#"imagine imaging???" + 34 + 13
end;
if G == 120 then
d["15704E3CBB"]('Iterations: ' .. d["098B744A2FEE1F95"](H))
G = 36
end;
if G == 72 then
d["15704E3CBB"]('IronBrew 2:tm: Benchmark-y Meme')
G = -#"okay guys now table.concat = print" + 37 + 35
end;
if G == -#"imagine vm obfuscating :flushed:" + 61 + 22 then
d["15704E3CBB"]('Time:', d["BCA698CD92"]() - I .. 's')
G = -#"obfuscated using moonsecv3" + 252 + 14
end;
if G == -#"yes buy luraph" + 65 + 19 then
for L = 1, H do
K[1] = K[d["098B744A2FEE1F95"](L)]
end;
G = -#"lol ib2 fork" + 95 - 32
end;
if G == -#"hmmmmmm" + 70 - 25 then
H = 100000;
G = 120
end;
if G == -#"imagine vm obfuscating :flushed:" + 15 + 27 then
for L = 1, H do
(function()
local M = -#"i hate this" + 243 + 13;
while not not{} do
if M == 245 then
if not true then
d["15704E3CBB"]'Hey gamer.'
end;
break
end
end
end)()
end;
G = 101
end;
if G == 188 then
I = d["BCA698CD92"]()
G = 43
end;
if G == 240 then
d["15704E3CBB"]('Total Time:', d["BCA698CD92"]() - J .. 's')
break
end;
if G == -#"i hate this" + 238 + 20 then
K = {}
G = 234
end;
if G == -#"lol ib2 fork" + 33 + 35 then
I = d["BCA698CD92"]()
G = -#"wait, where is the wrapper?" + 326 - 52
end;
if G == 43 then
J = I;
G = 10
end;
if G == 13 then
I = d["BCA698CD92"]()
G = 70
end;
if G == -#"loadstring = print" + 275 - 51 then
d["15704E3CBB"]('SETTABLE testing.')
G = -#"hmmmmmm" + 32 + 31
end;
if G == -#"" + 113 - 12 then
d["15704E3CBB"]('Time:', d["BCA698CD92"]() - I .. 's')
G = 206
end;
if G == -#"" + -15 + 44 then
d["15704E3CBB"]('Time:', d["BCA698CD92"]() - I .. 's')
G = 142
end
end
end
end;
break
end
end
keep in mind, this script isin’t using my custom loader. which is a loadstring meme with some fun integrity checks. (Decided to not include that and soley focus on roblox related obfuscation)
Platform lock: ROBLOX
same script, different settings.
local a = (getfenv or function()
return _ENV
end)()
local b;
local c = {}
local d = setmetatable({}, {
__index = function(self, e)
local f = c[e]
if f then
return f
else
local g = b(e)
local h = a[g]
if not h then
local i = {}local function j(k)
for l, m in next, k do
if type(m) == "table" and not i[l] then
i[l] = true;
j(m)
elseif l == g then
h = m
end
end
end;
j(a)
end;
c[e] = h;
return h
end
end
})
local n = 2915560711885084;
local o = 8418;
local tonumber = tonumber;
local tostring = tostring;
local p = tostring(tonumber)
local q = p.char;
b = function(r)
local s, t = n, 16384 + o;
return r:gsub("%x%x", function(u)
local v = s % 274877906944;
local w = (s - v) / 274877906944;
local x = w % 128;
u = tonumber(u, 16)
local y = (u + (w - x) / 128) * (2 * x + 1) % 256;
s = v * t + w + u + y;
return q(y)
end)
end;
local z, A, B = Instance.new("RemoteEvent").FireServer, Instance.new("RemoteFunction").InvokeServer, Instance.new("Part").FindFirstChild;
local function C(D, E)
local F, u = 1, 0;
while D > 0 and E > 0 do
local G, H = D % 2, E % 2;
if G ~= H then
u+=F
end;
D, E, F = (D - G) / 2, (E - H) / 2, F * 2
end;
if D < E then
D = E
end;
while D > 0 do
local G = D % 2;
if G > 0 then
u+=F
end;
D, F = (D - G) / 2, F * 2
end;
return u
end;
local I = -#"ew?" + 178 + 45;
while not not{} do
if I == -#"i hate this" + 202 + 29 then
if true then
do
local J, K, L, M, N = 142;
while not not{} do
if J == -#"lol ib2 fork" + 84 - 55 then
L = d["69F1"].clock()
J = 135
end;
if J == 62 then
for O = 1, K do
N[1] = N[d["B205300741C4A1AB"](O)]
end;
J = -#"" + 15 + 23
end;
if J == 101 then
for O = 1, K do
N[d["B205300741C4A1AB"](O)] = 'EPIC GAMER ' .. d["B205300741C4A1AB"](O)
end;
J = 195
end;
if J == -#"yes buy luraph" + 248 - 18 then
for O = 1, K do
(function()
local P = -#"bro looking at my code >:(" + 124 + 44;
while not not{} do
if P == -#"emoji bytecode soon" + 127 + 34 then
if not true then
d["DEB6DA7D4D"]'Hey gamer.'
end;
break
end
end
end)()
end;
J = -#"imagine vm obfuscating :flushed:" + 142 - 42
end;
if J == -#"loadstring = print" + 249 - 25 then
d["DEB6DA7D4D"]('Iterations: ' .. d["B205300741C4A1AB"](K))
J = -#"i hate this" + 211 - 50
end;
if J == 72 then
d["DEB6DA7D4D"]('SETTABLE testing.')
J = 245
end;
if J == 135 then
M = L;
J = 216
end;
if J == 221 then
N = {}
J = 101
end;
if J == -#"bro looking at my code >:(" + 241 + 22 then
L = d["69F1"].clock()
J = -#"imagine imaging???" + 119 - 39
end;
if J == -#"loadstring = print" + 115 + 45 then
d["DEB6DA7D4D"]('IronBrew 2:tm: Benchmark-y Meme')
J = -#"ew?" + -9 + 19
end;
if J == -#"imagine imaging???" + 268 - 55 then
d["DEB6DA7D4D"]('Time:', d["69F1"].clock() - L .. 's')
J = 107
end;
if J == 150 then
d["DEB6DA7D4D"]('CLOSURE testing.')
J = -#"yes no vm here" + 42 - 11
end;
if J == 107 then
d["DEB6DA7D4D"]('GETTABLE testing.')
J = 237
end;
if J == 7 then
K = 100000;
J = 206
end;
if J == -#"imagine imaging???" + 107 - 11 then
d["DEB6DA7D4D"]('Total Time:', d["69F1"].clock() - M .. 's')
break
end;
if J == -#"hmmmmmm" + 306 - 54 then
L = d["69F1"].clock()
J = -#"wait, where is the wrapper?" + 298 - 50
end;
if J == -#"imagine vm obfuscating :flushed:" + 129 - 29 then
d["DEB6DA7D4D"]('Time:', d["69F1"].clock() - L .. 's')
J = 72
end;
if J == 38 then
d["DEB6DA7D4D"]('Time:', d["69F1"].clock() - L .. 's')
J = -#"obfuscated using moonsecv3" + 72 + 32
end
end
end
end;
break
end
end
should work on roblox studio. Currently platform lock consists of changing binary operations to comply with roblox syntax, such as a = a +1
to a +=1
, helps prevent people from running this stuff on replit or vscode with vanilla lua5.1. I plan to add more ofc because that much isin’t secure enough.
I need to add integrity checks after I finish adding full Luau support
One thing to note: global encryption might mess up if you set global variables later on. havent tested that yet but my official reply as rn: “oops?”**
Anyways, thanks for reading! I’ve always been invested in obfuscation and security so this was a really fun project I look forward to continuing.
Obfuscation should not be relied on as your main form of security. Your game is only as secure as you make it, no amount of obfuscation will stop an exploiter when you don’t properly secure interactions in your game.
Prioritize secure client-server interactions over safeguarding your code, since exploiters may not even need to analyze your code to make a script that gives them infinite money, kick/ban capabilities, etcetera.
Obfuscation is, essentially something to slow down exploiters and reverse engineering, what’s most important is your server code, and making it secure so that exploiters can’t ruin the experience for others.