From a7058f1c54d52d76660563f4ab9a8b13115f9bb4 Mon Sep 17 00:00:00 2001 From: speyrefitte Date: Mon, 2 Mar 2015 18:37:18 +0100 Subject: [PATCH] not handle correctly the ntlmv2 auth message --- rdpy/protocol/rdp/nla/ntlm.py | 260 +++++++++++++++++++++++++++++++++- rdpy/protocol/rdp/tpkt.py | 3 +- 2 files changed, 256 insertions(+), 7 deletions(-) diff --git a/rdpy/protocol/rdp/nla/ntlm.py b/rdpy/protocol/rdp/nla/ntlm.py index 832b2a8..bc93422 100644 --- a/rdpy/protocol/rdp/nla/ntlm.py +++ b/rdpy/protocol/rdp/nla/ntlm.py @@ -22,7 +22,10 @@ @see: https://msdn.microsoft.com/en-us/library/cc236621.aspx """ -from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt24Le, UInt32Le +import hashlib, hmac +import rdpy.security.pyDes as pyDes +from rdpy.security.rsa_wrapper import random +from rdpy.core.type import CompositeType, CallableValue, String, UInt8, UInt16Le, UInt24Le, UInt32Le, sizeof class MajorVersion(object): """ @@ -99,12 +102,12 @@ class NegotiateMessage(CompositeType): self.NegotiateFlags = UInt32Le(Negotiate.NTLMSSP_NEGOTIATE_KEY_EXCH | Negotiate.NTLMSSP_NEGOTIATE_128 | + Negotiate.NTLMSSP_NEGOTIATE_56 | Negotiate.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY | Negotiate.NTLMSSP_NEGOTIATE_ALWAYS_SIGN | Negotiate.NTLMSSP_NEGOTIATE_NTLM | - Negotiate.NTLMSSP_NEGOTIATE_SIGN | - Negotiate.NTLMSSP_NEGOTIATE_SEAL | Negotiate.NTLMSSP_REQUEST_TARGET | + Negotiate.NTLMSSP_NEGOTIATE_TARGET_INFO | Negotiate.NTLMSSP_NEGOTIATE_VERSION | Negotiate.NTLMSSP_NEGOTIATE_UNICODE) @@ -116,7 +119,7 @@ class NegotiateMessage(CompositeType): self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value) self.WorkstationBufferOffset = UInt32Le() - self.Version = Version(conditional = lambda:(self.NegotiateFlags & Negotiate.NTLMSSP_NEGOTIATE_VERSION)) + self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION)) self.Payload = String() @@ -143,5 +146,250 @@ class ChallengeMessage(CompositeType): self.TargetInfoMaxLen = UInt16Le(lambda:self.TargetInfoLen.value) self.TargetInfoBufferOffset = UInt32Le() - self.Version = Version(conditional = lambda:(self.NegotiateFlags & Negotiate.NTLMSSP_NEGOTIATE_VERSION)) - self.Payload = String() \ No newline at end of file + self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION)) + self.Payload = String() + + def getTargetName(self): + if self.TargetNameLen.value == 0: + return None + offset = sizeof(self) - sizeof(self.Payload) + start = self.TargetNameBufferOffset.value - offset + end = start + self.TargetNameLen.value + return self.Payload.value[start:end] + + def getTargetInfo(self): + if self.TargetInfoLen.value == 0: + return None + offset = sizeof(self) - sizeof(self.Payload) + start = self.TargetInfoBufferOffset.value - offset + end = start + self.TargetInfoLen.value - 4 + return self.Payload.value[start:end] + +class AuthenticateMessage(CompositeType): + """ + @summary: Last message in ntlm authentication + @see: https://msdn.microsoft.com/en-us/library/cc236643.aspx + """ + def __init__(self): + CompositeType.__init__(self) + self.Signature = String("NTLMSSP\x00", readLen = CallableValue(8), constant = True) + self.MessageType = UInt32Le(0x00000003, constant = True) + + self.LmChallengeResponseLen = UInt16Le() + self.LmChallengeResponseMaxLen = UInt16Le(lambda:self.LmChallengeResponseLen.value) + self.LmChallengeResponseBufferOffset = UInt32Le() + + self.NtChallengeResponseLen = UInt16Le() + self.NtChallengeResponseMaxLen = UInt16Le(lambda:self.NtChallengeResponseLen.value) + self.NtChallengeResponseBufferOffset = UInt32Le() + + self.DomainNameLen = UInt16Le() + self.DomainNameMaxLen = UInt16Le(lambda:self.DomainNameLen.value) + self.DomainNameBufferOffset = UInt32Le() + + self.UserNameLen = UInt16Le() + self.UserNameMaxLen = UInt16Le(lambda:self.UserNameLen.value) + self.UserNameBufferOffset = UInt32Le() + + self.WorkstationLen = UInt16Le() + self.WorkstationMaxLen = UInt16Le(lambda:self.WorkstationLen.value) + self.WorkstationBufferOffset = UInt32Le() + + self.EncryptedRandomSessionLen = UInt16Le() + self.EncryptedRandomSessionMaxLen = UInt16Le(lambda:self.EncryptedRandomSessionLen.value) + self.EncryptedRandomSessionBufferOffset = UInt32Le() + + self.NegotiateFlags = UInt32Le() + self.Version = Version(conditional = lambda:(self.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_VERSION)) + + #self.MIC = String("\x00" * 16, readLen = CallableValue(16)) + self.Payload = String() + + def getUserName(self): + if self.UserNameLen.value == 0: + return None + offset = sizeof(self) - sizeof(self.Payload) + start = self.UserNameBufferOffset.value - offset + end = start + self.UserNameLen.value + return self.Payload.value[start:end] + + def getDomainName(self): + if self.DomainNameLen.value == 0: + return None + offset = sizeof(self) - sizeof(self.Payload) + start = self.DomainNameBufferOffset.value - offset + end = start + self.DomainNameLen.value + return self.Payload.value[start:end] + +def expandDesKey(key): + """ + @summary: Expand the key from a 7-byte password key into a 8-byte DES key + """ + s = chr(((ord(key[0]) >> 1) & 0x7f) << 1) + s = s + chr(((ord(key[0]) & 0x01) << 6 | ((ord(key[1]) >> 2) & 0x3f)) << 1) + s = s + chr(((ord(key[1]) & 0x03) << 5 | ((ord(key[2]) >> 3) & 0x1f)) << 1) + s = s + chr(((ord(key[2]) & 0x07) << 4 | ((ord(key[3]) >> 4) & 0x0f)) << 1) + s = s + chr(((ord(key[3]) & 0x0f) << 3 | ((ord(key[4]) >> 5) & 0x07)) << 1) + s = s + chr(((ord(key[4]) & 0x1f) << 2 | ((ord(key[5]) >> 6) & 0x03)) << 1) + s = s + chr(((ord(key[5]) & 0x3f) << 1 | ((ord(key[6]) >> 7) & 0x01)) << 1) + s = s + chr((ord(key[6]) & 0x7f) << 1) + return s + +def DES(key, data): + """ + @summary: DES use in microsoft specification + @param key: {str} Des key on 56 bits or 7 bytes + @param data: {str} data to encrypt + """ + return pyDes.des(expandDesKey(key)).encrypt(data) + +def DESL(key, data): + """ + @summary: an krosoft security function (triple des = des + des + des ;-)) + @param key: {str} Des key + @param data: {str} encrypted data + """ + return DES(key[0:7], data) + DES(key[7:14], data) + DES(key[14:16] + "\x00" * 5, data) + +def UNICODE(s): + """ + @param s: source + @return: {str} encoded in unicode + """ + return s.encode('utf-16le') + +def MD4(s): + """ + @summary: compute the md4 sum + @param s: {str} input data + @return: {str} MD4(s) + """ + return hashlib.new('md4', s).digest() + +def MD5(s): + """ + @summary: compute the md5 sum + @param s: {str} input data + @return: {str} MD5(s) + """ + return hashlib.new('md5', s).digest() + +def Z(m): + """ + @summary: fill m zero in string + @param m: {int} size of string + @return: \x00 * m + """ + return "\x00" * m + +def NTOWFv1(Passwd, User, UserDom): + """ + @see: https://msdn.microsoft.com/en-us/library/cc236699.aspx + """ + return MD4(UNICODE(Passwd)) + +def LMOWFv1(Passwd, User, UserDom): + """ + @see: https://msdn.microsoft.com/en-us/library/cc236699.aspx + """ + password = (Passwd.upper() + "\x00" * 14)[:14] + return DES(password[:7], "KGS!@#$%") + DES(password[7:], "KGS!@#$%") + +def NTLMv1Anthentication(negFlag, domain, user, password, serverChallenge, serverName): + """ + @summary: Not tested yet + @param negFlag: {int} use another non secure way + @param domain: {str} microsoft domain + @param user: {str} username + @param password: {str} password + @param serverChallenge: {str[8]} server challenge + """ + ResponseKeyNT = NTOWFv1(password, user, domain) + ResponseKeyLM = LMOWFv1(password, user, domain) + + if negFlag & Negotiate.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: + ClientChallenge = random(64) + NtChallengeResponse = DESL(ResponseKeyNT, MD5(serverChallenge + ClientChallenge)) + LmChallengeResponse = ClientChallenge + Z(16) + else: + NtChallengeResponse = DESL(ResponseKeyNT, serverChallenge) + LmChallengeResponse = DESL(ResponseKeyLM, serverChallenge) + + SessionBaseKey = MD4(ResponseKeyNT) + + return NtChallengeResponse, LmChallengeResponse, SessionBaseKey + +def HMAC_MD5(key, data): + return hmac.new(key, data, hashlib.md5).digest() + +def NTOWFv2(Passwd, User, UserDom): + return HMAC_MD5(MD4(UNICODE(Passwd)), UNICODE(User.upper() + UserDom)) + +def LMOWFv2(Passwd, User, UserDom): + return NTOWFv2(Passwd, User, UserDom) + +def NTLMv2Authenticate(negFlag, domain, user, password, serverChallenge, serverName): + """ + @summary: process NTLMv2 Authenticate hash + @see: https://msdn.microsoft.com/en-us/library/cc236700.aspx + """ + ResponseKeyNT = NTOWFv2(password, user, domain) + ResponseKeyLM = LMOWFv2(password, user, domain) + + Responserversion = "\0x01" + HiResponserversion = "0x01" + Time = "\x00" * 8 + ClientChallenge = random(64) + + temp = Responserversion + HiResponserversion + Z(6) + Time + ClientChallenge + Z(4) + serverName + Z(4) + NTProofStr = HMAC_MD5(ResponseKeyNT, serverChallenge + temp) + NtChallengeResponse = NTProofStr + temp + LmChallengeResponse = HMAC_MD5(ResponseKeyLM, serverChallenge + ClientChallenge) + ClientChallenge + + SessionBaseKey = HMAC_MD5(ResponseKeyNT, NTProofStr) + + return NtChallengeResponse, LmChallengeResponse, SessionBaseKey + +def createAuthenticationMessage(method, challengeResponse, domain, user, password): + #extract target info + NtChallengeResponse, LmChallengeResponse, SessionBaseKey = method(challengeResponse.NegotiateFlags.value, + domain, user, password, + challengeResponse.ServerChallenge.value, + challengeResponse.getTargetInfo()) + + if challengeResponse.NegotiateFlags.value & Negotiate.NTLMSSP_NEGOTIATE_UNICODE: + domain, user = UNICODE(domain), UNICODE(user) + + message = AuthenticateMessage() + #fill message + offset = sizeof(message) + + message.NegotiateFlags.value = challengeResponse.NegotiateFlags.value + message.NegotiateFlags.value &= ~Negotiate.NTLMSSP_NEGOTIATE_VERSION + + message.LmChallengeResponseLen.value = len(LmChallengeResponse) + message.LmChallengeResponseBufferOffset.value = offset + message.Payload.value += LmChallengeResponse + offset += len(LmChallengeResponse) + + message.NtChallengeResponseLen.value = len(NtChallengeResponse) + message.NtChallengeResponseBufferOffset.value = offset + message.Payload.value += NtChallengeResponse + offset += len(NtChallengeResponse) + + message.DomainNameLen.value = len(domain) + message.DomainNameBufferOffset.value = offset + message.Payload.value += domain + offset += len(domain) + + message.UserNameLen.value = len(user) + message.UserNameBufferOffset.value = offset + message.Payload.value += user + offset += len(user) + + message.EncryptedRandomSessionLen.value = len(SessionBaseKey) + message.EncryptedRandomSessionBufferOffset.value = offset + message.Payload.value += SessionBaseKey + offset += len(SessionBaseKey) + + return message \ No newline at end of file diff --git a/rdpy/protocol/rdp/tpkt.py b/rdpy/protocol/rdp/tpkt.py index 1777ae6..2664608 100644 --- a/rdpy/protocol/rdp/tpkt.py +++ b/rdpy/protocol/rdp/tpkt.py @@ -235,4 +235,5 @@ class TPKT(RawLayer, IFastPathSender): s = cssp.decodeDERTRequest(data) challenge = ntlm.ChallengeMessage() s[0].readType(challenge) - print challenge.ServerChallenge.value \ No newline at end of file + RawLayer.send(self, cssp.encodeDERTRequest( [ ntlm.createAuthenticationMessage(ntlm.NTLMv2Authenticate, challenge, "toto", "toto", "toto") ] )) + self.restartAutomata() \ No newline at end of file