-
-
Save Dliv3/7155a85e04c562216b73ac5ace3dda56 to your computer and use it in GitHub Desktop.
Decrypt kerberos tickets and parse out authorization data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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