Automatically deobfuscate Luraph scripts. Tested on Luraph scripts between v11.5 and v11.8.1 (May 1, 2020 release)
Requires Java 8 to run.
Come discuss this project in my Lua discord
Add me on discord
The Great Sage Equal To Heaven#7780
This project was created for intellectual purposes as a way to explore code deobfuscation techniques on Lua scripts obfuscated by Luraph, which is a tool used to obfuscate Lua scripts. This Lua obfuscator has been used by cybercriminals as a way of protecting malicious exploits they sell for Robolox or bots for World of Warcraft. This project may be of interest to game security developers to prevent cheating in their games by discovering how hackers are breaking their code.
Luraph scripts are extremely complicated to tackle, especially in an automated matter. The process is essentially.
- Lex and parse input Lua file to generate parse tree
- Convert parse tree into abstract syntax treee
- Optimize the abstract syntax tree
- Rename variables, populate symbol table.
- Detect and rename variables and function names into names such as
get_float64
, etc - Detect VM handlers
- Find all encryption information in Lua VM
- Load and decrypt bytecode using above information.
- Decrypt and populate chunks by symbolically executing Lua code (constants, instructions, prototypes).
- Process VM handler redirection and decryption (bytecode redirection).
- Remove antidecompiler tricks from optimized bytecode
- Remove junk code and further optimize bytecode using various techniques and algorithms.
- Generate luac file
LuraphDeobfuscator provides a Lua deobfuscation engine which can be ported to other Lua obfuscators trivially. This source code can be used, with minor modifications, for a vast array of things related to Lua. I emplore the community to reuse my code and engine to create other Lua deobfuscators, as long as they state they are using my code and provide a link back to this project.
Generate a .luac file from an obfuscated Luraph VM script
java -jar LuraphDeobfuscator.jar -i obfuscated.lua -o obfuscated.luac
You may now use a Lua decompiler such as unluac to decompile the code back into Lua. I recommend unluac for decompilation and luadec for generating disassemblies with pseudocode.
I have written the sample Lua code below:
local x = 0
local name = "name: "
for i = 0, 10 do
x = x + i
end
for i = 0, 10 do
name = name .. i .. ","
end
print(x)
print(name)
Ive uploaded the obfuscated version of that Luraph script to pastebin here: https://pastebin.com/SubyTCBL
Here is a screenshot of what the obfuscated code looks like:
We are now going to explore all of Luraph Deobfuscator's features:
Download the above script as file.lua and run the deobfuscator
java -jar LuraphDeobfuscator.java -i file.lua -o file.luac
Use unluac to decompile the generated bytecode and save it to file.lua
java -jar unluac.jar file.luac > file.lua
Deobfuscated code using luadec
local l_0_0 = 0
local l_0_1 = "name: "
for l_0_5 = 0, 10 do
l_0_0 = l_0_0 + l_0_5
end
for l_0_9 = 0, 10 do
l_0_1 = l_0_1 .. l_0_9 .. ","
end
print(l_0_0)
print(l_0_1)
Deobfuscated code using unluac
local L0_0, L1_1, L2_2, L3_3, L4_4, L5_5
L0_0 = 0
L1_1 = "name: "
for L5_5 = 0, 10 do
L0_0 = L0_0 + L5_5
end
for L5_5 = 0, 10 do
L1_1 = L1_1 .. L5_5 .. ","
end
L2_2(L3_3)
L2_2(L3_3)
Of course, we know that using a decompiler we can't always expect to get back a complete version of the source code. In this case luadec generates the complete source code but unluac misses assigning print
to L2_2
To get an accurate dissasembly listing, we can also use luadec's disassembler
luadec -dis file.lua > file.dis.txt
-dis file.lua
; Function: 0
; Defined at line: 0
; #Upvalues: 0
; #Parameters: 0
; Is_vararg: 2
; Max Stack Size: 13
0 [-]: LOADK R0 K0 ; R0 := 0
1 [-]: LOADK R1 K1 ; R1 := "name: "
2 [-]: LOADK R6 K0 ; R6 := 0
3 [-]: LOADK R7 K2 ; R7 := 10
4 [-]: LOADK R8 K3 ; R8 := 1
5 [-]: FORPREP R6 1 ; R6 -= R8; pc += 1 (goto 7)
6 [-]: ADD R0 R0 R9 ; R0 := R0 + R9
7 [-]: FORLOOP R6 -2 ; R6 += R8; if R6 <= R7 then R9 := R6; PC += -2 , goto 6 end
8 [-]: LOADK R6 K0 ; R6 := 0
9 [-]: LOADK R7 K2 ; R7 := 10
10 [-]: LOADK R8 K3 ; R8 := 1
11 [-]: FORPREP R6 4 ; R6 -= R8; pc += 4 (goto 16)
12 [-]: MOVE R10 R1 ; R10 := R1
13 [-]: MOVE R11 R9 ; R11 := R9
14 [-]: LOADK R12 K4 ; R12 := ","
15 [-]: CONCAT R1 R10 R12 ; R1 := concat(R10 to R12)
16 [-]: FORLOOP R6 -5 ; R6 += R8; if R6 <= R7 then R9 := R6; PC += -5 , goto 12 end
17 [-]: MOVE R6 R2 ; R6 := R2
18 [-]: MOVE R7 R3 ; R7 := R3
19 [-]: CALL R6 2 1 ; := R6(R7)
20 [-]: MOVE R6 R2 ; R6 := R2
21 [-]: MOVE R7 R3 ; R7 := R3
22 [-]: CALL R6 2 1 ; := R6(R7)
23 [-]: RETURN R0 1 ; return
If we are curious about manually inspecting the VM ourselves, we can use LuraphDeobfuscator to generate a pseudocode source listing of the Luraph VM either in psuedocode or with its graphical display
To save the VM to a file
java -jar LuraphDeobfuscator -i file.lua -s > file.vm.lua
To copy the VM to your clipboard (CTRL+C/CTRL+P)
java -jar LuraphDeobfuscator -i file.lua -c
The contents of running that command on the script we are interested can be found here:
And when we look at a screenshot we can now easily read the VM code. This process is used internally by LuraphDeobfuscator in order to fully deobfuscate Luraph code.
We can see it even assigns names to functions and variables. Pretty neat!
We can also view the optimized VM graphically:
java -jar LuraphDeobfuscator -i file.lua -s
The following window will be displayed:
It is normally better to use luadec to generate a disassembly of bytecode, but for whatever reason if that doesn't happen or you are curious about exploring LuraphDeobfuscator's features, you can also use it to dump optimized bytecode.
java -jar LuraphDeobfuscator -i file.lua -b > file.bytecode.txt
DUMPING CHUNK
#UPVALUES = 0.0
#PARAMS = 0.0
#MAXSTACKSIZE = 9.0
#CONSTANTS = 7
#INSTRUCTIONS = 24
#PROTOTYPES = 0
Constant 0 = 0.0
Constant 1 = name:
Constant 2 = 10.0
Constant 3 = 1.0
Constant 4 = ,
Constant 5 = print
Constant 6 =
[0] LOADK 0.0 0.0
[1] LOADK 1.0 1.0
[2] LOADK 2.0 0.0
[3] LOADK 3.0 2.0
[4] LOADK 4.0 3.0
[5] FORPREP 2.0 1.0
[6] ADD 0.0 0.0 5.0
[7] FORLOOP 2.0 -2.0
[8] LOADK 2.0 0.0
[9] LOADK 3.0 2.0
[10] LOADK 4.0 3.0
[11] FORPREP 2.0 4.0
[12] MOVE 6.0 1.0
[13] MOVE 7.0 5.0
[14] LOADK 8.0 4.0
[15] CONCAT 1.0 6.0 8.0
[16] FORLOOP 2.0 -5.0
[17] GETGLOBAL 2.0 5.0
[18] MOVE 3.0 0.0
[19] CALL 2.0 2.0 1.0
[20] GETGLOBAL 2.0 5.0
[21] MOVE 3.0 1.0
[22] CALL 2.0 2.0 1.0
[23] RETURN 0.0 1.0
END CHUNK