AsyncRAT Analysis

Hero

I previously wrote an article on AsyncRAT JS Loader Analysis which began with the initial access phase. However, at that time, I was unable to obtain any malware samples loaded by the loader, so I had to abandon the investigation at that point. Therefore, this time, I decided to analyze a different AsyncRAT malware sample.

1st Stage (Loader, Spreader)

  • Download URL: MalwareBazaar
  • SHA256: f52eeccf731a3deb198e5ddb2e8dd8e5041c8c2d740fe1e2830f48d97ebd3801

First, as usual, I downloaded the sample from MalwareBazaar and extracted it.

7z e f52eeccf731a3deb198e5ddb2e8dd8e5041c8c2d740fe1e2830f48d97ebd3801.zip

Just to be sure, I verified the hash.

Get-FileHash -Path f52eeccf731a3deb198e5ddb2e8dd8e5041c8c2d740fe1e2830f48d97ebd3801.exe

Algorithm       Hash                                                                   Path
---------       ----                                                                   ----
SHA256          F52EECCF731A3DEB198E5DDB2E8DD8E5041C8C2D740FE1E2830F48D97EBD3801       C:\Users\...

.NET, Obfuscation, Packer

According to Detect It Easy, it is written in .NET and uses an unknown obfuscator and packer:

1st Stage Die

So, I tried to deobfuscate it using de4dot:

de4dot f52eeccf731a3deb198e5ddb2e8dd8e5041c8c2d740fe1e2830f48d97ebd3801.exe

Code Analysis

Since it’s written in .NET, I analyzed it statically using dnSpy and its file structure is as follows:

1st Stage dnSpy Tree

When I right-clicked on Lnavamowijd.exe in the tree and selected Go to Entry Point, I found the Main function:

1st Stage dnSpy Main

Dynamic Assembly Generation

1st Stage Class22 Clas23 Method1

It dynamically generates the assembly by reflectively executing the DefineDynamicAssembly method, likely to hinder static analysis.
Also, since method_0 is called within the above code, let’s analyze it as well.

1st Stage Class22 Class23 Method0 1

As shown in the screenshot above, it begins by decrypting the obfuscated Base64-encoded binary. After Base64-decoding, each byte is XORed with a specific key. The result is then converted to a UTF-8 string and split into individual entities using ; as the delimiter.

To obtain the array of decrypted strings, I (and ChatGPT) wrote a Python script to perform the decrypting.

# decrypt.py
import base64

b64_string = "niwuxktC4wc41EJKriE03UABjCYu10NNoSxm9UtbiDspwFduviY430xDtG4611pwiyAx3mBOoDBm3V5whDs4w1tOoTwpyxVIqCEC/ktBqiE1iWlKuQEkwktpvzow+k9BqTk4iUlKuQoT00NK9hwz1ktXgjNm4EtOqQYpwEdBqm4c1koUqjAp7X5Avjwp20FB9jI4xnFsuCcv10BbiTow00dB9gY4xmpOuTRmgxgf9Wxm811cqDg/3ld8qCcr11wUnjwwwkJKjCYu10NNoSwYyl5Doic4wBVNrDc43lhC9iYw3UVKuTAuxg=="

# Base64 decode
encrypted_bytes = base64.b64decode(b64_string)

# XOR
xor_keys = [205, 85, 93, 178, 46, 47]
decrypted_bytes = bytearray()
for i, b in enumerate(encrypted_bytes):
    decrypted_bytes.append(b ^ xor_keys[i % 6])

# Convert to UTF-8
decoded_string = decrypted_bytes.decode('utf-8')

# Output
for idx, entry in enumerate(decoded_string.split(";")):
    print(f"[{idx:02}] {entry}")

As a result, I obtained the following strings by this script:

python decrypt.py

[00] System.Reflection.Assembly
[01] GetEntryAssembly
[02] get_FullName
[03] op_Inequality
[04] get_Length
[05] GetTypeFromHandle
[06] get_Name
[07] IndexOf
[08] ReadString
[09] Add
[10] get_Position
[11] get_CurrentDomain
[12] SetData
[13] 16089
[14] AssemblyServer
[15] SimpleAssemblyExplorer
[16] babelvm
[17] smoketest

Based on this result, I replaced each array2[x] with the corresponding string in the code that follows within the method_0 function:

1st Stage Class22 Class23 Method0 2

Decrypting obfuscated API names and retrieving them dynamically at runtime is a common technique used for anti-analysis.

1st Stage Class22 Class23 Method0 3

The screenshot above shows the second half of the method_0 function, where the following processing takes place:

  1. Defines local variables using DeclareLocal.
  2. Declares new labels using DeclareLabel.
  3. Stores the decimal value 16089 in the local variable at index 0 (Stloc_0). This value will be used later as an XOR key.
  4. Calls GetEntryAssembly and stores the result in the local variable at index 1 (Stloc_1).
  5. If the result of GetEntryAssembly is null, jumps to a specific label.
  6. Calls get_FullName to get the entry assembly’s name and store it in the local variable at index 6 (Callvirt, method_2, Stloc_S).
  7. If the assembly name contains “AssemblyServer”, “SimpleAssemblyExplorer”, or “babelvm”, sets the local variable at index 0 to 0. (16080 -> 0)
  8. Additionally, if the assembly name does not contain “smoketest”, performs the same action as above.
  9. Gets the string length of arg_0 and initializes a BinaryReader and a Hashtable.
  10. Reads the first string and stores it in the Hashtable with the key -1.
  11. If num is 0, jumps to label3 (skipping the loop); otherwise, jumps to label4 (enter the loop).
  12. In the loop (label5 and label4), XORs the Hashtable keys using 16089 as the XOR key, read corresponding strings, and stores them in the Hashtable.
  13. Finally (label3), the Hashtable is saved globally using the first string read from arg_0 as the key — making the Hashtable accessible from anywhere in the program.

To briefly summarize the processing of the method_0 function: it first detects and avoids specific debugging environments other than SmokeTest, then reads the keys and values (string) for later use the stream_0, which is passed as an argument to the method_1 function. It performs XOR operations on the keys and stores the results in a hashtable, which is then made globally accessible.

This required me to parse the contents of stream_0.

Embedded Payload

1st Stage Class22 cctor

The stream is created before method_1 is called, as shown in the screenshot above. I appears to be resource data embedded in the currently running assembly. Class22.delegate0_0, which is the string returned by the smethod_1 function, serves as the embedded resource name. Below is the code of the smethod_1 function:

1st Stage Class22 Smethod1

I decrypted it using the code above and obtained the string “xzDW”. However, which I checked the Resources section of the malware in dnSpy, only “xOVz” was presented, as shown in the earlier screenshot in this article. That said, since it’s the only file embedded in the resources, I’ll proceed under the assumption that it is the actual payload.

However, since this is the only file embedded in the resources, I will proceed under the assumption that it is the actual payload.

Decrypting the Payload

Another thing to note is that after loading this resource, the data is decrypted by the Class27.smethod_0 function. That function performs rather complex decoding, and I spent a considerable amount of time analyzing it.

In the end, I was able to successfully decrypt the payload by extracting the relevant decryption logic (e.g. Class27, Class33, etc.) and creating a new decryption program in Visual Studio to run it.

However, there was an issue—the beginning of the decrypted file contained some unknown byte data, and the ‘MZ (4D 5A)’ header appeared at offset 0x8B, as shown below:

1st Stage Decrypted Payload Hex

So, I figured that if I extracted the file starting from offset 0x8B to the end, I might be able to obtain a valid PE file—so I did exactly that. And finally, I was able to retrieve it.

2nd Stage (?)

  • SHA256: 3E2061C2ECC2F7BE65A3EF5DF2A1D73CA6F2165474C56AF72D8913DE35DD93F8

2nd Stage Die

According to Detect It Easy, this decrypted file is written in .NET and also uses a packer. So, I attempted to unpack it using de4dot.

de4dot .\xOVz-decrypted-extracted

Then, as usual, I opened it in dnSpy. When I checked the file structure, I found that the xzDW file I mentioned earlier was embedded in the Resources section, as shown below.

2nd Stage Tree

The decrypted xOVz file turned out to be nearly empty, consisting mostly of Resources. In other words, this xzDW file is likely the true payload. However, since it’s only 851 bytes, things started to seem a bit suspicious.

In the end, I wasn’t able to decrypt the file. So I decided to switch to dynamic analysis to observe its behavior instead.

Dumping Injected PE

After executing the first-stage loader, I monitored the process in Task Manager and noticed that the executable process terminated within a second or two, and shortly afterword, a RegAsm.exe process started. This behavior suggests that processs injection, such as process hollowing, may be taking place.

The screenshot below illustrates this.

Dynamic Task Manager

Dynamic Task Manager Details

The Location property shown above is C:\Windows\Microsoft.NET\Framework\v4.0.30319, indicating that a legitimate process is being abused.

To further verify this, I also checked Procmon and observed that CreateFileMapping and Process Create operations were being performed on RegAsm.exe, as shown in the screenshot below:

Dynamic Procmon RegAsm Process Injection

Based on the result from PE-sieve shown below, there is no doubt that the process is injected into RegAsm.exe.

Total scanned:      59
Skipped:            0
-
Hooked:             1
Replaced:           1
Hdrs Modified:      0
IAT Hooks:          37
Implanted:          2
Implanted PE:       1
Implanted shc:      1
Unreachable files:  0
Other:              1
-
Total suspicious:   42

In addition, since PE-sieve dumped the RegAsm.exe process, I decided to perform a static analysis of the dumped RegAsm.exe.

AsyncRAT

  • SHA256: 864E43C24CD678E7DCFA931BDB34AB936DF62C7EE19CE01EB39F9AE39E652952

When I opened the injected RegAsm.exe with Detect It Easy, I obtained the following results:

  • PE32
  • Operation system: Windows(95)[I386, 32-bit, GUI]
  • Linker: Microsoft Linker(8.0)
  • Compiler: VB.NET
  • Language: VB.NET
  • Library: .NET Framework(CLR v4.0.30319)
  • Malware: VenomRAT(6.X)[Obfuscated]
  • (Heur)Protection: Anti analysis[Anti-debug + Anti-SandBoxie]

It appears to be detected as VenomRAT here. Since I wasn’t very familiar with this malware family, I did a quick research on Malpedia, and according to a Rapid7 article, it states:

Significantly, as these RATs evolved, some security vendors have started to blur the line between them, often grouping detections under a single label, such as AsyncRAT or AsyncRAT/VenomRAT. This indicates how closely related the two are, but also suggests that their similarities may cause challenges for detection systems.

As mentioned above, I’m not sure at this point whether the detection is accurate.

In any cases, I first opened the file in dnSpy and renamed the meaningless, randomly generated function and variable names to reflect their actual behavior as accurately as possible.

Entry Point

RAT EntryPoint

When I saw thie Main function, I felt that I had finally found the AsyncRAT (or possibly VenomRAT at this point) I had been searching for. Based on its behavior, there’s little doubt that this is a Remote Access Trojan.

The malware’s actions can be summarised as follows:

  1. Sleeps for a few seconds after executing to evade sandbox detection.
  2. Decrypts hardcoded configuration values encrypted with AES.
  3. Uses a mutex to prevent multiple instances from running.
  4. Immediately terminates itself if it detects an analysis environment.
  5. Establishes persistence on the system.
  6. Sets itself as as critical process to prevent forced termination.
  7. Disables system sleep.
  8. Connects to a C2 server and awaits instructions.

From here, I’ll analyze each of these behavior in detail.

Encrypted Settings

RAT Decrypt Config

At the beginning of this RAT, the Encryption Key and MAC Key are first derived using a key derivation function, as shown in the screenshot below:

RAT Key Deriver

Next, the encrypted and hardcoded settings are decrypted using the AES-CBC algorithm with the derived keys.
These encrypted strings are hardcoded directly in the source code, as shown below:

RAT Hardcoded Config

And the AES decryption method is implemented as follows:

RAT Decrypted Method

More specifically, the decryption is performed using the following method:

  • Algorithm: AES
  • Mode: CBC
  • Padding Mode: PKCS#7
  • Verification: HMAC-SHA256

I (and ChatGPT) created a Python script based on the mechanism to decode all the hard-coded configuration values:

import base64
import hmac
import hashlib
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import sys

def decrypt_payload(base64_input, key, hmac_key):
    # Base64 decode
    data = base64.b64decode(base64_input)
  
    if len(data) < 48:
        raise ValueError("Data is too short to contain MAC + IV")

    # Extract MAC and verify
    provided_mac = data[:32]
    encrypted_data = data[32:]  # includes IV + ciphertext

    # Compute MAC on the encrypted_data portion
    computed_mac = hmac.new(hmac_key, encrypted_data, hashlib.sha256).digest()
    if not hmac.compare_digest(provided_mac, computed_mac):
        raise ValueError("Invalid MAC. The data has been tampered with or key is wrong.")

    # Extract IV (next 16 bytes) and ciphertext
    iv = encrypted_data[:16]
    ciphertext = encrypted_data[16:]

    # AES-256-CBC decrypt
    backend = default_backend()
    cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend)
    decryptor = cipher.decryptor()
    padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()

    # Remove PKCS7 padding
    padding_len = padded_plaintext[-1]
    plaintext = padded_plaintext[:-padding_len]

    return plaintext.decode('utf-8')

if __name__ == "__main__":
    KEY_32 = bytes.fromhex("[REDACTED]")  # encryption key
    HMAC_KEY_64 = bytes.fromhex("[REDACTED]")  # MAC key

    if len(sys.argv) != 2:
        print("Usage: python decrypt.py <base64_encrypted_string>")
        sys.exit(1)

    input_base64 = sys.argv[1]

    try:
        result = decrypt_payload(input_base64, KEY_32, HMAC_KEY_64)
        print("[Decrypted Result]")
        print(result)
    except Exception as e:
        print(f"Decryption failed: {e}")

The decrypted data using this script are as follows:

  • Host: 185.196.9.158
  • Port: 6689
  • Version (?): 2022 | Edit 3LOSH RAT
  • Mutex Name: AsyncMutex_6SI8d4s55g4OkPnk
  • Group Name: Default

*All other dump data, including derived encryption keys, MAC keys, signatures, etc., can be viewed on the Dumps page, so please take a look if you are interested.

Based on the decrypted configuration, this RAT connects to the C2 server at 185.196.9.158:6689 to send and receive data. As a side note, the mutex name suggests that this malware is AsyncRAT after all—but the possibility remains that it could be VenomRAT, which may have inherited AsyncRAT’s configuration structure. In any case, I’m still not certain.

HWID Generation

RAT HWID

Along with decrypting the settings, the function also casually generates the target system’s HWID. It appears to to combine the number of CPU cores, user name, machine name, OS version, and drive name into a single string, hash it using MD5, convert the result to hexadecimal, and then use the first 20 characters of that hash as the HWID.

Mutual Exclusion

RAT Init Mutex

Next, it initializes a mutex to prevent multiple instances of the process from running simultaneously. This is a very common technique used in many types of malware.

Anti-Analysis & Anti-Forensics

RAT Anti Analysis

After that, if the current execution environment is identified as an analysis environment, the malware immediately terminates its own process. Additionally, it uses the the Environment.FailFast method to prevent any error message from being displayed.

I’ve also analyzed the details of each method used within this function below.

1. Virtual Machine Detection

RAT Anti Analysis VM

It uses WMI to retrieve the computer’s manufacturer information and performs detection for VMWare or Virtual Box environments.

2. Debugger Detection

RAT Anti Analysis Debug

It uses the CheckRemoteDebuggerPresent function to detect whether the current process is being interfered with by a debugger.

3. Sandboxie Detection

RAT Anti Analysis Sandboxie

It attempts to get a handle to the Sbie.dll module to detect if it is running inside a Sandboxie environment.

4. System Drive Size Checking

RAT Anti Analysis SystemDrive

This function likely checks whether the system drive size is 61 GB or less to detect an analysis environment or honeypot.

RAT Anti Analysis XP

And finally, it checks if the operating system is Windows XP. This malware appears to stop execution on Windows XP.

Persistence

1. Avoiding Duplicate Process

RAT Persistence 1

Next, it performs the persistence process. Here, it likely stops any existing instance of the malware running from the %APPDATA% folder. Based on my investigation, the FileInfo initializing line takes an empty string as an argument instead of using Path.Combine, so I can’t be certain. However, given the code and context, this is the most plausible conclusion. It’s possible that the empty string is assigned a proper value at runtime.

2. Scheduled Tasks & Registry Runkey

RAT Persistence 2

Next, if the current user has Administrator privileges, it registers the malware to run automatically at logon by creating a scheduled task using the following command:

cmd /c schtasks /create /f /sc onlogon /rl highest /tn "[MALWARE_FILE_NAME]" /tr '"[MALWARE_FULL_PATH]" & exit 

If the current user is not an Administrator, it registers the malware path in the Registry Run key:

HKCU\Software\Microsoft\Windows\CurrentVersion\Run

so that it will execute when the system boots.
During dynamic analysis, we observed that suspicious tasks were added, as shown below:

Dynamic Schtasks

3. Self-Replication

RAT Persistence 3

It then deletes the old copy and creates a new copy of the malware.

4. Stopping Itself & C2 Disconnection

RAT Persistence 4

Here, if the current user is Administrator, it marks the current process as Critical, closes the current mutex, and then terminates the connection with the C2 server.

Setting the process as critical causes a blue screen if the process is terminated. This technique is used for anti-analysis and anti-forensics purposes.

5. Temp BAT File Execution

RAT Persistence 5

At the end of the persistence process, a temporary BAT file is created with the following content:

@echo off
timeout 3 > NUL
START "" "[MALWARE_FULL_PATH]"
CD [TEMP_DIR]
DEL "BAT_FILE_PATH" /f /q

It then starts a process to run this BAT file and terminates its own current process.

Process Hardening, Escalation

RAT BSOD

After persistence, it tries to grant special privileges and settings to itself.
First, it attempts to elevate the current process to debug mode, allowing it to perform operations normally restricted in regular mode. Finally, the process is set as Critical, which will cause a BSOD (Blue Screen of Death) if the process is terminated.

Sleep/Display Off Prevention

RAT Prevent Sleep

Furthermore, it attempts to prevent the target system from sleeping or turning off the display by setting the current thread’s state as below:

ES_CONTINUOUS        = 0x80000000 // Keep the state
ES_SYSTEM_REQUIRED   = 0x00000001 // Do not sleep the system
ES_DISPLAY_REQUIRED  = 0x00000002 // Do not off the display

// 0x80000003 (hex) = 2147483651 (decimal)

Command & Control

After completing processes like anti-analysis and persistence, the connection to the C2 server will finally begins.

RAT C2 1

First, if the setting value is “null”, it retrieves the host and port from the configuration and attempts a TCP connection.

RAT C2 2

If the address is not “null”, it sends an HTTP request to that address to obtain the host and port settings externally. This appears to be a method to hide the C2 server address from static analysis. As will be revealed later, the specific address used here seems to be a Pastebin URL.

By the way, in this malware sample, the address is “null”, so the host and port were obtained directly after AES decryption.

1. System Information Gathering

RAT C2 3

After establishing the TCP connection, SSL communication is initialized, and the target system information is sent to the server.

RAT System Info

Specifically, it appears to collect and send the following information:

  • Packet (which means command name or something)
  • HWID
  • Username
  • OS
  • Currently running executable path
  • Version (maybe the malware’s version)
  • Whether the current user is an Administrator
  • An active window title
  • Pastebin address
  • List of installed antivirus names
  • The date and time the malware was installed
  • Ping result
  • Group name

Finally, this information is compiled into a list of strings and compressed using gzip.
The compression functions are as follows:

RAT Compress

RAT Gzip

Then, it uses sslStream.BeginRead to asynchronously wait for data from the C2 server.

2. Adding/Retrieving DLL Plugins in Registry

From here, I will analyze the processing performed after receiving data from the C2 server.

RAT C2 Command 1

The above screenshot shows the first half of the function that processes data after it is received.

If the “Packet” field in the received data contains “plugin”, the “Dll” field is used to retrieve and execute the DLL plugin from the registry. If the specified DLL does not exist in the registry, it requests the plugin from the C2 server by sending a packet with “sendPlugin” in the “Packet” field.

RAT C2 Command 2

RAT C2 Command 3

When a DLL plugin is received from the C2 server, its data is stored as the pair (Hash, DLL bytes) under the registry path HKCU\Software\[HWID]\ as shown in the screenshot above. Then, the plugin is executed.

3. Fileless Malware Execution

RAT C2 Command 4

The above function executes the DLL plugin received from the C2 server.

  1. Retrieves the binary data of the specified DLL from HKCU\Software\[HWID]\ in the registry. Since the data is compressed with gzip, it first decompresses (gunzips) it.
  2. Loads the resulting DLL directly into memory as an assembly without saving it to disk.
  3. Obtains the class “Plugin.Plugin” from the loaded assembly and creates an instance of it.
  4. Uses CallSite to dynamically invoke the “Run” method.
  5. Finally, sends a reception completion notification to the C2 server.

Up to this point, I have analyzed the general behavior of this AsyncRAT. Based on the results of this analysis and referring to the Rapid7 article I introduced earlier, I believe that this malware is definitely AsyncRAT, not VenomRAT. The reason is that VenomRAT seems to have the following processes implemented, but these processes were not seen in this malware:

  • AMSI Bypass
  • ETW Bypass
  • Process Discovery
  • Anti-process Monitoring
  • Webcam Access

And the functionality of this AsyncRAT is basic, and I did not find any Keylogger etc. that are found in its variants.

IOCs

Files

  • Loader.exe: f52eeccf731a3deb198e5ddb2e8dd8e5041c8c2d740fe1e2830f48d97ebd3801
  • 2ndStage.exe: 3E2061C2ECC2F7BE65A3EF5DF2A1D73CA6F2165474C56AF72D8913DE35DD93F8
  • RegAsm.exe: 864E43C24CD678E7DCFA931BDB34AB936DF62C7EE19CE01EB39F9AE39E652952

Hosts

  • 185.196.9.158