From 7ec97d47fb07a612cf533f91b607045d3177c35f Mon Sep 17 00:00:00 2001 From: p0rtL6 Date: Tue, 21 Jan 2025 21:18:39 -0500 Subject: [PATCH 1/5] Allow KDC options and encryption type to be caller-provided & add script to generate option codes --- examples/GenerateKerberosOptions.py | 113 ++++++++++++++++++++++++++++ examples/GetUserSPNs.py | 46 +++++++++-- impacket/krb5/kerberosv5.py | 51 ++++++++----- 3 files changed, 185 insertions(+), 25 deletions(-) create mode 100644 examples/GenerateKerberosOptions.py diff --git a/examples/GenerateKerberosOptions.py b/examples/GenerateKerberosOptions.py new file mode 100644 index 0000000000..33dac72d74 --- /dev/null +++ b/examples/GenerateKerberosOptions.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# Impacket - Collection of Python classes for working with network protocols. +# +# Copyright Fortra, LLC and its affiliated companies +# +# All rights reserved. +# +# This software is provided under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# Description: +# This module generates a hex value that can be passed to other examples (GetUserSPNs, etc.) to set Kerberos options. +# Both parsing of command line arguments in the form of a comma separated integer list, and an interactive menu are provided. +# The logic in here simply sets bits in a 32-bit integer based on the value of the option, then converts that to hex. +# This is based off of the code written by @ben0xa for Orpheus: https://github.com/trustedsec/orpheus. +# +# Author: +# p0rtL (@p0rtl6) +# + +import sys +from impacket.krb5 import constants + +selectedOptions = {enum.value: False for enum in constants.KDCOptions} + + +def print_help(): + print("Generate Kerberos Options Script") + print("") + print("Run without any arguments to open the interactive menu") + print("OR") + print("Provide a comma separated list of integers between 0-31 (e.g. 1,8,15,27)") + print("") + + +def print_options(): + print("") + + output = list() + + for option in sorted(constants.KDCOptions, key=lambda variant: variant.value): + output.append( + "({}) {} [{}]".format( + option.value, + option.name, + "On" if selectedOptions[option.value] else "Off", + ) + ) + + output.append("") # Pad to even number of elements + + halfPoint = len(output) // 2 + firstHalf = output[:halfPoint] + secondHalf = output[halfPoint:] + + for left, right in zip(firstHalf, secondHalf): + print("{:30s}{}".format(left, right)) + + print("") + + +def print_hex(simple=False): + binOptions = "".join("1" if selectedOptions.get(i, False) else "0" for i in range(32)) + hexOptions = hex(int(binOptions, 2)) + + if simple: + print(hexOptions) + else: + print("Generated Hex [{}]".format(hexOptions)) + print("") + + +if __name__ == "__main__": + if len(sys.argv) > 1: + strippedArg = sys.argv[1].lstrip("-") + if strippedArg == "help" or strippedArg == "h": + print_help() + sys.exit() + + # Take command line list of options + if len(sys.argv) > 1: + try: + argInputString = sys.argv[1] + argInputList = [int(number) for number in argInputString.split(",")] + for optionNumber in argInputList: + if optionNumber < 0 or optionNumber > 31: + raise ValueError("Number is not between 0-31") + + selectedOptions[optionNumber] = True + + print_hex(simple=True) + sys.exit() + except Exception as _: + print("[ERROR] Cannot parse argument list, make sure you are providing a comma seperated list of integers between 0-31.") + + # If options are not provided on the command line, prompt for them in a menu + while True: + print_options() + print_hex() + + userInput = input("Enter a number to toggle the corresponding option (or 'exit' to exit): ") + if userInput.lower() == "exit": + break + + try: + userInputInteger = int(userInput) + if userInputInteger < 0 or userInputInteger > 31: + raise ValueError("Number is not between 0-31") + + selectedOptions[userInputInteger] = not selectedOptions[userInputInteger] + except: + print("[ERROR] Please enter a valid number from 0-31") diff --git a/examples/GetUserSPNs.py b/examples/GetUserSPNs.py index 5b477345e6..fdcf825b36 100755 --- a/examples/GetUserSPNs.py +++ b/examples/GetUserSPNs.py @@ -92,6 +92,9 @@ def __init__(self, username, password, user_domain, target_domain, cmdLineOption self.__saveTGS = cmdLineOptions.save self.__requestUser = cmdLineOptions.request_user self.__stealth = cmdLineOptions.stealth + self.__tgsOptions = self.parseKerberosOptions(cmdLineOptions.tgs_options) + self.__tgtOptions = self.parseKerberosOptions(cmdLineOptions.tgt_options) + self.__encryption = cmdLineOptions.encryption if cmdLineOptions.hashes is not None: self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':') @@ -133,6 +136,23 @@ def getMachineName(self, target): s.logoff() return "%s.%s" % (s.getServerName(), s.getServerDNSDomainName()) + @staticmethod + def parseKerberosOptions(hex): + # convert hex to binary + scale = 16 + kdcbin = bin(int(hex, scale))[2:].zfill(32) + + # enable options based on binary (left to right) + opt = list() + idx = -1 + for b in kdcbin: + idx += 1 + if int(b) == 1: + print("Adding " + constants.KDCOptions(idx).name) + opt.append(constants.KDCOptions(idx).value) + + return opt + @staticmethod def getUnixTime(t): t -= 116444736000000000 @@ -156,19 +176,25 @@ def getTGT(self): tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, '', self.__domain, compute_lmhash(self.__password), compute_nthash(self.__password), self.__aesKey, - kdcHost=self.__kdcIP) + kdcHost=self.__kdcIP, + encType=self.__encryption, + options=self.__tgtOptions) except Exception as e: logging.debug('TGT: %s' % str(e)) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, - kdcHost=self.__kdcIP) + kdcHost=self.__kdcIP, + encType=self.__encryption, + options=self.__tgtOptions) else: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, - kdcHost=self.__kdcIP) + kdcHost=self.__kdcIP, + encType=self.__encryption, + options=self.__tgtOptions) TGT = {} TGT['KDC_REP'] = tgt TGT['cipher'] = cipher @@ -412,7 +438,7 @@ def run(self): tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain, self.__kdcIP, TGT['KDC_REP'], TGT['cipher'], - TGT['sessionKey']) + TGT['sessionKey'], options=self.__tgsOptions, encType=self.__encryption) self.outputTGS(tgs, oldSessionKey, sessionKey, sAMAccountName, self.__targetDomain + "/" + sAMAccountName, fd) except Exception as e: @@ -450,7 +476,9 @@ def request_multiple_TGSs(self, usernames): aesKey=self.__aesKey, kdcHost=self.__kdcHost, serverName=username, - kerberoast_no_preauth=True) + kerberoast_no_preauth=True, + encType=self.__encryption, + options=self.__tgtOptions) self.outputTGS(tgt, oldSessionKey, sessionKey, username, username, fd) except Exception as e: logging.debug("Exception:", exc_info=True) @@ -471,7 +499,7 @@ def request_multiple_TGSs(self, usernames): tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(principalName, self.__domain, self.__kdcIP, TGT['KDC_REP'], TGT['cipher'], - TGT['sessionKey']) + TGT['sessionKey'], options=self.__tgsOptions, encType=self.__encryption) self.outputTGS(tgs, oldSessionKey, sessionKey, username, username, fd) except Exception as e: logging.debug("Exception:", exc_info=True) @@ -529,6 +557,12 @@ def request_multiple_TGSs(self, usernames): 'If ommited, the domain part (FQDN) ' 'specified in the account parameter will be used') + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv) == 1: parser.print_help() sys.exit(1) diff --git a/impacket/krb5/kerberosv5.py b/impacket/krb5/kerberosv5.py index 7f45b4dc36..fe6309d122 100644 --- a/impacket/krb5/kerberosv5.py +++ b/impacket/krb5/kerberosv5.py @@ -94,7 +94,7 @@ def sendReceive(data, host, kdcHost, port=88): return r -def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True, serverName=None, kerberoast_no_preauth=False): +def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True, serverName=None, kerberoast_no_preauth=False, encType=None, options=None): # Convert to binary form, just in case we're receiving strings if isinstance(lmhash, str): @@ -182,6 +182,9 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH # We have hashes to try, only way is to request RC4 only supportedCiphers = (int(constants.EncryptionTypes.rc4_hmac.value),) + if encType: + supportedCiphers = (int(encType),) + seq_set_iter(reqBody, 'etype', supportedCiphers) message = encoder.encode(asReq) @@ -301,10 +304,14 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH reqBody = seq_set(asReq, 'req-body') - opts = list() - opts.append( constants.KDCOptions.forwardable.value ) - opts.append( constants.KDCOptions.renewable.value ) - opts.append( constants.KDCOptions.proxiable.value ) + if options is not None: + opts = options + else: + opts = list() + opts.append( constants.KDCOptions.forwardable.value ) + opts.append( constants.KDCOptions.renewable.value ) + opts.append( constants.KDCOptions.proxiable.value ) + reqBody['kdc-options'] = constants.encodeFlags(opts) seq_set(reqBody, 'sname', serverName.components_to_asn1) @@ -366,7 +373,7 @@ def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcH return tgt, cipher, key, sessionKey -def getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey, renew = False): +def getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey, renew=False, encType=None, options=None): # Decode the TGT try: @@ -425,11 +432,14 @@ def getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey, renew = reqBody = seq_set(tgsReq, 'req-body') - opts = list() - opts.append( constants.KDCOptions.forwardable.value ) - opts.append( constants.KDCOptions.renewable.value ) - opts.append( constants.KDCOptions.renewable_ok.value ) - opts.append( constants.KDCOptions.canonicalize.value ) + if options is not None: + opts = options + else: + opts = list() + opts.append( constants.KDCOptions.forwardable.value ) + opts.append( constants.KDCOptions.renewable.value ) + opts.append( constants.KDCOptions.renewable_ok.value ) + opts.append( constants.KDCOptions.canonicalize.value ) if renew == True: opts.append( constants.KDCOptions.renew.value ) @@ -442,14 +452,17 @@ def getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey, renew = reqBody['till'] = KerberosTime.to_asn1(now) reqBody['nonce'] = rand.getrandbits(31) - seq_set_iter(reqBody, 'etype', - ( - int(constants.EncryptionTypes.rc4_hmac.value), - int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), - int(constants.EncryptionTypes.des_cbc_md5.value), - int(cipher.enctype) - ) - ) + if encType is not None: + seq_set_iter(reqBody, 'etype', ( (int(encType),))) + else: + seq_set_iter(reqBody, 'etype', + ( + int(constants.EncryptionTypes.rc4_hmac.value), + int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), + int(constants.EncryptionTypes.des_cbc_md5.value), + int(cipher.enctype) + ) + ) message = encoder.encode(tgsReq) From 7356303d69810a4a2fbabc09cefda9d3909f9134 Mon Sep 17 00:00:00 2001 From: p0rtL6 Date: Tue, 21 Jan 2025 22:18:46 -0500 Subject: [PATCH 2/5] Add support for kerberos options to all examples --- examples/GetUserSPNs.py | 25 ++++--------------------- examples/addcomputer.py | 21 +++++++++++++++------ examples/dacledit.py | 19 ++++++++++++------- examples/getPac.py | 17 +++++++++++++---- examples/getST.py | 16 +++++++++++++--- examples/getTGT.py | 12 ++++++++++-- examples/goldenPac.py | 20 +++++++++++++++----- examples/owneredit.py | 16 +++++++++++----- examples/raiseChild.py | 16 +++++++++++++--- examples/rbcd.py | 16 +++++++++++----- examples/ticketer.py | 16 +++++++++++++--- impacket/krb5/kerberosv5.py | 19 +++++++++++++++++++ 12 files changed, 149 insertions(+), 64 deletions(-) diff --git a/examples/GetUserSPNs.py b/examples/GetUserSPNs.py index fdcf825b36..6e33f6cbc3 100755 --- a/examples/GetUserSPNs.py +++ b/examples/GetUserSPNs.py @@ -47,7 +47,7 @@ from impacket.krb5 import constants from impacket.krb5.asn1 import TGS_REP, AS_REP from impacket.krb5.ccache import CCache -from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS +from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, parseKerberosOptions from impacket.krb5.types import Principal from impacket.ldap import ldap, ldapasn1 from impacket.smbconnection import SMBConnection, SessionError @@ -92,8 +92,8 @@ def __init__(self, username, password, user_domain, target_domain, cmdLineOption self.__saveTGS = cmdLineOptions.save self.__requestUser = cmdLineOptions.request_user self.__stealth = cmdLineOptions.stealth - self.__tgsOptions = self.parseKerberosOptions(cmdLineOptions.tgs_options) - self.__tgtOptions = self.parseKerberosOptions(cmdLineOptions.tgt_options) + self.__tgsOptions = parseKerberosOptions(cmdLineOptions.tgs_options) + self.__tgtOptions = parseKerberosOptions(cmdLineOptions.tgt_options) self.__encryption = cmdLineOptions.encryption if cmdLineOptions.hashes is not None: self.__lmhash, self.__nthash = cmdLineOptions.hashes.split(':') @@ -136,23 +136,6 @@ def getMachineName(self, target): s.logoff() return "%s.%s" % (s.getServerName(), s.getServerDNSDomainName()) - @staticmethod - def parseKerberosOptions(hex): - # convert hex to binary - scale = 16 - kdcbin = bin(int(hex, scale))[2:].zfill(32) - - # enable options based on binary (left to right) - opt = list() - idx = -1 - for b in kdcbin: - idx += 1 - if int(b) == 1: - print("Adding " + constants.KDCOptions(idx).name) - opt.append(constants.KDCOptions(idx).value) - - return opt - @staticmethod def getUnixTime(t): t -= 116444736000000000 @@ -558,7 +541,7 @@ def request_multiple_TGSs(self, usernames): 'specified in the account parameter will be used') kerberos_options = parser.add_argument_group('kerberos options') - + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') diff --git a/examples/addcomputer.py b/examples/addcomputer.py index 120428bb6a..8f87c0174f 100755 --- a/examples/addcomputer.py +++ b/examples/addcomputer.py @@ -68,6 +68,9 @@ def __init__(self, username, password, domain, cmdLineOptions): self.__targetIp = cmdLineOptions.dc_ip self.__baseDN = cmdLineOptions.baseDN self.__computerGroup = cmdLineOptions.computer_group + self.__encryption = cmdLineOptions.encryption + self.__tgtOptions = cmdLineOptions.tgt_options + self.__tgsOptions = cmdLineOptions.tgs_options if self.__targetIp is not None: self.__kdcHost = self.__targetIp @@ -156,7 +159,7 @@ def run_ldaps(self): if self.__doKerberos: ldapConn = ldap3.Connection(ldapServer) self.LDAP3KerberosLogin(ldapConn, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, - self.__aesKey, kdcHost=self.__kdcHost) + self.__aesKey, kdcHost=self.__kdcHost, encType=self.__encryption, tgtOptions=self.__tgtOptions, tgsOptions=self.__tgsOptions) elif self.__hashes is not None: ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__hashes, authentication=ldap3.NTLM) ldapConn.bind() @@ -171,7 +174,7 @@ def run_ldaps(self): if self.__doKerberos: ldapConn = ldap3.Connection(ldapServer) self.LDAP3KerberosLogin(ldapConn, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, - self.__aesKey, kdcHost=self.__kdcHost) + self.__aesKey, kdcHost=self.__kdcHost, encType=self.__encryption, tgtOptions=self.__tgtOptions, tgsOptions=self.__tgsOptions) elif self.__hashes is not None: ldapConn = ldap3.Connection(ldapServer, user=user, password=self.__hashes, authentication=ldap3.NTLM) ldapConn.bind() @@ -266,7 +269,7 @@ def LDAPGetComputer(self, connection, computerName): return connection.entries[0] def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, - TGS=None, useCache=True): + TGS=None, useCache=True, encType=None, tgtOptions=None, tgsOptions=None): from pyasn1.codec.ber import encoder, decoder from pyasn1.type.univ import noValue """ @@ -300,7 +303,7 @@ def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', n # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set - from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS + from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, parseKerberosOptions from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket import datetime @@ -317,7 +320,7 @@ def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', n if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, - aesKey, kdcHost) + aesKey, kdcHost, encType=encType, options=parseKerberosOptions(tgtOptions)) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] @@ -326,7 +329,7 @@ def LDAP3KerberosLogin(self, connection, user, password, domain='', lmhash='', n if TGS is None: serverName = Principal(targetName, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, - sessionKey) + sessionKey, encType=encType, options=parseKerberosOptions(tgsOptions)) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] @@ -577,6 +580,12 @@ def run(self): 'Useful if you can\'t translate the FQDN.' 'specified in the account parameter will be used') + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv)==1: parser.print_help() diff --git a/examples/dacledit.py b/examples/dacledit.py index 00fbb93af9..4cf7837ad0 100755 --- a/examples/dacledit.py +++ b/examples/dacledit.py @@ -725,6 +725,11 @@ def parse_args(): dacl_parser.add_argument('-inheritance', action="store_true", help='Enable the inheritance in the ACE flag with CONTAINER_INHERIT_ACE and OBJECT_INHERIT_ACE. Useful when target is a Container or an OU, ' 'ACE will be inherited by objects within the container/OU (except objects with adminCount=1)') + kerberos_options = parser.add_argument_group('kerberos options') + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv) == 1: parser.print_help() sys.exit(1) @@ -784,7 +789,7 @@ def get_machine_name(args, domain): def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, - TGT=None, TGS=None, useCache=True): + TGT=None, TGS=None, useCache=True, encType=None, tgtOptions=None, tgsOptions=None): from pyasn1.codec.ber import encoder, decoder from pyasn1.type.univ import noValue """ @@ -816,7 +821,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set - from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS + from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, parseKerberosOptions from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket import datetime @@ -833,7 +838,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, - aesKey, kdcHost) + aesKey, kdcHost, encType=encType, options=parseKerberosOptions(tgtOptions)) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] @@ -842,7 +847,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' if TGS is None: serverName = Principal(target, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, - sessionKey) + sessionKey, encType=encType, options=parseKerberosOptions(tgsOptions)) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] @@ -910,7 +915,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' return True -def init_ldap_connection(target, tls_version, args, domain, username, password, lmhash, nthash): +def init_ldap_connection(target, tls_version, args, domain, username, password, lmhash, nthash, encType=None, tgtOptions=None, tgsOptions=None): user = '%s\\%s' % (domain, username) connect_to = target if args.dc_ip is not None: @@ -927,7 +932,7 @@ def init_ldap_connection(target, tls_version, args, domain, username, password, if args.k: ldap_session = ldap3.Connection(ldap_server) ldap_session.bind() - ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip) + ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip, encType=args.encryption, tgtOptions=args.tgt_options, tgsOptions=args.tgs_options) elif args.hashes is not None: ldap_session = ldap3.Connection(ldap_server, user=user, password=lmhash + ":" + nthash, authentication=ldap3.NTLM, auto_bind=True) else: @@ -936,7 +941,7 @@ def init_ldap_connection(target, tls_version, args, domain, username, password, return ldap_server, ldap_session -def init_ldap_session(args, domain, username, password, lmhash, nthash): +def init_ldap_session(args, domain, username, password, lmhash, nthash, encType=None, tgtOptions=None, tgsOptions=None): if args.k: target = get_machine_name(args, domain) else: diff --git a/examples/getPac.py b/examples/getPac.py index 61d6cb1fc7..b77253f022 100755 --- a/examples/getPac.py +++ b/examples/getPac.py @@ -45,7 +45,7 @@ from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \ EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1 from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5, Enctype -from impacket.krb5.kerberosv5 import getKerberosTGT, sendReceive +from impacket.krb5.kerberosv5 import getKerberosTGT, parseKerberosOptions, sendReceive from impacket.krb5.pac import PACTYPE, PAC_INFO_BUFFER, KERB_VALIDATION_INFO, PAC_CLIENT_INFO_TYPE, PAC_CLIENT_INFO, \ PAC_SERVER_CHECKSUM, PAC_SIGNATURE_DATA, PAC_PRIVSVR_CHECKSUM, PAC_UPN_DNS_INFO, UPN_DNS_INFO from impacket.krb5.types import Principal, KerberosTime, Ticket @@ -108,13 +108,16 @@ def printPac(self, data): buff = buff[len(infoBuffer):] - def __init__(self, behalfUser, username = '', password = '', domain='', hashes = None): + def __init__(self, behalfUser, username = '', password = '', domain='', hashes = None, encType=None, tgtOptions=None): self.__username = username self.__password = password self.__domain = domain.upper() self.__behalfUser = behalfUser self.__lmhash = '' self.__nthash = '' + self.__encryption = encType + self.__tgtOptions = parseKerberosOptions(tgtOptions) + if hashes is not None: self.__lmhash, self.__nthash = hashes.split(':') @@ -123,7 +126,7 @@ def dump(self): userName = Principal(self.__username, type=constants.PrincipalNameType.NT_PRINCIPAL.value) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, - unhexlify(self.__lmhash), unhexlify(self.__nthash)) + unhexlify(self.__lmhash), unhexlify(self.__nthash), encType=self.__encryption, options=self.__tgtOptions) decodedTGT = decoder.decode(tgt, asn1Spec = AS_REP())[0] @@ -305,6 +308,12 @@ def dump(self): group = parser.add_argument_group('authentication') group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') + + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv)==1: parser.print_help() sys.exit(1) @@ -328,7 +337,7 @@ def dump(self): logging.getLogger().setLevel(logging.INFO) try: - dumper = S4U2SELF(options.targetUser, username, password, domain, options.hashes) + dumper = S4U2SELF(options.targetUser, username, password, domain, options.hashes, options.encryption, options.tgt_options) dumper.dump() except Exception as e: if logging.getLogger().level == logging.DEBUG: diff --git a/examples/getST.py b/examples/getST.py index c9b0536a7a..ebfb5836f2 100755 --- a/examples/getST.py +++ b/examples/getST.py @@ -67,7 +67,7 @@ from impacket.krb5.ccache import CCache, Credential from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5, _AES256CTS, Enctype, string_to_key from impacket.krb5.constants import TicketFlags, encodeFlags -from impacket.krb5.kerberosv5 import getKerberosTGS, getKerberosTGT, sendReceive +from impacket.krb5.kerberosv5 import getKerberosTGS, getKerberosTGT, parseKerberosOptions, sendReceive from impacket.krb5.types import Principal, KerberosTime, Ticket from impacket.ntlm import compute_nthash from impacket.winregistry import hexdump @@ -87,6 +87,10 @@ def __init__(self, target, password, domain, options): self.__additional_ticket = options.additional_ticket self.__saveFileName = None self.__no_s4u2proxy = options.no_s4u2proxy + self.__tgsOptions = parseKerberosOptions(options.tgt_options) + self.__tgtOptions = parseKerberosOptions(options.tgs_options) + self.__encryption = options.encryption + if options.hashes is not None: self.__lmhash, self.__nthash = options.hashes.split(':') @@ -692,7 +696,7 @@ def run(self): tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(self.__lmhash), unhexlify(self.__nthash), self.__aesKey, - self.__kdcHost) + self.__kdcHost, encType=self.__encryption, options=self.__tgtOptions) logging.debug("TGT session key: %s" % hexlify(sessionKey.contents).decode()) # Ok, we have valid TGT, let's try to get a service ticket @@ -706,7 +710,7 @@ def run(self): logging.info('Getting ST for user') serverName = Principal(self.__options.spn, type=constants.PrincipalNameType.NT_SRV_INST.value) - tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, self.__options.renew) + tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, self.__kdcHost, tgt, cipher, sessionKey, self.__options.renew, encType=self.__encryption, options=self.__tgsOptions) self.__saveFileName = self.__user else: # Here's the rock'n'roll @@ -768,6 +772,12 @@ def run(self): group.add_argument('-dc-ip', action='store', metavar="ip address", help='IP Address of the domain controller. If ' 'omitted it use the domain part (FQDN) specified in the target parameter') + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv) == 1: parser.print_help() print("\nExamples: ") diff --git a/examples/getTGT.py b/examples/getTGT.py index d3b0e8bdc1..fd03bf84a5 100755 --- a/examples/getTGT.py +++ b/examples/getTGT.py @@ -29,7 +29,7 @@ from impacket import version from impacket.examples import logger from impacket.examples.utils import parse_credentials -from impacket.krb5.kerberosv5 import getKerberosTGT +from impacket.krb5.kerberosv5 import getKerberosTGT, parseKerberosOptions from impacket.krb5 import constants from impacket.krb5.types import Principal @@ -45,6 +45,8 @@ def __init__(self, target, password, domain, options): self.__options = options self.__kdcHost = options.dc_ip self.__service = options.service + self.__encryption = options.encryption + self.__tgtOptions = parseKerberosOptions(options.tgt_options) if options.hashes is not None: self.__lmhash, self.__nthash = options.hashes.split(':') @@ -65,7 +67,7 @@ def run(self): nthash = unhexlify(self.__nthash), aesKey = self.__aesKey, kdcHost = self.__kdcHost, - serverName = self.__service) + serverName = self.__service, encType=self.__encryption, options=self.__tgtOptions) self.saveTicket(tgt,oldSessionKey) if __name__ == '__main__': @@ -90,6 +92,12 @@ def run(self): 'ommited it use the domain part (FQDN) specified in the target parameter') group.add_argument('-service', action='store', metavar="SPN", help='Request a Service Ticket directly through an AS-REQ') group.add_argument('-principalType', nargs="?", type=lambda value: constants.PrincipalNameType[value.upper()] if value.upper() in constants.PrincipalNameType.__members__ else None, action='store', default=constants.PrincipalNameType.NT_PRINCIPAL, help='PrincipalType of the token, can be one of NT_UNKNOWN, NT_PRINCIPAL, NT_SRV_INST, NT_SRV_HST, NT_SRV_XHST, NT_UID, NT_SMTP_NAME, NT_ENTERPRISE, NT_WELLKNOWN, NT_SRV_HST_DOMAIN, NT_MS_PRINCIPAL, NT_MS_PRINCIPAL_AND_ID, NT_ENT_PRINCIPAL_AND_ID; default is NT_PRINCIPAL, ') + + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') if len(sys.argv)==1: parser.print_help() diff --git a/examples/goldenPac.py b/examples/goldenPac.py index 452731e9ef..bbbb505145 100755 --- a/examples/goldenPac.py +++ b/examples/goldenPac.py @@ -433,7 +433,7 @@ class VALIDATION_INFO(TypeSerialization1): ) def __init__(self, target, targetIp=None, username='', password='', domain='', hashes=None, command='', - copyFile=None, writeTGT=None, kdcHost=None): + copyFile=None, writeTGT=None, kdcHost=None, encType=None, tgtOptions=None, tgsOptions=None): self.__username = username self.__password = password self.__domain = domain @@ -450,6 +450,9 @@ def __init__(self, target, targetIp=None, username='', password='', domain='', h self.__forestSid = None self.__domainControllers = list() self.__kdcHost = kdcHost + self.__encryption = encType + self.__tgtOptions = parseKerberosOptions(tgtOptions) + self.__tgsOptions = parseKerberosOptions(tgsOptions) if hashes is not None: self.__lmhash, self.__nthash = hashes.split(':') @@ -936,7 +939,7 @@ def exploit(self): try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, self.__lmhash, self.__nthash, None, - self.__kdcHost, requestPAC=False) + self.__kdcHost, requestPAC=False, encType=self.__encryption, options=self.__tgtOptions) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably @@ -991,7 +994,7 @@ def exploit(self): try: tgsCIFS, cipher, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, domain, self.__kdcHost, tgs, cipher, - sessionKey) + sessionKey, encType=self.__encryption, options=self.__tgsOptions) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably @@ -1060,7 +1063,7 @@ def exploit(self): from impacket.dcerpc.v5 import transport from impacket.krb5.types import Principal, Ticket, KerberosTime from impacket.krb5 import constants - from impacket.krb5.kerberosv5 import sendReceive, getKerberosTGT, getKerberosTGS, KerberosError + from impacket.krb5.kerberosv5 import sendReceive, getKerberosTGT, getKerberosTGS, KerberosError, parseKerberosOptions from impacket.krb5.asn1 import AS_REP, TGS_REQ, AP_REQ, TGS_REP, Authenticator, EncASRepPart, AuthorizationData, \ AD_IF_RELEVANT, seq_set, seq_set_iter, KERB_PA_PAC_REQUEST, \ EncTGSRepPart, ETYPE_INFO2_ENTRY @@ -1097,6 +1100,13 @@ def exploit(self): group = parser.add_argument_group('authentication') group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') + + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv)==1: parser.print_help() print("\nExamples: ") @@ -1137,7 +1147,7 @@ def exploit(self): commands = 'cmd.exe' dumper = MS14_068(address, options.target_ip, username, password, domain, options.hashes, commands, options.c, - options.w, options.dc_ip) + options.w, options.dc_ip, options.encryption, options.tgt_options, options.tgs_options) try: dumper.exploit() diff --git a/examples/owneredit.py b/examples/owneredit.py index fc66733f58..8fb55c13a0 100644 --- a/examples/owneredit.py +++ b/examples/owneredit.py @@ -256,6 +256,12 @@ def parse_args(): dacl_parser = parser.add_argument_group("dacl editor") dacl_parser.add_argument('-action', choices=['read', 'write'], nargs='?', default='read', help='Action to operate on the owner attribute') + + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') if len(sys.argv) == 1: parser.print_help() @@ -315,7 +321,7 @@ def get_machine_name(args, domain): return s.getServerName() -def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True): +def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True, encType=None, tgtOptions=None, tgsOptions=None): from pyasn1.codec.ber import encoder, decoder from pyasn1.type.univ import noValue """ @@ -347,7 +353,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set - from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS + from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, parseKerberosOptions from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket import datetime @@ -364,7 +370,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, - aesKey, kdcHost) + aesKey, kdcHost, encType=encType, options=parseKerberosOptions(tgtOptions)) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] @@ -373,7 +379,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' if TGS is None: serverName = Principal(target, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, - sessionKey) + sessionKey, encType=encType, options=parseKerberosOptions(tgsOptions)) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] @@ -458,7 +464,7 @@ def init_ldap_connection(target, tls_version, args, domain, username, password, if args.k: ldap_session = ldap3.Connection(ldap_server) ldap_session.bind() - ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip) + ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip, encType=args.encryption, tgtOptions=args.tgt_options, tgsOptions=args.tgs_options) elif args.hashes is not None: ldap_session = ldap3.Connection(ldap_server, user=user, password=lmhash + ":" + nthash, authentication=ldap3.NTLM, auto_bind=True) else: diff --git a/examples/raiseChild.py b/examples/raiseChild.py index abab970b96..68761631a0 100755 --- a/examples/raiseChild.py +++ b/examples/raiseChild.py @@ -84,7 +84,7 @@ from impacket import version from impacket.krb5.types import Principal, KerberosTime from impacket.krb5 import constants -from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, KerberosError +from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, KerberosError, parseKerberosOptions from impacket.krb5.asn1 import AS_REP, AuthorizationData, AD_IF_RELEVANT, EncTicketPart from impacket.krb5.crypto import Key, _enctype_table, _checksum_table, Enctype from impacket.dcerpc.v5.ndr import NDRULONG @@ -486,6 +486,10 @@ def __init__(self, target = None, username = '', password = '', domain='', optio # self.__kdcHost = domain self.__kdcHost = None + self.__encryption = options.encryption + self.__tgtOptions = parseKerberosOptions(options.tgt_options) + self.__tgsOptions = parseKerberosOptions(options.tgs_options) + if options.hashes is not None: lmhash, nthash = options.hashes.split(':') self.__creds['lmhash'] = unhexlify(lmhash) @@ -1131,7 +1135,7 @@ def raiseUp(self, childName, childCreds, parentName): try: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, childCreds['password'], childCreds['domain'], childCreds['lmhash'], - childCreds['nthash'], None, self.__kdcHost) + childCreds['nthash'], None, self.__kdcHost, encType=self.__encryption, options=self.__tgtOptions) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES (most probably @@ -1167,7 +1171,7 @@ def raiseUp(self, childName, childCreds, parentName): tgsCIFS, cipherCIFS, oldSessionKeyCIFS, sessionKeyCIFS = getKerberosTGS(serverName, childCreds['domain'], None, goldenTicket, cipher, - sessionKey) + sessionKey, encType=self.__encryption, options=self.__tgsOptions) TGS['KDC_REP'] = tgsCIFS TGS['cipher'] = cipherCIFS TGS['oldSessionKey'] = oldSessionKeyCIFS @@ -1273,6 +1277,12 @@ def exploit(self): group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' '(128 or 256 bits)') + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv)==1: parser.print_help() print("\nExamples: ") diff --git a/examples/rbcd.py b/examples/rbcd.py index 0db8be17fd..1d7d22cf6b 100755 --- a/examples/rbcd.py +++ b/examples/rbcd.py @@ -53,7 +53,7 @@ def get_machine_name(args, domain): def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, - TGT=None, TGS=None, useCache=True): + TGT=None, TGS=None, useCache=True, encType=None, tgtOptions=None, tgsOptions=None): from pyasn1.codec.ber import encoder, decoder from pyasn1.type.univ import noValue """ @@ -85,7 +85,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' # Importing down here so pyasn1 is not required if kerberos is not used. from impacket.krb5.ccache import CCache from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set - from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS + from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, parseKerberosOptions from impacket.krb5 import constants from impacket.krb5.types import Principal, KerberosTime, Ticket import datetime @@ -102,7 +102,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' if TGT is None: if TGS is None: tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, - aesKey, kdcHost) + aesKey, kdcHost, encType=encType, options=parseKerberosOptions(tgtOptions)) else: tgt = TGT['KDC_REP'] cipher = TGT['cipher'] @@ -111,7 +111,7 @@ def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash=' if TGS is None: serverName = Principal(target, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, - sessionKey) + sessionKey, encType=encType, options=parseKerberosOptions(tgsOptions)) else: tgs = TGS['KDC_REP'] cipher = TGS['cipher'] @@ -439,6 +439,12 @@ def parse_args(): 'omitted it will use the domain part (FQDN) specified in ' 'the identity parameter') + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv) == 1: parser.print_help() sys.exit(1) @@ -498,7 +504,7 @@ def init_ldap_connection(target, tls_version, args, domain, username, password, if args.k: ldap_session = ldap3.Connection(ldap_server) ldap_session.bind() - ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip) + ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, args.aesKey, kdcHost=args.dc_ip, encType=args.encryption, tgtOptions=args.tgt_options, tgsOptions=args.tgs_options) elif args.hashes is not None: ldap_session = ldap3.Connection(ldap_server, user=user, password=lmhash + ":" + nthash, authentication=ldap3.NTLM, auto_bind=True) else: diff --git a/examples/ticketer.py b/examples/ticketer.py index 9ac6ca0550..ff2f6fec22 100755 --- a/examples/ticketer.py +++ b/examples/ticketer.py @@ -80,7 +80,7 @@ VALIDATION_INFO, PAC_CLIENT_INFO, KERB_VALIDATION_INFO, UPN_DNS_INFO_FULL, PAC_REQUESTOR_INFO, PAC_UPN_DNS_INFO, PAC_ATTRIBUTES_INFO, PAC_REQUESTOR, \ PAC_ATTRIBUTE_INFO from impacket.krb5.types import KerberosTime, Principal -from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS +from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS, parseKerberosOptions from impacket.krb5 import constants, pac from impacket.krb5.asn1 import AP_REQ, TGS_REQ, Authenticator, seq_set, seq_set_iter, PA_FOR_USER_ENC, Ticket as TicketAsn1 @@ -97,6 +97,10 @@ def __init__(self, target, password, domain, options): self.__options = options self.__tgt = None self.__tgt_session_key = None + self.__encryption = options.encryption + self.__tgtOptions = parseKerberosOptions(options.tgt_options) + self.__tgtOptions = parseKerberosOptions(options.tgt_options) + if options.spn: spn = options.spn.split('/') self.__service = spn[0] @@ -338,14 +342,14 @@ def createBasicTicket(self): userName = Principal(self.__options.user, type=PrincipalNameType.NT_PRINCIPAL.value) tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, self.__password, self.__domain, unhexlify(lmhash), unhexlify(nthash), None, - self.__options.dc_ip) + self.__options.dc_ip, encType=self.__encryption, options=self.__tgtOptions) self.__tgt, self.__tgt_cipher, self.__tgt_session_key = tgt, cipher, sessionKey if self.__domain == self.__server: kdcRep = decoder.decode(tgt, asn1Spec=AS_REP())[0] else: serverName = Principal(self.__options.spn, type=PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, None, tgt, cipher, - sessionKey) + sessionKey, encType=self.__encryption, options=self.__tgsOptions) kdcRep = decoder.decode(tgs, asn1Spec=TGS_REP())[0] # Let's check we have all the necessary data based on the ciphers used. Boring checks @@ -1143,6 +1147,12 @@ def run(self): ' for querying the ST and extracting the PAC, which will be' ' included in the new ticket') + kerberos_options = parser.add_argument_group('kerberos options') + + kerberos_options.add_argument('-tgs-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Service (TGS).') + kerberos_options.add_argument('-tgt-options', action="store", metavar="hex value", default=None, help='The hexadecimal value to send to the Kerberos Ticket Granting Ticket (TGT).') + kerberos_options.add_argument('-encryption', action="store", metavar="18 or 23", default="23", help='Set encryption to AES256 (18) or RC4 (23).') + if len(sys.argv)==1: parser.print_help() print("\nExamples: ") diff --git a/impacket/krb5/kerberosv5.py b/impacket/krb5/kerberosv5.py index fe6309d122..2a9e975e08 100644 --- a/impacket/krb5/kerberosv5.py +++ b/impacket/krb5/kerberosv5.py @@ -94,6 +94,25 @@ def sendReceive(data, host, kdcHost, port=88): return r +def parseKerberosOptions(hex=None): + if hex is None: + return None + + # convert hex to binary + scale = 16 + kdcbin = bin(int(hex, scale))[2:].zfill(32) + + # enable options based on binary (left to right) + options = list() + idx = -1 + for b in kdcbin: + idx += 1 + if int(b) == 1: + print("Adding " + constants.KDCOptions(idx).name) + options.append(constants.KDCOptions(idx).value) + + return options + def getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey='', kdcHost=None, requestPAC=True, serverName=None, kerberoast_no_preauth=False, encType=None, options=None): # Convert to binary form, just in case we're receiving strings From 67c1ee12546986d2d67e8af611186e321e065ed2 Mon Sep 17 00:00:00 2001 From: p0rtL6 Date: Tue, 25 Feb 2025 18:01:45 -0500 Subject: [PATCH 3/5] Add Documentation --- README.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/README.md b/README.md index fc9dba1871..5768297a74 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,37 @@ Impacket ======== +Kerberos Options Branch +----------------------- + +This branch exposes functionality to change the options on Kerberos requests. +Originally taken from [Orpheus](https://github.com/trustedsec/orpheus). +The functionality has been added to the underlying functions, and has been propogated up to the command line examples that use it. There has also been an example added that can generate the option codes that are used by the examples (follows the same format as Orpheus). + +**Examples updated:** + + * GetUserSPNs.py + * addcomputer.py + * dacledit.py + * getPac.py + * getST.py + * getTGT.py + * goldenPac.py + * owneredit.py + * raiseChild.py + * rbcd.py + * ticketer.py + +**Added:** + + * GenerateKerberosOptions.py + +**Modified Libraries:** + + * kerberosv5.py + +--- + [![Latest Version](https://img.shields.io/pypi/v/impacket.svg)](https://pypi.python.org/pypi/impacket/) [![Build and test Impacket](https://github.com/fortra/impacket/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/fortra/impacket/actions/workflows/build_and_test.yml) From 8ba2da1bf103e093cd32a6fa18b2046cb321bf6e Mon Sep 17 00:00:00 2001 From: p0rtL6 Date: Tue, 25 Feb 2025 18:02:20 -0500 Subject: [PATCH 4/5] Update Documentation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5768297a74..cca19b09f2 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ The functionality has been added to the underlying functions, and has been propo * kerberosv5.py +Original README --- [![Latest Version](https://img.shields.io/pypi/v/impacket.svg)](https://pypi.python.org/pypi/impacket/) From 7c552d86df9211e1efaae054d9fc2fe20a2e7e68 Mon Sep 17 00:00:00 2001 From: p0rtL6 Date: Tue, 25 Feb 2025 18:54:47 -0500 Subject: [PATCH 5/5] fix spelling error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cca19b09f2..62d0354319 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Kerberos Options Branch This branch exposes functionality to change the options on Kerberos requests. Originally taken from [Orpheus](https://github.com/trustedsec/orpheus). -The functionality has been added to the underlying functions, and has been propogated up to the command line examples that use it. There has also been an example added that can generate the option codes that are used by the examples (follows the same format as Orpheus). +The functionality has been added to the underlying functions, and has been propagated up to the command line examples that use it. There has also been an example added that can generate the option codes that are used by the examples (follows the same format as Orpheus). **Examples updated:**