Skip to content

smbserver.py: add signing (NTLM/Kerberos) support, add read-only option #1975

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 13 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
54 changes: 2 additions & 52 deletions examples/describeTicket.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
from impacket.krb5.asn1 import TGS_REP, EncTicketPart, AD_IF_RELEVANT
from impacket.krb5.ccache import CCache
from impacket.krb5.constants import ChecksumTypes
from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum, string_to_key
from impacket.krb5.crypto import Key, _enctype_table, InvalidChecksum, string_to_key, generate_kerberos_keys
from impacket.ldap.ldaptypes import LDAP_SID

PSID = PRPC_SID
Expand Down Expand Up @@ -290,7 +290,7 @@ def parse_ccache(args):
logging.debug("No kvno in ticket, skipping")
logging.info(" %-28s: %d" % ("Key version number (kvno)", decodedTicket['ticket']['enc-part']['kvno']))
logging.debug("Handling Kerberos keys")
ekeys = generate_kerberos_keys(args)
ekeys = generate_kerberos_keys(args.rc4, args.aes, args.password, args.hex_pass, args.salt, args.user, args.domain)

# copypasta from krbrelayx.py
# Select the correct encryption key
Expand Down Expand Up @@ -630,56 +630,6 @@ def PACparseGroupIds(data):
return parsed_tuPAC


def generate_kerberos_keys(args):
# copypasta from krbrelayx.py
# Store Kerberos keys
keys = {}
if args.rc4:
keys[int(constants.EncryptionTypes.rc4_hmac.value)] = unhexlify(args.rc4)
if args.aes:
if len(args.aes) == 64:
keys[int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value)] = unhexlify(args.aes)
else:
keys[int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)] = unhexlify(args.aes)
ekeys = {}
for kt, key in keys.items():
ekeys[kt] = Key(kt, key)

allciphers = [
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),
int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)
]

# Calculate Kerberos keys from specified password/salt
if args.password or args.hex_pass:
if not args.salt and args.user and args.domain: # https://www.thehacker.recipes/ad/movement/kerberos
if args.user.endswith('$'):
args.salt = "%shost%s.%s" % (args.domain.upper(), args.user.rstrip('$').lower(), args.domain.lower())
else:
args.salt = "%s%s" % (args.domain.upper(), args.user)
for cipher in allciphers:
if cipher == 23 and args.hex_pass:
# RC4 calculation is done manually for raw passwords
md4 = MD4.new()
md4.update(unhexlify(args.hex_pass))
ekeys[cipher] = Key(cipher, md4.digest())
logging.debug('Calculated type %s (%d) Kerberos key: %s' % (constants.EncryptionTypes(cipher).name, cipher, hexlify(ekeys[cipher].contents).decode('utf-8')))
elif args.salt:
# Do conversion magic for raw passwords
if args.hex_pass:
rawsecret = unhexlify(args.hex_pass).decode('utf-16-le', 'replace').encode('utf-8', 'replace')
else:
# If not raw, it was specified from the command line, assume it's not UTF-16
rawsecret = args.password
ekeys[cipher] = string_to_key(cipher, rawsecret, args.salt)
logging.debug('Calculated type %s (%d) Kerberos key: %s' % (constants.EncryptionTypes(cipher).name, cipher, hexlify(ekeys[cipher].contents).decode('utf-8')))
else:
logging.debug('Cannot calculate type %s (%d) Kerberos key: salt is None: Missing -s/--salt or (-u/--user and -d/--domain)' % (constants.EncryptionTypes(cipher).name, cipher))
else:
logging.debug('No password (-p/--password or -hp/--hex_pass supplied, skipping Kerberos keys calculation')
return ekeys


def kerberoast_from_ccache(decodedTGS, spn, username, domain):
try:
Expand Down
27 changes: 26 additions & 1 deletion examples/smbserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,18 @@
parser.add_argument('-comment', action='store', help='share\'s comment to display when asked for shares')
parser.add_argument('-username', action="store", help='Username to authenticate clients')
parser.add_argument('-password', action="store", help='Password for the Username')
parser.add_argument('-computeraccountname', action="store", help='computer account name to authenticate arbitrary clients with signing via NetLogon')
parser.add_argument('-computeraccounthash', action="store", help='computer account NT hash to authenticate arbitrary clients with signing via NetLogon')
parser.add_argument('-computeraccountaes', action="store", help='computer account AES key to authenticate arbitrary clients with signing via Kerberos')
parser.add_argument('-computeraccountpassword', action="store", help='computer account NT hash to authenticate arbitrary clients with signing via Kerberos')
parser.add_argument('-computeraccountdomain', action="store", help='DC IP/hostname to authenticate arbitrary clients with signing via NetLogon')
parser.add_argument('-dcip', action="store", help='IP of domain controller to authenticate arbitrary clients with signing via NetLogon')
parser.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes for the Username, format is LMHASH:NTHASH')
parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output')
parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
parser.add_argument('-readonly', action='store_true', help='Only allow reading of files')
parser.add_argument('-disablekerberos', action='store_true', help='Do not offer Kerberos authentication')
parser.add_argument('-disablentlm', action='store_true', help='Do not offer NTLM authentication')
parser.add_argument('-ip', '--interface-address', action='store', default='0.0.0.0', help='ip address of listening interface')
parser.add_argument('-port', action='store', default='445', help='TCP port for listening incoming connections (default 445)')
parser.add_argument('-smb2support', action='store_true', default=False, help='SMB2 Support (experimental!)')
Expand Down Expand Up @@ -70,8 +79,10 @@
logging.info('Switching output to file %s' % options.outputfile)
server.setLogFile(options.outputfile)

server.addShare(options.shareName.upper(), options.sharePath, comment)
server.addShare(options.shareName.upper(), options.sharePath, comment, readOnly="yes" if options.readonly else "no")
server.setSMB2Support(options.smb2support)
server.setKerberosSupport(not options.disablekerberos)
server.setNTLMSupport(not options.disablentlm)

# If a user was specified, let's add it to the credentials for the SMBServer. If no user is specified, anonymous
# connections will be allowed
Expand All @@ -91,6 +102,20 @@

server.addCredential(options.username, 0, lmhash, nthash)

# If we want clients to be able to connect to us which enforce signing, we need a computer account to properly setup the connection
# Only works with SMB2
# FIXME: For NTLM just NT hash is supported for now
required_secure_server_options = [options.computeraccountname, options.computeraccountdomain, options.dcip]
at_least_one_secure_server_options = [options.computeraccounthash, options.computeraccountaes, options.computeraccountpassword]
if any(required_secure_server_options):
if not all(required_secure_server_options):
logging.critical("All of the following options need to be set for accepting signed connections from arbitrary users in the domain: -computeraccountname, -computeraccountdomain, -dcip")
sys.exit(1)
if not any(at_least_one_secure_server_options):
logging.critical("At least one of the following options need to be set for accepting signed connections from arbitrary users in the domain: -computeraccounthash, -computeraccountaes, -computeraccountpassword")
sys.exit(1)
server.setComputerAccount(options.computeraccountname, options.computeraccounthash, options.computeraccountaes, options.computeraccountpassword, options.computeraccountdomain, options.dcip)

# Here you can set a custom SMB challenge in hex format
# If empty defaults to '4141414141414141'
# (remember: must be 16 hex bytes long)
Expand Down
9 changes: 9 additions & 0 deletions impacket/krb5/asn1.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,15 @@ class AP_REQ(univ.Sequence):
_sequence_component('authenticator', 4, EncryptedData())
)

class GSSAPIHeader_KRB5_AP_REQ(univ.Sequence):
tagSet = univ.Sequence.tagSet.tagImplicitly(tag.Tag(tag.tagClassApplication, tag.tagFormatConstructed, 0))
componentType = namedtype.NamedTypes(
namedtype.NamedType('tokenOid', univ.ObjectIdentifier()),
# Actualy this is a constant 0x0001, but this decodes as an asn1 boolean
namedtype.NamedType('krb5_ap_req', univ.Boolean()),
namedtype.NamedType('apReq', AP_REQ()),
)

class AP_REP(univ.Sequence):
tagSet = _application_tag(constants.ApplicationTagNumbers.AP_REP.value)
componentType = namedtype.NamedTypes(
Expand Down
55 changes: 53 additions & 2 deletions impacket/krb5/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
#
from binascii import unhexlify
from binascii import hexlify, unhexlify
from functools import reduce
from os import urandom
# XXX current status:
Expand All @@ -61,7 +61,8 @@
from Cryptodome.Protocol.KDF import PBKDF2
from Cryptodome.Util.number import GCD as gcd
from six import b, PY3, indexbytes, binary_type

from impacket.krb5 import constants
import logging

def get_random_bytes(lenBytes):
# We don't really need super strong randomness here to use PyCrypto.Random
Expand Down Expand Up @@ -718,3 +719,53 @@ def prfplus(key, pepper, l):
e = _get_enctype_profile(enctype)
return e.random_to_key(_xorbytes(bytearray(prfplus(key1, pepper1, e.seedsize)),
bytearray(prfplus(key2, pepper2, e.seedsize))))

def generate_kerberos_keys(rc4=None, aes=None, password=None, hex_pass=None, salt=None, user=None, domain=None):
# copypasta from krbrelayx.py
# Store Kerberos keys
keys = {}
if rc4:
keys[int(constants.EncryptionTypes.rc4_hmac.value)] = unhexlify(rc4)
if aes:
if len(aes) == 64:
keys[int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value)] = unhexlify(aes)
else:
keys[int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)] = unhexlify(aes)
ekeys = {}
for kt, key in keys.items():
ekeys[kt] = Key(kt, key)

allciphers = [
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),
int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)
]

# Calculate Kerberos keys from specified password/salt
if password or hex_pass:
if not salt and user and domain: # https://www.thehacker.recipes/ad/movement/kerberos
if user.endswith('$'):
salt = "%shost%s.%s" % (domain.upper(), user.rstrip('$').lower(), domain.lower())
else:
salt = "%s%s" % (domain.upper(), user)
for cipher in allciphers:
if cipher == 23 and hex_pass:
# RC4 calculation is done manually for raw passwords
md4 = MD4.new()
md4.update(unhexlify(hex_pass))
ekeys[cipher] = Key(cipher, md4.digest())
logging.debug('Calculated type %s (%d) Kerberos key: %s' % (constants.EncryptionTypes(cipher).name, cipher, hexlify(ekeys[cipher].contents).decode('utf-8')))
elif salt:
# Do conversion magic for raw passwords
if hex_pass:
rawsecret = unhexlify(hex_pass).decode('utf-16-le', 'replace').encode('utf-8', 'replace')
else:
# If not raw, it was specified from the command line, assume it's not UTF-16
rawsecret = password
ekeys[cipher] = string_to_key(cipher, rawsecret, salt)
logging.debug('Calculated type %s (%d) Kerberos key: %s' % (constants.EncryptionTypes(cipher).name, cipher, hexlify(ekeys[cipher].contents).decode('utf-8')))
else:
logging.debug('Cannot calculate type %s (%d) Kerberos key: salt is None: Missing -s/--salt or (-u/--user and -d/--domain)' % (constants.EncryptionTypes(cipher).name, cipher))
else:
logging.debug('No password (-p/--password or -hp/--hex_pass supplied, skipping Kerberos keys calculation')
return ekeys
3 changes: 3 additions & 0 deletions impacket/smb3structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,9 @@ class SMB3Packet(SMBPacketBase):
('Data',':=""'),
)


class Empty(Structure):
pass
class SMB2Error(Structure):
structure = (
('StructureSize','<H=9'),
Expand Down
Loading