wfuscator · whut.dev

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:

  1. Adding --@wfuscator comment anywhere in the file. For example:
--@wfuscator options
local function add(a, b)
	return a + b
end
print(add(3, 6))
  1. 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

TypeDescriptionExample
booltrue or false value. Truthy values include: true, yes, no. Falsey values include false, no, 0minfied=yes;

Available Settings: Link to heading

SettingTypeDescriptionDefaultExample
enabledboolControls whether the obfuscator is enabled. Will only output a prettified version of your code if it is disabledyesenabled=yes;
minifiedboolControls whether the output file is minifiedyesminfied=yes;
vardebugboolControls whether all variables will have their unobfuscated name appended in frontnovardebug=no;
vmboolWhether VM is enabledyesvm=yes
stringboolWhether string obfuscation is enabledyesstring=yes
numberboolWhether number obfuscation is enabledyesnumber=yes
boolboolWhether boolean obfuscation is enabled (this includes nils)yesbool=yes
obfuscate_vmrunboolWhether to obfuscate the code being compiled into bytecodeyesobfuscate_vmrun=yes
out_of_robloxboolEXPERIMENTAL! Allows your code to run in different Lua environments.noout_of_roblox=yes

Available Lua Block Settings: Link to heading

Settingvalue typeFor Lua Blocks of typeDescriptionExample
run_unsandboxedboolAnonymous FunctionsRuns 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.

luaparser by boolangery
FiOne by Rerumu