From 0abf18d130e6a9ca72a1064abbbce5296cd090c1 Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Fri, 6 Mar 2015 18:19:42 +0100 Subject: [PATCH] NLA Security Layer is AVAILABLE --- bin/rdpy-rdpclient.py | 4 +- bin/rdpy-rdpmitm.py | 6 +- rdpy/core/layer.py | 8 +- rdpy/protocol/rdp/nla/cssp.py | 98 ++++++++-- rdpy/protocol/rdp/nla/ntlm.py | 337 +++++++++++++--------------------- rdpy/protocol/rdp/nla/sspi.py | 18 +- rdpy/protocol/rdp/rdp.py | 23 ++- rdpy/protocol/rdp/tpkt.py | 6 +- rdpy/protocol/rdp/x224.py | 30 ++- rdpy/security/x509.py | 8 +- 10 files changed, 274 insertions(+), 264 deletions(-) diff --git a/bin/rdpy-rdpclient.py b/bin/rdpy-rdpclient.py index dbe979a..5e88652 100755 --- a/bin/rdpy-rdpclient.py +++ b/bin/rdpy-rdpclient.py @@ -115,7 +115,7 @@ class RDPClientQtFactory(rdp.ClientFactory): self._nego = security == "nego" self._recodedPath = recodedPath if self._nego: - self._security = "nla" + self._security = rdp.SecurityLevel.RDP_LEVEL_NLA else: self._security = security self._w = None @@ -163,7 +163,7 @@ class RDPClientQtFactory(rdp.ClientFactory): #stop nego log.info("due to security nego error back to standard RDP security layer") self._nego = False - self._security = "rdp" + self._security = rdp.SecurityLevel.RDP_LEVEL_RDP self._client._widget.hide() connector.connect() return diff --git a/bin/rdpy-rdpmitm.py b/bin/rdpy-rdpmitm.py index 4fb06d0..f13b8db 100755 --- a/bin/rdpy-rdpmitm.py +++ b/bin/rdpy-rdpmitm.py @@ -35,7 +35,7 @@ from rdpy.core import log, error, rss from rdpy.protocol.rdp import rdp from twisted.internet import reactor -log._LOG_LEVEL = log.Level.INFO +log._LOG_LEVEL = log.Level.DEBUG class ProxyServer(rdp.RDPServerObserver): """ @@ -265,7 +265,7 @@ if __name__ == '__main__': privateKeyFilePath = None certificateFilePath = None ouputDirectory = None - clientSecurity = "ssl" + clientSecurity = rdp.SecurityLevel.RDP_LEVEL_SSL try: opts, args = getopt.getopt(sys.argv[1:], "hl:k:c:o:r") @@ -284,7 +284,7 @@ if __name__ == '__main__': elif opt == "-o": ouputDirectory = arg elif opt == "-r": - clientSecurity = "rdp" + clientSecurity = rdp.SecurityLevel.RDP_LEVEL_RDP if ouputDirectory is None or not os.path.dirname(ouputDirectory): log.error("%s is an invalid output directory"%ouputDirectory) diff --git a/rdpy/core/layer.py b/rdpy/core/layer.py index b57d7d3..7194495 100644 --- a/rdpy/core/layer.py +++ b/rdpy/core/layer.py @@ -222,13 +222,19 @@ class RawLayer(protocol.Protocol, LayerAutomata, IStreamSender): """ self._factory.connectionLost(self, reason) + def getDescriptor(self): + """ + @return: the twited file descriptor + """ + return self.transport + def close(self): """ @summary: Close raw layer Use File descriptor directly to not use TLS close Because is bugged """ - FileDescriptor.loseConnection(self.transport) + FileDescriptor.loseConnection(self.getDescriptor()) def expect(self, expectedLen, callback = None): """ diff --git a/rdpy/protocol/rdp/nla/cssp.py b/rdpy/protocol/rdp/nla/cssp.py index c99c646..f8c2382 100644 --- a/rdpy/protocol/rdp/nla/cssp.py +++ b/rdpy/protocol/rdp/nla/cssp.py @@ -23,11 +23,16 @@ """ from pyasn1.type import namedtype, univ, tag -from pyasn1.codec.der import encoder, decoder +import pyasn1.codec.der.encoder as der_encoder +import pyasn1.codec.der.decoder as der_decoder +import pyasn1.codec.ber.encoder as ber_encoder + from rdpy.core.type import Stream -from rdpy.security import x509 from twisted.internet import protocol from OpenSSL import crypto +from Crypto.Util import asn1 +from rdpy.security import x509 +from rdpy.core import error class NegoToken(univ.Sequence): componentType = namedtype.NamedTypes( @@ -100,11 +105,13 @@ class TSSmartCardCreds(univ.Sequence): namedtype.OptionalNamedType('domainHint', univ.OctetString().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))) ) -def encodeDERTRequest(negoTypes = [], pubKeyAuth = None): +def encodeDERTRequest(negoTypes = [], authInfo = None, pubKeyAuth = None): """ @summary: create TSRequest from list of Type @param negoTypes: {list(Type)} - @return: {str} + @param authInfo: {str} authentication info TSCredentials encrypted with authentication protocol + @param pubKeyAuth: {str} public key encrypted with authentication protocol + @return: {str} TRequest der encoded """ negoData = NegoData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1)) @@ -120,18 +127,24 @@ def encodeDERTRequest(negoTypes = [], pubKeyAuth = None): request = TSRequest() request.setComponentByName("version", univ.Integer(2).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) - request.setComponentByName("negoTokens", negoData) + + if i > 0: + request.setComponentByName("negoTokens", negoData) + + if not authInfo is None: + request.setComponentByName("authInfo", univ.OctetString(authInfo).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))) + if not pubKeyAuth is None: - request.setComponentByName("pubKeyAuth", univ.OctetString(pubKeyAuth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))) + request.setComponentByName("pubKeyAuth", univ.OctetString(pubKeyAuth).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 3))) - return encoder.encode(request) + return der_encoder.encode(request) def decodeDERTRequest(s): """ @summary: Decode the stream as @param s: {str} """ - return decoder.decode(s, asn1Spec=TSRequest())[0] + return der_decoder.decode(s, asn1Spec=TSRequest())[0] def getNegoTokens(tRequest): negoData = tRequest.getComponentByName("negoTokens") @@ -140,6 +153,18 @@ def getNegoTokens(tRequest): def getPubKeyAuth(tRequest): return tRequest.getComponentByName("pubKeyAuth").asOctets() +def encodeDERTCredentials(domain, username, password): + passwordCred = TSPasswordCreds() + passwordCred.setComponentByName("domainName", univ.OctetString(domain).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + passwordCred.setComponentByName("userName", univ.OctetString(username).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + passwordCred.setComponentByName("password", univ.OctetString(password).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 2))) + + credentials = TSCredentials() + credentials.setComponentByName("credType", univ.Integer(1).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))) + credentials.setComponentByName("credentials", univ.OctetString(der_encoder.encode(passwordCred)).subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 1))) + + return der_encoder.encode(credentials) + class CSSP(protocol.Protocol): """ @summary: Handle CSSP connection @@ -152,6 +177,10 @@ class CSSP(protocol.Protocol): """ self._layer = layer self._authenticationProtocol = authenticationProtocol + #IGenericSecurityService + self._interface = None + #function call at the end of nego + self._callback = None def setFactory(self, factory): """ @@ -173,6 +202,7 @@ class CSSP(protocol.Protocol): @summary: install proxy """ self._layer.transport = self + self._layer.getDescriptor = lambda:self.transport self._layer.connectionMade() def write(self, data): @@ -189,10 +219,14 @@ class CSSP(protocol.Protocol): """ self.transport.startTLS(sslContext) - def startNLA(self): + def startNLA(self, sslContext, callback = None): """ @summary: start NLA authentication + @param sslContext: {ssl.ClientContextFactory | ssl.DefaultOpenSSLContextFactory} context use for TLS protocol + @param callback: {function} function call when cssp layer is read """ + self._callback = callback + self.startTLS(sslContext) #send negotiate message self.transport.write(encodeDERTRequest( negoTypes = [ self._authenticationProtocol.getNegotiateMessage() ] )) #next state is receive a challenge @@ -201,17 +235,41 @@ class CSSP(protocol.Protocol): def recvChallenge(self, data): """ @summary: second state in cssp automata - @param {str}: all data available on buffer + @param data : {str} all data available on buffer """ - #get back public key - toto = self.transport.protocol._tlsConnection.get_peer_certificate().get_pubkey() - lolo = crypto.dump_privatekey(crypto.FILETYPE_ASN1, toto) - modulus, exponent = x509.extractRSAKey2(lolo) - import hexdump - print lolo - hexdump.hexdump(lolo) request = decodeDERTRequest(data) - message, interface = self._authenticationProtocol.getAuthenticateMessage(getNegoTokens(request)[0]) + message, self._interface = self._authenticationProtocol.getAuthenticateMessage(getNegoTokens(request)[0]) + #get back public key + #convert from der to ber... + pubKeyDer = crypto.dump_privatekey(crypto.FILETYPE_ASN1, self.transport.protocol._tlsConnection.get_peer_certificate().get_pubkey()) + pubKey = asn1.DerSequence() + pubKey.decode(pubKeyDer) + + rsa = x509.RSAPublicKey() + rsa.setComponentByName("modulus", univ.Integer(pubKey[1])) + rsa.setComponentByName("publicExponent", univ.Integer(pubKey[2])) + self._pubKeyBer = ber_encoder.encode(rsa) + #send authenticate message with public key encoded - import ntlm - self.transport.write(encodeDERTRequest( negoTypes = [ message ], pubKeyAuth = interface.GSS_WrapEx("".join([chr(i) for i in ntlm.pubKeyHex])))) \ No newline at end of file + self.transport.write(encodeDERTRequest( negoTypes = [ message ], pubKeyAuth = self._interface.GSS_WrapEx(self._pubKeyBer))) + #next step is received public key incremented by one + self.dataReceived = self.recvPubKeyInc + + def recvPubKeyInc(self, data): + """ + @summary: the server send the pubKeyBer + 1 + @param data : {str} all data available on buffer + """ + request = decodeDERTRequest(data) + pubKeyInc = self._interface.GSS_UnWrapEx(getPubKeyAuth(request)) + #check pubKeyInc = self._pubKeyBer + 1 + if not self._pubKeyBer[:-1] == pubKeyInc[:-1] and ord(self._pubKeyBer[-1]) + 1 == pubKeyInc[-1]: + raise error.InvalidExpectedDataException("CSSP : Invalid public key increment") + + domain, user, password = self._authenticationProtocol.getEncodedCredentials() + #send credentials + self.transport.write(encodeDERTRequest( authInfo = self._interface.GSS_WrapEx(encodeDERTCredentials(domain, user, password)))) + #reset state back to normal state + self.dataReceived = lambda x: self.__class__.dataReceived(self, x) + if not self._callback is None: + self._callback() \ No newline at end of file diff --git a/rdpy/protocol/rdp/nla/ntlm.py b/rdpy/protocol/rdp/nla/ntlm.py index 505be1a..3d541f6 100644 --- a/rdpy/protocol/rdp/nla/ntlm.py +++ b/rdpy/protocol/rdp/nla/ntlm.py @@ -28,7 +28,7 @@ import rdpy.security.pyDes as pyDes import rdpy.security.rc4 as rc4 from rdpy.security.rsa_wrapper import random from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt24Le, UInt32Le, sizeof, Stream -from rdpy.core import filetimes +from rdpy.core import filetimes, error class MajorVersion(object): """ @@ -80,6 +80,22 @@ class Negotiate(object): NTLM_NEGOTIATE_OEM = 0x00000002 NTLMSSP_NEGOTIATE_UNICODE = 0x00000001 +class AvId(object): + """ + @see: https://msdn.microsoft.com/en-us/library/cc236646.aspx + """ + MsvAvEOL = 0x0000 + MsvAvNbComputerName = 0x0001 + MsvAvNbDomainName = 0x0002 + MsvAvDnsComputerName = 0x0003 + MsvAvDnsDomainName = 0x0004 + MsvAvDnsTreeName = 0x0005 + MsvAvFlags = 0x0006 + MsvAvTimestamp = 0x0007 + MsvAvSingleHost = 0x0008 + MsvAvTargetName = 0x0009 + MsvChannelBindings = 0x000A + def getPayLoadField(message, length, bufferOffset): if length == 0: return None @@ -101,6 +117,16 @@ class Version(CompositeType): self.Reserved = UInt24Le() self.NTLMRevisionCurrent = UInt8(NTLMRevision.NTLMSSP_REVISION_W2K3) +class AvPair(CompositeType): + """ + @see: https://msdn.microsoft.com/en-us/library/cc236646.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.AvId = UInt16Le() + self.AvLen = UInt16Le(lambda:sizeof(self.Value)) + self.Value = String(readLen = self.AvLen) + class MessageSignatureEx(CompositeType): """ @summary: Signature for message @@ -109,7 +135,7 @@ class MessageSignatureEx(CompositeType): def __init__(self): CompositeType.__init__(self) self.Version = UInt32Le(0x00000001, constant = True) - self.Checksum = String(readLen = CallableValue(16)) + self.Checksum = String(readLen = CallableValue(8)) self.SeqNum = UInt32Le() class NegotiateMessage(CompositeType): @@ -166,7 +192,22 @@ class ChallengeMessage(CompositeType): return getPayLoadField(self, self.TargetNameLen.value, self.TargetNameBufferOffset.value) def getTargetInfo(self): - return getPayLoadField(self, self.TargetInfoLen.value - 4, self.TargetInfoBufferOffset.value) + return getPayLoadField(self, self.TargetInfoLen.value, self.TargetInfoBufferOffset.value) + + def getTargetInfoAsAvPairArray(self): + """ + @summary: Parse Target info field to retrieve array of AvPair + @return: {map(AvId, str)} + """ + result = {} + s = Stream(self.getTargetInfo()) + while(True): + avPair = AvPair() + s.readType(avPair) + if avPair.AvId.value == AvId.MsvAvEOL: + return result + result[avPair.AvId.value] = avPair.Value.value + class AuthenticateMessage(CompositeType): """ @@ -205,7 +246,7 @@ class AuthenticateMessage(CompositeType): self.NegotiateFlags = UInt32Le() self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION)) - #self.MIC = String("\x00" * 16, readLen = CallableValue(16)) + self.MIC = String("\x00" * 16, readLen = CallableValue(16)) self.Payload = String() def getUserName(self): @@ -223,7 +264,7 @@ class AuthenticateMessage(CompositeType): def getEncryptedRandomSession(self): return getPayLoadField(self, self.EncryptedRandomSessionLen.value, self.EncryptedRandomSessionBufferOffset.value) -def createAuthenticationMessage(domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey): +def createAuthenticationMessage(NegFlag, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, Workstation): """ @summary: Create an Authenticate Message @param domain: {str} domain microsoft @@ -233,6 +274,7 @@ def createAuthenticationMessage(domain, user, NtChallengeResponse, LmChallengeRe @param EncryptedRandomSessionKey: {str} EncryptedRandomSessionKey """ message = AuthenticateMessage() + message.NegotiateFlags.value = NegFlag #fill message offset = sizeof(message) @@ -246,6 +288,11 @@ def createAuthenticationMessage(domain, user, NtChallengeResponse, LmChallengeRe message.Payload.value += user offset += len(user) + message.WorkstationLen.value = len(Workstation) + message.WorkstationBufferOffset.value = offset + message.Payload.value += Workstation + offset += len(Workstation) + message.LmChallengeResponseLen.value = len(LmChallengeResponse) message.LmChallengeResponseBufferOffset.value = offset message.Payload.value += LmChallengeResponse @@ -399,7 +446,7 @@ def ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChall Responserversion = "\x01" HiResponserversion = "\x01" - temp = Responserversion + HiResponserversion + Z(6) + Time + ClientChallenge + Z(4) + ServerName + Z(4) + temp = Responserversion + HiResponserversion + Z(6) + Time + ClientChallenge + Z(4) + ServerName NTProofStr = HMAC_MD5(ResponseKeyNT, ServerChallenge + temp) NtChallengeResponse = NTProofStr + temp LmChallengeResponse = HMAC_MD5(ResponseKeyLM, ServerChallenge + ClientChallenge) + ClientChallenge @@ -427,6 +474,19 @@ def MAC(handle, SigningKey, SeqNum, Message): signature.Checksum.value = rc4.crypt(handle, HMAC_MD5(SigningKey, s.getvalue() + Message)[:8]) return signature + +def MIC(ExportedSessionKey, negotiateMessage, challengeMessage, authenticateMessage): + """ + @summary: Compute MIC signature + @param negotiateMessage: {NegotiateMessage} + @param challengeMessage: {ChallengeMessage} + @param authenticateMessage: {AuthenticateMessage} + @return: {str} signature + @see: https://msdn.microsoft.com/en-us/library/cc236676.aspx + """ + s = Stream() + s.writeType((negotiateMessage, challengeMessage, authenticateMessage)) + return HMAC_MD5(ExportedSessionKey, s.getvalue()) class NTLMv2(sspi.IAuthenticationProtocol): """ @@ -435,16 +495,23 @@ class NTLMv2(sspi.IAuthenticationProtocol): def __init__(self, domain, user, password): self._domain = domain self._user = user + self._password = password + self._enableUnicode = False #https://msdn.microsoft.com/en-us/library/cc236700.aspx self._ResponseKeyNT = NTOWFv2(password, user, domain) self._ResponseKeyLM = LMOWFv2(password, user, domain) + + #For MIC computation + self._negotiateMessage = None + self._challengeMessage = None + self._authenticateMessage = None def getNegotiateMessage(self): """ @summary: generate first handshake messgae """ - message = NegotiateMessage() - message.NegotiateFlags.value = (Negotiate.NTLMSSP_NEGOTIATE_KEY_EXCH | + self._negotiateMessage = NegotiateMessage() + self._negotiateMessage.NegotiateFlags.value = (Negotiate.NTLMSSP_NEGOTIATE_KEY_EXCH | Negotiate.NTLMSSP_NEGOTIATE_128 | Negotiate.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | Negotiate.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | @@ -453,7 +520,7 @@ class NTLMv2(sspi.IAuthenticationProtocol): Negotiate.NTLMSSP_NEGOTIATE_SIGN | Negotiate.NTLMSSP_REQUEST_TARGET | Negotiate.NTLMSSP_NEGOTIATE_UNICODE) - return message + return self._negotiateMessage def getAuthenticateMessage(self, s): """ @@ -462,13 +529,21 @@ class NTLMv2(sspi.IAuthenticationProtocol): @return: {(AuthenticateMessage, NTLMv2SecurityInterface)} Last handshake message and security interface use to encrypt @see: https://msdn.microsoft.com/en-us/library/cc236676.aspx """ - challenge = ChallengeMessage() - s.readType(challenge) + self._challengeMessage = ChallengeMessage() + s.readType(self._challengeMessage) - ServerChallenge = challenge.ServerChallenge.value + ServerChallenge = self._challengeMessage.ServerChallenge.value ClientChallenge = random(64) - Timestamp = CurrentFileTimes() - ServerName = challenge.getTargetInfo() + + computeMIC = False + ServerName = self._challengeMessage.getTargetInfo() + infos = self._challengeMessage.getTargetInfoAsAvPairArray() + if infos.has_key(AvId.MsvAvTimestamp): + Timestamp = infos[AvId.MsvAvTimestamp] + computeMIC = True + else: + Timestamp = CurrentFileTimes() + NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ComputeResponsev2(self._ResponseKeyNT, self._ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName) KeyExchangeKey = KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge) @@ -476,9 +551,15 @@ class NTLMv2(sspi.IAuthenticationProtocol): EncryptedRandomSessionKey = RC4K(KeyExchangeKey, ExportedSessionKey) domain, user = self._domain, self._user - if challenge.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE: + if self._challengeMessage.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE: + self._enableUnicode = True domain, user = UNICODE(domain), UNICODE(user) - message = createAuthenticationMessage(domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey) + self._authenticateMessage = createAuthenticationMessage(self._challengeMessage.NegotiateFlags.value, domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey, "") + + if computeMIC: + self._authenticateMessage.MIC.value = MIC(ExportedSessionKey, self._negotiateMessage, self._challengeMessage, self._authenticateMessage) + else: + self._authenticateMessage.MIC._conditional = lambda:False ClientSigningKey = SIGNKEY(ExportedSessionKey, True) ServerSigningKey = SIGNKEY(ExportedSessionKey, False) @@ -487,7 +568,17 @@ class NTLMv2(sspi.IAuthenticationProtocol): interface = NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey) - return message, interface + return self._authenticateMessage, interface + + def getEncodedCredentials(self): + """ + @summary: return encoded credentials accorded with authentication protocol nego + @return: (domain, username, password) + """ + if self._enableUnicode: + return UNICODE(self._domain), UNICODE(self._user), UNICODE(self._password) + else: + return self._domain, self._user, self._password class NTLMv2SecurityInterface(sspi.IGenericSecurityService): @@ -519,196 +610,26 @@ class NTLMv2SecurityInterface(sspi.IGenericSecurityService): s = Stream() s.writeType(signature) return s.getvalue() + encryptedData - -pubKeyHex = [ -0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0x9E, -0x95, 0xB5, 0x41, 0x03, 0xC5, 0x33, 0xEA, 0x29, -0x65, 0x2B, 0x65, 0xEF, 0x30, 0x71, 0xDD, 0x73, -0xBB, 0x30, 0x3B, 0xEC, 0xCA, 0x72, 0xCF, 0xBD, -0xE0, 0xF8, 0x21, 0xFF, 0xA6, 0x97, 0x76, 0xA1, -0x08, 0xB5, 0xD2, 0xC6, 0x95, 0x81, 0xD2, 0xBA, -0x71, 0x10, 0x4A, 0xAC, 0x25, 0x34, 0x37, 0xA0, -0xC3, 0x57, 0xF0, 0xEA, 0x1F, 0x8C, 0x84, 0xEB, -0x7B, 0xE6, 0x6C, 0x50, 0x26, 0x1F, 0xB7, 0x41, -0x0A, 0x58, 0xD3, 0x80, 0x87, 0x3D, 0x0B, 0x41, -0xD9, 0xBC, 0x54, 0x3A, 0x0F, 0x77, 0x14, 0x79, -0xF5, 0xB9, 0xA4, 0x38, 0xEB, 0x13, 0x08, 0x35, -0xAE, 0xBF, 0xB3, 0x17, 0x5A, 0xE2, 0x58, 0x89, -0x39, 0xC4, 0x22, 0x7F, 0x16, 0x57, 0x90, 0x08, -0xAF, 0x91, 0x3B, 0x95, 0xC8, 0x53, 0xD0, 0xC0, -0x8E, 0x19, 0x8A, 0xF3, 0x10, 0xBC, 0xC8, 0xC7, -0x42, 0xFB, 0x12, 0xDE, 0x2D, 0x5E, 0x83, 0x02, -0x03, 0x01, 0x00, 0x01 ] - -peer0_0 = [ -0x30, 0x2f, 0xa0, 0x03, 0x02, 0x01, 0x02, 0xa1, -0x28, 0x30, 0x26, 0x30, 0x24, 0xa0, 0x22, 0x04, -0x20, 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, -0x00, 0x01, 0x00, 0x00, 0x00, 0x35, 0x82, 0x08, -0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00 ] -peer1_0 = [ -0x30, 0x82, 0x01, 0x09, 0xa0, 0x03, 0x02, 0x01, -0x02, 0xa1, 0x82, 0x01, 0x00, 0x30, 0x81, 0xfd, -0x30, 0x81, 0xfa, 0xa0, 0x81, 0xf7, 0x04, 0x81, -0xf4, 0x4e, 0x54, 0x4c, 0x4d, 0x53, 0x53, 0x50, -0x00, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, -0x00, 0x38, 0x00, 0x00, 0x00, 0x35, 0x82, 0x89, -0x62, 0x0a, 0xee, 0xd7, 0xc3, 0xeb, 0x8e, 0x34, -0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0xae, 0x00, 0xae, 0x00, 0x46, 0x00, 0x00, -0x00, 0x06, 0x01, 0xb1, 0x1d, 0x00, 0x00, 0x00, -0x0f, 0x53, 0x00, 0x49, 0x00, 0x52, 0x00, 0x41, -0x00, 0x44, 0x00, 0x45, 0x00, 0x4c, 0x00, 0x02, -0x00, 0x0e, 0x00, 0x53, 0x00, 0x49, 0x00, 0x52, -0x00, 0x41, 0x00, 0x44, 0x00, 0x45, 0x00, 0x4c, -0x00, 0x01, 0x00, 0x16, 0x00, 0x57, 0x00, 0x41, -0x00, 0x56, 0x00, 0x2d, 0x00, 0x47, 0x00, 0x4c, -0x00, 0x57, 0x00, 0x2d, 0x00, 0x30, 0x00, 0x30, -0x00, 0x39, 0x00, 0x04, 0x00, 0x1a, 0x00, 0x53, -0x00, 0x69, 0x00, 0x72, 0x00, 0x61, 0x00, 0x64, -0x00, 0x65, 0x00, 0x6c, 0x00, 0x2e, 0x00, 0x6c, -0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, -0x00, 0x03, 0x00, 0x32, 0x00, 0x77, 0x00, 0x61, -0x00, 0x76, 0x00, 0x2d, 0x00, 0x67, 0x00, 0x6c, -0x00, 0x77, 0x00, 0x2d, 0x00, 0x30, 0x00, 0x30, -0x00, 0x39, 0x00, 0x2e, 0x00, 0x53, 0x00, 0x69, -0x00, 0x72, 0x00, 0x61, 0x00, 0x64, 0x00, 0x65, -0x00, 0x6c, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x6f, -0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x05, -0x00, 0x1a, 0x00, 0x53, 0x00, 0x69, 0x00, 0x72, -0x00, 0x61, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, -0x00, 0x2e, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, -0x00, 0x61, 0x00, 0x6c, 0x00, 0x07, 0x00, 0x08, -0x00, 0xe5, 0x40, 0x3c, 0xa6, 0x68, 0x57, 0xd0, -0x01, 0x00, 0x00, 0x00, 0x00 ] -peer0_1 = [ -0x30, 0x82, 0x02, 0x21, 0xa0, 0x03, 0x02, 0x01, -0x02, 0xa1, 0x82, 0x01, 0x76, 0x30, 0x82, 0x01, -0x72, 0x30, 0x82, 0x01, 0x6e, 0xa0, 0x82, 0x01, -0x6a, 0x04, 0x82, 0x01, 0x66, 0x4e, 0x54, 0x4c, -0x4d, 0x53, 0x53, 0x50, 0x00, 0x03, 0x00, 0x00, -0x00, 0x18, 0x00, 0x18, 0x00, 0x64, 0x00, 0x00, -0x00, 0xda, 0x00, 0xda, 0x00, 0x7c, 0x00, 0x00, -0x00, 0x0e, 0x00, 0x0e, 0x00, 0x40, 0x00, 0x00, -0x00, 0x16, 0x00, 0x16, 0x00, 0x4e, 0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x10, 0x00, 0x10, 0x00, 0x56, 0x01, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x00, 0x69, -0x00, 0x72, 0x00, 0x61, 0x00, 0x64, 0x00, 0x65, -0x00, 0x6c, 0x00, 0x73, 0x00, 0x70, 0x00, 0x65, -0x00, 0x79, 0x00, 0x72, 0x00, 0x65, 0x00, 0x66, -0x00, 0x69, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, -0x00, 0x8a, 0x01, 0x34, 0xd8, 0x57, 0x6e, 0x14, -0x2b, 0xda, 0xc6, 0x91, 0x02, 0x49, 0xbb, 0xc4, -0x00, 0x19, 0x6c, 0x60, 0x26, 0x16, 0xdb, 0x37, -0x8f, 0x98, 0xe1, 0x04, 0xf8, 0x36, 0x6a, 0x96, -0xa2, 0xa1, 0x9a, 0xf9, 0x5f, 0x1f, 0x04, 0x63, -0x69, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x38, 0x8d, 0x10, 0x08, 0x71, 0x57, 0xd0, -0x01, 0x19, 0x6c, 0x60, 0x26, 0x16, 0xdb, 0x37, -0x8f, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x0e, -0x00, 0x53, 0x00, 0x49, 0x00, 0x52, 0x00, 0x41, -0x00, 0x44, 0x00, 0x45, 0x00, 0x4c, 0x00, 0x01, -0x00, 0x16, 0x00, 0x57, 0x00, 0x41, 0x00, 0x56, -0x00, 0x2d, 0x00, 0x47, 0x00, 0x4c, 0x00, 0x57, -0x00, 0x2d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x39, -0x00, 0x04, 0x00, 0x1a, 0x00, 0x53, 0x00, 0x69, -0x00, 0x72, 0x00, 0x61, 0x00, 0x64, 0x00, 0x65, -0x00, 0x6c, 0x00, 0x2e, 0x00, 0x6c, 0x00, 0x6f, -0x00, 0x63, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x03, -0x00, 0x32, 0x00, 0x77, 0x00, 0x61, 0x00, 0x76, -0x00, 0x2d, 0x00, 0x67, 0x00, 0x6c, 0x00, 0x77, -0x00, 0x2d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x39, -0x00, 0x2e, 0x00, 0x53, 0x00, 0x69, 0x00, 0x72, -0x00, 0x61, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, -0x00, 0x2e, 0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, -0x00, 0x61, 0x00, 0x6c, 0x00, 0x05, 0x00, 0x1a, -0x00, 0x53, 0x00, 0x69, 0x00, 0x72, 0x00, 0x61, -0x00, 0x64, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x2e, -0x00, 0x6c, 0x00, 0x6f, 0x00, 0x63, 0x00, 0x61, -0x00, 0x6c, 0x00, 0x07, 0x00, 0x08, 0x00, 0xe5, -0x40, 0x3c, 0xa6, 0x68, 0x57, 0xd0, 0x01, 0x00, -0x00, 0x00, 0x00, 0x59, 0x49, 0x84, 0x63, 0xf2, -0x84, 0x53, 0x18, 0xea, 0xa4, 0xc3, 0xb6, 0x97, -0x0d, 0x3e, 0x38, 0xa3, 0x81, 0x9f, 0x04, 0x81, -0x9c, 0x01, 0x00, 0x00, 0x00, 0xc9, 0x56, 0x22, -0x84, 0x7d, 0xba, 0xa2, 0xe6, 0x00, 0x00, 0x00, -0x00, 0x1c, 0x6d, 0x39, 0xe1, 0x5a, 0x31, 0x5d, -0xf5, 0x01, 0xa6, 0xea, 0x4b, 0xaf, 0x83, 0x13, -0xdc, 0x8a, 0x45, 0xb3, 0x76, 0xc6, 0x3d, 0xbf, -0x73, 0x4c, 0x93, 0xe6, 0x75, 0x8b, 0x42, 0x21, -0xea, 0xe6, 0x0c, 0xfa, 0x3c, 0xd0, 0x7c, 0x8d, -0xd6, 0x2a, 0x97, 0x7a, 0x49, 0xb5, 0x7d, 0xeb, -0xc2, 0x94, 0xc0, 0x84, 0xb4, 0xef, 0x7f, 0x1e, -0xa3, 0xa3, 0x3f, 0x61, 0x7c, 0x1c, 0xd9, 0x82, -0xc6, 0x0b, 0x6c, 0x85, 0x15, 0xb0, 0x47, 0x25, -0xe9, 0x0a, 0x88, 0x58, 0x3c, 0x6d, 0x8e, 0x60, -0x2a, 0xbc, 0x04, 0x57, 0x7f, 0x5b, 0x03, 0x7c, -0x7a, 0x8f, 0x1b, 0x7b, 0xe3, 0x67, 0xb6, 0x02, -0xa4, 0xc0, 0xdd, 0x9e, 0x97, 0x4c, 0xd8, 0x86, -0x5c, 0x9a, 0x45, 0x0d, 0x85, 0x4b, 0x46, 0x87, -0xde, 0xcf, 0x31, 0x72, 0xe3, 0xd7, 0x5d, 0x0b, -0x67, 0x1b, 0xa1, 0xde, 0x24, 0x87, 0xdf, 0xd9, -0xb2, 0x18, 0xfd, 0x5a, 0x29, 0xbb, 0x35, 0xe0, -0x3d, 0x9f, 0x85, 0xf7, 0x36 ] - -if __name__ == "__main__": - import cssp, hexdump - negotiate_data_request = cssp.decodeDERTRequest(Stream("".join([chr(i) for i in peer0_0]))) - challenge_data_request = cssp.decodeDERTRequest(Stream("".join([chr(i) for i in peer1_0]))) - authenticate_data_request = cssp.decodeDERTRequest(Stream("".join([chr(i) for i in peer0_1]))) - negotiate_data = cssp.getNegoTokens(negotiate_data_request)[0] - challenge_data = cssp.getNegoTokens(challenge_data_request)[0] - authenticate_data = cssp.getNegoTokens(authenticate_data_request)[0] - - negotiate = NegotiateMessage() - negotiate_data.readType(negotiate) - - challenge = ChallengeMessage() - challenge_data.readType(challenge) - - ServerChallenge = challenge.ServerChallenge.value - ServerName = challenge.getTargetInfo() - - authenticate = AuthenticateMessage() - authenticate_data.readType(authenticate) - - NtChallengeResponseTemp = authenticate.getNtChallengeResponse() - NTProofStr = NtChallengeResponseTemp[:16] - temp = NtChallengeResponseTemp[16:] - Timestamp = temp[8:16] - ClientChallenge = temp[16:24] - EncryptedRandomSessionKey = authenticate.getEncryptedRandomSession() - - domain = "" - user = "" - password = "" - ResponseKeyNT = NTOWFv2(password, user, domain) - ResponseKeyLM = LMOWFv2(password, user, domain) + def GSS_UnWrapEx(self, data): + """ + @summary: decrypt data with key exchange in Authentication protocol + @param data: {str} + """ + signature = MessageSignatureEx() + message = String() + s = Stream(data) + s.readType((signature, message)) - NtChallengeResponse, LmChallengeResponse, SessionBaseKey = ComputeResponsev2(ResponseKeyNT, ResponseKeyLM, ServerChallenge, ClientChallenge, Timestamp, ServerName) - KeyExchangeKey = KXKEYv2(SessionBaseKey, LmChallengeResponse, ServerChallenge) - ExportedSessionKey = RC4K(KeyExchangeKey, EncryptedRandomSessionKey) - - domain, user = domain, user - if challenge.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE: - domain, user = UNICODE(domain), UNICODE(user) - message = createAuthenticationMessage(domain, user, NtChallengeResponse, LmChallengeResponse, EncryptedRandomSessionKey) - - ClientSigningKey = SIGNKEY(ExportedSessionKey, True) - ServerSigningKey = SIGNKEY(ExportedSessionKey, False) - ClientSealingKey = SEALKEY(ExportedSessionKey, True) - ServerSealingKey = SEALKEY(ExportedSessionKey, False) - - interface = NTLMv2SecurityInterface(rc4.RC4Key(ClientSealingKey), rc4.RC4Key(ServerSealingKey), ClientSigningKey, ServerSigningKey) - - EncryptedPubKeySrc = cssp.getPubKeyAuth(authenticate_data_request) - EncryptedPubKeyDst = interface.GSS_WrapEx("".join([chr(i) for i in pubKeyHex])) - - print "EncryptedPubKeySrc" - hexdump.hexdump(EncryptedPubKeySrc) - print "EncryptedPubKeyDst" - hexdump.hexdump(EncryptedPubKeyDst) + #decrypt message + plaintextMessage = rc4.crypt(self._decryptHandle, message.value) + checksum = rc4.crypt(self._decryptHandle, signature.Checksum.value) + + #recompute checksum + t = Stream() + t.writeType(signature.SeqNum) + verify = HMAC_MD5(self._verifyKey, t.getvalue() + plaintextMessage)[:8] + if verify != checksum: + raise error.InvalidExpectedDataException("NTLMv2SecurityInterface : Invalid checksum") + + return plaintextMessage \ No newline at end of file diff --git a/rdpy/protocol/rdp/nla/sspi.py b/rdpy/protocol/rdp/nla/sspi.py index 0e7d072..b8a3a7d 100644 --- a/rdpy/protocol/rdp/nla/sspi.py +++ b/rdpy/protocol/rdp/nla/sspi.py @@ -42,6 +42,13 @@ class IAuthenticationProtocol(object): """ raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getAuthenticateMessage", "IAuthenticationProtocol")) + def getEncodedCredentials(self): + """ + @summary: return encoded credentials accorded with authentication protocol nego + @return: (domain, username, password) + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "getEncodedCredentials", "IAuthenticationProtocol")) + class IGenericSecurityService(object): """ @summary: use by application from authentification protocol @@ -49,7 +56,14 @@ class IGenericSecurityService(object): """ def GSS_WrapEx(self, data): """ - @summary: encrypt data with key exchage in Authentication protocol + @summary: encrypt data with key exchange in Authentication protocol @param data: {str} """ - raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_WrapEx", "IGenericSecurityService")) \ No newline at end of file + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_WrapEx", "IGenericSecurityService")) + + def GSS_UnWrapEx(self, data): + """ + @summary: decrypt data with key exchange in Authentication protocol + @param data: {str} + """ + raise CallPureVirtualFuntion("%s:%s defined by interface %s"%(self.__class__, "GSS_UnWrapEx", "IGenericSecurityService")) \ No newline at end of file diff --git a/rdpy/protocol/rdp/rdp.py b/rdpy/protocol/rdp/rdp.py index fa9c495..84dd80b 100644 --- a/rdpy/protocol/rdp/rdp.py +++ b/rdpy/protocol/rdp/rdp.py @@ -31,6 +31,14 @@ import tpkt, x224, sec from t125 import mcs, gcc from nla import cssp, ntlm +class SecurityLevel(object): + """ + @summary: RDP security level + """ + RDP_LEVEL_RDP = 0 + RDP_LEVEL_SSL = 1 + RDP_LEVEL_NLA = 2 + class RDPClientController(pdu.layer.PDUClientListener): """ Manage RDP stack as client @@ -59,7 +67,8 @@ class RDPClientController(pdu.layer.PDUClientListener): @return: return Protocol layer for twisted In case of RDP TPKT is the Raw layer """ - return self._tpktLayer + #build a cssp wrapper in case of nla authentication + return cssp.CSSP(self._tpktLayer, ntlm.NTLMv2(self._secLayer._info.domain.value, self._secLayer._info.userName.value, self._secLayer._info.password.value)) def getColorDepth(self): """ @@ -139,13 +148,13 @@ class RDPClientController(pdu.layer.PDUClientListener): def setSecurityLevel(self, level): """ @summary: Request basic security - @param level: {str} (ssl | rdp | nla) + @param level: {SecurityLevel} """ - if level == "rdp": + if level == SecurityLevel.RDP_LEVEL_RDP: self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_RDP - elif level == "ssl": + elif level == SecurityLevel.RDP_LEVEL_SSL: self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL - elif level == "nla": + elif level == SecurityLevel.RDP_LEVEL_NLA: self._x224Layer._requestedProtocol = x224.Protocols.PROTOCOL_SSL | x224.Protocols.PROTOCOL_HYBRID def addClientObserver(self, observer): @@ -351,6 +360,7 @@ class RDPServerController(pdu.layer.PDUServerListener): self._x224Layer = x224.Server(self._mcsLayer, privateKeyFileName, certificateFileName, False) #transport packet (protocol layer) self._tpktLayer = tpkt.TPKT(self._x224Layer) + #fastpath stack self._pduLayer.initFastPath(self._secLayer) self._secLayer.initFastPath(self._tpktLayer) @@ -527,8 +537,7 @@ class ClientFactory(layer.RawLayerClientFactory): """ controller = RDPClientController() self.buildObserver(controller, addr) - #Add cssp proxy in case of nla protocol - return cssp.CSSP(controller.getProtocol(), ntlm.NTLMv2("toto", "coco", "lolo")) + return controller.getProtocol() def buildObserver(self, controller, addr): """ diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py index a37fa85..e7fa150 100644 --- a/rdpy/protocol/rdp/tpkt.py +++ b/rdpy/protocol/rdp/tpkt.py @@ -26,8 +26,6 @@ from rdpy.core.layer import RawLayer from rdpy.core.type import UInt8, UInt16Be, sizeof from rdpy.core.error import CallPureVirtualFuntion -from nla import cssp, ntlm - class Action(object): """ @see: http://msdn.microsoft.com/en-us/library/cc240621.aspx @@ -218,9 +216,9 @@ class TPKT(RawLayer, IFastPathSender): """ self.transport.startTLS(sslContext) - def startNLA(self): + def startNLA(self, sslContext, callback): """ @summary: use to start NLA (NTLM over SSL) protocol must be called after startTLS function """ - self.transport.startNLA() \ No newline at end of file + self.transport.startNLA(sslContext, callback) \ No newline at end of file diff --git a/rdpy/protocol/rdp/x224.py b/rdpy/protocol/rdp/x224.py index 5e469b6..af3e289 100644 --- a/rdpy/protocol/rdp/x224.py +++ b/rdpy/protocol/rdp/x224.py @@ -208,19 +208,29 @@ class Client(X224Layer): if self._selectedProtocol in [ Protocols.PROTOCOL_HYBRID_EX ]: raise InvalidExpectedDataException("RDPY doesn't support PROTOCOL_HYBRID_EX security Layer") - if self._selectedProtocol in [ Protocols.PROTOCOL_SSL, Protocols.PROTOCOL_HYBRID ]: - log.debug("*" * 10 + " select SSL layer " + "*" * 10) - self._transport.startTLS(ClientTLSContext()) + #now i'm ready to receive data + self.setNextState(self.recvData) - if self._selectedProtocol == Protocols.PROTOCOL_HYBRID: - log.debug("*" * 10 + " select NLA layer " + "*" * 10) - self._transport.startNLA() - else: - #now i'm ready to receive data - self.setNextState(self.recvData) - + if self._selectedProtocol == Protocols.PROTOCOL_RDP: + log.warning("*" * 43) + log.warning("*" * 10 + " RDP Security selected " + "*" * 10) + log.warning("*" * 43) #connection is done send to presentation self._presentation.connect() + + elif self._selectedProtocol == Protocols.PROTOCOL_SSL: + log.info("*" * 43) + log.info("*" * 10 + " SSL Security selected " + "*" * 10) + log.info("*" * 43) + self._transport.startTLS(ClientTLSContext()) + #connection is done send to presentation + self._presentation.connect() + + elif self._selectedProtocol == Protocols.PROTOCOL_HYBRID: + log.info("*" * 43) + log.info("*" + " " * 10 + "NLA Security selected" + " " * 10 + "*") + log.info("*" * 43) + self._transport.startNLA(ClientTLSContext(), lambda:self._presentation.connect()) class Server(X224Layer): """ diff --git a/rdpy/security/x509.py b/rdpy/security/x509.py index 7e38c92..5b77cc7 100644 --- a/rdpy/security/x509.py +++ b/rdpy/security/x509.py @@ -154,10 +154,4 @@ def extractRSAKey(certificate): def extractRSAKeyFromASN1(subjectPublicKey): rsaKey = decoder.decode(subjectPublicKey, asn1Spec=RSAPublicKey())[0] - return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value - -def extractRSAKey2(key): - binaryTuple = decoder.decode(key, asn1Spec=univ.BitString())[0] - l = int("".join([str(i) for i in binaryTuple]), 2) - return extractRSAKeyFromASN1(hex(l)[2:-1].decode('hex')) - \ No newline at end of file + return rsaKey.getComponentByName('modulus')._value , rsaKey.getComponentByName('publicExponent')._value \ No newline at end of file