diff --git a/SMBLibrary/Authentication/AuthenticateMessage/AuthenticateMessage.cs b/SMBLibrary/Authentication/AuthenticateMessage/AuthenticateMessage.cs index 79e50a9..1604a90 100644 --- a/SMBLibrary/Authentication/AuthenticateMessage/AuthenticateMessage.cs +++ b/SMBLibrary/Authentication/AuthenticateMessage/AuthenticateMessage.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, @@ -16,6 +16,8 @@ namespace SMBLibrary.Authentication /// public class AuthenticateMessage { + public const string ValidSignature = "NTLMSSP\0"; + public string Signature; // 8 bytes public MessageTypeName MessageType; public byte[] LmChallengeResponse; @@ -30,7 +32,7 @@ namespace SMBLibrary.Authentication public AuthenticateMessage() { - Signature = AuthenticationMessageUtils.ValidSignature; + Signature = ValidSignature; MessageType = MessageTypeName.Authenticate; DomainName = String.Empty; UserName = String.Empty; @@ -66,7 +68,7 @@ namespace SMBLibrary.Authentication int payloadLength = LmChallengeResponse.Length + NtChallengeResponse.Length + DomainName.Length * 2 + UserName.Length * 2 + WorkStation.Length * 2 + EncryptedRandomSessionKey.Length; byte[] buffer = new byte[fixedLength + payloadLength]; - ByteWriter.WriteAnsiString(buffer, 0, AuthenticationMessageUtils.ValidSignature, 8); + ByteWriter.WriteAnsiString(buffer, 0, ValidSignature, 8); LittleEndianWriter.WriteUInt32(buffer, 8, (uint)MessageType); LittleEndianWriter.WriteUInt32(buffer, 60, (uint)NegotiateFlags); if ((NegotiateFlags & NegotiateFlags.NegotiateVersion) > 0) diff --git a/SMBLibrary/Authentication/AuthenticateMessage/AuthenticationMessageUtils.cs b/SMBLibrary/Authentication/AuthenticateMessage/AuthenticationMessageUtils.cs index 78b6385..1b463f1 100644 --- a/SMBLibrary/Authentication/AuthenticateMessage/AuthenticationMessageUtils.cs +++ b/SMBLibrary/Authentication/AuthenticateMessage/AuthenticationMessageUtils.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, @@ -13,8 +13,6 @@ namespace SMBLibrary.Authentication { public class AuthenticationMessageUtils { - public const string ValidSignature = "NTLMSSP\0"; - public static string ReadAnsiStringBufferPointer(byte[] buffer, int offset) { byte[] bytes = ReadBufferPointer(buffer, offset); @@ -52,8 +50,12 @@ namespace SMBLibrary.Authentication public static bool IsSignatureValid(byte[] messageBytes) { + if (messageBytes.Length < 8) + { + return false; + } string signature = ByteReader.ReadAnsiString(messageBytes, 0, 8); - return (signature == ValidSignature); + return (signature == AuthenticateMessage.ValidSignature); } public static MessageTypeName GetMessageType(byte[] messageBytes) diff --git a/SMBLibrary/Authentication/AuthenticateMessage/ChallengeMessage.cs b/SMBLibrary/Authentication/AuthenticateMessage/ChallengeMessage.cs index 31797e3..f70dcb7 100644 --- a/SMBLibrary/Authentication/AuthenticateMessage/ChallengeMessage.cs +++ b/SMBLibrary/Authentication/AuthenticateMessage/ChallengeMessage.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, @@ -27,7 +27,7 @@ namespace SMBLibrary.Authentication public ChallengeMessage() { - Signature = AuthenticationMessageUtils.ValidSignature; + Signature = AuthenticateMessage.ValidSignature; MessageType = MessageTypeName.Challenge; } @@ -56,7 +56,7 @@ namespace SMBLibrary.Authentication int payloadLength = TargetName.Length * 2 + TargetInfo.Length; byte[] buffer = new byte[fixedLength + payloadLength]; - ByteWriter.WriteAnsiString(buffer, 0, AuthenticationMessageUtils.ValidSignature, 8); + ByteWriter.WriteAnsiString(buffer, 0, AuthenticateMessage.ValidSignature, 8); LittleEndianWriter.WriteUInt32(buffer, 8, (uint)MessageType); LittleEndianWriter.WriteUInt32(buffer, 20, (uint)NegotiateFlags); ByteWriter.WriteBytes(buffer, 24, ServerChallenge); diff --git a/SMBLibrary/Authentication/AuthenticateMessage/NegotiateMessage.cs b/SMBLibrary/Authentication/AuthenticateMessage/NegotiateMessage.cs index 10f58ad..b48d380 100644 --- a/SMBLibrary/Authentication/AuthenticateMessage/NegotiateMessage.cs +++ b/SMBLibrary/Authentication/AuthenticateMessage/NegotiateMessage.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, @@ -25,7 +25,7 @@ namespace SMBLibrary.Authentication public NegotiateMessage() { - Signature = AuthenticationMessageUtils.ValidSignature; + Signature = AuthenticateMessage.ValidSignature; MessageType = MessageTypeName.Negotiate; DomainName = String.Empty; Workstation = String.Empty; @@ -53,7 +53,7 @@ namespace SMBLibrary.Authentication } int payloadLength = DomainName.Length * 2 + Workstation.Length * 2; byte[] buffer = new byte[fixedLength + payloadLength]; - ByteWriter.WriteAnsiString(buffer, 0, AuthenticationMessageUtils.ValidSignature, 8); + ByteWriter.WriteAnsiString(buffer, 0, AuthenticateMessage.ValidSignature, 8); LittleEndianWriter.WriteUInt32(buffer, 8, (uint)MessageType); LittleEndianWriter.WriteUInt32(buffer, 12, (uint)NegotiateFlags); diff --git a/SMBLibrary/Authentication/GSSAPI/DerEncodingHelper.cs b/SMBLibrary/Authentication/GSSAPI/DerEncodingHelper.cs new file mode 100644 index 0000000..14d6618 --- /dev/null +++ b/SMBLibrary/Authentication/GSSAPI/DerEncodingHelper.cs @@ -0,0 +1,82 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using Utilities; + +namespace SMBLibrary.Authentication +{ + public enum DerEncodingTag : byte + { + ByteArray = 0x04, + ObjectIdentifier = 0x06, + Enum = 0x0A, + Sequence = 0x30, + } + + public class DerEncodingHelper + { + public static int ReadLength(byte[] buffer, ref int offset) + { + int length = ByteReader.ReadByte(buffer, ref offset); + if (length >= 0x80) + { + int lengthFieldSize = (length & 0x7F); + byte[] lengthField = ByteReader.ReadBytes(buffer, ref offset, lengthFieldSize); + length = 0; + foreach (byte value in lengthField) + { + length *= Byte.MaxValue; + length += value; + } + } + return length; + } + + public static void WriteLength(byte[] buffer, ref int offset, int length) + { + if (length >= 0x80) + { + List values = new List(); + do + { + byte value = (byte)(length % Byte.MaxValue); + values.Add(value); + length = value / Byte.MaxValue; + } + while (length > 0); + values.Reverse(); + byte[] lengthField = values.ToArray(); + ByteWriter.WriteByte(buffer, ref offset, (byte)(0x80 | lengthField.Length)); + ByteWriter.WriteBytes(buffer, ref offset, lengthField); + } + else + { + ByteWriter.WriteByte(buffer, ref offset, (byte)length); + } + } + + public static int GetLengthFieldSize(int length) + { + if (length >= 0x80) + { + int result = 1; + do + { + length = length / Byte.MaxValue; + result++; + } + while(length > 0); + return result; + } + else + { + return 1; + } + } + } +} diff --git a/SMBLibrary/Authentication/GSSAPI/GSSAPIHelper.cs b/SMBLibrary/Authentication/GSSAPI/GSSAPIHelper.cs new file mode 100644 index 0000000..54c041d --- /dev/null +++ b/SMBLibrary/Authentication/GSSAPI/GSSAPIHelper.cs @@ -0,0 +1,62 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using Utilities; + +namespace SMBLibrary.Authentication +{ + public class GSSAPIHelper + { + public static readonly byte[] NTLMSSPIdentifier = new byte[] { 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a }; + + /// + /// https://msdn.microsoft.com/en-us/library/ms995330.aspx + /// + public static byte[] GetNTLMSSPMessage(byte[] tokenBytes) + { + SimpleProtectedNegotiationToken token = SimpleProtectedNegotiationToken.ReadToken(tokenBytes, 0); + if (token != null) + { + if (token is SimpleProtectedNegotiationTokenInit) + { + List tokens = ((SimpleProtectedNegotiationTokenInit)token).Tokens; + foreach (TokenInitEntry entry in tokens) + { + foreach (byte[] identifier in entry.MechanismTypeList) + { + if (ByteUtils.AreByteArraysEqual(identifier, NTLMSSPIdentifier)) + { + return entry.MechanismToken; + } + } + } + } + else + { + List tokens = ((SimpleProtectedNegotiationTokenResponse)token).Tokens; + if (tokens.Count > 0) + { + return tokens[0].ResponseToken; + } + } + } + return null; + } + + public static byte[] GetGSSTokenResponseBytesFromNTLMSSPMessage(byte[] messageBytes) + { + SimpleProtectedNegotiationTokenResponse token = new SimpleProtectedNegotiationTokenResponse(); + TokenResponseEntry entry = new TokenResponseEntry(); + entry.NegState = NegState.AcceptIncomplete; + entry.SupportedMechanism = NTLMSSPIdentifier; + entry.ResponseToken = messageBytes; + token.Tokens.Add(entry); + return token.GetBytes(); + } + } +} diff --git a/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationToken.cs b/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationToken.cs new file mode 100644 index 0000000..f2dd9f5 --- /dev/null +++ b/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationToken.cs @@ -0,0 +1,52 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using System.Text; +using Utilities; + +namespace SMBLibrary.Authentication +{ + public abstract class SimpleProtectedNegotiationToken + { + public const byte ApplicationTag = 0x60; + + public static readonly byte[] SPNEGOIdentifier = new byte[] { 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02 }; + + public abstract byte[] GetBytes(); + + /// + /// https://tools.ietf.org/html/rfc2743 + /// + public static SimpleProtectedNegotiationToken ReadToken(byte[] tokenBytes, int offset) + { + byte tag = ByteReader.ReadByte(tokenBytes, ref offset); + if (tag == ApplicationTag) + { + // when an InitToken is sent, it is prepended by an Application Constructed Object specifier (0x60), + // and the OID for SPNEGO (see value in OID table above). This is the generic GSSAPI header. + int tokenLength = DerEncodingHelper.ReadLength(tokenBytes, ref offset); + tag = ByteReader.ReadByte(tokenBytes, ref offset); + if (tag == (byte)DerEncodingTag.ObjectIdentifier) + { + int objectIdentifierLength = DerEncodingHelper.ReadLength(tokenBytes, ref offset); + byte[] objectIdentifier = ByteReader.ReadBytes(tokenBytes, ref offset, objectIdentifierLength); + if (ByteUtils.AreByteArraysEqual(objectIdentifier, SPNEGOIdentifier)) + { + return new SimpleProtectedNegotiationTokenInit(tokenBytes, offset); + } + } + } + else if (tag == SimpleProtectedNegotiationTokenResponse.NegTokenRespTag) + { + offset--; + return new SimpleProtectedNegotiationTokenResponse(tokenBytes, offset); + } + return null; + } + } +} diff --git a/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationTokenInit.cs b/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationTokenInit.cs new file mode 100644 index 0000000..b2dd4cd --- /dev/null +++ b/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationTokenInit.cs @@ -0,0 +1,204 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using System.IO; +using Utilities; + +namespace SMBLibrary.Authentication +{ + public class TokenInitEntry + { + public List MechanismTypeList = new List(); // Optional + // reqFlags - Optional, Unused + public byte[] MechanismToken = new byte[0]; // Optional + // mechListMIC - Optional, Unused + } + + /// + /// RFC 4178 - negTokenInit + /// + public class SimpleProtectedNegotiationTokenInit : SimpleProtectedNegotiationToken + { + public const byte NegTokenInitTag = 0xA0; + public const byte MechanismTypeListTag = 0xA0; + public const byte RequiredFlagsTag = 0xA1; + public const byte MechanismTokenTag = 0xA2; + public const byte MechanismListMICTag = 0xA3; + + public List Tokens = new List(); + + public SimpleProtectedNegotiationTokenInit(byte[] buffer, int offset) + { + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != NegTokenInitTag) + { + throw new InvalidDataException(); + } + int constructionLength = DerEncodingHelper.ReadLength(buffer, ref offset); + int sequenceEndOffset = offset + constructionLength; + tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.Sequence) + { + throw new InvalidDataException(); + } + while (offset < sequenceEndOffset) + { + int entryLength = DerEncodingHelper.ReadLength(buffer, ref offset); + int entryEndOffset = offset + entryLength; + TokenInitEntry entry = new TokenInitEntry(); + while (offset < entryEndOffset) + { + tag = ByteReader.ReadByte(buffer, ref offset); + if (tag == MechanismTypeListTag) + { + entry.MechanismTypeList = ReadMechanismTypeList(buffer, ref offset); + } + else if (tag == MechanismTokenTag) + { + entry.MechanismToken = ReadMechanismToken(buffer, ref offset); + } + else + { + throw new InvalidDataException(); + } + } + Tokens.Add(entry); + } + } + + public override byte[] GetBytes() + { + int sequenceLength = 0; + foreach (TokenInitEntry token in Tokens) + { + int entryLength = GetEntryLength(token); + sequenceLength += DerEncodingHelper.GetLengthFieldSize(entryLength) + entryLength; + } + int constructionLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(1 + sequenceLength); + int bufferSize = 1 + constructionLengthFieldSize + 1 + sequenceLength; + byte[] buffer = new byte[bufferSize]; + int offset = 0; + ByteWriter.WriteByte(buffer, ref offset, NegTokenInitTag); + DerEncodingHelper.WriteLength(buffer, ref offset, 1 + sequenceLength); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.Sequence); + foreach (TokenInitEntry token in Tokens) + { + int entryLength = GetEntryLength(token); + DerEncodingHelper.WriteLength(buffer, ref offset, entryLength); + if (token.MechanismTypeList != null) + { + WriteMechanismTypeList(buffer, ref offset, token.MechanismTypeList); + } + if (token.MechanismToken != null) + { + WriteMechanismToken(buffer, ref offset, token.MechanismToken); + } + } + return buffer; + } + + public int GetEntryLength(TokenInitEntry token) + { + int result = 0; + if (token.MechanismTypeList != null) + { + int typeListSequenceLength = GetSequenceLength(token.MechanismTypeList); + int constructionLenthFieldSize = DerEncodingHelper.GetLengthFieldSize(1 + typeListSequenceLength); + int typeListLength = 1 + constructionLenthFieldSize + 1 + typeListSequenceLength; + result += typeListLength; + } + if (token.MechanismToken != null) + { + int byteArrayFieldSize = DerEncodingHelper.GetLengthFieldSize(token.MechanismToken.Length); + int constructionLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(1 + byteArrayFieldSize + token.MechanismToken.Length); + int tokenLength = 1 + constructionLengthFieldSize + 1 + byteArrayFieldSize + token.MechanismToken.Length; + result += tokenLength; + } + return result; + } + + private static List ReadMechanismTypeList(byte[] buffer, ref int offset) + { + List result = new List(); + int constructionLength = DerEncodingHelper.ReadLength(buffer, ref offset); + int sequenceEndOffset = offset + constructionLength; + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.Sequence) + { + throw new InvalidDataException(); + } + while (offset < sequenceEndOffset) + { + int entryLength = DerEncodingHelper.ReadLength(buffer, ref offset); + int entryEndOffset = offset + entryLength; + tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.ObjectIdentifier) + { + throw new InvalidDataException(); + } + int mechanismTypeLength = DerEncodingHelper.ReadLength(buffer, ref offset); + byte[] mechanismType = ByteReader.ReadBytes(buffer, ref offset, mechanismTypeLength); + result.Add(mechanismType); + } + return result; + } + + private static byte[] ReadMechanismToken(byte[] buffer, ref int offset) + { + int constructionLength = DerEncodingHelper.ReadLength(buffer, ref offset); + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.ByteArray) + { + throw new InvalidDataException(); + } + int mechanismTokenLength = DerEncodingHelper.ReadLength(buffer, ref offset); + byte[] token = ByteReader.ReadBytes(buffer, ref offset, mechanismTokenLength); + return token; + } + + private static int GetSequenceLength(List mechanismTypeList) + { + int sequenceLength = 0; + foreach (byte[] mechanismType in mechanismTypeList) + { + int lengthFieldSize = DerEncodingHelper.GetLengthFieldSize(mechanismType.Length); + int entryLength = 1 + lengthFieldSize + mechanismType.Length; + sequenceLength += DerEncodingHelper.GetLengthFieldSize(entryLength) + entryLength; + } + return sequenceLength; + } + + private static void WriteMechanismTypeList(byte[] buffer, ref int offset, List mechanismTypeList) + { + int sequenceLength = GetSequenceLength(mechanismTypeList); + ByteWriter.WriteByte(buffer, ref offset, MechanismTypeListTag); + DerEncodingHelper.WriteLength(buffer, ref offset, 1 + sequenceLength); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.Sequence); + foreach (byte[] mechanismType in mechanismTypeList) + { + int lengthFieldSize = DerEncodingHelper.GetLengthFieldSize(mechanismType.Length); + int entryLength = 1 + lengthFieldSize + mechanismType.Length; + + DerEncodingHelper.WriteLength(buffer, ref offset, entryLength); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.ObjectIdentifier); + DerEncodingHelper.WriteLength(buffer, ref offset, mechanismType.Length); + ByteWriter.WriteBytes(buffer, ref offset, mechanismType); + } + } + + private static void WriteMechanismToken(byte[] buffer, ref int offset, byte[] mechanismToken) + { + int constructionLength = 1 + DerEncodingHelper.GetLengthFieldSize(mechanismToken.Length) + mechanismToken.Length; + ByteWriter.WriteByte(buffer, ref offset, MechanismTokenTag); + DerEncodingHelper.WriteLength(buffer, ref offset, constructionLength); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.ByteArray); + DerEncodingHelper.WriteLength(buffer, ref offset, mechanismToken.Length); + ByteWriter.WriteBytes(buffer, ref offset, mechanismToken); + } + } +} diff --git a/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationTokenResponse.cs b/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationTokenResponse.cs new file mode 100644 index 0000000..5b300ca --- /dev/null +++ b/SMBLibrary/Authentication/GSSAPI/SimpleProtectedNegotiationTokenResponse.cs @@ -0,0 +1,215 @@ +/* Copyright (C) 2017 Tal Aloni . All rights reserved. + * + * You can redistribute this program and/or modify it under the terms of + * the GNU Lesser Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Collections.Generic; +using System.IO; +using Utilities; + +namespace SMBLibrary.Authentication +{ + public enum NegState : byte + { + AcceptCompleted = 0x00, + AcceptIncomplete = 0x01, + Reject = 0x02, + RequestMic = 0x03, + } + + public class TokenResponseEntry + { + public NegState? NegState; // Optional + public byte[] SupportedMechanism; // Optional + public byte[] ResponseToken; // Optional + // mechListMIC - Optional, Unused + } + + /// + /// RFC 4178 - negTokenInit + /// + public class SimpleProtectedNegotiationTokenResponse : SimpleProtectedNegotiationToken + { + public const byte NegTokenRespTag = 0xA1; + public const byte NegStateTag = 0xA0; + public const byte SupportedMechanismTag = 0xA1; + public const byte ResponseTokenTag = 0xA2; + public const byte MechanismListMICTag = 0xA3; + + public List Tokens = new List(); + + public SimpleProtectedNegotiationTokenResponse() + { + } + + public SimpleProtectedNegotiationTokenResponse(byte[] buffer, int offset) + { + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != NegTokenRespTag) + { + throw new InvalidDataException(); + } + int constuctionLength = DerEncodingHelper.ReadLength(buffer, ref offset); + int sequenceEndOffset = offset + constuctionLength; + tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.Sequence) + { + throw new InvalidDataException(); + } + while (offset < sequenceEndOffset) + { + int entryLength = DerEncodingHelper.ReadLength(buffer, ref offset); + int entryEndOffset = offset + entryLength; + TokenResponseEntry entry = new TokenResponseEntry(); + while (offset < entryEndOffset) + { + tag = ByteReader.ReadByte(buffer, ref offset); + if (tag == NegStateTag) + { + entry.NegState = ReadNegState(buffer, ref offset); + } + else if (tag == SupportedMechanismTag) + { + entry.SupportedMechanism = ReadSupportedMechanism(buffer, ref offset); + } + else if (tag == ResponseTokenTag) + { + entry.ResponseToken = ReadResponseToken(buffer, ref offset); + } + else + { + throw new InvalidDataException(); + } + } + Tokens.Add(entry); + } + } + + public override byte[] GetBytes() + { + int sequenceLength = 0; + foreach (TokenResponseEntry token in Tokens) + { + int entryLength = GetEntryLength(token); + sequenceLength += DerEncodingHelper.GetLengthFieldSize(entryLength) + entryLength; + } + int constructionLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(1 + sequenceLength); + int bufferSize = 1 + constructionLengthFieldSize + 1 + sequenceLength; + byte[] buffer = new byte[bufferSize]; + int offset = 0; + ByteWriter.WriteByte(buffer, ref offset, NegTokenRespTag); + DerEncodingHelper.WriteLength(buffer, ref offset, 1 + sequenceLength); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.Sequence); + foreach (TokenResponseEntry token in Tokens) + { + int entryLength = GetEntryLength(token); + DerEncodingHelper.WriteLength(buffer, ref offset, entryLength); + if (token.NegState.HasValue) + { + WriteNegState(buffer, ref offset, token.NegState.Value); + } + if (token.SupportedMechanism != null) + { + WriteSupportedMechanism(buffer, ref offset, token.SupportedMechanism); + } + if (token.ResponseToken != null) + { + WriteResponseToken(buffer, ref offset, token.ResponseToken); + } + } + return buffer; + } + + private static NegState ReadNegState(byte[] buffer, ref int offset) + { + int length = DerEncodingHelper.ReadLength(buffer, ref offset); + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.Enum) + { + throw new InvalidDataException(); + } + length = DerEncodingHelper.ReadLength(buffer, ref offset); + return (NegState)ByteReader.ReadByte(buffer, ref offset); + } + + private static byte[] ReadSupportedMechanism(byte[] buffer, ref int offset) + { + int constructionLength = DerEncodingHelper.ReadLength(buffer, ref offset); + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.ObjectIdentifier) + { + throw new InvalidDataException(); + } + int length = DerEncodingHelper.ReadLength(buffer, ref offset); + return ByteReader.ReadBytes(buffer, ref offset, length); + } + + private static byte[] ReadResponseToken(byte[] buffer, ref int offset) + { + int constructionLength = DerEncodingHelper.ReadLength(buffer, ref offset); + byte tag = ByteReader.ReadByte(buffer, ref offset); + if (tag != (byte)DerEncodingTag.ByteArray) + { + throw new InvalidDataException(); + } + int length = DerEncodingHelper.ReadLength(buffer, ref offset); + return ByteReader.ReadBytes(buffer, ref offset, length); + } + + private static int GetEntryLength(TokenResponseEntry token) + { + int result = 0; + if (token.NegState.HasValue) + { + int negStateLength = 5; + result += negStateLength; + } + if (token.SupportedMechanism != null) + { + int supportedMechanismLength2FieldSize = DerEncodingHelper.GetLengthFieldSize(token.SupportedMechanism.Length); + int supportedMechanismLength1FieldSize = DerEncodingHelper.GetLengthFieldSize(1 + supportedMechanismLength2FieldSize + token.SupportedMechanism.Length); + int supportedMechanismLength = 1 + supportedMechanismLength1FieldSize + 1 + supportedMechanismLength2FieldSize + token.SupportedMechanism.Length; + result += supportedMechanismLength; + } + if (token.ResponseToken != null) + { + int responseToken2FieldSize = DerEncodingHelper.GetLengthFieldSize(token.ResponseToken.Length); + int responseToken1FieldSize = DerEncodingHelper.GetLengthFieldSize(1 + responseToken2FieldSize + token.ResponseToken.Length); + int responseTokenLength = 1 + responseToken1FieldSize + 1 + responseToken2FieldSize + token.ResponseToken.Length; + result += responseTokenLength; + } + return result; + } + + private static void WriteNegState(byte[] buffer, ref int offset, NegState negState) + { + ByteWriter.WriteByte(buffer, ref offset, NegStateTag); + DerEncodingHelper.WriteLength(buffer, ref offset, 3); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.Enum); + DerEncodingHelper.WriteLength(buffer, ref offset, 1); + ByteWriter.WriteByte(buffer, ref offset, (byte)negState); + } + + private static void WriteSupportedMechanism(byte[] buffer, ref int offset, byte[] supportedMechanism) + { + int supportedMechanismLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(supportedMechanism.Length); + ByteWriter.WriteByte(buffer, ref offset, SupportedMechanismTag); + DerEncodingHelper.WriteLength(buffer, ref offset, 1 + supportedMechanismLengthFieldSize + supportedMechanism.Length); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.ObjectIdentifier); + DerEncodingHelper.WriteLength(buffer, ref offset, supportedMechanism.Length); + ByteWriter.WriteBytes(buffer, ref offset, supportedMechanism); + } + + private static void WriteResponseToken(byte[] buffer, ref int offset, byte[] responseToken) + { + int responseTokenLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(responseToken.Length); + ByteWriter.WriteByte(buffer, ref offset, ResponseTokenTag); + DerEncodingHelper.WriteLength(buffer, ref offset, 1 + responseTokenLengthFieldSize + responseToken.Length); + ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.ByteArray); + DerEncodingHelper.WriteLength(buffer, ref offset, responseToken.Length); + ByteWriter.WriteBytes(buffer, ref offset, responseToken); + } + } +} diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 7e0bdcf..393972a 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -41,6 +41,11 @@ + + + + + diff --git a/SMBLibrary/Server/ResponseHelpers/NegotiateHelper.cs b/SMBLibrary/Server/ResponseHelpers/NegotiateHelper.cs index 79dbd17..708decf 100644 --- a/SMBLibrary/Server/ResponseHelpers/NegotiateHelper.cs +++ b/SMBLibrary/Server/ResponseHelpers/NegotiateHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. * * You can redistribute this program and/or modify it under the terms of * the GNU Lesser Public License as published by the Free Software Foundation, @@ -119,18 +119,32 @@ namespace SMBLibrary.Server { SessionSetupAndXResponseExtended response = new SessionSetupAndXResponseExtended(); - bool isValid = AuthenticationMessageUtils.IsSignatureValid(request.SecurityBlob); - if (!isValid) + // [MS-SMB] The Windows GSS implementation supports raw Kerberos / NTLM messages in the SecurityBlob + byte[] messageBytes = request.SecurityBlob; + bool isRawMessage = true; + if (!AuthenticationMessageUtils.IsSignatureValid(messageBytes)) + { + messageBytes = GSSAPIHelper.GetNTLMSSPMessage(request.SecurityBlob); + isRawMessage = false; + } + if (!AuthenticationMessageUtils.IsSignatureValid(messageBytes)) { header.Status = NTStatus.STATUS_NOT_IMPLEMENTED; return new ErrorResponse(CommandName.SMB_COM_SESSION_SETUP_ANDX); } - MessageTypeName messageType = AuthenticationMessageUtils.GetMessageType(request.SecurityBlob); + MessageTypeName messageType = AuthenticationMessageUtils.GetMessageType(messageBytes); if (messageType == MessageTypeName.Negotiate) { - byte[] challengeMessageBytes = users.GetChallengeMessageBytes(request.SecurityBlob); - response.SecurityBlob = challengeMessageBytes; + byte[] challengeMessageBytes = users.GetChallengeMessageBytes(messageBytes); + if (isRawMessage) + { + response.SecurityBlob = challengeMessageBytes; + } + else + { + response.SecurityBlob = GSSAPIHelper.GetGSSTokenResponseBytesFromNTLMSSPMessage(challengeMessageBytes); + } header.Status = NTStatus.STATUS_MORE_PROCESSING_REQUIRED; } else // MessageTypeName.Authenticate @@ -138,7 +152,7 @@ namespace SMBLibrary.Server User user; try { - user = users.Authenticate(request.SecurityBlob); + user = users.Authenticate(messageBytes); } catch(EmptyPasswordNotAllowedException) {