Empire/lib/common/listeners.py

402 lines
15 KiB
Python

"""
Listener handling functionality for Empire.
"""
import sys
import fnmatch
import imp
import helpers
import os
import pickle
import hashlib
import copy
class Listeners:
"""
Listener handling class.
"""
def __init__(self, MainMenu, args):
self.mainMenu = MainMenu
self.args = args
self.conn = MainMenu.conn
# loaded listener format:
# {"listenerModuleName": moduleInstance, ...}
self.loadedListeners = {}
# active listener format (these are listener modules that are actually instantiated)
# {"listenerName" : {moduleName: 'http', options: {setModuleOptions} }}
self.activeListeners = {}
self.load_listeners()
self.start_existing_listeners()
def load_listeners(self):
"""
Load listeners from the install + "/lib/listeners/*" path
"""
rootPath = "%s/lib/listeners/" % (self.mainMenu.installPath)
pattern = '*.py'
print helpers.color("[*] Loading listeners from: %s" % (rootPath))
for root, dirs, files in os.walk(rootPath):
for filename in fnmatch.filter(files, pattern):
filePath = os.path.join(root, filename)
# don't load up any of the templates
if fnmatch.fnmatch(filename, '*template.py'):
continue
# extract just the listener module name from the full path
listenerName = filePath.split("/lib/listeners/")[-1][0:-3]
# instantiate the listener module and save it to the internal cache
self.loadedListeners[listenerName] = imp.load_source(listenerName, filePath).Listener(self.mainMenu, [])
def set_listener_option(self, listenerName, option, value):
"""
Sets an option for the given listener module or all listener module.
"""
# for name, listener in self.listeners.iteritems():
# for listenerOption, optionValue in listener.options.iteritems():
# if listenerOption == option:
# listener.options[option]['Value'] = str(value)
for name, listenerObject in self.loadedListeners.iteritems():
if (listenerName.lower() == 'all' or listenerName == name) and (option in listenerObject.options):
# parse and auto-set some host parameters
if option == 'Host':
if not value.startswith('http'):
parts = value.split(':')
# if there's a current ssl cert path set, assume this is https
if ('CertPath' in listenerObject.options) and (listenerObject.options['CertPath']['Value'] != ''):
protocol = 'https'
defaultPort = 443
else:
protocol = 'http'
defaultPort = 80
elif value.startswith('https'):
value = value.split('//')[1]
parts = value.split(':')
protocol = 'https'
defaultPort = 443
elif value.startswith('http'):
value = value.split('//')[1]
parts = value.split(':')
protocol = 'http'
defaultPort = 80
if len(parts) != 1 and parts[-1].isdigit():
# if a port is specified with http://host:port
listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
if listenerObject.options['Port']['Value'] == '':
listenerObject.options['Port']['Value'] = parts[-1]
elif listenerObject.options['Port']['Value'] != '':
# otherwise, check if the port value was manually set
listenerObject.options['Host']['Value'] = "%s://%s:%s" % (protocol, value, listenerObject.options['Port']['Value'])
else:
# otherwise use default port
listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
if listenerObject.options['Port']['Value'] == '':
listenerObject.options['Port']['Value'] = defaultPort
return True
elif option == 'CertPath':
listenerObject.options[option]['Value'] = value
host = listenerObject.options['Host']['Value']
# if we're setting a SSL cert path, but the host is specific at http
if host.startswith('http:'):
listenerObject.options['Host']['Value'] = listenerObject.options['Host']['Value'].replace('http:', 'https:')
return True
if option == 'Port':
listenerObject.options[option]['Value'] = value
return True
elif option == 'StagingKey':
# if the staging key isn't 32 characters, assume we're md5 hashing it
value = str(value).strip()
if len(value) != 32:
stagingKeyHash = hashlib.md5(value).hexdigest()
print helpers.color('[!] Warning: staging key not 32 characters, using hash of staging key instead: %s' % (stagingKeyHash))
listenerObject.options[option]['Value'] = stagingKeyHash
else:
listenerObject.options[option]['Value'] = str(value)
return True
elif option in listenerObject.options:
listenerObject.options[option]['Value'] = value
# if option.lower() == 'type':
# if value.lower() == "hop":
# # set the profile for hop.php for hop
# parts = self.options['DefaultProfile']['Value'].split("|")
# self.options['DefaultProfile']['Value'] = "/hop.php|" + "|".join(parts[1:])
return True
# if parts[0].lower() == 'defaultprofile' and os.path.exists(parts[1]):
# try:
# open_file = open(parts[1], 'r')
# profile_data_raw = open_file.readlines()
# open_file.close()
# profile_data = [l for l in profile_data_raw if not l.startswith('#' and l.strip() != '')]
# profile_data = profile_data[0].strip("\"")
# self.mainMenu.listeners.set_listener_option(parts[0], profile_data)
# except Exception:
# print helpers.color("[!] Error opening profile file %s" % (parts[1]))
else:
print helpers.color('[!] Error: invalid option name')
return False
def start_listener(self, moduleName, listenerObject):
"""
Takes a listener module object, starts the listener, adds the listener to the database, and
adds the listener to the current listener cache.
"""
category = listenerObject.info['Category']
name = listenerObject.options['Name']['Value']
nameBase = name
if not listenerObject.validate_options():
return
i = 1
while name in self.activeListeners.keys():
name = "%s%s" % (nameBase, i)
listenerObject.options['Name']['Value'] = name
try:
print helpers.color("[*] Starting listener '%s'" % (name))
success = listenerObject.start(name=name)
if success:
print helpers.color('[+] Listener successfully started!')
listenerOptions = copy.deepcopy(listenerObject.options)
self.activeListeners[name] = {'moduleName': moduleName, 'options':listenerOptions}
pickledOptions = pickle.dumps(listenerObject.options)
cur = self.conn.cursor()
cur.execute("INSERT INTO listeners (name, module, listener_category, options) VALUES (?,?,?,?)", [name, moduleName, category, pickledOptions])
cur.close()
else:
print helpers.color('[!] Listener failed to start!')
except Exception as e:
if name in self.activeListeners:
del self.activeListeners[name]
print helpers.color("[!] Error starting listener: %s" % (e))
def start_existing_listeners(self):
"""
Startup any listeners that are currently in the database.
"""
oldFactory = self.conn.row_factory
self.conn.row_factory = helpers.dict_factory
cur = self.conn.cursor()
cur.execute("SELECT id,name,module,listener_type,listener_category,options FROM listeners")
results = cur.fetchall()
cur.close()
for result in results:
listenerName = result['name']
moduleName = result['module']
nameBase = listenerName
i = 1
while listenerName in self.activeListeners.keys():
listenerName = "%s%s" % (nameBase, i)
# unpickle all the listener options
options = pickle.loads(result['options'])
try:
listenerModule = self.loadedListeners[moduleName]
for option, value in options.iteritems():
listenerModule.options[option] = value
print helpers.color("[*] Starting listener '%s'" % (listenerName))
if moduleName == 'redirector':
success = True
else:
success = listenerModule.start(name=listenerName)
if success:
print helpers.color('[+] Listener successfully started!')
listenerOptions = copy.deepcopy(listenerModule.options)
self.activeListeners[listenerName] = {'moduleName': moduleName, 'options':listenerOptions}
else:
print helpers.color('[!] Listener failed to start!')
except Exception as e:
if listenerName in self.activeListeners:
del self.activeListeners[listenerName]
print helpers.color("[!] Error starting listener: %s" % (e))
self.conn.row_factory = oldFactory
def kill_listener(self, listenerName):
"""
Shut down the server associated with a listenerName and delete the
listener from the database.
To kill all listeners, use listenerName == 'all'
"""
if listenerName.lower() == 'all':
listenerNames = self.activeListeners.keys()
else:
listenerNames = [listenerName]
for listenerName in listenerNames:
if listenerName not in self.activeListeners:
print helpers.color("[!] Listener '%s' not active!" % (listenerName))
return False
# shut down the listener and remove it from the cache
if self.mainMenu.listeners.get_listener_module(listenerName) == 'redirector':
# remove the listener object from the internal cache
del self.activeListeners[listenerName]
self.conn.row_factory = None
cur = self.conn.cursor()
cur.execute("DELETE FROM listeners WHERE name=?", [listenerName])
cur.close()
continue
self.shutdown_listener(listenerName)
# remove the listener from the database
self.conn.row_factory = None
cur = self.conn.cursor()
cur.execute("DELETE FROM listeners WHERE name=?", [listenerName])
cur.close()
def shutdown_listener(self, listenerName):
"""
Shut down the server associated with a listenerName, but DON'T
delete it from the database.
"""
if listenerName.lower() == 'all':
listenerNames = self.activeListeners.keys()
else:
listenerNames = [listenerName]
for listenerName in listenerNames:
if listenerName not in self.activeListeners:
print helpers.color("[!] Listener '%s' doesn't exist!" % (listenerName))
return False
# retrieve the listener module for this listener name
activeListenerModuleName = self.activeListeners[listenerName]['moduleName']
activeListenerModule = self.loadedListeners[activeListenerModuleName]
if activeListenerModuleName == 'redirector':
print helpers.color("[!] skipping redirector listener %s. Start/Stop actions can only initiated by the user." % (listenerName))
continue
# signal the listener module to shut down the thread for this particular listener instance
activeListenerModule.shutdown(name=listenerName)
# remove the listener object from the internal cache
del self.activeListeners[listenerName]
def is_listener_valid(self, name):
return name in self.activeListeners
def get_listener_id(self, name):
"""
Resolve a name to listener ID.
"""
oldFactory = self.conn.row_factory
self.conn.row_factory = None
cur = self.conn.cursor()
cur.execute('SELECT id FROM listeners WHERE name=? or id=?', [name, name])
results = cur.fetchone()
cur.close()
self.conn.row_factory = oldFactory
if results:
return results[0]
else:
return None
def get_listener_name(self, listenerId):
"""
Resolve a listener ID to a name.
"""
cur = self.conn.cursor()
cur.execute('SELECT name FROM listeners WHERE name=? or id=?', [listenerId, listenerId])
results = cur.fetchone()
cur.close()
if results:
return results[0]
else:
return None
def get_listener_module(self, listenerName):
"""
Resolve a listener name to the module used to instantiate it.
"""
cur = self.conn.cursor()
cur.execute('SELECT module FROM listeners WHERE name=?', [listenerName])
results = cur.fetchone()
cur.close()
if results:
return results[0]
else:
return None
def get_listener_options(self):
"""
Return the options for a listener type
"""
cur = self.conn.cursor()
cur.execute('SELECT options FROM listeners')
results = cur.fetchall()
cur.close()
if results:
return results[0][0]
else:
return None
def get_listener_names(self):
"""
Return all current listener names.
"""
return self.activeListeners.keys()