Howler Cell

Reverse Engineering .NET AOT Malware: A Guide to Trace the Multi-Stage Attack Chain with Binary Ninja

Written by Rahul Ramesh | March 17, 2026 12:07:26 PM Z

Introduction

  • Tutorial-focused research: This blog by Howler Cell is designed as a hands-on, beginner-friendly guide to malware reverse engineering, walking aspiring threat researchers through real-world analysis techniques step by step, using Binary Ninja as the primary toolset throughout.

  • Newly discovered multistage malware chain: The research documents a previously unidentified attack chain delivered via a phishing URL, beginning with a digitally signed lure ZIP containing a C++ Trojan downloader (KeyAuth.exe) alongside legitimate Qt5 modules to appear benign.
  • Spotlight on .NET AOT compilation as an evasion technique: The campaign's core evasion relies on .NET Native Ahead-of-Time (AOT) compiled binaries, which strip traditional .NET metadata, frustrate common .NET analysis tools, and force analysts to fall back on native-level tooling, making detection and reverse engineering significantly harder.
  • Multi-payload delivery including Rhadamanthys and XMRig: The attack chain stages multiple payloads: a .NET AOT loader, additional C++ downloaders, Rhadamanthys infostealer delivered as Donut-packed shellcode, and a final XMRig cryptocurrency miner installed with encrypted configuration and built-in persistence.
  • Sophisticated anti-analysis capabilities: The AOT loader employs a sandbox scoring system evaluating RAM size, system uptime, user file counts, and AV process presence; virtual machine detection via registry inspection; and active suppression of miner activity when monitoring tools like Task Manager, Process Hacker, or x64dbg are detected.
  • Practical Binary Ninja workflow for AOT binaries: A core contribution of the blog is a repeatable four-step methodology, compiling a reference AOT application, generating custom WARP signatures, applying them to the malware sample, and recovering runtime-hydrated strings and type data that took function coverage from under 1% (34 of 4,588 functions) to over 85% (3,918 functions).
  • Growing threat trend: While not yet dominant, .NET AOT adoption among financially motivated threat actors is accelerating, as it provides a low-cost, high-impact obfuscation layer that undermines IL-focused detection pipelines, making the analytical techniques presented in this guide increasingly essential for defenders.

Summary

This blog serves both as an examination of newly identified malware and as a practical guide for researchers beginning their journey into malware analysis. Throughout the guide, we use Binary Ninja to reverse engineer the samples. Howler Cell’s mission is not only to publish research that equips defenders with actionable threat intelligence, but also to empower and educate others to perform their own analysis. We hope this guide proves valuable to aspiring threat researchers.

Howler Cell recently analyzed a newly uncovered multistage malware delivery chain that employs an increasingly prevalent evasion technique: .NET Native Ahead‑of‑Time (AOT) compilation. Unlike traditional .NET assemblies that contain Intermediate Language (IL) and extensive metadata easily parsed by common .NET reverse‑engineering tools, AOT‑compiled binaries introduce significant analytical challenges. These binaries:

    • Generate native machine code with most .NET metadata stripped away
    • Greatly reduce analyst visibility, forcing reverse engineers to rely on native‑level tooling
    • Impede static analysis, automated detection, and accurate function identification

The attack sequence begins with a digitally signed lure bundle that deploys a native C++‑based downloader. Once executed, the downloader initiates a .NET AOT‑compiled loaderstrong>, which is responsible for decrypting and staging additional components. These components include:

    • Additional native C++ downloaders
    • Rhadamanthys malware delivered as Donut‑packed shellcode
    • A final AOT‑based loader responsible for subsequent stages

In the final phase, the loader installs XMRig, a cryptocurrency miner. The miner is delivered with an encrypted configuration and embedded persistence mechanisms designed to maintain durable access to the compromised host.

Notably, the AOT loader integrates multiple evasion and anti‑analysis capabilities, including a sandbox‑scoring system, extensive environmental checks, encrypted resource loading, and suppression of malicious behavior when debugging or monitoring tools are detected.

Overcoming the Challenge with Binary Ninja

To address the analytical challenges presented by AOT‑compiled .NET malware, this research outlines a repeatable workflow in Binary Ninja that enables analysts to regain visibility into heavily stripped binaries. The process includes generating custom WARP signatures, recovering runtime‑hydrated sections, and reconstructing strings, classes, and type structures that are typically removed during AOT compilation.

This blog is designed as a hands‑on guide for malware analysts, providing step‑by‑step methodology for reversing modern .NET AOT threats using Binary Ninja.

Although .NET AOT compilation is not a new capability, its adoption within the malware ecosystem has accelerated. Threat actors increasingly rely on AOT to evade IL‑focused analysis pipelines and to undermine the effectiveness of traditional .NET reversing tools. As a result, AOT has emerged as a low‑cost, high‑impact obfuscation layer—one that significantly delays analysis, reduces detection opportunities, and complicates automated tooling.

While AOT‑based malware is not yet the dominant trend, its use is steadily expanding among financially motivated threat groups. The technique is well‑positioned for broader adoption across modern loader architectures and evasion frameworks, underscoring the need for robust and repeatable analysis workflows, such as the one detailed in this guide.

Technical Analysis

The Howler Cell Team initiated the investigation with a suspicious URL, likely originating from a phishing campaign.

  • hxxps[://]files[.]catbox[.]moe/tv0ww9[.]zip

Figure 1: Overview of Attack Chain

 

 

Visiting the URL triggers an automatic download of a ZIP archive named tv0ww9.zip. Upon extraction, as seen in Figure 2, the archive contains six legitimate digitally signed Qt5 modules, along with a malicious executable named KeyAuth.exe.

Figure 2: Overview of contents within tv0ww9.zip

KeyAuth.exe

KeyAuth.exe is a 64-bit Trojan downloader compiled in C++. Upon execution, it uses URLDownloadToFileA API (see Figure 3 for snippet) to download a remote payload named bound_build.exe from a hardcoded URL, saves it to disk, and executes it.

Figure 3: KeyAuth.exe downloading next stage payload

 

As seen from Figure 4, we identified that the downloaded payload was programmed using .NET and compiled to native code using Ahead-of-Time (AOT) deployment. To reverse engineer this, we relied on Binary Ninja, a native disassembler/decompiler tool, instead of .NET debuggers.

Figure 4: AOT Deployment detection - Bound_build.exe

 

Once the binary is allowed to be processed by Binary Ninja, we were presented with a massive amount of unlabeled functions. The inbuilt signature matcher, including WARP, identified only 34 functions of the total 4588 functions (Figure 5). WARP is Binary Ninja’s GUID‑based signature system that automatically identifies known functions and restores their metadata during analysis.

Figure 5: WARP library detection - Bound_build.exe

 

To proceed with the malware analysis, we must identify the libraries and data types used to further understand the code.

.NET AOT (Ahead of Time) Setup - Preparing Malware for Analysis in Binary Ninja 

We drew inspiration from the excellent research conducted by the HarfangLab team and adopted similar techniques to enrich Binary Ninja with names of compiled library functions and data types. This blog examines a real-world malware sample compiled using AOT, and this section provides a step‑by‑step walkthrough that can serve as a practical guide for reversing similar AOT-compiled threats.

Step 1 - Compiling a Sample AOT Console Application

Begin by creating a new project using the basic Console App template available in Visual Studio, as shown in Figure 6, followed by giving our project a name. For convenience, we’ve named it SimpleAOTForWarp (Figure 7).

Figure 6: Selecting Console App from Visual Studio Templates

 

Figure 7: Naming our Sample Project - SimpleAOTForWarp

 

As shown earlier in Figure  4, the malware was compiled using .NET 8. Accordingly, we select .NET 8.0 as the target framework. Additionally, it is essential to enable the "Native AOT publish" option, as shown in Figure 8, to ensure the project is built as an AOT application.

Figure 8: Selecting AOT method during project setup

 

After confirming the framework version and clicking the Create button, Visual Studio generates a basic Hello World C# program. Replace the default code with this sample code that includes commonly used libraries, as shown in Figure 9.

Figure 9: Sample C# code used

 

After updating the template code, right‑click the project name and click Publish (see Figure 10 for steps). From the dialog that appears, select Folder (twice, even in the next pop-up) and designate a directory where the compiled EXE and its PDB should be placed.

Figure 10: Publishing Project

 

Once the Folder publishing option is confirmed, a configuration pane will be displayed as shown in Figure 11. In this pane, select Show All Settings and update the Target Runtime from the default Portable to Windows‑x64, as the malware is intended to run on Windows, and click Save.

Figure 11: AOT publish options

 

After confirming the publishing options, click Publish to initiate the build process and to generate the required EXE and PDB files, as depicted in Figure 12.

Figure 12: Generated AOT Application and its PDB

 

Step 2 - Generating WARP Signature File

After generating the EXE and PDB files, open the EXE in Binary Ninja. Since the PDB file has the same name and resides in the same directory as the executable, Binary Ninja will automatically import it. Wait a few minutes for Binary Ninja to finish analyzing the binary. Once Binary Ninja completes its analysis, it logs the message “[Analysis] Analysis update took <xxx> seconds.” As visualized in Figure 13, ensure this message appears in the log pane before continuing.

Figure 13: Binary Ninja Log - Confirmation

 

Once the confirmation step in Binary Ninja is complete, navigate to the WARP option in the plugin dropdown and choose From Current View, as illustrated in Figure 14, and click “OK” in the next pane to start the signature file generation.

Figure 14: Generating WARP signature

 

After generation, the WARP signature file (.warp) is automatically written to the selected directory. Binary Ninja then opens a new tab, confirming the report and listing the number of functions included in the signatures. In our example, this count is 24,872, as illustrated in Figure 15.

Figure 15: WARP Report and signature file

 

 

Step 3 - Loading WARP Within Malware

With the WARP signature generated for the appropriate .NET version (8.0 for our sample), we can resume our earlier examination of Bound_build.exe. As noted in Figure 5, Binary Ninja initially detected only 34 functions. Next, we will explore how to load the generated WARP file to incorporate the missing library functions. In the Plugins menu, navigate to WARP and select Load. Choose the previously generated .warp file and allow Binary Ninja a few minutes to apply the associated type-information.

Figure 16: WARP - Load File

 

For our sample Bound_build.exe, applying the WARP file created from the sample code for .NET version 8.0 resulted in the identification of 3,918 functions (see Figure 17), a dramatic improvement over the 34 functions detected during baseline analysis.

Initially, Binary Ninja covered less than 1% of the binary (34 out of 4,588 functions), leaving most of the code unresolved. After loading the WARP signature, coverage increased to over 85%, giving visibility into the majority of the function set.

In practice, WARP eliminated the need to manually inspect almost 4,000 library functions, significantly reducing analyst workload and allowing the focus to shift quickly toward understanding the sample’s actual malicious behaviour.

Figure 17: Applying WARP - Before & After

 

Step 4 - String Hydration - Dumping From Memory

Now that the signatures have been applied and both the library code and .NET AOT wrapper functions have been identified, we began noticing specific runtime behavior unique to AOT‑compiled binaries. During execution, these binaries dynamically populate a Portable Executable (PE) section named hydrated with strings, method virtual table pointers, and type information. This population happens through the RehydrateData method, which is called inside CreateTypeManagers, nested under the InitializeModules function within Main (refer to Figure 18 for snippet).

Figure 18: RehydrateData invocation to dynamically populate hydrated section

 

To extract this runtime‑generated data, we executed the malware inside a controlled analysis environment until execution reached the RehydrateData method, at which point we dumped the PE image directly from memory. During subsequent static analysis, we parsed the hydrated section to correctly resolve string references and associate them with their corresponding variables. The resulting output is shown in Figure 19.

Figure 19: Applying struct to Hydrated section

Once the AOT pre-setup is performed, the second stage payload is ready for further analysis.

Note: All .NET AOT‑compiled binaries discussed in this blog were prepared using the exact pre‑setup workflow outlined above. The subsequent sections will move straight into the malware analysis and behavioral observations, without repeating the setup procedure.

Bound_build.exe

Bound_build.exe acts as a malware loader, responsible for XOR-decrypting two embedded resources in memory and writing them to disk. It uses the Assembly.GetManifestResourceStream method to load the named resources into memory, which are then XOR decrypted using a 32-bit key mentioned below.

XOR Key - 26 DE 1A 47 A0 8D B5 2B 67 E4 4D 6F D0 AD 48 C7 0F D3 CE 5A 38 D9 42 F0 E0 E2 C7 8D B0 8B 4B C7

The decrypted resources are written to the %LOCALAPPDATA% directory with randomly generated filenames. Immediately after being dropped, they are executed via the CreateProcessA API (refer Figure 20 for snippet).

Figure 20: Loading Embedded Resource and XOR Decryption

Dropped Resources

Both resources dropped to disk closely mirror the initial stage and contain identical code to KeyAuth.exe. They are 64-bit Trojan downloaders designed to fetch and execute additional payloads from hardcoded URLs shown in Table-1.

Table 1: Overview of downloaded resources

Filename Downloaded from URL
Crypted_build.exe hxxp[://]176[.]46[.]152[.]62:5858/801b0b4d118a4c0a8c4523e7805f5227_crypted_build[.]exe
Miner.exe hxxp[://]176[.]46[.]152[.]62:5858/fc2e7a9930c9496c9df103b2bff5e372_miner[.]exe

Crypted_build.exe

 

Crypted_build.exe is C++ compiled and serves as a downloader that retrieves a remote Donut shellcode using URLDownloadToCacheFileW API, loads it into memory, and transfers execution to it using the CreateThread API (Figure 21).

Figure 21: Donut shellcode execution

 

The shellcode on execution is responsible for executing Rhadamanthys stealer, and its command and control (C2) server is 176[.]46[.]152[.]80. Since Rhadamanthys has already been extensively analyzed by the security community, we won’t dig into its internals in this post.

Miner.exe

Miner.exe is also C++ compiled and serves as a downloader that retrieves and executes a malicious payload named: MicrosoftEdgeUpdater

MicrosoftEdgeUpdater is .NET programmed and AOT deployed; It acts as a loader for XMRig and starts its execution by performing an environmental analysis to check for execution within sandbox or analyst-controlled machines. If it detects execution within these instances, it exits silently without exhibiting any malicious behavior.

Sandbox Checks

The loader calculates a scoring sum based on different information it collects from the system, and the sum is compared at the end to identify if the current execution is within a sandbox machine.

Physical Memory/RAM Check

As shown in Figure 22, the loader calls the GlobalMemoryStatusEx API to collect system memory details, focusing on total physical RAM.

The value is read in bytes and converted to gigabytes.

The code then applies a conditional check:

  • If total RAM is greater than 8 GB, the internal counter is increased by 2.
  • If total RAM is greater than 4 GB and up to 8 GB, the counter is increased by 1.

This logic suggests the malware adjusts its behavior based on the system’s available memory.

Figure 22: Physical Memory Check

 

System Uptime Check

The loader retrieves the system uptime using the GetTickCount64 API and approximates the value to minutes through bitwise manipulation. Based on the derived uptime, it adds to the scoring mechanism: if the system has been active for more than two hours, the scoring sum is incremented by 2; if the uptime exceeds one hour but is less than or equal to two hours, the scoring sum is incremented by 1 (Figure 23).

Figure 23: System Uptime Check

 

Count of User Files Check

The loader then retrieves the count of user files within user's Desktop and Documents folder. If the combined count of files is greater than 10, the scoring sum is increased by 2; if count is between 3 and 10 the scoring sum is increased by 1 (Figure 24).

Figure 24: No. of user files check

 

Anti-Virus (AV) Process Check

The loader performs a runtime check to determine if any processes from a predefined list (Table 2) are actively running on the system. If at least one matching process is detected, the scoring sum remains unchanged. However, if none of the listed processes are found, the score is incremented by 2.

Table 2: Antivirus process list

avp

mcshield

windefend

msmpeng

avgnt

avast

kaspersky

bitdefender

eset

Main Sandbox Check

The loader evaluates the accumulated scoring value against a predefined threshold of 5. If the score falls below this threshold, it clears a specific global data reference and terminates execution silently, avoiding further activity.

Virtual Machine Checks

The loader performs a runtime check for the presence of known monitoring processes (Table 3). However, the result of this check is unused, suggesting the functionality is either disabled in the current build or still under development.

Table 3: Monitoring process list

wireshark

processmonitor

filemon

windbg

fiddler

procmon

apimonitor

ollydbg

processhacker

regmon

detours

ida

ghidra

x64dbg

immunity

taskmgr

SystemProductName and ProcessorNameString Check

The loader queries specific registry keys and compares their values against a hardcoded list of known virtual machines and hypervisor identifiers (refer to Figure 25 for snippet).

  1. HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\BIOS
    • SystemProductName
  2. HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0
    • ProcessorNameString

If any of the retrieved registry values contain a match from this list the loader clears a specific global data reference and terminates execution.

Figure 25: Virtual Machine Check

 

Self-Copy and Persistence

The loader checks if it's running from C:\Users\<user>\AppData\Local\SystemMonitoring\ as DataSyncHost.exe. If not, it replicates itself to that location using the CopyFileExW API. It then establishes persistence by creating a Run registry entry named SystemDataSync_<Random_GUID_8bytes> (Figure 26).

Figure 26: Persistence using Run Registry

 

XOR Decryption and Dropping XMRig

As stated above, the loader proceeds only if the execution is from path C:\Users\<user>\AppData\Local\SystemMonitoring\DataSyncHost.exe. It continues further by dynamically loading an embedded .NET resource named xmrig_encrypted.exe in-memory using Assembly.GetManifestResourceStream Method (Figure 27).

Figure 27: Loading encrypted resource in memory

 

The loaded resource is then XOR decrypted in-memory using the XOR-key KeyForXmrigEncryption2025SystemM and the resultant PE file is dropped to disk as msedge_updater.exe under the directory C:\Users\<user>\AppData\Local\Microsoft\EdgeUpdate\Components\

Figure 28: XOR decryption

 

Interestingly, the loader also embeds a native vulnerable driver file encrypted with the same XOR password, but it doesn't seem to have been utilized.

Table 4: Overview of Embedded Resources

Filename

Decrypted Payload - Sha256

Xmrig.exe

f29d673b032f7ff763dec032aefd6c5759a1583b211625f7f770017bedf03689

WinRing0.sys

11bd2c9f9e2397c9a16e0990e4ed2cf0679498fe0fd418a3dfdac60b5c160ee5

 

Executing XMRig

After dropping the XMRig miner to disk, the loader initializes its execution parameters. These parameters are embedded within the binary as XOR-encrypted byte sequences and are decrypted at runtime using the same XOR key before being passed to the miner (Figure 29).

Figure 29: Miner Configuration Initialization

 

Table 5: XMRig command line options used

 

Parameter

Value

Description

-o

pool[.]supportxmr[.]com:443

URL of mining server

-u

49gq8ibvuN3LvMdbEU7iiyLzyhMMV9eU3KjUaDQBrhiRei8JCV
YqXE6Ku1Evm1fmyRK23SZhbscLzRbhjURSKHZbBsk5Rp2

Username for mining server

-p

new

Password for mining server

--cpu-priority

3

Process priority

--tls

N/A

Enable SSL/TLS support

-t

<Dynamically Generated>

Number of CPU threads

The loader launches the miner with the above-described parameters and updates the priority of the launched process to ABOVE_NORMAL_PRIORITY_CLASS(0x8000) using SetPriorityClass API.

While the miner is launched and running as a separate process, its loader - DataSyncHost.exe continuously checks the active processes list for CPU performance and monitoring tools listed below

Table 6: Monitored Executables

Taskmgr

processhacker

procexp

procexp64

resmon

perfmon

If it finds any of the above processes to be running, it terminates the XMRig miner immediately and enters into cooldown state for few minutes making its own execution as idle as possible; DataSyncHost respawns the miner after when the detected tool(s) are terminated.

Conclusion

As with many emerging malware techniques, .NET AOT compilation initially gained traction in game cheats and cracks before being adopted by malware authors. Much like Go binaries lacking traditional signatures, AOT-compiled samples pose significant challenges for static analysis. However, the approach outlined in this post enables analysts to craft custom signatures and effectively dissect such samples, improving detection and understanding of this evolving threat landscape.

Appendix

IOC's

Filename

Sha256

tv0ww9.zip

6762c5807021fb7871f051135248cfd8f2cb387495b4544817433
d71c160acaa

KeyAuth.exe

63e3d55e61542fb15706ad2f87551fecbb0c2b94e85e597a1f31
eca54cfe2c4f

Bound_build.exe

1133f41e2b463ce5024423dcad44bcc0ff9543c2d38eae5bddc
6016da9f7a64d

Crypted_build.exe

0cb27847077eae63a1641cf3efc9ba807612cdde1cdd4ebcb732
3f4697449114

Donut Shellcode

5ff26d13c49966d579f5cf97c8a20fb6e2aa327acadfa8662f3
d018032af2bb2

Rhadamanthys stealer

6bbb5d3086050c49741fb934dabecad210ece7991eef718c2
32767195bea1527

Miner.exe

edf1b62620e3a90eca34eb6660182f492a02912e652b19fec
4b8d4be1ffa0e0f

MicrosoftEdgeUpdater

556d8ed53e1015b4d40383198e5a787e022fa26dc132820fc
053e55ec28317b7

Xmrig.exe

f29d673b032f7ff763dec032aefd6c5759a1583b211625f7f77
0017bedf03689

WinRing0.sys

11bd2c9f9e2397c9a16e0990e4ed2cf0679498fe0fd418a3d
fdac60b5c160ee5

C2

pool[.]supportxmr[.]com

176[.]46[.]152[.]62

176[.]46[.]152[.]80

MITRE

Initial Access

    • T1204.001 – User Execution: Malicious Link
    • T1204.002 – User Execution: Malicious File

Execution

    • T1105 – Ingress Tool Transfer
    • T1106 – Native API
    • T1620 – Reflective Code Loading

Persistence

    • T1547.001 – Registry Run Keys / Startup Folder

Privilege Escalation

    • T1548 – Abuse Elevation Control Mechanisms

Defense Evasion

    • T1027 – Obfuscated/Encrypted Files or Information
    • T1027.002 – Software Packing
    • T1140 – Deobfuscate/Decode Files
    • T1497 – Virtualization/Sandbox Evasion
    • T1497.001 – System Checks
    • T1036 – Masquerading
    • T1112 – Modify Registry

Discovery

    • T1082 – System Information Discovery
    • T1518 – Software Discovery

Command and Control

    • T1071.001 – Web Protocols
    • T1095 – Non-Application Layer Protocol

Impact

    • T1496 – Resource Hijacking

References

1. https://harfanglab.io/insidethelab/reverse-engineering-ida-pro-aot-net/

2. https://github.com/TheWover/donut