Rootkit detection from a memory image is more reliable than live system investigation — you bypass the rootkit’s interception layer entirely by reading physical memory directly.
The Rootkit Detection Hierarchy
Live system investigation:
┌────────────────────────────────────────────────────────────┐
│ User-mode tools: ps, ls, netstat, Task Manager │
│ → All subject to SSDT hooks and DKOM manipulation │
│ → Results may be filtered by rootkit │
└────────────────────────────────────────────────────────────┘
Memory image (offline analysis — rootkit cannot intercept):
┌────────────────────────────────────────────────────────────┐
│ Direct structure scanning: psscan, modscan, netscan │
│ → Reads raw physical memory │
│ → Compares against kernel data structure expectations │
│ → Unaffected by OS API hooks │
└────────────────────────────────────────────────────────────┘
DKOM Detection
# Side-by-side comparison of pslist vs psscan
vol.py -f memory.raw windows.pslist | awk '{print $2}' | sort > pslist_pids.txt
vol.py -f memory.raw windows.psscan | awk '{print $2}' | sort > psscan_pids.txt
# Find processes in psscan not in pslist
comm -23 psscan_pids.txt pslist_pids.txt > hidden_processes.txt
# For each hidden PID (zero ExitTime = still running, hidden):
vol.py -f memory.raw windows.psscan | grep "[hidden_PID]"
# If ExitTime = 0 and PID not in pslist → DKOM-hidden active process
# Additional cross-check: ETHREAD scan
# Running processes have active threads — find threads for the hidden PID
vol.py -f memory.raw windows.thrdscan | grep "[hidden_PID]"
# Active ETHREAD structures for a hidden PID confirm the process is running
SSDT Hook Detection
# List all SSDT entries and their target addresses
vol.py -f memory.raw windows.ssdt
# Output format:
# Index | Function Name | Address | Module (who owns this address)
# Legitimate entry:
# 0 | NtMapUserPhysicalPagesScatter | 0xfffff8003a1b4c50 | ntoskrnl.exe
# Hooked entry (address in unknown module):
# 53 | NtQuerySystemInformation | 0xfffff88004a2c100 | UNKNOWN
# Or hooked to a known but unexpected module:
# 53 | NtQuerySystemInformation | 0xfffff88005dd0000 | rootkit.sys
# All SSDT entries should point to ntoskrnl.exe or hal.dll
# Any entry pointing to a 3rd-party driver = potential hook
# Also check Shadow SSDT (used by Win32k — GUI system calls)
vol.py -f memory.raw windows.ssdt | grep -v "ntoskrnl\|hal\|Unknown"
Hidden Kernel Driver Detection
# Method 1: Compare modules (API list) vs modscan (physical scan)
vol.py -f memory.raw windows.modules > modules_list.txt
vol.py -f memory.raw windows.modscan > modscan_list.txt
# Drivers in modscan not in modules = potentially hidden
# Extract module names from each and compare
awk '{print $NF}' modules_list.txt | sort > modules_names.txt
awk '{print $NF}' modscan_list.txt | sort > modscan_names.txt
comm -23 modscan_names.txt modules_names.txt
# Method 2: Driver object scan
vol.py -f memory.raw windows.driverscan
# _DRIVER_OBJECT structures — compare with windows.modules
# Method 3: Check IRP handlers for driver manipulation
vol.py -f memory.raw windows.driverirp
# IRP_MJ_READ, IRP_MJ_WRITE handlers should point to the driver's own .sys file
# Hooks redirecting to another module = filter driver or rootkit
# Suspicious driver characteristics:
# - Name is a single character or random string
# - Loaded from \??\C:\Windows\Temp\
# - Base address falls in unusual memory range
# - No file on disk (windows.filescan shows no matching .sys file)
Network Connection Hiding Detection
# Live netstat vs memory-based netscan comparison
# (Requires memory acquired before live netstat — timing matters)
# Connections visible in memory but not in live system output = rootkit hiding
vol.py -f memory.raw windows.netscan > netscan_results.txt
# From live system (if still accessible):
netstat -anob > live_netstat.txt
# Compare: any connection in netscan not in live netstat = hidden network connection
# Focus on ESTABLISHED connections to external IPs
# Note: some timing discrepancy is normal (connections open/close)
# True hidden connections: stable ESTABLISHED state in memory, absent from live view