Memory and volatile data · Lesson 3 of 4
Network connections, handles and injected code
11 min read
A process list tells you what was running. This lesson answers the next questions: what each process was talking to on the network, which files, registry keys and mutexes it held open, and whether code was running inside it that no file on disk explains. These three views are where memory analysis finds activity that a disk image cannot show, such as a fileless implant inside a legitimate process. Volatility 3 plugin names are used as examples; Lesson 5.2 covered running Volatility 3 and the process plugins, so they are not repeated here.
Network artifacts on Windows
Windows keeps one kernel structure per TCP endpoint, TCP listener and UDP endpoint, allocated by the TCP/IP driver (tcpip.sys) in kernel pool memory. Two plugins read them in different ways:
- windows.netscan scans memory for these structures by their pool signatures (for example the tag
TcpEfor TCP endpoints). Because it finds structures by pattern rather than by following lists, it also recovers connections that were closed and freed but not yet overwritten. Its columns are Offset, Proto, LocalAddr, LocalPort, ForeignAddr, ForeignPort, State, PID, Owner and Created. - windows.netstat walks the driver's own lists of active endpoints, like
netstat.exeon a live system. It shows only what was current, and it depends on symbol information fortcpip.sys, so it may fail on some builds.
An entry present in netscan but absent from netstat is either an old, closed connection or a structure unlinked to hide it. Decide which from the state and the timestamp. Proto is TCPv4, TCPv6, UDPv4 or UDPv6. UDP is connectionless, so UDP rows have no state and usually no foreign address. Created is the socket creation time, shown in UTC. Scanning can also return fragments of freed memory with impossible ports or PIDs; ignore rows that do not make sense.
| State | Meaning | What it suggests |
|---|---|---|
| LISTENING | Socket waiting for incoming connections | A service or backdoor accepting connections on LocalPort |
| SYN_SENT | Outbound attempt not yet answered | Beaconing to a server that is down or blocked |
| ESTABLISHED | Open connection, data can flow both ways | Active communication at capture time |
| CLOSE_WAIT | Remote side has closed; local application has not | The peer dropped the session; the process still holds the socket |
| TIME_WAIT | Local side closed first; the system holds the tuple briefly | A recent, finished connection; the owner is often missing |
From a connection to a command line
The PID column links a socket to a process. Confirm the PID in windows.pslist or windows.pstree and check that the process creation time is earlier than the socket's Created time, because PIDs are reused after a process exits. Then run windows.cmdline for that PID: the command line usually tells you why the process was talking to the network, or shows that it has no good reason to.
Linux sockets and open files
In the CFX practical exam, memory questions use a Linux capture. On Linux, linux.sockstat lists every socket held by every process, with the PID, file descriptor, family (AF_INET, AF_INET6, AF_UNIX, AF_NETLINK), protocol, source and destination address and port, and TCP state. Filter on AF_INET and AF_INET6 to remove the many UNIX domain and netlink sockets. Volatility 3 has no linux.netstat plugin; linux.sockstat fills that role. linux.lsof lists the open file descriptors of each process: regular files with their paths, and sockets as socket:[inode]. Together they answer "which process had this connection, and which files did it have open?"
Handles and cached files
A handle is a process's reference to a kernel object. windows.handles, normally run with --pid, lists each handle's Type, GrantedAccess and Name. The useful types are File (open files and devices), Key (registry keys), Mutant (a mutex), Process and Thread (handles to other processes and threads, as an injector needs), Event and Section (shared memory). Many malware families create a named mutex so that only one copy runs, and that name is a published indicator. An open Key handle shows access to a key, not that a value was written.
windows.filescan scans for FILE_OBJECT structures and lists their paths, including files that are no longer open. windows.dumpfiles recovers a file's cached content from its data section (the file cache), its image section (an executable mapped for execution) or the shared cache map. The result is often incomplete: pages that were not resident are missing or zero-filled. An executable recovered from its image section is laid out as in memory, so its hash will not match the file on disk. On Linux, linux.lsof gives the equivalent list of open files.
Code injection and the VAD tree
Code injection means placing code into another process and running it there, so that it inherits that process's name, privileges and trust. Three common forms:
- Classic remote thread injection: open the target, allocate memory in it (
VirtualAllocEx), write code (WriteProcessMemory) and start it (CreateRemoteThread). - Process hollowing: start a legitimate executable suspended, unmap its image, write a malicious image in its place, redirect the thread's start address and resume it.
- Reflective DLL loading: a DLL maps and relocates itself from memory with its own loader, so the Windows loader never records it in the module lists.
The kernel describes each process's address space with a tree of Virtual Address Descriptors (VADs). Each VAD node covers one range and records whether it is private memory or a mapped file (with the file name), its tag (VadS for typical private allocations), and the protection requested when the range was allocated. Executable images and DLLs are mapped from files with PAGE_EXECUTE_WRITECOPY; heaps and stacks are private PAGE_READWRITE. windows.vadinfo lists every VAD of a process.
Finding injected code with malfind
windows.malfind flags VAD regions whose protection allows both writing and execution, chiefly private (not file-backed) PAGE_EXECUTE_READWRITE regions, and skips those whose pages are empty, so it reports committed content. For each hit it prints a hexdump and a disassembly of the start of the region. Read the first bytes:
4D 5A("MZ") means a PE file sits in private memory, which the Windows loader never produces. Disassembled, a header is nonsense such asdec ebp; pop edx(32-bit) orpop r10(64-bit).- Bytes that disassemble to plausible code, such as a function prologue or a call/pop sequence, suggest shellcode.
- A header that has been zeroed hides the "MZ"; look further for
PE\0\0or "This program cannot be run in DOS mode".
Two limits matter. First, the VAD records the original protection; code allocated as RW and later changed to RX with VirtualProtect will not appear as RWX. Second, legitimate software produces hits: just-in-time (JIT) compilers in the .NET runtime and JavaScript engines write and execute code in private memory, and some security products inject their own code. Confirm a hit by dumping it (--dump) and scanning it with YARA rules, either with windows.vadyarascan on process VADs (one PID or all) or yarascan on kernel memory.
Process hollowing indicators
Hollowing leaves a mismatch between what the process claims and what is mapped. The PEB image path, shown by cmdline and dlllist, names a legitimate executable, but the VAD at the image base is not backed by that file. windows.ldrmodules compares mapped VADs with the three PEB module lists (InLoad, InInit, InMem); a main image that is unlinked, or a base region with no mapped path, is suspicious. The executable normally shows False in InInit, so that alone is not an indicator. A malfind hit at the image base completes the picture.
Services, registry and Linux rootkit checks
windows.svcscan reads service records from services.exe memory, including name, state, start type and binary path, and can show a service that was added for persistence. windows.registry.hivelist lists loaded hives and windows.registry.printkey reads a key from memory, such as a Run key, including changes not yet flushed to disk. On Linux, linux.malfind flags writable-and-executable or anonymous executable memory mappings; linux.lsmod lists loaded kernel modules, linux.check_modules compares that list with sysfs to find hidden modules, and linux.check_syscall reports system call table entries that point outside the kernel. Since 2025, Volatility 3 lists malfind, ldrmodules and the Linux check plugins under windows.malware. and linux.malware. (for example windows.malware.malfind); the short names used here are deprecated aliases.
Worked example: rundll32.exe with no arguments
A Windows memory image is analyzed. Selected netscan columns:
| Proto | LocalAddr:Port | ForeignAddr:Port | State | PID | Owner | Created |
|---|---|---|---|---|---|---|
| TCPv4 | 0.0.0.0:135 | 0.0.0.0:0 | LISTENING | 1012 | svchost.exe | 2026-03-14T08:01:44Z |
| TCPv4 | 192.0.2.15:49823 | 203.0.113.50:443 | ESTABLISHED | 7344 | rundll32.exe | 2026-03-14T14:22:09Z |
| TCPv4 | 192.0.2.15:49790 | 198.51.100.24:443 | CLOSE_WAIT | 5128 | msedge.exe | 2026-03-14T14:05:31Z |
rundll32.exe exists to run a DLL export, so its command line normally names one (rundll32.exe file.dll,Export). windows.cmdline for PID 7344 shows only C:\Windows\System32\rundll32.exe, with no arguments. That pattern is typical of a sacrificial process started by a post-exploitation framework to host injected code. windows.malfind --pid 7344:
| Start VPN | End VPN | Tag | Protection | CommitCharge | PrivateMemory | First bytes |
|---|---|---|---|---|---|---|
| 0x1e0000 | 0x20afff | VadS | PAGE_EXECUTE_READWRITE | 43 | 1 | 4D 5A 90 00 |
Size = End − Start + 1 = 0x20afff − 0x1e0000 + 1 = 0x2b000 = 176,128 bytes = 176,128 / 4,096 = 43 pages, matching CommitCharge 43, so the whole region is committed. windows.handles --pid 7344 shows a Mutant named qz7k2m_upd_0451, which a threat report lists for a known family.
The cautious conclusion: the evidence is consistent with a PE image injected into rundll32.exe PID 7344, which held an established connection to 203.0.113.50:443 created at 2026-03-14T14:22:09Z. It does not show what data was sent or how the code arrived. Collect next: dump the region with malfind --dump and record its SHA-256 (a memory-layout PE will not match an on-disk sample by hash); scan it with YARA; check the parent in windows.pstree; look for other processes talking to 203.0.113.50; and check the disk for persistence and delivery (Run keys, services, scheduled tasks, Prefetch, and the file system timeline from Module 3).
Indicators and their false positives
| Indicator | Plugin | Why suspicious | Common false positive |
|---|---|---|---|
Private RWX region starting with MZ | windows.malfind | PE not loaded by the Windows loader | Packers and self-unpacking software |
| Private RWX region of code-like bytes | windows.malfind | Possible shellcode | .NET and JavaScript JIT compilers |
| Outbound connection from a process that rarely uses the network | windows.netscan | Injected or abused system binary | A legitimate DLL hosted by rundll32 |
| LISTENING socket on an unusual port | windows.netscan, linux.sockstat | Backdoor accepting connections | Developer tools, peer-to-peer software |
| Known mutex name | windows.handles | Family-specific infection marker | Short, generic names that collide |
| Image base not backed by its file | windows.ldrmodules, windows.vadinfo | Process hollowing | InInit False for the main image is normal |
| Syscall entry outside the kernel | linux.check_syscall | Rootkit hook | Security or tracing modules |
Exam focus: Part A may ask which plugin recovers closed connections (netscan, not netstat), what a TCP state means, which handle type is a mutex (Mutant), or why malfind flags a region and when the flag is a false positive. Expect to compute a region's size from Start and End VPN. In Part B the memory capture is Linux, so be ready to find a PID, remote address or open file path with linux.sockstat and linux.lsof and state it exactly.
Key points
- windows.netscan finds endpoint structures by scanning and includes closed connections; windows.netstat walks only the active lists.
- Link a socket to its process by PID, check the process creation time against Created, then read the command line.
- windows.handles shows open files, keys and mutexes; a known mutex name is a strong indicator, and dumped files may be incomplete.
- windows.malfind flags private, committed, executable and writable VAD regions; "MZ" at the start is a red flag, JIT compilers are the classic false positive.
- Hollowing shows as an image path that does not match the VAD at the image base, an unlinked main image in ldrmodules, and a malfind hit at that base.
- On Linux, use linux.sockstat for connections, linux.lsof for open files, and linux.lsmod, linux.check_modules and linux.check_syscall for rootkit checks.