Skip to content

Instantly share code, notes, and snippets.

@tothi
Forked from xan7r/decryptKerbTicket.py
Last active June 19, 2024 07:29
Show Gist options
  • Save tothi/b020112f8460e23bf09d59d403383086 to your computer and use it in GitHub Desktop.
Save tothi/b020112f8460e23bf09d59d403383086 to your computer and use it in GitHub Desktop.
Decrypt kerberos tickets and parse out authorization data
#!/usr/bin/env python3
# NOTE: this script was created for educational purposes to assist learning about kerberos tickets.
# Likely to have a few bugs that cause it to fail to decrypt some TGT or Service tickets.
#
# Recommended Instructions:
# Obtain valid kerberos tickets using Rubeus or mimikatz "sekurlsa::tickets /export"
# Optionally convert tickets to ccache format using kekeo "misc::convert ccache <ticketName.kirbi>"
# Obtain appropriate aes256 key using dcsync (krbtgt for TGT or usually target computer account for Service Ticket)
# Run this script to decrypt:
# ./decryptKerbTicket.py -k 5c7ee0b8f0ffeedbeefdeadbeeff1eefc7d313620feedbeefdeadbeefafd601e -t ./[email protected][email protected]
# ./decryptKerbTicket.py -k 64aed4bbdac65342c94cf8db9522ca5a73a3f3fb4b6fdd4b7b332a6e98d10760 -t ./ASK_cifs-box1.testlab.local.kirbi
import struct, argparse, sys
from binascii import unhexlify,hexlify
from pyasn1.codec.der import encoder, decoder
from pyasn1.type.univ import noValue
from impacket.krb5.ccache import CCache
from impacket.krb5.crypto import Key, _enctype_table
from impacket.krb5.constants import EncryptionTypes
from impacket.krb5.pac import PACTYPE, VALIDATION_INFO
from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \
Ticket as TicketAsn1, EncTGSRepPart, EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1, KRB_CRED, EncKrbCredPart
from impacket.krb5.ccache import CCache, Header, Credential, Times, CountedOctetString, Principal, Ticket
from impacket.krb5.keytab import KeyBlock
from impacket.krb5 import types
# KrbCredCCache Class copied from: https://github.com/dirkjanm/krbrelayx/blob/master/lib/utils/krbcredccache.py
# Needed to support kirbi2ccache() function
class KrbCredCCache(CCache):
"""
This is just the impacket ccache, but with an extra function to create it from
a Krb Cred Ticket and ticket data
"""
def fromKrbCredTicket(self, ticket, ticketdata):
self.headers = []
header = Header()
header['tag'] = 1
header['taglen'] = 8
header['tagdata'] = '\xff\xff\xff\xff\x00\x00\x00\x00'
self.headers.append(header)
tmpPrincipal = types.Principal()
tmpPrincipal.from_asn1(ticketdata, 'prealm', 'pname')
self.principal = Principal()
self.principal.fromPrincipal(tmpPrincipal)
encASRepPart = ticketdata
credential = Credential()
server = types.Principal()
server.from_asn1(encASRepPart, 'srealm', 'sname')
tmpServer = Principal()
tmpServer.fromPrincipal(server)
credential['client'] = self.principal
credential['server'] = tmpServer
credential['is_skey'] = 0
credential['key'] = KeyBlock()
credential['key']['keytype'] = int(encASRepPart['key']['keytype'])
credential['key']['keyvalue'] = str(encASRepPart['key']['keyvalue'])
credential['key']['keylen'] = len(credential['key']['keyvalue'])
credential['time'] = Times()
credential['time']['authtime'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['starttime']))
credential['time']['starttime'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['starttime']))
credential['time']['endtime'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['endtime']))
credential['time']['renew_till'] = self.toTimeStamp(types.KerberosTime.from_asn1(encASRepPart['renew-till']))
flags = self.reverseFlags(encASRepPart['flags'])
credential['tktflags'] = flags
credential['num_address'] = 0
credential.ticket = CountedOctetString()
credential.ticket['data'] = encoder.encode(ticket.clone(tagSet=Ticket.tagSet, cloneValueFlag=True))
credential.ticket['length'] = len(credential.ticket['data'])
credential.secondTicket = CountedOctetString()
credential.secondTicket['data'] = ''
credential.secondTicket['length'] = 0
self.credentials.append(credential)
def p(x):
return struct.pack('<L',x)
# https://msdn.microsoft.com/en-us/library/cc237954.aspx
def processPacInfoBuffer(pacData):
dword = 8 # 4 bytes
bufferList = []
for i in range(0,32,dword):
bufferStr = pacData[i:i+dword]
bufferInt = int(bufferStr,16)
bufferStr = hexlify(p(bufferInt))
bufferInt = int(bufferStr,16)
bufferList.append(bufferInt)
return bufferList
def processTicket(ticket, key, verbose):
ticketCreds = ticket.credentials[0]
if verbose:
print("\n\n[+] ENCRYPTED TICKET:")
cipherText = ticketCreds.ticket['data']
# TGT/TGS tickets contain the SPN that they are applied to (e.g. krbtgt/[email protected]), which will change the location of the PAC
spnLength = len(ticketCreds['server'].realm['data'])
for i in ticketCreds['server'].toPrincipal().components:
spnLength += len(i)
decryptOffset = 128 + (2 * spnLength) # 2x is due to hexlified formatting
decryptOffset -= 8 # python3 fix for CountedOctetString
encryptedTicket = hexlify(cipherText)[decryptOffset:]
if verbose:
print(encryptedTicket)
else:
print("\tClient: " + ticketCreds['client'].prettyPrint().decode())
print("\tServer: " + ticketCreds['server'].prettyPrint().decode())
if verbose:
print("\n\n[+] DECRYPTED TICKET (still encoded)")
else:
print("[+] DECRYPTING TICKET")
encType = ticketCreds['key']['keytype'] # determine encryption type that ticket is using
# create encryption key based on type that ticket uses
try:
if encType == EncryptionTypes.aes256_cts_hmac_sha1_96.value:
key = Key(encType, unhexlify(key))
elif encType == EncryptionTypes.aes128_cts_hmac_sha1_96.value:
key = Key(encType, unhexlify(key))
elif encType == EncryptionTypes.rc4_hmac.value:
key = Key(encType, unhexlify(key))
else:
raise Exception('Unsupported enctype 0x%x' % encType)
except Exception as e:
print("[!] Error creating encryption key\n[!] Make sure you specified the correct key, your ticket is using type: " + str(encType))
print(e)
sys.exit(1)
cipher = _enctype_table[encType]
try:
decryptedText = cipher.decrypt(key, 2, unhexlify(encryptedTicket))
except Exception as e:
print("[!] Error \"" + str(e) + "\" occured while decrypting ticket. Attempting quick fix...")
try:
encryptedTicket = hexlify(cipherText)[decryptOffset+4:]
decryptedText = cipher.decrypt(key, 2, unhexlify(encryptedTicket))
print("[+] Decryption successful, quick fix worked")
except Exception as e2:
print("[!] Error \"" + str(e2) + "\" Quick fix failed. Make sure that correct decryption key is specified")
sys.exit(1)
if verbose:
print(hexlify(decryptedText))
decodedEncTicketPart = decoder.decode(decryptedText)[0]
if verbose:
print("\n\n[+] DECODED TICKET:")
print(decodedEncTicketPart)
print("[+] AUTHORIZATION DATA:")
pacData = decodedEncTicketPart['field-9'][0]['field-1']
decAuthData = decoder.decode(pacData)[0][0]['field-1']
pacBuffers = PACTYPE(decAuthData.asOctets())
pacBuffer = pacBuffers['Buffers']
pacBufferHex = hexlify(pacBuffer)
pacInfoList = processPacInfoBuffer(pacBufferHex)
authDataType = pacInfoList[0]
authDataLength = pacInfoList[1]
authDataOffset = pacInfoList[2]
authDataEnd = (authDataLength * 2) - 40 # subtract out the getData() part
offsetStart = 24 + authDataOffset*2
authDataHex = pacBufferHex[offsetStart:offsetStart+authDataEnd]
print("\nPAC_INFO_BUFFER")
print("ulType: " + str(authDataType))
print("cbBufferSize: " + str(authDataLength) + " bytes")
print("Offset: " + str(authDataOffset) + " bytes")
print("\nVALIDATION_INFO(raw)\n" + authDataHex.decode() + "\n")
if authDataType != 1:
raise Exception("PAC Buffer Sanity check failed. Excpected 1, Actual " + str(authDataType))
finalValidationInfo = VALIDATION_INFO()
finalValidationInfo.fromStringReferents(unhexlify(authDataHex))
finalValidationInfo.dump()
# kirbi2ccache function copied from https://github.com/dirkjanm/krbrelayx/blob/master/lib/utils/kerberos.py
def kirbi2ccache(kirbifile):
with open(kirbifile, 'rb') as infile:
data = infile.read()
creds = decoder.decode(data, asn1Spec=KRB_CRED())[0]
# This shouldn't be encrypted normally
if creds['enc-part']['etype'] != 0:
raise Exception('Ticket info is encrypted with cipher other than null')
enc_part = decoder.decode(creds['enc-part']['cipher'], asn1Spec=EncKrbCredPart())[0]
tinfo = enc_part['ticket-info']
ccache = KrbCredCCache()
# Enumerate all
for i, tinfo in enumerate(tinfo):
ccache.fromKrbCredTicket(creds['tickets'][i], tinfo)
return ccache
def loadTicket(ticket, verbose):
try:
ticket = CCache.loadFile(ticket)
except Exception as e:
print("ERROR: unable to load specified ticket. Make sure it is in ccache format.")
print(e)
sys.exit(1)
print("\n[+] TICKET LOADED SUCCESSFULLY")
if verbose:
print('')
ticket.prettyPrint()
return ticket
def parseArgs():
parser = argparse.ArgumentParser(add_help=True, description="Attempts to decrypt kerberos TGT or Service Ticket and display authorization data")
parser.add_argument('-t','--ticket', required=True, help='location of kerberos ticket file (ccache or kirbi format)')
parser.add_argument('-k','--key', required = True, action="store", help='decryption key (ntlm/aes128/aes256)')
parser.add_argument('-v','--verbose', action='store_true', help='Increase verbosity')
if len(sys.argv) == 1:
parser.print_help(sys.stderr)
print("\nExample:\n\t./decryptKerbTicket.py -k 5c7ee0b8f0ffeedbeefdeadbeeff1eefc7d313620feedbeefdeadbeefafd601e -t ./[email protected][email protected]")
sys.exit(1)
args = parser.parse_args()
return args
def main():
args = parseArgs()
if (args.ticket.upper().endswith(".KIRBI")):
ticket = kirbi2ccache(args.ticket)
else:
ticket = loadTicket(args.ticket, args.verbose)
processTicket(ticket, args.key, args.verbose)
if __name__ == '__main__':
main()
# this fork of decryptKerbTicket.py requires python3 impacket to work. Tested with impacket-0.10.0.
install python3
pip3 install impacket
check installation with `python3 ./decryptKerbTicket.py -h`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment