General Information and preface

Agent Tesla, according to the data provided by CERT-EU, is one the most prominent threats that are hitting European cyberspace. Because of that, I found that it could be quite interesting understanding its behavior. However, as I usually prefer, instead of focusing on the infection chain or information about TA methodologies, sharing knowledge that were already provided by more authoritative sources, I preferred to focus mostly on malware analysis and in this specific case to the latest encryption algorithm adopted. Then, upon the knowledge acquired, I would like to write a decryptor script that extracts payload configuration.

Encryption variants

Agent Tesla first appeared in 2014, however, its evolution over time could be tracked by multiple TTPs. For the purpose of this article, I’m versioning it through the encryption algorithm adopted. At the time of writing there have been 4 different versions with unique characteristics:

Encryption v1: Through this encryption implementation, strings are stored (encrypted) in base64. The decryption function uses a password and a salt ( both hardcoded) as input for the SHA1 algorithm, in order to generate the decryption key. Then, ciphertext and key are eventually used with AES in CBC mode.

Encryption v2: The main difference from the previous method, is that each encrypted string is paired with a dedicated key and an IV. The algorithm used is still AES in CBC mode.

Encryption v3: In this version, TA completely changed their approach, shifting to a pure xor decryption. The decryption function is defined within the .cctor() constructor. The structure of encrypted strings is quite simple. Each string is contained in a byte array paired with a key. The size of the ciphertext allows the decryption routine to iterate over the byte array distinguishing all parameters.

Encryption v4: The latest version of Agent Tesla is based on a xor string algorithm that stores information within a macro-structure that contains raw data organized as follow:

Figure 1: Encrypted data structure

Figure 1 - Encrypted data structure

In this version we have a macro-struct that contains the encrypted data. It’s actually possible to visualize it as an array where each element follows a specific structure where the first 4 bytes are dedicated to define the encryption data length, then other 4 bytes are used to describe the encryption key and the remaining bytes are reserved for the actual data.

As always, the decryption routine iterates over the encrypted data, using key bytes.

Analysis of encryption v4 and obfuscation routine

As I wrote in the first paragraph Agent Tesla analysis will be carried out on the latest encryption mechanism. Because of that, let’s start analyzing the main components of this algorithm:

private byte[] RXOR(byte[] data, out int key)
key = RandomNumberGenerator.GetInt32(int.MaxValue);
// RXOR Cipher: reverse array order and decrypt byte by byte using single XOR
int n = data.Length - 1;
for (int i = 0; i < n; i++, n--)
data[i] ^= data[n];
data[n] ^= (byte) (data[i] ^ key);
data[i] ^= data[n];
if (data.Length % 2 != 0)
data[data.Length >> 1] ^= (byte) key; // x >> 1 == x / 2
return data;

The key is generated randomly through RandomNumberGenerator.GetInt32(int.MaxValue) function. The int.MaxValue constraint is related to the key size limitation of 4 bytes within the encrypted data structure. The algorithm it’s pretty straightforward, performing xor between plaintext and key bytes. However, what really matters here is the obfuscator that happens at runtime.

The obfuscation routine is part of an open source project. Basically, this obfuscation works creating multiple placeholders that are going to be replaced at runtime. Analyzing the sample statically, this technique messes up with code decompilers such as DnSpy. However, the author wrote a detailed blogpost explaining obfuscator features and its modus operandi:

“After processing all methods we need to do some patches in the injected runtime. First, we need to set up the placeholder struct with the correct attribute values. The struct needs a ClassLayout with packing size 1 and the length of the encrypted data as its size.”

“We also need to create a new field which will be an initialized version of our struct. By adding a DataSegment in its FieldRva, we can use the field to store any raw data we want, in this case, our encrypted string data.”

Following the indication provided by the author, we should have a more clear idea of what these components are and how to hunt for them in the code. Now it’s possible to start looking at the code gathering as much information as possible (e.g., locating these placeholders) and finding out a pattern to write an effective configuration decryptor.

Binary inspection

If we open up DnSpy, we could be overwhelmed by the mess that is going to be presented in front of us. However, trying to follow the code flow, we could start exploring.

Figure 2: Entry point

Figure 2 - Entry point

Starting from the Entry Point, it’s possible to locate something promising. Exploring variable and function calls and following references to the object GWZl2RFJ6nA, it’s possible to bump into quite interesting piece of code.

Figure 3: Obfuscated decryption routine

Figure 3 - Obfuscated decryption routine

First of all, we see the function cpblk that it’s necessary to perform the injection at runtime, moreover, scrolling the code it’s possible to get insight about pointer operations paired with xor. In fact, observing the line 104 in Figure 3, it is very similar to the encryption routine that we saw in the previous paragraph.  Nevertheless, on the top of that, as a final proof that we are looking in the right place, scrolling at the end of the code we see a class that fulfills all requirements requested by the obfuscator.

  • ClassLayout
  • Pack size = 1
  • Size = encrypted data length

Figure 4 - Class placeholder for runtime decryption

Figure 4 - Class placeholder for runtime decryption

Moreover, following the instruction provided by the author, we should be able to locate the encrypted string in raw bytes within the binary. If we have a closer look to struct zqRrwrwgu examining the raw value, we are redirected to a very suspicious sequence of bytes.

figure 5 - Encrypted String

Figure 5 - Encrypted string

Analyzing those bytes, it’s immediate to find out that we are dealing with an encrypted payload that is ready to be decrypted.

Decrypting Strings

The idea behind this script is simple:

  • Find out a class that is big enough to contain the encrypted data ( usually the biggest class in the code). Then we could retrieve raw bytes related to its size from the binary file and forward them towards our decryption routine

Before proceeding, it’s worth mentioning that I’m quite a newbie in .NET interaction with python and generally, I’m still learning .NET layout. Because of that, if someone else is going to produce more efficient code. Please do it! But for now, let’s do a quick look to this this script:

Figure 6 - Agent Tesla decryptor

Figure 6 - Agent Tesla decryptor

  • Line 40 - 44: Gathered references to ClassLayout and FieldRVA Tables.
  • Line 46 - 47: Following the initial idea that encrypted data should be stored in a quite large class, I started to enumerate class, selecting the biggest (likely the one that contains the encrypted strings)
  • Line 51: retrieve the last element of the FieldRVA table that contains our data (I don’t know if it is an unexpected behavior caused by the obfuscation process applied, but the raw data containing the encryption string resulted to be always the last element of the FieldRVA table).

Running our script on one of the latest Agent Tesla sample, we got the following result:

Figure 7 - Decryptor result

Figure 7 - Decryptor result


Agent Tesla Decryptor:

DotNet references:

  • General .NET implementation info
  • Python parser for .NET


  • ae26382f191225447550e9a691453fc3ea2e02127222787c662efc8db63c59e3 (SHA256) MalwareBazaar
  • acedd97c8350bacc485e87ae56c851d08b497c202deb46df28c3e7218cb4469a (SHA256) MalwareBazaar
  • f5751d89bc6f15c3ade6513d3cf44f92a25e8cd25d2f5ad239b8c44f8f732cb8 (SHA256)MalwareBazaar