Dissecting PureCrypter: A Technical Malware Analysis

Analyzing the Infection Chain of a Stealer Malware. Part 2

malware
reversing
loader
dropper
.NET
Published

May 22, 2025

Introduction

After some time away (lots of distractions) from the initial analysis of the infection chain, explained in the previous blog post, I decided to continue the investigation.

In that post, I briefly suggested that the malware might be part of the Heracles infostealer family. However, that assumption was superficial and primarily based on VirusTotal labels. Some vendors had also tagged it as “PureLog,” but at the time, I had no idea what that referred to, so I blindly ignored it.

As I dug deeper into the next stage, what I initially thought was the final payload, I discovered that it was actually another step in the infection chain. Eventually, I identified it as a loader called PureCrypter.

There are several detailed analyses of this malware. The reports I found most useful are listed below:

date article
2022-06-13 Technical Analysis of PureCrypter: A Fully-Functional Loader Distributing Remote Access Trojans and Information Stealers
2022-08-29 PureCrypter is busy pumping out various malicious malware families
2022-12-16 Types of Recent .NET Packers and Their Distribution Trends in Korea
2022-12-27 Pure coder offers multiple malware for sale in Darkweb forums
2024-01-16 A Full Analysis of the Pure Malware Family: Unique and Growing Threat
2024-05-13 Mallox affiliate leverages PureCrypter in MS-SQL exploitation campaigns
2025-01-28 New TorNet backdoor seen in widespread campaign
2025-05-08 Pure Crypter Malware Analysis: 99 Problems but Detection Ain’t One

To summarize, PureCrypter is a .NET-based malware whose primary function is to download and execute additional payloads. This type of functionality classifies it as a loader or dropper, a critical component in the modern malware ecosystem.

PureCrypter is distributed as part of a Malware-as-a-Service (MaaS) offering by a threat actor known as PureCoder. The service includes various malware tools from the “Pure” family, such as PureMiner, PureLogs, and PureClipper, alongside PureCrypter itself. Customers can subscribe to the service with either monthly or lifetime licenses and are given the ability to customize their PureCrypter builds by selecting options for code injection methods, anti-analysis techniques, and persistence strategies.

Interestingly, I discovered the most recent article on this list, from eSentire, while wrapping up this blog post. It turns out I was analyzing more or less the same sample (or malware version) described in their report, which helped validate many of my findings. It also guided me in renaming some variables to better reflect the original ones and improve overall readability.

In this article, I’ll take a deeper look into the loader itself and correct some of the (bad) assumptions I made in the previous post.

Pure Crypter Outer Layer

This section focuses on analyzing the file 5.exe, which was extracted in the previous post: Analyzing the Infection Chain of a Stealer Malware.

Initial Analysis

The first step is to identify the file:

Detect It Easy reveals it’s a 32-bit .NET binary, heavily obfuscated, and contains packed (encrypted) resources.

The file includes obfuscated class and method names, and two different embedded resources.

Automatic Deobfuscation

In order to understand more what it really does, I used Net Reactor Slayer to help me deobfuscate it.

The resulting structure is slightly more readable in dnSpy, though still partially obfuscated.

The manifest tries to impersonate Macrium Rescue Media Builder. It is a legitimate utility used to restore disk images, repair Windows boot issues, or recover from ransomware attacks wtf.

No relevant strings were found in the binary, only some general imports, the module name (Upsiugyll.exe), and some cryptographic class references. This strongly suggests the file decrypts and loads an embedded payload.

Upsiugyll
Upsiugyll.exe
Txompdygpzu.Properties.Resources.resources
System.Security.Cryptography
CryptoStream
CryptoStreamMode
ICryptoTransform
SymmetricAlgorithm
FromBase64String
CreateDelegate
GenerateKey
set_Key
set_IV
CreateDecryptor

The binary contains two embedded resources:

  • One is very small (359 bytes), with apparently random data.
  • The other is ~1 MB with high entropy (8.0), most likely encrypted.

Main Method Analysis

The program’s entry point is the Main method, which immediately calls another method.

String (de)obfuscation

The strings of the binary are obfuscated, and use the Class4.smethod_0(279212158) function to decrypt them during runtime.

While Karsten Hahn’s (MalwareAnalysisForHedgehogs) excellent video explains how to reverse this kind of obfuscation, it didn’t work in my case.

Example output:

decoded 279212158 : X0X
decoded 279212426 : X0X
decoded 279212474 : X0X
decoded 279212449 : X0X
decoded 279212220 : X0X
...

Analyze Decryption Methods

Looking further, it’s quite easy to discover the real functionality.

The key function is GClass1.smethod_0, which takes two parameters: a Type and a string.

According to .NET documentation, it uses CreateDelegate to invoke a method dynamically:

  • typeof(Action) is used to encapsulate a method with no parameters and no return value.
  • type_0 is the class instance.
  • string_0 is the name of the method to be called.

In our case, GClass1.smethod_0(Class4.smethod_0(279212158)) will be the new class instance, and Class4.smethod_0(279212426) the method to call.

All signs point to this method being responsible for decrypting the large encrypted resource.

This function also acts as a wrapper around a deeper decryption method:

The decryption logic becomes obvious when looking at the method body:

It takes three parameters:

  1. Encrypted Data
  2. Decryption Key
  3. Initialization Vector (IV)

With this knowledge, we just have to place some breakpoints and execute the malware to capture the decrypted payload.

Extract the Encrypted Resource

Dynamic Analysis

Using dnSpy, I began dynamic analysis by setting breakpoints inside the decryption routine. I first identified the AES key, which is retrieved from a separate method:

Continuing execution through the decryption function, I captured the IV and the decrypted data:

Key:

  • 5488863CFB8BAFDDD9194AD722F0D263DFF9A80051D7739DE22E24316BF42C46
  • VIiGPPuLr93ZGUrXIvDSY9/5qABR13Od4i4kMWv0LEY=

IV:

  • 9F01D9E9F6881F70A4A59268514BE120
  • nwHZ6faIH3CkpZJoUUvhIA==

The decrypted data (held in the array variable) is another binary, which I saved as 6.dll.

At the end of the main method, we can see the malware creating a delegate for a new method call using the decrypted assembly (“MkGDQLeJ4nATPJctZX.NGCpBwA62Zg4TyqkUP”) and entry method (“IBdGkVOwc”).

Static Analysis With Openssl

To verify the dynamic results, I attempted to decrypt the resource statically using OpenSSL. I saved the encrypted resource as Jobotp, and used the known key and IV:

$ openssl enc -d -aes-256-cbc -in Jobotp -out Jobotp_dec -A -K 5488863CFB8BAFDDD9194AD722F0D263DFF9A80051D7739DE22E24316BF42C46 -iv 9F01D9E9F6881F70A4A59268514BE120
$ sha256sum Jobotp* 6.dll
b988bafe5ed6c8249f8f930be0b5c825cc76d62b23ec635d6e2ee03170415cb2  Jobotp
1edfa0f52db64ab0ee4ec893e49967eea811da7aec2c5f865c5653d2b6cfc643  Jobotp_dec
1edfa0f52db64ab0ee4ec893e49967eea811da7aec2c5f865c5653d2b6cfc643  6.dll

The hashes of the decrypted file (Jobotp_dec) and the dynamically extracted 6.dll are the same, great :)

Pure Crypter Inner Layer

The next stage of the malware, saved as 6.dll, is another obfuscated .NET binary.

Initial Analysis

Once again, this is a 32-bit .NET executable. Packed, encrypted, and obfuscated.

The entropy shows packed content:

The binary is deeply obfuscated, and although the entry point (“IBdGkVOwc”) was extracted in the previous stage, it doesn’t seem to do anything meaningful on its own.

Embedded resources of 6.dll:

  • 0x000526B3: a8sd8xXjW1fQDg5lt1.TtAThnlX8QQWlpiIxY (328269 bytes)
  • 0x0004EC48: eL6CJJmBevreXd7yBF.JaNMZ3LqTZJqXYVa7s (14951 bytes)
  • 0x000A2904: NjeipF3nBvRK43tZ0T.kF2eOl1lrd1DxwkVBP (256 bytes)
  • 0x000A2A08: OgrEIKkQH20BVumdDM.6xsIgkhl6Yf897SD0n (337408 bytes)
  • 0x000F500C: tgZ33CHmTLqfrCcG2b.8Z63dnKmBDPRIoEiDg (1116 bytes)
  • 0x0004D894: xVi40WxNWgcbs5XrtX.XflwYC6A8MigIQwC9W (5040 bytes)

Automatic Deofbuscation

I ran .NET Reactor Slayer again to simplify the analysis process.

While the obfuscation is slightly reduced and the code is easier to navigate, it still contains over a hundred classes and even more methods.

The resources have been reduced to only three:

  • 0x0001BAF0: eL6CJJmBevreXd7yBF.JaNMZ3LqTZJqXYVa7s (14951 bytes)
  • 0x0001F55C: NjeipF3nBvRK43tZ0T.kF2eOl1lrd1DxwkVBP (256 bytes)
  • 0x0001F660: OgrEIKkQH20BVumdDM.6xsIgkhl6Yf897SD0n (337408 bytes)

String Deofbuscation

This sample uses a similar string encryption technique as seen in the previous layer. Here, strings are decrypted using Class47.smethod_0().

I tested this by decrypting a single string, and it worked! Then I used Karsten’s Python script to automatically extract more strings:

python.exe .\string_decrypter.py "C:\Users\user\Desktop\6_Slayed.dll" 0x0600020a

This time, the script worked perfectly. Shoutout to Karsten Hahn <3

With the decrypted strings in place, many functions became much clearer. For example, in Class3, several dynamically loaded system functions are now visible:

After renaming the methods and variables, the code starts to make more sense:

By analyzing these functions and their usage across the binary, I was able to identify what appears to be the main logic of this payload. After some time and a lot of manual renaming and tracing, I began to understand the inner workings.

I renamed all the function names and parameters myself. Some names might not be exact or even misleading, but I picked ones that made the most sense to me during the analysis.

Main Function and Malware Configuration

The main function begins by loading an embedded resource, which is then decrypted and decompressed. The resulting data is deserialized into objects, which are mapped to internal classes.

By debugging the malware and placing a breakpoint after this stage, I was able to extract the configuration data (distributed across three classes), and rename the associated fields to improve readability and make more sense of it. This process took several iterations.

All these configuration options are used later in the code, enabling or disabling specific functionalities. Many of these options have been previously observed in the samples analyzed by Romain Dumont from Zscaler (2022), and they are also present in the lastest eSentire report (2025).

Additionally, leaked images of PureCrypter’s builder helped me to confirm, rename, and identify some features in the binary.

  • PureCrypter Builder v2 (2022):

  • PureCrypter Builder v4 (2025):

Resources

There are three embedded resources after slayering the binary. One of them, OgrEIKkQH20BVumdDM.6xsIgkhl6Yf897SD0n, is quite large (337408 bytes). By looking at the entropy, it revealed a constant value of 8.0, strongly indicating that this resource is encrypted.

I tried to manually extract its contents, but nothing worked. I kept thinking it might hide another part of the malware, like a payload, or some extra piece of the loader. That idea stuck with me the whole time and became the reason I kept going deeper into the analysis.

I had already found that the malware’s configuration is in some place of the resources, but the line that gets the object ("Qjbdw") doesn’t directly match any of the visible resources in the binary.

internal static byte[] \uE004 {
    get {
        object @object = Resources.\uE002.GetObject("Qjbdw", Resources.cultureInfo_0);
        return (byte[])@object;
    }
}

[EditorBrowsable(EditorBrowsableState.Advanced)]
internal static ResourceManager \uE002 {
    get {
        if (Resources.resourceManager_0 == null) {
            ResourceManager resourceManager = new ResourceManager("Ygksfxaml.Properties.Resources", typeof(Resources).Assembly);
            Resources.resourceManager_0 = resourceManager;
        }
        return Resources.resourceManager_0;
    }
}

This discrepancy is explained by a helper method within Class68, which I manually renamed to load_resource_OgrEIKkQH20BVumdDM(). This function is called in a lot of different places of the code.

Resource Loader

Class68 is a central piece of the resource unpacking logic:

  • Extracts the embedded resource OgrEIKkQH20BVumdDM.6xsIgkhl6Yf897SD0n using Assembly.GetManifestResourceStream()
  • Unpacks the data
  • Loads the data into memory as an Assembly object
  • Adds the in-memory assembly in AppDomain.CurrentDomain.ResourceResolve, in order to dynamically resolve dependencies.

This mechanism acts as a dependency resolver, and all the dependencies are embedded in a single compressed resource.

Class49 also decrypts resources and adds them to AppDomain.CurrentDomain.ResourceResolve.

Embedded Resource Registry

Class45 reinforces this hypothesis. It contains a dictionary mapping different assembly dependencies.

This list includes multiple resource DLLs, commonly bundled via tools like Costura. These DLLs are not directly visible in the unpacked binary because they’re nested inside the larger OgrEIKkQH20BVumdDM.6xsIgkhl6Yf897SD0n resource.

Pure Crypter Features

With the resource topic clarified, we can continue with the main function analysis.

The main function calls 17 methods, each corresponding to a distinct feature of the malware. These features are enabled or disabled based on the configuration parameters, extracted just before.

In the analyzed sample, many of these features were disabled. However, it was still possible to identify their intended behavior.

The PureCrypter malware includes several mechanisms for executing additional payloads:

    1. run_powershell_command

      Executes a PowerShell command

    1. run_dropper

      Drops a payload to the system’s temporary directory and executes it.

    1. run_input_payload

      Loads and executes a payload either embedded within the binary or downloaded from a remote URL.

      Supports multiple injection techniques:

      - Reflection
      - Process Hollowing
      - Shellcode
    1. run_loader

      Executes shellcode directly from an embedded resource.

Only run_input_payload was enabled in my sample.

1. ensure_single_instance

This function is called at the very start, and ensures the malware runs only once per system. It creates a Mutex using a name from the configuration.

If the mutex already exists, it calls a function to restore the network and exits.

2. antidebug

If is_debug() returns true, the malware exits early after restoring the network.

The is_debug function performs several anti-analysis checks:

  1. CheckRemoteDebuggerPresent
  2. Checks if SbieDll.dll or cuckoomon.dll are loaded (Sandboxie and Cuckoo Sandbox detection)
  3. Number of processors > 2
  4. Parent process is not cmd
  5. Detects virtual environments via WMI ("VMware|VIRTUAL|A M I|Xen" or "Microsoft|VMWare|Virtual")
  6. Checks monitor size
  7. Confirms system is 64-bit
  8. Username is not "john", "anna" or ones containing "xxxxxxxx"

3. ipconfig_release

Disconnects the network using ipconfig /release

4. sleep_x_seconds

Implements a delay before proceeding. Wasting my time.

5. bypass_AMSI

Bypasses the AMSI using in-memory patching.

The obfuscation layer simply strips “Hzhmoojfp” from strings to reveal amsi.dll and AmsiScanBuffer.

private static string smethod_1(string string_0)
{
    return string_0.Replace("Hzhmoojfp", null);
}

The function:

  • Resolves AmsiScanBuffer via GetProcAddress
  • Deobfuscates the patch
  • Sets memory to PAGE_EXECUTE_READWRITE
  • Patches the function using Marshal.Copy
  • Restores the original memory protection

Patch forces AmsiScanBuffer to always return 0x80070057 (E_INVALIDARG), making AMSI scans fail.

  • 64-bit patch: "uFcAB4D"
0x00000000      b857000780     mov eax, 0x80070057
  • 32-bit patch: "uFcAB4DCGAA="
0x00000000      b857000780     mov eax, 0x80070057
0x00000005      c21800         ret 0x18

This technique is well-documented in Rasta Mouse’s article.

6. bypass_ETW

Disables Event Tracing for Windows (ETW) by patching EtwEventWrite.

This technique is detailed in The Red Team Vade Mecum. It prevents ETW from logging telemetry data, evading detection by Windows Defender and EDR tools.

The function patches EtwEventWrite similarly to the AMSI bypass:

  • 64-bit patch: "whQA"
0x00000000      c3             ret
  • 32-bit patch: "ww=="
0x00000000      c21400         ret 0x14

7. DLL_unhooker

Implements API unhooking for ntdll.dll and kernel32.dll on Windows 10+ systems.

The core logic is in remove_security_hooks, which performs the following steps:

  • Loads the target DLL
  • Gets information about the loaded module via GetModuleInformation
  • Locates the original file from the system directory
  • Opens the clean DLL file from disk and creates a file mapping of it
  • Maps the file into memory to access its original contents
  • Changes memory protection to allow writing
  • Copies the clean, original code from the file mapping over the potentially hooked code in memory
  • Restores the original memory protection

This is a technique used to blind security tools that use API hooking to monitor malware behavior. By “unhooking” these DLLs, the malware restores the original, unmodified functions, effectively bypassing the security monitoring.

8. defender_exclusion

If the defender_bypass option is enabled in the configuration, the malware attempts to add its own executable and the persistence file to Windows Defender’s exclusion list.

The process:

  • Constructs PowerShell commands to exclude:
    • The malware file path
    • The persistence file path
  • Encodes the commands in Base64
  • Enters an infinite loop attempting to execute the commands with elevated privileges and a hidden window

9. run_powershell

Executes arbitrary PowerShell commands defined in the configuration.

  • If runas is set in the config, it attempts to execute with elevated privileges
  • Uses ProcessStartInfo with CreateNoWindow and WindowStyle.Hidden to remain stealthy
  • Keeps retrying until the command runs successfully (if elevated execution is required)

10. run_dropper

Drops and executes an embedded payload to the system’s temporary directory.

Execution flow:

  • Checks if the dropper feature is enabled and if persistence is already installed
  • Builds the file path in the system’s temp directory (Path.GetTempPath())
  • If one_time is false or the file doesn’t already exist:
    • The payload is reversed and GZip decompressed
    • Saved to disk
    • Executed using Process.Start()

This is a traditional dropper approach.

11. show_fake_message

Displays a fake message box to deceive the user.

12. setup_persistence

This function checks the configuration to determine if persistence should be installed, and chooses one of three available methods:

  1. Registry Run key

    Adds a value to HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run pointing to the malware executable path.

  2. VBS Startup Script

    Drops a .vbs file into the Startup folder which silently executes the malware.

    File.WriteAllText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Startup), Path.GetFileNameWithoutExtension(fileInfo.FullName)) + ".vbs", "CreateObject(\"WScript.Shell\").Run \"\"\"" + fileInfo.FullName + "\"\"\"");
  3. Scheduled Task

    Creates a Windows scheduled task that:

    • Runs repeatedly every 2–4 minutes
    • Is not disabled by battery usage settings
    • Has no time limit and is not hidden
    • Executes the malware persistence path

In all cases, the malware first ensures the target persistence directory exists and copies itself to the configured location.

Anti-upload

If anti_upload option is enabled in the configuration, and no input_payload.destination_path is defined, the malware appends 260MB-300MB of random bytes to its copied file. This bloats the file to avoid automatic submission to antivirus vendor clouds, which often restrict large samples.

13. anti_file_delete

If enabled, the malware opens its own file with restrictive FILE_SHARE_READ permissions, then uses DuplicateHandle to pass the handle to explorer.exe.

This prevents the file from being deleted, as explorer.exe holds an open handle with exclusive access.

14. run_input_payload

This is the main loader. It first attempts to enter debug mode if running as admin, then retrieves the payload and executes it using one of the configured injection methods.

If load_from_disk is false, the loader fetches the payload from a remote URL. Otherwise, it decrypts and decompresses the local embedded payload.

If the payload is embedded, it is decrypted and decompressed using the same method and encryption keys as the configuration, meaning it is indeed double-packed.

  • AES Key: /aafR1VtIS+XL4NVuj3yhOxi1YHh8mOLQ9CIjDHJPwM=
  • AES IV: EnIC8TwBlRAospftIvUaCA==

The loader supports three execution methods:

  1. execute_reflection

    Loads and executes the payload as a .NET assembly in memory.

  2. execute_process_hollowing

    Starts a new process (InstallUtil.exe, RegAsm.exe, MSBuild.exe, aspnet_compiler.exe, or AppLauch.exe, based on the information from the leaked builder), then deallocates its original memory and injects the malicious payload in its place.

  3. execute_shellcode

    Allocates memory in another process (or the same one if target doesn’t exist), writes shellcode, and spawns a thread to run it.

15. run_loader

This function executes an additional payload embedded in the configuration. It works similarly to the run_dropper function (reversing and decompressing the payload) but instead of writing it to disk, it injects and runs it directly using the previously described execute_shellcode method.

16. self_deletion

As part of its cleanup routine, and if enabled in the configuration, the malware attempts to delete its own executable using a PowerShell command.

17. ipconfig_renew

As a final step, the malware tries to reconnect the network using ipconfig /renew

Then, it terminates its own process:

Process.GetCurrentProcess().Kill();

Next Stage Payload

After all this looong analysis, let’s continue with my actual sample.

By looking at the configuration, many of the explained options were disabled. The only relevant information was the payload, of only 0x22 bytes.

hxxps://usa[.]trendys[.]cloud/code.bin

Sadly, at the time of the analysis, the domain was no longer alive :(

But that didn’t stop me there. I checked urlscan and bingo! There was one hit, and it even captured the downloaded file.

SHA256: dffdda5092b49622eb45606a32a23015c08e81df9fc4a8ba75bc1f6c32fa1b8e
MIME: Audio file with ID3 version 2.4.0
Size: 7 MB (6975602 bytes, 100% done)
Downloaded from: https://usa[.]trendys[.]cloud/

A 7MB audio file? That sounded familiar…

The hash matched the one analyzed in my previous blog post, confirming it’s from the same campaign. However, it wasn’t the code.bin file I was after.

Luckily (or not), I found it on VirusTotal:

According to VirusTotal, the downloaded file is a Rhadamanthys stealer.

But this post is already long enough. Let’s save that analysis for another time, if so.

Conclusion

I wanted to be absolutely sure the loader didn’t contain any hidden payloads, so I went all in on a full deep dive.

In the end, my particular sample wasn’t all that interesting, but the journey was well worth it. I learned a ton about this loader and .NET internals along the way.

If you made it this far, I hope you learned something too :)

Back to top