Projects STRLCPY aardwolf Commits efed124a
🤬
Showing first 37 files as there are too many
  • ■ ■ ■ ■ ■
    aardwolf/authentication/__init__.py
    1  - 
  • ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/__init__.py
    1  - 
  • ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/messages/__init__.py
    1  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/messages/asn1_structs.py
    1  - 
    2  -from asn1crypto.core import Integer, ObjectIdentifier, Sequence, SequenceOf, Enumerated, GeneralString, OctetString, BitString, Choice, Any, Boolean
    3  - 
    4  -TAG = 'explicit'
    5  - 
    6  -# class
    7  -UNIVERSAL = 0
    8  -APPLICATION = 1
    9  -CONTEXT = 2
    10  - 
    11  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/9664994d-0784-4659-b85b-83b8d54c2336
    12  -# NegoData ::= SEQUENCE OF SEQUENCE {
    13  -# negoToken [0] OCTET STRING
    14  -# }
    15  - 
    16  -class NegoData(Sequence):
    17  - _fields = [
    18  - ('negoToken', OctetString, {'explicit': 0}),]
    19  - 
    20  -class NegoDatas(SequenceOf):
    21  - _child_spec = NegoData
    22  - 
    23  - 
    24  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/6aac4dea-08ef-47a6-8747-22ea7f6d8685
    25  -# TSRequest ::= SEQUENCE {
    26  -# version [0] INTEGER,
    27  -# negoTokens [1] NegoData OPTIONAL,
    28  -# authInfo [2] OCTET STRING OPTIONAL,
    29  -# pubKeyAuth [3] OCTET STRING OPTIONAL,
    30  -# errorCode [4] INTEGER OPTIONAL,
    31  -# clientNonce [5] OCTET STRING OPTIONAL
    32  -# }
    33  - 
    34  -class TSRequest(Sequence):
    35  - _fields = [
    36  - ('version', Integer, {'explicit': 0}),
    37  - ('negoTokens', NegoDatas, {'explicit': 1, 'optional': True}),
    38  - ('authInfo', OctetString, {'explicit': 2, 'optional': True}),
    39  - ('pubKeyAuth', OctetString, {'explicit': 3, 'optional': True}),
    40  - ('errorCode', Integer, {'explicit': 4, 'optional': True}),
    41  - ('clientNonce', OctetString, {'explicit': 5, 'optional': True}),
    42  -]
    43  - 
    44  - 
    45  - 
    46  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/94a1ab00-5500-42fd-8d3d-7a84e6c2cf03
    47  -# TSCredentials ::= SEQUENCE {
    48  -# credType [0] INTEGER,
    49  -# credentials [1] OCTET STRING
    50  -# }
    51  - 
    52  -class TSCredentials(Sequence):
    53  - _fields = [
    54  - ('credType', Integer, {'explicit': 0}),
    55  - ('credentials', OctetString, {'explicit': 1}),
    56  -]
    57  - 
    58  - 
    59  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/17773cc4-21e9-4a75-a0dd-72706b174fe5
    60  -# TSPasswordCreds ::= SEQUENCE {
    61  -# domainName [0] OCTET STRING,
    62  -# userName [1] OCTET STRING,
    63  -# password [2] OCTET STRING
    64  -# }
    65  - 
    66  -class TSPasswordCreds(Sequence):
    67  - _fields = [
    68  - ('domainName', OctetString, {'explicit': 0}),
    69  - ('userName', OctetString, {'explicit': 1}),
    70  - ('password', OctetString, {'explicit': 2}),
    71  -]
    72  - 
    73  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/34ee27b3-5791-43bb-9201-076054b58123
    74  -# TSCspDataDetail ::= SEQUENCE {
    75  -# keySpec [0] INTEGER,
    76  -# cardName [1] OCTET STRING OPTIONAL,
    77  -# readerName [2] OCTET STRING OPTIONAL,
    78  -# containerName [3] OCTET STRING OPTIONAL,
    79  -# cspName [4] OCTET STRING OPTIONAL
    80  -# }
    81  - 
    82  -class TSCspDataDetail(Sequence):
    83  - _fields = [
    84  - ('keySpec', Integer, {'explicit': 0}),
    85  - ('cardName', OctetString, {'explicit': 1,'optional': True}),
    86  - ('readerName', OctetString, {'explicit': 2,'optional': True}),
    87  - ('containerName', OctetString, {'explicit': 3,'optional': True}),
    88  - ('cspName', OctetString, {'explicit': 4,'optional': True }),
    89  -]
    90  - 
    91  - 
    92  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/4251d165-cf01-4513-a5d8-39ee4a98b7a4
    93  -# TSSmartCardCreds ::= SEQUENCE {
    94  -# pin [0] OCTET STRING,
    95  -# cspData [1] TSCspDataDetail,
    96  -# userHint [2] OCTET STRING OPTIONAL,
    97  -# domainHint [3] OCTET STRING OPTIONAL
    98  -# }
    99  -#
    100  - 
    101  -class TSSmartCardCreds(Sequence):
    102  - _fields = [
    103  - ('pin', OctetString, {'explicit': 0}),
    104  - ('cspData', TSCspDataDetail, {'explicit': 1}),
    105  - ('userHint', OctetString, {'explicit': 2, 'optional': True}),
    106  - ('domainHint', OctetString, {'explicit': 3, 'optional': True}),
    107  -]
    108  - 
    109  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/173eee44-1a2c-463f-b909-c15db01e68d7
    110  -# TSRemoteGuardPackageCred ::= SEQUENCE{
    111  -# packageName [0] OCTET STRING,
    112  -# credBuffer [1] OCTET STRING,
    113  -# }
    114  - 
    115  -class TSRemoteGuardPackageCred(Sequence):
    116  - _fields = [
    117  - ('packageName', OctetString, {'explicit': 0}),
    118  - ('credBuffer', OctetString, {'explicit': 1}),
    119  -]
    120  - 
    121  - 
    122  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/7ef8229c-44ea-4c1b-867f-00369b882b38
    123  -# TSRemoteGuardCreds ::= SEQUENCE{
    124  -# logonCred [0] TSRemoteGuardPackageCred,
    125  -# supplementalCreds [1] SEQUENCE OF TSRemoteGuardPackageCred OPTIONAL,
    126  -# }
    127  - 
    128  - 
    129  -class TSRemoteGuardPackageCreds(SequenceOf):
    130  - _child_spec = TSRemoteGuardPackageCred
    131  - 
    132  -class TSRemoteGuardCreds(Sequence):
    133  - _fields = [
    134  - ('logonCred', TSRemoteGuardPackageCred, {'explicit': 0}),
    135  - ('supplementalCreds', TSRemoteGuardPackageCreds, {'explicit': 1, 'optional':True}),
    136  -]
    137  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/messages/remcredguard.py
    1  - 
    2  -from aardwolf.authentication.credssp.messages.asn1_structs import *
    3  - 
    4  -## this is not implemented yet!
    5  - 
    6  -"""
    7  -typedef struct _NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL {
    8  - ULONG Version;
    9  - ULONG Flags;
    10  - MSV1_0_CREDENTIAL_KEY_TYPE reserved;
    11  - MSV1_0_CREDENTIAL_KEY reserved;
    12  - ULONG reservedsize;
    13  - [size_is(reservedSize)] UCHAR* reserved;
    14  - } NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL;
    15  - 
    16  - 
    17  - 
    18  - 
    19  -"""
    20  -import enum
    21  - 
    22  -class MSV1_0_CREDENTIAL_KEY_TYPE(enum.Enum):
    23  - InvalidCredKey = 0 # // reserved
    24  - IUMCredKey = 1 # // reserved
    25  - DomainUserCredKey = 2 #
    26  - LocalUserCredKey = 3 # // For internal use only - should never be present in MSV1_0_REMOTE_ENCRYPTED_SECRETS
    27  - ExternallySuppliedCredKey = 4 # // reserved
    28  - 
    29  -class NRSC_FLAG(enum.IntFlag):
    30  - LMOWF = 1
    31  - NTOWF = 2
    32  - CREDKEY_PRESENT = 8
    33  - 
    34  -class NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL:
    35  - def __init__(self):
    36  - self.Version:int = None
    37  - self.Flags:int = None
    38  - self.KeyType:MSV1_0_CREDENTIAL_KEY_TYPE = None
    39  - self.Key:bytes = None
    40  - self.reservedSize: int = None
    41  - self.reserved6: bytes = None #size is 'reservedSize'
    42  - 
    43  -
    44  - def to_bytes(self):
    45  - t = self.Version.to_bytes(4, byteorder='little', signed = False)
    46  - t += self.Flags.to_bytes(4, byteorder='little', signed = False)
    47  - t += self.KeyType.value.to_bytes(4, byteorder='little', signed = False)
    48  - t += self.Key
    49  - t += len(self.reserved6).to_bytes(4, byteorder='little', signed = False)
    50  - t += self.reserved6
    51  - 
    52  - return t
    53  - 
    54  - @staticmethod
    55  - def from_bytes(bbuff: bytes):
    56  - return NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL.from_buffer(io.BytesIO(bbuff))
    57  - 
    58  - @staticmethod
    59  - def from_buffer(buff: io.BytesIO):
    60  - msg = NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL()
    61  - msg.Version = int.from_bytes(buff.read(4), byteorder='little', signed = False)
    62  - msg.Flags = NRSC_FLAG(int.from_bytes(buff.read(4), byteorder='little', signed = False))
    63  - msg.Key = buff.read(20)
    64  - msg.KeyType = MSV1_0_CREDENTIAL_KEY_TYPE(int.from_bytes(buff.read(4), byteorder='little', signed = False))
    65  - msg.reservedSize = int.from_bytes(buff.read(4), byteorder='little', signed = False)
    66  - msg.reserved6 = buff.read(msg.reservedSize)
    67  - return msg
    68  - 
    69  - def __repr__(self):
    70  - t = '==== NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL ====\r\n'
    71  - for k in self.__dict__:
    72  - if isinstance(self.__dict__[k], enum.IntFlag):
    73  - value = self.__dict__[k]
    74  - elif isinstance(self.__dict__[k], enum.Enum):
    75  - value = self.__dict__[k].name
    76  - else:
    77  - value = self.__dict__[k]
    78  - t += '%s: %s\r\n' % (k, value)
    79  - return t
    80  - 
    81  - 
    82  - 
    83  -# TSRemoteGuardPackageCred
    84  -data = bytes.fromhex('3081dea00a04084e0054004c004d00a181cf0481cc0200ffff08000000d389fe0bc98d23bfef683a874e048c59000000000200000054000000ca7a124b7c282f0c714e025b3f5486310100000000000000000000000000000047e7674810c28f0cdb956d0aa1f4cac4005fb744b102871e16b207d789b3da815b9fac95ba79da02c2ba0e134472979f4b592621000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000')
    85  - 
    86  -x = TSRemoteGuardPackageCred.load(data)
    87  -print(x.native)
    88  - 
    89  -print(x.native['credBuffer'])
    90  - 
    91  -credbuff = NTLM_REMOTE_SUPPLEMENTAL_CREDENTIAL.from_bytes(x.native['credBuffer'])
    92  -print(credbuff)
    93  - 
    94  -print(credbuff.reserved6[:16])
    95  -print(credbuff.reserved6[16:32])
    96  -print(credbuff.reserved6[32:])
    97  - 
    98  -data = bytes.fromhex('30820157a003020104a382014e0482014a050407ff0000001c00000000681ec3f55cbce0b2ef037dbe8afa8f7a6dbc5e7439ff675453fbcdb7cfd0c5b4af942453acf92677f21be4dc829c9e553280c5f37c08c5db5453330328d2fb8e65dd3f1a4f934d6888645365fee5e65888219f6dcaf5020ecea4a91595c1c95b1e3229d1b389c2ee23876e1c89a2fa3b06610201fce4a2427c8a95d52234c2343fbaaeeedecd636a0dc8b3fad75b3be9736ab488183e0bdc793dcce61fc019811970c1d56a02fcd7827ea64f60e163adf01b47ae1641d48ac719ba77c78ec5818d6a09c41c38da69b359a63a602319ab735243acf6c18d40307290d622bf6546a4650494b481228fcaaa62393e29fd10e6d31646eed50fa658eb0d8454ccf88dc4bf859cd98feed69d17fafce30bd03a6e0ccdccd55ba7cc146b60648308cabd2ee989e711675e4150da79ea9070df22223d2df1e41ed687a6575a2ecbce')
    99  -x = TSRequest.load(data)
    100  -print(x.native)
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/credssp/native.py
    1  - 
    2  -import os
    3  -from aardwolf import logger
    4  -from aardwolf.authentication.spnego.native import SPNEGO
    5  -from aardwolf.authentication.credssp.messages.asn1_structs import NegoDatas, \
    6  - NegoData, TSRequest, TSRequest, TSPasswordCreds, TSCredentials, TSRemoteGuardCreds, \
    7  - TSRemoteGuardPackageCred
    8  -from hashlib import sha256
    9  -from asn1crypto.x509 import Certificate
    10  - 
    11  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-cssp/385a7489-d46b-464c-b224-f7340e308a5c
    12  - 
    13  -class CredSSPAuth:
    14  - def __init__(self, mode = 'CLIENT'):
    15  - self.mode = mode
    16  - self.auth_ctx:SPNEGO = None
    17  - self.credtype:str = None
    18  - self.cred = None
    19  - self.version = 4 #ver 5 and 6 not working TODO
    20  - self.nonce = os.urandom(32)
    21  - self.__internal_auth_continue = True
    22  - self.seqno = 0
    23  - 
    24  - @staticmethod
    25  - def certificate_to_pubkey(certdata):
    26  - # credSSP auth requires knowledge of the server's public key
    27  - cert = Certificate.load(certdata)
    28  - return cert['tbs_certificate']['subject_public_key_info']['public_key'].dump()[5:] #why?
    29  - 
    30  - def get_extra_info(self):
    31  - return self.auth_ctx.get_extra_info()
    32  - 
    33  - async def authenticate(self, token, flags = None, certificate = None, remote_credguard = False):
    34  - try:
    35  - # currently only SSPI supported
    36  - 
    37  - if certificate is not None:
    38  - pubkey = CredSSPAuth.certificate_to_pubkey(certificate)
    39  - if self.mode != 'CLIENT':
    40  - raise NotImplementedError()
    41  - else:
    42  - if token is None:
    43  - # initial auth
    44  - returndata, self.__internal_auth_continue, err = await self.auth_ctx.authenticate(token, flags = flags, seq_number = None)
    45  - if err is not None:
    46  - raise err
    47  -
    48  - negotoken = {
    49  - 'negoToken' : returndata
    50  - }
    51  - retoken = {
    52  - 'version' : self.version,
    53  - 'negoTokens' : NegoDatas([NegoData(negotoken)])
    54  - }
    55  - return TSRequest(retoken).dump(), True, None
    56  - else:
    57  - if self.__internal_auth_continue is True:
    58  - tdata = TSRequest.load(token)
    59  - if tdata.native['version'] < self.version:
    60  - logger.debug('[CREDSSP] Server supports version %s which is smaller than our supported version %s' % (tdata.native['version'], self.version))
    61  - self.version = tdata.native['version']
    62  - if tdata.native['negoTokens'] is None:
    63  - raise Exception('SSPI auth not supported by server')
    64  - sspitoken = tdata.native['negoTokens'][0]['negoToken']
    65  - returndata, self.__internal_auth_continue, err = await self.auth_ctx.authenticate(sspitoken, flags = flags)
    66  - if err is not None:
    67  - raise err
    68  -
    69  - negotoken = {
    70  - 'negoToken' : returndata
    71  - }
    72  - retoken = {
    73  - 'version' : self.version,
    74  - }
    75  - if returndata is not None:
    76  - retoken['negoTokens'] = NegoDatas([NegoData(negotoken)])
    77  -
    78  -
    79  - if self.__internal_auth_continue is False:
    80  - if self.version in [5,6]:
    81  - ClientServerHashMagic = b"CredSSP Client-To-Server Binding Hash\x00"
    82  - ClientServerHash = sha256(ClientServerHashMagic + self.nonce + pubkey).digest()
    83  - sealedMessage, signature = await self.auth_ctx.encrypt(ClientServerHash, self.seqno)
    84  - self.seqno += 1
    85  - #print(sealedMessage)
    86  - retoken['pubKeyAuth'] = signature+sealedMessage
    87  - retoken['clientNonce'] = self.nonce
    88  -
    89  - elif self.version in [2,3,4]:
    90  - sealedMessage, signature = await self.auth_ctx.encrypt(pubkey, self.seqno)
    91  - self.seqno += 1
    92  - #print(sealedMessage)
    93  - retoken['pubKeyAuth'] = signature+sealedMessage
    94  -
    95  - return TSRequest(retoken).dump(), True, None
    96  - else:
    97  - # sub-level auth protocol finished, now for the other stuff
    98  -
    99  - # waiting for server to reply with the re-encrypted verification string + b'\x01'
    100  - tdata = TSRequest.load(token).native
    101  - if tdata['errorCode'] is not None:
    102  - raise Exception('CredSSP - Server sent an error! Code: %s' % hex(tdata['errorCode'] & (2**32-1)))
    103  - if tdata['pubKeyAuth'] is None:
    104  - raise Exception('Missing pubKeyAuth')
    105  - verification_data, _ = await self.auth_ctx.decrypt(tdata['pubKeyAuth'], 0)
    106  - #print('DEC: %s' % verification_data)
    107  - 
    108  - # at this point the verification should be implemented
    109  - # TODO: maybe later...
    110  - 
    111  -
    112  - # sending credentials
    113  - if remote_credguard is False:
    114  - creds = {
    115  - 'domainName' : b'',
    116  - 'userName' : b'',
    117  - 'password' : b'',
    118  - }
    119  - if self.cred.password is not None:
    120  - creds = {
    121  - 'domainName' : self.cred.domain.encode('utf-16-le'),
    122  - 'userName' : self.cred.username.encode('utf-16-le'),
    123  - 'password' : self.cred.password.encode('utf-16-le'),
    124  - }
    125  -
    126  -
    127  - res = TSPasswordCreds(creds)
    128  - res = TSCredentials({'credType': 1, 'credentials': res.dump()})
    129  - sealedMessage, signature = await self.auth_ctx.encrypt(res.dump(), self.seqno) #seq number must be incremented here..
    130  - self.seqno += 1
    131  - retoken = {
    132  - 'version' : self.version,
    133  - 'authInfo' : signature+sealedMessage
    134  - }
    135  - return TSRequest(retoken).dump(), False, None
    136  - else:
    137  - credBuffer = b''
    138  - data = {
    139  - 'packageName' : 'KERBEROS'.encode('utf-16-le'), #dont forget the encoding!
    140  - 'credBuffer' : credBuffer
    141  - }
    142  - 
    143  - remcredguardcreds = TSRemoteGuardCreds({
    144  - 'logonCred' : TSRemoteGuardPackageCred(data),
    145  - #'supplementalCreds' : [TSRemoteGuardPackageCred(xxxx)]
    146  - })
    147  - 
    148  - #print(remcredguardcreds)
    149  - sealedMessage, signature = await self.auth_ctx.encrypt(remcredguardcreds.dump(), self.seqno) #seq number must be incremented here..
    150  - self.seqno += 1
    151  - 
    152  - retoken = {
    153  - 'version' : self.version,
    154  - 'authInfo' : signature+sealedMessage
    155  - }
    156  - return TSRequest(retoken).dump(), False, None
    157  -
    158  - except Exception as e:
    159  - return None, None, e
  • ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/__init__.py
    1  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/gssapi.py
    1  -import enum
    2  -import io
    3  -import os
    4  - 
    5  -from asn1crypto.core import ObjectIdentifier
    6  - 
    7  -from minikerberos.protocol.constants import EncryptionType
    8  -from minikerberos.protocol import encryption
    9  -from unicrypto import hmac
    10  -from unicrypto.hashlib import md5
    11  -from unicrypto.symmetric import RC4
    12  - 
    13  -#TODO: RC4 support!
    14  - 
    15  -# https://tools.ietf.org/html/draft-raeburn-krb-rijndael-krb-05
    16  -# https://tools.ietf.org/html/rfc2478
    17  -# https://tools.ietf.org/html/draft-ietf-krb-wg-gssapi-cfx-02
    18  -# https://tools.ietf.org/html/rfc4757
    19  -# https://www.rfc-editor.org/errata/rfc4757
    20  - 
    21  -GSS_WRAP_HEADER = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
    22  -GSS_WRAP_HEADER_OID = b'\x60\x2b\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
    23  - 
    24  -class KRB5_MECH_INDEP_TOKEN:
    25  - # https://tools.ietf.org/html/rfc2743#page-81
    26  - # Mechanism-Independent Token Format
    27  - 
    28  - def __init__(self, data, oid, remlen = None):
    29  - self.oid = oid
    30  - self.data = data
    31  - 
    32  - #dont set this
    33  - self.length = remlen
    34  -
    35  - @staticmethod
    36  - def from_bytes(data):
    37  - return KRB5_MECH_INDEP_TOKEN.from_buffer(io.BytesIO(data))
    38  -
    39  - @staticmethod
    40  - def from_buffer(buff):
    41  -
    42  - start = buff.read(1)
    43  - if start != b'\x60':
    44  - raise Exception('Incorrect token data!')
    45  - remaining_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    46  - token_data = buff.read(remaining_length)
    47  -
    48  - buff = io.BytesIO(token_data)
    49  - pos = buff.tell()
    50  - buff.read(1)
    51  - oid_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    52  - buff.seek(pos)
    53  - token_oid = ObjectIdentifier.load(buff.read(oid_length+2))
    54  -
    55  - return KRB5_MECH_INDEP_TOKEN(buff.read(), str(token_oid), remlen = remaining_length)
    56  -
    57  - @staticmethod
    58  - def decode_length_buffer(buff):
    59  - lf = buff.read(1)[0]
    60  - if lf <= 127:
    61  - length = lf
    62  - else:
    63  - bcount = lf - 128
    64  - length = int.from_bytes(buff.read(bcount), byteorder = 'big', signed = False)
    65  - return length
    66  -
    67  - @staticmethod
    68  - def encode_length(length):
    69  - if length <= 127:
    70  - return length.to_bytes(1, byteorder = 'big', signed = False)
    71  - else:
    72  - lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
    73  - return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
    74  -
    75  -
    76  - def to_bytes(self):
    77  - t = ObjectIdentifier(self.oid).dump() + self.data
    78  - t = b'\x60' + KRB5_MECH_INDEP_TOKEN.encode_length(len(t)) + t
    79  - return t[:-len(self.data)] , self.data
    80  - 
    81  - 
    82  -class GSSAPIFlags(enum.IntFlag):
    83  - GSS_C_DCE_STYLE = 0x1000
    84  - GSS_C_DELEG_FLAG = 1
    85  - GSS_C_MUTUAL_FLAG = 2
    86  - GSS_C_REPLAY_FLAG = 4
    87  - GSS_C_SEQUENCE_FLAG = 8
    88  - GSS_C_CONF_FLAG = 0x10
    89  - GSS_C_INTEG_FLAG = 0x20
    90  -
    91  -class KG_USAGE(enum.Enum):
    92  - ACCEPTOR_SEAL = 22
    93  - ACCEPTOR_SIGN = 23
    94  - INITIATOR_SEAL = 24
    95  - INITIATOR_SIGN = 25
    96  -
    97  -class FlagsField(enum.IntFlag):
    98  - SentByAcceptor = 0
    99  - Sealed = 2
    100  - AcceptorSubkey = 4
    101  - 
    102  -# https://tools.ietf.org/html/rfc4757 (7.2)
    103  -class GSSMIC_RC4:
    104  - def __init__(self):
    105  - self.TOK_ID = b'\x01\x01'
    106  - self.SGN_ALG = b'\x11\x00' #HMAC
    107  - self.Filler = b'\xff'*4
    108  - self.SND_SEQ = None
    109  - self.SGN_CKSUM = None
    110  - 
    111  - @staticmethod
    112  - def from_bytes(data):
    113  - return GSSMIC_RC4.from_buffer(io.BytesIO(data))
    114  -
    115  - @staticmethod
    116  - def from_buffer(buff):
    117  - mic = GSSMIC_RC4()
    118  - mic.TOK_ID = buff.read(2)
    119  - mic.SGN_ALG = buff.read(2)
    120  - mic.Filler = buff.read(4)
    121  - mic.SND_SEQ = buff.read(8)
    122  - mic.SGN_CKSUM = buff.read(8)
    123  -
    124  - return mic
    125  -
    126  - def to_bytes(self):
    127  - t = self.TOK_ID
    128  - t += self.SGN_ALG
    129  - t += self.Filler
    130  - t += self.SND_SEQ
    131  - if self.SGN_CKSUM is not None:
    132  - t += self.SGN_CKSUM
    133  -
    134  - return t
    135  -
    136  -class GSSWRAP_RC4:
    137  - def __init__(self):
    138  - self.TOK_ID = b'\x02\x01'
    139  - self.SGN_ALG = b'\x11\x00' #HMAC
    140  - self.SEAL_ALG = None
    141  - self.Filler = b'\xFF' * 2
    142  - self.SND_SEQ = None
    143  - self.SGN_CKSUM = None
    144  - self.Confounder = None
    145  - 
    146  - def __str__(self):
    147  - t = 'GSSWRAP_RC4\r\n'
    148  - t += 'TOK_ID : %s\r\n' % self.TOK_ID.hex()
    149  - t += 'SGN_ALG : %s\r\n' % self.SGN_ALG.hex()
    150  - t += 'SEAL_ALG : %s\r\n' % self.SEAL_ALG.hex()
    151  - t += 'Filler : %s\r\n' % self.Filler.hex()
    152  - t += 'SND_SEQ : %s\r\n' % self.SND_SEQ.hex()
    153  - t += 'SGN_CKSUM : %s\r\n' % self.SGN_CKSUM.hex()
    154  - t += 'Confounder : %s\r\n' % self.Confounder.hex()
    155  - return t
    156  -
    157  - @staticmethod
    158  - def from_bytes(data):
    159  - return GSSWRAP_RC4.from_buffer(io.BytesIO(data))
    160  -
    161  - @staticmethod
    162  - def from_buffer(buff):
    163  - wrap = GSSWRAP_RC4()
    164  - wrap.TOK_ID = buff.read(2)
    165  - wrap.SGN_ALG = buff.read(2)
    166  - wrap.SEAL_ALG = buff.read(2)
    167  - wrap.Filler = buff.read(2)
    168  - wrap.SND_SEQ = buff.read(8)
    169  - wrap.SGN_CKSUM = buff.read(8)
    170  - wrap.Confounder = buff.read(8)
    171  -
    172  - return wrap
    173  -
    174  - def to_bytes(self):
    175  - t = self.TOK_ID
    176  - t += self.SGN_ALG
    177  - t += self.SEAL_ALG
    178  - t += self.Filler
    179  - t += self.SND_SEQ
    180  -
    181  - if self.SGN_CKSUM:
    182  - t += self.SGN_CKSUM
    183  - if self.Confounder:
    184  - t += self.Confounder
    185  -
    186  -
    187  - return t
    188  -
    189  -class GSSAPI_RC4:
    190  - def __init__(self, session_key):
    191  - self.session_key = session_key
    192  -
    193  - def GSS_GetMIC(self, data, sequenceNumber, direction = 'init'):
    194  - raise Exception('Not tested! Sure it needs some changes')
    195  - GSS_GETMIC_HEADER = b'\x60\x23\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02'
    196  -
    197  - # Let's pad the data
    198  - pad = (4 - (len(data) % 4)) & 0x3
    199  - padStr = bytes([pad]) * pad
    200  - data += padStr
    201  -
    202  - mic = GSSMIC_RC4()
    203  -
    204  - if direction == 'init':
    205  - mic.SND_SEQ = sequenceNumber.to_bytes(4, 'big', signed = False) + b'\x00'*4
    206  - else:
    207  - mic.SND_SEQ = sequenceNumber.to_bytes(4, 'big', signed = False) + b'\xff'*4
    208  -
    209  - Ksign_ctx = hmac_md5(self.session_key.contents, digestmod='md5')
    210  - Ksign_ctx.update(b'signaturekey\0')
    211  - Ksign = Ksign_ctx.digest()
    212  -
    213  - id = 15
    214  - temp = md5( id.to_bytes(4, 'little', signed = False) + mic.to_bytes()[:8] ).digest()
    215  - chksum_ctx = hmac_md5(Ksign, digestmod='md5')
    216  - chksum_ctx.update(temp)
    217  - mic.SGN_CKSUM = chksum_ctx.digest()[:8]
    218  -
    219  - id = 0
    220  - temp = hmac_md5(self.session_key.contents, digestmod='md5')
    221  - temp.update(id.to_bytes(4, 'little', signed = False))
    222  -
    223  - Kseq_ctx = hmac_md5(temp.digest(), digestmod='md5')
    224  - Kseq_ctx.update(mic.SGN_CKSUM)
    225  - Kseq = Kseq_ctx.digest()
    226  -
    227  - mic.SGN_CKSUM = RC4(Kseq).encrypt(mic.SND_SEQ)
    228  -
    229  - return GSS_GETMIC_HEADER + mic.to_bytes()
    230  -
    231  -
    232  - def GSS_Wrap(self, data, seq_num, direction = 'init', encrypt=True, cofounder = None):
    233  - #direction = 'a'
    234  - #seq_num = 0
    235  - #print('[GSS_Wrap] data: %s' % data)
    236  - #print('[GSS_Wrap] seq_num: %s' % seq_num.to_bytes(4, 'big', signed = False).hex())
    237  - #print('[GSS_Wrap] direction: %s' % direction)
    238  - #print('[GSS_Wrap] encrypt: %s' % encrypt)
    239  - #
    240  - #print('[GSS_Wrap] auth_data: %s' % auth_data)
    241  -
    242  - #pad = 0
    243  - if encrypt is True:
    244  - data += b'\x01'
    245  - #pad = (8 - (len(data) % 8)) & 0x7
    246  - #padStr = bytes([pad]) * pad
    247  - #data += padStr
    248  - #
    249  - ##data += b'\x08' * 8
    250  - #print('[GSS_Wrap] pad: %s' % pad)
    251  - #print('[GSS_Wrap] data padded: %s' % data)
    252  -
    253  - 
    254  - token = GSSWRAP_RC4()
    255  - token.SEAL_ALG = b'\x10\x00' # RC4
    256  -
    257  - if direction == 'init':
    258  - token.SND_SEQ = seq_num.to_bytes(4, 'big', signed = False) + b'\x00'*4
    259  - else:
    260  - token.SND_SEQ = seq_num.to_bytes(4, 'big', signed = False) + b'\xff'*4
    261  -
    262  - token.Confounder = os.urandom(8)
    263  - #if cofounder is not None:
    264  - # token.Confounder = cofounder
    265  - # #testing purposes only, pls remove
    266  -
    267  -
    268  - temp = hmac.new(self.session_key.contents, digestmod='md5')
    269  - temp.update(b'signaturekey\0')
    270  - Ksign = temp.digest()
    271  -
    272  - id = 13
    273  - Sgn_Cksum = md5(id.to_bytes(4, 'little', signed = False) + token.to_bytes()[:8] + token.Confounder + data).digest()
    274  -
    275  - klocal = b''
    276  - for b in self.session_key.contents:
    277  - klocal += bytes([b ^ 0xf0])
    278  - 
    279  - id = 0
    280  - temp = hmac.new(klocal, digestmod='md5')
    281  - temp.update(id.to_bytes(4, 'little', signed = False))
    282  - temp = hmac.new(temp.digest(), digestmod='md5')
    283  - temp.update(seq_num.to_bytes(4, 'big', signed = False))
    284  - Kcrypt = temp.digest()
    285  - 
    286  - temp = hmac.new(Ksign, digestmod='md5')
    287  - temp.update(Sgn_Cksum)
    288  - token.SGN_CKSUM = temp.digest()[:8]
    289  -
    290  - id = 0
    291  - temp = hmac.new(self.session_key.contents, digestmod='md5')
    292  - temp.update(id.to_bytes(4, 'little', signed = False))
    293  - temp = hmac.new(temp.digest(), digestmod='md5')
    294  - temp.update(token.SGN_CKSUM)
    295  - Kseq = temp.digest()
    296  -
    297  - token.SND_SEQ = RC4(Kseq).encrypt(token.SND_SEQ)
    298  -
    299  -
    300  - #if auth_data is not None:
    301  - if encrypt is False:
    302  - #print('Unwrap sessionkey: %s' % self.session_key.contents.hex())
    303  - #print('Unwrap data : %s' % data.hex())
    304  - 
    305  - sspi_wrap = KRB5_MECH_INDEP_TOKEN.from_bytes(data)
    306  - 
    307  - hdr = sspi_wrap.data[:32]
    308  - data = sspi_wrap.data[32:]
    309  - 
    310  - wrap = GSSWRAP_RC4.from_bytes(hdr)
    311  -
    312  - id = 0
    313  - temp = hmac.new(self.session_key.contents, digestmod='md5')
    314  - temp.update(id.to_bytes(4, 'little', signed = False))
    315  - temp = hmac.new(temp.digest(), digestmod='md5')
    316  - temp.update(wrap.SGN_CKSUM)
    317  - Kseq = temp.digest()
    318  -
    319  - snd_seq = RC4(Kseq).encrypt(wrap.SND_SEQ)
    320  -
    321  - id = 0
    322  - temp = hmac.new(klocal, digestmod='md5')
    323  - temp.update(id.to_bytes(4, 'little', signed = False))
    324  - temp = hmac.new(temp.digest(), digestmod='md5')
    325  - temp.update(snd_seq[:4])
    326  - Kcrypt = temp.digest()
    327  -
    328  - rc4 = RC4(Kcrypt)
    329  - dec_cofounder = rc4.decrypt(wrap.Confounder)
    330  - dec_data = rc4.decrypt(data)
    331  - 
    332  - id = 13
    333  - Sgn_Cksum_calc = md5(id.to_bytes(4, 'little', signed = False) + wrap.to_bytes()[:8] + dec_cofounder + dec_data).digest()
    334  - 
    335  - temp = hmac.new(Ksign, digestmod='md5')
    336  - temp.update(Sgn_Cksum_calc)
    337  - Sgn_Cksum_calc = temp.digest()[:8]
    338  - 
    339  - if wrap.SGN_CKSUM != Sgn_Cksum_calc[:8]:
    340  - return None, Exception('Integrity verification failed')
    341  - 
    342  - pad = 1
    343  - return dec_data[:-pad], None
    344  -
    345  - elif encrypt is True:
    346  - rc4 = RC4(Kcrypt)
    347  - token.Confounder = rc4.encrypt(token.Confounder)
    348  - cipherText = rc4.encrypt(data)
    349  - finalData, cipherText = KRB5_MECH_INDEP_TOKEN( token.to_bytes() + cipherText, '1.2.840.113554.1.2.2' ).to_bytes()
    350  - 
    351  - 
    352  - #print('cipherText %s' % cipherText.hex())
    353  - #print('finalData %s' % finalData.hex())
    354  - #print('sessionkey %s' % self.session_key.contents.hex())
    355  - return cipherText, finalData
    356  -
    357  - 
    358  - def GSS_Unwrap(self, data, seq_num, direction='init'):
    359  - #print('GSS_Unwrap data : %s' % data)
    360  - dec_data, err = self.GSS_Wrap(data, seq_num, direction=direction, encrypt = False)
    361  - #print('GSS_Unwrap decrypted data : %s' % dec_data)
    362  - return dec_data, err
    363  -
    364  -# 4.2.6.1. MIC Tokens
    365  -class GSSMIC:
    366  - def __init__(self):
    367  - self.TOK_ID = b'\x04\x04'
    368  - self.Flags = None
    369  - self.Filler = b'\xFF' * 5
    370  - self.SND_SEQ = None
    371  - self.SGN_CKSUM = None
    372  -
    373  - @staticmethod
    374  - def from_bytes(data):
    375  - return GSSMIC.from_buffer(io.BytesIO(data))
    376  -
    377  - @staticmethod
    378  - def from_buffer(buff):
    379  - m = GSSMIC()
    380  - m.TOK_ID = buff.read(2)
    381  - m.Flags = FlagsField(int.from_bytes(buff.read(1), 'big', signed = False))
    382  - m.Filler = buff.read(5)
    383  - m.SND_SEQ = int.from_bytes(buff.read(8), 'big', signed = False)
    384  - m.SGN_CKSUM = buff.read() #should know the size based on the algo!
    385  - return m
    386  -
    387  - def to_bytes(self):
    388  - t = self.TOK_ID
    389  - t += self.Flags.to_bytes(1, 'big', signed = False)
    390  - t += self.Filler
    391  - t += self.SND_SEQ.to_bytes(8, 'big', signed = False)
    392  - if self.SGN_CKSUM is not None:
    393  - t += self.SGN_CKSUM
    394  -
    395  - return t
    396  -
    397  -# 4.2.6.2. Wrap Tokens
    398  -class GSSWrapToken:
    399  - def __init__(self):
    400  - self.TOK_ID = b'\x05\x04'
    401  - self.Flags = None
    402  - self.Filler = b'\xFF'
    403  - self.EC = None
    404  - self.RRC = None
    405  - self.SND_SEQ = None
    406  - self.Data = None
    407  -
    408  - @staticmethod
    409  - def from_bytes(data):
    410  - return GSSWrapToken.from_buffer(io.BytesIO(data))
    411  -
    412  - @staticmethod
    413  - def from_buffer(buff):
    414  - m = GSSWrapToken()
    415  - m.TOK_ID = buff.read(2)
    416  - m.Flags = FlagsField(int.from_bytes(buff.read(1), 'big', signed = False))
    417  - m.Filler = buff.read(1)
    418  - m.EC = int.from_bytes(buff.read(2), 'big', signed = False)
    419  - m.RRC = int.from_bytes(buff.read(2), 'big', signed = False)
    420  - m.SND_SEQ = int.from_bytes(buff.read(8), 'big', signed = False)
    421  - return m
    422  -
    423  - def to_bytes(self):
    424  - t = self.TOK_ID
    425  - t += self.Flags.to_bytes(1, 'big', signed = False)
    426  - t += self.Filler
    427  - t += self.EC.to_bytes(2, 'big', signed = False)
    428  - t += self.RRC.to_bytes(2, 'big', signed = False)
    429  - t += self.SND_SEQ.to_bytes(8, 'big', signed = False)
    430  - if self.Data is not None:
    431  - t += self.Data
    432  -
    433  - return t
    434  -
    435  -class GSSAPI_AES:
    436  - def __init__(self, session_key, cipher_type, checksum_profile):
    437  - self.session_key = session_key
    438  - self.checksum_profile = checksum_profile
    439  - self.cipher_type = cipher_type
    440  - self.cipher = None
    441  -
    442  - def rotate(self, data, numBytes):
    443  - numBytes %= len(data)
    444  - left = len(data) - numBytes
    445  - result = data[left:] + data[:left]
    446  - return result
    447  -
    448  - def unrotate(self, data, numBytes):
    449  - numBytes %= len(data)
    450  - result = data[numBytes:] + data[:numBytes]
    451  - return result
    452  -
    453  - def GSS_GetMIC(self, data, seq_num):
    454  - pad = (4 - (len(data) % 4)) & 0x3
    455  - padStr = bytes([pad]) * pad
    456  - data += padStr
    457  -
    458  - m = GSSMIC()
    459  - m.Flags = FlagsField.AcceptorSubkey
    460  - m.SND_SEQ = seq_num
    461  - checksum_profile = self.checksum_profile()
    462  - m.checksum = checksum_profile.checksum(self.session_key, KG_USAGE.INITIATOR_SIGN.value, data + m.to_bytes()[:16])
    463  -
    464  - return m.to_bytes()
    465  -
    466  - def GSS_Wrap(self, data, seq_num, use_padding = False):
    467  - #print('[GSS_Wrap] seq_num: %s' % seq_num.to_bytes(4, 'big', signed = False).hex())
    468  - cipher = self.cipher_type()
    469  - pad = 0
    470  - if use_padding is True:
    471  - pad = ((cipher.blocksize - len(data)) % cipher.blocksize) #(cipher.blocksize - (len(data) % cipher.blocksize)) & 15
    472  - padStr = b'\xFF' * pad
    473  - data += padStr
    474  -
    475  - t = GSSWrapToken()
    476  - t.Flags = FlagsField.AcceptorSubkey | FlagsField.Sealed
    477  - t.EC = pad
    478  - t.RRC = 0
    479  - t.SND_SEQ = seq_num
    480  -
    481  - #print('Wrap data: %s' % (data + t.to_bytes()))
    482  - cipher_text = cipher.encrypt(self.session_key, KG_USAGE.INITIATOR_SEAL.value, data + t.to_bytes(), None)
    483  - t.RRC = 28 #[RFC4121] section 4.2.5
    484  - cipher_text = self.rotate(cipher_text, t.RRC + t.EC)
    485  -
    486  - ret1 = cipher_text
    487  - ret2 = t.to_bytes()
    488  - 
    489  - return ret1, ret2
    490  -
    491  - def GSS_Unwrap(self, data, seq_num, direction='init', auth_data = None, use_padding = False):
    492  - #print('')
    493  - #print('Unwrap data %s' % data[16:])
    494  - #print('Unwrap hdr %s' % data[:16])
    495  - 
    496  - cipher = self.cipher_type()
    497  - original_hdr = GSSWrapToken.from_bytes(data[:16])
    498  - rotated = data[16:]
    499  -
    500  - cipher_text = self.unrotate(rotated, original_hdr.RRC + original_hdr.EC)
    501  - plain_text = cipher.decrypt(self.session_key, KG_USAGE.ACCEPTOR_SEAL.value, cipher_text)
    502  - new_hdr = GSSWrapToken.from_bytes(plain_text[-16:])
    503  - 
    504  - #signature checking
    505  - new_hdr.RRC = 28
    506  - if data[:16] != new_hdr.to_bytes():
    507  - return None, Exception('GSS_Unwrap signature mismatch!')
    508  -
    509  - 
    510  - #print('Unwrap checksum: %s' % plain_text[-(original_hdr.EC + 16):])
    511  - #print('Unwrap orig chk: %s' % original_hdr.to_bytes())
    512  - #print('Unwrap result 1: %s' % plain_text)
    513  - #print('Unwrap result : %s' % plain_text[:-(original_hdr.EC + 16)])
    514  - return plain_text[:-(original_hdr.EC + 16)], None
    515  -
    516  -def get_gssapi(session_key):
    517  - if session_key.enctype == encryption.Enctype.AES256:
    518  - return GSSAPI_AES(session_key, encryption._AES256CTS, encryption._SHA1AES256)
    519  - if session_key.enctype == encryption.Enctype.AES128:
    520  - return GSSAPI_AES(session_key, encryption._AES128CTS, encryption._SHA1AES128)
    521  - elif session_key.enctype == encryption.Enctype.RC4:
    522  - return GSSAPI_RC4(session_key)
    523  - else:
    524  - raise Exception('Unsupported etype %s' % session_key.enctype)
    525  -
    526  -
    527  -def test():
    528  - data = b'\xAF' * 1024
    529  - session_key = encryption.Key( encryption.Enctype.AES256 , bytes.fromhex('3e242e91996aadd513ecb1bc2369e44183e08e08c51550fa4b681e77f75ed8e1'))
    530  - sequenceNumber = 0
    531  - gssapi = get_gssapi(session_key)
    532  - 
    533  - r1, r2 = gssapi.GSS_Wrap(data, sequenceNumber)
    534  - print(len(r2))
    535  - sent = r2 + r1
    536  - print(r1)
    537  - ret1, ret2 = gssapi.GSS_Unwrap(sent, sequenceNumber)
    538  - 
    539  - print(r1.hex())
    540  - print(ret1.hex())
    541  - 
    542  - 
    543  -if __name__ == '__main__':
    544  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/mpn.py
    1  - 
    2  -##
    3  -##
    4  -## Interface to allow remote kerberos authentication via Multiplexor
    5  -##
    6  -##
    7  -##
    8  -##
    9  -##
    10  -## TODO: RPC auth type is not implemented or tested!!!!
    11  - 
    12  -import enum
    13  -import asyncio
    14  - 
    15  -from aardwolf.authentication.spnego.asn1_structs import KRB5Token
    16  -from minikerberos.gssapi.gssapi import get_gssapi, GSSWrapToken
    17  -from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
    18  -from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
    19  - 
    20  -from mpnop.operator import MPNOPerator
    21  - 
    22  -import enum
    23  -import io
    24  -import os
    25  - 
    26  -from asn1crypto.core import ObjectIdentifier
    27  - 
    28  -class KRB5_MECH_INDEP_TOKEN:
    29  - # https://tools.ietf.org/html/rfc2743#page-81
    30  - # Mechanism-Independent Token Format
    31  - 
    32  - def __init__(self, data, oid, remlen = None):
    33  - self.oid = oid
    34  - self.data = data
    35  - 
    36  - #dont set this
    37  - self.length = remlen
    38  -
    39  - @staticmethod
    40  - def from_bytes(data):
    41  - return KRB5_MECH_INDEP_TOKEN.from_buffer(io.BytesIO(data))
    42  -
    43  - @staticmethod
    44  - def from_buffer(buff):
    45  -
    46  - start = buff.read(1)
    47  - if start != b'\x60':
    48  - raise Exception('Incorrect token data!')
    49  - remaining_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    50  - token_data = buff.read(remaining_length)
    51  -
    52  - buff = io.BytesIO(token_data)
    53  - pos = buff.tell()
    54  - buff.read(1)
    55  - oid_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    56  - buff.seek(pos)
    57  - token_oid = ObjectIdentifier.load(buff.read(oid_length+2))
    58  -
    59  - return KRB5_MECH_INDEP_TOKEN(buff.read(), str(token_oid), remlen = remaining_length)
    60  -
    61  - @staticmethod
    62  - def decode_length_buffer(buff):
    63  - lf = buff.read(1)[0]
    64  - if lf <= 127:
    65  - length = lf
    66  - else:
    67  - bcount = lf - 128
    68  - length = int.from_bytes(buff.read(bcount), byteorder = 'big', signed = False)
    69  - return length
    70  -
    71  - @staticmethod
    72  - def encode_length(length):
    73  - if length <= 127:
    74  - return length.to_bytes(1, byteorder = 'big', signed = False)
    75  - else:
    76  - lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
    77  - return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
    78  -
    79  -
    80  - def to_bytes(self):
    81  - t = ObjectIdentifier(self.oid).dump() + self.data
    82  - t = b'\x60' + KRB5_MECH_INDEP_TOKEN.encode_length(len(t)) + t
    83  - return t[:-len(self.data)] , self.data
    84  - 
    85  - 
    86  -class ISC_REQ(enum.IntFlag):
    87  - DELEGATE = 1
    88  - MUTUAL_AUTH = 2
    89  - REPLAY_DETECT = 4
    90  - SEQUENCE_DETECT = 8
    91  - CONFIDENTIALITY = 16
    92  - USE_SESSION_KEY = 32
    93  - PROMPT_FOR_CREDS = 64
    94  - USE_SUPPLIED_CREDS = 128
    95  - ALLOCATE_MEMORY = 256
    96  - USE_DCE_STYLE = 512
    97  - DATAGRAM = 1024
    98  - CONNECTION = 2048
    99  - CALL_LEVEL = 4096
    100  - FRAGMENT_SUPPLIED = 8192
    101  - EXTENDED_ERROR = 16384
    102  - STREAM = 32768
    103  - INTEGRITY = 65536
    104  - IDENTIFY = 131072
    105  - NULL_SESSION = 262144
    106  - MANUAL_CRED_VALIDATION = 524288
    107  - RESERVED1 = 1048576
    108  - FRAGMENT_TO_FIT = 2097152
    109  - HTTP = 0x10000000
    110  - 
    111  -class RDPKerberosMPN:
    112  - def __init__(self, settings):
    113  - self.iterations = 0
    114  - self.settings = settings
    115  - self.operator = settings.operator
    116  - self.agent_id = settings.agent_id
    117  - self.mode = 'CLIENT'
    118  - self.ksspi = None
    119  - self.client = None
    120  - self.target = None
    121  - self.gssapi = None
    122  - self.etype = None
    123  - self.session_key = None
    124  - self.seq_number = None
    125  -
    126  - self.setup()
    127  -
    128  - def setup(self):
    129  - return
    130  - 
    131  - def get_seq_number(self):
    132  - """
    133  - Fetches the starting sequence number. This is either zero or can be found in the authenticator field of the
    134  - AP_REQ structure. As windows uses a random seq number AND a subkey as well, we can't obtain it by decrypting the
    135  - AP_REQ structure. Insead under the hood we perform an encryption operation via EncryptMessage API which will
    136  - yield the start sequence number
    137  - """
    138  - return self.seq_number
    139  -
    140  - async def encrypt(self, data, message_no):
    141  - return self.gssapi.GSS_Wrap(data, message_no)
    142  -
    143  - async def decrypt(self, data, message_no, direction='init', auth_data=None):
    144  - return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
    145  -
    146  - def get_session_key(self):
    147  - return self.session_key
    148  -
    149  - async def authenticate(self, authData = None, flags = None, seq_number = 0, is_rpc = False):
    150  - #authdata is only for api compatibility reasons
    151  - if self.operator is None:
    152  - self.operator = MPNOPerator(self.settings.get_url())
    153  - asyncio.create_task(self.operator.run())
    154  - await asyncio.wait_for(self.operator.connected_evt.wait(), timeout=self.settings.timeout)
    155  - if self.ksspi is None:
    156  - self.ksspi, err = await self.operator.create_sspi(self.agent_id)
    157  - if err is not None:
    158  - return None, None, err
    159  - try:
    160  - if is_rpc == True:
    161  - if self.iterations == 0:
    162  - flags = ISC_REQ.CONFIDENTIALITY | \
    163  - ISC_REQ.INTEGRITY | \
    164  - ISC_REQ.MUTUAL_AUTH | \
    165  - ISC_REQ.REPLAY_DETECT | \
    166  - ISC_REQ.SEQUENCE_DETECT|\
    167  - ISC_REQ.USE_DCE_STYLE
    168  -
    169  - context_attributes, apreq, err = await self.ksspi.kerberos('termsrv/%s' % self.settings.target, context_attributes = flags.value)
    170  - if err is not None:
    171  - raise err
    172  - 
    173  - self.iterations += 1
    174  - return apreq, True, None
    175  -
    176  - elif self.iterations == 1:
    177  - context_attributes, data, err = await self.ksspi.kerberos(target_name = 'termsrv/%s' % self.settings.target, context_attributes = flags.value, token_data = authData)
    178  - if err is not None:
    179  - return None, None, err
    180  - self.session_key, err = await self.ksspi.get_sessionkey()
    181  - if err is not None:
    182  - return None, None, err
    183  -
    184  - aprep = AP_REP.load(data).native
    185  - subkey = Key(aprep['enc-part']['etype'], self.session_key)
    186  - self.gssapi = get_gssapi(subkey)
    187  - 
    188  - if aprep['enc-part']['etype'] != 23: #no need for seq number in rc4
    189  - raw_seq_data, err = await self.ksspi.get_sequenceno()
    190  - if err is not None:
    191  - return None, None, err
    192  - self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
    193  -
    194  - self.iterations += 1
    195  - await self.ksspi.disconnect()
    196  - return data, False, None
    197  -
    198  - else:
    199  - raise Exception('SSPI Kerberos -RPC - auth encountered too many calls for authenticate.')
    200  -
    201  -
    202  - else:
    203  - context_attributes, apreq, err = await self.ksspi.kerberos(target_name = 'termsrv/%s' % self.settings.target)
    204  - #print('MULTIPLEXOR KERBEROS SSPI, APREQ: %s ERROR: %s' % (apreq, res))
    205  - if err is not None:
    206  - raise err
    207  -
    208  - self.session_key, err = await self.ksspi.get_sessionkey()
    209  - if err is not None:
    210  - raise err
    211  - await self.ksspi.disconnect()
    212  - 
    213  - return apreq, False, None
    214  - except Exception as e:
    215  - return None, None, err
    216  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/multiplexor.py
    1  - 
    2  -##
    3  -##
    4  -## Interface to allow remote kerberos authentication via Multiplexor
    5  -##
    6  -##
    7  -##
    8  -##
    9  -##
    10  -## TODO: RPC auth type is not implemented or tested!!!!
    11  - 
    12  -import enum
    13  - 
    14  -from aardwolf.authentication.spnego.asn1_structs import KRB5Token
    15  -from minikerberos.gssapi.gssapi import get_gssapi, GSSWrapToken
    16  -from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
    17  -from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
    18  - 
    19  -from multiplexor.operator.external.sspi import KerberosSSPIClient
    20  -from multiplexor.operator import MultiplexorOperator
    21  - 
    22  -import enum
    23  -import io
    24  -import os
    25  - 
    26  -from asn1crypto.core import ObjectIdentifier
    27  - 
    28  -class KRB5_MECH_INDEP_TOKEN:
    29  - # https://tools.ietf.org/html/rfc2743#page-81
    30  - # Mechanism-Independent Token Format
    31  - 
    32  - def __init__(self, data, oid, remlen = None):
    33  - self.oid = oid
    34  - self.data = data
    35  - 
    36  - #dont set this
    37  - self.length = remlen
    38  -
    39  - @staticmethod
    40  - def from_bytes(data):
    41  - return KRB5_MECH_INDEP_TOKEN.from_buffer(io.BytesIO(data))
    42  -
    43  - @staticmethod
    44  - def from_buffer(buff):
    45  -
    46  - start = buff.read(1)
    47  - if start != b'\x60':
    48  - raise Exception('Incorrect token data!')
    49  - remaining_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    50  - token_data = buff.read(remaining_length)
    51  -
    52  - buff = io.BytesIO(token_data)
    53  - pos = buff.tell()
    54  - buff.read(1)
    55  - oid_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    56  - buff.seek(pos)
    57  - token_oid = ObjectIdentifier.load(buff.read(oid_length+2))
    58  -
    59  - return KRB5_MECH_INDEP_TOKEN(buff.read(), str(token_oid), remlen = remaining_length)
    60  -
    61  - @staticmethod
    62  - def decode_length_buffer(buff):
    63  - lf = buff.read(1)[0]
    64  - if lf <= 127:
    65  - length = lf
    66  - else:
    67  - bcount = lf - 128
    68  - length = int.from_bytes(buff.read(bcount), byteorder = 'big', signed = False)
    69  - return length
    70  -
    71  - @staticmethod
    72  - def encode_length(length):
    73  - if length <= 127:
    74  - return length.to_bytes(1, byteorder = 'big', signed = False)
    75  - else:
    76  - lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
    77  - return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
    78  -
    79  -
    80  - def to_bytes(self):
    81  - t = ObjectIdentifier(self.oid).dump() + self.data
    82  - t = b'\x60' + KRB5_MECH_INDEP_TOKEN.encode_length(len(t)) + t
    83  - return t[:-len(self.data)] , self.data
    84  - 
    85  - 
    86  -class ISC_REQ(enum.IntFlag):
    87  - DELEGATE = 1
    88  - MUTUAL_AUTH = 2
    89  - REPLAY_DETECT = 4
    90  - SEQUENCE_DETECT = 8
    91  - CONFIDENTIALITY = 16
    92  - USE_SESSION_KEY = 32
    93  - PROMPT_FOR_CREDS = 64
    94  - USE_SUPPLIED_CREDS = 128
    95  - ALLOCATE_MEMORY = 256
    96  - USE_DCE_STYLE = 512
    97  - DATAGRAM = 1024
    98  - CONNECTION = 2048
    99  - CALL_LEVEL = 4096
    100  - FRAGMENT_SUPPLIED = 8192
    101  - EXTENDED_ERROR = 16384
    102  - STREAM = 32768
    103  - INTEGRITY = 65536
    104  - IDENTIFY = 131072
    105  - NULL_SESSION = 262144
    106  - MANUAL_CRED_VALIDATION = 524288
    107  - RESERVED1 = 1048576
    108  - FRAGMENT_TO_FIT = 2097152
    109  - HTTP = 0x10000000
    110  - 
    111  -class RDPKerberosMultiplexor:
    112  - def __init__(self, settings):
    113  - self.iterations = 0
    114  - self.settings = settings
    115  - self.mode = 'CLIENT'
    116  - self.ksspi = None
    117  - self.client = None
    118  - self.target = None
    119  - self.gssapi = None
    120  - self.etype = None
    121  - self.session_key = None
    122  - self.seq_number = None
    123  -
    124  - self.setup()
    125  -
    126  - def setup(self):
    127  - return
    128  - 
    129  - def get_seq_number(self):
    130  - """
    131  - Fetches the starting sequence number. This is either zero or can be found in the authenticator field of the
    132  - AP_REQ structure. As windows uses a random seq number AND a subkey as well, we can't obtain it by decrypting the
    133  - AP_REQ structure. Insead under the hood we perform an encryption operation via EncryptMessage API which will
    134  - yield the start sequence number
    135  - """
    136  - return self.seq_number
    137  -
    138  - async def encrypt(self, data, message_no):
    139  - return self.gssapi.GSS_Wrap(data, message_no)
    140  -
    141  - async def decrypt(self, data, message_no, direction='init', auth_data=None):
    142  - return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
    143  -
    144  - def get_session_key(self):
    145  - return self.session_key
    146  -
    147  - async def authenticate(self, authData = None, flags = None, seq_number = 0, is_rpc = False):
    148  - #authdata is only for api compatibility reasons
    149  - if self.ksspi is None:
    150  - await self.start_remote_kerberos()
    151  - try:
    152  - if is_rpc == True:
    153  - if self.iterations == 0:
    154  - flags = ISC_REQ.CONFIDENTIALITY | \
    155  - ISC_REQ.INTEGRITY | \
    156  - ISC_REQ.MUTUAL_AUTH | \
    157  - ISC_REQ.REPLAY_DETECT | \
    158  - ISC_REQ.SEQUENCE_DETECT|\
    159  - ISC_REQ.USE_DCE_STYLE
    160  -
    161  - apreq, res = await self.ksspi.authenticate('termsrv/%s' % self.settings.target, flags = str(flags.value))
    162  - 
    163  - self.iterations += 1
    164  - return apreq, True, None
    165  -
    166  - elif self.iterations == 1:
    167  - data, err = await self.ksspi.authenticate('termsrv/%s' % self.settings.target, flags = str(flags.value), token_data = authData)
    168  - if err is not None:
    169  - return None, None, err
    170  - self.session_key, err = await self.ksspi.get_session_key()
    171  - if err is not None:
    172  - return None, None, err
    173  -
    174  - aprep = AP_REP.load(data).native
    175  - subkey = Key(aprep['enc-part']['etype'], self.session_key)
    176  - self.gssapi = get_gssapi(subkey)
    177  - 
    178  - if aprep['enc-part']['etype'] != 23: #no need for seq number in rc4
    179  - raw_seq_data, err = await self.ksspi.get_seq_number()
    180  - if err is not None:
    181  - return None, None, err
    182  - self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
    183  -
    184  - self.iterations += 1
    185  - await self.ksspi.disconnect()
    186  - return data, False, None
    187  -
    188  - else:
    189  - raise Exception('SSPI Kerberos -RPC - auth encountered too many calls for authenticate.')
    190  -
    191  -
    192  - else:
    193  - apreq, res = await self.ksspi.authenticate('termsrv/%s' % self.settings.target)
    194  - #print('MULTIPLEXOR KERBEROS SSPI, APREQ: %s ERROR: %s' % (apreq, res))
    195  - if res is None:
    196  - self.session_key, res = await self.ksspi.get_session_key()
    197  - await self.ksspi.disconnect()
    198  - 
    199  - return apreq, res, None
    200  - except Exception as e:
    201  - return None, None, err
    202  - 
    203  - async def start_remote_kerberos(self):
    204  - try:
    205  - #print(self.settings.get_url())
    206  - #print(self.settings.agent_id)
    207  - self.operator = MultiplexorOperator(self.settings.get_url())
    208  - await self.operator.connect()
    209  - #creating virtual sspi server
    210  - server_info = await self.operator.start_sspi(self.settings.agent_id)
    211  - #print(server_info)
    212  - 
    213  - sspi_url = 'ws://%s:%s' % (server_info['listen_ip'], server_info['listen_port'])
    214  - 
    215  - #print(sspi_url)
    216  - self.ksspi = KerberosSSPIClient(sspi_url)
    217  - await self.ksspi.connect()
    218  - except Exception as e:
    219  - import traceback
    220  - traceback.print_exc()
    221  - return None
    222  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/native.py
    1  -#
    2  -#
    3  -# This is just a simple interface to the minikerberos library to support SPNEGO
    4  -#
    5  -#
    6  -# - Links -
    7  -# 1. See minikerberos library
    8  - 
    9  -from minikerberos.protocol.asn1_structs import AP_REP, EncAPRepPart, Ticket
    10  -from aardwolf.authentication.kerberos.gssapi import get_gssapi, KRB5_MECH_INDEP_TOKEN
    11  -from minikerberos.protocol.structures import ChecksumFlags
    12  -from minikerberos.protocol.encryption import Key, _enctype_table
    13  -from minikerberos.aioclient import AIOKerberosClient
    14  -from aardwolf import logger
    15  - 
    16  - 
    17  -class RDPKerberos:
    18  - def __init__(self, settings):
    19  - self.settings = settings
    20  - self.signing_preferred = None
    21  - self.encryption_preferred = None
    22  - self.ccred = None
    23  - self.target = None
    24  - self.spn = None
    25  - self.kc = None
    26  - self.flags = None
    27  - self.preferred_etypes = [23,17,18]
    28  -
    29  - self.session_key = None
    30  - self.gssapi = None
    31  - self.iterations = 0
    32  - self.etype = None
    33  - self.seq_number = 0
    34  - self.expected_server_seq_number = None
    35  - self.from_ccache = False
    36  -
    37  - self.setup()
    38  -
    39  - def get_seq_number(self):
    40  - """
    41  - Returns the initial sequence number. It is 0 by default, but can be adjusted during authentication,
    42  - by passing the 'seq_number' parameter in the 'authenticate' function
    43  - """
    44  - return self.seq_number
    45  -
    46  - def signing_needed(self):
    47  - """
    48  - Checks if integrity protection was negotiated
    49  - """
    50  - return ChecksumFlags.GSS_C_INTEG_FLAG in self.flags
    51  -
    52  - def encryption_needed(self):
    53  - """
    54  - Checks if confidentiality flag was negotiated
    55  - """
    56  - return ChecksumFlags.GSS_C_CONF_FLAG in self.flags
    57  -
    58  - async def sign(self, data, message_no, direction = 'init'):
    59  - """
    60  - Signs a message.
    61  - """
    62  - return self.gssapi.GSS_GetMIC(data, message_no, direction = direction)
    63  -
    64  - async def encrypt(self, data, message_no):
    65  - """
    66  - Encrypts a message.
    67  - """
    68  - 
    69  - return self.gssapi.GSS_Wrap(data, message_no)
    70  -
    71  - async def decrypt(self, data, message_no, direction='init'):
    72  - """
    73  - Decrypts message. Also performs integrity checking.
    74  - """
    75  - 
    76  - return self.gssapi.GSS_Unwrap(data, message_no, direction=direction)
    77  -
    78  - def setup(self):
    79  - self.ccred = self.settings.ccred
    80  - self.spn = self.settings.spn
    81  - self.target = self.settings.target
    82  -
    83  - self.flags = ChecksumFlags.GSS_C_MUTUAL_FLAG
    84  - if True: #self.settings.encrypt is
    85  - self.flags = \
    86  - ChecksumFlags.GSS_C_CONF_FLAG |\
    87  - ChecksumFlags.GSS_C_INTEG_FLAG |\
    88  - ChecksumFlags.GSS_C_REPLAY_FLAG |\
    89  - ChecksumFlags.GSS_C_SEQUENCE_FLAG
    90  -
    91  -
    92  - def get_session_key(self):
    93  - return self.session_key.contents, None
    94  - 
    95  - 
    96  - async def setup_kc(self):
    97  - try:
    98  - self.kc = AIOKerberosClient(self.ccred, self.target)
    99  - # sockst/wsnet proxying is handled by the minikerberos&asysocks modules
    100  - #if self.target.proxy is None or self.target.proxy.type in RDP_SOCKS_PROXY_TYPES:
    101  - # self.kc = AIOKerberosClient(self.ccred, self.target)
    102  - 
    103  - #elif self.target.proxy.type in [RDPProxyType.MULTIPLEXOR, RDPProxyType.MULTIPLEXOR_SSL]:
    104  - # from aardwolf.network.multiplexor import MultiplexorProxyConnection
    105  - # mpc = MultiplexorProxyConnection(self.target)
    106  - # socks_proxy = await mpc.connect(is_kerberos = True)
    107  - #
    108  - # self.kc = AIOKerberosClient(self.ccred, socks_proxy)
    109  - #
    110  - #else:
    111  - # raise Exception('Unknown proxy type %s' % self.target.proxy.type)
    112  - 
    113  - return None, None
    114  - except Exception as e:
    115  - return None, e
    116  -
    117  - async def authenticate(self, authData, flags = None, seq_number = 0, cb_data = None, is_rpc = False):
    118  - """
    119  - This function is called (multiple times depending on the flags) to perform authentication.
    120  - """
    121  - try:
    122  - if self.kc is None:
    123  - _, err = await self.setup_kc()
    124  - if err is not None:
    125  - return None, None, err
    126  - 
    127  - if self.iterations == 0:
    128  - self.seq_number = 0
    129  - self.iterations += 1
    130  -
    131  - try:
    132  - #check TGS first, maybe ccache already has what we need
    133  - for target in self.ccred.ccache.list_targets():
    134  - # just printing this to debug...
    135  - logger.debug('CCACHE SPN record: %s' % target)
    136  - tgs, encpart, self.session_key = await self.kc.get_TGS(self.spn)
    137  -
    138  - self.from_ccache = True
    139  - except:
    140  - tgt = await self.kc.get_TGT(override_etype = self.preferred_etypes)
    141  - tgs, encpart, self.session_key = await self.kc.get_TGS(self.spn)#, override_etype = self.preferred_etypes)
    142  - 
    143  - #self.expected_server_seq_number = encpart.get('nonce', seq_number)
    144  -
    145  - ap_opts = []
    146  - if ChecksumFlags.GSS_C_MUTUAL_FLAG in self.flags or ChecksumFlags.GSS_C_DCE_STYLE in self.flags:
    147  - if ChecksumFlags.GSS_C_MUTUAL_FLAG in self.flags:
    148  - ap_opts.append('mutual-required')
    149  - if self.from_ccache is False:
    150  - apreq = self.kc.construct_apreq(tgs, encpart, self.session_key, flags = self.flags, seq_number = self.seq_number, ap_opts=ap_opts, cb_data = cb_data)
    151  - else:
    152  - apreq = self.kc.construct_apreq_from_ticket(Ticket(tgs['ticket']).dump(), self.session_key, tgs['crealm'], tgs['cname']['name-string'][0], flags = self.flags, seq_number = self.seq_number, ap_opts = ap_opts, cb_data = cb_data)
    153  - return apreq, True, None
    154  -
    155  - else:
    156  - #no mutual or dce auth will take one step only
    157  - if self.from_ccache is False:
    158  - apreq = self.kc.construct_apreq(tgs, encpart, self.session_key, flags = self.flags, seq_number = self.seq_number, ap_opts=[], cb_data = cb_data)
    159  - else:
    160  - apreq = self.kc.construct_apreq_from_ticket(Ticket(tgs['ticket']).dump(), self.session_key, tgs['crealm'], tgs['cname']['name-string'][0], flags = self.flags, seq_number = self.seq_number, ap_opts = ap_opts, cb_data = cb_data)
    161  -
    162  -
    163  - self.gssapi = get_gssapi(self.session_key)
    164  - return apreq, False, None
    165  - 
    166  - else:
    167  - self.iterations += 1
    168  - if ChecksumFlags.GSS_C_DCE_STYLE in self.flags:
    169  - # adata = authData[16:]
    170  - # if ChecksumFlags.GSS_C_DCE_STYLE in self.flags:
    171  - # adata = authData
    172  - raise Exception('DCE auth Not implemented!')
    173  -
    174  - # at this point we are dealing with mutual authentication
    175  - # This means that the server sent back an AP-rep wrapped in a token
    176  - # The APREP contains a new session key we'd need to update and a seq-number
    177  - # that is expected the server will use for future communication.
    178  - # For mutual auth we dont need to reply anything after this step,
    179  - # but for DCE auth a reply is expected. TODO
    180  - 
    181  - # converting the token to aprep
    182  - token = KRB5_MECH_INDEP_TOKEN.from_bytes(authData)
    183  - if token.data[:2] != b'\x02\x00':
    184  - raise Exception('Unexpected token type! %s' % token.data[:2].hex() )
    185  - aprep = AP_REP.load(token.data[2:]).native
    186  -
    187  - # decrypting aprep
    188  - cipher = _enctype_table[int(aprep['enc-part']['etype'])]()
    189  - cipher_text = aprep['enc-part']['cipher']
    190  - temp = cipher.decrypt(self.session_key, 12, cipher_text)
    191  - enc_part = EncAPRepPart.load(temp).native
    192  - 
    193  - #updating session key, gssapi
    194  - self.session_key = Key(int(enc_part['subkey']['keytype']), enc_part['subkey']['keyvalue'])
    195  - #self.seq_number = enc_part.get('seq-number', 0)
    196  - self.gssapi = get_gssapi(self.session_key)
    197  - 
    198  - return b'', False, None
    199  -
    200  - except Exception as e:
    201  - return None, None, e
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/sspi.py
    1  -#
    2  -# This is just a simple interface to the winsspi library to support Kerberos
    3  -# Will only work on windows, ovbiously
    4  -#
    5  -#
    6  -#
    7  - 
    8  -from aardwolf.authentication.spnego.asn1_structs import KRB5Token
    9  -from winsspi.sspi import KerberosMSLDAPSSPI
    10  -from winsspi.common.function_defs import ISC_REQ, GetSequenceNumberFromEncryptdataKerberos
    11  -from minikerberos.gssapi.gssapi import get_gssapi
    12  -from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP
    13  -from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
    14  - 
    15  -class RDPKerberosSSPI:
    16  - def __init__(self, settings):
    17  - self.iterations = 0
    18  - self.settings = settings
    19  - self.mode = 'CLIENT'
    20  - self.ksspi = KerberosMSLDAPSSPI()
    21  - self.client = None
    22  - self.target = None
    23  - self.gssapi = None
    24  - self.etype = None
    25  - self.actual_ctx_flags = None
    26  - 
    27  - self.seq_number = None
    28  -
    29  - self.setup()
    30  - 
    31  - def get_seq_number(self):
    32  - """
    33  - Fetches the starting sequence number. This is either zero or can be found in the authenticator field of the
    34  - AP_REQ structure. As windows uses a random seq number AND a subkey as well, we can't obtain it by decrypting the
    35  - AP_REQ structure. Insead under the hood we perform an encryption operation via EncryptMessage API which will
    36  - yield the start sequence number
    37  - """
    38  - self.seq_number = GetSequenceNumberFromEncryptdataKerberos(self.ksspi.context)
    39  - return self.seq_number
    40  -
    41  - def setup(self):
    42  - self.mode = self.settings.mode
    43  - self.client = self.settings.client
    44  - self.target = self.settings.target
    45  -
    46  - async def encrypt(self, data, message_no):
    47  - return self.gssapi.GSS_Wrap(data, message_no)
    48  -
    49  - async def decrypt(self, data, message_no, direction='init', auth_data=None):
    50  - return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
    51  -
    52  - def get_session_key(self):
    53  - return self.ksspi.get_session_key()
    54  -
    55  - async def authenticate(self, authData = None, flags = ISC_REQ.CONNECTION, seq_number = 0, is_rpc = False):
    56  - #authdata is only for api compatibility reasons
    57  - if is_rpc == True:
    58  - if self.iterations == 0:
    59  - flags = ISC_REQ.CONFIDENTIALITY | \
    60  - ISC_REQ.INTEGRITY | \
    61  - ISC_REQ.MUTUAL_AUTH | \
    62  - ISC_REQ.REPLAY_DETECT | \
    63  - ISC_REQ.SEQUENCE_DETECT|\
    64  - ISC_REQ.USE_DCE_STYLE
    65  -
    66  - token, self.actual_ctx_flags, err = self.ksspi.get_ticket_for_spn(self.target, flags = flags, is_rpc = True, token_data = authData)
    67  - #print(token.hex())
    68  - self.iterations += 1
    69  - return token, True, None
    70  -
    71  - elif self.iterations == 1:
    72  - flags = ISC_REQ.USE_DCE_STYLE
    73  - token, self.actual_ctx_flags, err = self.ksspi.get_ticket_for_spn(self.target, flags = flags, is_rpc = True, token_data = authData)
    74  -
    75  - aprep = AP_REP.load(token).native
    76  - subkey = Key(aprep['enc-part']['etype'], self.get_session_key())
    77  - 
    78  - self.get_seq_number()
    79  -
    80  - self.gssapi = get_gssapi(subkey)
    81  -
    82  - self.iterations += 1
    83  - return token, False, None
    84  -
    85  - else:
    86  - raise Exception('SSPI Kerberos -RPC - auth encountered too many calls for authenticate.')
    87  -
    88  - else:
    89  - apreq, self.actual_ctx_flags, err = self.ksspi.get_ticket_for_spn(self.target, flags = flags)
    90  - return apreq, False, None
    91  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/sspiproxy.py
    1  - 
    2  -import enum
    3  - 
    4  -from aardwolf.authentication.spnego.asn1_structs import KRB5Token
    5  -from minikerberos.gssapi.gssapi import get_gssapi, GSSWrapToken
    6  -from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
    7  -from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
    8  -from wsnet.operator.sspiproxy import WSNETSSPIProxy
    9  - 
    10  -import enum
    11  -import io
    12  -import os
    13  - 
    14  -from asn1crypto.core import ObjectIdentifier
    15  - 
    16  -class KRB5_MECH_INDEP_TOKEN:
    17  - # https://tools.ietf.org/html/rfc2743#page-81
    18  - # Mechanism-Independent Token Format
    19  - 
    20  - def __init__(self, data, oid, remlen = None):
    21  - self.oid = oid
    22  - self.data = data
    23  - 
    24  - #dont set this
    25  - self.length = remlen
    26  -
    27  - @staticmethod
    28  - def from_bytes(data):
    29  - return KRB5_MECH_INDEP_TOKEN.from_buffer(io.BytesIO(data))
    30  -
    31  - @staticmethod
    32  - def from_buffer(buff):
    33  -
    34  - start = buff.read(1)
    35  - if start != b'\x60':
    36  - raise Exception('Incorrect token data!')
    37  - remaining_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    38  - token_data = buff.read(remaining_length)
    39  -
    40  - buff = io.BytesIO(token_data)
    41  - pos = buff.tell()
    42  - buff.read(1)
    43  - oid_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    44  - buff.seek(pos)
    45  - token_oid = ObjectIdentifier.load(buff.read(oid_length+2))
    46  -
    47  - return KRB5_MECH_INDEP_TOKEN(buff.read(), str(token_oid), remlen = remaining_length)
    48  -
    49  - @staticmethod
    50  - def decode_length_buffer(buff):
    51  - lf = buff.read(1)[0]
    52  - if lf <= 127:
    53  - length = lf
    54  - else:
    55  - bcount = lf - 128
    56  - length = int.from_bytes(buff.read(bcount), byteorder = 'big', signed = False)
    57  - return length
    58  -
    59  - @staticmethod
    60  - def encode_length(length):
    61  - if length <= 127:
    62  - return length.to_bytes(1, byteorder = 'big', signed = False)
    63  - else:
    64  - lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
    65  - return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
    66  -
    67  -
    68  - def to_bytes(self):
    69  - t = ObjectIdentifier(self.oid).dump() + self.data
    70  - t = b'\x60' + KRB5_MECH_INDEP_TOKEN.encode_length(len(t)) + t
    71  - return t[:-len(self.data)] , self.data
    72  - 
    73  - 
    74  -class ISC_REQ(enum.IntFlag):
    75  - DELEGATE = 1
    76  - MUTUAL_AUTH = 2
    77  - REPLAY_DETECT = 4
    78  - SEQUENCE_DETECT = 8
    79  - CONFIDENTIALITY = 16
    80  - USE_SESSION_KEY = 32
    81  - PROMPT_FOR_CREDS = 64
    82  - USE_SUPPLIED_CREDS = 128
    83  - ALLOCATE_MEMORY = 256
    84  - USE_DCE_STYLE = 512
    85  - DATAGRAM = 1024
    86  - CONNECTION = 2048
    87  - CALL_LEVEL = 4096
    88  - FRAGMENT_SUPPLIED = 8192
    89  - EXTENDED_ERROR = 16384
    90  - STREAM = 32768
    91  - INTEGRITY = 65536
    92  - IDENTIFY = 131072
    93  - NULL_SESSION = 262144
    94  - MANUAL_CRED_VALIDATION = 524288
    95  - RESERVED1 = 1048576
    96  - FRAGMENT_TO_FIT = 2097152
    97  - HTTP = 0x10000000
    98  - 
    99  -class RDPSSPIProxyKerberosAuth:
    100  - def __init__(self, settings):
    101  - self.iterations = 0
    102  - self.settings = settings
    103  - self.mode = 'CLIENT'
    104  - url = '%s://%s:%s' % (self.settings.proto, self.settings.host, self.settings.port)
    105  - self.ksspi = WSNETSSPIProxy(url, self.settings.agent_id)
    106  - self.client = None
    107  - self.target = None
    108  - self.gssapi = None
    109  - self.etype = None
    110  - self.session_key = None
    111  - self.seq_number = None
    112  -
    113  - self.setup()
    114  -
    115  - def setup(self):
    116  - return
    117  - 
    118  - def get_seq_number(self):
    119  - return self.seq_number
    120  -
    121  - async def encrypt(self, data, message_no):
    122  - return self.gssapi.GSS_Wrap(data, message_no)
    123  -
    124  - async def decrypt(self, data, message_no, direction='init', auth_data=None):
    125  - return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
    126  -
    127  - def get_session_key(self):
    128  - return self.session_key
    129  -
    130  - async def authenticate(self, authData = None, flags = ISC_REQ.CONNECTION, seq_number = 0, is_rpc = False):
    131  - try:
    132  - if is_rpc == True:
    133  - if self.iterations == 0:
    134  - flags = ISC_REQ.CONFIDENTIALITY | \
    135  - ISC_REQ.INTEGRITY | \
    136  - ISC_REQ.MUTUAL_AUTH | \
    137  - ISC_REQ.REPLAY_DETECT | \
    138  - ISC_REQ.SEQUENCE_DETECT|\
    139  - ISC_REQ.USE_DCE_STYLE
    140  -
    141  -
    142  - status, ctxattr, apreq, err = await self.ksspi.authenticate('KERBEROS', '', 'termsrv/%s' % self.settings.target, 3, flags.value, authdata = b'')
    143  - if err is not None:
    144  - raise err
    145  - self.iterations += 1
    146  - return apreq, True, None
    147  -
    148  - elif self.iterations == 1:
    149  - status, ctxattr, data, err = await self.ksspi.authenticate('KERBEROS', '','termsrv/%s' % self.settings.target, 3, flags.value, authdata = authData)
    150  - if err is not None:
    151  - return None, None, err
    152  - self.session_key, err = await self.ksspi.get_sessionkey()
    153  - if err is not None:
    154  - return None, None, err
    155  -
    156  - aprep = AP_REP.load(data).native
    157  - subkey = Key(aprep['enc-part']['etype'], self.session_key)
    158  - self.gssapi = get_gssapi(subkey)
    159  - 
    160  - if aprep['enc-part']['etype'] != 23: #no need for seq number in rc4
    161  - raw_seq_data, err = await self.ksspi.get_sequenceno()
    162  - if err is not None:
    163  - return None, None, err
    164  - self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
    165  -
    166  - self.iterations += 1
    167  - await self.ksspi.disconnect()
    168  - return data, False, None
    169  -
    170  - else:
    171  - raise Exception('SSPI Kerberos -RPC - auth encountered too many calls for authenticate.')
    172  -
    173  -
    174  - else:
    175  - status, ctxattr, apreq, err = await self.ksspi.authenticate('KERBEROS', '','termsrv/%s' % self.settings.target, 3, flags.value, authdata = b'')
    176  - if err is not None:
    177  - return None, None, err
    178  -
    179  - self.session_key, err = await self.ksspi.get_sessionkey()
    180  - if err is not None:
    181  - raise err
    182  - await self.ksspi.disconnect()
    183  - 
    184  - return apreq, False, None
    185  - except Exception as e:
    186  - return None, None, e
    187  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/kerberos/wsnet.py
    1  - 
    2  -##
    3  -##
    4  -## Interface to allow remote kerberos authentication via Multiplexor
    5  -##
    6  -##
    7  -##
    8  -##
    9  -##
    10  -## TODO: RPC auth type is not implemented or tested!!!!
    11  - 
    12  -import enum
    13  - 
    14  -from aardwolf.authentication.spnego.asn1_structs import KRB5Token
    15  -from minikerberos.gssapi.gssapi import get_gssapi, GSSWrapToken
    16  -from minikerberos.protocol.asn1_structs import AP_REQ, AP_REP, TGS_REP
    17  -from minikerberos.protocol.encryption import Enctype, Key, _enctype_table
    18  -from wsnet.clientauth import WSNETAuth
    19  - 
    20  -import enum
    21  -import io
    22  -import os
    23  - 
    24  -from asn1crypto.core import ObjectIdentifier
    25  - 
    26  -class KRB5_MECH_INDEP_TOKEN:
    27  - # https://tools.ietf.org/html/rfc2743#page-81
    28  - # Mechanism-Independent Token Format
    29  - 
    30  - def __init__(self, data, oid, remlen = None):
    31  - self.oid = oid
    32  - self.data = data
    33  - 
    34  - #dont set this
    35  - self.length = remlen
    36  -
    37  - @staticmethod
    38  - def from_bytes(data):
    39  - return KRB5_MECH_INDEP_TOKEN.from_buffer(io.BytesIO(data))
    40  -
    41  - @staticmethod
    42  - def from_buffer(buff):
    43  -
    44  - start = buff.read(1)
    45  - if start != b'\x60':
    46  - raise Exception('Incorrect token data!')
    47  - remaining_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    48  - token_data = buff.read(remaining_length)
    49  -
    50  - buff = io.BytesIO(token_data)
    51  - pos = buff.tell()
    52  - buff.read(1)
    53  - oid_length = KRB5_MECH_INDEP_TOKEN.decode_length_buffer(buff)
    54  - buff.seek(pos)
    55  - token_oid = ObjectIdentifier.load(buff.read(oid_length+2))
    56  -
    57  - return KRB5_MECH_INDEP_TOKEN(buff.read(), str(token_oid), remlen = remaining_length)
    58  -
    59  - @staticmethod
    60  - def decode_length_buffer(buff):
    61  - lf = buff.read(1)[0]
    62  - if lf <= 127:
    63  - length = lf
    64  - else:
    65  - bcount = lf - 128
    66  - length = int.from_bytes(buff.read(bcount), byteorder = 'big', signed = False)
    67  - return length
    68  -
    69  - @staticmethod
    70  - def encode_length(length):
    71  - if length <= 127:
    72  - return length.to_bytes(1, byteorder = 'big', signed = False)
    73  - else:
    74  - lb = length.to_bytes((length.bit_length() + 7) // 8, 'big')
    75  - return (128+len(lb)).to_bytes(1, byteorder = 'big', signed = False) + lb
    76  -
    77  -
    78  - def to_bytes(self):
    79  - t = ObjectIdentifier(self.oid).dump() + self.data
    80  - t = b'\x60' + KRB5_MECH_INDEP_TOKEN.encode_length(len(t)) + t
    81  - return t[:-len(self.data)] , self.data
    82  - 
    83  - 
    84  -class ISC_REQ(enum.IntFlag):
    85  - DELEGATE = 1
    86  - MUTUAL_AUTH = 2
    87  - REPLAY_DETECT = 4
    88  - SEQUENCE_DETECT = 8
    89  - CONFIDENTIALITY = 16
    90  - USE_SESSION_KEY = 32
    91  - PROMPT_FOR_CREDS = 64
    92  - USE_SUPPLIED_CREDS = 128
    93  - ALLOCATE_MEMORY = 256
    94  - USE_DCE_STYLE = 512
    95  - DATAGRAM = 1024
    96  - CONNECTION = 2048
    97  - CALL_LEVEL = 4096
    98  - FRAGMENT_SUPPLIED = 8192
    99  - EXTENDED_ERROR = 16384
    100  - STREAM = 32768
    101  - INTEGRITY = 65536
    102  - IDENTIFY = 131072
    103  - NULL_SESSION = 262144
    104  - MANUAL_CRED_VALIDATION = 524288
    105  - RESERVED1 = 1048576
    106  - FRAGMENT_TO_FIT = 2097152
    107  - HTTP = 0x10000000
    108  - 
    109  -class RDPWSNetKerberosAuth:
    110  - def __init__(self, settings):
    111  - self.iterations = 0
    112  - self.settings = settings
    113  - self.mode = 'CLIENT'
    114  - self.ksspi = WSNETAuth()
    115  - self.client = None
    116  - self.target = None
    117  - self.gssapi = None
    118  - self.etype = None
    119  - self.session_key = None
    120  - self.seq_number = None
    121  -
    122  - self.setup()
    123  -
    124  - def setup(self):
    125  - return
    126  - 
    127  - def get_seq_number(self):
    128  - return self.seq_number
    129  -
    130  - async def encrypt(self, data, message_no):
    131  - return self.gssapi.GSS_Wrap(data, message_no)
    132  -
    133  - async def decrypt(self, data, message_no, direction='init', auth_data=None):
    134  - return self.gssapi.GSS_Unwrap(data, message_no, direction=direction, auth_data=auth_data)
    135  -
    136  - def get_session_key(self):
    137  - return self.session_key
    138  -
    139  - async def authenticate(self, authData = None, flags = ISC_REQ.CONNECTION, seq_number = 0, is_rpc = False):
    140  - try:
    141  - if is_rpc == True:
    142  - if self.iterations == 0:
    143  - flags = ISC_REQ.CONFIDENTIALITY | \
    144  - ISC_REQ.INTEGRITY | \
    145  - ISC_REQ.MUTUAL_AUTH | \
    146  - ISC_REQ.REPLAY_DETECT | \
    147  - ISC_REQ.SEQUENCE_DETECT|\
    148  - ISC_REQ.USE_DCE_STYLE
    149  -
    150  -
    151  - status, ctxattr, apreq, err = await self.ksspi.authenticate('KERBEROS', '', 'termsrv/%s' % self.settings.target, 3, flags.value, authdata = b'')
    152  - if err is not None:
    153  - raise err
    154  - self.iterations += 1
    155  - return apreq, True, None
    156  -
    157  - elif self.iterations == 1:
    158  - status, ctxattr, data, err = await self.ksspi.authenticate('KERBEROS', '','termsrv/%s' % self.settings.target, 3, flags.value, authdata = authData)
    159  - if err is not None:
    160  - return None, None, err
    161  - self.session_key, err = await self.ksspi.get_sessionkey()
    162  - if err is not None:
    163  - return None, None, err
    164  -
    165  - aprep = AP_REP.load(data).native
    166  - subkey = Key(aprep['enc-part']['etype'], self.session_key)
    167  - self.gssapi = get_gssapi(subkey)
    168  - 
    169  - if aprep['enc-part']['etype'] != 23: #no need for seq number in rc4
    170  - raw_seq_data, err = await self.ksspi.get_sequenceno()
    171  - if err is not None:
    172  - return None, None, err
    173  - self.seq_number = GSSWrapToken.from_bytes(raw_seq_data[16:]).SND_SEQ
    174  -
    175  - self.iterations += 1
    176  - await self.ksspi.disconnect()
    177  - return data, False, None
    178  -
    179  - else:
    180  - raise Exception('SSPI Kerberos -RPC - auth encountered too many calls for authenticate.')
    181  -
    182  -
    183  - else:
    184  - status, ctxattr, apreq, err = await self.ksspi.authenticate('KERBEROS', '','termsrv/%s' % self.settings.target, 3, flags.value, authdata = b'')
    185  - if err is not None:
    186  - return None, None, err
    187  -
    188  - self.session_key, err = await self.ksspi.get_sessionkey()
    189  - if err is not None:
    190  - raise err
    191  - await self.ksspi.disconnect()
    192  - 
    193  - return apreq, False, None
    194  - except Exception as e:
    195  - return None, None, e
    196  -
  • ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/__init__.py
    1  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/creds_calc.py
    1  -import datetime
    2  - 
    3  -from unicrypto.symmetric import DES
    4  -from unicrypto import hashlib
    5  -from unicrypto import hmac
    6  - 
    7  -from aardwolf.authentication.ntlm.structures.challenge_response import *
    8  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    9  - 
    10  -class NTLMCredentials:
    11  - @staticmethod
    12  - def construct(ntlmNegotiate, ntlmChallenge, ntlmAuthenticate):
    13  - # now the guessing-game begins
    14  - 
    15  - if isinstance(ntlmAuthenticate.NTChallenge, NTLMv2Response):
    16  - #if ntlmAuthenticate._use_NTLMv2:
    17  - # this is a netNTLMv2 then, otherwise auth would have failed on protocol level
    18  - creds = netntlmv2()
    19  - creds.username = ntlmAuthenticate.UserName
    20  - creds.domain = ntlmAuthenticate.DomainName
    21  - creds.ServerChallenge = ntlmChallenge.ServerChallenge
    22  - creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response
    23  - creds.ChallengeFromClinet = ntlmAuthenticate.NTChallenge.ChallengeFromClinet_hex
    24  - 
    25  - creds2 = netlmv2()
    26  - creds2.username = ntlmAuthenticate.UserName
    27  - creds2.domain = ntlmAuthenticate.DomainName
    28  - creds2.ServerChallenge = ntlmChallenge.ServerChallenge
    29  - creds2.ClientResponse = ntlmAuthenticate.LMChallenge.Response
    30  - creds2.ChallengeFromClinet = ntlmAuthenticate.LMChallenge.ChallengeFromClinet
    31  - return [creds, creds2]
    32  - 
    33  - else:
    34  - if ntlmAuthenticate.NegotiateFlags & NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY:
    35  - # extended security is used, this means that the LMresponse actually contains client challenge data
    36  - # and the LM and NT respondses need to be combined to form the cred data
    37  - creds = netntlm_ess()
    38  - creds.username = ntlmAuthenticate.UserName
    39  - creds.domain = ntlmAuthenticate.DomainName
    40  - creds.ServerChallenge = ntlmChallenge.ServerChallenge
    41  - creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response
    42  - creds.ChallengeFromClinet = ntlmAuthenticate.LMChallenge.Response
    43  - 
    44  - return [creds]
    45  - 
    46  - else:
    47  - creds = netntlm()
    48  - creds.username = ntlmAuthenticate.UserName
    49  - creds.domain = ntlmAuthenticate.DomainName
    50  - creds.ServerChallenge = ntlmChallenge.ServerChallenge
    51  - creds.ClientResponse = ntlmAuthenticate.NTChallenge.Response
    52  -
    53  - if ntlmAuthenticate.NTChallenge.Response == ntlmAuthenticate.LMChallenge.Response:
    54  - # the the two responses are the same, then the client did not send encrypted LM hashes, only NT
    55  - return [creds]
    56  -
    57  - 
    58  - # CAME FOR COPPER, FOUND GOLD!!!!!
    59  - # HOW OUTDATED IS YOUR CLIENT ANYHOW???
    60  - creds2 = netlm()
    61  - creds2.username = ntlmAuthenticate.UserName
    62  - creds2.domain = ntlmAuthenticate.DomainName
    63  - creds2.ServerChallenge = ntlmChallenge.ServerChallenge
    64  - creds2.ClientResponse = ntlmAuthenticate.LMChallenge.Response
    65  - return [creds2, creds]
    66  - 
    67  -class netlm:
    68  - # not supported by hashcat?
    69  - def __init__(self):
    70  - # this part comes from the NTLMAuthenticate class
    71  - self.username = None
    72  - self.domain = None
    73  - # this comes from the NTLMChallenge class
    74  - self.ServerChallenge = None
    75  - 
    76  - # this is from the LMv1Response class (that is a member of NTLMAuthenticate class)
    77  - self.ClientResponse = None
    78  - 
    79  - def to_credential(self):
    80  - cred = Credential('netLM',
    81  - username = self.username,
    82  - fullhash = '%s:$NETLM$%s$%s' % (self.username, self.ServerChallenge, self.ClientResponse)
    83  - )
    84  - return cred
    85  - 
    86  - def verify(self, creds, credtype='plain'):
    87  - """
    88  - Verifies the authentication data against the user credentials
    89  - Be careful! If the credtype is 'hash' then LM hash is expected!
    90  - :param creds: dictionary containing the domain, user, hash/password
    91  - :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
    92  - :return: bool
    93  - """
    94  - 
    95  - # print('Creds: %s' % creds)
    96  - if creds is None:
    97  - return True
    98  - 
    99  - if self.domain not in creds:
    100  - return False
    101  - if self.username not in creds[self.domain]:
    102  - return False
    103  - 
    104  - if credtype == 'plain':
    105  - lm_hash = LMOWFv1(creds[self.domain][self.username])
    106  - elif credtype == 'hash':
    107  - lm_hash = bytes.fromhex(creds[self.domain][self.username])
    108  - else:
    109  - raise Exception('Unknown cred type!')
    110  - 
    111  - calc_response = DESL(lm_hash, self.ServerChallenge)
    112  - 
    113  - return self.ClientResponse == calc_response.hex()
    114  - 
    115  - 
    116  -class netlmv2:
    117  - # not supported by hashcat?
    118  - def __init__(self):
    119  - # this part comes from the NTLMAuthenticate class
    120  - self.username = None
    121  - self.domain = None
    122  - # this comes from the NTLMChallenge class
    123  - self.ServerChallenge = None
    124  - 
    125  - # this is from the LMv2Response class (that is a member of NTLMAuthenticate class)
    126  - self.ClientResponse = None
    127  - self.ChallengeFromClinet = None
    128  - 
    129  - def to_credential(self):
    130  - cred = Credential(
    131  - 'netLMv2',
    132  - username = self.username,
    133  - fullhash = '$NETLMv2$%s$%s$%s$%s' % (self.username, self.ServerChallenge, self.ClientResponse, self.ChallengeFromClinet)
    134  - )
    135  - return cred
    136  - 
    137  - def verify(self, creds, credtype='plain'):
    138  - """
    139  - Verifies the authentication data against the user credentials
    140  - :param creds: dictionary containing the domain, user, hash/password
    141  - :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
    142  - :return: bool
    143  - """
    144  - 
    145  - # print('Creds: %s' % creds)
    146  - if creds is None:
    147  - return True
    148  - 
    149  - if self.domain not in creds:
    150  - return False
    151  - if self.username not in creds[self.domain]:
    152  - return False
    153  - 
    154  - if credtype == 'plain':
    155  - lm_hash = LMOWFv2(creds[self.domain][self.username], self.username, self.domain)
    156  - elif credtype == 'hash':
    157  - lm_hash = LMOWFv2(None, self.username, self.domain, bytes.fromhex(creds[self.domain][self.username]))
    158  - else:
    159  - raise Exception('Unknown cred type!')
    160  - 
    161  - hm = hmac.new(lm_hash, digestmod = 'md5')
    162  - hm.update(bytes.fromhex(self.ServerChallenge))
    163  - hm.update(bytes.fromhex(self.ChallengeFromClinet))
    164  - 
    165  - return self.ClientResponse == hm.hexdigest()
    166  - 
    167  - 
    168  -class netntlm_ess:
    169  - def __init__(self):
    170  - # this part comes from the NTLMAuthenticate class
    171  - self.credentials = None
    172  - # this comes from the NTLMChallenge class
    173  - self.ServerChallenge = None
    174  - 
    175  - self.LMResponse = None
    176  - self.NTResponse = None
    177  -
    178  - self.SessionBaseKey = None
    179  -
    180  -
    181  - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370
    182  - def calc_key_exchange_key(self):
    183  - if not self.credentials.nt_hash:
    184  - nt_hash = NTOWFv1(self.credentials.password)
    185  - else:
    186  - nt_hash = bytes.fromhex(self.credentials.nt_hash)
    187  -
    188  - hm = hmac.new(self.SessionBaseKey, digestmod = 'md5')
    189  - hm.update(self.ServerChallenge)
    190  - hm.update(self.LMResponse.to_bytes()[:8])
    191  -
    192  - return hm.digest()
    193  -
    194  - @staticmethod
    195  - def construct(server_challenge, client_challenge, credentials):
    196  - ntlm_creds = netntlm_ess()
    197  - ntlm_creds.credentials = credentials
    198  - ntlm_creds.ServerChallenge = server_challenge
    199  -
    200  - if credentials.password:
    201  - nt_hash = NTOWFv1(credentials.password)
    202  - lm_hash = LMOWFv1(credentials.password)
    203  - else:
    204  - nt_hash = bytes.fromhex(credentials.nt_hash)
    205  - lm_hash = bytes.fromhex(credentials.lm_hash) if credentials.lm_hash else None
    206  -
    207  -
    208  - ntlm_creds.LMResponse = LMResponse()
    209  - ntlm_creds.LMResponse.Response = client_challenge + b'\x00' * 16
    210  -
    211  - temp_1 = hashlib.md5(server_challenge + client_challenge[:8]).digest()
    212  - data = DESL(nt_hash, temp_1[:8])
    213  -
    214  - ntlm_creds.NTResponse = NTLMv1Response()
    215  - ntlm_creds.NTResponse.Response = data
    216  -
    217  - ntlm_creds.SessionBaseKey = hashlib.md4(nt_hash).digest()
    218  -
    219  - return ntlm_creds
    220  - 
    221  - def to_credential(self):
    222  - cred = Credential(
    223  - 'netNTLMv1-ESS',
    224  - username = self.username,
    225  - fullhash = '%s::%s:%s:%s:%s' % (self.credentials.username, self.credentials.domain, ntlm_creds.LMResponse.Response, ntlm_creds.NTResponse.Response, self.ServerChallenge)
    226  - )
    227  - return cred
    228  - # u4-netntlm::kNS:338d08f8e26de93300000000000000000000000000000000:9526fb8c23a90751cdd619b6cea564742e1e4bf33006ba41:cb8086049ec4736c
    229  - 
    230  - def calc_session_base_key(self, creds, credtype = 'plain'):
    231  - if credtype == 'plain':
    232  - nt_hash = NTOWFv1(creds[self.domain][self.username])
    233  - elif credtype == 'hash':
    234  - nt_hash = bytes.fromhex(creds[self.domain][self.username])
    235  - else:
    236  - raise Exception('Unknown cred type!')
    237  - 
    238  - session_base_key = hashlib.md4(nt_hash).digest()
    239  - return session_base_key
    240  - 
    241  - def verify(self, creds, credtype='plain'):
    242  - """
    243  - Verifies the authentication data against the user credentials
    244  - :param creds: dictionary containing the domain, user, hash/password
    245  - :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
    246  - :return: bool
    247  - """
    248  - if creds is None:
    249  - return True
    250  - if self.domain not in creds:
    251  - return False
    252  - if self.username not in creds[self.domain]:
    253  - return False
    254  - 
    255  - if credtype == 'plain':
    256  - nt_hash = NTOWFv1(creds[self.domain][self.username])
    257  - elif credtype == 'hash':
    258  - nt_hash = bytes.fromhex(creds[self.domain][self.username])
    259  - else:
    260  - raise Exception('Unknown cred type!')
    261  - 
    262  - # print('Server chall: %s' % self.ServerChallenge)
    263  - # print('Client chall: %s' % self.ChallengeFromClinet)
    264  - 
    265  - temp_1 = hashlib.md5(bytes.fromhex(self.ServerChallenge) + bytes.fromhex(self.ChallengeFromClinet)[:8]).digest()
    266  - calc_response = DESL(nt_hash, temp_1[:8])
    267  - # print('calc_response: %s' % calc_response.hex())
    268  - # print('ClientResponse: %s' % self.ClientResponse)
    269  - 
    270  - return calc_response == bytes.fromhex(self.ClientResponse)
    271  - 
    272  - 
    273  -class netntlm:
    274  - # not supported by hashcat?
    275  - def __init__(self):
    276  - # this part comes from the NTLMAuthenticate class
    277  - self.credentials = None
    278  - # this comes from the NTLMChallenge class
    279  - self.ServerChallenge = None
    280  - 
    281  - self.LMResponse = None
    282  - self.NTResponse = None
    283  -
    284  -
    285  - self.SessionBaseKey = None
    286  -
    287  - def calc_key_exchange_key(self, with_lm = False, non_nt_session_key = False):
    288  -
    289  - if self.credentials.password:
    290  - lm_hash = LMOWFv1(self.credentials.password)
    291  - else:
    292  - lm_hash = self.credentials.lm_hash
    293  -
    294  - if with_lm:
    295  - temp1 = DES(lm_hash[:7]).encrypt(self.LMResponse.to_bytes()[:8])
    296  - temp2 = DES(lm_hash[7:8] + b'\xBD\xBD\xBD\xBD\xBD\xBD').encrypt(self.LMResponse.to_bytes()[:8])
    297  - kex = temp1 + temp2
    298  - 
    299  - else:
    300  - if non_nt_session_key:
    301  - kex = lm_hash[:8] + b'\x00' * 8
    302  - else:
    303  - kex = self.SessionBaseKey
    304  -
    305  - return kex
    306  -
    307  - @staticmethod
    308  - def construct(server_challenge, credentials):
    309  - ntlm_creds = netntlm()
    310  - ntlm_creds.credentials = credentials
    311  - ntlm_creds.ServerChallenge = server_challenge
    312  -
    313  - if credentials.password:
    314  - nt_hash = NTOWFv1(credentials.password)
    315  - lm_hash = LMOWFv1(credentials.password)
    316  - else:
    317  - nt_hash = bytes.fromhex(credentials.nt_hash)
    318  - lm_hash = bytes.fromhex(credentials.lm_hash) if credentials.lm_hash else None
    319  -
    320  - ntlm_creds.NTResponse = NTLMv1Response()
    321  - ntlm_creds.NTResponse.Response = DESL(nt_hash, server_challenge)
    322  -
    323  - if lm_hash:
    324  - ntlm_creds.LMResponse = LMResponse()
    325  - ntlm_creds.LMResponse.Response = DESL(lm_hash, server_challenge)
    326  - else:
    327  - ntlm_creds.LMResponse = ntresponse
    328  -
    329  - ntlm_creds.SessionBaseKey = hashlib.md4(nt_hash).digest()
    330  -
    331  - return ntlm_creds
    332  - 
    333  - def to_credential(self):
    334  - cred = Credential('netNTLMv1',
    335  - username = self.username,
    336  - fullhash = '%s:$NETNTLM$%s$%s' % (self.username, self.ServerChallenge, self.NTResponse.Response)
    337  - )
    338  - return cred
    339  - #username:$NETNTLM$11223333895667788$B2B2220790F40C88BCFF347C652F67A7C4A70D3BEBD70233
    340  - 
    341  - def calc_session_base_key(self, creds, credtype = 'plain'):
    342  - if credtype == 'plain':
    343  - nt_hash = NTOWFv1(creds[self.domain][self.username])
    344  - elif credtype == 'hash':
    345  - nt_hash = bytes.fromhex(creds[self.domain][self.username])
    346  - else:
    347  - raise Exception('Unknown cred type!')
    348  - 
    349  - session_base_key = hashlib.md4(nt_hash).digest()
    350  - return session_base_key
    351  - 
    352  - def verify(self, creds, credtype='plain'):
    353  - """
    354  - Verifies the authentication data against the user credentials
    355  - :param creds: dictionary containing the domain, user, hash/password
    356  - :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
    357  - :return: bool
    358  - """
    359  - if creds is None:
    360  - return True
    361  - if self.domain not in creds:
    362  - return False
    363  - if self.username not in creds[self.domain]:
    364  - return False
    365  - 
    366  - if credtype == 'plain':
    367  - nt_hash = NTOWFv1(creds[self.domain][self.username])
    368  - elif credtype == 'hash':
    369  - nt_hash = bytes.fromhex(creds[self.domain][self.username])
    370  - else:
    371  - raise Exception('Unknown cred type!')
    372  - 
    373  - return DESL(nt_hash, self.ServerChallenge) == bytes.fromhex(self.ClientResponse)
    374  - 
    375  - 
    376  -class netntlmv2:
    377  - def __init__(self):
    378  - self.credentials = None
    379  -
    380  - # this comes from the NTLMChallenge class
    381  - self.ServerChallenge = None
    382  - 
    383  - # this is from the NTLMv2Response class (that is a member of NTLMAuthenticate class)
    384  - #self.ClientResponse = None
    385  - #self.ChallengeFromClinet = None
    386  -
    387  - self.LMResponse = None
    388  - self.NTResponse = None
    389  -
    390  -
    391  - self.SessionBaseKey = None
    392  -
    393  - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/d86303b5-b29e-4fb9-b119-77579c761370
    394  - def calc_key_exchange_key(self):
    395  - return self.SessionBaseKey
    396  -
    397  - def calc_key_exhange_key_server(self, credentials):
    398  - if not credentials.nt_hash and not credentials.password:
    399  - raise Exception('Password or NT hash must be supplied!')
    400  -
    401  - if credentials.password:
    402  - nt_hash_v2 = NTOWFv2(credentials.password, credentials.username, credentials.domain)
    403  - else:
    404  - nt_hash_v2 = NTOWFv2(None, credentials.username, credentials.domain, bytes.fromhex(credentials.nt_hash))
    405  -
    406  - response = self.NTResponse.Response
    407  - if isinstance(self.NTResponse.Response, str):
    408  - response = bytes.fromhex(self.NTResponse.Response)
    409  - 
    410  - hm = hmac.new(nt_hash_v2, digestmod = 'md5')
    411  - hm.update(response)
    412  - return hm.digest()
    413  -
    414  - @staticmethod
    415  - def construct(server_challenge, client_challenge, server_details, credentials, timestamp = None):
    416  - ntlm_creds = netntlmv2()
    417  - ntlm_creds.credentials = credentials
    418  - ntlm_creds.ServerChallenge = server_challenge
    419  -
    420  - if not credentials.nt_hash and not credentials.password:
    421  - raise Exception('Password or NT hash must be supplied!')
    422  -
    423  - if credentials.password:
    424  - nt_hash_v2 = NTOWFv2(credentials.password, credentials.username, credentials.domain)
    425  - else:
    426  - nt_hash_v2 = NTOWFv2(None, credentials.username, credentials.domain, bytes.fromhex(credentials.nt_hash))
    427  -
    428  - if not timestamp:
    429  - timestamp = datetime.datetime.utcnow()
    430  -
    431  - cc = NTLMv2ClientChallenge.construct(timestamp, client_challenge, server_details)
    432  - temp = cc.to_bytes()
    433  -
    434  - hm = hmac.new(nt_hash_v2, digestmod = 'md5')
    435  - hm.update(server_challenge)
    436  - hm.update(temp)
    437  -
    438  - NTProofStr = hm.digest()
    439  -
    440  - ntlm_creds.NTResponse = NTLMv2Response()
    441  - ntlm_creds.NTResponse.Response = NTProofStr
    442  - ntlm_creds.NTResponse.ChallengeFromClinet = cc
    443  -
    444  -
    445  - hm = hmac.new(nt_hash_v2, digestmod = 'md5')
    446  - hm.update(server_challenge)
    447  - hm.update(client_challenge)
    448  -
    449  - ntlm_creds.LMResponse = LMv2Response()
    450  - ntlm_creds.LMResponse.Response = hm.digest()
    451  - ntlm_creds.LMResponse.ChallengeFromClinet = client_challenge
    452  -
    453  -
    454  - hm = hmac.new(nt_hash_v2, digestmod = 'md5')
    455  - hm.update(NTProofStr)
    456  - ntlm_creds.SessionBaseKey = hm.digest()
    457  -
    458  - return ntlm_creds
    459  - 
    460  - def to_credential(self):
    461  - cred = Credential(
    462  - 'netNTLMv2',
    463  - username = self.username,
    464  - domain = self.domain,
    465  - fullhash = '%s::%s:%s:%s:%s' % (self.credentials.username, self.credentials.domain, self.ServerChallenge, self.NTResponse.Response, self.NTResponse.ChallengeFromClinet)
    466  - )
    467  - return cred
    468  - 
    469  - def verify(self, creds, credtype = 'plain'):
    470  - """
    471  - Verifies the authentication data against the user credentials
    472  - :param creds: dictionary containing the domain, user, hash/password
    473  - :param credtype: can be 'plain' or 'hash' this indicates what type of credential lookup to perform
    474  - :return: bool
    475  - """
    476  - 
    477  - # print('Creds: %s' % creds)
    478  - if creds is None:
    479  - return True
    480  - 
    481  - if self.domain not in creds:
    482  - return False
    483  - if self.username not in creds[self.domain]:
    484  - return False
    485  - 
    486  - if credtype == 'plain':
    487  - nt_hash = NTOWFv2(creds[self.domain][self.username], self.username, self.domain)
    488  - elif credtype == 'hash':
    489  - nt_hash = NTOWFv2(None, self.username, self.domain, bytes.fromhex(creds[self.domain][self.username]))
    490  - else:
    491  - raise Exception('Unknown cred type!')
    492  - 
    493  - # print(self.ServerChallenge)
    494  - # print(self.ChallengeFromClinet)
    495  - 
    496  - hm = hmac.new(nt_hash, digestmod = 'md5')
    497  - hm.update(bytes.fromhex(self.ServerChallenge))
    498  - hm.update(bytes.fromhex(self.ChallengeFromClinet))
    499  - 
    500  - # print('M_nthash: %s' % nthash.hex())
    501  - # print('M_temp: %s' % self.ChallengeFromClinet)
    502  - # print('M_nthash: %s' % nthash.hex())
    503  - # print('M_server_chall: %s' % self.ServerChallenge)
    504  - # print('M_ntproof_string: %s' % self.ClientResponse)
    505  - # print('M_ntproof_string_calc: %s' % hm.hexdigest())
    506  - 
    507  - return self.ClientResponse == hm.hexdigest()
    508  - 
    509  - 
    510  -def LMOWFv1(password):
    511  - LM_SECRET = b'KGS!@#$%'
    512  - t1 = password[:14].ljust(14, '\x00').upper()
    513  - d = DES(t1[:7].encode('ascii'))
    514  - r1 = d.encrypt(LM_SECRET)
    515  - d = DES(t1[7:].encode('ascii'))
    516  - r2 = d.encrypt(LM_SECRET)
    517  - 
    518  - return r1+r2
    519  -
    520  - 
    521  -def NTOWFv1(password):
    522  - return hashlib.md4(password.encode('utf-16le')).digest()
    523  - 
    524  - 
    525  -def LMOWFv2(Passwd, User, UserDom, PasswdHash = None):
    526  - return NTOWFv2(Passwd, User, UserDom, PasswdHash)
    527  - 
    528  - 
    529  -def NTOWFv2(Passwd, User, UserDom, PasswdHash = None):
    530  - if PasswdHash is not None:
    531  - fp = hmac.new(PasswdHash, digestmod = 'md5')
    532  - else:
    533  - fp = hmac.new(NTOWFv1(Passwd), digestmod = 'md5')
    534  - fp.update((User.upper() + UserDom).encode('utf-16le'))
    535  - return fp.digest()
    536  - 
    537  - 
    538  -def DESL(K, D):
    539  - """
    540  - Indicates the encryption of an 8-byte data item D with the 16-byte key K
    541  - using the Data Encryption Standard Long (DESL) algorithm.
    542  - The result is 24 bytes in length.
    543  - :param K:
    544  - :param D:
    545  - :return:
    546  - """
    547  - if len(K) != 16:
    548  - raise Exception("K MUST be 16 bytes long")
    549  - if len(D) != 8:
    550  - raise Exception("D MUST be 8 bytes long")
    551  - 
    552  - res = b''
    553  - res += DES(K[:7]).encrypt(D)
    554  - res += DES(K[7:14]).encrypt(D)
    555  - res += DES(K[14:] + b'\x00'*5).encrypt(D)
    556  - return res
    557  - 
  • ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/messages/__init__.py
    1  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/messages/authenticate.py
    1  -import io
    2  - 
    3  -from aardwolf.authentication.ntlm.structures.fields import Fields
    4  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    5  -from aardwolf.authentication.ntlm.structures.version import Version
    6  -from aardwolf.authentication.ntlm.structures.challenge_response import *
    7  - 
    8  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/033d32cc-88f9-4483-9bf2-b273055038ce
    9  -class NTLMAuthenticate:
    10  - def __init__(self, _use_NTLMv2 = True):
    11  - self.Signature = b'NTLMSSP\x00'
    12  - self.MessageType = 3
    13  - self.LmChallengeResponseFields = None
    14  - self.NtChallengeResponseFields = None
    15  - self.DomainNameFields = None
    16  - self.UserNameFields = None
    17  - self.WorkstationFields = None
    18  - self.EncryptedRandomSessionKeyFields = None
    19  - self.NegotiateFlags = None
    20  - self.Version = None
    21  - self.MIC = None
    22  - self.Payload = None
    23  - 
    24  - # high level
    25  - self.LMChallenge = None
    26  - self.NTChallenge = None
    27  - self.DomainName = None
    28  - self.UserName = None
    29  - self.Workstation = None
    30  - self.EncryptedRandomSession = None
    31  - 
    32  - # this is a global variable that needs to be indicated
    33  - self._use_NTLMv2 = _use_NTLMv2
    34  -
    35  - @staticmethod
    36  - def construct(flags, domainname= None, workstationname= None, username= None, encrypted_session= None, lm_response= None, nt_response= None, version = None, mic = b'\x00'*16):
    37  - auth = NTLMAuthenticate()
    38  - auth.Payload = b''
    39  -
    40  - payload_pos = 8+4+8+8+8+8+8+8+4
    41  - if flags & NegotiateFlags.NEGOTIATE_VERSION:
    42  - if not version:
    43  - raise Exception('NEGOTIATE_VERSION set but no Version supplied!')
    44  -
    45  - auth.Version = version
    46  -
    47  - payload_pos += 8
    48  -
    49  - if mic is not None:
    50  - auth.MIC = mic
    51  - payload_pos += 16
    52  -
    53  - if lm_response:
    54  - data = lm_response.to_bytes()
    55  - auth.Payload += data
    56  - auth.LmChallengeResponseFields = Fields(len(data), payload_pos)
    57  - payload_pos += len(data)
    58  - auth.LMChallenge = lm_response
    59  - else:
    60  - auth.LmChallengeResponseFields = Fields(0,0)
    61  -
    62  - if nt_response:
    63  - data = nt_response.to_bytes()
    64  - auth.Payload += data
    65  - auth.NtChallengeResponseFields = Fields(len(data), payload_pos)
    66  - payload_pos += len(data)
    67  - auth.NTChallenge = nt_response
    68  - else:
    69  - auth.NtChallengeResponseFields = Fields(0,payload_pos)
    70  -
    71  -
    72  - if domainname:
    73  - data = domainname.encode('utf-16le')
    74  - auth.Payload += data
    75  - auth.DomainNameFields = Fields(len(data), payload_pos)
    76  - payload_pos += len(data)
    77  - auth.DomainName = domainname
    78  - else:
    79  - auth.DomainNameFields = Fields(0,payload_pos)
    80  -
    81  - if username:
    82  - data = username.encode('utf-16le')
    83  - auth.Payload += data
    84  - auth.UserNameFields = Fields(len(data), payload_pos)
    85  - payload_pos += len(data)
    86  - auth.UserName = username
    87  - else:
    88  - auth.UserNameFields = Fields(0,payload_pos)
    89  -
    90  - if workstationname:
    91  - data = workstationname.encode('utf-16le')
    92  - auth.Payload += data
    93  - auth.WorkstationFields = Fields(len(data), payload_pos)
    94  - payload_pos += len(data)
    95  - auth.Workstation = workstationname
    96  - else:
    97  - auth.WorkstationFields = Fields(0,payload_pos)
    98  -
    99  - if encrypted_session:
    100  - data = encrypted_session
    101  - auth.Payload += data
    102  - auth.EncryptedRandomSessionKeyFields = Fields(len(data), payload_pos)
    103  - payload_pos += len(data)
    104  - auth.EncryptedRandomSession = encrypted_session
    105  - else:
    106  - auth.EncryptedRandomSessionKeyFields = Fields(0,payload_pos)
    107  -
    108  - auth.NegotiateFlags = flags
    109  - return auth
    110  -
    111  - def to_bytes(self):
    112  - t = b''
    113  - t += self.Signature
    114  - t += self.MessageType.to_bytes(4, byteorder = 'little', signed = False)
    115  -
    116  - t += self.LmChallengeResponseFields.to_bytes()
    117  - t += self.NtChallengeResponseFields.to_bytes()
    118  - t += self.DomainNameFields.to_bytes()
    119  - t += self.UserNameFields.to_bytes()
    120  - t += self.WorkstationFields.to_bytes()
    121  - t += self.EncryptedRandomSessionKeyFields.to_bytes()
    122  - t += self.NegotiateFlags.to_bytes(4, byteorder = 'little', signed = False)
    123  - if self.Version:
    124  - t += self.Version.to_bytes()
    125  - if self.MIC is not None:
    126  - t += self.MIC
    127  - t += self.Payload
    128  - return t
    129  -
    130  - 
    131  - @staticmethod
    132  - def from_bytes(bbuff,_use_NTLMv2 = True):
    133  - return NTLMAuthenticate.from_buffer(io.BytesIO(bbuff), _use_NTLMv2 = _use_NTLMv2)
    134  - 
    135  - @staticmethod
    136  - def from_buffer(buff, _use_NTLMv2 = True):
    137  - auth = NTLMAuthenticate(_use_NTLMv2)
    138  - auth.Signature = buff.read(8)
    139  - auth.MessageType = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    140  - auth.LmChallengeResponseFields = Fields.from_buffer(buff)
    141  - auth.NtChallengeResponseFields = Fields.from_buffer(buff)
    142  - auth.DomainNameFields = Fields.from_buffer(buff)
    143  - auth.UserNameFields = Fields.from_buffer(buff)
    144  - auth.WorkstationFields = Fields.from_buffer(buff)
    145  - auth.EncryptedRandomSessionKeyFields = Fields.from_buffer(buff)
    146  - auth.NegotiateFlags = NegotiateFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
    147  - if auth.NegotiateFlags & NegotiateFlags.NEGOTIATE_VERSION:
    148  - auth.Version = Version.from_buffer(buff)
    149  - 
    150  - # TODO: I'm not sure about this condition!!! Need to test this!
    151  - if auth.NegotiateFlags & NegotiateFlags.NEGOTIATE_ALWAYS_SIGN:
    152  - auth.MIC = buff.read(16)
    153  - 
    154  - currPos = buff.tell()
    155  - auth.Payload = buff.read()
    156  - 
    157  - if auth._use_NTLMv2 and auth.NtChallengeResponseFields.length > 24:
    158  - buff.seek(auth.LmChallengeResponseFields.offset, io.SEEK_SET)
    159  - auth.LMChallenge = LMv2Response.from_buffer(buff)
    160  -
    161  - 
    162  - buff.seek(auth.NtChallengeResponseFields.offset, io.SEEK_SET)
    163  - auth.NTChallenge = NTLMv2Response.from_buffer(buff)
    164  - 
    165  - else:
    166  - buff.seek(auth.LmChallengeResponseFields.offset, io.SEEK_SET)
    167  - auth.LMChallenge = LMResponse.from_buffer(buff)
    168  -
    169  - buff.seek(auth.NtChallengeResponseFields.offset, io.SEEK_SET)
    170  - auth.NTChallenge = NTLMv1Response.from_buffer(buff)
    171  - 
    172  - buff.seek(auth.DomainNameFields.offset,io.SEEK_SET)
    173  - auth.DomainName = buff.read(auth.DomainNameFields.length).decode('utf-16le')
    174  -
    175  - buff.seek(auth.UserNameFields.offset,io.SEEK_SET)
    176  - auth.UserName = buff.read(auth.UserNameFields.length).decode('utf-16le')
    177  - 
    178  - buff.seek(auth.WorkstationFields.offset,io.SEEK_SET)
    179  - auth.Workstation = buff.read(auth.WorkstationFields.length).decode('utf-16le')
    180  - 
    181  - buff.seek(auth.EncryptedRandomSessionKeyFields.offset,io.SEEK_SET)
    182  - auth.EncryptedRandomSession = buff.read(auth.EncryptedRandomSessionKeyFields.length)
    183  -
    184  - buff.seek(currPos, io.SEEK_SET)
    185  - 
    186  - return auth
    187  - 
    188  - def __repr__(self):
    189  - t = '== NTLMAuthenticate ==\r\n'
    190  - t += 'Signature : %s\r\n' % repr(self.Signature)
    191  - t += 'MessageType : %s\r\n' % repr(self.MessageType)
    192  - t += 'NegotiateFlags: %s\r\n' % repr(self.NegotiateFlags)
    193  - t += 'Version : %s\r\n' % repr(self.Version)
    194  - t += 'MIC : %s\r\n' % repr(self.MIC.hex() if self.MIC else 'None')
    195  - t += 'LMChallenge : %s\r\n' % repr(self.LMChallenge)
    196  - t += 'NTChallenge : %s\r\n' % repr(self.NTChallenge)
    197  - t += 'DomainName : %s\r\n' % repr(self.DomainName)
    198  - t += 'UserName : %s\r\n' % repr(self.UserName)
    199  - t += 'Workstation : %s\r\n' % repr(self.Workstation)
    200  - t += 'EncryptedRandomSession: %s\r\n' % repr(self.EncryptedRandomSession.hex())
    201  - return t
    202  - 
    203  -def test():
    204  - test_reconstrut()
    205  - test_construct()
    206  - test_2()
    207  -
    208  -def test_2():
    209  - data = bytes.fromhex('4e 54 4c 4d 53 53 50 00 03 00 00 00 18 00 18 006c 00 00 00 54 00 54 00 84 00 00 00 0c 00 0c 0048 00 00 00 08 00 08 00 54 00 00 00 10 00 10 005c 00 00 00 10 00 10 00 d8 00 00 00 35 82 88 e205 01 28 0a 00 00 00 0f 44 00 6f 00 6d 00 61 0069 00 6e 00 55 00 73 00 65 00 72 00 43 00 4f 004d 00 50 00 55 00 54 00 45 00 52 00 86 c3 50 97ac 9c ec 10 25 54 76 4a 57 cc cc 19 aa aa aa aaaa aa aa aa 68 cd 0a b8 51 e5 1c 96 aa bc 92 7beb ef 6a 1c 01 01 00 00 00 00 00 00 00 00 00 0000 00 00 00 aa aa aa aa aa aa aa aa 00 00 00 0002 00 0c 00 44 00 6f 00 6d 00 61 00 69 00 6e 0001 00 0c 00 53 00 65 00 72 00 76 00 65 00 72 0000 00 00 00 00 00 00 00 c5 da d2 54 4f c9 79 9094 ce 1c e9 0b c9 d0 3e')
    210  - challenge = NTLMAuthenticate.from_bytes(data)
    211  - print(repr(challenge))
    212  -
    213  -def test_reconstrut(data = None):
    214  - print('=== reconstruct===')
    215  - if not data:
    216  - auth_test_data = bytes.fromhex('4e544c4d5353500003000000180018007c000000180118019400000008000800580000000c000c0060000000100010006c00000010001000ac010000158288e20a00d73a0000000f0d98eb57e9c52820709c99b98ca321a15400450053005400760069006300740069006d00570049004e0031003000580036003400000000000000000000000000000000000000000000000000fade3940b9381c53c91ddcdd0d44000b0101000000000000aec600bfc5fdd4011bfa20699d7628730000000002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40106000400020000000800300030000000000000000000000000200000527d27f234de743760966384d36f61ae2aa4fc2a380699f8caa600011b486d890a0010000000000000000000000000000000000009001e0063006900660073002f00310030002e00310030002e00310030002e003200000000000000000000000000fd67edfb41c09465a91fd733deb0b55b')
    217  - else:
    218  - auth_test_data = data
    219  - challenge = NTLMAuthenticate.from_bytes(auth_test_data)
    220  - print(repr(challenge))
    221  - auth_test_data_verify = challenge.to_bytes()
    222  - print('====== reconstructed ====')
    223  - print(hexdump(auth_test_data_verify))
    224  - print('====== original ====')
    225  - print(hexdump(auth_test_data))
    226  - assert auth_test_data == auth_test_data_verify
    227  -
    228  -
    229  -def test_construct():
    230  - pass
    231  -
    232  -if __name__ == '__main__':
    233  - from aardwolf.utils.hexdump import hexdump
    234  -
    235  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/messages/challenge.py
    1  -import os
    2  -import io
    3  - 
    4  -from aardwolf.authentication.ntlm.structures.fields import Fields
    5  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    6  -from aardwolf.authentication.ntlm.structures.version import Version
    7  -from aardwolf.authentication.ntlm.structures.avpair import AVPairs
    8  - 
    9  -from aardwolf.authentication.ntlm.templates.server import NTLMServerTemplates
    10  - 
    11  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/801a4681-8809-4be9-ab0d-61dcfe762786
    12  -class NTLMChallenge:
    13  - def __init__(self):
    14  - self.Signature = b'NTLMSSP\x00'
    15  - self.MessageType = 2
    16  - self.TargetNameFields = None
    17  - self.NegotiateFlags = None
    18  - self.ServerChallenge = None
    19  - self.Reserved = b'\x00'*8
    20  - self.TargetInfoFields = None
    21  - self.Version = None
    22  - self.Payload = None
    23  - 
    24  - self.TargetName = None
    25  - self.TargetInfo = None
    26  -
    27  -
    28  - @staticmethod
    29  - def from_bytes(bbuff):
    30  - return NTLMChallenge.from_buffer(io.BytesIO(bbuff))
    31  - 
    32  - @staticmethod
    33  - def from_buffer(buff):
    34  - t = NTLMChallenge()
    35  - t.Signature = buff.read(8)
    36  - t.MessageType = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    37  - t.TargetNameFields = Fields.from_buffer(buff)
    38  - t.NegotiateFlags = NegotiateFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
    39  - t.ServerChallenge = buff.read(8)
    40  - t.Reserved = buff.read(8)
    41  - t.TargetInfoFields = Fields.from_buffer(buff)
    42  -
    43  - if t.NegotiateFlags & NegotiateFlags.NEGOTIATE_VERSION:
    44  - t.Version = Version.from_buffer(buff)
    45  -
    46  - currPos = buff.tell()
    47  - t.Payload = buff.read()
    48  -
    49  - if t.TargetNameFields.length != 0:
    50  - buff.seek(t.TargetNameFields.offset, io.SEEK_SET)
    51  - raw_data = buff.read(t.TargetNameFields.length)
    52  - try:
    53  - t.TargetName = raw_data.decode('utf-16le')
    54  - except UnicodeDecodeError:
    55  - # yet another cool bug.
    56  - t.TargetName = raw_data.decode('utf-8')
    57  -
    58  - if t.TargetInfoFields.length != 0:
    59  - buff.seek(t.TargetInfoFields.offset, io.SEEK_SET)
    60  - raw_data = buff.read(t.TargetInfoFields.length)
    61  - t.TargetInfo = AVPairs.from_bytes(raw_data)
    62  -
    63  -
    64  -
    65  - return t
    66  - 
    67  - @staticmethod
    68  - def construct_from_template(templateName, challenge = os.urandom(8), ess = True):
    69  - version = NTLMServerTemplates[templateName]['version']
    70  - challenge = challenge
    71  - targetName = NTLMServerTemplates[templateName]['targetname']
    72  - targetInfo = NTLMServerTemplates[templateName]['targetinfo']
    73  - targetInfo = NTLMServerTemplates[templateName]['targetinfo']
    74  - flags = NTLMServerTemplates[templateName]['flags']
    75  - if ess:
    76  - flags |= NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY
    77  - else:
    78  - flags &= ~NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY
    79  - 
    80  - return NTLMChallenge.construct(challenge=challenge, targetName = targetName, targetInfo = targetInfo, version = version, flags= flags)
    81  -
    82  -
    83  - # TODO: needs some clearning up (like re-calculating flags when needed)
    84  - @staticmethod
    85  - def construct(challenge = os.urandom(8), targetName = None, targetInfo = None, version = None, flags = None):
    86  - pos = 48
    87  - if version:
    88  - pos += 8
    89  - t = NTLMChallenge()
    90  - t.NegotiateFlags = flags
    91  - t.Version = version
    92  - t.ServerChallenge = challenge
    93  - t.TargetName = targetName
    94  - t.TargetInfo = targetInfo
    95  - 
    96  - t.TargetNameFields = Fields(len(t.TargetName.encode('utf-16le')),pos)
    97  - t.TargetInfoFields = Fields(len(t.TargetInfo.to_bytes()), pos + len(t.TargetName.encode('utf-16le')))
    98  - 
    99  - t.Payload = t.TargetName.encode('utf-16le')
    100  - t.Payload += t.TargetInfo.to_bytes()
    101  - 
    102  - return t
    103  - 
    104  - def to_bytes(self):
    105  - tn = self.TargetName.encode('utf-16le')
    106  - ti = self.TargetInfo.to_bytes()
    107  - 
    108  - buff = self.Signature
    109  - buff += self.MessageType.to_bytes(4, byteorder = 'little', signed = False)
    110  - buff += self.TargetNameFields.to_bytes()
    111  - buff += self.NegotiateFlags.to_bytes(4, byteorder = 'little', signed = False)
    112  - buff += self.ServerChallenge
    113  - buff += self.Reserved
    114  - buff += self.TargetInfoFields.to_bytes()
    115  - if self.Version:
    116  - buff += self.Version.to_bytes()
    117  - buff += self.Payload
    118  - 
    119  - return buff
    120  - 
    121  - def __repr__(self):
    122  - t = '== NTLMChallenge ==\r\n'
    123  - t += 'Signature : %s\r\n' % repr(self.Signature)
    124  - t += 'MessageType : %s\r\n' % repr(self.MessageType)
    125  - t += 'ServerChallenge: %s\r\n' % repr(self.ServerChallenge)
    126  - t += 'TargetName : %s\r\n' % repr(self.TargetName)
    127  - t += 'TargetInfo : %s\r\n' % repr(self.TargetInfo)
    128  - return t
    129  - 
    130  - def toBase64(self):
    131  - return base64.b64encode(self.to_bytes()).decode('ascii')
    132  - 
    133  - 
    134  -def test():
    135  - test_reconstrut()
    136  - test_construct()
    137  - test_template()
    138  -
    139  -def test_reconstrut(data = None):
    140  - print('=== reconstruct===')
    141  - if not data:
    142  - challenge_test_data = bytes.fromhex('4e544c4d53535000020000000800080038000000158289e2a7314a557bdb11bf000000000000000072007200400000000a0063450000000f540045005300540002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40100000000')
    143  - else:
    144  - challenge_test_data = data
    145  - challenge = NTLMChallenge.from_bytes(challenge_test_data)
    146  - print(repr(challenge))
    147  - challenge_test_data_verify = challenge.to_bytes()
    148  - print('====== reconstructed ====')
    149  - print(hexdump(challenge_test_data_verify))
    150  - print('====== original ====')
    151  - print(hexdump(challenge_test_data))
    152  - assert challenge_test_data == challenge_test_data_verify
    153  -
    154  -def test_template():
    155  -
    156  - challenge = NTLMChallenge.construct_from_template('Windows2003')
    157  - test_reconstrut(challenge.to_bytes())
    158  -
    159  -def test_construct():
    160  - pass
    161  -
    162  -if __name__ == '__main__':
    163  - from aardwolf.utils.hexdump import hexdump
    164  -
    165  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/messages/negotiate.py
    1  -import io
    2  - 
    3  - 
    4  -from aardwolf.authentication.ntlm.structures.fields import Fields
    5  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    6  -from aardwolf.authentication.ntlm.structures.version import Version
    7  - 
    8  -# https://msdn.microsoft.com/en-us/library/cc236641.aspx
    9  -class NTLMNegotiate:
    10  - def __init__(self):
    11  - self.Signature = b'NTLMSSP\x00'
    12  - self.MessageType = 1
    13  - self.NegotiateFlags = None
    14  - self.DomainNameFields = None
    15  - self.WorkstationFields = None
    16  - self.Version = None
    17  - self.Payload = None
    18  - 
    19  - ####High-level variables
    20  - self.Domain = None
    21  - self.Workstation = None
    22  - 
    23  - @staticmethod
    24  - def from_bytes(bbuff):
    25  - return NTLMNegotiate.from_buffer(io.BytesIO(bbuff))
    26  - 
    27  - @staticmethod
    28  - def from_buffer(buff):
    29  - t = NTLMNegotiate()
    30  - t.Signature = buff.read(8)
    31  - t.MessageType = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    32  - t.NegotiateFlags = NegotiateFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
    33  - t.DomainNameFields = Fields.from_buffer(buff)
    34  - t.WorkstationFields = Fields.from_buffer(buff)
    35  - 
    36  - if t.NegotiateFlags & NegotiateFlags.NEGOTIATE_VERSION:
    37  - t.Version = Version.from_buffer(buff)
    38  -
    39  -
    40  - currPos = buff.tell()
    41  - t.Payload = buff.read()
    42  - 
    43  - #currPos = buff.tell()
    44  -
    45  - if t.DomainNameFields.length != 0:
    46  - buff.seek(t.DomainNameFields.offset, io.SEEK_SET)
    47  - raw_data = buff.read(t.WorkstationFields.length)
    48  - #print(raw_data)
    49  - #print(t.DomainNameFields.length)
    50  - try:
    51  - t.Domain = raw_data.decode('utf-16le')
    52  - except UnicodeDecodeError:
    53  - # yet another cool bug.
    54  - t.Domain = raw_data.decode('utf-8')
    55  - 
    56  - if t.WorkstationFields.length != 0:
    57  - buff.seek(t.WorkstationFields.offset, io.SEEK_SET)
    58  - raw_data = buff.read(t.WorkstationFields.length)
    59  - try:
    60  - t.Workstation = raw_data.decode('utf-16le')
    61  - except UnicodeDecodeError:
    62  - # yet another cool bug.
    63  - t.Workstation = raw_data.decode('utf-8')
    64  - 
    65  - #buff.seek(currPos, io.SEEK_SET)
    66  -
    67  - return t
    68  -
    69  - @staticmethod
    70  - def construct(flags, domainname = None, workstationname = None, version = None):
    71  - nego = NTLMNegotiate()
    72  - nego.NegotiateFlags = flags
    73  - nego.Payload = b''
    74  -
    75  - payload_pos = 32
    76  - if flags & NegotiateFlags.NEGOTIATE_VERSION:
    77  - if not version:
    78  - raise Exception('NEGOTIATE_VERSION set but no Version supplied!')
    79  - payload_pos += 8
    80  - nego.Version = version
    81  -
    82  -
    83  -
    84  - if nego.NegotiateFlags & NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED and domainname:
    85  - data = domainname.encode('utf-16le')
    86  - nego.Payload += data
    87  - nego.DomainNameFields = Fields(len(data), payload_pos)
    88  - payload_pos += len(data)
    89  - nego.Domain = data
    90  -
    91  - else:
    92  - nego.DomainNameFields = Fields(0,0)
    93  -
    94  - if nego.NegotiateFlags & NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED and workstationname:
    95  - data = workstationname.encode('utf-16le')
    96  - nego.Payload += data
    97  - nego.WorkstationFields = Fields(len(data), payload_pos)
    98  - payload_pos += len(data)
    99  - nego.Workstation = data
    100  -
    101  - else:
    102  - nego.WorkstationFields = Fields(0,0)
    103  -
    104  - return nego
    105  -
    106  - def to_bytes(self):
    107  - t = b''
    108  - t += self.Signature
    109  - t += self.MessageType.to_bytes(4, byteorder = 'little', signed = False)
    110  - t += self.NegotiateFlags.to_bytes(4, byteorder = 'little', signed = False)
    111  - t += self.DomainNameFields.to_bytes()
    112  - t += self.WorkstationFields.to_bytes()
    113  - if self.Version:
    114  - t += self.Version.to_bytes()
    115  - t += self.Payload
    116  - return t
    117  - 
    118  - def __repr__(self):
    119  - t = '== NTLMNegotiate ==\r\n'
    120  - t += 'Signature : %s\r\n' % repr(self.Signature)
    121  - t += 'MessageType: %s\r\n' % repr(self.MessageType)
    122  - t += 'NegotiateFlags: %s\r\n' % repr(self.NegotiateFlags)
    123  - t += 'Version : %s\r\n' % repr(self.Version)
    124  - t += 'Domain : %s\r\n' % repr(self.Domain)
    125  - t += 'Workstation: %s\r\n' % repr(self.Workstation)
    126  -
    127  - return t
    128  -
    129  -def test():
    130  - test_reconstrut()
    131  - test_construct()
    132  -
    133  -def test_reconstrut(data = None):
    134  - print('=== reconstruct===')
    135  - if not data:
    136  - nego_test_data = bytes.fromhex('4e544c4d5353500001000000978208e2000000000000000000000000000000000a00d73a0000000f')
    137  - else:
    138  - nego_test_data = data
    139  - nego = NTLMNegotiate.from_bytes(nego_test_data)
    140  - print(repr(nego))
    141  - nego_test_data_verify = nego.to_bytes()
    142  - assert nego_test_data == nego_test_data_verify
    143  -
    144  -def test_construct():
    145  - flags = NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_KEY_EXCH|NegotiateFlags.NEGOTIATE_128|\
    146  - NegotiateFlags.NEGOTIATE_VERSION|\
    147  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|\
    148  - NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|NegotiateFlags.NEGOTIATE_NTLM|NegotiateFlags.NEGOTIATE_LM_KEY|\
    149  - NegotiateFlags.NEGOTIATE_SIGN|NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NTLM_NEGOTIATE_OEM|NegotiateFlags.NEGOTIATE_UNICODE|\
    150  - NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED|NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED
    151  - nego = NTLMNegotiate.construct(flags, domainname = "alma.com", workstationname = "testjoe", version = Version.construct())
    152  - nego.to_bytes()
    153  - print(repr(nego))
    154  -
    155  - test_reconstrut(nego.to_bytes())
    156  -
    157  - flags = NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_KEY_EXCH|NegotiateFlags.NEGOTIATE_128|\
    158  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|\
    159  - NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|NegotiateFlags.NEGOTIATE_NTLM|NegotiateFlags.NEGOTIATE_LM_KEY|\
    160  - NegotiateFlags.NEGOTIATE_SIGN|NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NTLM_NEGOTIATE_OEM|NegotiateFlags.NEGOTIATE_UNICODE|\
    161  - NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED|NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED
    162  - nego = NTLMNegotiate.construct(flags, domainname = "alma.com", workstationname = "testjoe2")
    163  - print(nego.to_bytes())
    164  - print(repr(nego))
    165  -
    166  - test_reconstrut(nego.to_bytes())
    167  -
    168  -if __name__ == '__main__':
    169  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/mpn.py
    1  -#
    2  -#
    3  -# Interface to support remote authentication via multiplexor
    4  -#
    5  -# Connects to the multiplexor server, and starts an SSPI server locally for the specific agentid
    6  -# SSPI server will be used to perform NTLM authentication remotely,
    7  -# while constructing a local NTLM authentication object
    8  -# After the auth finishes, it also grabs the sessionkey.
    9  -# The NTLM object can be used in future operations (encrypt/decrypt/sign) locally
    10  -# without the need of future remote calls
    11  -#
    12  - 
    13  -import asyncio
    14  -from aardwolf import logger
    15  -from aardwolf.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
    16  -from mpnop.operator import MPNOPerator
    17  -import enum
    18  - 
    19  -class ISC_REQ(enum.IntFlag):
    20  - DELEGATE = 1
    21  - MUTUAL_AUTH = 2
    22  - REPLAY_DETECT = 4
    23  - SEQUENCE_DETECT = 8
    24  - CONFIDENTIALITY = 16
    25  - USE_SESSION_KEY = 32
    26  - PROMPT_FOR_CREDS = 64
    27  - USE_SUPPLIED_CREDS = 128
    28  - ALLOCATE_MEMORY = 256
    29  - USE_DCE_STYLE = 512
    30  - DATAGRAM = 1024
    31  - CONNECTION = 2048
    32  - CALL_LEVEL = 4096
    33  - FRAGMENT_SUPPLIED = 8192
    34  - EXTENDED_ERROR = 16384
    35  - STREAM = 32768
    36  - INTEGRITY = 65536
    37  - IDENTIFY = 131072
    38  - NULL_SESSION = 262144
    39  - MANUAL_CRED_VALIDATION = 524288
    40  - RESERVED1 = 1048576
    41  - FRAGMENT_TO_FIT = 2097152
    42  - HTTP = 0x10000000
    43  - 
    44  -class RDPNTLMMPN:
    45  - def __init__(self, settings):
    46  - self.settings = settings
    47  - self.operator = settings.operator
    48  - self.agent_id = settings.agent_id
    49  - self.mode = None #'CLIENT'
    50  - self.sspi = None
    51  - self.operator = None
    52  - self.client = None
    53  - self.target = None
    54  - #self.ntlmChallenge = None
    55  -
    56  - self.session_key = None
    57  - self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
    58  - 
    59  - def setup(self):
    60  - return
    61  -
    62  - @property
    63  - def ntlmChallenge(self):
    64  - return self.ntlm_ctx.ntlmChallenge
    65  -
    66  - def get_sealkey(self, mode = 'Client'):
    67  - return self.ntlm_ctx.get_sealkey(mode = mode)
    68  -
    69  - def get_signkey(self, mode = 'Client'):
    70  - return self.ntlm_ctx.get_signkey(mode = mode)
    71  -
    72  -
    73  - def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
    74  - return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
    75  -
    76  - def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
    77  - return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
    78  -
    79  - def get_session_key(self):
    80  - return self.session_key
    81  -
    82  - def get_extra_info(self):
    83  - return self.ntlm_ctx.get_extra_info()
    84  -
    85  - def is_extended_security(self):
    86  - return self.ntlm_ctx.is_extended_security()
    87  -
    88  - async def authenticate(self, authData = None, flags = None, seq_number = 0, is_rpc = False):
    89  - try:
    90  - if self.operator is None:
    91  - self.operator = MPNOPerator(self.settings.get_url())
    92  - asyncio.create_task(self.operator.run())
    93  - await asyncio.wait_for(self.operator.connected_evt.wait(), timeout=self.settings.timeout)
    94  - if self.sspi is None:
    95  - self.sspi, err = await self.operator.create_sspi(self.agent_id)
    96  - if err is not None:
    97  - return None, None, err
    98  - 
    99  - if is_rpc is True and flags is None:
    100  - flags = ISC_REQ.REPLAY_DETECT | ISC_REQ.CONFIDENTIALITY| ISC_REQ.USE_SESSION_KEY| ISC_REQ.INTEGRITY| ISC_REQ.SEQUENCE_DETECT| ISC_REQ.CONNECTION
    101  - flags = int(flags)
    102  -
    103  - if self.settings.mode == 'CLIENT':
    104  - if authData is None:
    105  - ctx_attr, data, err = await self.sspi.ntlm_authenticate(context_attributes = flags)
    106  - if err is not None:
    107  - raise err
    108  -
    109  - self.ntlm_ctx.load_negotiate(data)
    110  - return data, err, err
    111  - else:
    112  - self.ntlm_ctx.load_challenge(authData)
    113  - ctx_attr, data, err = await self.sspi.ntlm_challenge(authData, context_attributes = flags)
    114  - if err is None:
    115  - self.ntlm_ctx.load_authenticate( data)
    116  - self.session_key, err = await self.sspi.get_sessionkey()
    117  - if err is None:
    118  - self.ntlm_ctx.load_sessionkey(self.get_session_key())
    119  -
    120  - await self.sspi.disconnect()
    121  - return data, err, err
    122  -
    123  - else:
    124  - return None, None, Exception('Server mode not implemented!')
    125  - except Exception as e:
    126  - return None, None, e
    127  - 
    128  -
    129  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/multiplexor.py
    1  -#
    2  -#
    3  -# Interface to support remote authentication via multiplexor
    4  -#
    5  -# Connects to the multiplexor server, and starts an SSPI server locally for the specific agentid
    6  -# SSPI server will be used to perform NTLM authentication remotely,
    7  -# while constructing a local NTLM authentication object
    8  -# After the auth finishes, it also grabs the sessionkey.
    9  -# The NTLM object can be used in future operations (encrypt/decrypt/sign) locally
    10  -# without the need of future remote calls
    11  -#
    12  - 
    13  -from aardwolf import logger
    14  -from aardwolf.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
    15  -from multiplexor.operator.external.sspi import SSPINTLMClient
    16  -from multiplexor.operator import MultiplexorOperator
    17  -import enum
    18  - 
    19  -class ISC_REQ(enum.IntFlag):
    20  - DELEGATE = 1
    21  - MUTUAL_AUTH = 2
    22  - REPLAY_DETECT = 4
    23  - SEQUENCE_DETECT = 8
    24  - CONFIDENTIALITY = 16
    25  - USE_SESSION_KEY = 32
    26  - PROMPT_FOR_CREDS = 64
    27  - USE_SUPPLIED_CREDS = 128
    28  - ALLOCATE_MEMORY = 256
    29  - USE_DCE_STYLE = 512
    30  - DATAGRAM = 1024
    31  - CONNECTION = 2048
    32  - CALL_LEVEL = 4096
    33  - FRAGMENT_SUPPLIED = 8192
    34  - EXTENDED_ERROR = 16384
    35  - STREAM = 32768
    36  - INTEGRITY = 65536
    37  - IDENTIFY = 131072
    38  - NULL_SESSION = 262144
    39  - MANUAL_CRED_VALIDATION = 524288
    40  - RESERVED1 = 1048576
    41  - FRAGMENT_TO_FIT = 2097152
    42  - HTTP = 0x10000000
    43  - 
    44  -class RDPNTLMMultiplexor:
    45  - def __init__(self, settings):
    46  - self.settings = settings
    47  - self.mode = None #'CLIENT'
    48  - self.sspi = None
    49  - self.operator = None
    50  - self.client = None
    51  - self.target = None
    52  - #self.ntlmChallenge = None
    53  -
    54  - self.session_key = None
    55  - self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
    56  - 
    57  - def setup(self):
    58  - return
    59  -
    60  - @property
    61  - def ntlmChallenge(self):
    62  - return self.ntlm_ctx.ntlmChallenge
    63  -
    64  - def get_sealkey(self, mode = 'Client'):
    65  - return self.ntlm_ctx.get_sealkey(mode = mode)
    66  -
    67  - def get_signkey(self, mode = 'Client'):
    68  - return self.ntlm_ctx.get_signkey(mode = mode)
    69  -
    70  -
    71  - def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
    72  - return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
    73  -
    74  - def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
    75  - return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
    76  -
    77  - def get_session_key(self):
    78  - return self.session_key
    79  -
    80  - def get_extra_info(self):
    81  - return self.ntlm_ctx.get_extra_info()
    82  -
    83  - def is_extended_security(self):
    84  - return self.ntlm_ctx.is_extended_security()
    85  -
    86  - #async def encrypt(self, data, message_no):
    87  - # return self.sspi.encrypt(data, message_no)
    88  - #
    89  - #async def decrypt(self, data, message_no):
    90  - # return self.sspi.decrypt(data, message_no)
    91  -
    92  - async def authenticate(self, authData = None, flags = None, seq_number = 0, is_rpc = False):
    93  - if self.sspi is None:
    94  - res, err = await self.start_remote_sspi()
    95  - if err is not None:
    96  - return None, None, err
    97  - 
    98  - if is_rpc is True and flags is None:
    99  - flags = ISC_REQ.REPLAY_DETECT | ISC_REQ.CONFIDENTIALITY| ISC_REQ.USE_SESSION_KEY| ISC_REQ.INTEGRITY| ISC_REQ.SEQUENCE_DETECT| ISC_REQ.CONNECTION
    100  - flags = int(flags)
    101  -
    102  - if self.settings.mode == 'CLIENT':
    103  - if authData is None:
    104  - data, res = await self.sspi.authenticate(flags = flags)
    105  - if res is None:
    106  - self.ntlm_ctx.load_negotiate(data)
    107  - return data, res, None
    108  - else:
    109  - self.ntlm_ctx.load_challenge( authData)
    110  - data, res = await self.sspi.challenge(authData, flags = flags)
    111  - if res is None:
    112  - self.ntlm_ctx.load_authenticate( data)
    113  - self.session_key, res = await self.sspi.get_session_key()
    114  - if res is None:
    115  - self.ntlm_ctx.load_sessionkey(self.get_session_key())
    116  -
    117  - await self.sspi.disconnect()
    118  - return data, res, None
    119  -
    120  - else:
    121  - return None, None, Exception('Server mode not implemented!')
    122  - 
    123  - 
    124  - async def start_remote_sspi(self):
    125  - try:
    126  - #print(self.settings.get_url())
    127  - self.operator = MultiplexorOperator(self.settings.get_url(), logging_sink=logger)
    128  - await self.operator.connect()
    129  - #creating virtual sspi server
    130  - server_info = await self.operator.start_sspi(self.settings.agent_id)
    131  - #print(server_info)
    132  - 
    133  - sspi_url = 'ws://%s:%s' % (server_info['listen_ip'], server_info['listen_port'])
    134  - 
    135  - #print(sspi_url)
    136  - self.sspi = SSPINTLMClient(sspi_url)
    137  - await self.sspi.connect()
    138  - return True, None
    139  - except Exception as e:
    140  - import traceback
    141  - traceback.print_exc()
    142  - return None, e
    143  -
    144  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/native.py
    1  -import os
    2  -import struct
    3  -import hmac
    4  -import copy
    5  -import hashlib
    6  - 
    7  -from aardwolf.authentication.ntlm.structures.serverinfo import NTLMServerInfo
    8  -from aardwolf.authentication.ntlm.templates.server import NTLMServerTemplates
    9  -from aardwolf.authentication.ntlm.templates.client import NTLMClientTemplates
    10  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    11  -from aardwolf.authentication.ntlm.structures.version import Version
    12  -from aardwolf.authentication.ntlm.structures.ntlmssp_message_signature import NTLMSSP_MESSAGE_SIGNATURE
    13  -from aardwolf.authentication.ntlm.structures.ntlmssp_message_signature_noext import NTLMSSP_MESSAGE_SIGNATURE_NOEXT
    14  -from aardwolf.authentication.ntlm.messages.negotiate import NTLMNegotiate
    15  -from aardwolf.authentication.ntlm.messages.challenge import NTLMChallenge
    16  -from aardwolf.authentication.ntlm.messages.authenticate import NTLMAuthenticate
    17  -from aardwolf.authentication.ntlm.creds_calc import netntlmv2, AVPAIRType, LMResponse, netntlm, netntlm_ess
    18  -from unicrypto.symmetric import RC4
    19  -from unicrypto import hashlib
    20  -from unicrypto import hmac
    21  - 
    22  - 
    23  -class NTLMHandlerSettings:
    24  - def __init__(self, credential, mode = 'CLIENT', template_name = 'Windows10_15063', custom_template = None):
    25  - self.credential = credential
    26  - self.mode = mode
    27  - self.template_name = template_name
    28  - self.custom_template = custom_template #for custom templates, must be dict
    29  - 
    30  - self.encrypt = False
    31  -
    32  - self.template = None
    33  - self.ntlm_downgrade = False
    34  -
    35  - self.construct_message_template()
    36  -
    37  - def construct_message_template(self):
    38  - if self.mode.upper() == 'MANUAL':
    39  - return
    40  -
    41  - if not self.template_name:
    42  - if not self.custom_template:
    43  - raise Exception('No NTLM tamplate specified!')
    44  -
    45  - self.template = self.custom_template
    46  -
    47  - self.encrypt = True
    48  - 
    49  - if self.encrypt is True:
    50  - self.template_name = 'Windows10_15063_channel'
    51  -
    52  - if self.mode.upper() == 'SERVER':
    53  - if self.template_name in NTLMServerTemplates:
    54  - self.template = NTLMServerTemplates[self.template_name]
    55  - else:
    56  - raise Exception('No NTLM server template found with name %s' % self.template_name)
    57  -
    58  - else:
    59  - if self.template_name in NTLMClientTemplates:
    60  - self.template = NTLMClientTemplates[self.template_name]
    61  - if 'ntlm_downgrade' in self.template:
    62  - self.ntlm_downgrade = self.template['ntlm_downgrade']
    63  - else:
    64  - raise Exception('No NTLM server template found with name %s' % self.template_name)
    65  -
    66  - 
    67  -class NTLMAUTHHandler:
    68  - def __init__(self, settings):
    69  - self.settings = settings #NTLMHandlerSettings
    70  -
    71  - self.mode = None
    72  - self.flags = None
    73  - self.challenge = None
    74  -
    75  - self.ntlmNegotiate = None #ntlm Negotiate message from client
    76  - self.ntlmChallenge = None #ntlm Challenge message to client
    77  - self.ntlmAuthenticate = None #ntlm Authenticate message from client
    78  -
    79  - self.ntlmNegotiate_raw = None #message as bytes, as it's recieved/sent
    80  - self.ntlmChallenge_raw = None #message as bytes, as it's recieved/sent
    81  - self.ntlmAuthenticate_raw = None #message as bytes, as it's recieved/sent
    82  - 
    83  -
    84  - self.EncryptedRandomSessionKey = None
    85  - self.RandomSessionKey = None
    86  - self.SessionBaseKey = None
    87  - self.KeyExchangeKey = None
    88  -
    89  - self.SignKey_client = None
    90  - self.SealKey_client = None
    91  - self.SignKey_server = None
    92  - self.SealKey_server = None
    93  - 
    94  - self.crypthandle_client = None
    95  - self.crypthandle_server = None
    96  - #self.signhandle_server = None doesnt exists, only crypthandle
    97  - #self.signhandle_client = None doesnt exists, only crypthandle
    98  -
    99  - self.seq_number = 0
    100  - self.iteration_cnt = 0
    101  - self.ntlm_credentials = None
    102  - self.timestamp = None #used in unittest only!
    103  - self.extra_info = None
    104  - self.setup()
    105  - 
    106  - def setup(self):
    107  - self.mode = self.settings.mode
    108  - if self.mode.upper() == 'MANUAL':
    109  - #for passign the messages automatically with the sessionbasekey, the using this class for sign and seal
    110  - return
    111  -
    112  - if 'challenge' not in self.settings.template:
    113  - self.challenge = os.urandom(8)
    114  - else:
    115  - self.challenge = self.settings.template['challenge']
    116  - self.flags = self.settings.template['flags']
    117  - if 'session_key' in self.settings.template:
    118  - self.RandomSessionKey = self.settings.template['session_key']
    119  -
    120  - self.timestamp = self.settings.template.get('timestamp') #used in unittest only!
    121  -
    122  - def load_negotiate(self, data):
    123  - self.ntlmNegotiate = NTLMNegotiate.from_bytes(data)
    124  -
    125  - def load_challenge(self, data):
    126  - self.ntlmChallenge = NTLMChallenge.from_bytes(data)
    127  -
    128  - def load_authenticate(self, data):
    129  - self.ntlmAuthenticate = NTLMAuthenticate.from_bytes(data)
    130  -
    131  - def load_sessionkey(self, data):
    132  - self.RandomSessionKey = data
    133  - self.setup_crypto()
    134  - 
    135  - def get_seq_number(self):
    136  - return self.seq_number
    137  -
    138  - def set_sign(self, tf = True):
    139  - if tf == True:
    140  - self.flags |= NegotiateFlags.NEGOTIATE_SIGN
    141  - else:
    142  - self.flags &= ~NegotiateFlags.NEGOTIATE_SIGN
    143  -
    144  - def set_seal(self, tf = True):
    145  - if tf == True:
    146  - self.flags |= NegotiateFlags.NEGOTIATE_SEAL
    147  - else:
    148  - self.flags &= ~NegotiateFlags.NEGOTIATE_SEAL
    149  -
    150  - def set_version(self, tf = True):
    151  - if tf == True:
    152  - self.flags |= NegotiateFlags.NEGOTIATE_VERSION
    153  - else:
    154  - self.flags &= ~NegotiateFlags.NEGOTIATE_VERSION
    155  -
    156  - def is_extended_security(self):
    157  - return NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY in self.ntlmChallenge.NegotiateFlags
    158  -
    159  - def get_extra_info(self):
    160  - self.extra_info = NTLMServerInfo.from_challenge(self.ntlmChallenge)
    161  - return self.extra_info
    162  -
    163  - def MAC(self, handle, signingKey, seqNum, message):
    164  - if self.is_extended_security() == True:
    165  - msg = NTLMSSP_MESSAGE_SIGNATURE()
    166  - if NegotiateFlags.NEGOTIATE_KEY_EXCH in self.ntlmChallenge.NegotiateFlags:
    167  - tt = struct.pack('<i', seqNum) + message
    168  - t = hmac.new(signingKey, digestmod='md5')
    169  - t.update(tt)
    170  -
    171  - msg.Checksum = handle(t.digest()[:8])
    172  - msg.SeqNum = seqNum
    173  - seqNum += 1
    174  - else:
    175  - t = hmac.new(signingKey, digestmod='md5')
    176  - t.update(struct.pack('<i',seqNum)+message)
    177  - msg.Checksum = t.digest()[:8]
    178  - msg.SeqNum = seqNum
    179  - seqNum += 1
    180  -
    181  - else:
    182  - raise Exception('Not implemented!')
    183  - #t = struct.pack('<I',binascii.crc32(message)& 0xFFFFFFFF)
    184  - #randompad = 0
    185  - #msg = NTLMSSP_MESSAGE_SIGNATURE_NOEXT()
    186  - #msg.RandomPad = handle(struct.pack('<I',randompad))
    187  - #msg.Checksum = struct.unpack('<I',handle(messageSignature['Checksum']))[0]
    188  -
    189  - return msg.to_bytes()
    190  - 
    191  - async def encrypt(self, data, sequence_no):
    192  - """
    193  - This function is to support SSPI encryption.
    194  - """
    195  - return self.SEAL(
    196  - #self.SignKey_client,
    197  - self.SignKey_client,
    198  - self.SealKey_client,
    199  - data,
    200  - data,
    201  - sequence_no,
    202  - self.crypthandle_client.encrypt
    203  - )
    204  - 
    205  - async def decrypt(self, data, sequence_no, direction='init', auth_data=None):
    206  - """
    207  - This function is to support SSPI decryption.
    208  - """
    209  - edata = data[16:]
    210  - srv_sig = NTLMSSP_MESSAGE_SIGNATURE.from_bytes(data[:16])
    211  - sealedMessage = self.crypthandle_server.encrypt(edata)
    212  - signature = self.MAC(self.crypthandle_server.encrypt, self.SignKey_server, srv_sig.SeqNum, sealedMessage)
    213  - #print('seqno %s' % sequence_no)
    214  - #print('Srv sig: %s' % data[:16])
    215  - #print('Calc sig: %s' % signature)
    216  - 
    217  - return sealedMessage, None
    218  - 
    219  - async def sign(self, data, message_no, direction=None, reset_cipher = False):
    220  - """
    221  - Singing outgoing messages. The reset_cipher parameter is needed for calculating mechListMIC.
    222  - """
    223  - #print('sign data : %s' % data)
    224  - #print('sign message_no : %s' % message_no)
    225  - #print('sign direction : %s' % direction)
    226  - signature = self.MAC(self.crypthandle_client.encrypt, self.SignKey_client, message_no, data)
    227  - if reset_cipher is True:
    228  - self.crypthandle_client = RC4(self.SealKey_client)
    229  - self.crypthandle_server = RC4(self.SealKey_server)
    230  - self.seq_number += 1
    231  - return signature
    232  - 
    233  - async def verify(self, data, signature):
    234  - """
    235  - Verifying incoming server message
    236  - """
    237  - signature_struct = NTLMSSP_MESSAGE_SIGNATURE.from_bytes(signature)
    238  - calc_sig = self.MAC(self.crypthandle_server.encrypt, self.SignKey_server, signature_struct.SeqNum, data)
    239  - #print('server signature : %s' % signature)
    240  - #print('calculates signature: %s' % calc_sig)
    241  - return signature == calc_sig
    242  - 
    243  - def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
    244  - """
    245  - This is the official SEAL function.
    246  - """
    247  - sealedMessage = cipher_encrypt(messageToEncrypt)
    248  - signature = self.MAC(cipher_encrypt, signingKey, seqNum, messageToSign)
    249  - return sealedMessage, signature
    250  -
    251  - def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
    252  - """
    253  - This is the official SIGN function.
    254  - """
    255  - return self.MAC(cipher_encrypt, signingKey, seqNum, message)
    256  -
    257  - def signing_needed(self):
    258  - return (
    259  - NegotiateFlags.NEGOTIATE_SIGN in self.ntlmChallenge.NegotiateFlags or \
    260  - NegotiateFlags.NEGOTIATE_SEAL in self.ntlmChallenge.NegotiateFlags
    261  - )
    262  -
    263  - def encryption_needed(self):
    264  - return NegotiateFlags.NEGOTIATE_SEAL in self.ntlmChallenge.NegotiateFlags
    265  - 
    266  - def calc_sealkey(self, mode = 'Client'):
    267  - if NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY in self.ntlmChallenge.NegotiateFlags:
    268  - if NegotiateFlags.NEGOTIATE_128 in self.ntlmChallenge.NegotiateFlags:
    269  - sealkey = self.RandomSessionKey
    270  - elif NegotiateFlags.NEGOTIATE_56 in self.ntlmChallenge.NegotiateFlags:
    271  - sealkey = self.RandomSessionKey[:7]
    272  - else:
    273  - sealkey = self.RandomSessionKey[:5]
    274  -
    275  - if mode == 'Client':
    276  - md5 = hashlib.new('md5')
    277  - md5.update(sealkey + b'session key to client-to-server sealing key magic constant\x00')
    278  - sealkey = md5.digest()
    279  - else:
    280  - md5 = hashlib.new('md5')
    281  - md5.update(sealkey + b'session key to server-to-client sealing key magic constant\x00')
    282  - sealkey = md5.digest()
    283  -
    284  - elif NegotiateFlags.NEGOTIATE_56 in self.ntlmChallenge.NegotiateFlags:
    285  - sealkey = self.RandomSessionKey[:7] + b'\xa0'
    286  - else:
    287  - sealkey = self.RandomSessionKey[:5] + b'\xe5\x38\xb0'
    288  -
    289  - if mode == 'Client':
    290  - self.SealKey_client = sealkey
    291  - if sealkey is not None:
    292  - self.crypthandle_client = RC4(self.SealKey_client)
    293  - else:
    294  - self.SealKey_server = sealkey
    295  - if sealkey is not None:
    296  - self.crypthandle_server = RC4(self.SealKey_server)
    297  -
    298  - return sealkey
    299  -
    300  - def calc_signkey(self, mode = 'Client'):
    301  - if NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY in self.ntlmChallenge.NegotiateFlags:
    302  - if mode == 'Client':
    303  - md5 = hashlib.new('md5')
    304  - md5.update(self.RandomSessionKey + b"session key to client-to-server signing key magic constant\x00")
    305  - signkey = md5.digest()
    306  - else:
    307  - md5 = hashlib.new('md5')
    308  - md5.update(self.RandomSessionKey + b"session key to server-to-client signing key magic constant\x00")
    309  - signkey = md5.digest()
    310  - else:
    311  - signkey = None
    312  -
    313  - if mode == 'Client':
    314  - self.SignKey_client = signkey
    315  - 
    316  - else:
    317  - self.SignKey_server = signkey
    318  -
    319  - return signkey
    320  -
    321  - def get_session_key(self):
    322  - return self.RandomSessionKey
    323  -
    324  - def get_sealkey(self, mode = 'Client'):
    325  - if mode == 'Client':
    326  - return self.SealKey_client
    327  - else:
    328  - return self.SealKey_server
    329  -
    330  - def get_signkey(self, mode = 'Client'):
    331  - if mode == 'Client':
    332  - return self.SignKey_client
    333  - else:
    334  - return self.SignKey_server
    335  -
    336  - def setup_crypto(self):
    337  - if not self.RandomSessionKey:
    338  - self.RandomSessionKey = os.urandom(16)
    339  -
    340  - if self.mode.upper() != 'MANUAL':
    341  - if self.settings.credential.is_guest == True:
    342  - self.SessionBaseKey = b'\x00' * 16
    343  - self.KeyExchangeKey = b'\x00' * 16
    344  -
    345  - rc4 = RC4(self.KeyExchangeKey)
    346  - self.EncryptedRandomSessionKey = rc4.encrypt(self.RandomSessionKey)
    347  - 
    348  - else:
    349  - #this check is here to provide the option to load the messages + the sessionbasekey manually
    350  - #then you will be able to use the sign and seal functions provided by this class
    351  - self.SessionBaseKey = self.ntlm_credentials.SessionBaseKey
    352  -
    353  - rc4 = RC4(self.KeyExchangeKey)
    354  - self.EncryptedRandomSessionKey = rc4.encrypt(self.RandomSessionKey)
    355  -
    356  - self.calc_sealkey('Client')
    357  - self.calc_sealkey('Server')
    358  - self.calc_signkey('Client')
    359  - self.calc_signkey('Server')
    360  - 
    361  - async def authenticate(self, authData, flags = None, seq_number = 0, cb_data = None):
    362  - if self.mode.upper() == 'CLIENT':
    363  - if self.iteration_cnt == 0:
    364  - if authData is not None:
    365  - raise Exception('First call as client MUST be with empty data!')
    366  -
    367  - self.iteration_cnt += 1
    368  - #negotiate message was already calulcated in setup
    369  - self.ntlmNegotiate = NTLMNegotiate.construct(self.flags, domainname = self.settings.template['domain_name'], workstationname = self.settings.template['workstation_name'], version = self.settings.template.get('version'))
    370  - self.ntlmNegotiate_raw = self.ntlmNegotiate.to_bytes()
    371  - return self.ntlmNegotiate_raw, True, None
    372  -
    373  - else:
    374  - #server challenge incoming
    375  - self.ntlmChallenge_raw = authData
    376  - self.ntlmChallenge = NTLMChallenge.from_bytes(authData)
    377  -
    378  - ##################self.flags = self.ntlmChallenge.NegotiateFlags
    379  -
    380  - #we need to calculate the response based on the credential and the settings flags
    381  - if self.settings.ntlm_downgrade == True:
    382  - #NTLMv1 authentication
    383  - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/464551a8-9fc4-428e-b3d3-bc5bfb2e73a5
    384  -
    385  - #check if we authenticate as guest
    386  - if self.settings.credential.is_guest == True:
    387  - lmresp = LMResponse()
    388  - self.set_version(False)
    389  - lmresp.Response = b'\x00'
    390  - self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= lmresp, mic=None, encrypted_session = self.EncryptedRandomSessionKey)
    391  - return self.ntlmAuthenticate.to_bytes(), False, None
    392  -
    393  - if self.flags & NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY:
    394  - #Extended auth!
    395  - self.ntlm_credentials = netntlm_ess.construct(self.ntlmChallenge.ServerChallenge, self.challenge, self.settings.credential)
    396  -
    397  - self.KeyExchangeKey = self.ntlm_credentials.calc_key_exchange_key()
    398  - self.setup_crypto()
    399  -
    400  - self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= self.ntlm_credentials.LMResponse, nt_response = self.ntlm_credentials.NTResponse, version = self.ntlmNegotiate.Version, encrypted_session = self.EncryptedRandomSessionKey)
    401  - else:
    402  - self.ntlm_credentials = netntlm.construct(self.ntlmChallenge.ServerChallenge, self.settings.credential)
    403  -
    404  - self.KeyExchangeKey = self.ntlm_credentials.calc_key_exchange_key(with_lm = self.flags & NegotiateFlags.NEGOTIATE_LM_KEY, non_nt_session_key = self.flags & NegotiateFlags.REQUEST_NON_NT_SESSION_KEY)
    405  - self.setup_crypto()
    406  - self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= self.ntlm_credentials.LMResponse, nt_response = self.ntlm_credentials.NTResponse, version = self.ntlmNegotiate.Version, encrypted_session = self.EncryptedRandomSessionKey)
    407  - 
    408  -
    409  -
    410  - else:
    411  - #NTLMv2
    412  - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/5e550938-91d4-459f-b67d-75d70009e3f3
    413  - if self.settings.credential.is_guest == True:
    414  - lmresp = LMResponse()
    415  - lmresp.Response = b'\x00'
    416  - self.set_version(False)
    417  - self.setup_crypto()
    418  - self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, lm_response= lmresp, encrypted_session = self.EncryptedRandomSessionKey)
    419  - return self.ntlmAuthenticate.to_bytes(), False, None
    420  -
    421  - else:
    422  - #comment this out for testing!
    423  - ti = self.ntlmChallenge.TargetInfo
    424  - ti[AVPAIRType.MsvAvTargetName] = 'TERMSRV/%s' % ti[AVPAIRType.MsvAvDnsComputerName]
    425  - if cb_data is not None:
    426  - md5_ctx = hashlib.new('md5')
    427  - md5_ctx.update(cb_data)
    428  - ti[AVPAIRType.MsvChannelBindings] = md5_ctx.digest()
    429  - ###
    430  -
    431  - self.ntlm_credentials = netntlmv2.construct(self.ntlmChallenge.ServerChallenge, self.challenge, ti, self.settings.credential, timestamp = self.timestamp)
    432  - self.KeyExchangeKey = self.ntlm_credentials.calc_key_exchange_key()
    433  - self.setup_crypto()
    434  -
    435  - #TODO: if "ti" / targetinfo in the challenge message has "MsvAvFlags" type and the bit for MIC is set (0x00000002) we need to send a MIC. probably...
    436  - mic = None
    437  -
    438  - self.ntlmAuthenticate = NTLMAuthenticate.construct(self.flags, domainname= self.settings.credential.domain, workstationname= self.settings.credential.workstation, username= self.settings.credential.username, lm_response= self.ntlm_credentials.LMResponse, nt_response= self.ntlm_credentials.NTResponse, version = self.ntlmNegotiate.Version, encrypted_session = self.EncryptedRandomSessionKey, mic = mic)
    439  -
    440  -
    441  - self.ntlmAuthenticate_raw = self.ntlmAuthenticate.to_bytes()
    442  - return self.ntlmAuthenticate_raw, False, None
    443  -
    444  - elif self.mode.upper() == 'RELAY':
    445  - if self.iteration_cnt == 0:
    446  - self.ntlmNegotiate_raw = authData
    447  - self.ntlmNegotiate = NTLMNegotiate.from_bytes(authData)
    448  - self.iteration_cnt += 1
    449  -
    450  - elif self.iteration_cnt == 1:
    451  - self.ntlmChallenge_raw = authData
    452  - self.ntlmChallenge = NTLMChallenge.from_bytes(authData)
    453  - self.iteration_cnt += 1
    454  -
    455  - elif self.iteration_cnt == 2:
    456  - self.ntlmChallenge_raw = authData
    457  - self.ntlmChallenge = NTLMChallenge.from_bytes(authData)
    458  - self.iteration_cnt += 1
    459  -
    460  - else:
    461  - raise Exception('Too many iterations for relay mode!')
    462  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/sspi.py
    1  -#
    2  -#
    3  -# This is just a simple interface to the winsspi library to support NTLM
    4  -#
    5  -from winsspi.sspi import NTLMMSLDAPSSPI
    6  -from aardwolf.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
    7  - 
    8  -class RDPNTLMSSPI:
    9  - def __init__(self, settings):
    10  - self.settings = settings
    11  - self.mode = None #'CLIENT'
    12  - self.sspi = NTLMMSLDAPSSPI()
    13  - self.client = None
    14  - self.target = None
    15  - #self.ntlmChallenge = None
    16  -
    17  - self.session_key = None
    18  - self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
    19  -
    20  - self.setup()
    21  -
    22  - @property
    23  - def ntlmChallenge(self):
    24  - return self.ntlm_ctx.ntlmChallenge
    25  -
    26  - def setup(self):
    27  - self.mode = self.settings.mode.upper()
    28  - self.client = self.settings.client
    29  - self.password = self.settings.password
    30  -
    31  - def get_sealkey(self, mode = 'Client'):
    32  - return self.ntlm_ctx.get_sealkey(mode = mode)
    33  -
    34  - def get_signkey(self, mode = 'Client'):
    35  - return self.ntlm_ctx.get_signkey(mode = mode)
    36  -
    37  -
    38  - def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
    39  - return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
    40  -
    41  - def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
    42  - return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
    43  -
    44  - def get_session_key(self):
    45  - if not self.session_key:
    46  - self.session_key = self.sspi.get_session_key()
    47  -
    48  - return self.session_key
    49  -
    50  - def get_extra_info(self):
    51  - return self.ntlm_ctx.get_extra_info()
    52  -
    53  - def is_extended_security(self):
    54  - return self.ntlm_ctx.is_extended_security()
    55  -
    56  - async def encrypt(self, data, message_no):
    57  - return self.sspi.encrypt(data, message_no)
    58  -
    59  - async def decrypt(self, data, message_no):
    60  - return self.sspi.decrypt(data, message_no)
    61  -
    62  - async def authenticate(self, authData = None, flags = None, seq_number = 0, is_rpc = False):
    63  - if self.mode == 'CLIENT':
    64  - if authData is None:
    65  - data, res = self.sspi.negotiate(is_rpc = is_rpc)
    66  - self.ntlm_ctx.load_negotiate(data)
    67  - return data, res, None
    68  - else:
    69  - self.ntlm_ctx.load_challenge( authData)
    70  - #data, res, err = self.sspi.authenticate(authData, is_rpc = is_rpc)
    71  - #if err is not None:
    72  - # return None, None, err
    73  - data, res = self.sspi.authenticate(authData, is_rpc = is_rpc)
    74  - 
    75  - self.ntlm_ctx.load_authenticate( data)
    76  - self.ntlm_ctx.load_sessionkey(self.get_session_key())
    77  -
    78  - return data, res, None
    79  -
    80  -
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/sspiproxy.py
    1  -from aardwolf import logger
    2  -from aardwolf.authentication.ntlm.native import NTLMAUTHHandler, NTLMHandlerSettings
    3  -from wsnet.operator.sspiproxy import WSNETSSPIProxy
    4  -import enum
    5  - 
    6  -class ISC_REQ(enum.IntFlag):
    7  - DELEGATE = 1
    8  - MUTUAL_AUTH = 2
    9  - REPLAY_DETECT = 4
    10  - SEQUENCE_DETECT = 8
    11  - CONFIDENTIALITY = 16
    12  - USE_SESSION_KEY = 32
    13  - PROMPT_FOR_CREDS = 64
    14  - USE_SUPPLIED_CREDS = 128
    15  - ALLOCATE_MEMORY = 256
    16  - USE_DCE_STYLE = 512
    17  - DATAGRAM = 1024
    18  - CONNECTION = 2048
    19  - CALL_LEVEL = 4096
    20  - FRAGMENT_SUPPLIED = 8192
    21  - EXTENDED_ERROR = 16384
    22  - STREAM = 32768
    23  - INTEGRITY = 65536
    24  - IDENTIFY = 131072
    25  - NULL_SESSION = 262144
    26  - MANUAL_CRED_VALIDATION = 524288
    27  - RESERVED1 = 1048576
    28  - FRAGMENT_TO_FIT = 2097152
    29  - HTTP = 0x10000000
    30  - 
    31  -class RDPSSPIProxyNTLMAuth:
    32  - def __init__(self, settings):
    33  - self.settings = settings
    34  - self.mode = None
    35  - url = '%s://%s:%s' % (self.settings.proto, self.settings.host, self.settings.port)
    36  - self.sspi = WSNETSSPIProxy(url, self.settings.agent_id)
    37  - self.operator = None
    38  - self.client = None
    39  - self.target = None
    40  - #self.ntlmChallenge = None
    41  - self.iterations = 0
    42  -
    43  - self.session_key = None
    44  - self.ntlm_ctx = NTLMAUTHHandler(NTLMHandlerSettings(None, 'MANUAL'))
    45  - 
    46  - def setup(self):
    47  - return
    48  -
    49  - @property
    50  - def ntlmChallenge(self):
    51  - return self.ntlm_ctx.ntlmChallenge
    52  -
    53  - def get_sealkey(self, mode = 'Client'):
    54  - return self.ntlm_ctx.get_sealkey(mode = mode)
    55  -
    56  - def get_signkey(self, mode = 'Client'):
    57  - return self.ntlm_ctx.get_signkey(mode = mode)
    58  -
    59  -
    60  - def SEAL(self, signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt):
    61  - return self.ntlm_ctx.SEAL(signingKey, sealingKey, messageToSign, messageToEncrypt, seqNum, cipher_encrypt)
    62  -
    63  - def SIGN(self, signingKey, message, seqNum, cipher_encrypt):
    64  - return self.ntlm_ctx.SIGN(signingKey, message, seqNum, cipher_encrypt)
    65  -
    66  - def get_session_key(self):
    67  - return self.session_key
    68  -
    69  - def get_extra_info(self):
    70  - return self.ntlm_ctx.get_extra_info()
    71  -
    72  - def is_extended_security(self):
    73  - return self.ntlm_ctx.is_extended_security()
    74  -
    75  - async def authenticate(self, authData = b'', flags = None, seq_number = 0, is_rpc = False):
    76  - try:
    77  - if is_rpc is True and flags is None:
    78  - flags = ISC_REQ.REPLAY_DETECT | ISC_REQ.CONFIDENTIALITY| ISC_REQ.USE_SESSION_KEY| ISC_REQ.INTEGRITY| ISC_REQ.SEQUENCE_DETECT| ISC_REQ.CONNECTION
    79  - elif flags is None:
    80  - flags = ISC_REQ.CONNECTION
    81  - 
    82  - if authData is None:
    83  - status, ctxattr, data, err = await self.sspi.authenticate('NTLM', '', '', 3, flags.value, authdata = b'')
    84  - if err is not None:
    85  - raise err
    86  - self.iterations += 1
    87  - self.ntlm_ctx.load_negotiate(data)
    88  - return data, True, None
    89  - else:
    90  - self.ntlm_ctx.load_challenge(authData)
    91  - status, ctxattr, data, err = await self.sspi.authenticate('NTLM', '', '', 3, flags.value, authdata = authData)
    92  - if err is not None:
    93  - raise err
    94  - if err is None:
    95  - self.ntlm_ctx.load_authenticate(data)
    96  - self.session_key, err = await self.sspi.get_sessionkey()
    97  - if err is not None:
    98  - raise err
    99  - self.ntlm_ctx.load_sessionkey(self.get_session_key())
    100  -
    101  - await self.sspi.disconnect()
    102  - return data, False, None
    103  - except Exception as e:
    104  - return None, None, e
    105  -
    106  -
  • ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/__init__.py
    1  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/avpair.py
    1  -import enum
    2  -import io
    3  -import collections
    4  - 
    5  -class MsvAvFlags(enum.IntFlag):
    6  - CONSTRAINED_AUTH = 0x00000001
    7  - MIC_PRESENT = 0x00000002
    8  - SPN_UNTRUSTED = 0x00000004
    9  - 
    10  -class AVPAIRType(enum.Enum):
    11  - MsvAvEOL = 0x0000 #Indicates that this is the last AV_PAIR in the list. AvLen MUST be 0. This type of information MUST be present in the AV pair list.
    12  - MsvAvNbComputerName = 0x0001 #The server's NetBIOS computer name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
    13  - MsvAvNbDomainName = 0x0002 #The server's NetBIOS domain name. The name MUST be in Unicode, and is not null-terminated. This type of information MUST be present in the AV_pair list.
    14  - MsvAvDnsComputerName = 0x0003 #The fully qualified domain name (FQDN) of the computer. The name MUST be in Unicode, and is not null-terminated.
    15  - MsvAvDnsDomainName = 0x0004 #The FQDN of the domain. The name MUST be in Unicode, and is not null-terminated.
    16  - MsvAvDnsTreeName = 0x0005 #The FQDN of the forest. The name MUST be in Unicode, and is not null-terminated.<13>
    17  - MsvAvFlags = 0x0006 #A 32-bit value indicating server or client configuration.
    18  - MsvAvTimestamp = 0x0007 #A FILETIME structure ([MS-DTYP] section 2.3.3) in little-endian byte order that contains the server local time. This structure is always sent in the CHALLENGE_MESSAGE.<16>
    19  - MsvAvSingleHost = 0x0008 #A Single_Host_Data (section 2.2.2.2) structure. The Value field contains a platform-specific blob, as well as a MachineID created at computer startup to identify the calling machine.<17>
    20  - MsvAvTargetName = 0x0009 #The SPN of the target server. The name MUST be in Unicode and is not null-terminated.<18>
    21  - MsvChannelBindings = 0x000A #A channel bindings hash. The Value field contains an MD5 hash ([RFC4121] section 4.1.1.2) of a gss_channel_bindings_struct ([RFC2744] section 3.11). An all-zero value of the hash is used to indicate absence of channel bindings.<19>
    22  - 
    23  - 
    24  -# ???? https://msdn.microsoft.com/en-us/library/windows/desktop/aa374793(v=vs.85).aspx
    25  -# https://msdn.microsoft.com/en-us/library/cc236646.aspx
    26  -class AVPairs(collections.UserDict):
    27  - """
    28  - AVPairs is a dictionary-like object that stores the "AVPair list" in a key -value format where key is an AVPAIRType object and value is the corresponding object defined by the MSDN documentation. Usually it's string but can be other object as well
    29  - """
    30  - def __init__(self, data = None):
    31  - collections.UserDict.__init__(self, data)
    32  - 
    33  - @staticmethod
    34  - def from_bytes(bbuff):
    35  - return AVPairs.from_buffer(io.BytesIO(bbuff))
    36  - 
    37  - @staticmethod
    38  - def from_buffer(buff):
    39  - avp = AVPairs()
    40  - while True:
    41  - avId = AVPAIRType(int.from_bytes(buff.read(2), byteorder = 'little', signed = False))
    42  - AvLen = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
    43  - if avId == AVPAIRType.MsvAvEOL:
    44  - break
    45  - 
    46  - elif avId in [AVPAIRType.MsvAvNbComputerName,
    47  - AVPAIRType.MsvAvNbDomainName,
    48  - AVPAIRType.MsvAvDnsComputerName,
    49  - AVPAIRType.MsvAvDnsDomainName,
    50  - AVPAIRType.MsvAvDnsTreeName,
    51  - AVPAIRType.MsvAvTargetName,
    52  - ]:
    53  - avp[avId] = buff.read(AvLen).decode('utf-16le')
    54  -
    55  - elif avId == AVPAIRType.MsvAvFlags:
    56  - avp[avId] = MsvAvFlags(int.from_bytes(buff.read(4), byteorder = 'little', signed = False))
    57  - 
    58  - # TODO IMPLEMENT PARSING OFR OTHER TYPES!!!!
    59  - else:
    60  - avp[avId] = buff.read(AvLen)
    61  - 
    62  - return avp
    63  - 
    64  - def to_bytes(self):
    65  - t = b''
    66  - for av in self.data:
    67  - t += AVPair(data = self.data[av], type = av).to_bytes()
    68  - 
    69  - t += AVPair(data = '', type = AVPAIRType.MsvAvEOL).to_bytes()
    70  - return t
    71  - 
    72  - 
    73  -class AVPair:
    74  - def __init__(self, data = None, type = None):
    75  - self.type = type
    76  - self.data = data
    77  - 
    78  - def to_bytes(self):
    79  - t = self.type.value.to_bytes(2, byteorder = 'little', signed = False)
    80  - raw_data = None
    81  - if self.type in [AVPAIRType.MsvAvNbComputerName,
    82  - AVPAIRType.MsvAvNbDomainName,
    83  - AVPAIRType.MsvAvDnsComputerName,
    84  - AVPAIRType.MsvAvDnsDomainName,
    85  - AVPAIRType.MsvAvDnsTreeName,
    86  - AVPAIRType.MsvAvTargetName,
    87  - AVPAIRType.MsvAvEOL
    88  - ]:
    89  - raw_data = self.data.encode('utf-16le')
    90  - else:
    91  - raw_data = self.data
    92  - t += len(raw_data).to_bytes(2, byteorder = 'little', signed = False)
    93  - t += raw_data
    94  - return t
    95  - 
    96  - 
    97  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/challenge_response.py
    1  -import io
    2  - 
    3  -from aardwolf.commons.utils.ts2dt import datetime2timestamp, timestamp2datetime
    4  -from aardwolf.authentication.ntlm.structures.avpair import AVPairs, AVPAIRType
    5  - 
    6  -# https://msdn.microsoft.com/en-us/library/cc236648.aspx
    7  -class LMResponse:
    8  - def __init__(self):
    9  - self.Response = None
    10  -
    11  - def to_bytes(self):
    12  - return self.Response
    13  - 
    14  - @staticmethod
    15  - def from_bytes(bbuff):
    16  - return LMResponse.from_buffer(io.BytesIO(bbuff))
    17  - 
    18  - @staticmethod
    19  - def from_buffer(buff):
    20  - t = LMResponse()
    21  - t.Response = buff.read(24)
    22  - return t
    23  - 
    24  - def __repr__(self):
    25  - t = '== LMResponse ==\r\n'
    26  - t += 'Response: %s\r\n' % repr(self.Response.hex())
    27  - return t
    28  - 
    29  - 
    30  -# https://msdn.microsoft.com/en-us/library/cc236649.aspx
    31  -class LMv2Response:
    32  - def __init__(self):
    33  - self.Response = None
    34  - self.ChallengeFromClinet = None
    35  -
    36  -
    37  - def to_bytes(self):
    38  - return self.Response + self.ChallengeFromClinet
    39  - 
    40  - @staticmethod
    41  - def from_bytes(bbuff):
    42  - return LMv2Response.from_buffer(io.BytesIO(bbuff))
    43  - 
    44  - @staticmethod
    45  - def from_buffer(buff):
    46  - t = LMv2Response()
    47  - t.Response = buff.read(16).hex()
    48  - t.ChallengeFromClinet = buff.read(8).hex()
    49  - return t
    50  - 
    51  - def __repr__(self):
    52  - t = '== LMv2Response ==\r\n'
    53  - t += 'Response: %s\r\n' % repr(self.Response)
    54  - t += 'ChallengeFromClinet: %s\r\n' % repr(self.ChallengeFromClinet)
    55  - return t
    56  - 
    57  - 
    58  -# https://msdn.microsoft.com/en-us/library/cc236651.aspx
    59  -class NTLMv1Response:
    60  - def __init__(self):
    61  - self.Response = None
    62  -
    63  - def to_bytes(self):
    64  - return self.Response
    65  - 
    66  - @staticmethod
    67  - def from_bytes(bbuff):
    68  - return NTLMv1Response.from_buffer(io.BytesIO(bbuff))
    69  - 
    70  - @staticmethod
    71  - def from_buffer(buff):
    72  - t = NTLMv1Response()
    73  - t.Response = buff.read(24).hex()
    74  - return t
    75  - 
    76  - def __repr__(self):
    77  - t = '== NTLMv1Response ==\r\n'
    78  - t += 'Response: %s\r\n' % repr(self.Response)
    79  - return t
    80  - 
    81  - 
    82  -# https://msdn.microsoft.com/en-us/library/cc236653.aspx
    83  -class NTLMv2Response:
    84  - def __init__(self):
    85  - self.Response = None
    86  - self.ChallengeFromClinet = None
    87  -
    88  - def to_bytes(self):
    89  - return self.Response + self.ChallengeFromClinet.to_bytes()
    90  - 
    91  - @staticmethod
    92  - def from_bytes(bbuff):
    93  - return NTLMv2Response.from_buffer(io.BytesIO(bbuff))
    94  - 
    95  - @staticmethod
    96  - def from_buffer(buff):
    97  - t = NTLMv2Response()
    98  - t.Response = buff.read(16)
    99  - pos = buff.tell()
    100  - t.ChallengeFromClinet = NTLMv2ClientChallenge.from_buffer(buff)
    101  - 
    102  - return t
    103  - 
    104  - def __repr__(self):
    105  - t = '== NTLMv2Response ==\r\n'
    106  - t += 'Response : %s\r\n' % repr(self.Response.hex())
    107  - t += 'ChallengeFromClinet: %s\r\n' % repr(self.ChallengeFromClinet)
    108  - return t
    109  - 
    110  - 
    111  -class NTLMv2ClientChallenge:
    112  - def __init__(self):
    113  - self.RespType = 1
    114  - self.HiRespType = 1
    115  - self.Reserved1 = 0
    116  - self.TimeStamp = None #bytes! because of conversion error :(
    117  - self.Reserved2 = 0
    118  - self.ChallengeFromClient = None
    119  - self.Reserved3 = 0
    120  - self.Details = None #named AVPairs in the documentation
    121  -
    122  - self.timestamp_dt = None
    123  - self.raw_data = b''
    124  -
    125  - @staticmethod
    126  - def construct(timestamp, client_challenge, details):
    127  - """
    128  - timestamp: datetime.datetime
    129  - client_challenge: 8 bytes
    130  - details: AVPairs object
    131  - """
    132  - cc = NTLMv2ClientChallenge()
    133  - cc.TimeStamp = datetime2timestamp(timestamp)
    134  - cc.ChallengeFromClient = client_challenge
    135  - cc.Details = details
    136  - cc.timestamp_dt = timestamp
    137  - return cc
    138  -
    139  - def to_bytes(self):
    140  - t = self.RespType.to_bytes(1 , byteorder = 'little', signed = False)
    141  - t += self.HiRespType.to_bytes(1 , byteorder = 'little', signed = False)
    142  - t += self.Reserved1.to_bytes(6, byteorder = 'little', signed = False)
    143  - t += self.TimeStamp
    144  - t += self.ChallengeFromClient
    145  - t += self.Reserved2.to_bytes(4, byteorder = 'little', signed = False)
    146  - t += self.Details.to_bytes()
    147  - t += self.Reserved3.to_bytes(4, byteorder = 'little', signed = False)
    148  -
    149  - return t
    150  - 
    151  - @staticmethod
    152  - def from_bytes(bbuff):
    153  - return NTLMv2ClientChallenge.from_buffer(io.BytesIO(bbuff))
    154  - 
    155  - @staticmethod
    156  - def from_buffer(buff):
    157  - cc = NTLMv2ClientChallenge()
    158  - cc.RespType = int.from_bytes(buff.read(1), byteorder = 'little', signed = False)
    159  - cc.HiRespType = int.from_bytes(buff.read(1), byteorder = 'little', signed = False)
    160  - cc.Reserved1 = int.from_bytes(buff.read(6), byteorder = 'little', signed = False)
    161  - cc.TimeStamp = buff.read(8)
    162  - cc.ChallengeFromClient = buff.read(8)
    163  - cc.Reserved2 = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    164  - cc.Details = AVPairs.from_buffer(buff) #referred to as ServerName in the documentation
    165  - cc.Reserved3 = int.from_bytes(buff.read(4), byteorder='little', signed=False)
    166  -
    167  - cc.timestamp_dt = timestamp2datetime(cc.TimeStamp)
    168  - 
    169  - return cc
    170  - 
    171  - def __repr__(self):
    172  - t = '== NTLMv2ClientChallenge ==\r\n'
    173  - t += 'RespType : %s\r\n' % repr(self.RespType)
    174  - t += 'TimeStamp : %s\r\n' % repr(self.timestamp_dt)
    175  - t += 'ChallengeFromClient: %s\r\n' % repr(self.ChallengeFromClient.hex())
    176  - t += 'Details : %s\r\n' % repr(self.Details)
    177  - return t
    178  - 
    179  -def test():
    180  - test_data = bytes.fromhex('0101000000000000aec600bfc5fdd4011bfa20699d7628730000000002000800540045005300540001001200570049004e003200300031003900410044000400120074006500730074002e0063006f007200700003002600570049004e003200300031003900410044002e0074006500730074002e0063006f007200700007000800aec600bfc5fdd40106000400020000000800300030000000000000000000000000200000527d27f234de743760966384d36f61ae2aa4fc2a380699f8caa600011b486d890a0010000000000000000000000000000000000009001e0063006900660073002f00310030002e00310030002e00310030002e0032000000000000000000')
    181  -
    182  - cc = NTLMv2ClientChallenge.from_bytes(test_data)
    183  - print(repr(cc))
    184  -
    185  - cc2 = NTLMv2ClientChallenge.from_bytes(cc.to_bytes())
    186  - print(repr(cc2))
    187  - print('=== Original ===')
    188  - print(hexdump(test_data))
    189  - print('=== CC ===')
    190  - print(hexdump(cc.to_bytes()))
    191  -
    192  - ### assertions here fail because of the timestamp re-conversion loosing info (float-int conversion)
    193  - #assert cc.to_bytes() == test_data
    194  - #assert cc2.to_bytes() == test_data
    195  -
    196  - details = AVPairs({AVPAIRType.MsvAvNbDomainName: 'TEST', AVPAIRType.MsvAvNbComputerName: 'WIN2019AD', AVPAIRType.MsvAvDnsDomainName: 'test.corp', AVPAIRType.MsvAvDnsComputerName: 'WIN2019AD.test.corp', AVPAIRType.MsvAvTimestamp: b'\xae\xc6\x00\xbf\xc5\xfd\xd4\x01', AVPAIRType.MsvAvFlags: b'\x02\x00\x00\x00', AVPAIRType.MsvAvSingleHost: b"0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \x00\x00R}'\xf24\xdet7`\x96c\x84\xd3oa\xae*\xa4\xfc*8\x06\x99\xf8\xca\xa6\x00\x01\x1bHm\x89", AVPAIRType.MsvChannelBindings: b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', AVPAIRType.MsvAvTargetName: 'termsrv/10.10.10.2'})
    197  - timestamp = datetime.datetime(2019,1,1)
    198  - client_challenge = os.urandom(8)
    199  -
    200  - cc3 = NTLMv2ClientChallenge.construct(timestamp, client_challenge, details)
    201  - print(repr(cc3))
    202  - cc4 = NTLMv2ClientChallenge.from_bytes(cc3.to_bytes())
    203  - 
    204  -if __name__ == '__main__':
    205  - from aardwolf.utils.hexdump import hexdump
    206  - import os
    207  - test()
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/fields.py
    1  - 
    2  -import io
    3  - 
    4  -class Fields:
    5  - def __init__(self, length, offset, maxLength = None):
    6  - self.length = length
    7  - self.maxLength = length if maxLength is None else maxLength
    8  - self.offset = offset
    9  - 
    10  - @staticmethod
    11  - def from_bytes(bbuff):
    12  - return Fields.from_buffer(io.BytesIO(bbuff))
    13  - 
    14  - @staticmethod
    15  - def from_buffer( buff):
    16  - length = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
    17  - maxLength = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
    18  - offset = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    19  - 
    20  - return Fields(length, offset, maxLength = maxLength)
    21  - 
    22  - def to_bytes(self):
    23  - return self.length.to_bytes(2, byteorder = 'little', signed = False) + \
    24  - self.maxLength.to_bytes(2, byteorder = 'little', signed = False) + \
    25  - self.offset.to_bytes(4, byteorder = 'little', signed = False)
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/negotiate_flags.py
    1  -import enum
    2  - 
    3  -# https://msdn.microsoft.com/en-us/library/cc236650.aspx
    4  -class NegotiateFlags(enum.IntFlag):
    5  - NEGOTIATE_56 = 0x80000000
    6  - NEGOTIATE_KEY_EXCH = 0x40000000 # needed for encryption/singing key
    7  - NEGOTIATE_128 = 0x20000000
    8  - r1 = 0x10000000
    9  - r2 = 0x8000000
    10  - r3 = 0x4000000
    11  - NEGOTIATE_VERSION = 0x2000000
    12  - r4 = 0x1000000
    13  - NEGOTIATE_TARGET_INFO = 0x800000
    14  - REQUEST_NON_NT_SESSION_KEY = 0x400000
    15  - r5 = 0x200000
    16  - NEGOTIATE_IDENTIFY = 0x100000
    17  - NEGOTIATE_EXTENDED_SESSIONSECURITY = 0x80000
    18  - r6 = 0x40000
    19  - TARGET_TYPE_SERVER = 0x20000
    20  - TARGET_TYPE_DOMAIN = 0x10000
    21  - NEGOTIATE_ALWAYS_SIGN = 0x8000
    22  - r7 = 0x4000
    23  - NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 0x2000
    24  - NEGOTIATE_OEM_DOMAIN_SUPPLIED = 0x1000
    25  - J = 0x800
    26  - r8 = 0x400
    27  - NEGOTIATE_NTLM = 0x200
    28  - r9 = 0x100
    29  - NEGOTIATE_LM_KEY = 0x80
    30  - NEGOTIATE_DATAGRAM = 0x40
    31  - NEGOTIATE_SEAL = 0x20
    32  - NEGOTIATE_SIGN = 0x10
    33  - r10 = 0x8
    34  - REQUEST_TARGET = 0x4
    35  - NTLM_NEGOTIATE_OEM = 0x2
    36  - NEGOTIATE_UNICODE = 0x1
    37  -
    38  -NegotiateFlagExp = {
    39  - NegotiateFlags.NEGOTIATE_56 : 'requests 56-bit encryption. If the client sends NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN with NTLMSSP_NEGOTIATE_56 to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_56 to the client in the CHALLENGE_MESSAGE. Otherwise it is ignored. If both NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set NTLMSSP_NEGOTIATE_56 if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_56.',
    40  - NegotiateFlags.NEGOTIATE_KEY_EXCH : 'requests an explicit key exchange. This capability SHOULD be used because it improves security for message integrity or confidentiality. See sections 3.2.5.1.2, 3.2.5.2.1, and 3.2.5.2.2 for details. An alternate name for this field is NTLMSSP_NEGOTIATE_KEY_EXCH.',
    41  - NegotiateFlags.NEGOTIATE_128 : 'requests 128-bit session key negotiation. An alternate name for this field is NTLMSSP_NEGOTIATE_128. If the client sends NTLMSSP_NEGOTIATE_128 to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_128 to the client in the CHALLENGE_MESSAGE only if the client sets NTLMSSP_NEGOTIATE_SEAL or NTLMSSP_NEGOTIATE_SIGN. Otherwise it is ignored. If both NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 are requested and supported by the client and server, NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128 will both be returned to the client. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD set NTLMSSP_NEGOTIATE_128 if it is supported. An alternate name for this field is NTLMSSP_NEGOTIATE_128.<23>',
    42  - NegotiateFlags.r1 : 'This bit is unused and MUST be zero.',
    43  - NegotiateFlags.r2 : 'This bit is unused and MUST be zero.',
    44  - NegotiateFlags.r3 : 'This bit is unused and MUST be zero.',
    45  - NegotiateFlags.NEGOTIATE_VERSION : 'requests the protocol version number. The data corresponding to this flag is provided in the Version field of the NEGOTIATE_MESSAGE, the CHALLENGE_MESSAGE, and the AUTHENTICATE_MESSAGE.<24> An alternate name for this field is NTLMSSP_NEGOTIATE_VERSION.',
    46  - NegotiateFlags.r4 : 'This bit is unused and MUST be zero.',
    47  - NegotiateFlags.NEGOTIATE_TARGET_INFO : 'indicates that the TargetInfo fields in the CHALLENGE_MESSAGE (section 2.2.1.2) are populated. An alternate name for this field is NTLMSSP_NEGOTIATE_TARGET_INFO.',
    48  - NegotiateFlags.REQUEST_NON_NT_SESSION_KEY : ' requests the usage of the LMOWF. An alternate name for this field is NTLMSSP_REQUEST_NON_NT_SESSION_KEY.',
    49  - NegotiateFlags.r5 : 'This bit is unused and MUST be zero.',
    50  - NegotiateFlags.NEGOTIATE_IDENTIFY : 'requests an identify level token. An alternate name for this field is NTLMSSP_NEGOTIATE_IDENTIFY.',
    51  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY : 'requests usage of the NTLM v2 session security. NTLM v2 session security is a misnomer because it is not NTLM v2. It is NTLM v1 using the extended session security that is also in NTLM v2. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY and NTLMSSP_NEGOTIATE_LM_KEY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires support from the client and the server in order to be used.<25> An alternate name for this field is NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.',
    52  - NegotiateFlags.r6 : 'This bit is unused and MUST be zero.',
    53  - NegotiateFlags.TARGET_TYPE_SERVER : 'TargetName MUST be a server name. The data corresponding to this flag is provided by the server in the TargetName field of the CHALLENGE_MESSAGE. If this bit is set, then NTLMSSP_TARGET_TYPE_DOMAIN MUST NOT be set. This flag MUST be ignored in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_SERVER.',
    54  - NegotiateFlags.TARGET_TYPE_DOMAIN : 'TargetName MUST be a domain name. The data corresponding to this flag is provided by the server in the TargetName field of the CHALLENGE_MESSAGE. then NTLMSSP_TARGET_TYPE_SERVER MUST NOT be set. This flag MUST be ignored in the NEGOTIATE_MESSAGE and the AUTHENTICATE_MESSAGE. An alternate name for this field is NTLMSSP_TARGET_TYPE_DOMAIN.',
    55  - NegotiateFlags.NEGOTIATE_ALWAYS_SIGN : ' requests the presence of a signature block on all messages. NTLMSSP_NEGOTIATE_ALWAYS_SIGN MUST be set in the NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. NTLMSSP_NEGOTIATE_ALWAYS_SIGN is overridden by NTLMSSP_NEGOTIATE_SIGN and NTLMSSP_NEGOTIATE_SEAL, if they are supported. An alternate name for this field is NTLMSSP_NEGOTIATE_ALWAYS_SIGN.',
    56  - NegotiateFlags.r7 : 'This bit is unused and MUST be zero.',
    57  - NegotiateFlags.NEGOTIATE_OEM_WORKSTATION_SUPPLIED : 'This flag indicates whether the Workstation field is present. If this flag is not set, the Workstation field MUST be ignored. If this flag is set, the length of the Workstation field specifies whether the workstation name is nonempty or not.<26> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED.',
    58  - NegotiateFlags.NEGOTIATE_OEM_DOMAIN_SUPPLIED : 'the domain name is provided (section 2.2.1.1).<27> An alternate name for this field is NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED.',
    59  - NegotiateFlags.J : 'the connection SHOULD be anonymous.<28>',
    60  - NegotiateFlags.r8 : 'This bit is unused and MUST be zero.',
    61  - NegotiateFlags.NEGOTIATE_NTLM : 'requests usage of the NTLM v1 session security protocol. NTLMSSP_NEGOTIATE_NTLM MUST be set in the NEGOTIATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for this field is NTLMSSP_NEGOTIATE_NTLM.',
    62  - NegotiateFlags.r9 : 'This bit is unused and MUST be zero.',
    63  - NegotiateFlags.NEGOTIATE_LM_KEY : 'requests LAN Manager (LM) session key computation. NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are mutually exclusive. If both NTLMSSP_NEGOTIATE_LM_KEY and NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY are requested, NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY alone MUST be returned to the client. NTLM v2 authentication session key generation MUST be supported by both the client and the DC in order to be used, and extended session security signing and sealing requires support from the client and the server to be used. An alternate name for this field is NTLMSSP_NEGOTIATE_LM_KEY.',
    64  - NegotiateFlags.NEGOTIATE_DATAGRAM : 'requests connectionless authentication. If NTLMSSP_NEGOTIATE_DATAGRAM is set, then NTLMSSP_NEGOTIATE_KEY_EXCH MUST always be set in the AUTHENTICATE_MESSAGE to the server and the CHALLENGE_MESSAGE to the client. An alternate name for this field is NTLMSSP_NEGOTIATE_DATAGRAM',
    65  - NegotiateFlags.NEGOTIATE_SEAL : 'requests session key negotiation for message confidentiality. If the client sends NTLMSSP_NEGOTIATE_SEAL to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SEAL to the client in the CHALLENGE_MESSAGE. Clients and servers that set NTLMSSP_NEGOTIATE_SEAL SHOULD always set NTLMSSP_NEGOTIATE_56 and NTLMSSP_NEGOTIATE_128, if they are supported. An alternate name for this field is NTLMSSP_NEGOTIATE_SEAL.',
    66  - NegotiateFlags.NEGOTIATE_SIGN : 'requests session key negotiation for message signatures. If the client sends NTLMSSP_NEGOTIATE_SIGN to the server in the NEGOTIATE_MESSAGE, the server MUST return NTLMSSP_NEGOTIATE_SIGN to the client in the CHALLENGE_MESSAGE. An alternate name for this field is NTLMSSP_NEGOTIATE_SIGN.',
    67  - NegotiateFlags.r10 : 'This bit is unused and MUST be zero.',
    68  - NegotiateFlags.REQUEST_TARGET : 'TargetName field of the CHALLENGE_MESSAGE (section 2.2.1.2) MUST be supplied. An alternate name for this field is NTLMSSP_REQUEST_TARGET.',
    69  - NegotiateFlags.NTLM_NEGOTIATE_OEM : 'requests OEM character set encoding. An alternate name for this field is NTLM_NEGOTIATE_OEM. See bit A for details.',
    70  - NegotiateFlags.NEGOTIATE_UNICODE : 'requests Unicode character set encoding. An alternate name for this field is NTLMSSP_NEGOTIATE_UNICODE.',
    71  - 
    72  -}
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/ntlmssp_message_signature.py
    1  -import io
    2  - 
    3  -class NTLMSSP_MESSAGE_SIGNATURE:
    4  - def __init__(self):
    5  - self.Version = 1
    6  - self.Checksum = None
    7  - self.SeqNum = None
    8  - 
    9  - def to_bytes(self):
    10  - t = self.Version.to_bytes(4, byteorder = 'little', signed = False)
    11  - t += self.Checksum
    12  - t += self.SeqNum.to_bytes(4, byteorder = 'little', signed = False)
    13  - return t
    14  - 
    15  - @staticmethod
    16  - def from_bytes(bbuff):
    17  - return NTLMSSP_MESSAGE_SIGNATURE.from_buffer(io.BytesIO(bbuff))
    18  - 
    19  - @staticmethod
    20  - def from_buffer(buff):
    21  - v = NTLMSSP_MESSAGE_SIGNATURE()
    22  - v.Version = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    23  - v.Checksum = buff.read(8)
    24  - v.SeqNum = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    25  -
    26  - return v
    27  - 
    28  - def __repr__(self):
    29  - t = '== NTLMSSP_MESSAGE_SIGNATURE ==\r\n'
    30  - t += 'Version : %s\r\n' % self.Version
    31  - t += 'Checksum : %s\r\n' % self.Checksum
    32  - t += 'SeqNum : %s\r\n' % self.SeqNum
    33  - return t
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/ntlmssp_message_signature_noext.py
    1  -import io
    2  - 
    3  -class NTLMSSP_MESSAGE_SIGNATURE_NOEXT:
    4  - def __init__(self):
    5  - self.Version = 1
    6  - self.RandomPad = None
    7  - self.Checksum = None
    8  - self.SeqNum = None
    9  - 
    10  - def to_bytes(self):
    11  - t = self.Version.to_bytes(4, byteorder = 'little', signed = False)
    12  - t += self.RandomPad
    13  - t += self.Checksum
    14  - t += self.SeqNum.to_bytes(4, byteorder = 'little', signed = False)
    15  - return t
    16  - 
    17  - @staticmethod
    18  - def from_bytes(bbuff):
    19  - return NTLMSSP_MESSAGE_SIGNATURE_NOEXT.from_buffer(io.BytesIO(bbuff))
    20  - 
    21  - @staticmethod
    22  - def from_buffer(buff):
    23  - v = NTLMSSP_MESSAGE_SIGNATURE_NOEXT()
    24  - v.Version = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    25  - v.RandomPad = buff.read(4)
    26  - v.Checksum = buff.read(4)
    27  - v.SeqNum = int.from_bytes(buff.read(4), byteorder = 'little', signed = False)
    28  -
    29  - return v
    30  - 
    31  - def __repr__(self):
    32  - t = '== NTLMSSP_MESSAGE_SIGNATURE_NOEXT ==\r\n'
    33  - t += 'Version : %s\r\n' % self.Version
    34  - t += 'RandomPad : %s\r\n' % self.RandomPad
    35  - t += 'Checksum : %s\r\n' % self.Checksum
    36  - t += 'SeqNum : %s\r\n' % self.SeqNum
    37  - return t
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/serverinfo.py
    1  - 
    2  -from aardwolf.authentication.ntlm.structures.avpair import AVPAIRType
    3  -import datetime
    4  -import json
    5  - 
    6  -NTLMSERVERINFO_TSV_HDR = ['domainname', 'computername', 'dnsforestname', 'dnscomputername', 'dnsdomainname', 'local_time', 'os_major_version', 'os_minor_version', 'os_build', 'os_guess' ]
    7  - 
    8  - 
    9  -import datetime
    10  -import io
    11  - 
    12  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/2c57429b-fdd4-488f-b5fc-9e4cf020fcdf
    13  -class FILETIME:
    14  - def __init__(self):
    15  - self.dwLowDateTime = None
    16  - self.dwHighDateTime = None
    17  -
    18  - self.datetime = None
    19  - @staticmethod
    20  - def from_bytes(data):
    21  - return FILETIME.from_buffer(io.BytesIO(data))
    22  - 
    23  - def calc_dt(self):
    24  - if self.dwHighDateTime == 4294967295 and self.dwLowDateTime == 4294967295:
    25  - self.datetime = self.datetime = datetime.datetime(3000, 1, 1, 0, 0)
    26  - else:
    27  - ft = (self.dwHighDateTime << 32) + self.dwLowDateTime
    28  - if ft == 0:
    29  - self.datetime = datetime.datetime(1970, 1, 1, 0, 0)
    30  - else:
    31  - self.datetime = datetime.datetime.utcfromtimestamp((ft - 116444736000000000) / 10000000)
    32  -
    33  - @staticmethod
    34  - def from_dict(d):
    35  - t = FILETIME()
    36  - t.dwLowDateTime = d['dwLowDateTime']
    37  - t.dwHighDateTime = d['dwHighDateTime']
    38  - t.calc_dt()
    39  - return t
    40  - 
    41  - @staticmethod
    42  - def from_buffer(buff):
    43  - t = FILETIME()
    44  - t.dwLowDateTime = int.from_bytes(buff.read(4), byteorder='little', signed = False)
    45  - t.dwHighDateTime = int.from_bytes(buff.read(4), byteorder='little', signed = False)
    46  - t.calc_dt()
    47  - return t
    48  - 
    49  - 
    50  - 
    51  -class NTLMServerInfo:
    52  - def __init__(self):
    53  - self.domainname = None
    54  - self.computername = None
    55  - self.dnscomputername = None
    56  - self.dnsdomainname = None
    57  - self.local_time = None
    58  - self.dnsforestname = None
    59  - self.os_major_version = None
    60  - self.os_minor_version = None
    61  - self.os_build = None
    62  - self.os_guess = None
    63  -
    64  - @staticmethod
    65  - def from_challenge(challenge):
    66  - si = NTLMServerInfo()
    67  - ti = challenge.TargetInfo
    68  - for k in ti:
    69  - if k == AVPAIRType.MsvAvNbDomainName:
    70  - si.domainname = ti[k]
    71  - elif k == AVPAIRType.MsvAvNbComputerName:
    72  - si.computername = ti[k]
    73  - elif k == AVPAIRType.MsvAvDnsDomainName:
    74  - si.dnsdomainname = ti[k]
    75  - elif k == AVPAIRType.MsvAvDnsComputerName:
    76  - si.dnscomputername = ti[k]
    77  - elif k == AVPAIRType.MsvAvDnsTreeName:
    78  - si.dnsforestname = ti[k]
    79  - elif k == AVPAIRType.MsvAvTimestamp:
    80  - if isinstance(ti[k], bytes):
    81  - si.local_time = FILETIME.from_bytes(ti[k]).datetime
    82  - elif isinstance(ti[k], datetime):
    83  - si.local_time = ti[k]
    84  -
    85  - if challenge.Version is not None:
    86  - if challenge.Version.ProductMajorVersion is not None:
    87  - si.os_major_version = challenge.Version.ProductMajorVersion
    88  - if challenge.Version.ProductMinorVersion is not None:
    89  - si.os_minor_version = challenge.Version.ProductMinorVersion
    90  - if challenge.Version.ProductBuild is not None:
    91  - si.os_build = challenge.Version.ProductBuild
    92  - if challenge.Version.WindowsProduct is not None:
    93  - si.os_guess = challenge.Version.WindowsProduct
    94  -
    95  - return si
    96  - 
    97  - def to_dict(self):
    98  - t = {
    99  - 'domainname' : self.domainname,
    100  - 'computername' : self.computername,
    101  - 'dnscomputername' : self.dnscomputername,
    102  - 'dnsdomainname' : self.dnsdomainname,
    103  - 'local_time' : self.local_time,
    104  - 'dnsforestname' : self.dnsforestname,
    105  - 'os_build' : self.os_build,
    106  - 'os_guess' : self.os_guess,
    107  - 'os_major_version' : None,
    108  - 'os_minor_version' : None,
    109  - }
    110  - if self.os_major_version is not None:
    111  - t['os_major_version'] = self.os_major_version.name
    112  - if self.os_minor_version is not None:
    113  - t['os_minor_version'] = self.os_minor_version.name
    114  - return t
    115  - 
    116  - def to_tsv(self, separator = '\t'):
    117  - def vn(x):
    118  - if x is None:
    119  - return ''
    120  - return str(x)
    121  - 
    122  - d = self.to_dict()
    123  - return separator.join([ vn(d[x]) for x in NTLMSERVERINFO_TSV_HDR])
    124  -
    125  - def __str__(self):
    126  - t = '=== Server Info ====\r\n'
    127  - for k in self.__dict__:
    128  - t += '%s: %s\r\n' % (k, self.__dict__[k])
    129  -
    130  - return t
    131  - 
    132  - def to_json(self):
    133  - return json.dumps(self.to_dict())
    134  - 
    135  - def to_grep(self):
    136  - t = ''
    137  - t += '[domainname,%s]' % self.domainname
    138  - t += '[computername,%s]' % self.computername
    139  - t += '[dnscomputername,%s]' % self.dnscomputername
    140  - t += '[dnsdomainname,%s]' % self.dnsdomainname
    141  - t += '[dnsforestname,%s]' % self.dnsforestname
    142  - t += '[os_build,%s]' % self.os_build
    143  - t += '[os_guess,%s]' % self.os_guess
    144  - if self.local_time is not None:
    145  - t += '[local_time,%s]' % self.local_time.isoformat()
    146  - if self.os_major_version is not None:
    147  - t += '[os_major,%s]' % self.os_major_version.value
    148  - if self.os_minor_version is not None:
    149  - t += '[os_minor,%s]' % self.os_minor_version.value
    150  -
    151  - return t
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/structures/version.py
    1  -import enum
    2  -import io
    3  - 
    4  -class NTLMRevisionCurrent(enum.Enum):
    5  - NTLMSSP_REVISION_W2K3 = 0x0F
    6  - NTLMSSP_REVISION_UNK = 0xFF
    7  - 
    8  - 
    9  -# https://msdn.microsoft.com/en-us/library/cc236722.aspx#Appendix_A_33
    10  -class WindowsMajorVersion(enum.Enum):
    11  - WINDOWS_MAJOR_VERSION_UNK = 0xFF
    12  - WINDOWS_MAJOR_VERSION_5 = 0x05
    13  - WINDOWS_MAJOR_VERSION_6 = 0x06
    14  - WINDOWS_MAJOR_VERSION_10 = 0x0A
    15  - 
    16  - 
    17  -# https://msdn.microsoft.com/en-us/library/cc236722.aspx#Appendix_A_33
    18  -class WindowsMinorVersion(enum.Enum):
    19  - WINDOWS_MINOR_VERSION_UNK = 0xFF
    20  - WINDOWS_MINOR_VERSION_0 = 0x00
    21  - WINDOWS_MINOR_VERSION_1 = 0x01
    22  - WINDOWS_MINOR_VERSION_2 = 0x02
    23  - WINDOWS_MINOR_VERSION_3 = 0x03
    24  - 
    25  -# https://msdn.microsoft.com/en-us/library/cc236722.aspx#Appendix_A_33
    26  -WindowsProduct = {
    27  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_5, WindowsMinorVersion.WINDOWS_MINOR_VERSION_1) : 'Windows XP operating system Service Pack 2 (SP2)',
    28  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_5, WindowsMinorVersion.WINDOWS_MINOR_VERSION_2) : 'Windows Server 2003',
    29  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_0) : 'Windows Vista or Windows Server 2008',
    30  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_1) : 'Windows 7 or Windows Server 2008 R2',
    31  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_2) : 'Windows 8 or Windows Server 2012 operating system',
    32  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_6, WindowsMinorVersion.WINDOWS_MINOR_VERSION_3) : 'Windows 8.1 or Windows Server 2012 R2',
    33  - (WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10,WindowsMinorVersion.WINDOWS_MINOR_VERSION_0) : 'Windows 10 or Windows Server 2016',
    34  -}
    35  - 
    36  -# https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/b1a6ceb2-f8ad-462b-b5af-f18527c48175
    37  -class Version:
    38  - def __init__(self):
    39  - self.ProductMajorVersion = None
    40  - self.ProductMinorVersion = None
    41  - self.ProductBuild = None
    42  - self.Reserved = 0
    43  - self.NTLMRevisionCurrent = None
    44  - 
    45  - # higher level
    46  - self.WindowsProduct = None
    47  -
    48  - @staticmethod
    49  - def construct(major = WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 1555 ):
    50  - v = Version()
    51  - v.ProductMajorVersion = major
    52  - v.ProductMinorVersion = minor
    53  - v.ProductBuild = build
    54  - v.NTLMRevisionCurrent = NTLMRevisionCurrent.NTLMSSP_REVISION_W2K3
    55  -
    56  - return v
    57  - 
    58  - def to_bytes(self):
    59  - t = self.ProductMajorVersion.value.to_bytes(1, byteorder = 'little', signed = False)
    60  - t += self.ProductMinorVersion.value.to_bytes(1, byteorder = 'little', signed = False)
    61  - t += self.ProductBuild.to_bytes(2, byteorder = 'little', signed = False)
    62  - t += self.Reserved.to_bytes(3, byteorder = 'little', signed = False)
    63  - t += self.NTLMRevisionCurrent.value.to_bytes(1, byteorder = 'little', signed = False)
    64  - return t
    65  - 
    66  - @staticmethod
    67  - def from_bytes(bbuff):
    68  - return Version.from_buffer(io.BytesIO(bbuff))
    69  - 
    70  - @staticmethod
    71  - def from_buffer(buff):
    72  - v = Version()
    73  - try:
    74  - v.ProductMajorVersion = WindowsMajorVersion(int.from_bytes(buff.read(1), byteorder = 'little', signed = False))
    75  - except:
    76  - v.ProductMajorVersion = WindowsMajorVersion.WINDOWS_MAJOR_VERSION_UNK
    77  - try:
    78  - v.ProductMinorVersion = WindowsMinorVersion(int.from_bytes(buff.read(1), byteorder = 'little', signed = False))
    79  - except:
    80  - v.ProductMinorVersion = WindowsMinorVersion.WINDOWS_MINOR_VERSION_UNK
    81  - v.ProductBuild = int.from_bytes(buff.read(2), byteorder = 'little', signed = False)
    82  - v.Reserved = int.from_bytes(buff.read(3), byteorder = 'little', signed = False)
    83  - try:
    84  - v.NTLMRevisionCurrent = NTLMRevisionCurrent(int.from_bytes(buff.read(1), byteorder = 'little', signed = False))
    85  - except:
    86  - v.NTLMRevisionCurrent = NTLMRevisionCurrent.NTLMSSP_REVISION_UNK
    87  - try:
    88  - v.WindowsProduct = WindowsProduct[(v.ProductMajorVersion, v.ProductMinorVersion)]
    89  - except:
    90  - pass
    91  -
    92  - return v
    93  - 
    94  - def __repr__(self):
    95  - t = '== NTLMVersion ==\r\n'
    96  - t += 'ProductMajorVersion : %s\r\n' % repr(self.ProductMajorVersion.name)
    97  - t += 'ProductMinorVersion : %s\r\n' % repr(self.ProductMinorVersion.name)
    98  - t += 'ProductBuild : %s\r\n' % repr(self.ProductBuild)
    99  - t += 'WindowsProduct : %s\r\n' % repr(self.WindowsProduct)
    100  - return t
  • ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/templates/__init__.py
    1  - 
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/templates/client.py
    1  -from aardwolf.authentication.ntlm.structures.fields import Fields
    2  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    3  -from aardwolf.authentication.ntlm.structures.version import Version, WindowsMajorVersion, WindowsMinorVersion
    4  - 
    5  -# LDAP doesnt seem to support sign-only. either no seal nor sign nor always_sign OR include seal.
    6  -NTLMClientTemplates = {
    7  - "Windows10_15063" : {
    8  - 'flags' : NegotiateFlags.NEGOTIATE_KEY_EXCH|
    9  - NegotiateFlags.NEGOTIATE_128|
    10  - NegotiateFlags.NEGOTIATE_VERSION|
    11  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    12  - NegotiateFlags.NEGOTIATE_NTLM|
    13  - NegotiateFlags.REQUEST_TARGET|
    14  - NegotiateFlags.NEGOTIATE_UNICODE,
    15  - 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
    16  - 'domain_name' : None,
    17  - 'workstation_name' : None,
    18  - 'ntlm_downgrade' : False,
    19  - },
    20  - "Windows10_15063_channel" : {
    21  - 'flags' : NegotiateFlags.NEGOTIATE_KEY_EXCH|
    22  - NegotiateFlags.NEGOTIATE_128|
    23  - NegotiateFlags.NEGOTIATE_VERSION|
    24  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    25  - NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|
    26  - NegotiateFlags.NEGOTIATE_NTLM|
    27  - NegotiateFlags.NEGOTIATE_SIGN|
    28  - NegotiateFlags.NEGOTIATE_SEAL|
    29  - NegotiateFlags.REQUEST_TARGET|
    30  - NegotiateFlags.NEGOTIATE_UNICODE,
    31  - 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
    32  - 'domain_name' : None,
    33  - 'workstation_name' : None,
    34  - 'ntlm_downgrade' : False,
    35  - },
    36  - "Windows10_15063_old" : {
    37  - 'flags' : NegotiateFlags.NEGOTIATE_56|
    38  - NegotiateFlags.NEGOTIATE_KEY_EXCH|
    39  - NegotiateFlags.NEGOTIATE_128|
    40  - NegotiateFlags.NEGOTIATE_VERSION|
    41  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    42  - NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|
    43  - NegotiateFlags.NEGOTIATE_NTLM|
    44  - NegotiateFlags.NEGOTIATE_LM_KEY|
    45  - NegotiateFlags.NEGOTIATE_SIGN|
    46  - NegotiateFlags.REQUEST_TARGET|
    47  - NegotiateFlags.NTLM_NEGOTIATE_OEM|
    48  - NegotiateFlags.NEGOTIATE_UNICODE,
    49  - 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
    50  - 'domain_name' : None,
    51  - 'workstation_name' : None,
    52  - 'ntlm_downgrade' : False,
    53  - },
    54  - "Windows10_15063_knowkey" : {
    55  - 'flags' : NegotiateFlags.NEGOTIATE_56|
    56  - NegotiateFlags.NEGOTIATE_KEY_EXCH|
    57  - NegotiateFlags.NEGOTIATE_128|
    58  - NegotiateFlags.NEGOTIATE_VERSION|
    59  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    60  - NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|
    61  - NegotiateFlags.NEGOTIATE_NTLM|
    62  - NegotiateFlags.NEGOTIATE_LM_KEY|
    63  - NegotiateFlags.NEGOTIATE_SIGN|
    64  - NegotiateFlags.REQUEST_TARGET|
    65  - NegotiateFlags.NTLM_NEGOTIATE_OEM|
    66  - NegotiateFlags.NEGOTIATE_UNICODE,
    67  - 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
    68  - 'domain_name' : None,
    69  - 'workstation_name' : None,
    70  - 'ntlm_downgrade' : False,
    71  - 'session_key' : b'A'*16,
    72  - },
    73  - "Windows10_15063_nosign" : {
    74  - 'flags' : NegotiateFlags.NEGOTIATE_56|
    75  - NegotiateFlags.NEGOTIATE_KEY_EXCH|
    76  - NegotiateFlags.NEGOTIATE_128|
    77  - NegotiateFlags.NEGOTIATE_VERSION|
    78  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    79  - NegotiateFlags.NEGOTIATE_NTLM|
    80  - NegotiateFlags.NEGOTIATE_LM_KEY|
    81  - NegotiateFlags.REQUEST_TARGET|
    82  - NegotiateFlags.NTLM_NEGOTIATE_OEM|
    83  - NegotiateFlags.NEGOTIATE_UNICODE,
    84  - 'version' : Version.construct(WindowsMajorVersion.WINDOWS_MAJOR_VERSION_10, minor = WindowsMinorVersion.WINDOWS_MINOR_VERSION_0, build = 15063 ),
    85  - 'domain_name' : None,
    86  - 'workstation_name' : None,
    87  - 'ntlm_downgrade' : False,
    88  - },
    89  -}
  • ■ ■ ■ ■ ■ ■
    aardwolf/authentication/ntlm/templates/server.py
    1  -from aardwolf.authentication.ntlm.structures.fields import Fields
    2  -from aardwolf.authentication.ntlm.structures.negotiate_flags import NegotiateFlags
    3  -from aardwolf.authentication.ntlm.structures.version import Version
    4  -from aardwolf.authentication.ntlm.structures.avpair import AVPairs, AVPAIRType
    5  - 
    6  -NTLMServerTemplates = {
    7  - "Windows2003_nosig" : {
    8  - 'flags' : NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_128|
    9  - NegotiateFlags.NEGOTIATE_VERSION|NegotiateFlags.NEGOTIATE_TARGET_INFO|
    10  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    11  - NegotiateFlags.TARGET_TYPE_DOMAIN|NegotiateFlags.NEGOTIATE_NTLM|
    12  - NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NEGOTIATE_UNICODE ,
    13  - 'version' : Version.from_bytes(b"\x05\x02\xce\x0e\x00\x00\x00\x0f"),
    14  - 'targetinfo' : AVPairs({ AVPAIRType.MsvAvNbDomainName : 'RDP',
    15  - AVPAIRType.MsvAvNbComputerName : 'RDP-TOOLKIT',
    16  - AVPAIRType.MsvAvDnsDomainName : 'RDP.local',
    17  - AVPAIRType.MsvAvDnsComputerName : 'server2003.RDP.local',
    18  - AVPAIRType.MsvAvDnsTreeName : 'RDP.local',
    19  - }),
    20  - 
    21  - 'targetname' : 'RDP',
    22  - },
    23  - "Windows2003" : {
    24  - 'flags' : NegotiateFlags.NEGOTIATE_56|NegotiateFlags.NEGOTIATE_128|
    25  - NegotiateFlags.NEGOTIATE_KEY_EXCH|NegotiateFlags.NEGOTIATE_ALWAYS_SIGN|NegotiateFlags.NEGOTIATE_SIGN|
    26  - NegotiateFlags.NEGOTIATE_VERSION|NegotiateFlags.NEGOTIATE_TARGET_INFO|
    27  - NegotiateFlags.NEGOTIATE_EXTENDED_SESSIONSECURITY|
    28  - NegotiateFlags.TARGET_TYPE_DOMAIN|NegotiateFlags.NEGOTIATE_NTLM|
    29  - NegotiateFlags.REQUEST_TARGET|NegotiateFlags.NEGOTIATE_UNICODE,
    30  - 'version' : Version.from_bytes(b"\x05\x02\xce\x0e\x00\x00\x00\x0f"),
    31  - 'targetinfo' : AVPairs({ AVPAIRType.MsvAvNbDomainName : 'RDP',
    32  - AVPAIRType.MsvAvNbComputerName : 'RDP-TOOLKIT',
    33  - AVPAIRType.MsvAvDnsDomainName : 'RDP.local',
    34  - AVPAIRType.MsvAvDnsComputerName : 'server2003.RDP.local',
    35  - AVPAIRType.MsvAvDnsTreeName : 'RDP.local',
    36  - }),
    37  - 
    38  - 'targetname' : 'RDP',
    39  - },
    40  -}
Please wait...
Page is in error, reload to recover