wfuscator
Last Updated Feb 13th, 2023 | September 16th, 2023
wfuscator is a Luau obfuscator developed by Whut.
wfuscator will first convert your Luau script back into Lua, without sacraficing functionality, and then obfuscate.
It is designed with performance and having a low footprint in mind.
Why should you use wfuscator? Well…
wfuscator protects your work from being stolen. Link to heading
wfuscator transforms your code from this:
local function add(a, b)
return a + b
end
print(add(3, 6))
into this monstrosity:
--[[
Obfuscated with wfuscator <https://whut.dev/wfuscator/>
Still a work in progress! Do not deobfuscate!
Credits: @NWhut <https://whut.dev/>; FiOne by Rerumu;
]]
return(function()local a;do local b=bit32;local c;local d;local e;local f=50;local g={[22]=18,[31]=8,[33]=28,[0]=3,[1]=13,[2]=23,[26]=33,[12]=1,[13]=6,[14]=10,[15]=16,[16]=20,[17]=26,[18]=30,[19]=36,[3]=0,[4]=2,[5]=4,[6]=7,[7]=9,[8]=12,[9]=14,[10]=17,[20]=19,[21]=22,[23]=24,[24]=27,[25]=29,[27]=32,[32]=34,[34]=37,[11]=5,[28]=11,[29]=15,[30]=21,[35]=25,[36]=31,[37]=35}local h={[0]='ABC','ABx','ABC','ABC','ABC','ABx','ABC','ABx','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','AsBx','ABC','ABC','ABC','ABC','ABC','ABC','ABC','ABC','AsBx','AsBx','ABC','ABC','ABC','ABx','ABC'}local i={[0]={b='OpArgR',c='OpArgN'},{b='OpArgK',c='OpArgN'},{b='OpArgU',c='OpArgU'},{b='OpArgR',c='OpArgN'},{b='OpArgU',c='OpArgN'},{b='OpArgK',c='OpArgN'},{b='OpArgR',c='OpArgK'},{b='OpArgK',c='OpArgN'},{b='OpArgU',c='OpArgN'},{b='OpArgK',c='OpArgK'},{b='OpArgU',c='OpArgU'},{b='OpArgR',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgR',c='OpArgN'},{b='OpArgR',c='OpArgN'},{b='OpArgR',c='OpArgN'},{b='OpArgR',c='OpArgR'},{b='OpArgR',c='OpArgN'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgK',c='OpArgK'},{b='OpArgR',c='OpArgU'},{b='OpArgR',c='OpArgU'},{b='OpArgU',c='OpArgU'},{b='OpArgU',c='OpArgU'},{b='OpArgU',c='OpArgN'},{b='OpArgR',c='OpArgN'},{b='OpArgR',c='OpArgN'},{b='OpArgN',c='OpArgU'},{b='OpArgU',c='OpArgU'},{b='OpArgN',c='OpArgN'},{b='OpArgU',c='OpArgN'},{b='OpArgU',c='OpArgN'}}local function j(k,l,m,n)local o=0;for p=l,m,n do local q=256^math.abs(p-l)o=o+q*string.byte(k,p,p)end;return o end;local function r(s,t,u,v)local w=(-1)^b.rshift(v,7)local x=b.rshift(u,7)+b.lshift(b.band(v,0x7F),1)local y=s+b.lshift(t,8)+b.lshift(b.band(u,0x7F),16)local z=1;if x==0 then if y==0 then return w*0 else z=0;x=1 end elseif x==0x7F then if y==0 then return w*1/0 else return w*0/0 end end;return w*2^(x-127)*(1+z/2^23)end;local function A(s,t,u,v,B,C,D,E)local w=(-1)^b.rshift(E,7)local x=b.lshift(b.band(E,0x7F),4)+b.rshift(D,4)local y=b.band(D,0x0F)*2^48;local z=1;y=y+C*2^40+B*2^32+v*2^24+u*2^16+t*2^8+s;if x==0 then if y==0 then return w*0 else z=0;x=1 end elseif x==0x7FF then if y==0 then return w*1/0 else return w*0/0 end end;return w*2^(x-1023)*(z+y/2^52)end;local function F(k,l,m)return j(k,l,m-1,1)end;local function G(k,l,m)return j(k,m-1,l,-1)end;local function H(k,l)return r(string.byte(k,l,l+3))end;local function I(k,l)local s,t,u,v=string.byte(k,l,l+3)return r(v,u,t,s)end;local function J(k,l)return A(string.byte(k,l,l+7))end;local function K(k,l)local s,t,u,v,B,C,D,E=string.byte(k,l,l+7)return A(E,D,C,B,v,u,t,s)end;local L={[4]={little=H,big=I},[8]={little=J,big=K}}local function M(N)local O=N.index;local P=string.byte(N.source,O,O)N.index=O+1;return P end;local function Q(N,R)local S=N.index+R;local T=string.sub(N.source,N.index,S-1)N.index=S;return T end;local function U(N)local R=N:s_szt()local T;if R~=0 then T=string.sub(Q(N,R),1,-2)end;return T end;local function V(R,W)return function(N)local S=N.index+R;local X=W(N.source,N.index,S)N.index=S;return X end end;local function Y(R,W)return function(N)local Z=W(N.source,N.index)N.index=N.index+R;return Z end end;local function _(N)local R=N:s_int()local a0=table.create(R)for p=1,R do local a1=N:s_ins()local a2=b.band(a1,0x3F)local a3=h[a2]local a4=i[a2]local a5={value=a1,op=g[a2],A=b.band(b.rshift(a1,6),0xFF)}if a3=='ABC'then a5.B=b.band(b.rshift(a1,23),0x1FF)a5.C=b.band(b.rshift(a1,14),0x1FF)a5.is_KB=a4.b=='OpArgK'and a5.B>0xFF;a5.is_KC=a4.c=='OpArgK'and a5.C>0xFF;if a2==10 then local m=b.band(b.rshift(a5.B,3),31)if m==0 then a5.const=a5.B else a5.const=b.lshift(b.band(a5.B,7)+8,m-1)end end elseif a3=='ABx'then a5.Bx=b.band(b.rshift(a1,14),0x3FFFF)a5.is_K=a4.b=='OpArgK'elseif a3=='AsBx'then a5.sBx=b.band(b.rshift(a1,14),0x3FFFF)-131071 end;a0[p]=a5 end;return a0 end;local function a6(N)local R=N:s_int()local a0=table.create(R)for p=1,R do local a7=M(N)local a8;if a7==1 then a8=M(N)~=0 elseif a7==3 then a8=N:s_num()elseif a7==4 then a8=U(N)end; -- (truncated. Try wfuscator yourself to see a full example ;) )
wfuscator has a low footprint Link to heading
Other traditional Luau obfuscators often have obfuscated scripts that are in the neighborhood of 500kb to 800kb. That’s massive!
With wfuscator, that cuts down to just 40kb (on average).
wfuscator does this by sacraficing some security for size and performance.
It is perfect for environments such as inside a live Roblox game, with tens or potentially hundreds of obfuscated scripts running at the same time.
wfuscator integrates well with your existing tools Link to heading
Tools like Rojo allow you to sync code on your filesystem with your Roblox game. It’s fairly easy to add an extra step into the syncing process, allowing you to sync unobfuscated raw source code and have it obfuscate on the fly!
wfuscator uses a lot of traditional Lua obfuscation techniques Link to heading
wfuscator uses a combination of the following techniques:
- Variable obfuscation
- Constants Array (
string
,number
,bool
& variables) - VM
wfuscator does NOT use getfenv
or setfenv
Link to heading
That’s right! wfuscator does not use getfenv
or setfenv
, allowing the script to be optimized by Roblox’s engine, achieving the best performance possible.
wfuscator supports tons of settings Link to heading
wfuscator has a bunch of configurable obfuscation settings. There are two ways you can apply settings:
- Adding
--@wfuscator
comment anywhere in the file. For example:
--@wfuscator options
local function add(a, b)
return a + b
end
print(add(3, 6))
- Adding a
--@wfuscator
comment within a Lua Block. This allows you to specify options that only apply to that particular Lua Block. For example:
local function add(a, b) --@wfuscator run_unsandboxed=true
return a + b
end
print(add(3, 6))
Options are key=value
pairs joined by ;
. For exampleminified=no;vardebug=yes
Types of values: Link to heading
Type | Description | Example |
---|---|---|
bool | true or false value. Truthy values include: true , yes , no . Falsey values include false , no , 0 | minfied=yes; |
Available Settings: Link to heading
Setting | Type | Description | Default | Example |
---|---|---|---|---|
enabled | bool | Controls whether the obfuscator is enabled. Will only output a prettified version of your code if it is disabled | yes | enabled=yes; |
minified | bool | Controls whether the output file is minified | yes | minfied=yes; |
vardebug | bool | Controls whether all variables will have their unobfuscated name appended in front | no | vardebug=no; |
vm | bool | Whether VM is enabled | yes | vm=yes |
string | bool | Whether string obfuscation is enabled | yes | string=yes |
number | bool | Whether number obfuscation is enabled | yes | number=yes |
bool | bool | Whether boolean obfuscation is enabled (this includes nil s) | yes | bool=yes |
obfuscate_vmrun | bool | Whether to obfuscate the code being compiled into bytecode | yes | obfuscate_vmrun=yes |
out_of_roblox | bool | EXPERIMENTAL! Allows your code to run in different Lua environments. | no | out_of_roblox=yes |
Available Lua Block Settings: Link to heading
Setting | value type | For Lua Blocks of type | Description | Example |
---|---|---|---|---|
run_unsandboxed | bool | Anonymous Functions | Runs the function outside of the VM. Sacrafices security for performance. Use sparingly! | See section below |
wfuscator allows you to run functions outside of the VM seamlessly Link to heading
local mul = 2
local function expensive() --@wfuscator run_unsandboxed=yes
local numbers = {}
for i=1, 100000 do
table.insert(numbers, i ^ mul)
end
return numbers
end
print(#expensive())
or:
local mul = 2
print(#(function() --@wfuscator run_unsandboxed=yes
local numbers = {}
for i=1, 100000 do
table.insert(numbers, i ^ mul)
end
return numbers
end)())
You can test whether the current function is being ran in a VM by printing the _VERSION
global like so:
print(_VERSION) -- "Luau" / "wfuscator-vm"
Try wfuscator Link to heading
Click here to try wfuscator (note: rate limited to 2 scripts/minute)
Credits Link to heading
wfuscator wouldn’t have been possible without these projects.