123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692 |
- /* Copyright (C) 2014-2020 Tal Aloni <tal.aloni.il@gmail.com>. 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.Diagnostics;
- using System.Net;
- using System.Net.Sockets;
- using System.Threading;
- using SMBLibrary.Authentication.NTLM;
- using SMBLibrary.NetBios;
- using SMBLibrary.Services;
- using SMBLibrary.SMB1;
- using Utilities;
- namespace SMBLibrary.Client
- {
- public class SMB1Client : ISMBClient
- {
- private const string NTLanManagerDialect = "NT LM 0.12";
-
- public static readonly int NetBiosOverTCPPort = 139;
- public static readonly int DirectTCPPort = 445;
- private static readonly ushort ClientMaxBufferSize = 65535; // Valid range: 512 - 65535
- private static readonly ushort ClientMaxMpxCount = 1;
- private SMBTransportType m_transport;
- private bool m_isConnected;
- private bool m_isLoggedIn;
- private Socket m_clientSocket;
- private bool m_forceExtendedSecurity;
- private bool m_unicode;
- private bool m_largeFiles;
- private bool m_infoLevelPassthrough;
- private bool m_largeRead;
- private bool m_largeWrite;
- private uint m_serverMaxBufferSize;
- private ushort m_maxMpxCount;
- private object m_incomingQueueLock = new object();
- private List<SMB1Message> m_incomingQueue = new List<SMB1Message>();
- private EventWaitHandle m_incomingQueueEventHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
- private SessionPacket m_sessionResponsePacket;
- private EventWaitHandle m_sessionResponseEventHandle = 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;
- int port;
- if (transport == SMBTransportType.NetBiosOverTCP)
- {
- port = NetBiosOverTCPPort;
- }
- else
- {
- port = DirectTCPPort;
- }
- if (!ConnectSocket(serverAddress, port))
- {
- return false;
- }
-
- if (transport == SMBTransportType.NetBiosOverTCP)
- {
- SessionRequestPacket sessionRequest = new SessionRequestPacket();
- sessionRequest.CalledName = NetBiosUtils.GetMSNetBiosName("*SMBSERVER", NetBiosSuffix.FileServiceService);
- sessionRequest.CallingName = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.WorkstationService);
- TrySendPacket(m_clientSocket, sessionRequest);
- SessionPacket sessionResponsePacket = WaitForSessionResponsePacket();
- if (!(sessionResponsePacket is PositiveSessionResponsePacket))
- {
- m_clientSocket.Disconnect(false);
- if (!ConnectSocket(serverAddress, port))
- {
- return false;
- }
- NameServiceClient nameServiceClient = new NameServiceClient(serverAddress);
- string serverName = nameServiceClient.GetServerName();
- if (serverName == null)
- {
- return false;
- }
- sessionRequest.CalledName = serverName;
- TrySendPacket(m_clientSocket, sessionRequest);
- sessionResponsePacket = WaitForSessionResponsePacket();
- if (!(sessionResponsePacket is PositiveSessionResponsePacket))
- {
- return false;
- }
- }
- }
- bool supportsDialect = NegotiateDialect(m_forceExtendedSecurity);
- if (!supportsDialect)
- {
- m_clientSocket.Close();
- }
- else
- {
- m_isConnected = true;
- }
- }
- return m_isConnected;
- }
- private bool ConnectSocket(IPAddress serverAddress, int port)
- {
- m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
-
- try
- {
- m_clientSocket.Connect(serverAddress, port);
- }
- catch (SocketException)
- {
- return false;
- }
- ConnectionState state = new ConnectionState(m_clientSocket);
- NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer;
- m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state);
- return true;
- }
- public void Disconnect()
- {
- if (m_isConnected)
- {
- m_clientSocket.Disconnect(false);
- m_isConnected = false;
- }
- }
- private bool NegotiateDialect(bool forceExtendedSecurity)
- {
- NegotiateRequest request = new NegotiateRequest();
- request.Dialects.Add(NTLanManagerDialect);
- TrySendMessage(request);
- SMB1Message reply = WaitForMessage(CommandName.SMB_COM_NEGOTIATE);
- if (reply == null)
- {
- return false;
- }
- if (reply.Commands[0] is NegotiateResponse && !forceExtendedSecurity)
- {
- NegotiateResponse response = (NegotiateResponse)reply.Commands[0];
- m_unicode = ((response.Capabilities & Capabilities.Unicode) > 0);
- m_largeFiles = ((response.Capabilities & Capabilities.LargeFiles) > 0);
- bool ntSMB = ((response.Capabilities & Capabilities.NTSMB) > 0);
- bool rpc = ((response.Capabilities & Capabilities.RpcRemoteApi) > 0);
- bool ntStatusCode = ((response.Capabilities & Capabilities.NTStatusCode) > 0);
- m_infoLevelPassthrough = ((response.Capabilities & Capabilities.InfoLevelPassthrough) > 0);
- m_largeRead = ((response.Capabilities & Capabilities.LargeRead) > 0);
- m_largeWrite = ((response.Capabilities & Capabilities.LargeWrite) > 0);
- m_serverMaxBufferSize = response.MaxBufferSize;
- m_maxMpxCount = Math.Min(response.MaxMpxCount, ClientMaxMpxCount);
- m_serverChallenge = response.Challenge;
- return ntSMB && rpc && ntStatusCode;
- }
- else if (reply.Commands[0] is NegotiateResponseExtended)
- {
- NegotiateResponseExtended response = (NegotiateResponseExtended)reply.Commands[0];
- m_unicode = ((response.Capabilities & Capabilities.Unicode) > 0);
- m_largeFiles = ((response.Capabilities & Capabilities.LargeFiles) > 0);
- bool ntSMB = ((response.Capabilities & Capabilities.NTSMB) > 0);
- bool rpc = ((response.Capabilities & Capabilities.RpcRemoteApi) > 0);
- bool ntStatusCode = ((response.Capabilities & Capabilities.NTStatusCode) > 0);
- m_infoLevelPassthrough = ((response.Capabilities & Capabilities.InfoLevelPassthrough) > 0);
- m_largeRead = ((response.Capabilities & Capabilities.LargeRead) > 0);
- m_largeWrite = ((response.Capabilities & Capabilities.LargeWrite) > 0);
- m_serverMaxBufferSize = response.MaxBufferSize;
- m_maxMpxCount = Math.Min(response.MaxMpxCount, ClientMaxMpxCount);
- m_securityBlob = response.SecurityBlob;
- return ntSMB && rpc && ntStatusCode;
- }
- else
- {
- return false;
- }
- }
- public NTStatus Login(string domainName, string userName, string password)
- {
- return Login(domainName, userName, password, AuthenticationMethod.NTLMv2);
- }
- public NTStatus Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod)
- {
- if (!m_isConnected)
- {
- throw new InvalidOperationException("A connection must be successfully established before attempting login");
- }
- Capabilities clientCapabilities = Capabilities.NTSMB | Capabilities.RpcRemoteApi | Capabilities.NTStatusCode | Capabilities.NTFind;
- if (m_unicode)
- {
- clientCapabilities |= Capabilities.Unicode;
- }
- if (m_largeFiles)
- {
- clientCapabilities |= Capabilities.LargeFiles;
- }
- if (m_largeRead)
- {
- clientCapabilities |= Capabilities.LargeRead;
- }
- if (m_serverChallenge != null)
- {
- SessionSetupAndXRequest request = new SessionSetupAndXRequest();
- request.MaxBufferSize = ClientMaxBufferSize;
- request.MaxMpxCount = m_maxMpxCount;
- request.Capabilities = clientCapabilities;
- 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(request);
- SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX);
- if (reply != null)
- {
- m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS);
- return reply.Header.Status;
- }
- return NTStatus.STATUS_INVALID_SMB;
- }
- else // m_securityBlob != null
- {
- byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, authenticationMethod);
- if (negotiateMessage == null)
- {
- return NTStatus.SEC_E_INVALID_TOKEN;
- }
- SessionSetupAndXRequestExtended request = new SessionSetupAndXRequestExtended();
- request.MaxBufferSize = ClientMaxBufferSize;
- request.MaxMpxCount = m_maxMpxCount;
- request.Capabilities = clientCapabilities;
- request.SecurityBlob = negotiateMessage;
- TrySendMessage(request);
-
- SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX);
- if (reply != null)
- {
- if (reply.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && reply.Commands[0] is SessionSetupAndXResponseExtended)
- {
- SessionSetupAndXResponseExtended response = (SessionSetupAndXResponseExtended)reply.Commands[0];
- byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(response.SecurityBlob, domainName, userName, password, authenticationMethod, out m_sessionKey);
- if (authenticateMessage == null)
- {
- return NTStatus.SEC_E_INVALID_TOKEN;
- }
- m_userID = reply.Header.UID;
- request = new SessionSetupAndXRequestExtended();
- request.MaxBufferSize = ClientMaxBufferSize;
- request.MaxMpxCount = m_maxMpxCount;
- request.Capabilities = clientCapabilities;
- request.SecurityBlob = authenticateMessage;
- TrySendMessage(request);
- reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX);
- if (reply != null)
- {
- m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS);
- return reply.Header.Status;
- }
- }
- else
- {
- return reply.Header.Status;
- }
- }
- return NTStatus.STATUS_INVALID_SMB;
- }
- }
- public NTStatus Logoff()
- {
- if (!m_isConnected)
- {
- throw new InvalidOperationException("A login session must be successfully established before attempting logoff");
- }
- LogoffAndXRequest request = new LogoffAndXRequest();
- TrySendMessage(request);
- SMB1Message reply = WaitForMessage(CommandName.SMB_COM_LOGOFF_ANDX);
- if (reply != null)
- {
- m_isLoggedIn = (reply.Header.Status != NTStatus.STATUS_SUCCESS);
- return reply.Header.Status;
- }
- return NTStatus.STATUS_INVALID_SMB;
- }
- public List<string> ListShares(out NTStatus status)
- {
- if (!m_isConnected || !m_isLoggedIn)
- {
- throw new InvalidOperationException("A login session must be successfully established before retrieving share list");
- }
- SMB1FileStore namedPipeShare = TreeConnect("IPC$", ServiceName.NamedPipe, out status);
- if (namedPipeShare == null)
- {
- return null;
- }
- List<string> shares = ServerServiceHelper.ListShares(namedPipeShare, ShareType.DiskDrive, out status);
- namedPipeShare.Disconnect();
- return shares;
- }
- public ISMBFileStore TreeConnect(string shareName, out NTStatus status)
- {
- return TreeConnect(shareName, ServiceName.AnyType, out status);
- }
- public SMB1FileStore TreeConnect(string shareName, ServiceName serviceName, out NTStatus status)
- {
- if (!m_isConnected || !m_isLoggedIn)
- {
- throw new InvalidOperationException("A login session must be successfully established before connecting to a share");
- }
- TreeConnectAndXRequest request = new TreeConnectAndXRequest();
- request.Path = shareName;
- request.Service = serviceName;
- TrySendMessage(request);
- SMB1Message reply = WaitForMessage(CommandName.SMB_COM_TREE_CONNECT_ANDX);
- if (reply != null)
- {
- status = reply.Header.Status;
- if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is TreeConnectAndXResponse)
- {
- TreeConnectAndXResponse response = (TreeConnectAndXResponse)reply.Commands[0];
- return new SMB1FileStore(this, reply.Header.TID);
- }
- }
- else
- {
- status = NTStatus.STATUS_INVALID_SMB;
- }
- return null;
- }
- private void OnClientSocketReceive(IAsyncResult ar)
- {
- ConnectionState state = (ConnectionState)ar.AsyncState;
- Socket clientSocket = state.ClientSocket;
- if (!clientSocket.Connected)
- {
- return;
- }
- int numberOfBytesReceived = 0;
- try
- {
- numberOfBytesReceived = clientSocket.EndReceive(ar);
- }
- catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class.
- {
- return;
- }
- catch (ObjectDisposedException)
- {
- Log("[ReceiveCallback] EndReceive ObjectDisposedException");
- return;
- }
- catch (SocketException ex)
- {
- Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message);
- return;
- }
- if (numberOfBytesReceived == 0)
- {
- m_isConnected = false;
- }
- else
- {
- NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer;
- buffer.SetNumberOfBytesReceived(numberOfBytesReceived);
- ProcessConnectionBuffer(state);
- try
- {
- clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state);
- }
- catch (ObjectDisposedException)
- {
- m_isConnected = false;
- Log("[ReceiveCallback] BeginReceive ObjectDisposedException");
- }
- catch (SocketException ex)
- {
- m_isConnected = false;
- Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message);
- }
- }
- }
- private void ProcessConnectionBuffer(ConnectionState state)
- {
- NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer;
- while (receiveBuffer.HasCompletePacket())
- {
- SessionPacket packet = null;
- try
- {
- packet = receiveBuffer.DequeuePacket();
- }
- catch (Exception)
- {
- state.ClientSocket.Close();
- break;
- }
- if (packet != null)
- {
- ProcessPacket(packet, state);
- }
- }
- }
- private void ProcessPacket(SessionPacket packet, ConnectionState state)
- {
- if (packet is SessionMessagePacket)
- {
- SMB1Message message;
- try
- {
- message = SMB1Message.GetSMB1Message(packet.Trailer);
- }
- catch (Exception ex)
- {
- Log("Invalid SMB1 message: " + ex.Message);
- state.ClientSocket.Close();
- m_isConnected = false;
- return;
- }
- // [MS-CIFS] 3.2.5.1 - If the MID value is the reserved value 0xFFFF, the message can be an OpLock break
- // sent by the server. Otherwise, if the PID and MID values of the received message are not found in the
- // Client.Connection.PIDMIDList, the message MUST be discarded.
- if ((message.Header.MID == 0xFFFF && message.Header.Command == CommandName.SMB_COM_LOCKING_ANDX) ||
- (message.Header.PID == 0 && message.Header.MID == 0))
- {
- lock (m_incomingQueueLock)
- {
- m_incomingQueue.Add(message);
- m_incomingQueueEventHandle.Set();
- }
- }
- }
- else if ((packet is PositiveSessionResponsePacket || packet is NegativeSessionResponsePacket) && m_transport == SMBTransportType.NetBiosOverTCP)
- {
- m_sessionResponsePacket = packet;
- m_sessionResponseEventHandle.Set();
- }
- else if (packet is SessionKeepAlivePacket && m_transport == SMBTransportType.NetBiosOverTCP)
- {
- // [RFC 1001] NetBIOS session keep alives do not require a response from the NetBIOS peer
- }
- else
- {
- Log("Inappropriate NetBIOS session packet");
- state.ClientSocket.Close();
- }
- }
- internal SMB1Message WaitForMessage(CommandName commandName)
- {
- const int TimeOut = 5000;
- Stopwatch stopwatch = new Stopwatch();
- stopwatch.Start();
- while (stopwatch.ElapsedMilliseconds < TimeOut)
- {
- lock (m_incomingQueueLock)
- {
- for (int index = 0; index < m_incomingQueue.Count; index++)
- {
- SMB1Message message = m_incomingQueue[index];
- if (message.Commands[0].CommandName == commandName)
- {
- m_incomingQueue.RemoveAt(index);
- return message;
- }
- }
- }
- m_incomingQueueEventHandle.WaitOne(100);
- }
- return null;
- }
- internal SessionPacket WaitForSessionResponsePacket()
- {
- const int TimeOut = 5000;
- Stopwatch stopwatch = new Stopwatch();
- stopwatch.Start();
- while (stopwatch.ElapsedMilliseconds < TimeOut)
- {
- if (m_sessionResponsePacket != null)
- {
- SessionPacket result = m_sessionResponsePacket;
- m_sessionResponsePacket = null;
- return result;
- }
- m_sessionResponseEventHandle.WaitOne(100);
- }
- return null;
- }
- private void Log(string message)
- {
- System.Diagnostics.Debug.Print(message);
- }
- internal void TrySendMessage(SMB1Command request)
- {
- TrySendMessage(request, 0);
- }
- internal void TrySendMessage(SMB1Command request, ushort treeID)
- {
- SMB1Message message = new SMB1Message();
- message.Header.UnicodeFlag = m_unicode;
- message.Header.ExtendedSecurityFlag = m_forceExtendedSecurity;
- message.Header.Flags2 |= HeaderFlags2.LongNamesAllowed | HeaderFlags2.LongNameUsed | HeaderFlags2.NTStatusCode;
- message.Header.UID = m_userID;
- message.Header.TID = treeID;
- message.Commands.Add(request);
- TrySendMessage(m_clientSocket, message);
- }
- public bool Unicode
- {
- get
- {
- return m_unicode;
- }
- }
- public bool LargeFiles
- {
- get
- {
- return m_largeFiles;
- }
- }
- public bool InfoLevelPassthrough
- {
- get
- {
- return m_infoLevelPassthrough;
- }
- }
- public bool LargeRead
- {
- get
- {
- return m_largeRead;
- }
- }
- public bool LargeWrite
- {
- get
- {
- return m_largeWrite;
- }
- }
- public uint ServerMaxBufferSize
- {
- get
- {
- return m_serverMaxBufferSize;
- }
- }
- public int MaxMpxCount
- {
- get
- {
- return m_maxMpxCount;
- }
- }
- public uint MaxReadSize
- {
- get
- {
- return (uint)ClientMaxBufferSize - (SMB1Header.Length + 3 + ReadAndXResponse.ParametersLength);
- }
- }
- public uint MaxWriteSize
- {
- get
- {
- uint result = ServerMaxBufferSize - (SMB1Header.Length + 3 + WriteAndXRequest.ParametersFixedLength + 4);
- if (m_unicode)
- {
- result--;
- }
- return result;
- }
- }
- public static void TrySendMessage(Socket socket, SMB1Message message)
- {
- SessionMessagePacket packet = new SessionMessagePacket();
- packet.Trailer = message.GetBytes();
- TrySendPacket(socket, packet);
- }
- public static void TrySendPacket(Socket socket, SessionPacket packet)
- {
- try
- {
- byte[] packetBytes = packet.GetBytes();
- socket.Send(packetBytes);
- }
- catch (SocketException)
- {
- }
- catch (ObjectDisposedException)
- {
- }
- }
- }
- }
|