From d5082d0f0108d740b2890e5705b5cb143f91648c Mon Sep 17 00:00:00 2001 From: Conor Hunt Date: Sat, 10 Nov 2012 15:15:12 -0500 Subject: [PATCH] generate challenge messages --- src/ntlm/messages/av_pairs.go | 30 ++++++++++++++++++---- src/ntlm/messages/challenge.go | 9 ++++--- src/ntlm/messages/challenge_test.go | 14 ++++++++++ src/ntlm/messages/negotiate_flags.go | 30 +++++++++++++++++++++- src/ntlm/ntlmv2.go | 38 ++++++++++++++++++++++++++-- src/ntlm/ntlmv2_test.go | 18 ++++++++++--- 6 files changed, 125 insertions(+), 14 deletions(-) diff --git a/src/ntlm/messages/av_pairs.go b/src/ntlm/messages/av_pairs.go index b4c5a4c..465ec84 100644 --- a/src/ntlm/messages/av_pairs.go +++ b/src/ntlm/messages/av_pairs.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "encoding/hex" "fmt" - "reflect" ) type AvPairType uint16 @@ -45,6 +44,11 @@ type AvPairs struct { List []AvPair } +func (p *AvPairs) AddAvPair(avId AvPairType, bytes []byte) { + a := &AvPair{AvId: avId, AvLen: uint16(len(bytes)), Value: bytes} + p.List = append(p.List, *a) +} + func ReadAvPairs(data []byte) *AvPairs { pairs := new(AvPairs) @@ -153,10 +157,26 @@ func (a *AvPair) String() string { switch a.AvId { case MsvAvEOL: outString = "MsvAvEOL" - case MsvAvNbComputerName, MsvAvNbDomainName, MsvAvDnsComputerName, MsvAvDnsDomainName, MsvAvDnsTreeName, MsvAvTargetName: - outString = fmt.Sprintf("%s: %s", reflect.TypeOf(a.AvId).Name(), a.UnicodeStringValue()) - case MsvAvFlags, MsvAvTimestamp, MsAvRestrictions, MsvChannelBindings: - outString = fmt.Sprintf("%s: %s", reflect.TypeOf(a.AvId).Name(), hex.EncodeToString(a.Value)) + case MsvAvNbComputerName: + outString = "MsAvNbComputerName: " + a.UnicodeStringValue() + case MsvAvNbDomainName: + outString = "MsvAvNbDomainName: " + a.UnicodeStringValue() + case MsvAvDnsComputerName: + outString = "MsvAvDnsComputerName: " + a.UnicodeStringValue() + case MsvAvDnsDomainName: + outString = "MsvAvDnsDomainName: " + a.UnicodeStringValue() + case MsvAvDnsTreeName: + outString = "MsvAvDnsTreeName: " + a.UnicodeStringValue() + case MsvAvFlags: + outString = "MsvAvFlags: " + hex.EncodeToString(a.Value) + case MsvAvTimestamp: + outString = "MsvAvTimestamp: " + hex.EncodeToString(a.Value) + case MsAvRestrictions: + outString = "MsAvRestrictions: " + hex.EncodeToString(a.Value) + case MsvAvTargetName: + outString = "MsvAvTargetName: " + a.UnicodeStringValue() + case MsvChannelBindings: + outString = "MsvChannelBindings: " + hex.EncodeToString(a.Value) default: outString = fmt.Sprintf("unknown pair type: '%d'", a.AvId) } diff --git a/src/ntlm/messages/challenge.go b/src/ntlm/messages/challenge.go index edb0dce..da73494 100644 --- a/src/ntlm/messages/challenge.go +++ b/src/ntlm/messages/challenge.go @@ -95,7 +95,7 @@ func ParseChallengeMessage(body []byte) (*Challenge, error) { func (c *Challenge) Bytes() []byte { payloadLen := int(c.TargetName.Len + c.TargetInfoPayloadStruct.Len) - messageLen := 8 + 4 + 12 + 4 + 8 + 8 + 12 + 8 + messageLen := 8 + 4 + 8 + 4 + 8 + 8 + 8 + 8 payloadOffset := uint32(messageLen) messageBytes := make([]byte, 0, messageLen+payloadLen) @@ -105,18 +105,21 @@ func (c *Challenge) Bytes() []byte { binary.Write(buffer, binary.LittleEndian, c.MessageType) c.TargetName.Offset = payloadOffset - payloadOffset += uint32(c.TargetName.Len) buffer.Write(c.TargetName.Bytes()) + payloadOffset += uint32(c.TargetName.Len) binary.Write(buffer, binary.LittleEndian, c.NegotiateFlags) buffer.Write(c.ServerChallenge) buffer.Write(make([]byte, 8)) c.TargetInfoPayloadStruct.Offset = payloadOffset - payloadOffset += uint32(c.TargetInfoPayloadStruct.Len) 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)) } // Write out the payloads diff --git a/src/ntlm/messages/challenge_test.go b/src/ntlm/messages/challenge_test.go index d31f984..624615e 100644 --- a/src/ntlm/messages/challenge_test.go +++ b/src/ntlm/messages/challenge_test.go @@ -46,4 +46,18 @@ func TestDecodeChallenge(t *testing.T) { } challenge.String() + + outBytes := challenge.Bytes() + + if len(outBytes) > 0 { + reparsed, err := ParseChallengeMessage(outBytes) + if err != nil { + t.Error("Could not re-parse challenge message") + } + if reparsed.String() != challenge.String() { + t.Error("Reparsed message is not the same") + } + } else { + t.Error("Invalid challenge messsage bytes") + } } diff --git a/src/ntlm/messages/negotiate_flags.go b/src/ntlm/messages/negotiate_flags.go index 3c8e0ad..121d976 100644 --- a/src/ntlm/messages/negotiate_flags.go +++ b/src/ntlm/messages/negotiate_flags.go @@ -138,6 +138,34 @@ func (f NegotiateFlag) String() string { return reflect.TypeOf(f).Name() } +func GetFlagName(flag NegotiateFlag) string { + nameMap := map[NegotiateFlag]string{ + NTLMSSP_NEGOTIATE_56: "NTLMSSP_NEGOTIATE_56", + NTLMSSP_NEGOTIATE_KEY_EXCH: "NTLMSSP_NEGOTIATE_KEY_EXCH", + NTLMSSP_NEGOTIATE_128: "NTLMSSP_NEGOTIATE_128", + NTLMSSP_NEGOTIATE_VERSION: "NTLMSSP_NEGOTIATE_VERSION", + NTLMSSP_NEGOTIATE_TARGET_INFO: "NTLMSSP_NEGOTIATE_TARGET_INFO", + NTLMSSP_REQUEST_NON_NT_SESSION_KEY: "NTLMSSP_REQUEST_NON_NT_SESSION_KEY", + NTLMSSP_NEGOTIATE_IDENTIFY: "NTLMSSP_NEGOTIATE_IDENTIFY", + NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY: "NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY", + NTLMSSP_TARGET_TYPE_SERVER: "NTLMSSP_TARGET_TYPE_SERVER", + NTLMSSP_TARGET_TYPE_DOMAIN: "NTLMSSP_TARGET_TYPE_DOMAIN", + NTLMSSP_NEGOTIATE_ALWAYS_SIGN: "NTLMSSP_NEGOTIATE_ALWAYS_SIGN", + NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED: "NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED", + NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED: "NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED", + NTLMSSP_ANONYMOUS: "NTLMSSP_ANONYMOUS", + NTLMSSP_NEGOTIATE_NTLM: "NTLMSSP_NEGOTIATE_NTLM", + NTLMSSP_NEGOTIATE_LM_KEY: "NTLMSSP_NEGOTIATE_LM_KEY", + NTLMSSP_NEGOTIATE_DATAGRAM: "NTLMSSP_NEGOTIATE_DATAGRAM", + NTLMSSP_NEGOTIATE_SEAL: "NTLMSSP_NEGOTIATE_SEAL", + NTLMSSP_NEGOTIATE_SIGN: "NTLMSSP_NEGOTIATE_SIGN", + NTLMSSP_REQUEST_TARGET: "NTLMSSP_REQUEST_TARGET", + NTLM_NEGOTIATE_OEM: "NTLM_NEGOTIATE_OEM", + NTLMSSP_NEGOTIATE_UNICODE: "NTLMSSP_NEGOTIATE_UNICODE"} + + return nameMap[flag] +} + func FlagsToString(flags uint32) string { allFlags := [...]NegotiateFlag{ NTLMSSP_NEGOTIATE_56, @@ -166,7 +194,7 @@ func FlagsToString(flags uint32) string { var buffer bytes.Buffer for i := range allFlags { f := allFlags[i] - buffer.WriteString(fmt.Sprintf("%s: %v\n", f.String(), f.IsSet(flags))) + buffer.WriteString(fmt.Sprintf("%s: %v\n", GetFlagName(f), f.IsSet(flags))) } return buffer.String() } diff --git a/src/ntlm/ntlmv2.go b/src/ntlm/ntlmv2.go index 19fe0ac..04880c2 100644 --- a/src/ntlm/ntlmv2.go +++ b/src/ntlm/ntlmv2.go @@ -81,8 +81,42 @@ func (n *V2ServerSession) ProcessNegotiateMessage(nm *messages.Negotiate) (err e } func (n *V2ServerSession) GenerateChallengeMessage() (cm *messages.Challenge, err error) { - // TODO: Generate this challenge message - return + cm = new(messages.Challenge) + cm.Signature = []byte("NTLMSSP\x00") + cm.MessageType = uint32(2) + cm.TargetName,_ = messages.CreateBytePayload(make([]byte, 0)) + + 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) + flags = messages.NTLMSSP_NEGOTIATE_IDENTIFY.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_ALWAYS_SIGN.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_NTLM.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_DATAGRAM.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_SIGN.Set(flags) + flags = messages.NTLMSSP_REQUEST_TARGET.Set(flags) + flags = messages.NTLMSSP_NEGOTIATE_UNICODE.Set(flags) + cm.NegotiateFlags = flags + + cm.ServerChallenge = randomBytes(8) + cm.Reserved = make([]byte, 8) + + // Create the AvPairs we need + pairs := new(messages.AvPairs) + pairs.AddAvPair(messages.MsvAvNbDomainName, messages.StringToUtf16("REUTERS")) + pairs.AddAvPair(messages.MsvAvNbComputerName, messages.StringToUtf16("UKBP-CBTRMFE06")) + pairs.AddAvPair(messages.MsvAvDnsDomainName, messages.StringToUtf16("Reuters.net")) + pairs.AddAvPair(messages.MsvAvDnsComputerName, messages.StringToUtf16("ukbp-cbtrmfe06.Reuters.net")) + pairs.AddAvPair(messages.MsvAvDnsTreeName, messages.StringToUtf16("Reuters.net")) + pairs.AddAvPair(messages.MsvAvEOL, make([]byte, 0)) + cm.TargetInfo = pairs + cm.TargetInfoPayloadStruct,_ = messages.CreateBytePayload(pairs.Bytes()) + + cm.Version = &messages.VersionStruct{ProductMajorVersion: uint8(5), ProductMinorVersion: uint8(1), ProductBuild: uint16(2600), NTLMRevisionCurrent: uint8(10)} + return cm, nil } func (n *V2ServerSession) ProcessAuthenticateMessage(am *messages.Authenticate) (err error) { diff --git a/src/ntlm/ntlmv2_test.go b/src/ntlm/ntlmv2_test.go index 9e55d78..2b714b7 100644 --- a/src/ntlm/ntlmv2_test.go +++ b/src/ntlm/ntlmv2_test.go @@ -62,14 +62,14 @@ func TestNTLMv2(t *testing.T) { challengeMessageBytes, _ := hex.DecodeString("4e544c4d53535000020000000c000c003800000033828ae20123456789abcdef00000000000000002400240044000000060070170000000f53006500720076006500720002000c0044006f006d00610069006e0001000c0053006500720076006500720000000000") challengeMessage, err := messages.ParseChallengeMessage(challengeMessageBytes) if err == nil { - challengeMessage.String() + challengeMessage.String() } else { - t.Errorf("Could not parse challenge message: %s", err) + t.Errorf("Could not parse challenge message: %s", err) } err = client.ProcessChallengeMessage(challengeMessage) if err != nil { - t.Errorf("Could not process challenge message: %s", err) + t.Errorf("Could not process challenge message: %s", err) } server := new(V2ServerSession) @@ -113,4 +113,16 @@ func TestNTLMv2(t *testing.T) { checkV2Value(t, "client seal key", server.clientSealingKey, "59f600973cc4960a25480a7c196e4c58", nil) checkV2Value(t, "client seal key", server.clientSigningKey, "4788dc861b4782f35d43fd98fe1a2d39", nil) + + // Have the server generate an initial challenge message + challenge, err := server.GenerateChallengeMessage() + challenge.String() + + // Have the client process this server challenge message + client = new(V2ClientSession) + client.SetUserInfo("User", "Password", "Domain") + err = client.ProcessChallengeMessage(challenge) + if err != nil { + t.Errorf("Could not process server generated challenge message: %s", err) + } }