Lesson 4 of 5 · 20 min read

Rootkit Detection — DKOM, SSDT Hooks, and Kernel Integrity

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