This article is the second part of a serie on memory fluctuation. The first one focused on the Windows platform, where the technique originally emerged while this part extends the research to Linux. It is covering the development of a proof-of-concept loader, detection heuristics, and hardening recommendations, it is written from a defensive (Blue Team) perspective and is intended for SOC analysts, DFIR practitioners, threat hunters, reversers, and EDR developers.
The research presented here was originally unveiled at CoRIIN 2026 in Lille, France. All proof-of-concept code and detection scripts are available on our GitHub repository.
Introduction
Memory Fluctuation in a Nutshell
Memory fluctuation is an in-memory evasion technique inspired by Gargoyle, a proof-of-concept released in 2017 by Josh Lospinoso. The original Gargoyle was a 32-bit Windows proof of concept that used APC timers and ROP chains to invoke VirtualProtect, alternating a payload’s memory page between non-executable and executable states. The core idea was refined by ShellcodeFluctuation (mgeeky, 2021), which introduced the concept of hooking sleep to encrypt the payload before sleeping and decrypt it after waking-up, a technique also known as “obfuscate-and-sleep”. Further variants such as Ekko, FOLIAGE, and DeepSleep explored different trigger mechanisms but kept the same fundamental principle: repeatedly call the memory protection API to alternate between executable and non-executable page states, optionally encrypting the payload content while dormant.
The concept is straightforward: an implant spending 99% of its time sleeping between C2 check-ins has a small window where it needs executable memory to run. During that window, the payload page is set to RX (Read-Execute). Immediately after, the page is flipped to RW (Read-Write) and the payload is encrypted. This dual protection achieves two things:
- Encryption defeats static scanners (e.g., YARA rules applied in memory). The encrypted, randomized content does not match known signatures.
- Residing in a RW anonymous (unbacked) page defeats anomaly-based scanners (e.g., PE-sieve, Moneta on Windows, or custom scanners on Linux). A private RW page is fairly common; it could be a heap allocation, a stack buffer, or any number of legitimate data structures. Unlike a private RX page (which is a strong indicator of injected code), RW pages do not raise suspicion.
On Windows, the function enabling this protection change is VirtualProtect / VirtualProtectEx, which internally invokes the NtProtectVirtualMemory NTDLL.dll syscall wrapper. On Linux, the equivalent is mprotect. On macOS, it is mach_vm_protect. Regardless of the platform, the protection-change syscall is the strong indicator for detecting fluctuation.
Why Linux?
While most of the published research on memory fluctuation targets Windows, where in-memory threats are most prevalent and EDR memory scanning is most mature, Linux is increasingly relevant: Linux servers form the backbone of cloud infrastructure, container deployments and enterprise data centers. Security solutions for Linux (e.g., Elastic Defend, Sysmon for Linux, Falco, Rustinel) are maturing, and with them, the defensive capabilities that memory fluctuation is designed to evade.
Neither Gargoyle nor ShellcodeFluctuation were designed for Linux. However, the technique is fully translatable. Notably, Kyle Avery (known for his DEF CON 30 talk on evading memory scanners on Windows) released Pendulum, a Linux sleep obfuscation tool inspired by Ekko. Pendulum uses POSIX timers (timer_create with SIGEV_THREAD) and ucontext_t context chaining to perform: mprotect(RW) → RC4 encrypt → sleep → RC4 decrypt → mprotect(RX). This confirms that the attack community is already exploring sleep obfuscation and fluctuation on Linux.
This article demonstrates that memory fluctuation is feasible on Linux, shows how a defender can detect it, and provides practical hardening recommendations.
Process Scanning on Linux
Before discussing evasion, it is worth establishing whether process memory scanning is even possible on Linux. The answer is yes, there are three primary methods:
ptrace
The ptrace system call allows a process to observe and control the execution of another process. ptrace(PTRACE_PEEKDATA, pid, addr, NULL) reads a word at a specific address in the target process’s memory. This method requires that the caller has PTRACE_MODE_ATTACH_REALCREDS permission, either being the same user as the target or having CAP_SYS_PTRACE.
process_vm_readv / process_vm_writev
Since Linux 3.2, the process_vm_readv() and process_vm_writev() syscalls allow direct memory transfer between two process address spaces. These require the same permission checks as PTRACE_ATTACH (PTRACE_MODE_ATTACH_REALCREDS): either matching UID/GID or CAP_SYS_PTRACE.
procfs: /proc/<pid>/maps and /proc/<pid>/mem
The procfs pseudo-filesystem provides two key files:
- /proc/<pid>/maps: A text listing of all mapped memory regions with their permissions, offset, device, inode, and optional pathname. Reading it requires PTRACE_MODE_READ_FSCREDS permission (same user or CAP_SYS_PTRACE / CAP_PERFMON).
- /proc/<pid>/mem: A pseudo-file giving raw access to the process’s entire virtual address space. Reading it requires PTRACE_MODE_ATTACH_FSCREDS, the same elevated permission as performing a ptrace attach.
memscan: A Minimal Linux Memory Scanner
To demonstrate detection, a small scanner (memscan) was developed. It is located in the memscan directory of the repository.
The scanner parses /proc/<pid>/maps and displays all memory regions that are both private (the p flag) and executable (the x flag). This targets the same heuristic as tools like Moneta and PE-sieve uses on Windows: private executable memory without a backing file is a strong indicator of injected code. The output format follows the /proc/<pid>/maps layout:

A blank pathname (no file backing the mapping) combined with r-xp permissions and an inode of 0 is the Linux equivalent of a Windows private commit RX region, a strong indicator of shellcode injection.

YARA Scanning Against Process Memory
In addition to anomaly-based scanning (looking at metadata), pattern-based scanning can be performed by running YARA against /proc/<pid>/mem. YARA can read a process’s memory directly via the PID as a target, applying rules to the raw memory content. When the payload is in its decrypted RX state, YARA will match known signatures.
Developing the Linux Loader: ELFluctuateLdr
To understand what defenders are up against, it is necessary to examine how a fluctuation loader works on Linux. The proof-of-concept loader, ELFluctuateLdr, is available in the ELFluctuateLdr directory of the repository.
The Challenge: No Dynamic Linker
On Windows, hooking Sleep is straightforward: the function resides in ntdll.dll within the process’s own address space, so the attacker writes a trampoline to redirect execution. On Linux, a similar approach would use LD_PRELOAD to hook sleep(), but this requires a dynamically-linked binary.
The ELFluctuateLdr loader uses Shelf (distributed as the py_shelf PyPI package), a tool that converts an ELF binary into position-independent shellcode. To work with Shelf, the payload is compiled as a static, position-independent binary:
Because the resulting shellcode is statically linked, there is no dynamic linker (ld-linux.so) involved at runtime. This means LD_PRELOAD is completely ignored, there is no symbol resolution for the dynamic linker to interpose on. Additionally, instructions using inline assembly or the syscall instruction to make syscalls directly bypass any libc function hook.
This creates a challenge: how to intercept sleep() when it cannot be hooked via LD_PRELOAD?
The Solution: fork() + PTRACE_TRACEME
The solution leverages the ptrace system call to intercept syscalls in the child process. The approach is as follows:
The parent process calls fork().
The child will execute the shellcode; the parent will act as the tracer.
The child allocates executable memory
via mmap(NULL, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) and copies the shellcode there. It then writes its shellcode address and size to a temporary file and calls ptrace(PTRACE_TRACEME, 0, NULL, NULL) to indicate it wishes to be traced by its parent, followed by raise(SIGSTOP) to stop itself, giving the parent a chance to set up tracing. Finally, the child jumps to the shellcode.
The parent reads the shellcode address
from the temporary file, then calls waitpid() on the child. It sets PTRACE_O_TRACESYSGOOD (which sets bit 7 of the signal number, delivering SIGTRAP | 0x80 on syscall traps to distinguish them from regular traps) and begins a PTRACE_SYSCALL loop to intercept each syscall entry and exit.
The parent intercepts nanosleep and clock_nanosleep syscalls.
The glibc sleep() function internally uses nanosleep(), which modern glibc implements in terms of clock_nanosleep() with CLOCK_REALTIME. When the parent detects either syscall at entry, it performs the fluctuation routine.
Syscall Injection via the vDSO
To call mprotect on the child’s shellcode page, the parent needs to execute a syscall within the child’s context. This is done by injecting a syscall instruction into the child’s memory.
The injection target is the vDSO (virtual Dynamic Shared Object), a small ELF shared library (linux-vdso.so.1) that the kernel maps into every process at execve() time. The vDSO is visible in /proc/<pid>/maps as [vdso] with r-xp permissions. Its purpose is to accelerate time-critical syscalls (like gettimeofday) by implementing them entirely in userspace.
For the loader, the vDSO is useful because:
- It is mapped as r-xp (read+execute) into every process, no mmap or mprotect is needed to make it executable.
- It is already present at a known, discoverable address (found by parsing /proc/<pid>/maps for the [vdso] entry).
The inject_syscall() function saves the original code at the injection address, writes a syscall opcode (0x0f, 0x05) followed by padding, sets the child’s registers (RIP to the injection address, RAX to the syscall number, RDI/RSI/RDX/R10/R8/R9 to arguments), and calls PTRACE_CONT to execute. After the syscall completes (child traps with SIGTRAP), the original code is restored and the saved registers are written back.
The Fluctuation Routine
When the parent intercepts a nanosleep or clock_nanosleep syscall at entry, it calls do_fluctuation(). The routine performs the following steps:
Protection (RW + Encrypt):
Inject mprotect(shellcode_addr, size, PROT_READ | PROT_WRITE) into the child via ptrace.
Read the child’s shellcode memory into a local buffer using process_vm_readv().
XOR-encrypt the local buffer with a hardcoded key (0x42 in the PoC; a random key would be used in production).
Write the encrypted buffer back into the child’s memory using process_vm_writev().
Sleep:
The parent calls sleep() for the duration specified in the intercepted nanosleep argument (read from the child’s timespec struct via process_vm_readv).
Unprotect (Decrypt + RX):
Inject mprotect(shellcode_addr, size, PROT_READ | PROT_WRITE) to make the page writable again.
Read the encrypted shellcode, XOR-decrypt it, and write it back.
Inject mprotect(shellcode_addr, size, PROT_READ | PROT_EXEC) to restore executable permissions.
Silencing the intercepted syscall:
The parent sets regs.orig_rax = -1 and regs.rax = 0 to nullify the intercepted nanosleep/clock_nanosleep call, the child never actually executes the sleep syscall, since the parent has already performed the equivalent sleep duration.
The lifecycle of the loader is:

Or more precisely for the Linux implementation:

The Payload
The shellcode payload (shellcode.c) is a minimal C program compiled for Shelf compatibility:
The stub functions (_start, _init, _fini) and empty symbols (_DYNAMIC, _edata, _end) are required for Shelf to parse the ELF correctly. The payload simply calls sleep(2) in a loop, writing a message to stdout each iteration.

Fluctuation in Action
When the loader runs with the --fluctuate flag, the shellcode executes in its child process. Each time sleep(2) is called:
- The parent intercepts the nanosleep/clock_nanosleep syscall.
- The shellcode page is flipped to RW and XOR-encrypted.
- The parent sleeps for 2 seconds.
- The shellcode is decrypted and the page is flipped back to RX.
Without fluctuation, running memscan against the child process shows a private executable (r-xp) anonymous page, a clear indicator. Additionally, YARA rules applied to the process memory match the payload signature.
With fluctuation enabled, if memscan or YARA is run during the sleep window (which is 99% of the time), there is:
- No private executable page : the shellcode resides in an anonymous rw-p page.
- No YARA match : the content is encrypted and does not match any signature.

Let’s Hunt for Indicators
Now that a working fluctuation loader exists, the question for defenders is: how do we detect it?
The strong indicator remains the same across platforms: the memory protection change function. On Linux, this is mprotect. All fluctuation techniques, whether inspired by Gargoyle, ShellcodeFluctuation, Ekko, or Pendulum; ultimately rely on mprotect to toggle page permissions between RW and RX.
The detection heuristic is: count the number of times a given memory page transitions between executable and non-executable states via mprotect. If a page flips back and forth more than a defined threshold (e.g., 5 times), classify it as fluctuating.
Userland Detection: LD_PRELOAD
The first approach, mirroring the Windows NTDLL hook strategy, is userland hooking. On Linux, this is done via LD_PRELOAD, an environment variable that tells the dynamic linker to load a specified shared library before all others, allowing it to interpose on libc functions.
A shared library could hook mprotect, track per-page protection changes, and count fluctuations. However, this approach has significant limitations:
- Static binaries: A statically-linked binary does not invoke ld-linux.so, so LD_PRELOAD is completely ignored. ELFluctuateLdr’s shellcode payload is compiled as static, making this approach ineffective against it.
- Direct syscalls: Syscalls invoked via inline assembly (the syscall instruction) bypass any libc function hook. An attacker can call mprotect directly without going through libc.
- Patchable: Since the hook resides in the same address space as the malware, it can be detected and patched.
A system-wide equivalent, /etc/ld.so.preload, has the same limitations and additionally requires root privileges.
Verdict: Userland detection via LD_PRELOAD is useful for live analysis or reverse engineering but is not a reliable defense against determined attackers.
Kernel Telemetry: auditd + Laurel
auditd
The Linux audit subsystem (auditd) can log any syscall, including mprotect. To log mprotect calls, rules must be added to the audit configuration:
Add rules for x86_64 and i386 architectures
auditd writes events to /var/log/audit/audit.log in a key=value textformat. A typical mprotect eventlooks like:
The a2 field contains the prot argument: 5 = PROT_READ|PROT_EXEC (RX), 3 = PROT_READ|PROT_WRITE (RW), 7 = PROT_READ|PROT_WRITE|PROT_EXEC (RWX).
Laurel
The raw auditd format is difficult to work with programmatically, events are split across multiple lines, strings are hex-encoded, and the format is not standard JSON. Laurel (“Linux Audit – Usable, Robust, Easy Logging”) is an auditd plugin that transforms the raw log into structured JSON. It decodes hex-encoded strings back to plain text, enriches events with parent process information, and outputs JSON Lines to /var/log/laurel/audit.log.
Detection Script: auditd_mprotect_fluct.py
The repository includes a Python script that parses Laurel’s JSON output to detect fluctuation. The script:
1. Reads /var/log/laurel/audit.log line by line.
2. Filters for mprotect syscalls where success == "yes".
3. Extracts the pid, address (argv[0]), length(argv[1]), and protection (argv[2]).
4. Maintains a per-page statedictionary (page_states) tracking whether each (pid, address) pair is currently executable.
5. On each mprotect call, checks if the executable state changed from the previous state.
6. Increments a per-page fluctuation counter. When the counter exceeds a threshold of 5, it prints an alert:

False positives observed during testing: Neovim triggered the fluctuation heuristic. This is likely because Neovim embeds Lua JIT, a just-in-time compiler that performs legitimate mprotect(RW→RX) cycles when generating code, behavior that is functionally similarto malicious fluctuation. The threshold of 5 helps filter one-time legitimate transitions, but JIT engines that repeatedly reuse pages will still trigger.
Kernel Telemetry: eBPF / bpftrace
eBPF (extended Berkeley Packet Filter)is an in-kernel virtual machine that allows safe, sandboxed programs to run within the Linux kernel. It is the Linux equivalent of a powerful kernel tracing mechanism, fully supported since Linux kernel 5.x.
it offers several tracing mechanisms. The most common include:
- kprobes / kretprobes: Dynamically hook any kernel function (entry and return).
- tracepoints: Predefined hook points in the kernel, considered stable across versions.
- uprobes: Similar to kprobes but hook user-space functions.
For mprotect monitoring, the relevant tracepoints are:
- tracepoint:syscalls:sys_enter_mprotect : fires on syscall entry, exposing args->start, args->len, args->prot.
- tracepoint:syscalls:sys_exit_mprotect : fires on syscall return, exposing args->ret.
Detection Script: mprotect_fluct.bt
The repository includes a bpftrace script that implements the same fluctuation-counting heuristic at the kernel level:

To run it:
sudo bpftrace mprotect_fluct.bt

Note: bpftrace does not log mprotect by default, this is a custom tracepoint script, not a built-in feature. The script must be deployed manually during investigation.
False positives observed during testing: GNOME Shell triggered the heuristic. GNOME Shell uses GJS (GNOMEJavaScript), which embeds SpiderMonkey, Mozilla’s JIT JavaScript engine.SpiderMonkey implements W^X JIT, flipping pages between RW and RX via mprotect during code generation, producing fluctuation-like patterns.
Zircolite: Sigma-Based Detection for Linux Logs
Zircolite is a standalone Sigma-based detection tool comparable to Hayabusa and Chainsaw for Windows that can apply Sigma rules to auditd logs and Sysmon for Linux logs without requiring a SIEM. It uses SQLite as its backend for rule execution.
The repository includes custom Sigma rules for mprotect-based detection:
To use :

A Note about Sysmonfor Linux
Sysmon for Linux (by Microsoft) uses eBPF to capture events and log them in a format compatible with the Windows Sysmon schema. It can capture process creation (Event ID 1), network connections (Event ID 3), and process access (Event ID 10, relevant for ptrace-based injection detection). While it does not natively capture mprotect calls, it can be combined with auditd for comprehensive telemetry and used with Zircolite for Sigma-based detection.
Kunai: Ane BPF-Native Alternative
Kunai is an open-source threat-hunting tool for Linux built on eBPF and written in Rust. It ships as a self-contained binary, embedding both the eBPF probes and the userland processing logic. Kunai guarantees chronologically ordered event delivery and provides built-in on-host correlation and enrichment, including full container-awareness for tracing activity across namespaces.
For memory fluctuation detection, Kunai exposes a dedicated mprotect_exec event that fires specifically when mprotect turns a memory region executable. The event carries structured fields : addr, prot, exe.path, ancestors, making it directly consumable for the RW→RX transition heuristics described earlier in this article. Kunai also emits a ptrace event (triggered on PTRACE_MODE_ATTACH) with process lineage and target process metadata, which can complement detection of the fork + PTRACE_TRACEME approach used by the PoC loader. While Sysmon for Linux does not natively capture mprotect calls and requires pairing with auditd, Kunai covers this telemetry gap out of the box
Understanding False Positives
The primary source of false positives for mprotect-based fluctuation detection is JIT (Just-In-Time) compilation. JIT engines share the same fundamental behavior as shellcode injection: they allocate writable memory, write generated code, flip it to executable via mprotect, execute it, and often reuse the same pages, creating fluctuation-like patterns. This is the exact same false positive problem as on Windows (V8, SpiderMonkey, .NET CLR, Java HotSpot).
Mitigating false positives: Setting a higher threshold (e.g., >5 fluctuations) helps filter one-time legitimate transitions. However, JIT engines that repeatedly reuse pages will still trigger. Production deployments should maintain an exclusion list for known JIT-capable processes (by comm name or exe path). Elastic’s prebuilt detection rules, for example, exclude: httpd, java, node, dotnet, github-desktop, code, tenzir, brave, qemu-*, php*, deno, and specific component paths (/usr/share/kibana/node/bin/node, /usr/share/elasticsearch/jdk/bin/java, /usr/sbin/apache2).
Hardening
For organizations aiming to strengthenthe security of their Linux endpoints against memory fluctuation and related in-memory evasion techniques, several measures are recommended:
1. Log Kernel Telemetry
Deploy auditd or Sysmon for Linux with mprotect logging rules. Without kernel-level telemetry, mprotect-based evasion is invisible to defenders. At minimum:
Pair with Laurel for JSON-formatted, SIEM-consumable logs.
2. MAC /Application Whitelisting: fapolicyd
fapolicyd (File Access Policy Daemon) is an application allowlisting daemon available on RHEL 8+, Fedora, and EPEL. It uses the kernel’s fanotify API to intercept execve/execveat/uselib and enforces trust-based policies, only allowing execution of files from trusted sources (RPM database, admin-defined trust lists). This blocks the execution of unknown or dropped binaries.
3. Yama LSM
The Yama Linux Security Module adds ptrace access control via /proc/sys/kernel/yama/ptrace_scope:
Setting ptrace_scope to 2 or higher breaks the fork() + PTRACE_TRACEME pattern used by ELFluctuateLdr, since the parent cannot trace the child at scopes ≥ 2 without CAP_SYS_PTRACE. Note that at scope 1 (the default on most modern distributions), PTRACE_TRACEME is unaffected, the fork+TRACEME pattern works unchanged. Only scopes 2 and 3 mitigate it.
However, Yama is a double-edged sword: if the restriction is too high (scope 2+), it may break legitimate security tooling, including the EDR’s own memory scanning and analysis tools that rely on ptrace, process_vm_readv, or /proc/<pid>/mem access. Scope 1 (the default on most modern distributions) provides a reasonable balance for general use, but does not mitigate fluctuation. Scope 2 or 3 can mitigate fluctuation at the cost of breaking ptrace-dependent tooling.
4. Seccomp and MemoryDenyWriteExecute
Seccomp (secure computing mode) allows a process to filter its own system calls. A seccomp filter can match mprotect and return EPERM, effectively forbidding the syscall entirely.
More precisely, systemd’s MemoryDenyWriteExecute directive uses PR_SET_MDWE with PR_MDWE_REFUSE_EXEC_GAIN (available since Linux 6.3) to deny mprotect transitions that would grant executable permission to a previously writable page. This directly breaks the mprotect(RW → RX) cycle used in fluctuation. When a process under MemoryDenyWriteExecute attempts mprotect with PROT_EXEC on a page that was ever PROT_WRITE, the kernel returns EACCES instead of 0.
Important caveat: This will break JIT compilers (Node.js, Java, Firefox/SpiderMonkey, .NET) and any application that legitimately needs to make writable pages executable. It is best suited for services that do not require JIT (e.g., C/C++ AOT-compiled systemd services).
Conclusion
Memory fluctuation is not a Windows-only phenomenon. As this article demonstrates, the technique fully applies to Linux, and the tools to implement it already exist, from the fork+ptrace approach in ELFluctuateLdr to the timer-based ucontext_t chaining in Pendulum. While Linux EDR memory scanning is less mature than its Windows counterpart, the same fundamental detection principles apply: the memory protection syscall (mprotect on Linux, VirtualProtect on Windows, mach_vm_protect on macOS) is the strong indicator that all fluctuation techniques depend on.
Key Takeaways by Role
SOC analysts:
- Rely on kernel telemetry (auditd + Laurel, or Sysmon for Linux), not userland hooks.
- syslog/EVTX-equivalent logs alone provide limited value for this technique. The signal is in the syscall stream.
- Correlate mprotect events with network beaconing patterns for higher-confidence alerts.
DFIR analysts:
- Use bpftrace for live investigation : the mprotect_fluct.bt script can be deployed instantly on a suspect machine.
- Use Zircolite with the custom Sigma rules to run flash analysis on collected auditd logs.
- Use memscan to check for anonymous executable pages, but understand that fluctuation will defeat this scanner during sleep windows.
- If a suspect process is identified, dump its /proc/<pid>/mem during its active (RX) window, you must catch it while it is awake.
Threat hunters:
- Build detection logic around mprotect frequency and RW ↔ RX transition counting per page, per process.
- Maintain an exclusion list for known JIT-capable processes (by comm and exe).
- Use bpftrace for dynamic, ad-hoc tracing during hunt operations.
Reversers:
- If you intercept a fluctuating payload, you need to catch it during its RX window. Use ptrace or process_vm_readv to dump the decrypted shellcode when the process is awake.
- The encryption routine itself must always be in a non-encrypted page : look for the XOR or RC4 key in the loader’s data section.
- ELFluctuateLdr uses a hardcoded XOR key (0x42); real-world implants would use a generated key, but the key must be accessible to the loader at runtime.
EDR developers:
- Monitor mprotect via eBPF tracepoints (sys_enter_mprotect / sys_exit_mprotect), this is kernel-level telemetry that cannot be bypassed from userland.
- Implement per-page, per-process fluctuation counting with a configurable threshold.
- Combine mprotect signals with other indicators (anonymous executable pages, network connections from processes with memory anomalies) to reduce false positives.
- Consider leveraging MemoryDenyWriteExecute / PR_SET_MDWE enforcement for services that do not require JIT.
Hardening Summary
Final Recommendations
- Allow only file-backed memory to execute. The most effective long-term defense is to enforce that executable memory must be backed by a file on disk, making anonymous executable pages a violation that triggers an alert. This is the Linux equivalent of the Windows ACG (Arbitrary Code Guard) recommendation.
- Monitor memory page permission switches. Regardless of the platform, the mprotect / VirtualProtect / mach_vm_protect syscall is the universal strong indicator. Count transitions per page. Alert when they exceed a threshold.
- Expect false positives from JIT. Just-in-time compilation is functionally indistinguishable from malicious fluctuation at the mprotect level. Build exclusion lists, use higher thresholds, and correlate with additional signals (network activity, anonymous page allocation, process lineage).
Fluctuation is possible on Linux. The defender’s job is to ensure it does not go unnoticed.
References
- kyleavery/pendulum - Kyle Avery, Linux sleep obfuscation






