While were on this topic, I thought to google a few Lua obfuscation scripts and see how they do and how much difficulty I would have in “breaking” them.
The first obfuscator that I found simply converted the text into bytecode, i.e.
local thing = [[function secret() print( "no one will ever find me" ) end]] -- Obfuscation function source: https://glot.io/snippets/f1tt9okm5w local encoded = thing:gsub(".", function(bb) return "\\" .. bb:byte() end) or thing .. "\"" print( encoded ) -- Output: \102\117\110\99\116\105\111\110\32\115\101\99\114\101\116\40\41\10\9\112\114\105\110\116\40\32\34\110\111\32\111\110\101\32\119\105\108\108\32\101\118\101\114\32\102\105\110\100\32\109\101\34\32\41\10\101\110\100\10
Then I thought, “Alright, so how to revert it?” and here’s what I came up with in around 5 minutes.
local function bytecodeToString( code ) local t, a = {}, 1 while true do if code:find( "\\", a ) then local b = code:find( "\\", a+1 ) if b then t[#t+1] = code:sub(a+1,b-1):char() a = b else t[#t+1] = code:sub(a+1):char() break end else break end end return table.concat( t ) end local decoded = bytecodeToString( encoded ) print( decoded )
Well, granted, that obfuscation function wasn’t all that difficult to break, so I found another one to try as well.
-- LuaSeel, source: https://github.com/Direnta/LuaSeel local a=[[function superSecret() print( "this time no one will ever find me" ) end]] a="--// Decompiled Code. \n"..a;function Obfuscate(b)local c="function IllIlllIllIlllIlllIlllIll(IllIlllIllIllIll) if (IllIlllIllIllIll==(((((919 + 636)-636)\*3147)/3147)+919)) then return not true end if (IllIlllIllIllIll==(((((968 + 670)-670)\*3315)/3315)+968)) then return not false end end; "local d=c;local e=""local f={"IllIllIllIllI","IIlllIIlllIIlllIIlllII","IIllllIIllll"}local g=[[local IlIlIlIlIlIlIlIlII = {]]local h=[[local IllIIllIIllIII = loadstring]]local i=[[local IllIIIllIIIIllI = table.concat]]local j=[[local IIIIIIIIllllllllIIIIIIII = "''"]]local k="local "..f[math.random(1,#f)].." = (7\*3-9/9+3\*2/0+3\*3);"local l="local "..f[math.random(1,#f)].." = (3\*4-7/7+6\*4/3+9\*9);"local m="--// Obfuscated with LuaSeel 1.1 \n\n"for n=1,string.len(b)do e=e.."'\\"..string.byte(b,n).."',"end;local o="function IllIIIIllIIIIIl("..f[math.random(1,#f)]..")"local p="function "..f[math.random(1,#f)].."("..f[math.random(1,#f)]..")"local q="local "..f[math.random(1,#f)].." = (5\*3-2/8+9\*2/9+8\*3)"local r="end"local s="IllIIIIllIIIIIl(900283)"local t="function IllIlllIllIlllIlllIlllIllIlllIIIlll("..f[math.random(1,#f)]..")"local q="function "..f[math.random(1,#f)].."("..f[math.random(1,#f)]..")"local u="local "..f[math.random(1,#f)].." = (9\*0-7/5+3\*1/3+8\*2)"local v="end"local w="IllIlllIllIlllIlllIlllIllIlllIIIlll(9083)"local x=m..d..k..l..i..";"..o.." "..p.." "..q.." "..r.." "..r.." "..r..";"..s..";"..t.." "..q.." "..u.." "..v.." "..v..";"..w..";"..h..";"..g..e.."}".."IllIIllIIllIII(IllIIIllIIIIllI(IlIlIlIlIlIlIlIlII,IIIIIIIIllllllllIIIIIIII))()"print(x)end;do Obfuscate(a)end
Well, now we’ve got something interesting! The GitHub page lists its features as: “Math value algorithms, Variable obfuscator/changer, Bytecode encryption, Randomized strings - credit to SadBoy22”, so now we are probably dealing with some serious shite… but let’s give it a go!
The obfuscated/encrypted code is as follows:
--// Obfuscated with LuaSeel 1.1 function IllIlllIllIlllIlllIlllIll(IllIlllIllIllIll) if (IllIlllIllIllIll==(((((919 + 636)-636)\*3147)/3147)+919)) then return not true end if (IllIlllIllIllIll==(((((968 + 670)-670)\*3315)/3315)+968)) then return not false end end; local IIlllIIlllIIlllIIlllII = (7\*3-9/9+3\*2/0+3\*3);local IIlllIIlllIIlllIIlllII = (3\*4-7/7+6\*4/3+9\*9);local IllIIIllIIIIllI = table.concat;function IllIIIIllIIIIIl(IIllllIIllll) function IIlllIIlllIIlllIIlllII(IIlllIIlllIIlllIIlllII) function IllIllIllIllI(IIlllIIlllIIlllIIlllII) end end end;IllIIIIllIIIIIl(900283);function IllIlllIllIlllIlllIlllIllIlllIIIlll(IllIllIllIllI) function IllIllIllIllI(IIlllIIlllIIlllIIlllII) local IIllllIIllll = (9\*0-7/5+3\*1/3+8\*2) end end;IllIlllIllIlllIlllIlllIllIlllIIIlll(9083);local IllIIllIIllIII = loadstring;local IlIlIlIlIlIlIlIlII = {'\45','\45','\47','\47','\32','\68','\101','\99','\111','\109','\112','\105','\108','\101','\100','\32','\67','\111','\100','\101','\46','\32','\10','\102','\117','\110','\99','\116','\105','\111','\110','\32','\115','\117','\112','\101','\114','\83','\101','\99','\114','\101','\116','\40','\41','\10','\32','\32','\112','\114','\105','\110','\116','\40','\32','\34','\116','\104','\105','\115','\32','\116','\105','\109','\101','\32','\110','\111','\32','\111','\110','\101','\32','\119','\105','\108','\108','\32','\101','\118','\101','\114','\32','\102','\105','\110','\100','\32','\109','\101','\34','\32','\41','\10','\101','\110','\100','\10',}IllIIllIIllIII(IllIIIllIIIIllI(IlIlIlIlIlIlIlIlII,IIIIIIIIllllllllIIIIIIII))() -- Well, that's all fine and good, but all we are interested in are the bytes, so let's just copy and paste them into print() print( "'\45','\45','\47','\47','\32','\68','\101','\99','\111','\109','\112','\105','\108','\101','\100','\32','\67','\111','\100','\101','\46','\32','\10','\102','\117','\110','\99','\116','\105','\111','\110','\32','\115','\117','\112','\101','\114','\83','\101','\99','\114','\101','\116','\40','\41','\10','\32','\32','\112','\114','\105','\110','\116','\40','\32','\34','\116','\104','\105','\115','\32','\116','\105','\109','\101','\32','\110','\111','\32','\111','\110','\101','\32','\119','\105','\108','\108','\32','\101','\118','\101','\114','\32','\102','\105','\110','\100','\32','\109','\101','\34','\32','\41','\10','\101','\110','\100','\10'" ) -- Output: '-','-','/','/',' ','D','e','c','o','m','p','i','l','e','d',' ','C','o','d','e','.',' ',' ','f','u','n','c','t','i','o','n',' ','s','u','p','e','r','S','e','c','r','e','t','(',')',' ',' ',' ','p','r','i','n','t','(',' ','"','t','h','i','s',' ','t','i','m','e',' ','n','o',' ','o','n','e',' ','w','i','l','l',' ','e','v','e','r',' ','f','i','n','d',' ','m','e','"',' ',')',' ','e','n','d',' '
Well, that doesn’t look all that obfuscated or encrypted after all. All that we need to do now is get every fourth character, starting from the second character and we’ll get:
-- This function took between 1 and 2 minutes to write. local function bytecodeToStringWithAwesomeSauce( code ) local t = {} for i = 2, code:len(), 4 do t[#t+1] = code:sub(i,i) end return table.concat( t ) end local decoded = bytecodeToStringWithAwesomeSauce( "'\45','\45','\47','\47','\32','\68','\101','\99','\111','\109','\112','\105','\108','\101','\100','\32','\67','\111','\100','\101','\46','\32','\10','\102','\117','\110','\99','\116','\105','\111','\110','\32','\115','\117','\112','\101','\114','\83','\101','\99','\114','\101','\116','\40','\41','\10','\9','\112','\114','\105','\110','\116','\40','\32','\34','\116','\104','\105','\115','\32','\116','\105','\109','\101','\32','\110','\111','\32','\111','\110','\101','\32','\119','\105','\108','\108','\32','\101','\118','\101','\114','\32','\102','\105','\110','\100','\32','\109','\101','\34','\32','\41','\10','\101','\110','\100','\10'" ) print( decoded ) -- Output: --// Decompiled Code. function superSecret() print( "this time no one will ever find me" ) end
The point is that I’ve spent a lot longer to write this lengthy post than I did to create two different functions to undo obfuscation. Obfuscation by itself isn’t worth it, which is why I am interested in seeing what Dave has planned.