From b6280748c15e8d3338ae5bd375bf64c7798160a9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 2 Sep 2017 18:00:07 +0300 Subject: [PATCH] SMB1 Client: Added Login implementation using NTLM authentication --- .../Client/Enums/AuthenticationMethod.cs | 10 + SMBLibrary/Client/NTLMAuthenticationHelper.cs | 236 ++++++++++++++++++ SMBLibrary/Client/SMB1Client.cs | 129 +++++++++- SMBLibrary/SMBLibrary.csproj | 2 + 4 files changed, 366 insertions(+), 11 deletions(-) create mode 100644 SMBLibrary/Client/Enums/AuthenticationMethod.cs create mode 100644 SMBLibrary/Client/NTLMAuthenticationHelper.cs diff --git a/SMBLibrary/Client/Enums/AuthenticationMethod.cs b/SMBLibrary/Client/Enums/AuthenticationMethod.cs new file mode 100644 index 0000000..d2ceafb --- /dev/null +++ b/SMBLibrary/Client/Enums/AuthenticationMethod.cs @@ -0,0 +1,10 @@ + +namespace SMBLibrary.Client +{ + public enum AuthenticationMethod + { + NTLMv1, + NTLMv1ExtendedSessionSecurity, + NTLMv2, + } +} diff --git a/SMBLibrary/Client/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/NTLMAuthenticationHelper.cs new file mode 100644 index 0000000..5398633 --- /dev/null +++ b/SMBLibrary/Client/NTLMAuthenticationHelper.cs @@ -0,0 +1,236 @@ +/* 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.Security.Cryptography; +using SMBLibrary.Authentication.GSSAPI; +using SMBLibrary.Authentication.NTLM; +using Utilities; + +namespace SMBLibrary.Client +{ + public class NTLMAuthenticationHelper + { + public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, AuthenticationMethod authenticationMethod) + { + bool useGSSAPI = false; + if (securityBlob.Length > 0) + { + SimpleProtectedNegotiationTokenInit inputToken = null; + try + { + inputToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0) as SimpleProtectedNegotiationTokenInit; + } + catch + { + } + + if (inputToken == null || !ContainsMechanism(inputToken, GSSProvider.NTLMSSPIdentifier)) + { + return null; + } + useGSSAPI = true; + } + + NegotiateMessage negotiateMessage = new NegotiateMessage(); + negotiateMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | + NegotiateFlags.OEMEncoding | + NegotiateFlags.Sign | + NegotiateFlags.NTLMSessionSecurity | + NegotiateFlags.DomainNameSupplied | + NegotiateFlags.WorkstationNameSupplied | + NegotiateFlags.AlwaysSign | + NegotiateFlags.Version | + NegotiateFlags.Use128BitEncryption | + NegotiateFlags.KeyExchange | + NegotiateFlags.Use56BitEncryption; + + if (authenticationMethod == AuthenticationMethod.NTLMv1) + { + negotiateMessage.NegotiateFlags |= NegotiateFlags.LanManagerSessionKey; + } + else + { + negotiateMessage.NegotiateFlags |= NegotiateFlags.ExtendedSessionSecurity; + } + + negotiateMessage.Version = NTLMVersion.Server2003; + negotiateMessage.DomainName = domainName; + negotiateMessage.Workstation = Environment.MachineName; + if (useGSSAPI) + { + SimpleProtectedNegotiationTokenInit outputToken = new SimpleProtectedNegotiationTokenInit(); + outputToken.MechanismTypeList = new List(); + outputToken.MechanismTypeList.Add(GSSProvider.NTLMSSPIdentifier); + outputToken.MechanismToken = negotiateMessage.GetBytes(); + return outputToken.GetBytes(); + } + else + { + return negotiateMessage.GetBytes(); + } + } + + public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainName, string userName, string password, AuthenticationMethod authenticationMethod, out byte[] sessionKey) + { + sessionKey = null; + bool useGSSAPI = false; + SimpleProtectedNegotiationTokenResponse inputToken = null; + try + { + inputToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0) as SimpleProtectedNegotiationTokenResponse; + } + catch + { + } + + ChallengeMessage challengeMessage; + if (inputToken != null) + { + challengeMessage = GetChallengeMessage(inputToken.ResponseToken); + useGSSAPI = true; + } + else + { + challengeMessage = GetChallengeMessage(securityBlob); + } + + if (challengeMessage == null) + { + return null; + } + + DateTime time = DateTime.UtcNow; + byte[] clientChallenge = new byte[8]; + new Random().NextBytes(clientChallenge); + + AuthenticateMessage authenticateMessage = new AuthenticateMessage(); + // https://msdn.microsoft.com/en-us/library/cc236676.aspx + authenticateMessage.NegotiateFlags = NegotiateFlags.Sign | + NegotiateFlags.NTLMSessionSecurity | + NegotiateFlags.AlwaysSign | + NegotiateFlags.Version | + NegotiateFlags.Use128BitEncryption | + NegotiateFlags.Use56BitEncryption; + if ((challengeMessage.NegotiateFlags & NegotiateFlags.UnicodeEncoding) > 0) + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.UnicodeEncoding; + } + else + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.OEMEncoding; + } + + if ((challengeMessage.NegotiateFlags & NegotiateFlags.KeyExchange) > 0) + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.KeyExchange; + } + + if (authenticationMethod == AuthenticationMethod.NTLMv1) + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.LanManagerSessionKey; + } + else + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.ExtendedSessionSecurity; + } + + authenticateMessage.UserName = userName; + authenticateMessage.DomainName = domainName; + authenticateMessage.WorkStation = Environment.MachineName; + byte[] sessionBaseKey; + byte[] keyExchangeKey; + if (authenticationMethod == AuthenticationMethod.NTLMv1 || authenticationMethod == AuthenticationMethod.NTLMv1ExtendedSessionSecurity) + { + if (authenticationMethod == AuthenticationMethod.NTLMv1) + { + authenticateMessage.LmChallengeResponse = NTLMCryptography.ComputeLMv1Response(challengeMessage.ServerChallenge, password); + authenticateMessage.NtChallengeResponse = NTLMCryptography.ComputeNTLMv1Response(challengeMessage.ServerChallenge, password); + } + else // NTLMv1ExtendedSessionSecurity + { + authenticateMessage.LmChallengeResponse = ByteUtils.Concatenate(clientChallenge, new byte[16]); + authenticateMessage.NtChallengeResponse = NTLMCryptography.ComputeNTLMv1ExtendedSessionSecurityResponse(challengeMessage.ServerChallenge, clientChallenge, password); + } + // https://msdn.microsoft.com/en-us/library/cc236699.aspx + sessionBaseKey = new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)); + byte[] lmowf = NTLMCryptography.LMOWFv1(password); + keyExchangeKey = NTLMCryptography.KXKey(sessionBaseKey, authenticateMessage.NegotiateFlags, authenticateMessage.LmChallengeResponse, challengeMessage.ServerChallenge, lmowf); + } + else // NTLMv2 + { + NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(time, clientChallenge, challengeMessage.TargetInfo); + byte[] clientChallengeStructurePadded = clientChallengeStructure.GetBytesPadded(); + byte[] ntProofStr = NTLMCryptography.ComputeNTLMv2Proof(challengeMessage.ServerChallenge, clientChallengeStructurePadded, password, userName, domainName); + + authenticateMessage.LmChallengeResponse = NTLMCryptography.ComputeLMv2Response(challengeMessage.ServerChallenge, clientChallenge, password, userName, challengeMessage.TargetName); + authenticateMessage.NtChallengeResponse = ByteUtils.Concatenate(ntProofStr, clientChallengeStructurePadded); + + // https://msdn.microsoft.com/en-us/library/cc236700.aspx + byte[] responseKeyNT = NTLMCryptography.NTOWFv2(password, userName, domainName); + sessionBaseKey = new HMACMD5(responseKeyNT).ComputeHash(ntProofStr); + keyExchangeKey = sessionBaseKey; + } + authenticateMessage.Version = NTLMVersion.Server2003; + + // https://msdn.microsoft.com/en-us/library/cc236676.aspx + if ((challengeMessage.NegotiateFlags & NegotiateFlags.KeyExchange) > 0) + { + sessionKey = new byte[8]; + new Random().NextBytes(sessionKey); + authenticateMessage.EncryptedRandomSessionKey = RC4.Encrypt(keyExchangeKey, sessionKey); + } + else + { + sessionKey = keyExchangeKey; + } + + if (useGSSAPI) + { + SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); + outputToken.ResponseToken = authenticateMessage.GetBytes(); + return outputToken.GetBytes(); + } + else + { + return authenticateMessage.GetBytes(); + } + } + + private static ChallengeMessage GetChallengeMessage(byte[] messageBytes) + { + if (AuthenticationMessageUtils.IsSignatureValid(messageBytes)) + { + MessageTypeName messageType = AuthenticationMessageUtils.GetMessageType(messageBytes); + if (messageType == MessageTypeName.Challenge) + { + try + { + return new ChallengeMessage(messageBytes); + } + catch + { + return null; + } + } + } + return null; + } + + private static bool ContainsMechanism(SimpleProtectedNegotiationTokenInit token, byte[] mechanismIdentifier) + { + for (int index = 0; index < token.MechanismTypeList.Count; index++) + { + if (ByteUtils.AreByteArraysEqual(token.MechanismTypeList[index], GSSProvider.NTLMSSPIdentifier)) + { + return true; + } + } + return false; + } + } +} diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index cdd1c3c..f8038a7 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -10,6 +10,7 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; +using SMBLibrary.Authentication.NTLM; using SMBLibrary.NetBios; using SMBLibrary.SMB1; using Utilities; @@ -21,25 +22,40 @@ namespace SMBLibrary.Client public const int NetBiosOverTCPPort = 139; public const int DirectTCPPort = 445; public const string NTLanManagerDialect = "NT LM 0.12"; + + public const int MaxBufferSize = 65535; // Valid range: 512 - 65535 + public const int MaxMpxCount = 1; private SMBTransportType m_transport; private bool m_isConnected; private Socket m_clientSocket; private IAsyncResult m_currentAsyncResult; + private bool m_forceExtendedSecurity; private object m_incomingQueueLock = new object(); private List m_incomingQueue = new List(); private EventWaitHandle m_incomingQueueEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset); + private ushort m_userID; + private byte[] m_serverChallenge; + private byte[] m_securityBlob; + private byte[] m_sessionKey; + public SMB1Client() { } public bool Connect(IPAddress serverAddress, SMBTransportType transport) + { + return Connect(serverAddress, transport, true); + } + + public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity) { m_transport = transport; if (!m_isConnected) { + m_forceExtendedSecurity = forceExtendedSecurity; m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); int port; if (transport == SMBTransportType.DirectTCPTransport) @@ -62,7 +78,7 @@ namespace SMBLibrary.Client ConnectionState state = new ConnectionState(); NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; m_currentAsyncResult = m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnServerSocketReceive), state); - bool supportsCIFS = NegotiateNTLanManagerDialect(); + bool supportsCIFS = NegotiateNTLanManagerDialect(m_forceExtendedSecurity); if (!supportsCIFS) { m_clientSocket.Close(); @@ -75,6 +91,91 @@ namespace SMBLibrary.Client return m_isConnected; } + public bool Login(string domainName, string userName, string password) + { + return Login(domainName, userName, password, AuthenticationMethod.NTLMv2); + } + + public bool Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + { + if (!m_isConnected) + { + return false; + } + + if (m_serverChallenge != null) + { + SessionSetupAndXRequest request = new SessionSetupAndXRequest(); + request.MaxBufferSize = MaxBufferSize; + request.MaxMpxCount = MaxMpxCount; + request.Capabilities = ServerCapabilities.Unicode | ServerCapabilities.NTStatusCode; + request.AccountName = userName; + request.PrimaryDomain = domainName; + byte[] clientChallenge = new byte[8]; + new Random().NextBytes(clientChallenge); + if (authenticationMethod == AuthenticationMethod.NTLMv1) + { + request.OEMPassword = NTLMCryptography.ComputeLMv1Response(m_serverChallenge, password); + request.UnicodePassword = NTLMCryptography.ComputeNTLMv1Response(m_serverChallenge, password); + } + else if (authenticationMethod == AuthenticationMethod.NTLMv1ExtendedSessionSecurity) + { + // [MS-CIFS] CIFS does not support Extended Session Security because there is no mechanism in CIFS to negotiate Extended Session Security + throw new ArgumentException("SMB Extended Security must be negotiated in order for NTLMv1 Extended Session Security to be used"); + } + else // NTLMv2 + { + // Note: NTLMv2 over non-extended security session setup is not supported under Windows Vista and later which will return STATUS_INVALID_PARAMETER. + // https://msdn.microsoft.com/en-us/library/ee441701.aspx + // https://msdn.microsoft.com/en-us/library/cc236700.aspx + request.OEMPassword = NTLMCryptography.ComputeLMv2Response(m_serverChallenge, clientChallenge, password, userName, domainName); + NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(DateTime.UtcNow, clientChallenge, AVPairUtils.GetAVPairSequence(domainName, Environment.MachineName)); + byte[] temp = clientChallengeStructure.GetBytesPadded(); + byte[] proofStr = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, temp, password, userName, domainName); + request.UnicodePassword = ByteUtils.Concatenate(proofStr, temp); + } + + TrySendMessage(m_clientSocket, request); + + SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX); + if (reply != null && reply.Header.Status == NTStatus.STATUS_SUCCESS) + { + return true; + } + return false; + } + else if (m_securityBlob != null) + { + SessionSetupAndXRequestExtended request = new SessionSetupAndXRequestExtended(); + request.MaxBufferSize = MaxBufferSize; + request.MaxMpxCount = MaxMpxCount; + request.Capabilities = ServerCapabilities.Unicode | ServerCapabilities.NTStatusCode; + request.SecurityBlob = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, authenticationMethod); + TrySendMessage(m_clientSocket, request); + + SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX); + if (reply != null && reply.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && reply.Commands[0] is SessionSetupAndXResponseExtended) + { + SessionSetupAndXResponseExtended response = (SessionSetupAndXResponseExtended)reply.Commands[0]; + m_userID = reply.Header.UID; + request = new SessionSetupAndXRequestExtended(); + request.MaxBufferSize = MaxBufferSize; + request.MaxMpxCount = MaxMpxCount; + request.Capabilities = ServerCapabilities.Unicode | ServerCapabilities.NTStatusCode | ServerCapabilities.ExtendedSecurity; + + request.SecurityBlob = NTLMAuthenticationHelper.GetAuthenticateMessage(response.SecurityBlob, domainName, userName, password, authenticationMethod, out m_sessionKey); + TrySendMessage(m_clientSocket, request); + + reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX); + if (reply != null && reply.Header.Status == NTStatus.STATUS_SUCCESS) + { + return true; + } + } + } + return false; + } + public void Disconnect() { if (m_isConnected) @@ -84,7 +185,7 @@ namespace SMBLibrary.Client } } - private bool NegotiateNTLanManagerDialect() + private bool NegotiateNTLanManagerDialect(bool forceExtendedSecurity) { if (m_transport == SMBTransportType.NetBiosOverTCP) { @@ -95,6 +196,7 @@ namespace SMBLibrary.Client } NegotiateRequest request = new NegotiateRequest(); request.Dialects.Add(NTLanManagerDialect); + TrySendMessage(m_clientSocket, request); SMB1Message reply = WaitForMessage(CommandName.SMB_COM_NEGOTIATE); if (reply == null) @@ -102,14 +204,16 @@ namespace SMBLibrary.Client return false; } - if (reply.Commands[0] is NegotiateResponse) + if (reply.Commands[0] is NegotiateResponse && !forceExtendedSecurity) { NegotiateResponse response = (NegotiateResponse)reply.Commands[0]; + m_serverChallenge = response.Challenge; return true; } else if (reply.Commands[0] is NegotiateResponseExtended) { NegotiateResponseExtended response = (NegotiateResponseExtended)reply.Commands[0]; + m_securityBlob = response.SecurityBlob; return true; } else @@ -200,7 +304,6 @@ namespace SMBLibrary.Client } } - private void ProcessPacket(SessionPacket packet, ConnectionState state) { if (packet is SessionKeepAlivePacket && m_transport == SMBTransportType.NetBiosOverTCP) @@ -263,30 +366,34 @@ namespace SMBLibrary.Client return null; } - public void Log(string message) + private void Log(string message) { System.Diagnostics.Debug.Print(message); } - public static void TrySendMessage(Socket serverSocket, SMB1Command request) + public void TrySendMessage(Socket socket, SMB1Command request) { SMB1Message message = new SMB1Message(); + message.Header.UnicodeFlag = true; + message.Header.ExtendedSecurityFlag = m_forceExtendedSecurity; + message.Header.Flags2 |= HeaderFlags2.LongNamesAllowed | HeaderFlags2.LongNameUsed | HeaderFlags2.NTStatusCode; + message.Header.UID = m_userID; message.Commands.Add(request); - TrySendMessage(serverSocket, message); + TrySendMessage(socket, message); } - public static void TrySendMessage(Socket serverSocket, SMB1Message message) + public static void TrySendMessage(Socket socket, SMB1Message message) { SessionMessagePacket packet = new SessionMessagePacket(); packet.Trailer = message.GetBytes(); - TrySendPacket(serverSocket, packet); + TrySendPacket(socket, packet); } - public static void TrySendPacket(Socket serverSocket, SessionPacket response) + public static void TrySendPacket(Socket socket, SessionPacket response) { try { - serverSocket.Send(response.GetBytes()); + socket.Send(response.GetBytes()); } catch (SocketException) { diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 9a61b00..87c2a16 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -55,6 +55,8 @@ + +