From e59844be72a591146ba9c995814b4afcace201f4 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 22 Nov 2015 17:25:28 -0500 Subject: [PATCH 1/2] Added ability to set a script to run on each agent checkin with "set Agent autorun" in module menu. "(Empire: agents) > clear autorun" will clear out any current autoruns WARNING: this requires a DB schema mod to work correctly, meaning you will lose current agent connection information if run! --- lib/common/agents.py | 142 ++++++++++++++++++++++++++++------------ lib/common/empire.py | 16 +++-- setup/setup_database.py | 6 +- 3 files changed, 117 insertions(+), 47 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index a3f16b1..d51399b 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -474,7 +474,83 @@ class Agents: else: return None + def get_agent_functions(self, sessionID): + """ + Get the tab-completable functions for an agent. + """ + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + if sessionID in self.agents: + return self.agents[sessionID][3] + else: + return [] + + + def get_agent_functions_database(self, sessionID): + """ + Get the tab-completable functions for an agent from the database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT functions FROM agents WHERE session_id=?", [sessionID]) + functions = cur.fetchone()[0] + cur.close() + if functions and functions != None: + return functions.split(",") + else: + return [] + + + def get_agent_uris(self, sessionID): + """ + Get the current and old URIs for an agent from the database. + """ + + # see if we were passed a name instead of an ID + nameid = self.get_agent_id(sessionID) + if nameid : sessionID = nameid + + cur = self.conn.cursor() + cur.execute("SELECT uris, old_uris FROM agents WHERE session_id=?", [sessionID]) + uris = cur.fetchone() + cur.close() + + return uris + + + def get_autoruns(self): + """ + Get any global script autoruns. + """ + + try: + cur = self.conn.cursor() + cur.execute("SELECT autorun_command FROM config") + results = cur.fetchone() + if results: + autorunCommand = results[0] + else: + autorunCommand = '' + + cur = self.conn.cursor() + cur.execute("SELECT autorun_data FROM config") + results = cur.fetchone() + if results: + autorunData = results[0] + else: + autorunData = '' + cur.close() + + return [autorunCommand, autorunData] + except: + pass ############################################################### # @@ -629,55 +705,34 @@ class Agents: cur.close() - def get_agent_functions(self, sessionID): + def set_autoruns(self, taskCommand, moduleData): """ - Get the tab-completable functions for an agent. + Set the global script autorun in the config. """ - # see if we were passed a name instead of an ID - nameid = self.get_agent_id(sessionID) - if nameid : sessionID = nameid - - if sessionID in self.agents: - return self.agents[sessionID][3] - else: - return [] + try: + cur = self.conn.cursor() + cur.execute("UPDATE config SET autorun_command=?", [taskCommand]) + cur.execute("UPDATE config SET autorun_data=?", [moduleData]) + cur.close() + except: + print helpers.color("[!] Error: script autoruns not a database field, run ./setup_database.py to reset DB schema.") + print helpers.color("[!] Warning: this will reset ALL agent connections!") - def get_agent_functions_database(self, sessionID): + def clear_autoruns(self): """ - Get the tab-completable functions for an agent from the database. + Clear the currently set global script autoruns in the config. """ - # see if we were passed a name instead of an ID - nameid = self.get_agent_id(sessionID) - if nameid : sessionID = nameid - - cur = self.conn.cursor() - cur.execute("SELECT functions FROM agents WHERE session_id=?", [sessionID]) - functions = cur.fetchone()[0] - cur.close() - if functions and functions != None: - return functions.split(",") - else: - return [] - - - def get_agent_uris(self, sessionID): - """ - Get the current and old URIs for an agent from the database. - """ - - # see if we were passed a name instead of an ID - nameid = self.get_agent_id(sessionID) - if nameid : sessionID = nameid - - cur = self.conn.cursor() - cur.execute("SELECT uris, old_uris FROM agents WHERE session_id=?", [sessionID]) - uris = cur.fetchone() - cur.close() - - return uris + try: + cur = self.conn.cursor() + cur.execute("UPDATE config SET autorun_command=''") + cur.execute("UPDATE config SET autorun_data=''") + cur.close() + except: + print helpers.color("[!] Error: script autoruns not a database field, run ./setup_database.py to reset DB schema.") + print helpers.color("[!] Warning: this will reset ALL agent connections!") ############################################################### @@ -1338,6 +1393,11 @@ class Agents: # save the initial sysinfo information in the agent log self.save_agent_log(sessionID, output + "\n") + # if a script autorun is set, set that as the agent's first tasking + autorun = self.get_autoruns() + if autorun[0] != '' and autorun[1] != '': + self.add_agent_task(sessionID, autorun[0], autorun[1]) + return(200, encryptedAgent) else: diff --git a/lib/common/empire.py b/lib/common/empire.py index 3e760cb..d53a548 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -779,6 +779,8 @@ class AgentsMenu(cmd.Cmd): if name.lower() == "all": self.mainMenu.agents.clear_agent_tasks("all") + elif name.lower() == "autorun": + self.mainMenu.agents.clear_autoruns() else: # extract the sessionID and clear the agent tasking sessionID = self.mainMenu.agents.get_agent_id(name) @@ -1100,7 +1102,7 @@ class AgentsMenu(cmd.Cmd): def complete_clear(self, text, line, begidx, endidx): "Tab-complete a clear command" - names = self.mainMenu.agents.get_agent_names() + ["all"] + names = self.mainMenu.agents.get_agent_names() + ["all", "autorun"] mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in names if s.startswith(mline)] @@ -2221,7 +2223,7 @@ class ModuleMenu(cmd.Cmd): try: # if we're running this module for all agents, skip this validation - if sessionID.lower() != "all": + 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 @@ -2236,7 +2238,7 @@ class ModuleMenu(cmd.Cmd): # check if the module needs admin privs if self.module.info['NeedsAdmin']: # if we're running this module for all agents, skip this validation - if sessionID.lower() != "all": + if sessionID.lower() != "all" and sessionID.lower() != "autorun": if not self.mainMenu.agents.is_agent_elevated(sessionID): print helpers.color("[!] Error: module needs to run in an elevated context.") return False @@ -2459,6 +2461,12 @@ class ModuleMenu(cmd.Cmd): except KeyboardInterrupt as e: 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") + else: if not self.mainMenu.agents.is_agent_present(agentName): print helpers.color("[!] Invalid agent name.") @@ -2484,7 +2492,7 @@ class ModuleMenu(cmd.Cmd): if line.split(" ")[1].lower() == "agent": # if we're tab-completing "agent", return the agent names - agentNames = self.mainMenu.agents.get_agent_names() + agentNames = self.mainMenu.agents.get_agent_names() + ["all", "autorun"] endLine = " ".join(line.split(" ")[1:]) mline = endLine.partition(' ')[2] diff --git a/setup/setup_database.py b/setup/setup_database.py index fcceede..78486b4 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -94,11 +94,13 @@ c.execute('''CREATE TABLE config ( "server_version" text, "ip_whitelist" text, "ip_blacklist" text, - "default_lost_limit" integer + "default_lost_limit" integer, + "autorun_command" text, + "autorun_data" 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, From aa9c9e804e82e153815befdadf48ed732f84c018 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 22 Nov 2015 17:36:57 -0500 Subject: [PATCH 2/2] Added management/invoke_script --- lib/modules/management/invoke_script.py | 75 +++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 lib/modules/management/invoke_script.py diff --git a/lib/modules/management/invoke_script.py b/lib/modules/management/invoke_script.py new file mode 100644 index 0000000..a520eb9 --- /dev/null +++ b/lib/modules/management/invoke_script.py @@ -0,0 +1,75 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-Script', + + 'Author': ['@harmj0y'], + + 'Description': ('Run a custom script. Useful for mass-taskings or script autoruns.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'MinPSVersion' : '2', + + 'Comments': [] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'ScriptPath' : { + 'Description' : 'Full path to the PowerShell script.ps1 to run (on attacker machine)', + 'Required' : True, + 'Value' : '' + }, + 'ScriptCmd' : { + 'Description' : 'Script command (Invoke-X) from file to run, along with any specified arguments.', + 'Required' : True, + 'Value' : '' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + scriptPath = self.options['ScriptPath']['Value'] + scriptCmd = self.options['ScriptCmd']['Value'] + + try: + f = open(scriptPath, 'r') + except: + print helpers.color("[!] Could not read script source path at: " + str(scriptPath)) + return "" + + script = f.read() + f.close() + + script += "\n%s" %(scriptCmd) + + return script