diff --git a/empire b/empire index d09cb2e..27be74f 100755 --- a/empire +++ b/empire @@ -1,13 +1,637 @@ #!/usr/bin/python -import sqlite3, argparse +import sqlite3, argparse, sys, argparse, logging, json, string, os, re, time, signal +from flask import Flask, request, jsonify, make_response, abort +from time import localtime, strftime +from OpenSSL import SSL +from Crypto.Random import random # Empire imports from lib.common import empire -from lib.common import listeners -from lib.common import http -from lib.common import packets -from lib.common import messages + + +##################################################### +# +# Database interaction methods for the RESTful API +# +##################################################### + +def database_connect(): + """ + Connect with the backend ./empire.db sqlite database and return the + connection object. + """ + try: + # set the database connectiont to autocommit w/ isolation level + conn = sqlite3.connect('./data/empire.db', check_same_thread=False) + conn.text_factory = str + conn.isolation_level = None + return conn + + except Exception as e: + print helpers.color("[!] Could not connect to database") + print helpers.color("[!] Please run database_setup.py") + sys.exit() + + +def execute_db_query(conn, query, args=None): + """ + Execute the supplied query on the provided db conn object + with optional args for a paramaterized query. + """ + cur = conn.cursor() + if(args): + cur.execute(query, args) + else: + cur.execute(query) + results = cur.fetchall() + cur.close() + return results + + +def refresh_api_token(conn): + """ + Generates a randomized RESTful API token and updates the value + in the config stored in the backend database. + """ + + # generate a randomized API token + apiToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40)) + + execute_db_query(conn, "UPDATE config SET api_token=?", [apiToken]) + + return apiToken + + +#################################################################### +# +# The Empire RESTful API. +# +# Adapted from http://blog.miguelgrinberg.com/post/designing-a-restful-api-with-python-and-flask +# example code at https://gist.github.com/miguelgrinberg/5614326 +# +# Verb URI Action +# ---- --- ------ +# GET http://localhost:1337/empire/api/version return the current Empire version +# +# GET http://localhost:1337/empire/api/config return the current default config +# +# GET http://localhost:1337/empire/api/stagers return all current stagers +# GET http://localhost:1337/empire/api/stagers/X return the stager with name X +# POST http://localhost:1337/empire/api/stagers generate a stager given supplied options (need to implement) +# +# GET http://localhost:1337/empire/api/modules return all current modules +# +# GET http://localhost:1337/empire/api/listeners return all current listeners +# GET http://localhost:1337/empire/api/listeners/Y return the listener with id Y +# GET http://localhost:1337/empire/api/listeners/options return all listener options +# POST http://localhost:1337/empire/api/listeners starts a new listener with the specified options +# DELETE http://localhost:1337/empire/api/listeners/Y kills listener Y +# +# GET http://localhost:1337/empire/api/agents return all current agents +# GET http://localhost:1337/empire/api/agents/Y return the agent with name Y +# GET http://localhost:1337/empire/api/agents/Y/results return tasking results for the agent with name Y +# POST http://localhost:1337/empire/api/agents/Y modify or task agent with Y +# DELETE http://localhost:1337/empire/api/agents/Y removes agent Y from the database +# DELETE http://localhost:1337/empire/api/agents/stale removes stale agents from the database +# +# GET http://localhost:1337/empire/api/reporting return all logged events +# GET http://localhost:1337/empire/api/reporting/agent/X return all logged events for the given agent name X +# GET http://localhost:1337/empire/api/reporting/type/Y return all logged events of type Y (checkin, task, result, rename) +# GET http://localhost:1337/empire/api/reporting/msg/Z return all logged events matching message Z, wildcards accepted +# +# GET http://localhost:1337/empire/api/admin/shutdown shutdown the RESTful API +# +#################################################################### + +def start_restful_api(startEmpire=False, suppress=False, port=1337): + ''' + + ''' + + app = Flask(__name__) + + class Namespace: + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + # instantiate an Empire instance in case we need to interact with stagers or listeners + args = Namespace(debug=None, listener=None, stager=None, stager_options=None, version=False) + + print "" + + if startEmpire: + # if we want to start a full-running empire instance + print " * Starting a full Empire instance" + main = empire.MainMenu(args=args) + else: + # if we just want the RESTful API, i.e. no listener/etc. startup + main = empire.MainMenu(args=args, restAPI=True) + + conn = database_connect() + + print " * Starting Empire RESTful API on port: %s" %(port) + + # refresh the token for the RESTful API + apiToken = refresh_api_token(conn) + print " * RESTful API token: %s" %(apiToken) + + tokenAllowed = re.compile("^[0-9a-z]{40}") + + oldStdout = sys.stdout + if suppress: + # suppress the normal Flask output + log = logging.getLogger('werkzeug') + log.setLevel(logging.ERROR) + + # suppress all stdout and don't initiate the main cmdloop + sys.stdout = open(os.devnull, 'w') + + + # validate API token before every request + @app.before_request + def check_token(): + token = request.args.get('token') + if (not token) or (not tokenAllowed.match(token)): + return make_response('', 403) + if token != apiToken: + return make_response('', 403) + + + @app.errorhandler(Exception) + def exception_handler(error): + return repr(error) + + + @app.errorhandler(404) + def not_found(error): + return make_response(jsonify( { 'error': 'Not found' } ), 404) + + + @app.route('/empire/api/version', methods=['GET']) + def get_version(): + """ + Returns the current Empire version. + """ + return jsonify({'version': empire.VERSION}) + + + @app.route('/empire/api/config', methods=['GET']) + def get_config(): + """ + Returns JSON of the current Empire config. + """ + configRaw = execute_db_query(conn, 'SELECT * FROM config') + + [staging_key, stage0_uri, stage1_uri, stage2_uri, default_delay, default_jitter, default_profile, default_cert_path, default_port, install_path, server_version, ip_whitelist, ip_blacklist, default_lost_limit, autorun_command, autorun_data, api_token] = configRaw[0] + config = {"version":empire.VERSION, "staging_key":staging_key, "stage0_uri":stage0_uri, "stage1_uri":stage1_uri, "stage2_uri":stage2_uri, "default_delay":default_delay, "default_jitter":default_jitter, "default_profile":default_profile, "default_cert_path":default_cert_path, "default_port":default_port, "install_path":install_path, "server_version":server_version, "ip_whitelist":ip_whitelist, "ip_blacklist":ip_blacklist, "default_lost_limit":default_lost_limit, "autorun_command":autorun_command, "autorun_data":autorun_data, "api_token":api_token} + return jsonify({'config': config}) + + + @app.route('/empire/api/stagers', methods=['GET']) + def get_stagers(): + """ + Returns JSON describing all stagers. + """ + stagerInfo = {} + for stagerName,stager in main.stagers.stagers.iteritems(): + info = stager.info + info['options'] = stager.options + stagerInfo[stagerName] = info + + return jsonify({'stagers': stagerInfo}) + + + @app.route('/empire/api/stagers/', methods=['GET']) + def get_stagers_name(stager_name): + """ + Returns JSON describing the specified stager_name passed. + """ + stagerInfo = {} + for stagerName,stager in main.stagers.stagers.iteritems(): + if(stagerName == stager_name): + info = stager.info + info['options'] = stager.options + stagerInfo[stagerName] = info + + return jsonify({'stagers': stagerInfo}) + + + @app.route('/empire/api/stagers', methods=['POST']) + def generate_stager(): + """ + Generates a stager with the supplied config and returns JSON information + describing the generated stager, with 'Output' being the stager output. + + Required JSON args: + StagerName - the stager name to generate + Listener - the Listener name to use for the stager + """ + if not request.json or not 'StagerName' in request.json or not 'Listener' in request.json: + abort(400) + + stagerName = request.json['StagerName'] + listener = request.json['Listener'] + + if stagerName not in main.stagers.stagers: + return jsonify({'error': 'StagerName invalid'}) + + if not main.listeners.is_listener_valid(listener): + return jsonify({'error': 'invalid listener ID or name'}) + + stager = main.stagers.stagers[stagerName] + + # set all passed options + for option,values in request.json.iteritems(): + if option != 'StagerName': + if(option not in stager.options): + return jsonify({'error': 'Invalid option %s, check capitalization.' %(option)}) + stager.options[option]['Value'] = values + + # validate stager options + for option,values in stager.options.iteritems(): + if values['Required'] and ((not values['Value']) or (values['Value'] == '')): + return jsonify({'error': 'required stager options missing'}) + + stagerOut = stager.options + stagerOut['Output'] = stager.generate() + + return jsonify({stagerName: stagerOut}) + + + @app.route('/empire/api/modules', methods=['GET']) + def get_modules(): + """ + Returns JSON describing all currently loaded modules. + """ + moduleInfo = {} + for moduleName,module in main.modules.modules.iteritems(): + info = module.info + info['options'] = module.options + moduleInfo[moduleName] = info + + return jsonify({'modules': moduleInfo}) + + + @app.route('/empire/api/listeners', methods=['GET']) + def get_listeners(): + """ + Returns JSON describing all currently registered listeners. + """ + activeListenersRaw = execute_db_query(conn, 'SELECT * FROM listeners') + activeListeners = {} + + for activeListener in activeListenersRaw: + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit] = activeListener + activeListeners[name] = {'ID':ID, 'name':name, 'host':host, 'port':port, 'cert_path':cert_path, 'staging_key':staging_key, 'default_delay':default_delay, 'default_jitter':default_jitter, 'default_profile':default_profile, 'kill_date':kill_date, 'working_hours':working_hours, 'listener_type':listener_type, 'redirect_target':redirect_target, 'default_lost_limit':default_lost_limit} + + return jsonify({'listeners' : activeListeners}) + + + @app.route('/empire/api/listeners/', methods=['GET']) + def get_listener_name(listener_name): + """ + Returns JSON describing the listener specified by listener_name. + """ + activeListenersRaw = execute_db_query(conn, 'SELECT * FROM listeners') + activeListeners = {} + + for activeListener in activeListenersRaw: + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit] = activeListener + if name == listener_name: + activeListeners[name] = {'ID':ID, 'name':name, 'host':host, 'port':port, 'cert_path':cert_path, 'staging_key':staging_key, 'default_delay':default_delay, 'default_jitter':default_jitter, 'default_profile':default_profile, 'kill_date':kill_date, 'working_hours':working_hours, 'listener_type':listener_type, 'redirect_target':redirect_target, 'default_lost_limit':default_lost_limit} + + return jsonify({'listeners' : activeListeners}) + + + @app.route('/empire/api/listeners/', methods=['DELETE']) + def kill_listener(listener_name): + """ + Kills the listener specified by listener_name. + """ + + if listener_name.lower() == "all": + activeListenersRaw = execute_db_query(conn, 'SELECT * FROM listeners') + for activeListener in activeListenersRaw: + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit] = activeListener + main.listeners.shutdown_listener(name) + main.listeners.delete_listener(name) + + return jsonify({'result': True}) + else: + if listener_name != "" and main.listeners.is_listener_valid(listener_name): + main.listeners.shutdown_listener(listener_name) + main.listeners.delete_listener(listener_name) + return jsonify({'result': True}) + else: + return jsonify({'error': 'invalid listener name: %s' %(listener_name)}) + + + @app.route('/empire/api/listeners/options', methods=['GET']) + def get_listener_options(): + """ + Returns JSON describing the current listener options. + """ + return jsonify({'ListenerOptions' : main.listeners.options}) + + + @app.route('/empire/api/listeners', methods=['POST']) + def start_listener(): + """ + Starts a listener with options supplied in the POST. + """ + + # set all passed options + for option,values in request.json.iteritems(): + returnVal = main.listeners.set_listener_option(option, values) + if not returnVal: + return jsonify({'error': 'Error setting listener value %s with option %s' %(option, values)}) + + valid = main.listeners.validate_listener_options() + if not valid: + return jsonify({'error': 'Error validating listener options'}) + + main.listeners.add_listener_from_config() + return jsonify({'result': True}) + + + @app.route('/empire/api/agents', methods=['GET']) + def get_agents(): + """ + Returns JSON describing all currently registered agents. + """ + activeAgentsRaw = execute_db_query(conn, 'SELECT * FROM agents') + activeAgents = {} + + for activeAgent in activeAgentsRaw: + [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = activeAgent + activeAgents[name] = {"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results} + + return jsonify({'agents' : activeAgents}) + + + @app.route('/empire/api/agents/', methods=['GET']) + def get_agents_name(agent_name): + """ + Returns JSON describing the agent specified by agent_name. + """ + activeAgentsRaw = execute_db_query(conn, 'SELECT * FROM agents WHERE name=? OR session_id=?', [agent_name, agent_name]) + activeAgents = {} + + for activeAgent in activeAgentsRaw: + [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = activeAgent + activeAgents[name] = {"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results} + + return jsonify({'agents' : activeAgents}) + + + @app.route('/empire/api/agents//results', methods=['GET']) + def get_agent_results(agent_name): + """ + Returns JSON describing the agent's results and removes the result field + from the backend database. + """ + + agentResults = execute_db_query(conn, 'SELECT results FROM agents WHERE name=? OR session_id=?', [agent_name, agent_name])[0] + + if agentResults and agentResults[0] and agentResults[0] != '': + out = json.loads(agentResults[0]) + if(out): + agentResults = "\n".join(out) + else: + agentResults = '' + else: + agentResults = '' + + execute_db_query(conn, 'UPDATE agents SET results=? WHERE name=? OR session_id=?', ['', agent_name, agent_name]) + + return jsonify({agent_name : {'Results': agentResults}}) + + + # TODO: add get /name/results to get/clear results from DB + @app.route('/empire/api/agents/', methods=['POST']) + def modify_agent(agent_name): + """ + Modifies an agent with name agent_name. + Used for tasking, clearing tasking, setting sleep, renaming, and killing. + """ + + if 'Task' in request.json.keys(): + + if agent_name.lower() == "all": + agent_name = '%' + + taskName = request.json['Task']['TaskName'] + task = request.json['Task']['Task'] + + # get existing agent taskings + agentTasks = execute_db_query(conn, 'SELECT taskings FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name])[0] + + if(agentTasks and agentTasks[0]): + agentTasks = json.loads(agentTasks[0]) + else: + agentTasks = [] + + # append our new json-ified task and update the backend + agentTasks.append([taskName, task]) + execute_db_query(conn, "UPDATE agents SET taskings=? WHERE name=? OR session_id=?", [json.dumps(agentTasks), agent_name, agent_name]) + + timeStamp = strftime("%Y-%m-%d %H:%M:%S", localtime()) + execute_db_query(conn, "INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (agent_name,"task",taskName + " - " + task[0:50], timeStamp )) + return jsonify({'AgentName':agent_name, 'TaskType':'Task', 'TaskName':taskName, 'Task':task}) + + + elif 'Clear' in request.json.keys(): + + if agent_name.lower() == "all": + agent_name = '%' + + execute_db_query(conn, "UPDATE agents SET taskings=? WHERE name like ? OR session_id like ?", ['', agent_name, agent_name]) + + return jsonify({'AgentName':agent_name, 'TaskType':'Clear', 'TaskName':'', 'Task':''}) + + + elif 'Rename' in request.json.keys(): + oldName = request.json['Rename']['OldName'] + newName = request.json['Rename']['NewName'] + + try: + main.agents.rename_agent(oldName, newName) + return jsonify({'result': True}) + except: + return jsonify({'error': 'error in renaming %s to %s' %(oldName, newName)}) + + + return jsonify({'error':'error in tasking agent %s' % (agent_name)}) + + + @app.route('/empire/api/agents/', methods=['DELETE']) + def remove_agent(agent_name): + """ + Removes an agent from the controller specified by agent_name. + WARNING: doesn't kill the agent first! Ensure the agent is dead. + """ + if agent_name.lower() == "all": + agent_name = '%' + + agentsRaw = execute_db_query(conn, 'SELECT * FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name]) + removedAgents = {} + + for agent in agentsRaw: + [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = agent + execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [sessionID]) + + removedAgents[name] = {"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results} + + return jsonify({'RemovedAgents': removedAgents}) + + + @app.route('/empire/api/agents/stale', methods=['DELETE']) + def remove_stale_agent(): + """ + Removes stale agents from the controller. + WARNING: doesn't kill the agent first! Ensure the agent is dead. + """ + agentsRaw = execute_db_query(conn, 'SELECT * FROM agents') + removedAgents = {} + + for agent in agentsRaw: + [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = agent + + intervalMax = (delay + delay * jitter)+30 + + # get the agent last check in time + agentTime = time.mktime(time.strptime(lastseen_time, "%Y-%m-%d %H:%M:%S")) + + if agentTime < time.mktime(time.localtime()) - intervalMax: + execute_db_query(conn, "DELETE FROM agents WHERE session_id LIKE ?", [sessionID]) + + removedAgents[name] = {"ID":ID, "sessionID":sessionID, "listener":listener, "name":name, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "uris":uris, "old_uris":old_uris, "user_agent":user_agent, "headers":headers, "functions":functions, "kill_date":kill_date, "working_hours":working_hours, "ps_version":ps_version, "lost_limit":lost_limit, "taskings":taskings, "results":results} + + return jsonify({'RemovedAgents': removedAgents}) + + + @app.route('/empire/api/reporting', methods=['GET']) + def get_reporting(): + """ + Returns JSON describing the reporting events from the backend database. + """ + reportingRaw = execute_db_query(conn, 'SELECT * FROM reporting') + reportingEvents = {} + + for reportingEvent in reportingRaw: + [ID, name, eventType, message, timestamp] = reportingEvent + reportingEvents[ID] = {"ID":ID, "name":name, "event_type":eventType, "message":message, "timestamp":timestamp} + + return jsonify({'reporting' : reportingEvents}) + + + @app.route('/empire/api/reporting/agent/', methods=['GET']) + def get_reporting_agent(reporting_agent): + """ + Returns JSON describing the reporting events from the backend database for + the agent specified by reporting_agent. + """ + + # first resolve the supplied name to a sessionID + results = execute_db_query(conn, 'SELECT session_id FROM agents WHERE name=?', [reporting_agent]) + if(results): + sessionID = results[0][0] + else: + return jsonify({'reporting' : ''}) + + reportingRaw = execute_db_query(conn, 'SELECT * FROM reporting WHERE name=?', [sessionID]) + reportingEvents = {} + + for reportingEvent in reportingRaw: + [ID, name, eventType, message, timestamp] = reportingEvent + reportingEvents[ID] = {"ID":ID, "name":name, "event_type":eventType, "message":message, "timestamp":timestamp} + + return jsonify({'reporting' : reportingEvents}) + + + @app.route('/empire/api/reporting/type/', methods=['GET']) + def get_reporting_type(event_type): + """ + Returns JSON describing the reporting events from the backend database for + the event type specified by event_type. + """ + reportingRaw = execute_db_query(conn, 'SELECT * FROM reporting WHERE event_type=?', [event_type]) + reportingEvents = {} + + for reportingEvent in reportingRaw: + [ID, name, eventType, message, timestamp] = reportingEvent + reportingEvents[ID] = {"ID":ID, "name":name, "event_type":eventType, "message":message, "timestamp":timestamp} + + return jsonify({'reporting' : reportingEvents}) + + + @app.route('/empire/api/reporting/msg/', methods=['GET']) + def get_reporting_msg(msg): + """ + Returns JSON describing the reporting events from the backend database for + the any messages with *msg* specified by msg. + """ + reportingRaw = execute_db_query(conn, "SELECT * FROM reporting WHERE message like ?", ['%'+msg+'%']) + reportingEvents = {} + + for reportingEvent in reportingRaw: + [ID, name, eventType, message, timestamp] = reportingEvent + reportingEvents[ID] = {"ID":ID, "name":name, "event_type":eventType, "message":message, "timestamp":timestamp} + + return jsonify({'reporting' : reportingEvents}) + + + @app.route('/empire/api/admin/shutdown', methods=['GET', 'POST', 'PUT']) + def shutdown_server(): + """ + Signal a shutdown for the Flask server and any Empire server. + """ + shutdown_server() + return jsonify({'result': True}) + + + if not os.path.exists('./data/empire.pem'): + print "[!] Error: cannot find certificate ./data/empire.pem" + sys.exit() + + + def shutdown_server(): + """ + Shut down the Flask server and any Empire server gracefully. + """ + if suppress: + # repair stdout + sys.stdout.close() + sys.stdout = oldStdout + + print "\n[*]Shutting down Empire RESTful API" + + func = request.environ.get('werkzeug.server.shutdown') + if func is not None: + func() + + if conn: conn.close() + + if startEmpire: + print "Shutting down the Empire instance" + main.shutdown() + + + # override the keyboardinterrupt signal handler so we can gracefully shut everything down + def signal_handler(signal, frame): + + with app.test_request_context(): + shutdown_server() + sys.exit(0) + + + signal.signal(signal.SIGINT, signal_handler) + + # wrap the Flask connection in SSL and start it + context = ('./data/empire.pem', './data/empire.pem') + app.run(host='0.0.0.0', port=port, ssl_context=context, threaded=True) + if __name__ == '__main__': @@ -19,11 +643,25 @@ if __name__ == '__main__': parser.add_argument('-o', '--stager-options', nargs='*', help="Supply options to set for a stager in OPTION=VALUE format. Lists options if nothing is specified.") parser.add_argument('-l', '--listener', nargs='?', const="list", help='Display listener options. Displays all listeners if nothing is specified.') parser.add_argument('-v', '--version', action='store_true', help='Display current Empire version.') + parser.add_argument('--rest', action='store_true', help='Run the Empire RESTful API.') + parser.add_argument('--headless', action='store_true', help='Run Empire and the RESTful API headless without the usual interface.') args = parser.parse_args() if args.version: print empire.VERSION + + elif args.rest: + # start just the RESTful API + start_restful_api(startEmpire=False, suppress=False, port=1337) + + elif args.headless: + # start an Empire instance and RESTful API and suppress output + start_restful_api(startEmpire=True, suppress=True, port=1337) + else: + # normal execution main = empire.MainMenu(args=args) main.cmdloop() + + sys.exit() diff --git a/lib/common/agents.py b/lib/common/agents.py index 29dd976..8c46491 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -12,12 +12,7 @@ the response types are handled as appropriate. """ from pydispatch import dispatcher -import sqlite3 -import pickle -import base64 -import string -import os -import iptools +import sqlite3, pickle, base64, string, os, iptools, json # Empire imports import encryption @@ -40,31 +35,28 @@ class Agents: self.installPath = self.mainMenu.installPath self.args = args - - # internal agent dictionary for the client's session key and tasking/result sets - # self.agents[sessionID] = [ clientSessionKey, - # [tasking1, tasking2, ...], - # [results1, results2, ...], - # [tab-completable function names for a script-import], - # current URIs, - # old URIs - # ] + + # internal agent dictionary for the client's session key, funcions, and URI sets + # this is done to prevent database reads for extremely common tasks (like checking tasking URI existence) + # self.agents[sessionID] = { 'sessionKey' : clientSessionKey, + # 'functions' : [tab-completable function names for a script-import], + # 'currentURIs' : [current URIs used by the client], + # 'oldURIs' : [old URIs used by the client] + # } self.agents = {} # reinitialize any agents that already exist in the database agentIDs = self.get_agent_ids() for agentID in agentIDs: - sessionKey = self.get_agent_session_key(agentID) - functions = self.get_agent_functions_database(agentID) + self.agents[agentID] = {} + self.agents[agentID]['sessionKey'] = self.get_agent_session_key(agentID) + self.agents[agentID]['functions'] = self.get_agent_functions_database(agentID) # get the current and previous URIs for tasking - uris,old_uris = self.get_agent_uris(agentID) - - if not old_uris: - old_uris = "" - - # [sessionKey, taskings, results, stored_functions, tasking uris, old uris] - self.agents[agentID] = [sessionKey, [], [], functions, uris, old_uris] + currentURIs,oldURIs = self.get_agent_uris(agentID) + if not oldURIs: oldURIs = '' + self.agents[agentID]['currentURIs'] = currentURIs + self.agents[agentID]['oldURIs'] = oldURIs # pull out common configs from the main menu object in empire.py self.ipWhiteList = self.mainMenu.ipWhiteList @@ -92,13 +84,13 @@ class Agents: # remove the agent from the internal cache self.agents.pop(sessionID, None) - # remove an agent from the database + # remove the agent from the database cur = self.conn.cursor() - cur.execute("DELETE FROM agents WHERE session_id like ?", [sessionID]) + cur.execute("DELETE FROM agents WHERE session_id LIKE ?", [sessionID]) cur.close() - def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours,lostLimit): + def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours, lostLimit): """ Add an agent to the internal cache and database. """ @@ -133,7 +125,8 @@ class Agents: # initialize the tasking/result buffers along with the client session key sessionKey = self.get_agent_session_key(sessionID) - self.agents[sessionID] = [sessionKey, [],[],[], requestUris, ""] + # TODO: should oldURIs be a string or list? + self.agents[sessionID] = {'sessionKey':sessionKey, 'functions':[], 'currentURIs':requestUris, 'oldURIs': ''} # report the initial checkin in the reporting database cur = self.conn.cursor() @@ -159,7 +152,7 @@ class Agents: """ for option,values in self.agents.iteritems(): - if resource in values[-1] or resource in values [-2]: + if resource in values['currentURIs'] or resource in values['oldURIs']: return True return False @@ -283,7 +276,6 @@ class Agents: # ############################################################### - def get_agents(self): """ Return all active agents from the database. @@ -388,7 +380,7 @@ class Agents: if ps_version and ps_version != None: if type(ps_version) is str: - return sessionKey + return ps_version else: return ps_version[0] @@ -416,7 +408,7 @@ class Agents: def get_agent_results(self, sessionID): """ - Get the agent's results buffer. + Return agent results from the backend database. """ agentName = sessionID @@ -426,11 +418,21 @@ class Agents: if nameid : sessionID = nameid if sessionID not in self.agents: - print helpers.color("[!] Agent " + str(agentName) + " not active.") + print helpers.color("[!] Agent %s not active." %(agentName)) else: - results = self.agents[sessionID][2] - self.agents[sessionID][2] = [] - return "\n".join(results) + cur = self.conn.cursor() + cur.execute("SELECT results FROM agents WHERE session_id=?", [sessionID]) + results = cur.fetchone() + + cur.execute("UPDATE agents SET results = ? WHERE session_id=?", ['',sessionID]) + + if results and results[0] and results[0] != '': + out = json.loads(results[0]) + if(out): + return "\n".join(out) + else: + return '' + cur.close() def get_agent_id(self, name): @@ -474,6 +476,7 @@ class Agents: else: return None + def get_agent_functions(self, sessionID): """ Get the tab-completable functions for an agent. @@ -484,7 +487,7 @@ class Agents: if nameid : sessionID = nameid if sessionID in self.agents: - return self.agents[sessionID][3] + return self.agents[sessionID]['functions'] else: return [] @@ -552,6 +555,7 @@ class Agents: except: pass + ############################################################### # # Methods to update agent information fields. @@ -568,7 +572,21 @@ class Agents: if nameid : sessionID = nameid if sessionID in self.agents: - self.agents[sessionID][2].append(results) + cur = self.conn.cursor() + + # get existing agent results + cur.execute("SELECT results FROM agents WHERE session_id LIKE ?", [sessionID]) + agentResults = cur.fetchone() + + if(agentResults and agentResults[0]): + agentResults = json.loads(agentResults[0]) + else: + agentResults = [] + + agentResults.append(results) + + cur.execute("UPDATE agents SET results = ? WHERE session_id=?", [json.dumps(agentResults),sessionID]) + cur.close() else: dispatcher.send("[!] Non-existent agent " + str(sessionID) + " returned results", sender="Agents") @@ -615,8 +633,8 @@ class Agents: cur = self.conn.cursor() # get the existing URIs from the agent and save them to - # the old_uris field, so we can ensure that it can check in - # to get the new URI tasking... bootstrapping problem :) + # the old_uris field, so we can ensure that it can check in + # to get the new URI tasking... bootstrapping problem :) cur.execute("SELECT uris FROM agents WHERE session_id=?", [sessionID]) oldURIs = cur.fetchone()[0] @@ -624,9 +642,8 @@ class Agents: print helpers.color("[!] Agent " + agentName + " not active.") else: # update the URIs in the cache - self.agents[sessionID][-1] = oldURIs - # new URIs - self.agents[sessionID][-2] = parts[0] + self.agents[sessionID]['oldURIs'] = oldURIs + self.agents[sessionID]['currentURIs'] = parts[0] # if no additional headers if len(parts) == 2: @@ -696,7 +713,7 @@ class Agents: if nameid : sessionID = nameid if sessionID in self.agents: - self.agents[sessionID][3] = functions + self.agents[sessionID]['functions'] = functions functions = ",".join(functions) @@ -756,8 +773,22 @@ class Agents: print helpers.color("[!] Agent " + str(agentName) + " not active.") else: if sessionID: + dispatcher.send("[*] Tasked " + str(sessionID) + " to run " + str(taskName), sender="Agents") - self.agents[sessionID][1].append([taskName, task]) + + # get existing agent taskings + cur = self.conn.cursor() + cur.execute("SELECT taskings FROM agents WHERE session_id=?", [sessionID]) + agentTasks = cur.fetchone() + + if(agentTasks and agentTasks[0]): + agentTasks = json.loads(agentTasks[0]) + else: + agentTasks = [] + + # append our new json-ified task and update the backend + agentTasks.append([taskName, task]) + cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agentTasks),sessionID]) # write out the last tasked script to "LastTask.ps1" if in debug mode if self.args and self.args.debug: @@ -766,8 +797,7 @@ class Agents: f.close() # report the agent tasking in the reporting database - cur = self.conn.cursor() - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (sessionID,"task",taskName + " - " + task[0:30],helpers.get_datetime())) + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (sessionID,"task",taskName + " - " + task[0:50],helpers.get_datetime())) cur.close() @@ -786,47 +816,37 @@ class Agents: print helpers.color("[!] Agent " + str(agentName) + " not active.") return [] else: - tasks = self.agents[sessionID][1] - # clear the taskings out - self.agents[sessionID][1] = [] + + cur = self.conn.cursor() + cur.execute("SELECT taskings FROM agents WHERE session_id=?", [sessionID]) + tasks = cur.fetchone() + + if(tasks and tasks[0]): + tasks = json.loads(tasks[0]) + + # clear the taskings out + cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", ['', sessionID]) + else: + tasks = [] + + cur.close() + return tasks - def get_agent_task(self, sessionID): - """ - Pop off the agent's top task. - """ - - # see if we were passed a name instead of an ID - nameid = self.get_agent_id(sessionID) - if nameid : sessionID = nameid - - try: - # pop the first task off the front of the stack - return self.agents[sessionID][1].pop(0) - except: - [] - - def clear_agent_tasks(self, sessionID): """ - Clear out the agent's task buffer. + Clear out one (or all) agent's task buffer. """ agentName = sessionID if sessionID.lower() == "all": - for option,values in self.agents.iteritems(): - self.agents[option][1] = [] - else: - # see if we were passed a name instead of an ID - nameid = self.get_agent_id(sessionID) - if nameid : sessionID = nameid + sessionID = '%' - if sessionID not in self.agents: - print helpers.color("[!] Agent " + agentName + " not active.") - else: - self.agents[sessionID][1] = [] + cur = self.conn.cursor() + cur.execute("UPDATE agents SET taskings=? WHERE session_id LIKE ?", ['', sessionID]) + cur.close() def handle_agent_response(self, sessionID, responseName, data): @@ -1122,7 +1142,7 @@ class Agents: allTaskPackets += taskPacket # get the session key for the agent - sessionKey = self.agents[sessionID][0] + sessionKey = self.agents[sessionID]['sessionKey'] # encrypt the tasking packets with the agent's session key encryptedData = encryption.aes_encrypt_then_mac(sessionKey, allTaskPackets) @@ -1199,7 +1219,7 @@ class Agents: else: # extract the agent's session key - sessionKey = self.agents[sessionID][0] + sessionKey = self.agents[sessionID]['sessionKey'] try: # verify, decrypt and depad the packet @@ -1241,7 +1261,7 @@ class Agents: return (404, "") except Exception as e: - dispatcher.send("[!] Error processing result packet from "+str(sessionID), sender="Agents") + dispatcher.send("[!] Error processing result packet from %s : %s" %(str(sessionID),e), sender="Agents") return (404, "") @@ -1330,7 +1350,7 @@ class Agents: lostLimit = config[11] # get the session key for the agent - sessionKey = self.agents[sessionID][0] + sessionKey = self.agents[sessionID]['sessionKey'] try: # decrypt and parse the agent's sysinfo checkin diff --git a/lib/common/empire.py b/lib/common/empire.py index 1986a9f..4ea85dc 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -9,7 +9,7 @@ menu loops. """ # make version for Empire -VERSION = "1.4.4" +VERSION = "1.4.6" from pydispatch import dispatcher @@ -39,7 +39,7 @@ class NavListeners(Exception): pass class MainMenu(cmd.Cmd): - def __init__(self, args=None): + def __init__(self, args=None, restAPI=False): cmd.Cmd.__init__(self) @@ -49,42 +49,8 @@ class MainMenu(cmd.Cmd): # empty database object self.conn = self.database_connect() - # grab the universal install path - # TODO: combine these into one query - cur = self.conn.cursor() - cur.execute("SELECT install_path FROM config") - self.installPath = cur.fetchone()[0] - cur.close() - - # pull out the stage0 uri - cur = self.conn.cursor() - cur.execute("SELECT stage0_uri FROM config") - self.stage0 = cur.fetchone()[0] - cur.close() - - # pull out the stage1 uri - cur = self.conn.cursor() - cur.execute("SELECT stage1_uri FROM config") - self.stage1 = cur.fetchone()[0] - cur.close() - - # pull out the stage2 uri - cur = self.conn.cursor() - cur.execute("SELECT stage2_uri FROM config") - self.stage2 = cur.fetchone()[0] - cur.close() - - # pull out the IP whitelist and create it, if applicable - cur = self.conn.cursor() - cur.execute("SELECT ip_whitelist FROM config") - self.ipWhiteList = helpers.generate_ip_list(cur.fetchone()[0]) - cur.close() - - # pull out the IP blacklist and create it, if applicable - cur = self.conn.cursor() - cur.execute("SELECT ip_blacklist FROM config") - self.ipBlackList = helpers.generate_ip_list(cur.fetchone()[0]) - cur.close() + # pull out some common configuration information + (self.installPath, self.stage0, self.stage1, self.stage2, self.ipWhiteList, self.ipBlackList) = helpers.get_config('install_path,stage0_uri,stage1_uri,stage2_uri,ip_whitelist,ip_blacklist') # instantiate the agents, listeners, and stagers objects self.agents = agents.Agents(self, args=args) @@ -94,7 +60,6 @@ class MainMenu(cmd.Cmd): self.credentials = credentials.Credentials(self, args=args) # make sure all the references are passed after instantiation - # TODO: replace these with self? self.agents.listeners = self.listeners self.agents.modules = self.modules self.agents.stagers = self.stagers @@ -116,8 +81,9 @@ class MainMenu(cmd.Cmd): self.args = args self.handle_args() - # start everything up normally - self.startup() + # start everything up normally if the RESTful API isn't being launched + if not restAPI: + self.startup() def handle_args(self): @@ -252,7 +218,7 @@ class MainMenu(cmd.Cmd): else: num_modules = 0 - num_listeners = self.listeners.listeners + num_listeners = self.listeners.get_listeners() if(num_listeners): num_listeners = len(num_listeners) else: @@ -407,6 +373,7 @@ class MainMenu(cmd.Cmd): except Exception as e: raise e + def do_usemodule(self, line): "Use an Empire module." if line not in self.modules.modules: @@ -418,6 +385,7 @@ class MainMenu(cmd.Cmd): except Exception as e: raise e + def do_searchmodule(self, line): "Search Empire module names/descriptions." self.modules.search_modules(line.strip()) @@ -748,7 +716,7 @@ class AgentsMenu(cmd.Cmd): # traceback.print_stack() # print a nicely formatted help menu - # stolen/adapted from recon-ng + # stolen/adapted from recon-ng def print_topics(self, header, cmds, cmdlen, maxcol): if cmds: self.stdout.write("%s\n"%str(header)) @@ -763,8 +731,13 @@ class AgentsMenu(cmd.Cmd): def do_back(self, line): - "Return back a menu." - return True + "Go back to the main menu." + raise NavMain() + + + def do_listeners(self, line): + "Jump to the listeners menu." + raise NavListeners() def do_main(self, line): @@ -796,8 +769,6 @@ class AgentsMenu(cmd.Cmd): # name sure we get an old name and new name for the agent if len(parts) == 2: # replace the old name with the new name - oldname = parts[0] - newname = parts[1] self.mainMenu.agents.rename_agent(parts[0], parts[1]) else: print helpers.color("[!] Please enter an agent name and new name") @@ -1107,11 +1078,6 @@ class AgentsMenu(cmd.Cmd): print helpers.color("[!] Invalid agent name") - def do_listeners(self, line): - "Jump to the listeners menu." - raise NavListeners() - - def do_usestager(self, line): "Use an Empire stager." @@ -1334,9 +1300,9 @@ class AgentMenu(cmd.Cmd): return True - def do_main(self, line): - "Go back to the main menu." - raise NavMain() + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() def do_listeners(self, line): @@ -1344,9 +1310,9 @@ class AgentMenu(cmd.Cmd): raise NavListeners() - def do_agents(self, line): - "Jump to the Agents menu." - raise NavAgents() + def do_main(self, line): + "Go back to the main menu." + raise NavMain() def do_help(self, *args): @@ -1538,7 +1504,6 @@ class AgentMenu(cmd.Cmd): self.mainMenu.agents.save_agent_log(self.sessionID, msg) - def do_shell(self, line): "Task an agent to use a shell command." @@ -2042,11 +2007,6 @@ class ListenerMenu(cmd.Cmd): def emptyline(self): pass - def do_exit(self, line): - "Exit Empire." - raise KeyboardInterrupt - - def do_list(self, line): "List all active listeners (or agents)." @@ -2059,8 +2019,13 @@ class ListenerMenu(cmd.Cmd): def do_back(self, line): - "Go back a menu." - return True + "Go back to the main menu." + raise NavMain() + + + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() def do_main(self, line): @@ -2068,6 +2033,11 @@ class ListenerMenu(cmd.Cmd): raise NavMain() + def do_exit(self, line): + "Exit Empire." + raise KeyboardInterrupt + + def do_set(self, line): "Set a listener option." parts = line.split(" ") @@ -2158,11 +2128,6 @@ class ListenerMenu(cmd.Cmd): self.do_execute(line) - def do_agents(self, line): - "Jump to the Agents menu." - raise NavAgents() - - def do_usestager(self, line): "Use an Empire stager." @@ -2370,6 +2335,11 @@ class ModuleMenu(cmd.Cmd): self.stdout.write("\n") + def do_back(self, line): + "Go back a menu." + return True + + def do_agents(self, line): "Jump to the Agents menu." raise NavAgents() @@ -2380,16 +2350,16 @@ class ModuleMenu(cmd.Cmd): raise NavListeners() + def do_main(self, line): + "Go back to the main menu." + raise NavMain() + + def do_exit(self, line): "Exit Empire." raise KeyboardInterrupt - def do_main(self, line): - "Return to the main menu." - return True - - def do_list(self, line): "Lists all active agents (or listeners)." @@ -2422,16 +2392,6 @@ class ModuleMenu(cmd.Cmd): messages.display_module(self.moduleName, self.module) - def do_back(self, line): - "Return to the main menu." - return True - - - def do_main(self, line): - "Go back to the main menu." - raise NavMain() - - def do_set(self, line): "Set a module option." @@ -2703,14 +2663,29 @@ class StagerMenu(cmd.Cmd): self.stdout.write("\n") - def do_exit(self, line): - "Exit Empire." - raise KeyboardInterrupt + def do_back(self, line): + "Go back a menu." + return True + + + def do_agents(self, line): + "Jump to the Agents menu." + raise NavAgents() + + + def do_listeners(self, line): + "Jump to the listeners menu." + raise NavListeners() def do_main(self, line): - "Return to the main menu." - return True + "Go back to the main menu." + raise NavMain() + + + def do_exit(self, line): + "Exit Empire." + raise KeyboardInterrupt def do_list(self, line): @@ -2734,16 +2709,6 @@ class StagerMenu(cmd.Cmd): messages.display_stager(self.stagerName, self.stager) - def do_back(self, line): - "Return to the main menu." - return True - - - def do_main(self, line): - "Go back to the main menu." - raise NavMain() - - def do_set(self, line): "Set a stager option." @@ -2826,7 +2791,6 @@ class StagerMenu(cmd.Cmd): def do_execute(self, line): "Generate/execute the given Empire stager." - self.do_generate(line) @@ -2862,13 +2826,3 @@ class StagerMenu(cmd.Cmd): mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in options if s.startswith(mline)] - - - def do_agents(self, line): - "Jump to the Agents menu." - raise NavAgents() - - - def do_listeners(self, line): - "Jump to the listeners menu." - raise NavListeners() diff --git a/lib/common/helpers.py b/lib/common/helpers.py index 3b5f2e3..3b36192 100644 --- a/lib/common/helpers.py +++ b/lib/common/helpers.py @@ -7,18 +7,9 @@ randomized stagers. """ +import re, string, commands, base64, binascii, sys, os, socket, sqlite3, iptools from time import localtime, strftime from Crypto.Random import random -import re -import string -import commands -import base64 -import binascii -import sys -import os -import socket -import sqlite3 -import iptools ############################################################### @@ -501,7 +492,7 @@ def get_config(fields): conn.isolation_level = None cur = conn.cursor() - cur.execute("SELECT "+fields+" FROM config") + cur.execute("SELECT %s FROM config" %(fields)) results = cur.fetchone() cur.close() conn.close() @@ -622,14 +613,6 @@ def uniquify_tuples(tuples): return [item for item in tuples if "%s%s%s%s"%(item[0],item[1],item[2],item[3]) not in seen and not seen.add("%s%s%s%s"%(item[0],item[1],item[2],item[3]))] -def urldecode(url): - """ - URL decode a string. - """ - rex=re.compile('%([0-9a-hA-H][0-9a-hA-H])',re.M) - return rex.sub(htc,url) - - def decode_base64(data): """ Try to decode a base64 string. diff --git a/lib/common/http.py b/lib/common/http.py index 1620a15..c504804 100644 --- a/lib/common/http.py +++ b/lib/common/http.py @@ -111,17 +111,18 @@ class RequestHandler(BaseHTTPRequestHandler): dispatcher.send("[*] Post to "+resource+" from "+str(sessionID)+" at "+clientIP, sender="HttpHandler") # read in the length of the POST data - length = int(self.headers.getheader('content-length')) - postData = self.rfile.read(length) + if self.headers.getheader('content-length'): + length = int(self.headers.getheader('content-length')) + postData = self.rfile.read(length) - # get the appropriate response for this agent - (code, responsedata) = self.server.agents.process_post(self.server.server_port, clientIP, sessionID, resource, postData) + # get the appropriate response for this agent + (code, responsedata) = self.server.agents.process_post(self.server.server_port, clientIP, sessionID, resource, postData) - # write the response out - self.send_response(code) - self.end_headers() - self.wfile.write(responsedata) - self.wfile.flush() + # write the response out + self.send_response(code) + self.end_headers() + self.wfile.write(responsedata) + self.wfile.flush() # self.wfile.close() # causes an error with HTTP comms # supress all the stupid default stdout/stderr output diff --git a/lib/common/listeners.py b/lib/common/listeners.py index 146de2c..0d86b1f 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -200,12 +200,15 @@ class Listeners: else: self.options['Port']['Value'] = "80" + return True + elif option == "CertPath": self.options[option]['Value'] = value host = self.options["Host"]['Value'] # if we're setting a SSL cert path, but the host is specific at http if host.startswith("http:"): self.options["Host"]['Value'] = self.options["Host"]['Value'].replace("http:", "https:") + return True elif option == "Port": self.options[option]['Value'] = value @@ -214,11 +217,13 @@ class Listeners: parts = host.split(":") if len(parts) == 2 or len(parts) == 3: self.options["Host"]['Value'] = parts[0] + ":" + parts[1] + ":" + str(value) + return True elif option == "StagingKey": # if the staging key isn't 32 characters, assume we're md5 hashing it if len(value) != 32: self.options[option]['Value'] = hashlib.md5(value).hexdigest() + return True elif option in self.options: @@ -228,9 +233,11 @@ class Listeners: # 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 + else: print helpers.color("[!] Error: invalid option name") - + return False def get_listener_options(self): """ @@ -404,7 +411,7 @@ class Listeners: if(listenerId): cur = self.conn.cursor() - cur.execute('SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners WHERE id=? or name=? limit 1', [listenerID, listenerID]) + cur.execute('SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit FROM listeners WHERE id=? or name=? limit 1', [listenerId, listenerId]) stagingInformation = cur.fetchone() cur.close() diff --git a/lib/common/messages.py b/lib/common/messages.py index 40b6646..96f8153 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -124,7 +124,7 @@ def agent_print (agents): print " --------- ----------- ------------ --------- ------- ----- --------------------" for agent in agents: - [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit] = agent + [ID, sessionID, listener, name, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, checkin_time, lastseen_time, parent, children, servers, uris, old_uris, user_agent, headers, functions, kill_date, working_hours, ps_version, lost_limit, taskings, results] = agent if str(high_integrity) == "1": # add a * to the username if it's high integrity username = "*" + username @@ -160,7 +160,7 @@ def display_agent(agent): """ # extract out database fields. - keys = ["ID", "sessionID", "listener", "name", "delay", "jitter", "external_ip", "internal_ip", "username", "high_integrity", "process_name", "process_id", "hostname", "os_details", "session_key", "checkin_time", "lastseen_time", "parent", "children", "servers", "uris", "old_uris", "user_agent", "headers", "functions", "kill_date", "working_hours", "ps_version", "lost_limit"] + keys = ["ID", "sessionID", "listener", "name", "delay", "jitter", "external_ip", "internal_ip", "username", "high_integrity", "process_name", "process_id", "hostname", "os_details", "session_key", "checkin_time", "lastseen_time", "parent", "children", "servers", "uris", "old_uris", "user_agent", "headers", "functions", "kill_date", "working_hours", "ps_version", "lost_limit", "takings", "results"] print helpers.color("\n[*] Agent info:\n") @@ -168,7 +168,7 @@ def display_agent(agent): agentInfo = dict(zip(keys, agent)) for key in agentInfo: - if key != "functions": + if key != "functions" and key != "takings" and key != "results": print "\t%s\t%s" % (helpers.color('{0: <16}'.format(key), "blue"), wrap_string(agentInfo[key], width=70)) print "" diff --git a/setup/install.sh b/setup/install.sh index a01d06f..5dc9aeb 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -14,6 +14,7 @@ if lsb_release -d | grep -q "Fedora"; then pip install pycrypto pip install iptools pip install pydispatcher + pip install flask elif lsb_release -d | grep -q "Kali"; then Release=Kali apt-get install python-dev @@ -23,6 +24,7 @@ elif lsb_release -d | grep -q "Kali"; then pip install pycrypto pip install iptools pip install pydispatcher + pip install flask elif lsb_release -d | grep -q "Ubuntu"; then Release=Ubuntu apt-get install python-dev @@ -31,6 +33,7 @@ elif lsb_release -d | grep -q "Ubuntu"; then pip install pycrypto pip install iptools pip install pydispatcher + pip install flask else echo "Unknown distro - Debian/Ubuntu Fallback" apt-get install python-dev @@ -39,6 +42,7 @@ else pip install pycrypto pip install iptools pip install pydispatcher + pip install flask fi # set up the database schema diff --git a/setup/setup_database.py b/setup/setup_database.py index e07ca55..15f26b1 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -104,11 +104,12 @@ c.execute('''CREATE TABLE config ( "ip_blacklist" text, "default_lost_limit" integer, "autorun_command" text, - "autorun_data" text + "autorun_data" text, + "api_token" text )''') # kick off the config component of the database -c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (STAGING_KEY,STAGE0_URI,STAGE1_URI,STAGE2_URI,DEFAULT_DELAY,DEFAULT_JITTER,DEFAULT_PROFILE,DEFAULT_CERT_PATH,DEFAULT_PORT,INSTALL_PATH,SERVER_VERSION,IP_WHITELIST,IP_BLACKLIST, DEFAULT_LOST_LIMIT, "", "")) +c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?)", (STAGING_KEY,STAGE0_URI,STAGE1_URI,STAGE2_URI,DEFAULT_DELAY,DEFAULT_JITTER,DEFAULT_PROFILE,DEFAULT_CERT_PATH,DEFAULT_PORT,INSTALL_PATH,SERVER_VERSION,IP_WHITELIST,IP_BLACKLIST, DEFAULT_LOST_LIMIT, "", "", "")) c.execute('''CREATE TABLE "agents" ( "id" integer PRIMARY KEY, @@ -139,7 +140,9 @@ c.execute('''CREATE TABLE "agents" ( "kill_date" text, "working_hours" text, "ps_version" text, - "lost_limit" integer + "lost_limit" integer, + "taskings" text, + "results" text )''') c.execute('''CREATE TABLE "listeners" (