How can I deobfuscate MoonSec V3?

Yes, I know that there are already multiple posts discussing this topic. None of them led to any real results. I want a straight answer. How do I deobfuscate MoonSecV3?

A bit of background

Basically, I’ve found a few exploiting scripts, and I want to dissect them for learning purposes. At the top of every single script is This file was protected with MoonSec V3 , followed by… gibberish.

1 Like

By being the one that obfuscated it. Kind of the whole point of that. A quick glance is showing every trick in the book. Basically a master class in obfuscation.

1 Like

Well, no duh xD. I was just wondering if you could deobfuscate it without the original code. If I had the original code, I wouldn’t post this.

What I was trying to say is that is professional obfuscation. So probably not. Can even see some encryption used. I wouldn’t touch this myself. Seems to be a few videos on YouTube (down a dark road).

1 Like

Very unlikely you’ll be able to decrypt this.

Obfuscation is a complicated thing, and this is likely a custom VM. They basically work by removing all the unnecessary information from the code, and creating a custom bytecode format. The person behind the obfuscator creates a VM (example: FiOne) which will execute their custom format. However, because it’s all custom, they have different instruction sets than regular Lua has, so you’d have to reverse engineer what each instruction is doing, and that’s even worse if they mix up the control flow (by adding fake functions, etc). So you’d need a heavy background in obfsucators in general to learn how to do this. Which is not something I have, however, there is a GitHub repository containing a semi-working way to decompile the code of MoonSec V2, but not MoonSec V3.

Example: (using Ironbrew 2.7.1)

print("hello world")

turns into


local Byte         = string.byte;
local Char         = string.char;
local Sub          = string.sub;
local Concat       = table.concat;
local Insert       = table.insert;
local LDExp        = math.ldexp;
local GetFEnv      = getfenv or function() return _ENV end;
local Setmetatable = setmetatable;
local Select       = select;

local Unpack = unpack or table.unpack;
local ToNumber = tonumber;local function decompress(b)local c,d,e="","",{}local f=256;local g={}for h=0,f-1 do g[h]=Char(h)end;local i=1;local function k()local l=ToNumber(Sub(b, i,i),36)i=i+1;local m=ToNumber(Sub(b, i,i+l-1),36)i=i+l;return m end;c=Char(k())e[1]=c;while i<#b do local n=k()if g[n]then d=g[n]else d=c..Sub(c, 1,1)end;g[f]=c..Sub(d, 1,1)e[#e+1],c,f=d,d,f+1 end;return table.concat(e)end;local ByteString=decompress('24A24827524924D27524826G26I26P26U26K24924E27926O27126926925525524824C27923U27H27627R24F24824924827427928027Z27X27U285279275');

local BitXOR = bit and bit.bxor or function(a,b)
    local p,c=1,0
    while a>0 and b>0 do
        local ra,rb=a%2,b%2
        if ra~=rb then c=c+p end
        a,b,p=(a-ra)/2,(b-rb)/2,p*2
    end
    if a<b then a=b end
    while a>0 do
        local ra=a%2
        if ra>0 then c=c+p end
        a,p=(a-ra)/2,p*2
    end
    return c
end

local function gBit(Bit, Start, End)
	if End then
		local Res = (Bit / 2 ^ (Start - 1)) % 2 ^ ((End - 1) - (Start - 1) + 1);
		return Res - Res % 1;
	else
		local Plc = 2 ^ (Start - 1);
        return (Bit % (Plc + Plc) >= Plc) and 1 or 0;
	end;
end;

local Pos = 1;

local function gBits32()
    local W, X, Y, Z = Byte(ByteString, Pos, Pos + 3);

	W = BitXOR(W, 152)
	X = BitXOR(X, 152)
	Y = BitXOR(Y, 152)
	Z = BitXOR(Z, 152)

    Pos	= Pos + 4;
    return (Z*16777216) + (Y*65536) + (X*256) + W;
end;

local function gBits8()
    local F = BitXOR(Byte(ByteString, Pos, Pos), 152);
    Pos = Pos + 1;
    return F;
end;

local function gBits16()
    local W, X = Byte(ByteString, Pos, Pos + 2);

	W = BitXOR(W, 152)
	X = BitXOR(X, 152)

    Pos	= Pos + 2;
    return (X*256) + W;
end;

local function gFloat()
	local Left = gBits32();
	local Right = gBits32();
	local IsNormal = 1;
	local Mantissa = (gBit(Right, 1, 20) * (2 ^ 32))
					+ Left;
	local Exponent = gBit(Right, 21, 31);
	local Sign = ((-1) ^ gBit(Right, 32));
	if (Exponent == 0) then
		if (Mantissa == 0) then
			return Sign * 0; -- +-0
		else
			Exponent = 1;
			IsNormal = 0;
		end;
	elseif (Exponent == 2047) then
        return (Mantissa == 0) and (Sign * (1 / 0)) or (Sign * (0 / 0));
	end;
	return LDExp(Sign, Exponent - 1023) * (IsNormal + (Mantissa / (2 ^ 52)));
end;

local gSizet = gBits32;
local function gString(Len)
    local Str;
    if (not Len) then
        Len = gSizet();
        if (Len == 0) then
            return '';
        end;
    end;

    Str	= Sub(ByteString, Pos, Pos + Len - 1);
    Pos = Pos + Len;

	local FStr = {}
	for Idx = 1, #Str do
		FStr[Idx] = Char(BitXOR(Byte(Sub(Str, Idx, Idx)), 152))
	end

    return Concat(FStr);
end;

local gInt = gBits32;
local function _R(...) return {...}, Select('#', ...) end

local function Deserialize()
    local Instrs = {};
    local Functions = {};
	local Lines = {};
    local Chunk = 
	{
		Instrs,
		Functions,
		nil,
		Lines
	};
	local ConstCount = gBits32()
    local Consts = {}

	for Idx=1, ConstCount do 
		local Type =gBits8();
		local Cons;
	
		if(Type==0) then Cons = (gBits8() ~= 0);
		elseif(Type==3) then Cons = gFloat();
		elseif(Type==1) then Cons = gString();
		end;
		
		Consts[Idx] = Cons;
	end;
Chunk[3] = gBits8();for Idx=1,gBits32() do 
									local Descriptor = gBits8();
									if (gBit(Descriptor, 1, 1) == 0) then
										local Type = gBit(Descriptor, 2, 3);
										local Mask = gBit(Descriptor, 4, 6);
										
										local Inst=
										{
											gBits16(),
											gBits16(),
											nil,
											nil
										};
	
										if (Type == 0) then 
											Inst[3] = gBits16(); 
											Inst[4] = gBits16();
										elseif(Type==1) then 
											Inst[3] = gBits32();
										elseif(Type==2) then 
											Inst[3] = gBits32() - (2 ^ 16)
										elseif(Type==3) then 
											Inst[3] = gBits32() - (2 ^ 16)
											Inst[4] = gBits16();
										end;
	
										if (gBit(Mask, 1, 1) == 1) then Inst[2] = Consts[Inst[2]] end
										if (gBit(Mask, 2, 2) == 1) then Inst[3] = Consts[Inst[3]] end
										if (gBit(Mask, 3, 3) == 1) then Inst[4] = Consts[Inst[4]] end
										
										Instrs[Idx] = Inst;
									end
								end;for Idx=1,gBits32() do Functions[Idx-1]=Deserialize();end;return Chunk;end;
local function Wrap(Chunk, Upvalues, Env)
	local Instr  = Chunk[1];
	local Proto  = Chunk[2];
	local Params = Chunk[3];

	return function(...)
		local Instr  = Instr; 
		local Proto  = Proto; 
		local Params = Params;

		local _R = _R
		local InstrPoint = 1;
		local Top = -1;

		local Vararg = {};
		local Args	= {...};

		local PCount = Select('#', ...) - 1;

		local Lupvals	= {};
		local Stk		= {};

		for Idx = 0, PCount do
			if (Idx >= Params) then
				Vararg[Idx - Params] = Args[Idx + 1];
			else
				Stk[Idx] = Args[Idx + 1];
			end;
		end;

		local Varargsz = PCount - Params + 1

		local Inst;
		local Enum;	

		while true do
			Inst		= Instr[InstrPoint];
			Enum		= Inst[1];if Enum <= 3 then if Enum <= 1 then if Enum > 0 then do return end;else Stk[Inst[2]] = Inst[3];end; elseif Enum == 2 then 
local A = Inst[2]
Stk[A](Stk[A + 1])
else 
local A = Inst[2]
Stk[A](Stk[A + 1])
end; elseif Enum <= 5 then if Enum > 4 then do return end;else Stk[Inst[2]]=Env[Inst[3]];end; elseif Enum > 6 then Stk[Inst[2]] = Inst[3];else Stk[Inst[2]]=Env[Inst[3]];end;
			InstrPoint	= InstrPoint + 1;
		end;
    end;
end;	
return Wrap(Deserialize(), {}, GetFEnv())();

Which is then minimized and turned into

local t=string.byte;local i=string.char;local c=string.sub;local h=table.concat;local l=table.insert;local b=math.ldexp;local g=getfenv or function()return _ENV end;local l=setmetatable;local u=select;local l=unpack or table.unpack;local f=tonumber;local function s(d)local e,n,a="","",{}local t=256;local o={}for l=0,t-1 do o[l]=i(l)end;local l=1;local function r()local e=f(c(d,l,l),36)l=l+1;local n=f(c(d,l,l+e-1),36)l=l+e;return n end;e=i(r())a[1]=e;while l<#d do local l=r()if o[l]then n=o[l]else n=e..c(e,1,1)end;o[t]=e..c(n,1,1)a[#a+1],e,t=n,n,t+1 end;return table.concat(a)end;local r=s('24A24827524924D27524826G26I26P26U26K24924E27926O27126926925525524824C27923U27H27627R24F24824924827427928027Z27X27U285279275');local o=bit and bit.bxor or function(l,n)local e,o=1,0
while l>0 and n>0 do
local a,c=l%2,n%2
if a~=c then o=o+e end
l,n,e=(l-a)/2,(n-c)/2,e*2
end
if l<n then l=n end
while l>0 do
local n=l%2
if n>0 then o=o+e end
l,e=(l-n)/2,e*2
end
return o
end
local function n(e,l,n)if n then
local l=(e/2^(l-1))%2^((n-1)-(l-1)+1);return l-l%1;else
local l=2^(l-1);return(e%(l+l)>=l)and 1 or 0;end;end;local l=1;local function e()local c,a,n,e=t(r,l,l+3);c=o(c,152)a=o(a,152)n=o(n,152)e=o(e,152)l=l+4;return(e*16777216)+(n*65536)+(a*256)+c;end;local function d()local e=o(t(r,l,l),152);l=l+1;return e;end;local function a()local e,n=t(r,l,l+2);e=o(e,152)n=o(n,152)l=l+2;return(n*256)+e;end;local function s()local l=e();local e=e();local c=1;local o=(n(e,1,20)*(2^32))+l;local l=n(e,21,31);local e=((-1)^n(e,32));if(l==0)then
if(o==0)then
return e*0;else
l=1;c=0;end;elseif(l==2047)then
return(o==0)and(e*(1/0))or(e*(0/0));end;return b(e,l-1023)*(c+(o/(2^52)));end;local f=e;local function w(e)local n;if(not e)then
e=f();if(e==0)then
return'';end;end;n=c(r,l,l+e-1);l=l+e;local e={}for l=1,#n do
e[l]=i(o(t(c(n,l,l)),152))end
return h(e);end;local l=e;local function b(...)return{...},u('#',...)end
local function h()local r={};local f={};local l={};local i={r,f,nil,l};local l=e()local o={}for n=1,l do
local e=d();local l;if(e==0)then l=(d()~=0);elseif(e==3)then l=s();elseif(e==1)then l=w();end;o[n]=l;end;i[3]=d();for i=1,e()do
local l=d();if(n(l,1,1)==0)then
local c=n(l,2,3);local t=n(l,4,6);local l={a(),a(),nil,nil};if(c==0)then
l[3]=a();l[4]=a();elseif(c==1)then
l[3]=e();elseif(c==2)then
l[3]=e()-(2^16)elseif(c==3)then
l[3]=e()-(2^16)l[4]=a();end;if(n(t,1,1)==1)then l[2]=o[l[2]]end
if(n(t,2,2)==1)then l[3]=o[l[3]]end
if(n(t,3,3)==1)then l[4]=o[l[4]]end
r[i]=l;end
end;for l=1,e()do f[l-1]=h();end;return i;end;local function i(l,e,t)local n=l[1];local e=l[2];local l=l[3];return function(...)local d=n;local e=e;local n=l;local l=b
local o=1;local l=-1;local r={};local a={...};local c=u('#',...)-1;local l={};local e={};for l=0,c do
if(l>=n)then
r[l-n]=a[l+1];else
e[l]=a[l+1];end;end;local l=c-n+1
local l;local n;while true do
l=d[o];n=l[1];if n<=3 then if n<=1 then if n>0 then do return end;else e[l[2]]=l[3];end;elseif n==2 then
local l=l[2]e[l](e[l+1])else
local l=l[2]e[l](e[l+1])end;elseif n<=5 then if n>4 then do return end;else e[l[2]]=t[l[3]];end;elseif n>6 then e[l[2]]=l[3];else e[l[2]]=t[l[3]];end;o=o+1;end;end;end;return i(h(),{},g())();

Which finally turns into

local t=string.byte;local i=string.char;local c=string.sub;local h=table.concat;local l=table.insert;local b=math.ldexp;local g=getfenv or function()return _ENV end;local l=setmetatable;local u=select;local l=unpack or table.unpack;local f=tonumber;local function s(d)local e,n,a="","",{}local t=256;local o={}for l=0,t-1 do o[l]=i(l)end;local l=1;local function r()local e=f(c(d,l,l),36)l=l+1;local n=f(c(d,l,l+e-1),36)l=l+e;return n end;e=i(r())a[1]=e;while l<#d do local l=r()if o[l]then n=o[l]else n=e..c(e,1,1)end;o[t]=e..c(n,1,1)a[#a+1],e,t=n,n,t+1 end;return table.concat(a)end;local r=s('24A24827524924D27524826G26I26P26U26K24924E27926O27126926925525524824C27923U27H27627R24F24824924827427928027Z27X27U285279275');local o=bit and bit.bxor or function(l,n)local e,o=1,0 while l>0 and n>0 do local a,c=l%2,n%2 if a~=c then o=o+e end l,n,e=(l-a)/2,(n-c)/2,e*2 end if l<n then l=n end while l>0 do local n=l%2 if n>0 then o=o+e end l,e=(l-n)/2,e*2 end return o end local function n(e,l,n)if n then local l=(e/2^(l-1))%2^((n-1)-(l-1)+1);return l-l%1;else local l=2^(l-1);return(e%(l+l)>=l)and 1 or 0;end;end;local l=1;local function e()local c,a,n,e=t(r,l,l+3);c=o(c,152)a=o(a,152)n=o(n,152)e=o(e,152)l=l+4;return(e*16777216)+(n*65536)+(a*256)+c;end;local function d()local e=o(t(r,l,l),152);l=l+1;return e;end;local function a()local e,n=t(r,l,l+2);e=o(e,152)n=o(n,152)l=l+2;return(n*256)+e;end;local function s()local l=e();local e=e();local c=1;local o=(n(e,1,20)*(2^32))+l;local l=n(e,21,31);local e=((-1)^n(e,32));if(l==0)then if(o==0)then return e*0;else l=1;c=0;end;elseif(l==2047)then return(o==0)and(e*(1/0))or(e*(0/0));end;return b(e,l-1023)*(c+(o/(2^52)));end;local f=e;local function w(e)local n;if(not e)then e=f();if(e==0)then return'';end;end;n=c(r,l,l+e-1);l=l+e;local e={}for l=1,#n do e[l]=i(o(t(c(n,l,l)),152))end return h(e);end;local l=e;local function b(...)return{...},u('#',...)end local function h()local r={};local f={};local l={};local i={r,f,nil,l};local l=e()local o={}for n=1,l do local e=d();local l;if(e==0)then l=(d()~=0);elseif(e==3)then l=s();elseif(e==1)then l=w();end;o[n]=l;end;i[3]=d();for i=1,e()do local l=d();if(n(l,1,1)==0)then local c=n(l,2,3);local t=n(l,4,6);local l={a(),a(),nil,nil};if(c==0)then l[3]=a();l[4]=a();elseif(c==1)then l[3]=e();elseif(c==2)then l[3]=e()-(2^16)elseif(c==3)then l[3]=e()-(2^16)l[4]=a();end;if(n(t,1,1)==1)then l[2]=o[l[2]]end if(n(t,2,2)==1)then l[3]=o[l[3]]end if(n(t,3,3)==1)then l[4]=o[l[4]]end r[i]=l;end end;for l=1,e()do f[l-1]=h();end;return i;end;local function i(l,e,t)local n=l[1];local e=l[2];local l=l[3];return function(...)local d=n;local e=e;local n=l;local l=b local o=1;local l=-1;local r={};local a={...};local c=u('#',...)-1;local l={};local e={};for l=0,c do if(l>=n)then r[l-n]=a[l+1];else e[l]=a[l+1];end;end;local l=c-n+1 local l;local n;while true do l=d[o];n=l[1];if n<=3 then if n<=1 then if n>0 then do return end;else e[l[2]]=l[3];end;elseif n==2 then local l=l[2]e[l](e[l+1])else local l=l[2]e[l](e[l+1])end;elseif n<=5 then if n>4 then do return end;else e[l[2]]=t[l[3]];end;elseif n>6 then e[l[2]]=l[3];else e[l[2]]=t[l[3]];end;o=o+1;end;end;end;return i(h(),{},g())();

Overall, without prior background in Lua VMs and machine code, and all of that loveliness, it’s highly unlikely you will be able to deobfuscate this any time soon. You’d likely be better off dumping the constants (something no VM can avoid), and figuring out what the script is trying to do in general.

4 Likes

Thank you for taking the time to write this for somebody like me with zero background on this subject whatsoever xD. Could you explain what you mean by “dumping”? I know very little, so excuse me if this sounds like a stupid question.

1 Like

Note: I have never deobufscated a script, because I do not have the time or brain power for that, however I have worked a little bit with VMs.

Well, it’s complicated because what these obfuscators do, is they remove any trace of the original code, like if you were to successfully deobfuscate it, you’ll never have the original code again, just something that does it, but there’s no comments, local variable names, etc. However, something that these obfuscators DO have are constants, which are like so:

print("hello world")
local i = 0
if i < 5 then
end
function main()
end
local function test()
end

Would have the constants:

"print","hello world",0,5,"main"

(which should be in order of how they are in the original script unless there’s control flow obfuscation)

Which these are much easier to get than say the fully deobufscated code, and will let you see somewhat what the code is doing, but not fully.

Here’s a video I found, I have not tested it:

If you’re confused on the earlier part, obfuscated systems will typically be broken into chunks, an example of a chunk (likely absolutely not written explicitly in the code):

	local Chunk	= {
			Instr	= Instr; -- Instructions
			Const	= Const; -- Constants
			Proto	= Proto; -- Prototypes
			Lines	= {}; -- Lines
			Name	= gString(); -- Grab name string.
			FirstL	= gInt(); -- First line.
			LastL	= gInt(); -- Last line.
			Upvals	= gBits8(); -- Upvalue count.
			Args	= gBits8(); -- Arg count.
			Vargs	= gBits8(); -- Vararg type.
			Stack	= gBits8(); -- Stack.
		};

And in the code, there’s a reference to the ‘Stack’ of each Chunk.


Example, how a script would handle addition:

				elseif (Enum == 12) then -- ADD
					local Stk = Stack;
					Stk[Inst[1]]	= (Inst[4] or Stk[Inst[2]]) + (Inst[5] or Stk[Inst[3]]);

This would handle all addition in the scripts, instead of it being done separately each time.

I got this from Rerubi

More examples?

A = 123

Would turn into:

.CONSTS[ -- this segment of data will hold all constants that the compiled script uses
    "A",
    123
]
.INSTRS[ -- this segment of data will hold all instructions and instruction data
    [LOADK, 0, 1],
    [SETGLOBAL, 1, 1],
    [RETURN, 0, 1]
]

and

local A = 1
print(A + 1)

would turn into

.CONSTS[
    1,
    "print"
]
.INSTRS[
    [LOADK, 0, 0],        -- defining 'A' by loading constant 0 (1) into register 0
    [GETGLOBAL, 1, 1],    -- getting the global 'print' and placing it into register 1
    [ADD, 2, 0, 256],    -- adding register 0 and constant 0
    [CALL, 1, 2, 1],        -- calling the function and not storing return values
    [RETURN, 0, 1]        -- generated by lua
]
1 Like

Genuinely heroes work xD. I hope anybody else stumbles upon this thread first instead of any other similar ones since they all either have no answers or deactivated links. Thank you! I have no more questions.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.