Scene 1: The Ghost Attack
Kwame's phone rang at 6:43 on a Tuesday morning. The caller ID showed the name of a mid-sized regional bank he'd worked with twice before β routine security assessments, nothing alarming. This call was different. The bank's Security Operations Center had detected suspicious lateral movement overnight: a workstation in the finance department had established connections to several servers it had never communicated with before, including a domain controller. The SOC had locked the workstation, captured a network log, and called Kwame.
By 9:15 he was in their operations center with his forensic workstation plugged into their network. He started where he always started: looking for the malicious file. He ran a full antivirus scan on the affected machine. Clean. He checked for recently created or modified executables β nothing unusual. He searched for scripts, batch files, unfamiliar DLLs in common staging directories. Nothing. He pulled a hash list of every file on the system and ran it against VirusTotal. All clean. He stood back from the workstation and said, quietly to himself: "It's fileless."
His junior analyst, Priya, looked over from her own laptop. "Fileless? How does that work β where does the malware live if not in a file?" Kwame pulled up a chair. "That," he said, "is exactly the right question. And answering it is going to take us through how Windows processes work at a level most people never think about."
Fileless malware is code that exists only in RAM β in the running memory of the computer β never written to disk as a file. It typically arrives via a phishing macro or a browser exploit that immediately loads code directly into memory and begins executing. Because it never writes a file to the filesystem, every tool that looks for malicious files β antivirus, file integrity monitors, hash comparisons β returns clean. The malicious code is running right now, in an active process, and the only place it exists is in RAM.
Scene 2: How Processes Work
Kwame pulled up Process Explorer on the forensic workstation and walked Priya through what she was looking at. Every program running on Windows runs as a process. Each process has its own virtual address space β a private region of memory that only that process can normally access, managed by the operating system. When a program starts, Windows loads its executable code into that address space, along with any libraries (DLLs β Dynamic Link Libraries) the program needs.
"Look at this process here β svchost.exe," Kwame said, pointing at the list. "There are eight of them. That's normal. svchost is the generic host process for Windows services. The operating system itself loads core DLLs into every single process automatically β kernel32.dll, ntdll.dll, user32.dll. These are always present, always trusted." He highlighted one of the svchost instances. "Now look at this one. Its parent process isn't services.exe, which is what it should always be. Its parent is explorer.exe. That is not normal."
Priya frowned. "So something spawned a fake svchost?" "Not a fake one," Kwame said. "That's an actual, legitimate svchost.exe binary on disk. The file is real. But what's running inside its memory space β that's the question. And this is why process injection is such a powerful technique. Attackers don't need to bring their own process. They hijack a legitimate one. svchost.exe, explorer.exe, lsass.exe, chrome.exe β these processes are always running, always trusted by security tools, and always have network access or user context that makes them useful. Once malicious code is running inside one of them, it inherits all of that trust."
Process injection is the technique of introducing and executing malicious code inside the address space of a legitimate process. From task manager, from network monitors, from most security tools β the process looks exactly like it should. The malicious code is hidden inside it, running under the identity and privileges of the host process.
Scene 3: DLL Injection
Kwame began pulling memory artifacts from the suspicious svchost process. The first technique he found evidence of β based on event log traces and a remnant string in a memory dump β was classic DLL injection. He wrote the steps on the conference room whiteboard so Priya could follow along.
"Step one: the attacker's code opens a handle to the target process using OpenProcess. With that handle, they call VirtualAllocEx to allocate a region of memory inside the target process's address space." He drew a box labeled "target process" and an arrow pointing at a small allocated region inside it. "Step two: they call WriteProcessMemory to write a string into that allocated region β the file path to their malicious DLL. The DLL file itself is sitting on disk somewhere." He added a second arrow. "Step three: they call GetProcAddress to find the address of Windows' own LoadLibrary function inside kernel32.dll. Then they call CreateRemoteThread β they create a new thread inside the target process, with the instruction pointer set to LoadLibrary, and the DLL path as the argument."
Priya stared at the board. "So Windows loads the malicious DLL itself?" "Exactly. Windows' own code loads it. The DLL file exists on disk β that's one detection opportunity. But the execution context is entirely inside the legitimate host process. When the DLL's DllMain function runs, it's running as svchost.exe."
The full API sequence: OpenProcess β VirtualAllocEx β WriteProcessMemory β GetProcAddress (for LoadLibraryA) β CreateRemoteThread. These are the same APIs used by legitimate software every day β debuggers, screen readers, accessibility tools, security software. Windows cannot simply block them. The attacker is abusing the operating system's own legitimate functionality against itself.
Scene 4: Reflective DLL Injection
Memory forensics on the compromised machine revealed something more sophisticated than standard DLL injection. Kwame ran Volatility β a memory forensics framework β against a full RAM image he had acquired. He used the malfind plugin, which scans for memory regions with unusual permission combinations and patterns consistent with injected code. One result jumped out: a region inside explorer.exe with read-write-execute permissions and what appeared to be a PE (Portable Executable) header β a DLL image β loaded at an address not registered with the Windows loader.
"No DLL on disk matches this," Priya noted, scanning the file list. "Exactly," Kwame said. "This is reflective DLL injection. The DLL never touched the disk. Here's how it works." He explained: unlike standard DLL injection, which writes a file path to disk and has Windows load it using LoadLibrary, reflective injection is entirely self-contained. The shellcode already present in memory contains a full DLL image β the entire binary β plus a small custom bootstrap loader function. That loader does everything the Windows loader normally does, but entirely within the process and without involving the OS loader at all: it maps the DLL sections into memory, resolves imported function addresses by manually scanning the export tables of loaded modules, applies base relocations, and then calls the DLL's entry point.
"No DLL file on disk. No LoadLibrary call in the API log. No registry entry. No entry in the Windows loader's list of modules β which means most tools that enumerate loaded DLLs won't find it." He paused. "This is what Meterpreter uses. CobaltStrike Beacon. Most modern command-and-control frameworks. When you hear about 'advanced' implants, this is usually what they mean: the payload is completely self-loading from memory. Detection requires memory scanning β looking for PE headers in unexpected memory regions β or behavioral EDR that catches the allocation and execution pattern."
Scene 5: Process Hollowing
While Priya worked through the explorer.exe memory dump, Kwame started on a second anomalous process: another svchost.exe instance that the EDR had flagged with a subtle note β its memory image didn't match its on-disk image. That note was the tell. "This one," Kwame said, "is process hollowing. Also called process replacement." He walked her through the sequence.
The attacker had created a new svchost.exe process in a suspended state β using the CREATE_SUSPENDED flag in CreateProcess. The process existed in memory, with the correct name, correct parent process initially assigned, correct file path listed. But it wasn't running yet. Then the attacker called NtUnmapViewOfSection (or ZwUnmapViewOfSection) to unmap β hollow out β the legitimate executable code that had been loaded into the process's memory. The process was now a shell: running but empty, its code regions deallocated. Next, the attacker wrote malicious code into that empty space using WriteProcessMemory, updated the process's thread context so the instruction pointer pointed to the malicious code's entry point, and called ResumeThread.
"The process is now running," Kwame said. "Task manager shows svchost.exe. The PID is real. The parent process looks right. The file path on disk is the real, unmodified svchost.exe β file integrity checks pass. But the code that's actually executing inside it is entirely the attacker's payload." He pulled up the EDR alert. "The only reason we caught it is that the EDR compared the in-memory executable sections to the hash of the on-disk file. Mismatch. The on-disk file is fine; the in-memory copy is replaced."
Process hollowing is particularly effective against security tools that track processes by name or file path. It combines the legitimacy of a trusted process name with the execution of completely arbitrary malicious code.
Scene 6: Shellcode and Thread Injection
The third injected component Kwame found was raw shellcode β no DLL structure, no PE header, just position-independent machine code injected directly into a process and executed via thread hijacking. He found it by looking for memory regions with execute permissions that contained no corresponding module name in Volatility's output β orphaned executable regions with no parent DLL.
The thread hijacking technique: the attacker had opened a handle to an existing thread in the target process using OpenThread, then called SuspendThread to pause it. With the thread suspended, they called SetThreadContext to modify the thread's CPU registers β specifically the instruction pointer (RIP on 64-bit, EIP on 32-bit) β to point to shellcode they had previously written into the process via VirtualAlloc and WriteProcessMemory. Then ResumeThread. The thread woke up and began executing the shellcode, believing it was continuing from where it had been paused.
He mentioned a historical related technique to Priya: AtomBombing, documented in 2016. Windows maintains an atom table β a legitimate inter-process communication mechanism that allows processes to store small strings and share them. AtomBombing worked by writing shellcode to the atom table using GlobalAddAtom, then using Windows' APC (Asynchronous Procedure Call) mechanism β another legitimate feature β to trick a legitimate process into reading the shellcode from the atom table and executing it. No VirtualAllocEx, no WriteProcessMemory β both of which behavioral tools were watching. It used entirely different Windows mechanisms to achieve the same result.
"Every one of these techniques," Kwame said, "has the same principle: abuse legitimate OS features β the memory APIs, the thread model, the IPC mechanisms β to run arbitrary code inside a trusted process. The attacker never brings their own process. They borrow yours."
Scene 7: The LSASS Target
By mid-afternoon, Kwame had mapped the full attack chain. The injected code in explorer.exe was a CobaltStrike Beacon β the attacker's command-and-control implant, communicating outbound over HTTPS on port 443 to an IP that resolved to a legitimate-looking cloud provider. The shellcode in the second process was a credential harvesting module. And the final target of the entire operation was lsass.exe.
LSASS β the Local Security Authority Subsystem Service β is one of the most targeted processes in Windows attacks. It is responsible for authentication: when you log into a Windows machine, your credentials are processed and verified by LSASS. As a consequence of this role, LSASS holds credential material in its memory at all times: NTLM hashes (the hashed form of user passwords), Kerberos tickets (the tokens used for network authentication in Active Directory environments), and in older configurations or when WDigest authentication is enabled, cleartext passwords. An attacker with access to LSASS memory has access to every credential that has authenticated on that machine since the last reboot.
Mimikatz β the open-source credential extraction tool created by Benjamin Delpy β works by injecting code into or reading from LSASS memory. On modern Windows, doing this requires SYSTEM privileges or the SeDebugPrivilege right. Once the attacker's code was running in a SYSTEM-level process via injection, extracting from LSASS was the natural next step.
Modern defenses against LSASS attacks: Windows Credential Guard uses Virtualization-Based Security (VBS) to isolate credential storage. LSASS runs in a separate, protected Virtual Secure Mode environment β a lightweight VM with its own isolated kernel, running at a higher privilege level (VTL1) than the normal OS (VTL0). Even if the OS kernel itself is compromised, it cannot read the credential material because it doesn't have access to the VTL1 memory. PPL (Protected Process Light) is a complementary defense: it marks the LSASS process as protected, requiring any other process that wants to open a handle to LSASS to itself be a protected process with a valid code signing certificate β something Mimikatz cannot satisfy without a kernel driver.
Scene 8: Defense Framework
That evening, Kwame sat with the bank's CISO and security team to brief them on findings and recommendations. He'd found the full kill chain: phishing email β malicious macro β PowerShell in memory β reflective DLL injection into explorer.exe (CobaltStrike Beacon) β lateral movement using harvested credentials β process hollowing in svchost.exe β LSASS credential dump β domain controller compromise. Forty-seven endpoints had been touched. Zero malicious files had been written to disk. The attack had been running for nineteen days before the SOC noticed the anomalous network traffic.
He walked through the defensive layers they needed to implement. First: DEP β Data Execution Prevention, enforced by the CPU's NX (No-Execute) bit. DEP marks every memory region as either writable or executable, not both. Stack memory (where shellcode is typically written) is writable but not executable. Code regions are executable but not writable. Simple shellcode injection into a stack or heap region fails because the CPU refuses to execute code from those pages. Attackers counter this with ROP (Return-Oriented Programming) chains β sequences of legitimate code fragments already in executable memory β but that significantly raises the complexity bar.
Second: ASLR β Address Space Layout Randomization. Every time Windows boots, it randomizes where DLLs load in memory, where the stack is, where the heap is. An attacker cannot hardcode the address of kernel32.dll's LoadLibrary function because it's different on every machine and every boot. They have to find it dynamically β typically via an information-leak vulnerability. ASLR is what makes ROP chains hard: even if the attacker has a chain, the addresses of the gadgets change every boot.
Third: CFG β Control Flow Guard. A Microsoft compiler feature that pre-computes a bitmap of valid indirect call targets. At runtime, every indirect function call is validated against this bitmap before execution. An attacker who overwrites a function pointer or vtable entry to redirect control flow will find their hijacked address isn't in the valid-target bitmap β the call is blocked before it happens.
Fourth: behavioral EDR. The injection API sequence is distinctive: VirtualAllocEx, then WriteProcessMemory, then CreateRemoteThread, targeting a different process than the caller. Legitimate software does this (debuggers, security tools) but the set of processes that should be doing it at runtime is very small and well-known. An EDR rule that alerts when a non-debugger process executes this triad against another process catches the vast majority of DLL injection attempts. Similar rules for OpenThread + SuspendThread + SetThreadContext catch thread hijacking.
Fifth: Credential Guard and LSA Protection for LSASS. Sixth: PowerShell constrained language mode and ScriptBlock logging to detect the initial-access macro-to-PowerShell chain. Seventh: memory scanning β periodic or on-demand scans of running process memory looking for PE headers in non-module-backed regions, shellcode signatures, and injected code patterns.
The CISO looked at the list. "Why wasn't any of this running before?" Kwame nodded slowly. "Credential Guard requires Windows 10 Enterprise and specific hardware. The EDR had behavioral rules, but they were tuned conservatively because they were generating false positives on a legitimate internal tool. DEP was enabled. ASLR was enabled. But none of it is sufficient alone β and the attacker was sophisticated enough to work around each individual control." He paused. "Defense in depth isn't just about having the controls. It's about having them all, tuned, monitored, and understood as a system."