github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/getPac.py (about)

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