Screening Serpens Loads a .NET AppDomainManager Before Its RAT Runs, and Your Sysmon ImageLoad Rule Can Miss the Managed Assembly
The managed loader assembly that Screening Serpens plants beside a signed .NET binary doesn’t reliably show up as a Sysmon Event ID 7. It gets instantiated as the process’s AppDomainManager before the host application’s Main() runs, which means your ImageLoad-based hunt for unsigned modules in unusual paths can see nothing, because the CLR loads it as a managed MSIL assembly rather than a native image mapped through LoadLibrary, so it doesn’t land in the native module-load telemetry your ImageLoad rule watches. That is the whole point of the technique, and it is why Unit 42’s writeup on the group’s February-through-April 2026 activity matters to anyone running a .NET-heavy Windows estate: the Iran-nexus operator tracked as Screening Serpens combined AppDomainManager-related managed loading (MITRE T1574.014) with conventional DLL sideloading — and in the MiniUpdate chain the operator also used config directives to suppress CLR ETW, undermining both the native ImageLoad-oriented hunts and the CLR loader telemetry most shops lean on first. One clarification up front, because it drives the whole detection story: the AppDomainManager assembly is the loader — it runs first, disables telemetry, and then hands execution to the actual RAT, which is staged separately and loaded afterward. The RAT is not itself the assembly named as the AppDomainManager.
TL;DR
- Screening Serpens ran a managed loader assembly as the .NET AppDomainManager to execute before the host’s Main(). MiniUpdate leans on this heavily; the February MiniJunk V2 chain used an older Framework configuration to influence local managed-assembly resolution before transitioning into conventional DLL sideloading. The loader runs first, then transfers control to the RAT; the managed assembly isn’t reliably represented in Sysmon Event ID 7 ImageLoad telemetry, which can blind the unsigned-DLL-in-odd-paths hunts most shops rely on.
- The paired .exe.config names the loader as the AppDomainManager and carries directives that disable CLR ETW (etwEnable=”false”), skip strong-name checks, and direct the CLR’s managed-assembly probing to the local application path — suppressing the CLR runtime telemetry for that process before the RAT beacons; note this is a .NET Framework problem, not modern .NET Core/5+.
- The durable detection moves to Sysmon Event ID 11: alert on one process dropping an .exe, a same-basename .exe.config, and a .dll into a suspicious path. That staging shape is a campaign-specific artifact, not a CLR invariant — the broader technique can resolve the assembly from the GAC or via the APPDOMAIN_MANAGER_ASM env var with no .config on disk — but it’s a durable disk tell for these samples.
- The triad fires constantly under Program Files from normal installers, so the rule lives or dies on a tight path filter (temp, public, fonts, perflogs, etc.) plus suppression of your own SYSTEM-run deployment tooling.
- FileCreate can’t see .config contents, so confirming the malicious directives needs EDR/Velociraptor/YARA content inspection. CLR loader ETW (Microsoft-Windows-DotNETRuntime — ModuleLoad_V2 is event 152, AssemblyLoad_V1 is 154) adds behavioral visibility, but etwEnable=”false” can suppress that channel; the env-var variant opens a separate gap by removing the malicious .config entirely, not by defeating a disk-content scan. That’s why WDAC/App Control enforce mode is the strongest preventive control.
The samples appear to have targeted aerospace, defense manufacturing, telecom, and technology-sector professionals — Unit 42’s conclusion puts the affected entities in up to five countries: the US, Israel, the UAE, and two additional Middle Eastern countries — and it hedges much of the attribution on VirusTotal metadata (“may have targeted,” “likely”), with activity climbing after the regional conflict began in the Middle East on February 28, 2026. Unit 42 counts six RAT samples across two families: four MiniUpdate samples (deployed from March 26, with two more variants submitted April 15–17) and two MiniJunk V2 samples. One analyzed MiniJunk V2 payload was padded to around 12 MB with repeated junk strings, apparently to exceed the file-size ceiling on a lot of sandbox pipelines. MiniUpdate is the family that leans hardest on explicit AppDomainManager designation and ETW suppression; the February MiniJunk V2 chain used an older Framework configuration to steer local managed-assembly resolution before transitioning into conventional DLL sideloading, so the two are related but not identical in how the final RAT arrives.
What the hijack actually does
.NET Framework lets you designate a managed class as the AppDomainManager for a process. When the CLR spins up the default AppDomain, it instantiates that class first and hands it control. Legitimately this is a hosting hook. Abused, it is arbitrary code execution that runs before the application you thought you launched does anything. Worth flagging the scope: this is a .NET Framework mechanism, and modern .NET (Core, 5+) doesn’t honor an AppDomainManager assembly named in app config the same way — so this is a Framework-estate problem.
The operator stages at least the three artifacts the detection keys on: a renamed-legitimate signed .exe, its matching .exe.config, and a loader DLL. MiniUpdate’s second stage actually drops four — Screening Serpens shipped update.exe (renamed setup.exe), update.exe.config, Updater.dll, and UpdateChecker.dll — where Updater.dll is the assembly the config names as the AppDomainManager and UpdateChecker.dll is the RAT staged alongside it. Note what happens next, because it’s the distinction that matters: Updater.dll doesn’t contain the RAT. It validates the environment, then loads a separate module, UpdateChecker.dll, into the process and calls its CheckForUpdates export — and that is the MiniUpdate RAT. So the AppDomainManager assembly is a loader; the RAT is a separately staged DLL — copied to disk with the other files, then loaded into the process and invoked, not delivered fileless. (MiniJunk V2’s February chain is a different shape: a config with <probing privatePath="./"/> loads uevmonitor.dll, which drops a renamed Microsoft binary SoftwareLicencing.exe and unbcl.dll, and a scheduled task runs the signed binary to sideload unbcl.dll — closer to classic DLL sideloading than to the AppDomainManager path.) The .config is where the work happens. It names the loader as the AppDomainManager assembly, and it carries a set of runtime directives that read like a checklist of things you’d want disabled if you were the one being hunted:
<etwEnable enabled="false"/>— disables the CLR’s own ETW emission (theMicrosoft-Windows-DotNETRuntimeprovider) for that process, a channel a good chunk of EDR .NET visibility rides on<bypassTrustedAppStrongNames enabled="true"/>— suppresses strong-name signature verification for applicable full-trust assemblies. It weakens an integrity check, but strong names are an assembly-identity mechanism, not an application-control boundary — Microsoft explicitly warns not to rely on them for security — so read this as removing one consistency check, not as a general “run any unsigned DLL” switch<publisherPolicy apply="no"/>— prevents publisher-policy assembly-version redirects from changing which assembly binds, keeping the attacker’s expected local dependency resolution stable. It’s a binding/version-management control, not a security-patch channel<probing privatePath="./"/>— directs the CLR’s managed-assembly probing to the local application directory (this is CLR assembly binding, not the nativeLoadLibrarysearch order), so private-assembly resolution lands on the attacker’s staged DLL
The ETW-disable line is the one to sit with. The technique loads a managed assembly into a process in a way that, as MITRE’s technique documentation and the broader research describe, does not reliably surface as a Sysmon ImageLoad (Event ID 7) event — a managed assembly loaded by the CLR isn’t a native module in the first place — and then it turns off the ETW provider that would have told you about the managed load anyway. You are down two sensors before the RAT has sent a single beacon.
Why the native-module hunt misses it
If your .NET malware detection is “alert on unsigned or Microsoft-masquerading DLLs loading from \Temp\, \Users\Public\, or \Windows\Fonts\,” it is built on Sysmon Event ID 7. That is a perfectly good rule for classic sideloading where the host binary calls LoadLibrary on a planted DLL after it starts. The AppDomainManager path defeats it because the assembly is resolved by the CLR host during initialization, and the managed module isn’t reliably represented in the same ImageLoad telemetry as a native DLL mapped through LoadLibrary.
So the signal moves earlier in the kill chain, to the moment the files land on disk.
The detection that survives: the file-create triad
The pivot is Sysmon Event ID 11 (FileCreate). Splunk ships a prebuilt rule for exactly this, Windows Potential AppDomainManager Hijack Artifacts Creation, and the logic is worth understanding rather than just enabling. It watches for one process creating an .exe, a same-basename .exe.config, and at least one .dll — file_count >= 3 with all three types present and exe_base_names = config_base_names. Sourcetype XmlWinEventLog:Microsoft-Windows-Sysmon/Operational, accelerated through the Endpoint.Filesystem datamodel if you have CIM in place. One implementation detail worth knowing before you trust it: the search groups by dest process_guid — a single creating process on a host — and it collects file_path but never actually tests it, so despite the “same directory” framing in the write-up, co-location isn’t enforced. A process that stages the .exe, .config, and .dll across more than one of the selected suspicious paths still trips it, and if you genuinely want same-directory correlation you have to add a normalized parent-directory field yourself. It’s an Anomaly-type detection, disabled by default, contributing a low risk score (20) rather than firing a standalone notable.
For the Screening Serpens samples, that triad is a durable artifact. You can obfuscate the DLL to 12 MB, rotate the C2, rename the host binary every campaign, and the staged shape on disk — a signed .exe, its same-name .exe.config, and the loader DLL beside it — stays recognizable. Be precise about why, though, because the broader technique is more flexible than “the CLR requires this triad.” It doesn’t. The AppDomainManager assembly only has to be fully trusted and resolvable from either the application’s base directory or the GAC, its assembly name doesn’t have to match the host .exe, and the whole thing can be driven by the APPDOMAIN_MANAGER_ASM/APPDOMAIN_MANAGER_TYPE environment variables with no .config on disk at all. So the local .exe + .exe.config + .dll shape is a campaign-specific deployment pattern that these operators happened to use — a good, durable disk tell for this actor — not an invariant you can lean on against every AppDomainManager variant.
Volume is the catch, and it is a real one. In a fleet with any meaningful .NET Framework footprint, that triad appears constantly under C:\Program Files\ and C:\Program Files (x86)\ every time an app installs or self-updates — MSI actions, ClickOnce deployments, Squirrel updaters, and roughly every enterprise .NET app that ships app.exe, app.exe.config, and its dependency DLLs into the same folder. Run the raw triad query fleet-wide and you will drown. The whole detection lives or dies on the path filter.
Splunk’s rule already constrains to suspicious write locations: *\temp\*, *\users\public\*, *\windows\fonts\*, *\windows\debug\*, *\perflogs\*, *\windows\servicing\*, Recycle.bin, and a few others. Scoped that way, the baseline drops from a firehose to something tunable — but the residual volume depends heavily on your software-distribution behavior, temporary extraction paths, developer tooling, and how the Endpoint.Filesystem model maps individual product telemetry, so measure it locally before you assign alert severity. Those remaining hits are your tuning job for week one.
Where the false positives come from
Likely survivors after path-filtering are installers and updaters that stage into %TEMP% before moving files to their final home — Chromium-based app updaters, some Adobe components, in-house tooling a developer wired up to extract a self-contained bundle into temp and run it. Expect environment-dependent residual volume, much of it software distribution running as SYSTEM. Carve those out by the parent process (SCCM/ccmexec, your patch tooling, the update service accounts) and by known-good extraction directories, and you get to a place where a hit is worth a look.
The process_guid grouping is what buys the rule its fidelity, and it’s also where its recall leaks. Because everything hangs off a single creating process, the rule misses the cases where one process writes the .exe, another drops the .config, and a third extracts the .dll; where the .exe and .dll already exist and only the .config is newly created (the up-front filter requires action="created"); and where the final placement happens through a rename rather than a create. Treat the Splunk analytic as a high-fidelity, lower-recall tripwire, and complement it with a periodic directory-state hunt: sweep for same-basename .exe/.exe.config pairs whose configuration contains AppDomainManager directives, regardless of which process created each file.
The false positives you cannot resolve from Event ID 11 alone are the ones where a legitimate app genuinely uses an AppDomainManager. They exist. This is where you have to go read the .config.
The high-fidelity signal Sysmon can’t give you
Here is the honest gap: Sysmon Event ID 11 tells you a .config was created, not what’s in it. A generic .exe/.exe.config/.dll triad is common; the content is what separates a hijack from an installer. Treat appDomainManagerAssembly and appDomainManagerType as a required pair — Microsoft documents that if either element is missing, the other is ignored, so both have to be present for a config-based designation — and elevate severity when that pair appears alongside unusual or campaign-relevant settings: etwEnable="false", a <probing privatePath> steering resolution to the local directory, the specific <requiredRuntime> combination observed in these samples, an assembly name that resolves to a newly staged DLL, or the config landing next to a renamed signed Framework binary. Weight them differently — etwEnable="false" alongside the AppDomainManager designation is a far stronger signal than a legacy runtime-selection directive on its own, which can appear in legitimate Framework configs. Those live in the file content, and FileCreate doesn’t carry content. To alert on them you need something that reads the file: an EDR with file-content collection, a Velociraptor hunt that greps .config files in suspect paths, or a scheduled YARA sweep. Splunk’s own guidance says the quiet part out loud, telling analysts to “review the associated .config file to confirm which DLLs are expected to load.” That review is manual unless you build the content collection yourself.
The other high-fidelity option is the .NET runtime ETW provider, Microsoft-Windows-DotNETRuntime, and its CLR loader events. Get the IDs right, because they’re easy to transpose: AssemblyLoad_V1 is event ID 154, ModuleLoad_V2 is 152, and DomainModuleLoad_V1 is 151. ID 152 is the one worth watching — its ModuleILPath field gives you the managed IL module path on disk. What none of these events carries is a field that says “this assembly is the configured AppDomainManager”; there’s no IsAppDomainManager=true in the payload. So you don’t get to “flag any assembly loaded as an AppDomainManager” straight from the event — you use the loader events to surface unexpected managed modules loading during process initialization, then correlate the module path and assembly name against the executable’s .config (or the APPDOMAIN_MANAGER_ASM env var) to establish that it was actually the designated AppDomainManager. It is a cleaner signal than file-create because it fires on the actual load. It is also expensive: turning on managed-load ETW fleet-wide is a volume and cost problem, the events are chatty on developer workstations, and the attacker’s etwEnable="false" directive is aimed squarely at this channel. When the process starts with etwEnable="false" in its runtime configuration, those CLR loader events may never be emitted in the first place — this is a config-time disable during CLR initialization, not a race the running payload has to win. Which loops back to the file-create rule being the backstop precisely because it fires before the process — and its ETW-kill directive — ever runs.
Don’t forget the environment-variable variant. AppDomainManager can also be set through APPDOMAIN_MANAGER_ASM and APPDOMAIN_MANAGER_TYPE in a process environment block, no .config on disk at all. Sysmon Event ID 1 doesn’t capture the environment block by default, so this path is invisible to a stock config. If your EDR records process environment variables, hunt for those two names; if it doesn’t, accept that you have a coverage hole here and weight your other controls accordingly.
Control mapping
The behavioral detection work is SI-4 (system monitoring), and the content inspection and integrity-validation work maps to SI-7 — where the strong-name-bypass directive illustrates why CLR identity checks shouldn’t substitute for enforceable code-integrity policy, rather than SI-7 depending on strong-name validation at all. The most effective preventive controls are configuration-side. CM-5 (access restrictions for change) and CM-7 (least functionality) cover the two moves that actually stop this: write-protecting the directories that .NET searches so an attacker can’t stage the triad next to a signed binary, and application control that refuses to run the payload in the first place. WDAC — App Control for Business — in enforce mode, with user-mode code integrity on and a policy that doesn’t broadly trust the payload’s signer or its staging path, can stop unauthorized managed or native code from loading even if it lands on disk. That signer/path caveat matters: a policy that trusts the wrong publisher or a writable directory won’t help, and this actor stages beside renamed legitimately signed binaries. AppLocker can also enforce DLL rules (its DLL collection is off by default and expensive to maintain), but Microsoft classifies AppLocker as a defense-in-depth feature rather than a security boundary and recommends App Control where you need an enforceable one — so App Control is the stronger preventive control for this path, not because AppLocker’s DLL rules skip managed assemblies (they don’t), but because App Control is designed and serviced as a security feature and applies system-wide. CA-7 carries the continuous-monitoring posture, and the C2 side — those rotating Azure-hosted domains — is SC-7 boundary protection against your egress logs.
What to do this week
Confirm your .NET malware detections aren’t resting entirely on Sysmon Event ID 7, because against this technique an ImageLoad-only rule can miss the managed stage entirely. Turn on the Event ID 11 triad rule with the path filter tight from day one, and budget the first week for suppressing your own software-deployment tooling rather than chasing the malware. Then decide whether you can afford Microsoft-Windows-DotNETRuntime ETW on the crown-jewel hosts — the aerospace and defense targets in this campaign are exactly the segment where that cost is justified — and stand up a .config content sweep for the directives that Sysmon can’t see. The staged config beside a signed binary is the durable disk artifact here; the technique doesn’t force that exact shape, but these analyzed campaigns repeatedly left it behind. Watch the disk, and read the .config.
Sources
- Tracking Iranian APT Screening Serpens’ 2026 espionage campaigns (Unit 42, Palo Alto Networks)
- Hijack Execution Flow: AppDomainManager, Sub-technique T1574.014 (MITRE ATT&CK)
- Windows Potential AppDomainManager Hijack Artifacts Creation (Splunk Security Content)
- Loader ETW Events — CLR AssemblyLoad/ModuleLoad/DomainModuleLoad event IDs (Microsoft Learn)
- <appDomainManagerAssembly> Element — requires both assembly and type, .NET Framework 4+ (Microsoft Learn)
- AppDomainManager Class — config or APPDOMAIN_MANAGER_ASM/TYPE, GAC or app directory (Microsoft Learn)
- App Control for Business and AppLocker overview — AppLocker is defense-in-depth, not a security boundary (Microsoft Learn)
- AppDomainManager Injection and Detection (Pentest Laboratories)
This post was engineered and validated through a multi-agent AI workflow — drafted, adversarially reviewed by several independent models, checked against primary sources, and given a human review before publishing. See an inaccuracy, or found this useful? Leave a comment below — corrections and feedback are read and shape what comes next.