text: #!/opt/impacket-impacket_0_9_20/bin/python3
  # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
  #
  # This software is provided under under a slightly modified version
  # of the Apache Software License. See the accompanying LICENSE file
  # for more information.
  #
  # Author:
  #  Alberto Solino (@agsolino)
  #
  # Description: [MS-RDPBCGR] and [MS-CREDSSP] partial implementation 
  #              just to reach CredSSP auth. This example test whether
  #              an account is valid on the target host.
  #
  # ToDo:
  #    [x] Manage to grab the server's SSL key so we can finalize the whole
  #        authentication process (check [MS-CSSP] section 3.1.5)
  #
  
  from struct import pack, unpack
  
  from impacket.examples import logger
  from impacket.structure import Structure
  from impacket.spnego import GSSAPI, ASN1_SEQUENCE, ASN1_OCTET_STRING, asn1decode, asn1encode
  
  TDPU_CONNECTION_REQUEST  = 0xe0
  TPDU_CONNECTION_CONFIRM  = 0xd0
  TDPU_DATA                = 0xf0
  TPDU_REJECT              = 0x50
  TPDU_DATA_ACK            = 0x60
  
  # RDP_NEG_REQ constants
  TYPE_RDP_NEG_REQ = 1
  PROTOCOL_RDP     = 0
  PROTOCOL_SSL     = 1
  PROTOCOL_HYBRID  = 2
  
  # RDP_NEG_RSP constants
  TYPE_RDP_NEG_RSP = 2
  EXTENDED_CLIENT_DATA_SUPPORTED = 1
  DYNVC_GFX_PROTOCOL_SUPPORTED   = 2
  
  # RDP_NEG_FAILURE constants
  TYPE_RDP_NEG_FAILURE                  = 3
  SSL_REQUIRED_BY_SERVER                = 1
  SSL_NOT_ALLOWED_BY_SERVER             = 2
  SSL_CERT_NOT_ON_SERVER                = 3
  INCONSISTENT_FLAGS                    = 4
  HYBRID_REQUIRED_BY_SERVER             = 5
  SSL_WITH_USER_AUTH_REQUIRED_BY_SERVER = 6
  
  class TPKT(Structure):
      commonHdr = (
          ('Version','B=3'),
          ('Reserved','B=0'),
          ('Length','>H=len(TPDU)+4'),
          ('_TPDU','_-TPDU','self["Length"]-4'),
          ('TPDU',':=""'),
      )
  
  class TPDU(Structure):
      commonHdr = (
          ('LengthIndicator','B=len(VariablePart)+1'),
          ('Code','B=0'),
          ('VariablePart',':=""'),
      )
  
      def __init__(self, data = None):
          Structure.__init__(self,data)
          self['VariablePart']=''
  
  class CR_TPDU(Structure):
      commonHdr = (
          ('DST-REF','<H=0'),
          ('SRC-REF','<H=0'),
          ('CLASS-OPTION','B=0'),
          ('Type','B=0'),
          ('Flags','B=0'),
          ('Length','<H=8'),
      )
  
  class DATA_TPDU(Structure):
      commonHdr = (
          ('EOT','B=0x80'),
          ('UserData',':=""'),
      )
  
      def __init__(self, data = None):
          Structure.__init__(self,data)
          self['UserData'] =''
  
  
  class RDP_NEG_REQ(CR_TPDU):
      structure = (
          ('requestedProtocols','<L'),
      )
      def __init__(self,data=None):
          CR_TPDU.__init__(self,data)
          if data is None:
              self['Type'] = TYPE_RDP_NEG_REQ
  
  class RDP_NEG_RSP(CR_TPDU):
      structure = (
          ('selectedProtocols','<L'),
      )
  
  class RDP_NEG_FAILURE(CR_TPDU):
      structure = (
          ('failureCode','<L'),
      )
  
  class TSPasswordCreds(GSSAPI):
  # TSPasswordCreds ::= SEQUENCE {
  #         domainName  [0] OCTET STRING,
  #         userName    [1] OCTET STRING,
  #         password    [2] OCTET STRING
  # }
     def __init__(self, data=None):
         GSSAPI.__init__(self,data)
         del self['UUID']
    
     def getData(self):
         ans = pack('B', ASN1_SEQUENCE)
         ans += asn1encode( pack('B', 0xa0) +
                asn1encode( pack('B', ASN1_OCTET_STRING) + 
                asn1encode( self['domainName'].encode('utf-16le'))) +
                pack('B', 0xa1) + 
                asn1encode( pack('B', ASN1_OCTET_STRING) + 
                asn1encode( self['userName'].encode('utf-16le'))) +
                pack('B', 0xa2) + 
                asn1encode( pack('B', ASN1_OCTET_STRING) + 
                asn1encode( self['password'].encode('utf-16le'))) )
         return ans 
  
  class TSCredentials(GSSAPI):
  # TSCredentials ::= SEQUENCE {
  #        credType    [0] INTEGER,
  #        credentials [1] OCTET STRING
  # }
     def __init__(self, data=None):
         GSSAPI.__init__(self,data)
         del self['UUID']
  
     def getData(self):
       # Let's pack the credentials field
       credentials =  pack('B',0xa1) 
       credentials += asn1encode(pack('B',ASN1_OCTET_STRING) +
                      asn1encode(self['credentials']))
  
       ans = pack('B',ASN1_SEQUENCE) 
       ans += asn1encode( pack('B', 0xa0) +
              asn1encode( pack('B', 0x02) + 
              asn1encode( pack('B', self['credType']))) +
              credentials)
       return ans
  
  class TSRequest(GSSAPI):
  # TSRequest ::= SEQUENCE {
  #	version     [0] INTEGER,
  #       negoTokens  [1] NegoData OPTIONAL,
  #       authInfo    [2] OCTET STRING OPTIONAL,
  #	pubKeyAuth  [3] OCTET STRING OPTIONAL,
  #}
  #
  # NegoData ::= SEQUENCE OF SEQUENCE {
  #        negoToken [0] OCTET STRING
  #}
  #
  
     def __init__(self, data=None):
         GSSAPI.__init__(self,data)
         del self['UUID']
         
     def fromString(self, data = None):
         next_byte = unpack('B',data[:1])[0]
         if next_byte != ASN1_SEQUENCE:
             raise Exception('SEQUENCE expected! (%x)' % next_byte)
         data = data[1:]
         decode_data, total_bytes = asn1decode(data) 
  
         next_byte = unpack('B',decode_data[:1])[0]
         if next_byte !=  0xa0:
              raise Exception('0xa0 tag not found %x' % next_byte)
         decode_data = decode_data[1:]
         next_bytes, total_bytes = asn1decode(decode_data)                
         # The INTEGER tag must be here
         if unpack('B',next_bytes[0:1])[0] != 0x02:
             raise Exception('INTEGER tag not found %r' % next_byte)
         next_byte, _ = asn1decode(next_bytes[1:])
         self['Version'] = unpack('B',next_byte)[0]
         decode_data = decode_data[total_bytes:]
         next_byte = unpack('B',decode_data[:1])[0]
         if next_byte == 0xa1:
             # We found the negoData token
             decode_data, total_bytes = asn1decode(decode_data[1:])
         
             next_byte = unpack('B',decode_data[:1])[0]
             if next_byte != ASN1_SEQUENCE:
                 raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
             decode_data, total_bytes = asn1decode(decode_data[1:])
  
             next_byte = unpack('B',decode_data[:1])[0]
             if next_byte != ASN1_SEQUENCE:
                 raise Exception('ASN1_SEQUENCE tag not found %r' % next_byte)
             decode_data, total_bytes = asn1decode(decode_data[1:])
  
             next_byte = unpack('B',decode_data[:1])[0]
             if next_byte != 0xa0:
                 raise Exception('0xa0 tag not found %r' % next_byte)
             decode_data, total_bytes = asn1decode(decode_data[1:])
     
             next_byte = unpack('B',decode_data[:1])[0]
             if next_byte != ASN1_OCTET_STRING:
                 raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
             decode_data2, total_bytes = asn1decode(decode_data[1:])
             # the rest should be the data
             self['NegoData'] = decode_data2
             decode_data = decode_data[total_bytes+1:]
  
         if next_byte == 0xa2:
             # ToDo: Check all this
             # We found the authInfo token
             decode_data, total_bytes = asn1decode(decode_data[1:])
             next_byte = unpack('B',decode_data[:1])[0]
             if next_byte != ASN1_OCTET_STRING:
                 raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
             decode_data2, total_bytes = asn1decode(decode_data[1:])
             self['authInfo'] = decode_data2
             decode_data = decode_data[total_bytes+1:]
  
         if next_byte == 0xa3:
             # ToDo: Check all this
             # We found the pubKeyAuth token
             decode_data, total_bytes = asn1decode(decode_data[1:])
             next_byte = unpack('B',decode_data[:1])[0]
             if next_byte != ASN1_OCTET_STRING:
                 raise Exception('ASN1_OCTET_STRING tag not found %r' % next_byte)
             decode_data2, total_bytes = asn1decode(decode_data[1:])
             self['pubKeyAuth'] = decode_data2
  
     def getData(self):
       # Do we have pubKeyAuth?
       if 'pubKeyAuth' in self.fields:
           pubKeyAuth = pack('B',0xa3)
           pubKeyAuth += asn1encode(pack('B', ASN1_OCTET_STRING) +
                         asn1encode(self['pubKeyAuth']))
       else:
           pubKeyAuth = b''
  
       if 'authInfo' in self.fields:
           authInfo = pack('B',0xa2)
           authInfo+= asn1encode(pack('B', ASN1_OCTET_STRING) +
                         asn1encode(self['authInfo']))
       else: 
           authInfo = b''
  
       if 'NegoData' in self.fields:
           negoData = pack('B',0xa1) 
           negoData += asn1encode(pack('B', ASN1_SEQUENCE) +
                      asn1encode(pack('B', ASN1_SEQUENCE) + 
                      asn1encode(pack('B', 0xa0) + 
                      asn1encode(pack('B', ASN1_OCTET_STRING) + 
                      asn1encode(self['NegoData'])))))
       else:
           negoData = b''
       ans = pack('B', ASN1_SEQUENCE)
       ans += asn1encode(pack('B',0xa0) + 
              asn1encode(pack('B',0x02) + asn1encode(pack('B',0x02))) +
              negoData + authInfo + pubKeyAuth)
       
       return ans
  
  if __name__ == '__main__':
  
      import socket
      import argparse
      import sys
      import logging
      from binascii import a2b_hex
      from Cryptodome.Cipher import ARC4
      from impacket import ntlm, version
      try:
          from OpenSSL import SSL, crypto
      except:
          logging.critical("pyOpenSSL is not installed, can't continue")
          sys.exit(1)
      
  
      class SPNEGOCipher:
          def __init__(self, flags, randomSessionKey):
              self.__flags = flags
              if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
                  self.__clientSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey)
                  self.__serverSigningKey = ntlm.SIGNKEY(self.__flags, randomSessionKey,"Server")
                  self.__clientSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey)
                  self.__serverSealingKey = ntlm.SEALKEY(self.__flags, randomSessionKey,"Server")
                  # Preparing the keys handle states
                  cipher3 = ARC4.new(self.__clientSealingKey)
                  self.__clientSealingHandle = cipher3.encrypt
                  cipher4 = ARC4.new(self.__serverSealingKey)
                  self.__serverSealingHandle = cipher4.encrypt
              else:
                  # Same key for everything
                  self.__clientSigningKey = randomSessionKey
                  self.__serverSigningKey = randomSessionKey
                  self.__clientSealingKey = randomSessionKey
                  self.__clientSealingKey = randomSessionKey
                  cipher = ARC4.new(self.__clientSigningKey)
                  self.__clientSealingHandle = cipher.encrypt
                  self.__serverSealingHandle = cipher.encrypt
              self.__sequence = 0
  
          def encrypt(self, plain_data):
              if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
                  # When NTLM2 is on, we sign the whole pdu, but encrypt just
                  # the data, not the dcerpc header. Weird..
                  sealedMessage, signature =  ntlm.SEAL(self.__flags, 
                         self.__clientSigningKey, 
                         self.__clientSealingKey,  
                         plain_data, 
                         plain_data, 
                         self.__sequence, 
                         self.__clientSealingHandle)
              else:
                  sealedMessage, signature =  ntlm.SEAL(self.__flags, 
                         self.__clientSigningKey, 
                         self.__clientSealingKey,  
                         plain_data, 
                         plain_data, 
                         self.__sequence, 
                         self.__clientSealingHandle)
  
              self.__sequence += 1
  
              return signature, sealedMessage
  
          def decrypt(self, answer):
              if self.__flags & ntlm.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY:
                  # TODO: FIX THIS, it's not calculating the signature well
                  # Since I'm not testing it we don't care... yet
                  answer, signature =  ntlm.SEAL(self.__flags, 
                          self.__serverSigningKey, 
                          self.__serverSealingKey,  
                          answer, 
                          answer, 
                          self.__sequence, 
                          self.__serverSealingHandle)
              else:
                  answer, signature = ntlm.SEAL(self.__flags, 
                          self.__serverSigningKey, 
                          self.__serverSealingKey, 
                          answer, 
                          answer, 
                          self.__sequence, 
                          self.__serverSealingHandle)
                  self.__sequence += 1
  
              return signature, answer
  
      def check_rdp(host, username, password, domain, hashes = None):
  
         if hashes is not None:
             lmhash, nthash = hashes.split(':')
             lmhash = a2b_hex(lmhash)
             nthash = a2b_hex(nthash)
  
         else:
             lmhash = ''
             nthash = ''
  
         tpkt = TPKT()
         tpdu = TPDU()
         rdp_neg = RDP_NEG_REQ()
         rdp_neg['Type'] = TYPE_RDP_NEG_REQ
         rdp_neg['requestedProtocols'] = PROTOCOL_HYBRID | PROTOCOL_SSL
         tpdu['VariablePart'] = rdp_neg.getData()
         tpdu['Code'] = TDPU_CONNECTION_REQUEST
         tpkt['TPDU'] = tpdu.getData()
     
         s = socket.socket()
         s.connect((host,3389))
         s.sendall(tpkt.getData())
         pkt = s.recv(8192)
         tpkt.fromString(pkt)
         tpdu.fromString(tpkt['TPDU'])
         cr_tpdu = CR_TPDU(tpdu['VariablePart'])
         if cr_tpdu['Type'] == TYPE_RDP_NEG_FAILURE:
             rdp_failure = RDP_NEG_FAILURE(tpdu['VariablePart'])
             rdp_failure.dump()
             logging.error("Server doesn't support PROTOCOL_HYBRID, hence we can't use CredSSP to check credentials")
             return
         else:
             rdp_neg.fromString(tpdu['VariablePart'])
  
         # Since we were accepted to talk PROTOCOL_HYBRID, below is its implementation
  
         # 1. The CredSSP client and CredSSP server first complete the TLS handshake, 
         # as specified in [RFC2246]. After the handshake is complete, all subsequent 
         # CredSSP Protocol messages are encrypted by the TLS channel. 
         # The CredSSP Protocol does not extend the TLS wire protocol. As part of the TLS 
         # handshake, the CredSSP server does not request the client's X.509 certificate 
         # (thus far, the client is anonymous). Also, the CredSSP Protocol does not require 
         # the client to have a commonly trusted certification authority root with the 
         # CredSSP server. Thus, the CredSSP server MAY use, for example, 
         # a self-signed X.509 certificate.
  
         # Switching to TLS now
         ctx = SSL.Context(SSL.TLSv1_2_METHOD)
         ctx.set_cipher_list('RC4,AES')
         tls = SSL.Connection(ctx,s)
         tls.set_connect_state()
         tls.do_handshake()
  
         # If you want to use Python internal ssl, uncomment this and comment 
         # the previous lines
         #tls = ssl.wrap_socket(s, ssl_version=ssl.PROTOCOL_TLSv1, ciphers='RC4')
  
         # 2. Over the encrypted TLS channel, the SPNEGO handshake between the client 
         # and server completes mutual authentication and establishes an encryption key 
         # that is used by the SPNEGO confidentiality services, as specified in [RFC4178]. 
         # All SPNEGO tokens as well as the underlying encryption algorithms are opaque to 
         # the calling application (the CredSSP client and CredSSP server). 
         # The wire protocol for SPNEGO is specified in [MS-SPNG].
         # The SPNEGO tokens exchanged between the client and the server are encapsulated 
         # in the negoTokens field of the TSRequest structure. Both the client and the 
         # server use this structure as many times as necessary to complete the SPNEGO 
         # exchange.<9>
         #
         # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted 
         # from the TSRequest structure by the client and server; the OPTIONAL pubKeyAuth 
         # field is omitted by the client unless the client is sending the last SPNEGO token. 
         # If the client is sending the last SPNEGO token, the TSRequest structure MUST have 
         # both the negoToken and the pubKeyAuth fields filled in.
  
         # NTLMSSP stuff
         auth = ntlm.getNTLMSSPType1('','',True, use_ntlmv2 = True)
  
         ts_request = TSRequest()
         ts_request['NegoData'] = auth.getData()
  
         tls.send(ts_request.getData())
         buff = tls.recv(4096)
         ts_request.fromString(buff)
  
     
         # 3. The client encrypts the public key it received from the server (contained 
         # in the X.509 certificate) in the TLS handshake from step 1, by using the 
         # confidentiality support of SPNEGO. The public key that is encrypted is the 
         # ASN.1-encoded SubjectPublicKey sub-field of SubjectPublicKeyInfo from the X.509 
         # certificate, as specified in [RFC3280] section 4.1. The encrypted key is 
         # encapsulated in the pubKeyAuth field of the TSRequest structure and is sent over 
         # the TLS channel to the server. 
         #
         # Note During this phase of the protocol, the OPTIONAL authInfo field is omitted 
         # from the TSRequest structure; the client MUST send its last SPNEGO token to the 
         # server in the negoTokens field (see step 2) along with the encrypted public key 
         # in the pubKeyAuth field.
  
         # Last SPNEGO token calculation
         #ntlmChallenge = ntlm.NTLMAuthChallenge(ts_request['NegoData'])
         type3, exportedSessionKey = ntlm.getNTLMSSPType3(auth, ts_request['NegoData'], username, password, domain, lmhash, nthash, use_ntlmv2 = True)
  
         # Get server public key
         server_cert =  tls.get_peer_certificate()
         pkey = server_cert.get_pubkey()
         dump = crypto.dump_privatekey(crypto.FILETYPE_ASN1, pkey)
  
         # Fix up due to PyOpenSSL lack for exporting public keys
         dump = dump[7:]
         dump = b'\x30'+ asn1encode(dump)
  
         cipher = SPNEGOCipher(type3['flags'], exportedSessionKey)
         signature, cripted_key = cipher.encrypt(dump)
         ts_request['NegoData'] = type3.getData()
         ts_request['pubKeyAuth'] = signature.getData() + cripted_key
  
         try:
             # Sending the Type 3 NTLM blob
             tls.send(ts_request.getData())
             # The other end is waiting for the pubKeyAuth field, but looks like it's
             # not needed to check whether authentication worked.
             # If auth is unsuccessful, it throws an exception with the previous send().
             # If auth is successful, the server waits for the pubKeyAuth and doesn't answer 
             # anything. So, I'm sending garbage so the server returns an error. 
             # Luckily, it's a different error so we can determine whether or not auth worked ;)
             buff = tls.recv(1024)
         except Exception as err:
             if str(err).find("denied") > 0:
                 logging.error("Access Denied")
             else:
                 logging.error(err)
             return
  
         # 4. After the server receives the public key in step 3, it first verifies that 
         # it has the same public key that it used as part of the TLS handshake in step 1. 
         # The server then adds 1 to the first byte representing the public key (the ASN.1 
         # structure corresponding to the SubjectPublicKey field, as described in step 3) 
         # and encrypts the binary result by using the SPNEGO encryption services. 
         # Due to the addition of 1 to the binary data, and encryption of the data as a binary 
         # structure, the resulting value may not be valid ASN.1-encoded values. 
         # The encrypted binary data is encapsulated in the pubKeyAuth field of the TSRequest 
         # structure and is sent over the encrypted TLS channel to the client. 
         # The addition of 1 to the first byte of the public key is performed so that the 
         # client-generated pubKeyAuth message cannot be replayed back to the client by an 
         # attacker.
         #
         # Note During this phase of the protocol, the OPTIONAL authInfo and negoTokens 
         # fields are omitted from the TSRequest structure.
  
         ts_request = TSRequest(buff)
  
         # Now we're decrypting the certificate + 1 sent by the server. Not worth checking ;)
         signature, plain_text = cipher.decrypt(ts_request['pubKeyAuth'][16:])
  
         # 5. After the client successfully verifies server authenticity by performing a 
         # binary comparison of the data from step 4 to that of the data representing 
         # the public key from the server's X.509 certificate (as specified in [RFC3280], 
         # section 4.1), it encrypts the user's credentials (either password or smart card 
         # PIN) by using the SPNEGO encryption services. The resulting value is 
         # encapsulated in the authInfo field of the TSRequest structure and sent over 
         # the encrypted TLS channel to the server.
         # The TSCredentials structure within the authInfo field of the TSRequest 
         # structure MAY contain either a TSPasswordCreds or a TSSmartCardCreds structure, 
         # but MUST NOT contain both.
         #
         # Note During this phase of the protocol, the OPTIONAL pubKeyAuth and negoTokens 
         # fields are omitted from the TSRequest structure.
         tsp = TSPasswordCreds()
         tsp['domainName'] = domain
         tsp['userName']   = username
         tsp['password']   = password
         tsc = TSCredentials()
         tsc['credType'] = 1 # TSPasswordCreds
         tsc['credentials'] = tsp.getData()
  
         signature, cripted_creds = cipher.encrypt(tsc.getData())
         ts_request = TSRequest()
         ts_request['authInfo'] = signature.getData() + cripted_creds
         tls.send(ts_request.getData())
         tls.close()
         logging.info("Access Granted")
  
      # Init the example's logger theme
      logger.init()
      print(version.BANNER)
  
      parser = argparse.ArgumentParser(add_help = True, description = "Test whether an account is valid on the target "
                                                                      "host using the RDP protocol.")
  
      parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>')
  
      group = parser.add_argument_group('authentication')
  
      group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH')
      if len(sys.argv)==1:
          parser.print_help()
          sys.exit(1)
   
      options = parser.parse_args()
  
      import re
      domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(options.target).groups('')
  
      #In case the password contains '@'
      if '@' in address:
          password = password + '@' + address.rpartition('@')[0]
          address = address.rpartition('@')[2]
  
      if domain is None:
          domain = ''
  
      if password == '' and username != '' and options.hashes is None:
          from getpass import getpass
          password = getpass("Password:")
  
      check_rdp(address, username, password, domain, options.hashes)
  