From d2a17e1782a790978f674df446f7e1a5336d1f51 Mon Sep 17 00:00:00 2001 From: Conor Hunt Date: Thu, 6 Dec 2012 21:48:17 -0500 Subject: [PATCH] fixes from NTLM real world testing --- src/ntlm/keys.go | 2 +- src/ntlm/messages/authenticate.go | 7 ++- src/ntlm/messages/challenge.go | 10 ++-- src/ntlm/messages/version.go | 2 +- src/ntlm/ntlm.go | 1 + src/ntlm/ntlmv1.go | 24 +++++++-- src/ntlm/ntlmv2.go | 90 +++++++++++++++++++++++-------- src/ntlm/ntlmv2_test.go | 13 +++++ src/ntlm/signature.go | 17 +++--- 9 files changed, 125 insertions(+), 41 deletions(-) diff --git a/src/ntlm/keys.go b/src/ntlm/keys.go index 5b0d4c9..ff9b714 100644 --- a/src/ntlm/keys.go +++ b/src/ntlm/keys.go @@ -62,7 +62,7 @@ func sealKey(flags uint32, randomSessionKey []byte, mode string) (sealKey []byte if messages.NTLMSSP_NEGOTIATE_56.IsSet(flags) { sealKey = concat(randomSessionKey[0:7], []byte{0xA0}) } else { - sealKey = concat(randomSessionKey[0:4], []byte{0xE5, 0x38, 0xB0}) + sealKey = concat(randomSessionKey[0:5], []byte{0xE5, 0x38, 0xB0}) } } else { sealKey = randomSessionKey diff --git a/src/ntlm/messages/authenticate.go b/src/ntlm/messages/authenticate.go index 97a6ec9..a38403b 100644 --- a/src/ntlm/messages/authenticate.go +++ b/src/ntlm/messages/authenticate.go @@ -211,7 +211,12 @@ func (a *Authenticate) Bytes() []byte { buffer.Write(a.EncryptedRandomSessionKey.Bytes()) buffer.Write(Uint32ToBytes(a.NegotiateFlags)) - buffer.Write(a.Version.Bytes()) + + if a.Version != nil { + buffer.Write(a.Version.Bytes()) + } else { + buffer.Write(make([]byte, 8)) + } if a.Mic != nil { buffer.Write(a.Mic) diff --git a/src/ntlm/messages/challenge.go b/src/ntlm/messages/challenge.go index aaacdbe..2fb2b34 100644 --- a/src/ntlm/messages/challenge.go +++ b/src/ntlm/messages/challenge.go @@ -122,11 +122,11 @@ func (c *Challenge) Bytes() []byte { buffer.Write(c.TargetInfoPayloadStruct.Bytes()) payloadOffset += uint32(c.TargetInfoPayloadStruct.Len) - if c.Version != nil { - buffer.Write(c.Version.Bytes()) - } else { - buffer.Write(make([]byte, 8)) - } + // if(c.Version != nil) { + buffer.Write(c.Version.Bytes()) + // } else { + // buffer.Write(make([]byte, 8)) + //} // Write out the payloads buffer.Write(c.TargetName.Payload) diff --git a/src/ntlm/messages/version.go b/src/ntlm/messages/version.go index f055bd7..c937f09 100644 --- a/src/ntlm/messages/version.go +++ b/src/ntlm/messages/version.go @@ -38,7 +38,7 @@ func (v *VersionStruct) Bytes() []byte { binary.Write(buffer, binary.LittleEndian, v.ProductMinorVersion) binary.Write(buffer, binary.LittleEndian, v.ProductBuild) buffer.Write(make([]byte, 3)) - binary.Write(buffer, binary.LittleEndian, uint8(0x0F)) + binary.Write(buffer, binary.LittleEndian, uint8(v.NTLMRevisionCurrent)) return buffer.Bytes() } diff --git a/src/ntlm/ntlm.go b/src/ntlm/ntlm.go index 61c92d2..cfd13cd 100644 --- a/src/ntlm/ntlm.go +++ b/src/ntlm/ntlm.go @@ -71,6 +71,7 @@ func CreateServerSession(version Version, mode Mode) (n ServerSession, err error type ServerSession interface { SetUserInfo(username string, password string, domain string) SetMode(mode Mode) + SetServerChallenge(challege []byte) ProcessNegotiateMessage(*messages.Negotiate) error GenerateChallengeMessage() (*messages.Challenge, error) diff --git a/src/ntlm/ntlmv1.go b/src/ntlm/ntlmv1.go index 4f786d8..41bcff3 100644 --- a/src/ntlm/ntlmv1.go +++ b/src/ntlm/ntlmv1.go @@ -79,7 +79,15 @@ func (n *V1Session) computeKeyExchangeKey() (err error) { return } -func (n *V1Session) calculateKeys() (err error) { +func (n *V1Session) calculateKeys(ntlmRevisionCurrent uint8) (err error) { + // This lovely piece of code comes courtesy of an the excellent Open Document support system from MSFT + // In order to calculate the keys correctly when the client has set the NTLMRevisionCurrent to 0xF (15) + // We must treat the flags as if NTLMSSP_NEGOTIATE_LM_KEY is set. + // This information is not contained (at least currently, until they correct it) in the MS-NLMP document + if ntlmRevisionCurrent == 15 { + n.negotiateFlags = messages.NTLMSSP_NEGOTIATE_LM_KEY.Set(n.negotiateFlags) + } + n.clientSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Client") n.serverSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Server") n.clientSealingKey = sealKey(n.negotiateFlags, n.exportedSessionKey, "Client") @@ -97,6 +105,9 @@ func (n *V1Session) Sign(message []byte) ([]byte, error) { func (n *V1Session) Mac(message []byte, sequenceNumber int) ([]byte, error) { // TODO: Need to keep track of the sequence number for connection oriented NTLM + if messages.NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(n.negotiateFlags) { + n.serverHandle, _ = reinitSealingKey(n.serverSealingKey, sequenceNumber) + } sig := mac(n.negotiateFlags, n.serverHandle, n.serverSigningKey, uint32(sequenceNumber), message) return sig.Bytes(), nil } @@ -119,6 +130,10 @@ func (n *V1ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, er return } +func (n *V1ServerSession) SetServerChallenge(challenge []byte) { + n.serverChallenge = challenge +} + func (n *V1ServerSession) ProcessAuthenticateMessage(am *messages.Authenticate) (err error) { n.authenticateMessage = am n.negotiateFlags = am.NegotiateFlags @@ -159,7 +174,7 @@ func (n *V1ServerSession) ProcessAuthenticateMessage(am *messages.Authenticate) return err } - err = n.calculateKeys() + err = n.calculateKeys(am.Version.NTLMRevisionCurrent) if err != nil { return err } @@ -214,7 +229,8 @@ func (n *V1ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err e flags := uint32(0) flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) // NOTE: Unsetting this flag in order to get the server to generate the signatures we can recognize - // flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) @@ -251,7 +267,7 @@ func (n *V1ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err e return err } - err = n.calculateKeys() + err = n.calculateKeys(cm.Version.NTLMRevisionCurrent) if err != nil { return err } diff --git a/src/ntlm/ntlmv2.go b/src/ntlm/ntlmv2.go index 78fb80c..eaf74e1 100644 --- a/src/ntlm/ntlmv2.go +++ b/src/ntlm/ntlmv2.go @@ -3,9 +3,11 @@ package ntlm import ( "bytes" + "encoding/binary" "errors" "ntlm/messages" "strings" + "time" ) /******************************* @@ -48,7 +50,15 @@ func (n *V2Session) computeKeyExchangeKey() (err error) { return } -func (n *V2Session) calculateKeys() (err error) { +func (n *V2Session) calculateKeys(ntlmRevisionCurrent uint8) (err error) { + // This lovely piece of code comes courtesy of an the excellent Open Document support system from MSFT + // In order to calculate the keys correctly when the client has set the NTLMRevisionCurrent to 0xF (15) + // We must treat the flags as if NTLMSSP_NEGOTIATE_LM_KEY is set. + // This information is not contained (at least currently, until they correct it) in the MS-NLMP document + if ntlmRevisionCurrent == 15 { + n.negotiateFlags = messages.NTLMSSP_NEGOTIATE_LM_KEY.Set(n.negotiateFlags) + } + n.clientSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Client") n.serverSigningKey = signKey(n.negotiateFlags, n.exportedSessionKey, "Server") n.clientSealingKey = sealKey(n.negotiateFlags, n.exportedSessionKey, "Client") @@ -62,9 +72,14 @@ func (n *V2Session) Seal(message []byte) ([]byte, error) { func (n *V2Session) Sign(message []byte) ([]byte, error) { return nil, nil } + func (n *V2Session) Mac(message []byte, sequenceNumber int) ([]byte, error) { // TODO: Need to keep track of the sequence number for connection oriented NTLM - return nil, nil + if messages.NTLMSSP_NEGOTIATE_DATAGRAM.IsSet(n.negotiateFlags) && messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.IsSet(n.negotiateFlags) { + n.serverHandle, _ = reinitSealingKey(n.serverSealingKey, sequenceNumber) + } + sig := mac(n.negotiateFlags, n.serverHandle, n.serverSigningKey, uint32(sequenceNumber), message) + return sig.Bytes(), nil } /************** @@ -75,6 +90,10 @@ type V2ServerSession struct { V2Session } +func (n *V2ServerSession) SetServerChallenge(challenge []byte) { + n.serverChallenge = challenge +} + func (n *V2ServerSession) ProcessNegotiateMessage(nm *messages.Negotiate) (err error) { n.negotiateMessage = nm return @@ -88,7 +107,6 @@ func (n *V2ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, er flags := uint32(0) flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) - // NOTE: Unsetting this in order for the signatures to work flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) @@ -99,6 +117,7 @@ func (n *V2ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, er flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags) flags = messages.NTLMSSP_REQUEST_TARGET.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags) + cm.NegotiateFlags = flags n.serverChallenge = randomBytes(8) @@ -116,7 +135,7 @@ func (n *V2ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, er cm.TargetInfo = pairs cm.TargetInfoPayloadStruct, _ = messages.CreateBytePayload(pairs.Bytes()) - cm.Version = &messages.VersionStruct{ProductMajorVersion: uint8(6), ProductMinorVersion: uint8(0), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(10)} + cm.Version = &messages.VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} return cm, nil } @@ -158,7 +177,7 @@ func (n *V2ServerSession) ProcessAuthenticateMessage(am *messages.Authenticate) return err } - err = n.calculateKeys() + err = n.calculateKeys(am.Version.NTLMRevisionCurrent) if err != nil { return err } @@ -213,6 +232,7 @@ func (n *V2ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err e flags := uint32(0) flags = messages.NTLMSSP_NEGOTIATE_KEY_EXCH.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_VERSION.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_TARGET_INFO.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) @@ -221,6 +241,7 @@ func (n *V2ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err e flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags) flags = messages.NTLMSSP_REQUEST_TARGET.Set(flags) flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_128.Set(flags) n.negotiateFlags = flags @@ -229,25 +250,35 @@ func (n *V2ClientSession) ProcessChallengeMessage(cm *messages.Challenge) (err e return err } - // TODO: Create the AvPairs and timestamp - /* - //err = n.computeExpectedResponses() - //if err != nil { return err } + timestamp := timeToWindowsFileTime(time.Now()) + err = n.computeExpectedResponses(timestamp, cm.TargetInfoPayloadStruct.Payload) + if err != nil { + return err + } - err = n.computeKeyExchangeKey() - if err != nil { return err } + err = n.computeKeyExchangeKey() + if err != nil { + return err + } - err = n.computeEncryptedSessionKey() - if err != nil { return err } + err = n.computeEncryptedSessionKey() + if err != nil { + return err + } - err = n.calculateKeys() - if err != nil { return err } + err = n.calculateKeys(cm.Version.NTLMRevisionCurrent) + if err != nil { + return err + } - n.clientHandle, err = rc4Init(n.clientSealingKey) - if err != nil { return err } - n.serverHandle, err = rc4Init(n.serverSealingKey) - if err != nil { return err } - */ + n.clientHandle, err = rc4Init(n.clientSealingKey) + if err != nil { + return err + } + n.serverHandle, err = rc4Init(n.serverSealingKey) + if err != nil { + return err + } return nil } @@ -262,7 +293,8 @@ func (n *V2ClientSession) GenerateAuthenticateMessage() (am *messages.Authentica am.Workstation, _ = messages.CreateStringPayload("SQUAREMILL") am.EncryptedRandomSessionKey, _ = messages.CreateBytePayload(n.encryptedRandomSessionKey) am.NegotiateFlags = n.negotiateFlags - am.Version = &messages.VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(15)} + am.Mic = make([]byte, 16) + am.Version = &messages.VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: 0x0F} return am, nil } @@ -283,13 +315,25 @@ func (n *V2ClientSession) computeEncryptedSessionKey() (err error) { NTLM V2 Password hash functions *********************************/ -// Define ntowfv2(Passwd, User, UserDom) as +// Define ntowfv2(Passwd, User, UserDom) as func ntowfv2(user string, passwd string, userDom string) []byte { concat := utf16FromString(strings.ToUpper(user) + userDom) return hmacMd5(md4(utf16FromString(passwd)), concat) } -// Define lmowfv2(Passwd, User, UserDom) as +// Define lmowfv2(Passwd, User, UserDom) as func lmowfv2(user string, passwd string, userDom string) []byte { return ntowfv2(user, passwd, userDom) } + +/******************************** + Helper functions +*********************************/ + +func timeToWindowsFileTime(t time.Time) []byte { + var ll int64 + ll = (int64(t.Unix()) * int64(10000000)) + int64(116444736000000000) + buffer := bytes.NewBuffer(make([]byte, 0, 8)) + binary.Write(buffer, binary.LittleEndian, ll) + return buffer.Bytes() +} diff --git a/src/ntlm/ntlmv2_test.go b/src/ntlm/ntlmv2_test.go index 3003e6e..d69b8cb 100644 --- a/src/ntlm/ntlmv2_test.go +++ b/src/ntlm/ntlmv2_test.go @@ -6,6 +6,7 @@ import ( "ntlm/messages" "strings" "testing" + "time" ) func checkV2Value(t *testing.T, name string, value []byte, expected string, err error) { @@ -126,3 +127,15 @@ func TestNTLMv2(t *testing.T) { t.Errorf("Could not process server generated challenge message: %s", err) } } + +func TestWindowsTimeConversion(t *testing.T) { + // From http://davenport.sourceforge.net/ntlm.html#theType3Message + // Next, the blob is constructed. The timestamp is the most tedious part of this; looking at the clock on my desk, + // it's about 6:00 AM EDT on June 17th, 2003. In Unix time, that would be 1055844000 seconds after the Epoch. + // Adding 11644473600 will give us seconds after January 1, 1601 (12700317600). Multiplying by 107 (10000000) + // will give us tenths of a microsecond (127003176000000000). As a little-endian 64-bit value, this is + // "0x0090d336b734c301" (in hexadecimal). + unix := time.Unix(1055844000, 0) + result := timeToWindowsFileTime(unix) + checkV2Value(t, "Timestamp", result, "0090d336b734c301", nil) +} diff --git a/src/ntlm/signature.go b/src/ntlm/signature.go index 4819b6b..8e153c6 100644 --- a/src/ntlm/signature.go +++ b/src/ntlm/signature.go @@ -9,6 +9,7 @@ import ( ) type NtlmsspMessageSignature struct { + ByteData []byte // A 32-bit unsigned integer that contains the signature version. This field MUST be 0x00000001. Version []byte // A 4-byte array that contains the random pad for the message. @@ -24,7 +25,12 @@ func (n *NtlmsspMessageSignature) String() string { } func (n *NtlmsspMessageSignature) Bytes() []byte { - return concat(n.Version, n.RandomPad, n.CheckSum, n.SeqNum) + if n.ByteData != nil { + return n.ByteData + } else { + return concat(n.Version, n.RandomPad, n.CheckSum, n.SeqNum) + } + return nil } // Define SEAL(Handle, SigningKey, SeqNum, Message) as @@ -53,7 +59,7 @@ func mac(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to CRC32(Message) // Set NTLMSSP_MESSAGE_SIGNATURE.RandomPad RC4(Handle, RandomPad) // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to RC4(Handle, NTLMSSP_MESSAGE_SIGNATURE.Checksum) -// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to RC4(Handle, 0x00000000) +// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to RC4(Handle, 0x00000000) // If (connection oriented) // Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to NTLMSSP_MESSAGE_SIGNATURE.SeqNum XOR SeqNum // Set SeqNum to SeqNum + 1 @@ -78,18 +84,17 @@ func macWithoutExtendedSessionSecurity(handle *rc4P.Cipher, seqNum uint32, messa sig.SeqNum[i] = sig.SeqNum[i] ^ seqNumBytes[i] } sig.RandomPad = zeroBytes(4) - return sig } // Define MAC(Handle, SigningKey, SeqNum, Message) as -// Set NTLMSSP_MESSAGE_SIGNATURE.Version to 0x00000001 +// Set NTLMSSP_MESSAGE_SIGNATURE.Version to 0x00000001 // if Key Exchange Key Negotiated // Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to RC4(Handle, HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7]) // else -// Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7] +// Set NTLMSSP_MESSAGE_SIGNATURE.Checksum to HMAC_MD5(SigningKey, ConcatenationOf(SeqNum, Message))[0..7] // end -// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to SeqNum +// Set NTLMSSP_MESSAGE_SIGNATURE.SeqNum to SeqNum // Set SeqNum to SeqNum + 1 // EndDefine func macWithExtendedSessionSecurity(negFlags uint32, handle *rc4P.Cipher, signingKey []byte, seqNum uint32, message []byte) *NtlmsspMessageSignature {