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.
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:
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:
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.
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.
The Howler Cell Team initiated the investigation with a suspicious URL, likely originating from a phishing campaign.
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 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.
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.
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
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
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
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 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
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 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 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.
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.
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:
This logic suggests the malware adjusts its behavior based on the system’s available memory.
Figure 22: Physical Memory 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
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
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 |
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.
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 |
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).
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
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
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 |
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 |
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.
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.
|
Filename |
Sha256 |
|
tv0ww9.zip |
6762c5807021fb7871f051135248cfd8f2cb387495b4544817433 |
|
KeyAuth.exe |
63e3d55e61542fb15706ad2f87551fecbb0c2b94e85e597a1f31 |
|
Bound_build.exe |
1133f41e2b463ce5024423dcad44bcc0ff9543c2d38eae5bddc |
|
Crypted_build.exe |
0cb27847077eae63a1641cf3efc9ba807612cdde1cdd4ebcb732 |
|
Donut Shellcode |
5ff26d13c49966d579f5cf97c8a20fb6e2aa327acadfa8662f3 |
|
Rhadamanthys stealer |
6bbb5d3086050c49741fb934dabecad210ece7991eef718c2 |
|
Miner.exe |
edf1b62620e3a90eca34eb6660182f492a02912e652b19fec |
|
MicrosoftEdgeUpdater |
556d8ed53e1015b4d40383198e5a787e022fa26dc132820fc |
|
Xmrig.exe |
f29d673b032f7ff763dec032aefd6c5759a1583b211625f7f77 |
|
WinRing0.sys |
11bd2c9f9e2397c9a16e0990e4ed2cf0679498fe0fd418a3d |
pool[.]supportxmr[.]com
176[.]46[.]152[.]62
176[.]46[.]152[.]80
Initial Access
Execution
Persistence
Privilege Escalation
Defense Evasion
Discovery
Command and Control
Impact
1. https://harfanglab.io/insidethelab/reverse-engineering-ida-pro-aot-net/
2. https://github.com/TheWover/donut