Process executed from an anonymous in-memory file on Linux
AN0839 asks for mmap + mprotect turning writable memory executable inside one process with no ELF on disk behind it. The permission-change half is not reachable here: the analytic's only source for it is the raw `auditd:MMAP` feed, which the brief gives no field names for, and lib/sigma has no way to join two syscalls in one process anyway. What is reachable is the end of that chain that does create an event — an `execve` whose executable is an anonymous file rather than a path on a filesystem. A payload placed with `memfd_create` and executed with `fexecve` has no directory entry at all, so the kernel reports its path as `/memfd:<label> (deleted)`, and that `memfd:` marker is the whole selection. MITRE's `ProcessNameScope` knob is what this implements — the knob's own text says it is uncommon for service binaries to call `memfd_create` — though scoping it to a named set of high-risk processes is left to the deployer rather than baked in, because on a clean host the unscoped form is already rare. One exclusion is applied: `runc` defends against CVE-2019-5736 by copying its own binary into a memfd and re-executing it, labelling the clone `runc_cloned:/proc/self/exe`, so on any container host that self-copy would otherwise be nearly every match. The exclusion matches runc's full label rather than a fragment of it, but the label of a memfd is chosen by whoever creates it, so an adversary who names theirs the same string is excluded too — treat the filter as noise suppression, not as a boundary that holds under contact. MITRE's `RWXMemoryThreshold` knob (allow some RWX allocations before alerting, for JIT runtimes) is not applied at all: it is a count over a window, and Sigma models neither. Scope and limits. This is the fileless *execution* case only. Reflective loading into a process that is already running — `Assembly.Load()` in a live PowerShell session, shellcode written into a JIT'd region, a dlopen from a memory buffer — creates no new process and therefore no event this rule can see, and that is the larger half of the technique. The Windows arm AN0838 is not attempted: it describes a VirtualAlloc/VirtualProtect/CreateThread sequence inside a single PID, and none of its three sources record it — Sysmon EventID 1 is process creation, EventID 7 fires only for modules that *have* a backing file, and EventID 10 is cross-process access, which reflective loading by definition does not perform. The macOS arm AN0840 offers only `macos:unifiedlog`, which has no standardised Sigma field vocabulary, so its fields would have to be invented. Vocabulary and prerequisite: this rule is written in the Sysmon-for-Linux-shaped `process_creation` vocabulary (`Image`) that the brief maps `auditd:SYSCALL::execve` onto; raw auditd carries the same value as `exe=` on a SYSCALL record and needs that field mapping first, and it records no execve at all until a rule such as `-a always,exit -F arch=b64 -S execve -k exec` is loaded. Until one is, this rule returns zero rows, and zero rows here means blind rather than quiet. UNVERIFIED AS AUTHORED — derived from MITRE ATT&CK DET0300, not hand-written and not tested by its author. Any lab result is on this rule's own page.Full descriptionShow less
The detection
The 4 SIEM queries below are previews produced by Siemphony’s own translator, not pySigma, and none has been executed against a real backend — review before you deploy.
detection.yml
Sentinel · KQL
Run this as a search.
DeviceProcessEvents| where (FolderPath contains "memfd:" and not (FolderPath contains "runc_cloned:/proc/self/exe"))
Splunk · SPL
Run this as a search.
index=* (Image="*memfd:*" AND NOT (Image="*runc_cloned:/proc/self/exe*"))Elastic · ES|QL
Run this as a search.
FROM logs-*| WHERE (TO_LOWER(process.executable) LIKE "*memfd:*" AND NOT (TO_LOWER(process.executable) LIKE "*runc_cloned:/proc/self/exe*"))
Wazuh · XML rule
Deploy to your manager — this is a rule, not a search.
<group name="sigma,linux,process_creation,"> <!-- Rule ids must be unique on your manager; 100000+ is the user range. --> <rule id="100000" level="7"> <!-- Set <if_sid> to the decoder/base rule for linux so this only evaluates relevant events. --> <field name="Image" type="pcre2">(?i)memfd:</field> <field name="Image" negate="yes" type="pcre2">(?i)runc_cloned:/proc/self/exe</field> <description>Process executed from an anonymous in-memory file on Linux</description> <mitre> <id>T1620</id> </mitre> </rule></group>
Verdicts · reactions · comments
Community
Verdicts from engineers who actually deployed it, and the conversation around it.
Nobody has run this in a real environment and said what happened.
Verdicts from engineers who deployed it
0 castNo verdicts reported yet. The first one is worth more than the tenth — say what you ran it against.
Log in to report a verdict.
Log in to join the discussion.
1 thread
@tomas-eriksen
On our Kubernetes workers the runc exclusion is doing enormous work — without it you get roughly one match per container start, which is tens of thousands a day on a busy node. Worth flagging for anyone on a mixed runtime estate: the exclusion matches runc's own label, and Kata and gVisor clone differently, so they come straight back. The sentence about a memfd label being chosen by whoever creates it is the most important line in the rule.
@priya-raman
Confirmed on our range: name your memfd `runc_cloned:/proc/self/exe` and the stager walks past the filter untouched. It is noise suppression, exactly as the description labels it, and I wish more rules were this honest about which of their filters hold under contact. We keep an unfiltered copy scoped to hosts that are not container nodes, where the label has no legitimate reason to appear.