Skip to content

Add Kerberos Request Options to Examples #1905

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,38 @@
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 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:**

* 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

Original README
---

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

Expand Down
113 changes: 113 additions & 0 deletions examples/GenerateKerberosOptions.py
Original file line number Diff line number Diff line change
@@ -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")
31 changes: 24 additions & 7 deletions examples/GetUserSPNs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = 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(':')

Expand Down Expand Up @@ -156,19 +159,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
Expand Down Expand Up @@ -412,7 +421,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:
Expand Down Expand Up @@ -450,7 +459,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)
Expand All @@ -471,7 +482,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)
Expand Down Expand Up @@ -529,6 +540,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)
Expand Down
21 changes: 15 additions & 6 deletions examples/addcomputer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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
"""
Expand Down Expand Up @@ -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
Expand All @@ -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']
Expand All @@ -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']
Expand Down Expand Up @@ -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()
Expand Down
Loading