Skip to content

Instantly share code, notes, and snippets.

@mashaGC
Forked from tothi/decryptKerbTicket.py
Last active August 9, 2022 11:48
Show Gist options
  • Save mashaGC/c00378d9587d4ba16c61b2096cf81842 to your computer and use it in GitHub Desktop.
Save mashaGC/c00378d9587d4ba16c61b2096cf81842 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 ./Administrator@TESTLAB.LOCAL_krbtgt~TESTLAB.LOCAL@TESTLAB.LOCAL.ccaches
# ./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, PAC_CLIENT_INFO, PAC_REQUESTOR ##added requestor
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, KeyBlock, Times, CountedOctetString, Principal, Ticket
from impacket.krb5 import types
#*******************PAC BUFFERS DICT***************************#
#**************************************************************#
pacBuffersDict = {
1: "KERB_VALIDATION_INFO", #0x00000001
2: "PAC_CREDENTIALS", #0x00000002
6: "PAC_SIGNATURE_SERVER", #0x00000006
7: "PAC_SIGNATURE_KDC", #0x00000007
10: "PAC_CLIENT_INFO", #0x0000000A
11: "DELEGATION_INFO", #0x0000000B
12: "UPN_DNS_INFO", #0x0000000C
13: "PAC_CLIENT_CLAIMS", #0x0000000D
14: "PAC_DEVICE_INFO", #0x0000000E
15: "PAC_DEVICE_CLAIMS", #0x0000000F
16: "PAC_SIGNATURE_TICKET",#0x00000010
17: "PAC_ATTRIBUTES_INFO",#0x00000011
18: "PAC_REQUESTOR"# 0x00000012
}
# 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
# Look for PACRequestor buffer
def processPacInfoBufferAll(pacData, totalBuffers):
dword = 8 # 4 bytes
pacBuffers = []
for j in range(totalBuffers):
bufferList = []
for i in range(j*32,(j+1)*32,dword):
bufferStr = pacData[i:i+dword]
bufferInt = int(bufferStr,16)
bufferStr = hexlify(p(bufferInt))
bufferInt = int(bufferStr,16)
bufferList.append(bufferInt)
pacBuffers.append(bufferList)
return pacBuffers
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/testlab.local@testlab.local), 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('\nExtract PAC from ticket...')
pacData = decodedEncTicketPart['field-9'][0]['field-1']
decAuthData = decoder.decode(pacData)[0][0]['field-1']
pacBuffers = PACTYPE(decAuthData.asOctets())
pacBuffer = pacBuffers['Buffers']
pacBeffersNum = pacBuffers['cBuffers']
pacBufferHex = hexlify(pacBuffer)
pacBuffersAll = processPacInfoBufferAll(pacBufferHex, pacBeffersNum)
print("Pac buffers number: " + str(pacBeffersNum))
print("\nPac buffers (structures) in parsed ticket are:")
for i in pacBuffersAll:
print(pacBuffersDict[i[0]])
print("\nPAC Buffers raw:")
print(str(pacBufferHex))
for i in pacBuffersAll:
if i[0] == 10: #ulType is PAC_CLIENT_INFO (value 0x0000000a)
clientLength = i[1]
clientOffset = i[2]
clientEnd = (clientLength * 2)
clientOffsetStart = clientOffset*2 -16
clientHex = pacBufferHex[clientOffsetStart:clientOffsetStart+clientEnd]
print("\n\n--------PAC_CLIENT_INFO (buffer info)--------")
print("ulType: " + str(i[0]))
print("cbBufferSize: " + str(clientLength) + " bytes")
print("Offset: " + str(clientOffset) + " bytes")
print("\nPAC_CLIENT_INFO(raw)\n" + clientHex.decode() + "\n")
finalClientInfo = PAC_CLIENT_INFO(unhexlify(clientHex))
finalClientInfo.dump()
elif i[0] == 18: #ulType is PAC_Requestor (value 0x00000012)
pRequestor = 1
requestorLength = i[1]
requestorOffset = i[2]
requestorEnd = (requestorLength * 2)
reqOffsetStart = requestorOffset*2 -16
requestorHex = pacBufferHex[reqOffsetStart:reqOffsetStart+requestorEnd]
print("\n\n---------PAC_REQUESTOR (buffer info)---------")
print("ulType: " + str(i[0]))
print("cbBufferSize: " + str(requestorLength) + " bytes")
print("Offset: " + str(requestorOffset) + " bytes")
print("\nPAC_REQUESTOR(raw)\n" + requestorHex.decode() + "\n")
finalRequestorInfo = PAC_REQUESTOR(unhexlify(requestorHex))
finalRequestorInfo.dump()
elif i[0] ==1: #ulType is KERB_VALIDATION_INFO (value 0x00000001)
authDataLength = i[1]
authDataOffset = i[2]
authDataEnd = (authDataLength * 2) - 40 # subtract out the getData() part
offsetStart = 24 + authDataOffset*2
authDataHex = pacBufferHex[offsetStart:offsetStart+authDataEnd]
print("\n-------KERB_VALIDATION (info buffer)-------")
print("ulType: " + str(i[0]))
print("cbBufferSize: " + str(authDataLength) + " bytes")
print("Offset: " + str(authDataOffset) + " bytes")
print("\nKERB_VALIDATION_INFO(raw)\n" + authDataHex.decode() + "\n")
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 ./Administrator@TESTLAB.LOCAL_krbtgt~TESTLAB.LOCAL@TESTLAB.LOCAL.ccaches")
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.9.23.
install python3
pip3 install impacket
check installation with `python3 ./decryptKerbTicket.py -h`
# This fork encrypts additional pac structures:
# PAC_CLIENT_INFO
# PAC_Requestor
# impackets pac.py should include the PAC_REQUESTOR structure as following:
# 2.15 PAC_REQUESTOR
class PAC_REQUESTOR(NDRSTRUCT):
structure = (
('UserSid', RPC_SID),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment