From 083128889a9a0631eb1c257790b447aebfb0e1a9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 19 Nov 2021 12:43:09 +0200 Subject: [PATCH 001/247] Correct parameter name in documentation --- SMBLibrary/NetBios/NetBiosUtils.cs | 4 ++-- SMBLibrary/SMB2/Commands/SMB2Command.cs | 2 +- SMBLibrary/Server/Shares/SMBShareCollection.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/NetBios/NetBiosUtils.cs b/SMBLibrary/NetBios/NetBiosUtils.cs index 67fb9b1d..16612223 100644 --- a/SMBLibrary/NetBios/NetBiosUtils.cs +++ b/SMBLibrary/NetBios/NetBiosUtils.cs @@ -60,7 +60,7 @@ public static byte[] EncodeName(string name, NetBiosSuffix suffix, string scopeI return EncodeName(netBiosName, scopeID); } - /// NetBIOS name + /// NetBIOS name /// dot-separated labels, formatted per DNS naming rules public static byte[] EncodeName(string netBiosName, string scopeID) { @@ -75,7 +75,7 @@ public static byte[] EncodeName(string netBiosName, string scopeID) // into two nibbles and then adding the value of 'A' (0x41). // Thus, the '&' character (0x26) would be encoded as "CG". // NetBIOS names are usually padded with spaces before being encoded. - /// NetBIOS name + /// NetBIOS name /// dot-separated labels, formatted per DNS naming rules public static string FirstLevelEncoding(string netBiosName, string scopeID) { diff --git a/SMBLibrary/SMB2/Commands/SMB2Command.cs b/SMBLibrary/SMB2/Commands/SMB2Command.cs index 0738cbf7..479aa361 100644 --- a/SMBLibrary/SMB2/Commands/SMB2Command.cs +++ b/SMBLibrary/SMB2/Commands/SMB2Command.cs @@ -135,7 +135,7 @@ public static byte[] GetCommandChainBytes(List commands) return GetCommandChainBytes(commands, null, SMB2Dialect.SMB2xx); } - /// + /// /// Message will be signed using this key if (not null and) SMB2_FLAGS_SIGNED is set. /// /// diff --git a/SMBLibrary/Server/Shares/SMBShareCollection.cs b/SMBLibrary/Server/Shares/SMBShareCollection.cs index 4cd61a18..03216745 100644 --- a/SMBLibrary/Server/Shares/SMBShareCollection.cs +++ b/SMBLibrary/Server/Shares/SMBShareCollection.cs @@ -39,7 +39,7 @@ public List ListShares() return result; } - /// e.g. \Shared + /// e.g. \Shared public FileSystemShare GetShareFromName(string shareName) { int index = IndexOf(shareName, StringComparison.OrdinalIgnoreCase); From 027b4652fadb72b6b5128490ae2c373eb4b336ce Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 19 Nov 2021 12:44:14 +0200 Subject: [PATCH 002/247] Documentation syntax corrections --- SMBLibrary/Helpers/SP800_1008.cs | 2 +- .../Transaction2QueryFileInformationResponse.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Helpers/SP800_1008.cs b/SMBLibrary/Helpers/SP800_1008.cs index 0ac76849..1c785128 100644 --- a/SMBLibrary/Helpers/SP800_1008.cs +++ b/SMBLibrary/Helpers/SP800_1008.cs @@ -1,4 +1,4 @@ -/// Adapted from https://referencesource.microsoft.com/#system.web/Security/Cryptography/SP800_108.cs +// Adapted from https://referencesource.microsoft.com/#system.web/Security/Cryptography/SP800_108.cs using System; using System.Security.Cryptography; using Utilities; diff --git a/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2QueryFileInformationResponse.cs b/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2QueryFileInformationResponse.cs index 75b156b9..bc750c1a 100644 --- a/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2QueryFileInformationResponse.cs +++ b/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2QueryFileInformationResponse.cs @@ -12,7 +12,7 @@ namespace SMBLibrary.SMB1 { /// /// TRANS2_QUERY_FILE_INFORMATION Response - /// public class Transaction2QueryFileInformationResponse : Transaction2Subcommand { public const int ParametersLength = 2; From 02c4ab2e22e9f4acdf79cddab7a8899d6d588e2d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 19 Nov 2021 12:46:43 +0200 Subject: [PATCH 003/247] Documentation XML syntax corrections --- SMBLibrary/Client/Helpers/ServerServiceHelper.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/Helpers/ServerServiceHelper.cs b/SMBLibrary/Client/Helpers/ServerServiceHelper.cs index fdc0921c..f37e12f1 100644 --- a/SMBLibrary/Client/Helpers/ServerServiceHelper.cs +++ b/SMBLibrary/Client/Helpers/ServerServiceHelper.cs @@ -20,7 +20,7 @@ public static List ListShares(INTFileStore namedPipeShare, ShareType? sh } /// - /// When a Windows Server host is using Failover Cluster & Cluster Shared Volumes, each of those CSV file shares is associated + /// When a Windows Server host is using Failover Cluster and Cluster Shared Volumes, each of those CSV file shares is associated /// with a specific host name associated with the cluster and is not accessible using the node IP address or node host name. /// public static List ListShares(INTFileStore namedPipeShare, string serverName, ShareType? shareType, out NTStatus status) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index f8c7dea9..b8fd19ad 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -60,7 +60,7 @@ public SMB2Client() } /// - /// When a Windows Server host is using Failover Cluster & Cluster Shared Volumes, each of those CSV file shares is associated + /// When a Windows Server host is using Failover Cluster and Cluster Shared Volumes, each of those CSV file shares is associated /// with a specific host name associated with the cluster and is not accessible using the node IP address or node host name. /// public bool Connect(string serverName, SMBTransportType transport) From db007d09e5bc7591a0e49a0c42b93adfbbb52d93 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Dec 2021 09:15:58 +0200 Subject: [PATCH 004/247] SMBServer: Added private Start overload allowing to specify the listening port --- SMBLibrary/Server/SMBServer.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index c85a2660..9e87ff8b 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2021 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, @@ -77,6 +77,12 @@ public void Start(IPAddress serverAddress, SMBTransportType transport, bool enab /// /// public void Start(IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) + { + int port = (m_transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); + Start(serverAddress, transport, port, enableSMB1, enableSMB2, enableSMB3, connectionInactivityTimeout); + } + + private void Start(IPAddress serverAddress, SMBTransportType transport, int port, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) { if (!m_listening) { @@ -95,7 +101,6 @@ public void Start(IPAddress serverAddress, SMBTransportType transport, bool enab m_serverStartTime = DateTime.Now; m_listenerSocket = new Socket(m_serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); - int port = (m_transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); m_listenerSocket.Bind(new IPEndPoint(m_serverAddress, port)); m_listenerSocket.Listen((int)SocketOptionName.MaxConnections); m_listenerSocket.BeginAccept(ConnectRequestCallback, m_listenerSocket); From 0ae5986759bad13fa06bdbcd362ee58318cf6839 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Dec 2021 09:17:58 +0200 Subject: [PATCH 005/247] Client: Added private Connect overload allowing to specify the server port --- SMBLibrary/Client/SMB1Client.cs | 16 ++++++---------- SMBLibrary/Client/SMB2Client.cs | 16 ++++++---------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index c3328fa6..7eb1efab 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -75,21 +75,17 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport) } public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity) + { + int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); + return Connect(serverAddress, transport, port, forceExtendedSecurity); + } + + private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, 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; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index b8fd19ad..b64f42bb 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -76,6 +76,12 @@ public bool Connect(string serverName, SMBTransportType transport) } public bool Connect(IPAddress serverAddress, SMBTransportType transport) + { + int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); + return Connect(serverAddress, transport, port); + } + + private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port) { if (m_serverName == null) { @@ -85,16 +91,6 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport) m_transport = transport; if (!m_isConnected) { - int port; - if (transport == SMBTransportType.NetBiosOverTCP) - { - port = NetBiosOverTCPPort; - } - else - { - port = DirectTCPPort; - } - if (!ConnectSocket(serverAddress, port)) { return false; From 849b99ad837f23ee8199f4d443c51c479819275e Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Dec 2021 10:21:50 +0200 Subject: [PATCH 006/247] Client: Correct NetrShareEnum ServerName syntax --- SMBLibrary/Client/Helpers/ServerServiceHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Client/Helpers/ServerServiceHelper.cs b/SMBLibrary/Client/Helpers/ServerServiceHelper.cs index f37e12f1..39d06458 100644 --- a/SMBLibrary/Client/Helpers/ServerServiceHelper.cs +++ b/SMBLibrary/Client/Helpers/ServerServiceHelper.cs @@ -38,7 +38,7 @@ public static List ListShares(INTFileStore namedPipeShare, string server shareEnumRequest.InfoStruct.Level = 1; shareEnumRequest.InfoStruct.Info = new ShareInfo1Container(); shareEnumRequest.PreferedMaximumLength = UInt32.MaxValue; - shareEnumRequest.ServerName = serverName; + shareEnumRequest.ServerName = @"\\" + serverName; RequestPDU requestPDU = new RequestPDU(); requestPDU.Flags = PacketFlags.FirstFragment | PacketFlags.LastFragment; requestPDU.DataRepresentation.CharacterFormat = CharacterFormat.ASCII; From 3c47285484b915dfd9da4d7d4d0cfaa64f5441f6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Dec 2021 10:37:02 +0200 Subject: [PATCH 007/247] SMBServer 1.4.7 --- SMBLibrary.Adapters/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/RevisionHistory.txt | 10 ++++++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/Properties/AssemblyInfo.cs | 4 ++-- 8 files changed, 21 insertions(+), 11 deletions(-) diff --git a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs index fc116cfb..1f55f8c0 100644 --- a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.6.0")] -[assembly: AssemblyFileVersion("1.4.6.0")] +[assembly: AssemblyVersion("1.4.7.0")] +[assembly: AssemblyFileVersion("1.4.7.0")] diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 9956471a..95107129 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Adapters - 1.4.6 + 1.4.7 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/Properties/AssemblyInfo.cs b/SMBLibrary.Win32/Properties/AssemblyInfo.cs index 75889325..13ac6135 100644 --- a/SMBLibrary.Win32/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Win32/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.6.0")] -[assembly: AssemblyFileVersion("1.4.6.0")] +[assembly: AssemblyVersion("1.4.7.0")] +[assembly: AssemblyFileVersion("1.4.7.0")] diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 2908afbb..1fe588af 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Win32 - 1.4.6 + 1.4.7 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index 91f1f85f..fae33fa2 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.6.2")] -[assembly: AssemblyFileVersion("1.4.6.2")] +[assembly: AssemblyVersion("1.4.7.0")] +[assembly: AssemblyFileVersion("1.4.7.0")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index d03156b1..6fbd7b8d 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -459,3 +459,13 @@ Revision History: NTLM: Bugfix: IndependentNTLMAuthenticationProvider login failed to to modification of message byte arrays. 1.4.6 - SMB2Client: Fixed InvalidCastException on failed login to SMB 3.0 server. + +1.4.7 - SMBServer: Added private Start overload allowing to specify the listening port. + Client: Added private Connect overload allowing to specify the server port. + SMB2Client: Correctly handle async responses. + SMB2Client: WaitForCommand: Compare MessageID instead of CommandName. + Client: SMB2FileStore: Implement Flush. + Client: Added support for accessing Cluster Shared Volumes file shares. + SMB2Command: Add MessageID property. + NTStatus: Added STATUS_WRONG_PASSWORD. + NTStatus: Correct STATUS_PASSWORD_MUST_CHANGE value. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index dfda563f..52304522 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.4.6.2 + 1.4.7 1573;1591 SMBLibrary false diff --git a/SMBServer/Properties/AssemblyInfo.cs b/SMBServer/Properties/AssemblyInfo.cs index 3a6ce023..a3803647 100644 --- a/SMBServer/Properties/AssemblyInfo.cs +++ b/SMBServer/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("1.4.6.0")] -[assembly: AssemblyFileVersion("1.4.6.0")] +[assembly: AssemblyVersion("1.4.7.0")] +[assembly: AssemblyFileVersion("1.4.7.0")] From 00990840651347063a1626cbe2d6c4157ce8f1d8 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Dec 2021 13:44:43 +0200 Subject: [PATCH 008/247] SMBServer: Start Bugfix --- SMBLibrary/Server/SMBServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 9e87ff8b..8cc48a21 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -78,7 +78,7 @@ public void Start(IPAddress serverAddress, SMBTransportType transport, bool enab /// public void Start(IPAddress serverAddress, SMBTransportType transport, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) { - int port = (m_transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); + int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); Start(serverAddress, transport, port, enableSMB1, enableSMB2, enableSMB3, connectionInactivityTimeout); } From 6b2906b6346ee093a9f0ea1361254483e419e080 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Dec 2021 13:49:27 +0200 Subject: [PATCH 009/247] SMBServer 1.4.8 --- SMBLibrary.Adapters/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/RevisionHistory.txt | 2 ++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/Properties/AssemblyInfo.cs | 4 ++-- 8 files changed, 13 insertions(+), 11 deletions(-) diff --git a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs index 1f55f8c0..5bfe40d9 100644 --- a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.7.0")] -[assembly: AssemblyFileVersion("1.4.7.0")] +[assembly: AssemblyVersion("1.4.8.0")] +[assembly: AssemblyFileVersion("1.4.8.0")] diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 95107129..991139f5 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Adapters - 1.4.7 + 1.4.8 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/Properties/AssemblyInfo.cs b/SMBLibrary.Win32/Properties/AssemblyInfo.cs index 13ac6135..13a5087d 100644 --- a/SMBLibrary.Win32/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Win32/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.7.0")] -[assembly: AssemblyFileVersion("1.4.7.0")] +[assembly: AssemblyVersion("1.4.8.0")] +[assembly: AssemblyFileVersion("1.4.8.0")] diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 1fe588af..65a2d6b6 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Win32 - 1.4.7 + 1.4.8 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index fae33fa2..e2d24a47 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.7.0")] -[assembly: AssemblyFileVersion("1.4.7.0")] +[assembly: AssemblyVersion("1.4.8.0")] +[assembly: AssemblyFileVersion("1.4.8.0")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 6fbd7b8d..678b1b74 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -469,3 +469,5 @@ Revision History: SMB2Command: Add MessageID property. NTStatus: Added STATUS_WRONG_PASSWORD. NTStatus: Correct STATUS_PASSWORD_MUST_CHANGE value. + +1.4.8 - SMBServer - Start method bugfix. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 52304522..81eb0d15 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.4.7 + 1.4.8 1573;1591 SMBLibrary false diff --git a/SMBServer/Properties/AssemblyInfo.cs b/SMBServer/Properties/AssemblyInfo.cs index a3803647..de3408d7 100644 --- a/SMBServer/Properties/AssemblyInfo.cs +++ b/SMBServer/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("1.4.7.0")] -[assembly: AssemblyFileVersion("1.4.7.0")] +[assembly: AssemblyVersion("1.4.8.0")] +[assembly: AssemblyFileVersion("1.4.8.0")] From 22247a8bae584e15f3c0b677cd4232caf7e55593 Mon Sep 17 00:00:00 2001 From: Jacob Hales Date: Mon, 27 Dec 2021 12:18:14 -0800 Subject: [PATCH 010/247] Adding STATUS_PATH_NOT_COVERED = 0xC0000257 to NTStatus (#120) --- SMBLibrary/Enums/NTStatus.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/Enums/NTStatus.cs b/SMBLibrary/Enums/NTStatus.cs index 98ffc8f7..d9ba068c 100644 --- a/SMBLibrary/Enums/NTStatus.cs +++ b/SMBLibrary/Enums/NTStatus.cs @@ -68,6 +68,7 @@ public enum NTStatus : uint STATUS_NOT_FOUND = 0xC0000225, STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234, STATUS_PASSWORD_MUST_CHANGE = 0xC0000224, + STATUS_PATH_NOT_COVERED = 0xC0000257, STATUS_NOT_A_REPARSE_POINT = 0xC0000275, STATUS_INVALID_SMB = 0x00010002, // SMB1/CIFS: A corrupt or invalid SMB request was received From 5c4b88c759f47b97e706a9ab5a86199651dfa545 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 27 Dec 2021 22:25:17 +0200 Subject: [PATCH 011/247] Update NTStatus.cs: Correct ordering --- SMBLibrary/Enums/NTStatus.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Enums/NTStatus.cs b/SMBLibrary/Enums/NTStatus.cs index d9ba068c..9ce465e4 100644 --- a/SMBLibrary/Enums/NTStatus.cs +++ b/SMBLibrary/Enums/NTStatus.cs @@ -65,9 +65,9 @@ public enum NTStatus : uint STATUS_FS_DRIVER_REQUIRED = 0xC000019C, STATUS_USER_SESSION_DELETED = 0xC0000203, STATUS_INSUFF_SERVER_RESOURCES = 0xC0000205, + STATUS_PASSWORD_MUST_CHANGE = 0xC0000224, STATUS_NOT_FOUND = 0xC0000225, STATUS_ACCOUNT_LOCKED_OUT = 0xC0000234, - STATUS_PASSWORD_MUST_CHANGE = 0xC0000224, STATUS_PATH_NOT_COVERED = 0xC0000257, STATUS_NOT_A_REPARSE_POINT = 0xC0000275, From f8baa1b1019ea938da2ef518d0690e3bb0fab53c Mon Sep 17 00:00:00 2001 From: Kieran Devlin Date: Fri, 27 May 2022 09:20:07 +0100 Subject: [PATCH 012/247] Fixes bug when trying to use local ip address as host name. (#133) --- SMBLibrary/Client/SMB1Client.cs | 6 +++--- SMBLibrary/Client/SMB2Client.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 7eb1efab..3301a86e 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -60,12 +60,12 @@ public SMB1Client() public bool Connect(string serverName, SMBTransportType transport) { - IPHostEntry hostEntry = Dns.GetHostEntry(serverName); - if (hostEntry.AddressList.Length == 0) + IPAddress[] hostAddresses = Dns.GetHostAddresses(serverName); + if (hostAddresses.Length == 0) { throw new Exception(String.Format("Cannot resolve host name {0} to an IP address", serverName)); } - IPAddress serverAddress = hostEntry.AddressList[0]; + IPAddress serverAddress = hostAddresses[0]; return Connect(serverAddress, transport); } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index b64f42bb..887a6899 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -66,12 +66,12 @@ public SMB2Client() public bool Connect(string serverName, SMBTransportType transport) { m_serverName = serverName; - IPHostEntry hostEntry = Dns.GetHostEntry(serverName); - if (hostEntry.AddressList.Length == 0) + IPAddress[] hostAddresses = Dns.GetHostAddresses(serverName); + if (hostAddresses.Length == 0) { throw new Exception(String.Format("Cannot resolve host name {0} to an IP address", serverName)); } - IPAddress serverAddress = hostEntry.AddressList[0]; + IPAddress serverAddress = hostAddresses[0]; return Connect(serverAddress, transport); } From 1ac805af51ca0ff960f3b8ab65276265082eaa28 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 May 2022 11:37:00 +0300 Subject: [PATCH 013/247] SessionSetupHelper: Bugfix: Use correct sessionID for session creation in which token is accepted immediately --- SMBLibrary/Server/SMB2/SessionSetupHelper.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs index aa28f673..b808cc06 100644 --- a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs @@ -40,14 +40,16 @@ internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, } // According to [MS-SMB2] 3.3.5.5.3, response.Header.SessionID must be allocated if the server returns STATUS_MORE_PROCESSING_REQUIRED - if (request.Header.SessionID == 0) + ulong sessionID = request.Header.SessionID; + if (sessionID == 0) { - ulong? sessionID = state.AllocateSessionID(); - if (!sessionID.HasValue) + ulong? allocatedSessionID = state.AllocateSessionID(); + if (!allocatedSessionID.HasValue) { return new ErrorResponse(request.CommandName, NTStatus.STATUS_TOO_MANY_SESSIONS); } - response.Header.SessionID = sessionID.Value; + sessionID = allocatedSessionID.Value; + response.Header.SessionID = allocatedSessionID.Value; } if (status == NTStatus.SEC_I_CONTINUE_NEEDED) @@ -69,12 +71,12 @@ internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, bool signingRequired = (request.SecurityMode & SecurityMode.SigningRequired) > 0; SMB2Dialect smb2Dialect = SMBServer.ToSMB2Dialect(state.Dialect); byte[] signingKey = SMB2Cryptography.GenerateSigningKey(sessionKey, smb2Dialect, null); - state.CreateSession(request.Header.SessionID, userName, machineName, sessionKey, accessToken, signingRequired, signingKey); + state.CreateSession(sessionID, userName, machineName, sessionKey, accessToken, signingRequired, signingKey); } else { state.LogToServer(Severity.Information, "Session Setup: User '{0}' failed authentication (Domain: '{1}', Workstation: '{2}', OS version: '{3}'), logged in as guest.", userName, domainName, machineName, osVersion); - state.CreateSession(request.Header.SessionID, "Guest", machineName, sessionKey, accessToken, false, null); + state.CreateSession(sessionID, "Guest", machineName, sessionKey, accessToken, false, null); response.SessionFlags = SessionFlags.IsGuest; } } From 9eba4f51102001cbc16afef00bfeec7c4dfa4bcf Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 May 2022 11:51:08 +0300 Subject: [PATCH 014/247] SessionSetupHelper: Bugfix: Trim session key if longer than 16 bytes --- SMBLibrary/Server/SMB1/SessionSetupHelper.cs | 14 ++++++++++++++ SMBLibrary/Server/SMB2/SessionSetupHelper.cs | 7 +++++++ 2 files changed, 21 insertions(+) diff --git a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs index e790b974..89f4df52 100644 --- a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs @@ -37,6 +37,13 @@ internal static SMB1Command GetSessionSetupResponse(SMB1Header header, SessionSe byte[] sessionKey = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.SessionKey) as byte[]; object accessToken = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.AccessToken); bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; + + if (sessionKey.Length > 16) + { + // [MS-CIFS] 3.3.5.43 If the session key is equal to or longer than 16 bytes, only the least significant 16 bytes MUST be stored in Server.Session.SessionKey + sessionKey = ByteReader.ReadBytes(sessionKey, 0, 16); + } + SMB1Session session; if (!isGuest.HasValue || !isGuest.Value) { @@ -120,6 +127,13 @@ internal static SMB1Command GetSessionSetupResponseExtended(SMB1Header header, S byte[] sessionKey = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.SessionKey) as byte[]; object accessToken = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.AccessToken); bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; + + if (sessionKey.Length > 16) + { + // [MS-CIFS] 3.3.5.43 If the session key is equal to or longer than 16 bytes, only the least significant 16 bytes MUST be stored in Server.Session.SessionKey + sessionKey = ByteReader.ReadBytes(sessionKey, 0, 16); + } + if (!isGuest.HasValue || !isGuest.Value) { state.LogToServer(Severity.Information, "Session Setup: User '{0}' authenticated successfully (Domain: '{1}', Workstation: '{2}', OS version: '{3}').", userName, domainName, machineName, osVersion); diff --git a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs index b808cc06..556f221d 100644 --- a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs @@ -65,6 +65,13 @@ internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, byte[] sessionKey = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.SessionKey) as byte[]; object accessToken = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.AccessToken); bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; + + if (sessionKey.Length > 16) + { + // [MS-SMB2] 3.3.1.8 SessionKey MUST be set to the first 16 bytes of the cryptographic key queried from the GSS protocol for this authenticated context. + sessionKey = ByteReader.ReadBytes(sessionKey, 0, 16); + } + if (!isGuest.HasValue || !isGuest.Value) { state.LogToServer(Severity.Information, "Session Setup: User '{0}' authenticated successfully (Domain: '{1}', Workstation: '{2}', OS version: '{3}').", userName, domainName, machineName, osVersion); From b268988d899e8deaa506110343f77e11cb2d5705 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 27 May 2022 20:07:10 +1000 Subject: [PATCH 015/247] Provide SPN with NTLMv2 token (#134) Ensure the `MsvAvTargetName` AV Pair is provided with the NTLM v2 authentication message. This is set to the Service Principal Name (SPN) of the server being connected to. --- .../NTLM/Structures/NTLMv2ClientChallenge.cs | 9 +++++++++ SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 4 ++-- SMBLibrary/Client/SMB1Client.cs | 10 +++++----- SMBLibrary/Client/SMB2Client.cs | 3 ++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Structures/NTLMv2ClientChallenge.cs b/SMBLibrary/Authentication/NTLM/Structures/NTLMv2ClientChallenge.cs index b8d5b4fd..91729835 100644 --- a/SMBLibrary/Authentication/NTLM/Structures/NTLMv2ClientChallenge.cs +++ b/SMBLibrary/Authentication/NTLM/Structures/NTLMv2ClientChallenge.cs @@ -45,12 +45,21 @@ public NTLMv2ClientChallenge(DateTime timeStamp, byte[] clientChallenge, string } public NTLMv2ClientChallenge(DateTime timeStamp, byte[] clientChallenge, KeyValuePairList targetInfo) + : this(timeStamp, clientChallenge, targetInfo, null) + { + } + + public NTLMv2ClientChallenge(DateTime timeStamp, byte[] clientChallenge, KeyValuePairList targetInfo, string spn) { CurrentVersion = StructureVersion; MaximumSupportedVersion = StructureVersion; TimeStamp = timeStamp; ClientChallenge = clientChallenge; AVPairs = targetInfo; + if (!string.IsNullOrEmpty(spn)) + { + AVPairs.Add(AVPairKey.TargetName, UnicodeEncoding.Unicode.GetBytes(spn)); + } } public NTLMv2ClientChallenge(byte[] buffer) : this(buffer, 0) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 61e29fae..1a0c670f 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -75,7 +75,7 @@ public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, } } - public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainName, string userName, string password, AuthenticationMethod authenticationMethod, out byte[] sessionKey) + public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) { sessionKey = null; bool useGSSAPI = false; @@ -163,7 +163,7 @@ public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainNa } else // NTLMv2 { - NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(time, clientChallenge, challengeMessage.TargetInfo); + NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(time, clientChallenge, challengeMessage.TargetInfo, spn); byte[] clientChallengeStructurePadded = clientChallengeStructure.GetBytesPadded(); byte[] ntProofStr = NTLMCryptography.ComputeNTLMv2Proof(challengeMessage.ServerChallenge, clientChallengeStructurePadded, password, userName, domainName); diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 3301a86e..c015d23d 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -21,7 +21,7 @@ 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; @@ -90,7 +90,7 @@ private bool Connect(IPAddress serverAddress, SMBTransportType transport, int po { return false; } - + if (transport == SMBTransportType.NetBiosOverTCP) { SessionRequestPacket sessionRequest = new SessionRequestPacket(); @@ -141,7 +141,7 @@ private bool Connect(IPAddress serverAddress, SMBTransportType transport, int po private bool ConnectSocket(IPAddress serverAddress, int port) { m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - + try { m_clientSocket.Connect(serverAddress, port); @@ -273,7 +273,7 @@ public NTStatus Login(string domainName, string userName, string password, Authe 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); @@ -305,7 +305,7 @@ public NTStatus Login(string domainName, string userName, string password, Authe 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); + byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(response.SecurityBlob, domainName, userName, password, null, authenticationMethod, out m_sessionKey); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 887a6899..ba3f98cf 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -224,7 +224,8 @@ public NTStatus Login(string domainName, string userName, string password, Authe { if (response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && response is SessionSetupResponse) { - byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(((SessionSetupResponse)response).SecurityBuffer, domainName, userName, password, authenticationMethod, out m_sessionKey); + string spn = string.Format("cifs/{0}", m_serverName); + byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(((SessionSetupResponse)response).SecurityBuffer, domainName, userName, password, spn, authenticationMethod, out m_sessionKey); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; From d6f6af4854799279c4c9ff35ec42d5ca43920a46 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 May 2022 13:26:18 +0300 Subject: [PATCH 016/247] Updated Readme.md --- Readme.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Readme.md b/Readme.md index 3c4ef8e9..fb779449 100644 --- a/Readme.md +++ b/Readme.md @@ -63,6 +63,12 @@ NuGet Packages: [SMBLibrary.Win32](https://www.nuget.org/packages/SMBLibrary.Win32/) - Allows utilizing Integrated Windows Authentication and/or the Windows storage subsystem on a Windows host. [SMBLibrary.Adapters](https://www.nuget.org/packages/SMBLibrary.Adapters/) - IFileSystem to INTFileStore adapter for SMBLibrary. +Licensing: +========== +A commercial license of SMBLibrary is available for a fee. +This is intended for companies who are unable to use the LGPL version. +Please contact me for additional details. + Contact: ======== If you have any question, feel free to contact me. From de3ea8d4eb2122bcbd1a7b37278c4a184a003ec9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 May 2022 13:44:11 +0300 Subject: [PATCH 017/247] SSPIHelper: Code refactoring: Moved NTLM functionality to a separate class --- .../SMBLibrary.Win32.VS2005.csproj | 1 + SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs | 227 +++++++++++++++++ SMBLibrary.Win32/Security/SSPIHelper.cs | 238 ++---------------- 3 files changed, 243 insertions(+), 223 deletions(-) create mode 100644 SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj index 3c03cc37..9d52da54 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj @@ -39,6 +39,7 @@ + diff --git a/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs b/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs new file mode 100644 index 00000000..c43d8634 --- /dev/null +++ b/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs @@ -0,0 +1,227 @@ +/* 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, + * either version 3 of the License, or (at your option) any later version. + */ +using System; +using System.Runtime.InteropServices; + +namespace SMBLibrary.Win32.Security +{ + public partial class SSPIHelper + { + public static SecHandle AcquireNTLMCredentialsHandle() + { + return AcquireNTLMCredentialsHandle(null); + } + + public static SecHandle AcquireNTLMCredentialsHandle(string domainName, string userName, string password) + { + SEC_WINNT_AUTH_IDENTITY auth = GetWinNTAuthIdentity(domainName, userName, password); + return AcquireNTLMCredentialsHandle(auth); + } + + private static SecHandle AcquireNTLMCredentialsHandle(SEC_WINNT_AUTH_IDENTITY? auth) + { + SecHandle credential; + SECURITY_INTEGER expiry; + + IntPtr pAuthData; + if (auth.HasValue) + { + pAuthData = Marshal.AllocHGlobal(Marshal.SizeOf(auth.Value)); + Marshal.StructureToPtr(auth.Value, pAuthData, false); + } + else + { + pAuthData = IntPtr.Zero; + } + + uint result = AcquireCredentialsHandle(null, "NTLM", SECPKG_CRED_BOTH, IntPtr.Zero, pAuthData, IntPtr.Zero, IntPtr.Zero, out credential, out expiry); + if (pAuthData != IntPtr.Zero) + { + Marshal.FreeHGlobal(pAuthData); + } + if (result != SEC_E_OK) + { + throw new Exception("AcquireCredentialsHandle failed, Error code 0x" + result.ToString("X8")); + } + + return credential; + } + + public static byte[] GetType1Message(string userName, string password, out SecHandle clientContext) + { + return GetType1Message(String.Empty, userName, password, out clientContext); + } + + public static byte[] GetType1Message(string domainName, string userName, string password, out SecHandle clientContext) + { + SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(domainName, userName, password); + clientContext = new SecHandle(); + SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); + SecBufferDesc output = new SecBufferDesc(outputBuffer); + uint contextAttributes; + SECURITY_INTEGER expiry; + + uint result = InitializeSecurityContext(ref credentialsHandle, IntPtr.Zero, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, IntPtr.Zero, 0, ref clientContext, ref output, out contextAttributes, out expiry); + if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) + { + if (result == SEC_E_INVALID_HANDLE) + { + throw new Exception("InitializeSecurityContext failed, Invalid handle"); + } + else if (result == SEC_E_BUFFER_TOO_SMALL) + { + throw new Exception("InitializeSecurityContext failed, Buffer too small"); + } + else + { + throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8")); + } + } + FreeCredentialsHandle(ref credentialsHandle); + byte[] messageBytes = output.GetBufferBytes(0); + outputBuffer.Dispose(); + output.Dispose(); + return messageBytes; + } + + public static byte[] GetType3Message(SecHandle clientContext, byte[] type2Message) + { + SecHandle newContext = new SecHandle(); + SecBuffer inputBuffer = new SecBuffer(type2Message); + SecBufferDesc input = new SecBufferDesc(inputBuffer); + SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); + SecBufferDesc output = new SecBufferDesc(outputBuffer); + uint contextAttributes; + SECURITY_INTEGER expiry; + + uint result = InitializeSecurityContext(IntPtr.Zero, ref clientContext, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, ref input, 0, ref newContext, ref output, out contextAttributes, out expiry); + if (result != SEC_E_OK) + { + if (result == SEC_E_INVALID_HANDLE) + { + throw new Exception("InitializeSecurityContext failed, Invalid handle"); + } + else if (result == SEC_E_INVALID_TOKEN) + { + throw new Exception("InitializeSecurityContext failed, Invalid token"); + } + else if (result == SEC_E_BUFFER_TOO_SMALL) + { + throw new Exception("InitializeSecurityContext failed, Buffer too small"); + } + else + { + throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8")); + } + } + byte[] messageBytes = output.GetBufferBytes(0); + inputBuffer.Dispose(); + input.Dispose(); + outputBuffer.Dispose(); + output.Dispose(); + return messageBytes; + } + + public static byte[] GetType2Message(byte[] type1MessageBytes, out SecHandle serverContext) + { + SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(); + SecBuffer inputBuffer = new SecBuffer(type1MessageBytes); + SecBufferDesc input = new SecBufferDesc(inputBuffer); + serverContext = new SecHandle(); + SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); + SecBufferDesc output = new SecBufferDesc(outputBuffer); + uint contextAttributes; + SECURITY_INTEGER timestamp; + + uint result = AcceptSecurityContext(ref credentialsHandle, IntPtr.Zero, ref input, ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, ref serverContext, ref output, out contextAttributes, out timestamp); + if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) + { + if (result == SEC_E_INVALID_HANDLE) + { + throw new Exception("AcceptSecurityContext failed, Invalid handle"); + } + else if (result == SEC_E_INVALID_TOKEN) + { + throw new Exception("AcceptSecurityContext failed, Invalid token"); + } + else if (result == SEC_E_BUFFER_TOO_SMALL) + { + throw new Exception("AcceptSecurityContext failed, Buffer too small"); + } + else + { + throw new Exception("AcceptSecurityContext failed, Error code 0x" + result.ToString("X8")); + } + } + FreeCredentialsHandle(ref credentialsHandle); + byte[] messageBytes = output.GetBufferBytes(0); + inputBuffer.Dispose(); + input.Dispose(); + outputBuffer.Dispose(); + output.Dispose(); + return messageBytes; + } + + /// + /// AcceptSecurityContext will return SEC_E_LOGON_DENIED when the password is correct in these cases: + /// 1. The account is listed under the "Deny access to this computer from the network" list. + /// 2. 'limitblankpassworduse' is set to 1, non-guest is attempting to login with an empty password, + /// and the Guest account is disabled, has non-empty pasword set or listed under the "Deny access to this computer from the network" list. + /// + /// Note: "If the Guest account is enabled, SSPI logon may succeed as Guest for user credentials that are not valid". + /// + /// + /// 1. 'limitblankpassworduse' will not affect the Guest account. + /// 2. Listing the user in the "Deny access to this computer from the network" or the "Deny logon locally" lists will not affect AcceptSecurityContext if all of these conditions are met. + /// - 'limitblankpassworduse' is set to 1. + /// - The user has an empty password set. + /// - Guest is NOT listed in the "Deny access to this computer from the network" list. + /// - Guest is enabled and has empty pasword set. + /// + public static bool AuthenticateType3Message(SecHandle serverContext, byte[] type3MessageBytes) + { + SecHandle newContext = new SecHandle(); + SecBuffer inputBuffer = new SecBuffer(type3MessageBytes); + SecBufferDesc input = new SecBufferDesc(inputBuffer); + SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); + SecBufferDesc output = new SecBufferDesc(outputBuffer); + uint contextAttributes; + SECURITY_INTEGER timestamp; + + uint result = AcceptSecurityContext(IntPtr.Zero, ref serverContext, ref input, ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, ref newContext, ref output, out contextAttributes, out timestamp); + + inputBuffer.Dispose(); + input.Dispose(); + outputBuffer.Dispose(); + output.Dispose(); + + if (result == SEC_E_OK) + { + return true; + } + else if ((uint)result == SEC_E_LOGON_DENIED) + { + return false; + } + else + { + if (result == SEC_E_INVALID_HANDLE) + { + throw new Exception("AcceptSecurityContext failed, Invalid handle"); + } + else if (result == SEC_E_INVALID_TOKEN) + { + throw new Exception("AcceptSecurityContext failed, Invalid security token"); + } + else + { + throw new Exception("AcceptSecurityContext failed, Error code 0x" + result.ToString("X8")); + } + } + } + } +} diff --git a/SMBLibrary.Win32/Security/SSPIHelper.cs b/SMBLibrary.Win32/Security/SSPIHelper.cs index 87aa1b04..dbd7760f 100644 --- a/SMBLibrary.Win32/Security/SSPIHelper.cs +++ b/SMBLibrary.Win32/Security/SSPIHelper.cs @@ -5,7 +5,6 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; namespace SMBLibrary.Win32.Security @@ -17,7 +16,7 @@ public struct SecHandle public IntPtr dwUpper; }; - public class SSPIHelper + public partial class SSPIHelper { private const int MAX_TOKEN_SIZE = 12000; @@ -50,7 +49,7 @@ public class SSPIHelper private const uint SECPKG_ATTR_NAME = 1; // Username private const uint SECPKG_ATTR_SESSION_KEY = 9; private const uint SECPKG_ATTR_ACCESS_TOKEN = 18; - + [StructLayout(LayoutKind.Sequential)] private struct SECURITY_INTEGER { @@ -179,226 +178,6 @@ public extern static uint DeleteSecurityContext( ref SecHandle phContext ); - public static SecHandle AcquireNTLMCredentialsHandle() - { - return AcquireNTLMCredentialsHandle(null); - } - - public static SecHandle AcquireNTLMCredentialsHandle(string domainName, string userName, string password) - { - SEC_WINNT_AUTH_IDENTITY auth = new SEC_WINNT_AUTH_IDENTITY(); - auth.Domain = domainName; - auth.DomainLength = (uint)domainName.Length; - auth.User = userName; - auth.UserLength = (uint)userName.Length; - auth.Password = password; - auth.PasswordLength = (uint)password.Length; - auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; - return AcquireNTLMCredentialsHandle(auth); - } - - private static SecHandle AcquireNTLMCredentialsHandle(SEC_WINNT_AUTH_IDENTITY? auth) - { - SecHandle credential; - SECURITY_INTEGER expiry; - - IntPtr pAuthData; - if (auth.HasValue) - { - pAuthData = Marshal.AllocHGlobal(Marshal.SizeOf(auth.Value)); - Marshal.StructureToPtr(auth.Value, pAuthData, false); - } - else - { - pAuthData = IntPtr.Zero; - } - - uint result = AcquireCredentialsHandle(null, "NTLM", SECPKG_CRED_BOTH, IntPtr.Zero, pAuthData, IntPtr.Zero, IntPtr.Zero, out credential, out expiry); - if (pAuthData != IntPtr.Zero) - { - Marshal.FreeHGlobal(pAuthData); - } - if (result != SEC_E_OK) - { - throw new Exception("AcquireCredentialsHandle failed, Error code 0x" + result.ToString("X8")); - } - - return credential; - } - - public static byte[] GetType1Message(string userName, string password, out SecHandle clientContext) - { - return GetType1Message(String.Empty, userName, password, out clientContext); - } - - public static byte[] GetType1Message(string domainName, string userName, string password, out SecHandle clientContext) - { - SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(domainName, userName, password); - clientContext = new SecHandle(); - SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); - SecBufferDesc output = new SecBufferDesc(outputBuffer); - uint contextAttributes; - SECURITY_INTEGER expiry; - - uint result = InitializeSecurityContext(ref credentialsHandle, IntPtr.Zero, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, IntPtr.Zero, 0, ref clientContext, ref output, out contextAttributes, out expiry); - if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) - { - if (result == SEC_E_INVALID_HANDLE) - { - throw new Exception("InitializeSecurityContext failed, Invalid handle"); - } - else if (result == SEC_E_BUFFER_TOO_SMALL) - { - throw new Exception("InitializeSecurityContext failed, Buffer too small"); - } - else - { - throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8")); - } - } - FreeCredentialsHandle(ref credentialsHandle); - byte[] messageBytes = output.GetBufferBytes(0); - outputBuffer.Dispose(); - output.Dispose(); - return messageBytes; - } - - public static byte[] GetType3Message(SecHandle clientContext, byte[] type2Message) - { - SecHandle newContext = new SecHandle(); - SecBuffer inputBuffer = new SecBuffer(type2Message); - SecBufferDesc input = new SecBufferDesc(inputBuffer); - SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); - SecBufferDesc output = new SecBufferDesc(outputBuffer); - uint contextAttributes; - SECURITY_INTEGER expiry; - - uint result = InitializeSecurityContext(IntPtr.Zero, ref clientContext, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, ref input, 0, ref newContext, ref output, out contextAttributes, out expiry); - if (result != SEC_E_OK) - { - if (result == SEC_E_INVALID_HANDLE) - { - throw new Exception("InitializeSecurityContext failed, Invalid handle"); - } - else if (result == SEC_E_INVALID_TOKEN) - { - throw new Exception("InitializeSecurityContext failed, Invalid token"); - } - else if (result == SEC_E_BUFFER_TOO_SMALL) - { - throw new Exception("InitializeSecurityContext failed, Buffer too small"); - } - else - { - throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8")); - } - } - byte[] messageBytes = output.GetBufferBytes(0); - inputBuffer.Dispose(); - input.Dispose(); - outputBuffer.Dispose(); - output.Dispose(); - return messageBytes; - } - - public static byte[] GetType2Message(byte[] type1MessageBytes, out SecHandle serverContext) - { - SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(); - SecBuffer inputBuffer = new SecBuffer(type1MessageBytes); - SecBufferDesc input = new SecBufferDesc(inputBuffer); - serverContext = new SecHandle(); - SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); - SecBufferDesc output = new SecBufferDesc(outputBuffer); - uint contextAttributes; - SECURITY_INTEGER timestamp; - - uint result = AcceptSecurityContext(ref credentialsHandle, IntPtr.Zero, ref input, ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, ref serverContext, ref output, out contextAttributes, out timestamp); - if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) - { - if (result == SEC_E_INVALID_HANDLE) - { - throw new Exception("AcceptSecurityContext failed, Invalid handle"); - } - else if (result == SEC_E_INVALID_TOKEN) - { - throw new Exception("AcceptSecurityContext failed, Invalid token"); - } - else if (result == SEC_E_BUFFER_TOO_SMALL) - { - throw new Exception("AcceptSecurityContext failed, Buffer too small"); - } - else - { - throw new Exception("AcceptSecurityContext failed, Error code 0x" + result.ToString("X8")); - } - } - FreeCredentialsHandle(ref credentialsHandle); - byte[] messageBytes = output.GetBufferBytes(0); - inputBuffer.Dispose(); - input.Dispose(); - outputBuffer.Dispose(); - output.Dispose(); - return messageBytes; - } - - /// - /// AcceptSecurityContext will return SEC_E_LOGON_DENIED when the password is correct in these cases: - /// 1. The account is listed under the "Deny access to this computer from the network" list. - /// 2. 'limitblankpassworduse' is set to 1, non-guest is attempting to login with an empty password, - /// and the Guest account is disabled, has non-empty pasword set or listed under the "Deny access to this computer from the network" list. - /// - /// Note: "If the Guest account is enabled, SSPI logon may succeed as Guest for user credentials that are not valid". - /// - /// - /// 1. 'limitblankpassworduse' will not affect the Guest account. - /// 2. Listing the user in the "Deny access to this computer from the network" or the "Deny logon locally" lists will not affect AcceptSecurityContext if all of these conditions are met. - /// - 'limitblankpassworduse' is set to 1. - /// - The user has an empty password set. - /// - Guest is NOT listed in the "Deny access to this computer from the network" list. - /// - Guest is enabled and has empty pasword set. - /// - public static bool AuthenticateType3Message(SecHandle serverContext, byte[] type3MessageBytes) - { - SecHandle newContext = new SecHandle(); - SecBuffer inputBuffer = new SecBuffer(type3MessageBytes); - SecBufferDesc input = new SecBufferDesc(inputBuffer); - SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); - SecBufferDesc output = new SecBufferDesc(outputBuffer); - uint contextAttributes; - SECURITY_INTEGER timestamp; - - uint result = AcceptSecurityContext(IntPtr.Zero, ref serverContext, ref input, ASC_REQ_INTEGRITY | ASC_REQ_CONFIDENTIALITY, SECURITY_NATIVE_DREP, ref newContext, ref output, out contextAttributes, out timestamp); - - inputBuffer.Dispose(); - input.Dispose(); - outputBuffer.Dispose(); - output.Dispose(); - - if (result == SEC_E_OK) - { - return true; - } - else if ((uint)result == SEC_E_LOGON_DENIED) - { - return false; - } - else - { - if (result == SEC_E_INVALID_HANDLE) - { - throw new Exception("AcceptSecurityContext failed, Invalid handle"); - } - else if (result == SEC_E_INVALID_TOKEN) - { - throw new Exception("AcceptSecurityContext failed, Invalid security token"); - } - else - { - throw new Exception("AcceptSecurityContext failed, Error code 0x" + result.ToString("X8")); - } - } - } - public static string GetUserName(SecHandle context) { string userName; @@ -447,5 +226,18 @@ public static IntPtr GetAccessToken(SecHandle serverContext) return IntPtr.Zero; } } + + private static SEC_WINNT_AUTH_IDENTITY GetWinNTAuthIdentity(string domainName, string userName, string password) + { + SEC_WINNT_AUTH_IDENTITY auth = new SEC_WINNT_AUTH_IDENTITY(); + auth.Domain = domainName; + auth.DomainLength = (uint)domainName.Length; + auth.User = userName; + auth.UserLength = (uint)userName.Length; + auth.Password = password; + auth.PasswordLength = (uint)password.Length; + auth.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI; + return auth; + } } } From c1979716fb3bd3f6700db93c9ae9c7124f3734f7 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 28 Oct 2022 13:38:35 +0300 Subject: [PATCH 018/247] Server: SMB2: Correctly handle invalid SessionSetup request containing a sessionID already in use --- SMBLibrary/Enums/NTStatus.cs | 1 + SMBLibrary/Server/SMB2/SessionSetupHelper.cs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Enums/NTStatus.cs b/SMBLibrary/Enums/NTStatus.cs index 9ce465e4..0a544ed6 100644 --- a/SMBLibrary/Enums/NTStatus.cs +++ b/SMBLibrary/Enums/NTStatus.cs @@ -54,6 +54,7 @@ public enum NTStatus : uint STATUS_BAD_DEVICE_TYPE = 0xC00000CB, STATUS_BAD_NETWORK_NAME = 0xC00000CC, STATUS_TOO_MANY_SESSIONS = 0xC00000CE, + STATUS_REQUEST_NOT_ACCEPTED = 0xC00000D0, STATUS_DIRECTORY_NOT_EMPTY = 0xC0000101, STATUS_NOT_A_DIRECTORY = 0xC0000103, STATUS_TOO_MANY_OPENED_FILES = 0xC000011F, diff --git a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs index 556f221d..902a8c54 100644 --- a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2022 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, @@ -51,6 +51,11 @@ internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, sessionID = allocatedSessionID.Value; response.Header.SessionID = allocatedSessionID.Value; } + else if (state.GetSession(sessionID) != null) + { + // We already have an established session associated with this sessionID, the client is in violation + return new ErrorResponse(request.CommandName, NTStatus.STATUS_REQUEST_NOT_ACCEPTED); + } if (status == NTStatus.SEC_I_CONTINUE_NEEDED) { From de28e672f96ff133562195dd355437fcdf005dd0 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 28 Oct 2022 13:49:49 +0300 Subject: [PATCH 019/247] NTFileSystemAdapter: Avoid modifications of entries returned from IFileSystem --- .../NTFileSystemAdapter.QueryDirectory.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryDirectory.cs b/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryDirectory.cs index 3a92023d..349d7937 100644 --- a/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryDirectory.cs +++ b/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryDirectory.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2022 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, @@ -50,9 +50,9 @@ public NTStatus QueryDirectory(out List result, o // The SMB1 / SMB2 specifications mandate that when zero entries are found, the server SHOULD / MUST return STATUS_NO_SUCH_FILE. // For this reason, we MUST include the current directory and/or parent directory when enumerating a directory // in order to diffrentiate between a directory that does not exist and a directory with no entries. - FileSystemEntry currentDirectory = m_fileSystem.GetEntry(path); + FileSystemEntry currentDirectory = m_fileSystem.GetEntry(path).Clone(); currentDirectory.Name = "."; - FileSystemEntry parentDirectory = m_fileSystem.GetEntry(FileSystem.GetParentDirectory(path)); + FileSystemEntry parentDirectory = m_fileSystem.GetEntry(FileSystem.GetParentDirectory(path)).Clone(); parentDirectory.Name = ".."; entries.Insert(0, parentDirectory); entries.Insert(0, currentDirectory); From 921b672437937e60e45b604d29ea4d1f32836e80 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 28 Oct 2022 14:12:32 +0300 Subject: [PATCH 020/247] SMBServer 1.4.9 --- SMBLibrary.Adapters/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/RevisionHistory.txt | 7 +++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/Properties/AssemblyInfo.cs | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs index 5bfe40d9..4dc0ea00 100644 --- a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.8.0")] -[assembly: AssemblyFileVersion("1.4.8.0")] +[assembly: AssemblyVersion("1.4.9.0")] +[assembly: AssemblyFileVersion("1.4.9.0")] diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 991139f5..97860def 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Adapters - 1.4.8 + 1.4.9 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/Properties/AssemblyInfo.cs b/SMBLibrary.Win32/Properties/AssemblyInfo.cs index 13a5087d..3dae9e78 100644 --- a/SMBLibrary.Win32/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Win32/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.8.0")] -[assembly: AssemblyFileVersion("1.4.8.0")] +[assembly: AssemblyVersion("1.4.9.0")] +[assembly: AssemblyFileVersion("1.4.9.0")] diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 65a2d6b6..654e1144 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Win32 - 1.4.8 + 1.4.9 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index e2d24a47..4af5b6d6 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.8.0")] -[assembly: AssemblyFileVersion("1.4.8.0")] +[assembly: AssemblyVersion("1.4.9.0")] +[assembly: AssemblyFileVersion("1.4.9.0")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 678b1b74..9781fec2 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -471,3 +471,10 @@ Revision History: NTStatus: Correct STATUS_PASSWORD_MUST_CHANGE value. 1.4.8 - SMBServer - Start method bugfix. + +1.4.9 - Server: SessionSetupHelper: Bugfix: Use correct sessionID for session creation in which token is accepted immediately. + Server: SessionSetupHelper: Bugfix: Trim session key if longer than 16 bytes. + Server: SMB2: Correctly handle invalid SessionSetup request containing a sessionID already in use. + Client: Fixed bug when trying to use local ip address as host name. + Client: Provide SPN with NTLMv2 token. + NTFileSystemAdapter: Avoid modifications of entries returned from IFileSystem. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 81eb0d15..5f557805 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.4.8 + 1.4.9 1573;1591 SMBLibrary false diff --git a/SMBServer/Properties/AssemblyInfo.cs b/SMBServer/Properties/AssemblyInfo.cs index de3408d7..483d93ae 100644 --- a/SMBServer/Properties/AssemblyInfo.cs +++ b/SMBServer/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("1.4.8.0")] -[assembly: AssemblyFileVersion("1.4.8.0")] +[assembly: AssemblyVersion("1.4.9.0")] +[assembly: AssemblyFileVersion("1.4.9.0")] From 7d07f65f7f03d11b153ece00ba0e96525959ea79 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 28 Oct 2022 14:47:03 +0300 Subject: [PATCH 021/247] Client: Added IsConnected property --- SMBLibrary/Client/ISMBClient.cs | 7 ++++++- SMBLibrary/Client/SMB1Client.cs | 10 +++++++++- SMBLibrary/Client/SMB2Client.cs | 10 +++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Client/ISMBClient.cs b/SMBLibrary/Client/ISMBClient.cs index 7ff3c85b..206fd257 100644 --- a/SMBLibrary/Client/ISMBClient.cs +++ b/SMBLibrary/Client/ISMBClient.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2021 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2022 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, @@ -37,5 +37,10 @@ uint MaxWriteSize { get; } + + bool IsConnected + { + get; + } } } diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index c015d23d..b4808eb6 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2021 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2022 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, @@ -674,6 +674,14 @@ public uint MaxWriteSize } } + public bool IsConnected + { + get + { + return m_isConnected; + } + } + public static void TrySendMessage(Socket socket, SMB1Message message) { SessionMessagePacket packet = new SessionMessagePacket(); diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index ba3f98cf..251709fd 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2021 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2022 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, @@ -619,6 +619,14 @@ public uint MaxWriteSize } } + public bool IsConnected + { + get + { + return m_isConnected; + } + } + public static void TrySendCommand(Socket socket, SMB2Command request, byte[] encryptionKey) { SessionMessagePacket packet = new SessionMessagePacket(); From d56a50c49b31341a8d4f0530d30bb5c621418b20 Mon Sep 17 00:00:00 2001 From: Kieran Devlin Date: Mon, 7 Nov 2022 06:02:16 +0000 Subject: [PATCH 022/247] Sets the socket address family to the one decided by the address passed in. (#148) --- SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index b4808eb6..29ba4be2 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -140,7 +140,7 @@ private bool Connect(IPAddress serverAddress, SMBTransportType transport, int po private bool ConnectSocket(IPAddress serverAddress, int port) { - m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + m_clientSocket = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 251709fd..e79cfbbc 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -145,7 +145,7 @@ private bool Connect(IPAddress serverAddress, SMBTransportType transport, int po private bool ConnectSocket(IPAddress serverAddress, int port) { - m_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + m_clientSocket = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); try { From 4aee6749569444f0d662cc49eb00df797057344f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 4 Mar 2023 19:36:28 +0200 Subject: [PATCH 023/247] Connect: Add private overload to set response timeout --- SMBLibrary/Client/SMB1Client.cs | 12 +++++++----- SMBLibrary/Client/SMB2Client.cs | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 29ba4be2..c20c60c9 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2022 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2023 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 @@ public class SMB1Client : ISMBClient private static readonly ushort ClientMaxBufferSize = 65535; // Valid range: 512 - 65535 private static readonly ushort ClientMaxMpxCount = 1; - private static readonly int ResponseTimeoutInMilliseconds = 5000; + private static readonly int DefaultResponseTimeoutInMilliseconds = 5000; private SMBTransportType m_transport; private bool m_isConnected; @@ -41,6 +41,7 @@ public class SMB1Client : ISMBClient private bool m_largeWrite; private uint m_serverMaxBufferSize; private ushort m_maxMpxCount; + private int m_responseTimeoutInMilliseconds; private object m_incomingQueueLock = new object(); private List m_incomingQueue = new List(); @@ -77,15 +78,16 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport) public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity) { int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); - return Connect(serverAddress, transport, port, forceExtendedSecurity); + return Connect(serverAddress, transport, port, forceExtendedSecurity, DefaultResponseTimeoutInMilliseconds); } - private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity) + private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) { m_transport = transport; if (!m_isConnected) { m_forceExtendedSecurity = forceExtendedSecurity; + m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; if (!ConnectSocket(serverAddress, port)) { return false; @@ -535,7 +537,7 @@ internal SMB1Message WaitForMessage(CommandName commandName) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < ResponseTimeoutInMilliseconds) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds) { lock (m_incomingQueueLock) { diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index e79cfbbc..2ccbcb0e 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2022 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2023 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,13 +25,14 @@ public class SMB2Client : ISMBClient public static readonly uint ClientMaxReadSize = 1048576; public static readonly uint ClientMaxWriteSize = 1048576; private static readonly ushort DesiredCredits = 16; - public static readonly int ResponseTimeoutInMilliseconds = 5000; + public static readonly int DefaultResponseTimeoutInMilliseconds = 5000; private string m_serverName; private SMBTransportType m_transport; private bool m_isConnected; private bool m_isLoggedIn; private Socket m_clientSocket; + private int m_responseTimeoutInMilliseconds; private object m_incomingQueueLock = new object(); private List m_incomingQueue = new List(); @@ -78,10 +79,10 @@ public bool Connect(string serverName, SMBTransportType transport) public bool Connect(IPAddress serverAddress, SMBTransportType transport) { int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); - return Connect(serverAddress, transport, port); + return Connect(serverAddress, transport, port, DefaultResponseTimeoutInMilliseconds); } - private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port) + private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) { if (m_serverName == null) { @@ -91,6 +92,7 @@ private bool Connect(IPAddress serverAddress, SMBTransportType transport, int po m_transport = transport; if (!m_isConnected) { + m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; if (!ConnectSocket(serverAddress, port)) { return false; @@ -485,7 +487,7 @@ internal SMB2Command WaitForCommand(ulong messageID) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < ResponseTimeoutInMilliseconds) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds) { lock (m_incomingQueueLock) { From 79486ffe45c10990140683980d5b70fa127641ce Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 4 Mar 2023 19:54:48 +0200 Subject: [PATCH 024/247] Client: Allow reusing client instance --- SMBLibrary/Client/SMB1Client.cs | 1 + SMBLibrary/Client/SMB2Client.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index c20c60c9..3d3754d7 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -165,6 +165,7 @@ public void Disconnect() { m_clientSocket.Disconnect(false); m_isConnected = false; + m_userID = 0; } } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 2ccbcb0e..64d7f7e3 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -170,6 +170,9 @@ public void Disconnect() { m_clientSocket.Disconnect(false); m_isConnected = false; + m_messageID = 0; + m_sessionID = 0; + m_availableCredits = 1; } } From 3d420c5256d760b5d358eecb7f163d5de4146a14 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 4 Mar 2023 20:13:05 +0200 Subject: [PATCH 025/247] Client: Prefer IPv4 when resolving DNS hostname --- SMBLibrary/Client/Helpers/IPAddressHelper.cs | 27 ++++++++++++++++++++ SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- SMBLibrary/SMBLibrary.VS2005.csproj | 1 + 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 SMBLibrary/Client/Helpers/IPAddressHelper.cs diff --git a/SMBLibrary/Client/Helpers/IPAddressHelper.cs b/SMBLibrary/Client/Helpers/IPAddressHelper.cs new file mode 100644 index 00000000..3a43b91f --- /dev/null +++ b/SMBLibrary/Client/Helpers/IPAddressHelper.cs @@ -0,0 +1,27 @@ +/* Copyright (C) 2023 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.Net; +using System.Net.Sockets; + +namespace SMBLibrary.Client +{ + public class IPAddressHelper + { + public static IPAddress SelectAddressPreferIPv4(IPAddress[] hostAddresses) + { + foreach (IPAddress hostAddress in hostAddresses) + { + if (hostAddress.AddressFamily == AddressFamily.InterNetwork) + { + return hostAddress; + } + } + + return hostAddresses[0]; + } + } +} diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 3d3754d7..13e2e1c6 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -66,7 +66,7 @@ public bool Connect(string serverName, SMBTransportType transport) { throw new Exception(String.Format("Cannot resolve host name {0} to an IP address", serverName)); } - IPAddress serverAddress = hostAddresses[0]; + IPAddress serverAddress = IPAddressHelper.SelectAddressPreferIPv4(hostAddresses); return Connect(serverAddress, transport); } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 64d7f7e3..ea264217 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -72,7 +72,7 @@ public bool Connect(string serverName, SMBTransportType transport) { throw new Exception(String.Format("Cannot resolve host name {0} to an IP address", serverName)); } - IPAddress serverAddress = hostAddresses[0]; + IPAddress serverAddress = IPAddressHelper.SelectAddressPreferIPv4(hostAddresses); return Connect(serverAddress, transport); } diff --git a/SMBLibrary/SMBLibrary.VS2005.csproj b/SMBLibrary/SMBLibrary.VS2005.csproj index d8707598..12bc83f9 100644 --- a/SMBLibrary/SMBLibrary.VS2005.csproj +++ b/SMBLibrary/SMBLibrary.VS2005.csproj @@ -57,6 +57,7 @@ + From 23fbe4fecaa72f7a7d9ec270dd3fd396e1675b95 Mon Sep 17 00:00:00 2001 From: bruh <61054048+cardboardboxpepe@users.noreply.github.com> Date: Fri, 14 Apr 2023 15:27:00 -0600 Subject: [PATCH 026/247] Added markdown code block highlighting for C# (#178) --- ClientExamples.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ClientExamples.md b/ClientExamples.md index 67dc699a..ba923efb 100644 --- a/ClientExamples.md +++ b/ClientExamples.md @@ -1,6 +1,6 @@ Login and list shares: ====================== -``` +```cs SMB1Client client = new SMB1Client(); // SMB2Client can be used as well bool isConnected = client.Connect(IPAddress.Parse("192.168.1.11"), SMBTransportType.DirectTCPTransport); if (isConnected) @@ -17,7 +17,7 @@ if (isConnected) Connect to share and list files and directories - SMB1: ======================================================= -``` +```cs ISMBFileStore fileStore = client.TreeConnect("Shared", out status); if (status == NTStatus.STATUS_SUCCESS) { @@ -36,7 +36,7 @@ status = fileStore.Disconnect(); Connect to share and list files and directories - SMB2: ======================================================= -``` +```cs ISMBFileStore fileStore = client.TreeConnect("Shared", out status); if (status == NTStatus.STATUS_SUCCESS) { @@ -55,7 +55,7 @@ status = fileStore.Disconnect(); Read large file to its end: =========================== -``` +```cs ISMBFileStore fileStore = client.TreeConnect("Shared", out status); object fileHandle; FileStatus fileStatus; @@ -93,7 +93,7 @@ status = fileStore.Disconnect(); Create a file and write to it: ============================== -``` +```cs ISMBFileStore fileStore = client.TreeConnect("Shared", out status); string filePath = "NewFile.txt"; if (fileStore is SMB1FileStore) @@ -119,7 +119,7 @@ status = fileStore.Disconnect(); Write a large file: =================== -``` +```cs ISMBFileStore fileStore = client.TreeConnect("Shared", out status); if (status != NTStatus.STATUS_SUCCESS) { @@ -161,7 +161,7 @@ status = fileStore.Disconnect(); Delete file: ============ -``` +```cs ISMBFileStore fileStore = client.TreeConnect("Shared", out status); string filePath = "DeleteMe.txt"; if (fileStore is SMB1FileStore) @@ -181,4 +181,4 @@ if (status == NTStatus.STATUS_SUCCESS) status = fileStore.CloseFile(fileHandle); } status = fileStore.Disconnect(); -``` \ No newline at end of file +``` From 4c59c21670e9f1438d7704b933420c7effdffabb Mon Sep 17 00:00:00 2001 From: Gabe Kirkpatrick Date: Thu, 18 May 2023 23:20:34 -0700 Subject: [PATCH 027/247] Server: Fix issue when GSSAPI SessionKey is null (#182) --- SMBLibrary/Server/SMB1/SessionSetupHelper.cs | 4 ++-- SMBLibrary/Server/SMB2/SessionSetupHelper.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs index 89f4df52..c63b4b1e 100644 --- a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs @@ -38,7 +38,7 @@ internal static SMB1Command GetSessionSetupResponse(SMB1Header header, SessionSe object accessToken = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.AccessToken); bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; - if (sessionKey.Length > 16) + if (sessionKey != null && sessionKey.Length > 16) { // [MS-CIFS] 3.3.5.43 If the session key is equal to or longer than 16 bytes, only the least significant 16 bytes MUST be stored in Server.Session.SessionKey sessionKey = ByteReader.ReadBytes(sessionKey, 0, 16); @@ -128,7 +128,7 @@ internal static SMB1Command GetSessionSetupResponseExtended(SMB1Header header, S object accessToken = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.AccessToken); bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; - if (sessionKey.Length > 16) + if (sessionKey != null && sessionKey.Length > 16) { // [MS-CIFS] 3.3.5.43 If the session key is equal to or longer than 16 bytes, only the least significant 16 bytes MUST be stored in Server.Session.SessionKey sessionKey = ByteReader.ReadBytes(sessionKey, 0, 16); diff --git a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs index 902a8c54..8e393370 100644 --- a/SMBLibrary/Server/SMB2/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB2/SessionSetupHelper.cs @@ -71,7 +71,7 @@ internal static SMB2Command GetSessionSetupResponse(SessionSetupRequest request, object accessToken = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.AccessToken); bool? isGuest = securityProvider.GetContextAttribute(state.AuthenticationContext, GSSAttributeName.IsGuest) as bool?; - if (sessionKey.Length > 16) + if (sessionKey != null && sessionKey.Length > 16) { // [MS-SMB2] 3.3.1.8 SessionKey MUST be set to the first 16 bytes of the cryptographic key queried from the GSS protocol for this authenticated context. sessionKey = ByteReader.ReadBytes(sessionKey, 0, 16); From 0416e5670d4991b2846d732b8bb12c3844b17b45 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 20 May 2023 00:57:05 +0300 Subject: [PATCH 028/247] NTLMCryptography: Add .NET 5.0 \ 6.0 support --- .../NTLM/NTLMAuthenticationTests.cs | 12 ++++++- .../NTLM/Helpers/NTLMCryptography.cs | 31 +++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs b/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs index 224da077..a96979df 100644 --- a/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2019 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2023 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,6 +27,15 @@ public void LMv1HashTest() Assert.IsTrue(ByteUtils.AreByteArraysEqual(hash, expected)); } + // Will use weak DES key + [TestMethod] + public void LMv1HashTestEmptyPassword() + { + byte[] hash = NTLMCryptography.LMOWFv1(""); + byte[] expected = new byte[] { 0xaa, 0xd3, 0xb4, 0x35, 0xb5, 0x14, 0x04, 0xee, 0xaa, 0xd3, 0xb4, 0x35, 0xb5, 0x14, 0x04, 0xee }; + Assert.IsTrue(ByteUtils.AreByteArraysEqual(hash, expected)); + } + [TestMethod] public void NTv1HashTest() { @@ -155,6 +164,7 @@ public void NTLMv2AuthenticateMessageTest() public void TestAll() { LMv1HashTest(); + LMv1HashTestEmptyPassword(); NTv1HashTest(); NTv2HashTest(); LMv1ResponseTest(); diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 5dcdc2e1..07545207 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2023 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, @@ -78,11 +78,26 @@ public static ICryptoTransform CreateWeakDesEncryptor(CipherMode mode, byte[] rg { DES des = DES.Create(); des.Mode = mode; - DESCryptoServiceProvider sm = des as DESCryptoServiceProvider; - MethodInfo mi = sm.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance); - object[] Par = { rgbKey, mode, rgbIV, sm.FeedbackSize, 0 }; - ICryptoTransform trans = mi.Invoke(sm, Par) as ICryptoTransform; - return trans; + ICryptoTransform transform; + if (DES.IsWeakKey(rgbKey) || DES.IsSemiWeakKey(rgbKey)) + { +#if NETSTANDARD2_0 + MethodInfo getTransformCoreMethodInfo = des.GetType().GetMethod("CreateTransformCore", BindingFlags.NonPublic | BindingFlags.Static); + object[] getTransformCoreParameters = { mode, des.Padding, rgbKey, rgbIV, des.BlockSize / 8 , des.FeedbackSize / 8, des.BlockSize / 8, true }; + transform = getTransformCoreMethodInfo.Invoke(null, getTransformCoreParameters) as ICryptoTransform; +#else + DESCryptoServiceProvider desServiceProvider = des as DESCryptoServiceProvider; + MethodInfo newEncryptorMethodInfo = desServiceProvider.GetType().GetMethod("_NewEncryptor", BindingFlags.NonPublic | BindingFlags.Instance); + object[] encryptorParameters = { rgbKey, mode, rgbIV, desServiceProvider.FeedbackSize, 0 }; + transform = newEncryptorMethodInfo.Invoke(desServiceProvider, encryptorParameters) as ICryptoTransform; +#endif + } + else + { + transform = des.CreateEncryptor(rgbKey, rgbIV); + } + + return transform; } /// @@ -123,7 +138,11 @@ public static byte[] DesLongEncrypt(byte[] key, byte[] plainText) public static Encoding GetOEMEncoding() { +#if NETSTANDARD2_0 + return ASCIIEncoding.GetEncoding(28591); +#else return Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); +#endif } /// From ecce6f9311655556d47a68f46e07fc096f26b1e0 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 20 May 2023 09:50:54 +0300 Subject: [PATCH 029/247] SMBServer 1.5.0 --- SMBLibrary.Adapters/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/RevisionHistory.txt | 8 ++++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/Properties/AssemblyInfo.cs | 4 ++-- 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs index 4dc0ea00..7461c8f0 100644 --- a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.9.0")] -[assembly: AssemblyFileVersion("1.4.9.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 97860def..72f96490 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Adapters - 1.4.9 + 1.5.0 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/Properties/AssemblyInfo.cs b/SMBLibrary.Win32/Properties/AssemblyInfo.cs index 3dae9e78..8ded8cd1 100644 --- a/SMBLibrary.Win32/Properties/AssemblyInfo.cs +++ b/SMBLibrary.Win32/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.9.0")] -[assembly: AssemblyFileVersion("1.4.9.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 654e1144..917f82b0 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary.Win32 - 1.4.9 + 1.5.0 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index 4af5b6d6..65081762 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.4.9.0")] -[assembly: AssemblyFileVersion("1.4.9.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 9781fec2..5d043ef0 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -478,3 +478,11 @@ Revision History: Client: Fixed bug when trying to use local ip address as host name. Client: Provide SPN with NTLMv2 token. NTFileSystemAdapter: Avoid modifications of entries returned from IFileSystem. + +1.5.0 - Server: Fix issue when GSSAPI SessionKey is null. + Client: Added IsConnected property. + Client: Fixed IPv6 related issue. + Client: Add private overload to set response timeout in Connect method. + Client: Allow reusing client instance. + Client: Prefer IPv4 when resolving DNS hostname. + NTLMCryptography: Add .NET 5.0 \ 6.0 support. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 5f557805..3b5a66a8 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.4.9 + 1.5.0 1573;1591 SMBLibrary false diff --git a/SMBServer/Properties/AssemblyInfo.cs b/SMBServer/Properties/AssemblyInfo.cs index 483d93ae..f81215da 100644 --- a/SMBServer/Properties/AssemblyInfo.cs +++ b/SMBServer/Properties/AssemblyInfo.cs @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("1.4.9.0")] -[assembly: AssemblyFileVersion("1.4.9.0")] +[assembly: AssemblyVersion("1.5.0.0")] +[assembly: AssemblyFileVersion("1.5.0.0")] From 4c3d3b542f33557bc19c678d96ccfb264f021acf Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 2 Jun 2023 22:40:21 +0300 Subject: [PATCH 030/247] Added stream related methods to LittleEndianWriter.cs --- Utilities/ByteUtils/LittleEndianWriter.cs | 54 +++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/Utilities/ByteUtils/LittleEndianWriter.cs b/Utilities/ByteUtils/LittleEndianWriter.cs index 4816e78b..2fa75e71 100644 --- a/Utilities/ByteUtils/LittleEndianWriter.cs +++ b/Utilities/ByteUtils/LittleEndianWriter.cs @@ -11,75 +11,75 @@ namespace Utilities { public class LittleEndianWriter { - public static void WriteUInt16(byte[] buffer, int offset, ushort value) + public static void WriteInt16(byte[] buffer, int offset, short value) { byte[] bytes = LittleEndianConverter.GetBytes(value); Array.Copy(bytes, 0, buffer, offset, bytes.Length); } - public static void WriteUInt16(byte[] buffer, ref int offset, ushort value) + public static void WriteInt16(byte[] buffer, ref int offset, short value) { - WriteUInt16(buffer, offset, value); + WriteInt16(buffer, offset, value); offset += 2; } - public static void WriteInt16(byte[] buffer, int offset, short value) + public static void WriteUInt16(byte[] buffer, int offset, ushort value) { byte[] bytes = LittleEndianConverter.GetBytes(value); Array.Copy(bytes, 0, buffer, offset, bytes.Length); } - public static void WriteInt16(byte[] buffer, ref int offset, short value) + public static void WriteUInt16(byte[] buffer, ref int offset, ushort value) { - WriteInt16(buffer, offset, value); + WriteUInt16(buffer, offset, value); offset += 2; } - public static void WriteUInt32(byte[] buffer, int offset, uint value) + public static void WriteInt32(byte[] buffer, int offset, int value) { byte[] bytes = LittleEndianConverter.GetBytes(value); Array.Copy(bytes, 0, buffer, offset, bytes.Length); } - public static void WriteUInt32(byte[] buffer, ref int offset, uint value) + public static void WriteInt32(byte[] buffer, ref int offset, int value) { - WriteUInt32(buffer, offset, value); + WriteInt32(buffer, offset, value); offset += 4; } - public static void WriteInt32(byte[] buffer, int offset, int value) + public static void WriteUInt32(byte[] buffer, int offset, uint value) { byte[] bytes = LittleEndianConverter.GetBytes(value); Array.Copy(bytes, 0, buffer, offset, bytes.Length); } - public static void WriteInt32(byte[] buffer, ref int offset, int value) + public static void WriteUInt32(byte[] buffer, ref int offset, uint value) { - WriteInt32(buffer, offset, value); + WriteUInt32(buffer, offset, value); offset += 4; } - public static void WriteUInt64(byte[] buffer, int offset, ulong value) + public static void WriteInt64(byte[] buffer, int offset, long value) { byte[] bytes = LittleEndianConverter.GetBytes(value); Array.Copy(bytes, 0, buffer, offset, bytes.Length); } - public static void WriteUInt64(byte[] buffer, ref int offset, ulong value) + public static void WriteInt64(byte[] buffer, ref int offset, long value) { - WriteUInt64(buffer, offset, value); + WriteInt64(buffer, offset, value); offset += 8; } - public static void WriteInt64(byte[] buffer, int offset, long value) + public static void WriteUInt64(byte[] buffer, int offset, ulong value) { byte[] bytes = LittleEndianConverter.GetBytes(value); Array.Copy(bytes, 0, buffer, offset, bytes.Length); } - public static void WriteInt64(byte[] buffer, ref int offset, long value) + public static void WriteUInt64(byte[] buffer, ref int offset, ulong value) { - WriteInt64(buffer, offset, value); + WriteUInt64(buffer, offset, value); offset += 8; } @@ -95,6 +95,12 @@ public static void WriteGuid(byte[] buffer, ref int offset, Guid value) offset += 16; } + public static void WriteInt16(Stream stream, short value) + { + byte[] bytes = LittleEndianConverter.GetBytes(value); + stream.Write(bytes, 0, bytes.Length); + } + public static void WriteUInt16(Stream stream, ushort value) { byte[] bytes = LittleEndianConverter.GetBytes(value); @@ -112,5 +118,17 @@ public static void WriteUInt32(Stream stream, uint value) byte[] bytes = LittleEndianConverter.GetBytes(value); stream.Write(bytes, 0, bytes.Length); } + + public static void WriteInt64(Stream stream, long value) + { + byte[] bytes = LittleEndianConverter.GetBytes(value); + stream.Write(bytes, 0, bytes.Length); + } + + public static void WriteUInt64(Stream stream, ulong value) + { + byte[] bytes = LittleEndianConverter.GetBytes(value); + stream.Write(bytes, 0, bytes.Length); + } } } From dd026cf704b46e978206cc72aca37d81c06e8fdf Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 27 Jul 2023 23:14:11 +0300 Subject: [PATCH 031/247] Client: Support anonymous login --- .../Helpers/NTLMAuthenticationHelper.cs | 43 ++++++++++++++----- SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 1a0c670f..184194bb 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2018 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2023 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, @@ -15,7 +15,7 @@ namespace SMBLibrary.Client { public class NTLMAuthenticationHelper { - public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, AuthenticationMethod authenticationMethod) + public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, string userName, string password, AuthenticationMethod authenticationMethod) { bool useGSSAPI = false; if (securityBlob.Length > 0) @@ -46,9 +46,13 @@ public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, NegotiateFlags.AlwaysSign | NegotiateFlags.Version | NegotiateFlags.Use128BitEncryption | - NegotiateFlags.KeyExchange | NegotiateFlags.Use56BitEncryption; + if (!(userName == String.Empty && password == String.Empty)) + { + negotiateMessage.NegotiateFlags |= NegotiateFlags.KeyExchange; + } + if (authenticationMethod == AuthenticationMethod.NTLMv1) { negotiateMessage.NegotiateFlags |= NegotiateFlags.LanManagerSessionKey; @@ -139,6 +143,11 @@ public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainNa authenticateMessage.NegotiateFlags |= NegotiateFlags.ExtendedSessionSecurity; } + if (userName == String.Empty && password == String.Empty) + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.Anonymous; + } + authenticateMessage.UserName = userName; authenticateMessage.DomainName = domainName; authenticateMessage.WorkStation = Environment.MachineName; @@ -146,7 +155,13 @@ public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainNa byte[] keyExchangeKey; if (authenticationMethod == AuthenticationMethod.NTLMv1 || authenticationMethod == AuthenticationMethod.NTLMv1ExtendedSessionSecurity) { - if (authenticationMethod == AuthenticationMethod.NTLMv1) + // https://msdn.microsoft.com/en-us/library/cc236699.aspx + if (userName == String.Empty && password == String.Empty) + { + authenticateMessage.LmChallengeResponse = new byte[1]; + authenticateMessage.NtChallengeResponse = new byte[0]; + } + else if (authenticationMethod == AuthenticationMethod.NTLMv1) { authenticateMessage.LmChallengeResponse = NTLMCryptography.ComputeLMv1Response(challengeMessage.ServerChallenge, password); authenticateMessage.NtChallengeResponse = NTLMCryptography.ComputeNTLMv1Response(challengeMessage.ServerChallenge, password); @@ -156,25 +171,33 @@ public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainNa 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 { + // https://msdn.microsoft.com/en-us/library/cc236700.aspx NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(time, clientChallenge, challengeMessage.TargetInfo, spn); 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 + if (userName == String.Empty && password == String.Empty) + { + authenticateMessage.LmChallengeResponse = new byte[1]; + authenticateMessage.NtChallengeResponse = new byte[0]; + } + else + { + authenticateMessage.LmChallengeResponse = NTLMCryptography.ComputeLMv2Response(challengeMessage.ServerChallenge, clientChallenge, password, userName, challengeMessage.TargetName); + authenticateMessage.NtChallengeResponse = ByteUtils.Concatenate(ntProofStr, clientChallengeStructurePadded); + } + 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 diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 13e2e1c6..175f68db 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -289,7 +289,7 @@ public NTStatus Login(string domainName, string userName, string password, Authe } else // m_securityBlob != null { - byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, authenticationMethod); + byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, userName, password, authenticationMethod); if (negotiateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index ea264217..2e3eeb78 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -214,7 +214,7 @@ public NTStatus Login(string domainName, string userName, string password, Authe throw new InvalidOperationException("A connection must be successfully established before attempting login"); } - byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, authenticationMethod); + byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, userName, password, authenticationMethod); if (negotiateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; From f5d5952230bc9097c9809d3ef08522102dbba4db Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 29 Jul 2023 16:30:09 +0300 Subject: [PATCH 032/247] LittleEndianConverter: Remove unused using statement --- Utilities/Conversion/LittleEndianConverter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Utilities/Conversion/LittleEndianConverter.cs b/Utilities/Conversion/LittleEndianConverter.cs index bb494dee..4ebb6a78 100644 --- a/Utilities/Conversion/LittleEndianConverter.cs +++ b/Utilities/Conversion/LittleEndianConverter.cs @@ -5,7 +5,6 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -using System.Collections.Generic; namespace Utilities { From 4fe937b7db05e791855ca99e9e679956d8b31716 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 29 Jul 2023 16:53:47 +0300 Subject: [PATCH 033/247] Remove unused using statements --- SMBLibrary/Server/ConnectionRequestEventArgs.cs | 1 - SMBLibrary/Server/SMBServer.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/SMBLibrary/Server/ConnectionRequestEventArgs.cs b/SMBLibrary/Server/ConnectionRequestEventArgs.cs index 16ba04b0..253df6c9 100644 --- a/SMBLibrary/Server/ConnectionRequestEventArgs.cs +++ b/SMBLibrary/Server/ConnectionRequestEventArgs.cs @@ -5,7 +5,6 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -using System.Collections.Generic; using System.Net; namespace SMBLibrary.Server diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 8cc48a21..817fbd67 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -11,7 +11,6 @@ using System.Threading; using SMBLibrary.Authentication.GSSAPI; using SMBLibrary.NetBios; -using SMBLibrary.Services; using SMBLibrary.SMB1; using SMBLibrary.SMB2; using Utilities; From 2e33492ff0cc5dcf2797a20a2c1d0e45b80b4bdf Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 30 Jul 2023 00:08:20 +0300 Subject: [PATCH 034/247] Update Copyright property --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 1 + SMBLibrary.Win32/SMBLibrary.Win32.csproj | 1 + SMBLibrary/SMBLibrary.csproj | 1 + 3 files changed, 3 insertions(+) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 72f96490..31c0997c 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -8,6 +8,7 @@ 1573;1591 SMBLibrary.Adapters Tal Aloni + Tal Aloni FileSystem adapters for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 917f82b0..2e227849 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -8,6 +8,7 @@ 1573;1591 SMBLibrary.Win32 Tal Aloni + Tal Aloni Windows specific extensions for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 3b5a66a8..a1669fed 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -9,6 +9,7 @@ SMBLibrary false Tal Aloni + Tal Aloni SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary From a0b07521a1921e4fc613cf5925d1eb06ff15e8f5 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 30 Jul 2023 00:03:03 +0300 Subject: [PATCH 035/247] SMBLibrary: Increment version to 1.5.0.1 --- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/SMBLibrary.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index 65081762..407f27c4 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] +[assembly: AssemblyVersion("1.5.0.1")] +[assembly: AssemblyFileVersion("1.5.0.1")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index a1669fed..f983b694 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.5.0 + 1.5.0.1 1573;1591 SMBLibrary false From 3a7e36b6d1afecf85a726c54bc8a18ff31ad2b70 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Oct 2023 12:52:55 +0300 Subject: [PATCH 036/247] Client: Added API to provide custom authentication --- .../Authentication/IAuthenticationClient.cs | 16 +++ .../NTLMAuthenticationClient.cs | 135 ++++++++++++++++++ .../Helpers/NTLMAuthenticationHelper.cs | 81 +---------- SMBLibrary/Client/SMB1Client.cs | 7 +- SMBLibrary/Client/SMB2Client.cs | 14 +- 5 files changed, 172 insertions(+), 81 deletions(-) create mode 100644 SMBLibrary/Client/Authentication/IAuthenticationClient.cs create mode 100644 SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs diff --git a/SMBLibrary/Client/Authentication/IAuthenticationClient.cs b/SMBLibrary/Client/Authentication/IAuthenticationClient.cs new file mode 100644 index 00000000..fe9115cb --- /dev/null +++ b/SMBLibrary/Client/Authentication/IAuthenticationClient.cs @@ -0,0 +1,16 @@ +/* Copyright (C) 2023-2023 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. + */ +namespace SMBLibrary.Client.Authentication +{ + public interface IAuthenticationClient + { + /// Credentials blob or null if security blob is invalid + byte[] InitializeSecurityContext(byte[] securityBlob); + + byte[] GetSessionKey(); + } +} diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs new file mode 100644 index 00000000..50c37392 --- /dev/null +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -0,0 +1,135 @@ +/* Copyright (C) 2017-2023 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 SMBLibrary.Authentication.GSSAPI; +using System.Collections.Generic; +using Utilities; + +namespace SMBLibrary.Client.Authentication +{ + public class NTLMAuthenticationClient : IAuthenticationClient + { + private string m_domainName; + private string m_userName; + private string m_password; + private string m_spn; + private byte[] m_sessionKey; + private AuthenticationMethod m_authenticationMethod; + + private bool m_isNegotiationMessageAcquired = false; + + public NTLMAuthenticationClient(string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod) + { + m_domainName = domainName; + m_userName = userName; + m_password = password; + m_spn = spn; + m_authenticationMethod = authenticationMethod; + } + + public byte[] InitializeSecurityContext(byte[] securityBlob) + { + if (!m_isNegotiationMessageAcquired) + { + m_isNegotiationMessageAcquired = true; + return GetNegotiateMessage(securityBlob); + } + else + { + return GetAuthenticateMessage(securityBlob); + } + } + + protected virtual byte[] GetNegotiateMessage(byte[] securityBlob) + { + bool useGSSAPI = false; + if (securityBlob.Length > 0) + { + SimpleProtectedNegotiationTokenInit spnegoToken = null; + try + { + spnegoToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, true) as SimpleProtectedNegotiationTokenInit; + } + catch + { + } + + if (spnegoToken == null || !ContainsMechanism(spnegoToken, GSSProvider.NTLMSSPIdentifier)) + { + return null; + } + useGSSAPI = true; + } + + byte[] negotiateMessageBytes = NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); + if (useGSSAPI) + { + SimpleProtectedNegotiationTokenInit outputToken = new SimpleProtectedNegotiationTokenInit(); + outputToken.MechanismTypeList = new List(); + outputToken.MechanismTypeList.Add(GSSProvider.NTLMSSPIdentifier); + outputToken.MechanismToken = negotiateMessageBytes; + return outputToken.GetBytes(true); + } + else + { + return negotiateMessageBytes; + } + } + + protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) + { + bool useGSSAPI = false; + SimpleProtectedNegotiationTokenResponse spnegoToken = null; + try + { + spnegoToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, false) as SimpleProtectedNegotiationTokenResponse; + } + catch + { + } + + byte[] challengeMessageBytes; + if (spnegoToken != null) + { + challengeMessageBytes = spnegoToken.ResponseToken; + useGSSAPI = true; + } + else + { + challengeMessageBytes = securityBlob; + } + + byte[] authenticationMessageBytes = NTLMAuthenticationHelper.GetAuthenticateMessage(challengeMessageBytes, m_domainName, m_userName, m_password, m_spn, m_authenticationMethod, out m_sessionKey); + if (useGSSAPI && authenticationMessageBytes != null) + { + SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); + outputToken.ResponseToken = authenticationMessageBytes; + return outputToken.GetBytes(); + } + else + { + return authenticationMessageBytes; + } + } + + public virtual byte[] GetSessionKey() + { + return m_sessionKey; + } + + private static bool ContainsMechanism(SimpleProtectedNegotiationTokenInit token, byte[] mechanismIdentifier) + { + for (int index = 0; index < token.MechanismTypeList.Count; index++) + { + if (ByteUtils.AreByteArraysEqual(token.MechanismTypeList[index], mechanismIdentifier)) + { + return true; + } + } + return false; + } + } +} diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 184194bb..47dc960b 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; -using SMBLibrary.Authentication.GSSAPI; using SMBLibrary.Authentication.NTLM; using Utilities; @@ -15,27 +14,8 @@ namespace SMBLibrary.Client { public class NTLMAuthenticationHelper { - public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + public static byte[] GetNegotiateMessage(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) { - bool useGSSAPI = false; - if (securityBlob.Length > 0) - { - SimpleProtectedNegotiationTokenInit inputToken = null; - try - { - inputToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, true) as SimpleProtectedNegotiationTokenInit; - } - catch - { - } - - if (inputToken == null || !ContainsMechanism(inputToken, GSSProvider.NTLMSSPIdentifier)) - { - return null; - } - useGSSAPI = true; - } - NegotiateMessage negotiateMessage = new NegotiateMessage(); negotiateMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.OEMEncoding | @@ -65,44 +45,14 @@ public static byte[] GetNegotiateMessage(byte[] securityBlob, string domainName, 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(true); - } - else - { - return negotiateMessage.GetBytes(); - } + return negotiateMessage.GetBytes(); } - public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) + public static byte[] GetAuthenticateMessage(byte[] challengeMessageBytes, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) { sessionKey = null; - bool useGSSAPI = false; - SimpleProtectedNegotiationTokenResponse inputToken = null; - try - { - inputToken = SimpleProtectedNegotiationToken.ReadToken(securityBlob, 0, false) as SimpleProtectedNegotiationTokenResponse; - } - catch - { - } - - ChallengeMessage challengeMessage; - if (inputToken != null) - { - challengeMessage = GetChallengeMessage(inputToken.ResponseToken); - useGSSAPI = true; - } - else - { - challengeMessage = GetChallengeMessage(securityBlob); - } + ChallengeMessage challengeMessage = GetChallengeMessage(challengeMessageBytes); if (challengeMessage == null) { return null; @@ -212,16 +162,7 @@ public static byte[] GetAuthenticateMessage(byte[] securityBlob, string domainNa sessionKey = keyExchangeKey; } - if (useGSSAPI) - { - SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); - outputToken.ResponseToken = authenticateMessage.GetBytes(); - return outputToken.GetBytes(); - } - else - { - return authenticateMessage.GetBytes(); - } + return authenticateMessage.GetBytes(); } private static ChallengeMessage GetChallengeMessage(byte[] messageBytes) @@ -243,17 +184,5 @@ private static ChallengeMessage GetChallengeMessage(byte[] messageBytes) } 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 175f68db..9ebf50d8 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -11,6 +11,7 @@ using System.Net.Sockets; using System.Threading; using SMBLibrary.Authentication.NTLM; +using SMBLibrary.Client.Authentication; using SMBLibrary.NetBios; using SMBLibrary.Services; using SMBLibrary.SMB1; @@ -289,7 +290,8 @@ public NTStatus Login(string domainName, string userName, string password, Authe } else // m_securityBlob != null { - byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, userName, password, authenticationMethod); + NTLMAuthenticationClient authenticationClient = new NTLMAuthenticationClient(domainName, userName, password, null, authenticationMethod); + byte[] negotiateMessage = authenticationClient.InitializeSecurityContext(m_securityBlob); if (negotiateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; @@ -308,11 +310,12 @@ public NTStatus Login(string domainName, string userName, string password, Authe 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, null, authenticationMethod, out m_sessionKey); + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(response.SecurityBlob); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; } + m_sessionKey = authenticationClient.GetSessionKey(); m_userID = reply.Header.UID; request = new SessionSetupAndXRequestExtended(); diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 2e3eeb78..4ef93a88 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -10,6 +10,7 @@ using System.Net; using System.Net.Sockets; using System.Threading; +using SMBLibrary.Client.Authentication; using SMBLibrary.NetBios; using SMBLibrary.SMB2; using Utilities; @@ -208,13 +209,20 @@ public NTStatus Login(string domainName, string userName, string password) } public NTStatus Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + { + string spn = string.Format("cifs/{0}", m_serverName); + NTLMAuthenticationClient authenticationClient = new NTLMAuthenticationClient(domainName, userName, password, spn, authenticationMethod); + return Login(authenticationClient); + } + + public NTStatus Login(IAuthenticationClient authenticationClient) { if (!m_isConnected) { throw new InvalidOperationException("A connection must be successfully established before attempting login"); } - byte[] negotiateMessage = NTLMAuthenticationHelper.GetNegotiateMessage(m_securityBlob, domainName, userName, password, authenticationMethod); + byte[] negotiateMessage = authenticationClient.InitializeSecurityContext(m_securityBlob); if (negotiateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; @@ -229,12 +237,12 @@ public NTStatus Login(string domainName, string userName, string password, Authe { if (response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && response is SessionSetupResponse) { - string spn = string.Format("cifs/{0}", m_serverName); - byte[] authenticateMessage = NTLMAuthenticationHelper.GetAuthenticateMessage(((SessionSetupResponse)response).SecurityBuffer, domainName, userName, password, spn, authenticationMethod, out m_sessionKey); + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(((SessionSetupResponse)response).SecurityBuffer); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; } + m_sessionKey = authenticationClient.GetSessionKey(); m_sessionID = response.Header.SessionID; request = new SessionSetupRequest(); From 660442e778c74bf6f5499e0efceee461c2fbcab9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Oct 2023 14:30:20 +0300 Subject: [PATCH 037/247] Client: Improve disconnection detection --- SMBLibrary/Client/SMB1Client.cs | 6 ++++-- SMBLibrary/Client/SMB1FileStore.cs | 6 +++++- SMBLibrary/Client/SMB2Client.cs | 6 ++++-- SMBLibrary/Client/SMB2FileStore.cs | 6 +++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 9ebf50d8..3ad47d45 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -688,14 +688,14 @@ public bool IsConnected } } - public static void TrySendMessage(Socket socket, SMB1Message message) + private 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) + private void TrySendPacket(Socket socket, SessionPacket packet) { try { @@ -704,9 +704,11 @@ public static void TrySendPacket(Socket socket, SessionPacket packet) } catch (SocketException) { + m_isConnected = false; } catch (ObjectDisposedException) { + m_isConnected = false; } } } diff --git a/SMBLibrary/Client/SMB1FileStore.cs b/SMBLibrary/Client/SMB1FileStore.cs index c0a41301..6ac04abd 100644 --- a/SMBLibrary/Client/SMB1FileStore.cs +++ b/SMBLibrary/Client/SMB1FileStore.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2019 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2023 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, @@ -560,6 +560,10 @@ public NTStatus Disconnect() private void TrySendMessage(SMB1Command request) { + if (!m_client.IsConnected) + { + throw new InvalidOperationException("The client is no longer connected"); + } m_client.TrySendMessage(request, m_treeID); } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 4ef93a88..eb147501 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -640,7 +640,7 @@ public bool IsConnected } } - public static void TrySendCommand(Socket socket, SMB2Command request, byte[] encryptionKey) + private void TrySendCommand(Socket socket, SMB2Command request, byte[] encryptionKey) { SessionMessagePacket packet = new SessionMessagePacket(); if (encryptionKey != null) @@ -655,7 +655,7 @@ public static void TrySendCommand(Socket socket, SMB2Command request, byte[] enc TrySendPacket(socket, packet); } - public static void TrySendPacket(Socket socket, SessionPacket packet) + private void TrySendPacket(Socket socket, SessionPacket packet) { try { @@ -664,9 +664,11 @@ public static void TrySendPacket(Socket socket, SessionPacket packet) } catch (SocketException) { + m_isConnected = false; } catch (ObjectDisposedException) { + m_isConnected = false; } } } diff --git a/SMBLibrary/Client/SMB2FileStore.cs b/SMBLibrary/Client/SMB2FileStore.cs index 64607d79..563514c7 100644 --- a/SMBLibrary/Client/SMB2FileStore.cs +++ b/SMBLibrary/Client/SMB2FileStore.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2021 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2023 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, @@ -335,6 +335,10 @@ public NTStatus Disconnect() private void TrySendCommand(SMB2Command request) { request.Header.TreeID = m_treeID; + if (!m_client.IsConnected) + { + throw new InvalidOperationException("The client is no longer connected"); + } m_client.TrySendCommand(request, m_encryptShareData); } From 45dbf282fb101b3f7d606b39034d3ca806bfe64a Mon Sep 17 00:00:00 2001 From: Jon Tirjan Date: Thu, 9 Nov 2023 12:43:32 -0600 Subject: [PATCH 038/247] SMB2Client: Add ability to control response timeout (#232) * SMB2Client Timeout Config & Exception * Revert gitignore * Revert TimeoutException --- SMBLibrary/Client/SMB2Client.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index eb147501..d5ecaaba 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -66,6 +66,11 @@ public SMB2Client() /// with a specific host name associated with the cluster and is not accessible using the node IP address or node host name. /// public bool Connect(string serverName, SMBTransportType transport) + { + return Connect(serverName, transport, DefaultResponseTimeoutInMilliseconds); + } + + public bool Connect(string serverName, SMBTransportType transport, int responseTimeoutInMilliseconds) { m_serverName = serverName; IPAddress[] hostAddresses = Dns.GetHostAddresses(serverName); @@ -74,13 +79,18 @@ public bool Connect(string serverName, SMBTransportType transport) throw new Exception(String.Format("Cannot resolve host name {0} to an IP address", serverName)); } IPAddress serverAddress = IPAddressHelper.SelectAddressPreferIPv4(hostAddresses); - return Connect(serverAddress, transport); + return Connect(serverAddress, transport, responseTimeoutInMilliseconds); } public bool Connect(IPAddress serverAddress, SMBTransportType transport) + { + return Connect(serverAddress, transport, DefaultResponseTimeoutInMilliseconds); + } + + public bool Connect(IPAddress serverAddress, SMBTransportType transport, int responseTimeoutInMilliseconds) { int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); - return Connect(serverAddress, transport, port, DefaultResponseTimeoutInMilliseconds); + return Connect(serverAddress, transport, port, responseTimeoutInMilliseconds); } private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) From 4152ab49886b929ba42323c08610e6dfc0e3a77c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 25 Nov 2023 22:44:58 +0200 Subject: [PATCH 039/247] SMBLibrary: Increment version to 1.5.0.2 --- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/SMBLibrary.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index 407f27c4..81c42fe2 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.1")] -[assembly: AssemblyFileVersion("1.5.0.1")] +[assembly: AssemblyVersion("1.5.0.2")] +[assembly: AssemblyFileVersion("1.5.0.2")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index f983b694..77281499 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.5.0.1 + 1.5.0.2 1573;1591 SMBLibrary false From 1554e2b6fc650140cd29ecf13cd432eee6956249 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 11 Dec 2023 12:43:03 +0200 Subject: [PATCH 040/247] SMBLibrary: Increment version to 1.5.0.3 --- SMBLibrary/Properties/AssemblyInfo.cs | 4 ++-- SMBLibrary/SMBLibrary.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs index 81c42fe2..c265fc17 100644 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ b/SMBLibrary/Properties/AssemblyInfo.cs @@ -31,6 +31,6 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.2")] -[assembly: AssemblyFileVersion("1.5.0.2")] +[assembly: AssemblyVersion("1.5.0.3")] +[assembly: AssemblyFileVersion("1.5.0.3")] [assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 77281499..53076bfa 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -4,7 +4,7 @@ net20;net40;netstandard2.0 false SMBLibrary - 1.5.0.2 + 1.5.0.3 1573;1591 SMBLibrary false From d6a149707649f545830268082334c91069a4fdac Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 14 Dec 2023 19:43:05 +0200 Subject: [PATCH 041/247] Client: ConnectionState: Remove unused using statements --- SMBLibrary/Client/ConnectionState.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/SMBLibrary/Client/ConnectionState.cs b/SMBLibrary/Client/ConnectionState.cs index dfd3e876..941bb544 100644 --- a/SMBLibrary/Client/ConnectionState.cs +++ b/SMBLibrary/Client/ConnectionState.cs @@ -1,16 +1,12 @@ -/* Copyright (C) 2017-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2023 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.Net; using System.Net.Sockets; using SMBLibrary.NetBios; -using SMBLibrary.SMB1; -using Utilities; namespace SMBLibrary.Client { From 94577969cd3f3f79b79c74a424dff868d7716b4c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 14 Dec 2023 19:47:35 +0200 Subject: [PATCH 042/247] NBTConnectionReceiveBuffer: Implement dispose pattern --- SMBLibrary/Client/SMB1Client.cs | 18 +++++++++++++++--- SMBLibrary/Client/SMB2Client.cs | 18 +++++++++++++++--- .../NetBios/NBTConnectionReceiveBuffer.cs | 10 +++++++--- SMBLibrary/Server/ConnectionManager.cs | 1 + SMBLibrary/Server/SMBServer.cs | 1 + 5 files changed, 39 insertions(+), 9 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 3ad47d45..412e4148 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -34,6 +34,7 @@ public class SMB1Client : ISMBClient private bool m_isConnected; private bool m_isLoggedIn; private Socket m_clientSocket; + private ConnectionState m_connectionState; private bool m_forceExtendedSecurity; private bool m_unicode; private bool m_largeFiles; @@ -154,9 +155,9 @@ private bool ConnectSocket(IPAddress serverAddress, int port) 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); + m_connectionState = new ConnectionState(m_clientSocket); + NBTConnectionReceiveBuffer buffer = m_connectionState.ReceiveBuffer; + m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), m_connectionState); return true; } @@ -165,6 +166,7 @@ public void Disconnect() if (m_isConnected) { m_clientSocket.Disconnect(false); + m_connectionState.ReceiveBuffer.Dispose(); m_isConnected = false; m_userID = 0; } @@ -418,6 +420,7 @@ private void OnClientSocketReceive(IAsyncResult ar) if (!clientSocket.Connected) { + state.ReceiveBuffer.Dispose(); return; } @@ -428,22 +431,26 @@ private void OnClientSocketReceive(IAsyncResult ar) } catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class. { + state.ReceiveBuffer.Dispose(); return; } catch (ObjectDisposedException) { Log("[ReceiveCallback] EndReceive ObjectDisposedException"); + state.ReceiveBuffer.Dispose(); return; } catch (SocketException ex) { Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); + state.ReceiveBuffer.Dispose(); return; } if (numberOfBytesReceived == 0) { m_isConnected = false; + state.ReceiveBuffer.Dispose(); } else { @@ -458,11 +465,13 @@ private void OnClientSocketReceive(IAsyncResult ar) catch (ObjectDisposedException) { m_isConnected = false; + buffer.Dispose(); Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); } catch (SocketException ex) { m_isConnected = false; + buffer.Dispose(); Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); } } @@ -481,6 +490,7 @@ private void ProcessConnectionBuffer(ConnectionState state) catch (Exception) { state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); break; } @@ -504,6 +514,7 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) { Log("Invalid SMB1 message: " + ex.Message); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); m_isConnected = false; return; } @@ -534,6 +545,7 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) { Log("Inappropriate NetBIOS session packet"); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); } } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index d5ecaaba..f7544e9c 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -33,6 +33,7 @@ public class SMB2Client : ISMBClient private bool m_isConnected; private bool m_isLoggedIn; private Socket m_clientSocket; + private ConnectionState m_connectionState; private int m_responseTimeoutInMilliseconds; private object m_incomingQueueLock = new object(); @@ -169,9 +170,9 @@ private bool ConnectSocket(IPAddress serverAddress, int port) 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); + m_connectionState = new ConnectionState(m_clientSocket); + NBTConnectionReceiveBuffer buffer = m_connectionState.ReceiveBuffer; + m_clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), m_connectionState); return true; } @@ -180,6 +181,7 @@ public void Disconnect() if (m_isConnected) { m_clientSocket.Disconnect(false); + m_connectionState.ReceiveBuffer.Dispose(); m_isConnected = false; m_messageID = 0; m_sessionID = 0; @@ -356,6 +358,7 @@ private void OnClientSocketReceive(IAsyncResult ar) if (!clientSocket.Connected) { + state.ReceiveBuffer.Dispose(); return; } @@ -366,22 +369,26 @@ private void OnClientSocketReceive(IAsyncResult ar) } catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class. { + state.ReceiveBuffer.Dispose(); return; } catch (ObjectDisposedException) { Log("[ReceiveCallback] EndReceive ObjectDisposedException"); + state.ReceiveBuffer.Dispose(); return; } catch (SocketException ex) { Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); + state.ReceiveBuffer.Dispose(); return; } if (numberOfBytesReceived == 0) { m_isConnected = false; + state.ReceiveBuffer.Dispose(); } else { @@ -397,11 +404,13 @@ private void OnClientSocketReceive(IAsyncResult ar) { m_isConnected = false; Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); + buffer.Dispose(); } catch (SocketException ex) { m_isConnected = false; Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + buffer.Dispose(); } } } @@ -419,6 +428,7 @@ private void ProcessConnectionBuffer(ConnectionState state) catch (Exception) { state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); break; } @@ -455,6 +465,7 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) Log("Invalid SMB2 response: " + ex.Message); state.ClientSocket.Close(); m_isConnected = false; + state.ReceiveBuffer.Dispose(); return; } @@ -501,6 +512,7 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) { Log("Inappropriate NetBIOS session packet"); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); } } diff --git a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs index 47148d1e..7f3a0a15 100644 --- a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs +++ b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs @@ -1,17 +1,16 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2023 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.NetBios { - public class NBTConnectionReceiveBuffer + public class NBTConnectionReceiveBuffer : IDisposable { private byte[] m_buffer; private int m_readOffset = 0; @@ -110,6 +109,11 @@ private void RemovePacketBytes() } } + public void Dispose() + { + m_buffer = null; + } + public byte[] Buffer { get diff --git a/SMBLibrary/Server/ConnectionManager.cs b/SMBLibrary/Server/ConnectionManager.cs index 383308ab..acafe368 100644 --- a/SMBLibrary/Server/ConnectionManager.cs +++ b/SMBLibrary/Server/ConnectionManager.cs @@ -42,6 +42,7 @@ public void ReleaseConnection(ConnectionState connection) connection.SendQueue.Stop(); SocketUtils.ReleaseSocket(connection.ClientSocket); connection.CloseSessions(); + connection.ReceiveBuffer.Dispose(); RemoveConnection(connection); } diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 817fbd67..95439676 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -288,6 +288,7 @@ private void ProcessConnectionBuffer(ref ConnectionState state) catch (Exception ex) { state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); state.LogToServer(Severity.Warning, "Rejected Invalid NetBIOS session packet: {0}", ex.Message); break; } From f3883fa8f5008121f7c136bf41718679cdb2f36d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 14 Dec 2023 19:51:13 +0200 Subject: [PATCH 043/247] NBTConnectionReceiveBuffer: Use ArrayPool rent buffers --- .../NetBios/NBTConnectionReceiveBuffer.cs | 19 +++++++++++++++++++ SMBLibrary/SMBLibrary.csproj | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs index 7f3a0a15..074ca80c 100644 --- a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs +++ b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs @@ -5,6 +5,9 @@ * either version 3 of the License, or (at your option) any later version. */ using System; +#if NETSTANDARD2_0 +using System.Buffers; +#endif using System.IO; using Utilities; @@ -28,17 +31,30 @@ public NBTConnectionReceiveBuffer(int bufferLength) { throw new ArgumentException("bufferLength must be large enough to hold the largest possible NBT packet"); } + +#if NETSTANDARD2_0 + m_buffer = ArrayPool.Shared.Rent(bufferLength); +#else m_buffer = new byte[bufferLength]; +#endif } public void IncreaseBufferSize(int bufferLength) { +#if NETSTANDARD2_0 + byte[] buffer = ArrayPool.Shared.Rent(bufferLength); +#else byte[] buffer = new byte[bufferLength]; +#endif if (m_bytesInBuffer > 0) { Array.Copy(m_buffer, m_readOffset, buffer, 0, m_bytesInBuffer); m_readOffset = 0; } + +#if NETSTANDARD2_0 + ArrayPool.Shared.Return(m_buffer); +#endif m_buffer = buffer; } @@ -111,6 +127,9 @@ private void RemovePacketBytes() public void Dispose() { +#if NETSTANDARD2_0 + ArrayPool.Shared.Return(m_buffer); +#endif m_buffer = null; } diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 53076bfa..b2c6e3c3 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -21,6 +21,12 @@ + + + 4.5.1 + + + From b988c3feb01b21a225611e71bc70613508008ef0 Mon Sep 17 00:00:00 2001 From: maharrabi Date: Thu, 14 Dec 2023 18:55:57 +0100 Subject: [PATCH 044/247] Add access denied ace support (#236) Co-authored-by: Mohammed Aharrabi --- .../Structures/SecurityInformation/ACE/ACE.cs | 2 + .../ACE/AccessDeniedACE.cs | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs index d27f0c88..b6707258 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs @@ -30,6 +30,8 @@ public static ACE GetAce(byte[] buffer, int offset) { case AceType.ACCESS_ALLOWED_ACE_TYPE: return new AccessAllowedACE(buffer, offset); + case AceType.ACCESS_DENIED_ACE_TYPE: + return new AccessDeniedACE(buffer, offset); default: throw new NotImplementedException(); } diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs new file mode 100644 index 00000000..bcaa0e2b --- /dev/null +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs @@ -0,0 +1,48 @@ +/* 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, + * either version 3 of the License, or (at your option) any later version. + */ +using Utilities; + +namespace SMBLibrary +{ + public class AccessDeniedACE : ACE + { + public const int FixedLength = 8; + + public AceHeader Header; + public AccessMask Mask; + public SID Sid; + + public AccessDeniedACE() + { + Header = new AceHeader(); + Header.AceType = AceType.ACCESS_DENIED_ACE_TYPE; + } + + public AccessDeniedACE(byte[] buffer, int offset) + { + Header = new AceHeader(buffer, offset + 0); + Mask = (AccessMask)LittleEndianConverter.ToUInt32(buffer, offset + 4); + Sid = new SID(buffer, offset + 8); + } + + public override void WriteBytes(byte[] buffer, ref int offset) + { + Header.AceSize = (ushort)this.Length; + Header.WriteBytes(buffer, ref offset); + LittleEndianWriter.WriteUInt32(buffer, ref offset, (uint)Mask); + Sid.WriteBytes(buffer, ref offset); + } + + public override int Length + { + get + { + return FixedLength + Sid.Length; + } + } + } +} From 0adfbd8ce14cbdd4a0cc7c9679a0797a335b7303 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 14 Dec 2023 20:07:04 +0200 Subject: [PATCH 045/247] Update Visual Studio 2005 project file --- SMBLibrary/SMBLibrary.VS2005.csproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SMBLibrary/SMBLibrary.VS2005.csproj b/SMBLibrary/SMBLibrary.VS2005.csproj index 12bc83f9..c4662e7b 100644 --- a/SMBLibrary/SMBLibrary.VS2005.csproj +++ b/SMBLibrary/SMBLibrary.VS2005.csproj @@ -55,6 +55,8 @@ + + @@ -77,6 +79,7 @@ + From 576c8c499b0577ac8cac7f5b5d785d01c3c4473a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 11:37:41 +0200 Subject: [PATCH 046/247] Remove Visual Studio 2005 Support --- ...ary.FileSystems.Abstractions.VS2005.csproj | 47 -- ...essLibrary.FileSystems.Abstractions.csproj | 2 +- .../Properties/AssemblyInfo.cs | 35 - .../Properties/AssemblyInfo.cs | 35 - .../SMBLibrary.Adapters.VS2005.csproj | 60 -- .../SMBLibrary.Adapters.csproj | 3 +- SMBLibrary.Tests/Properties/AssemblyInfo.cs | 33 - .../SMBLibrary.Tests.VS2005.csproj | 73 --- SMBLibrary.Tests/SMBLibrary.Tests.csproj | 1 - SMBLibrary.Win32/Properties/AssemblyInfo.cs | 35 - .../SMBLibrary.Win32.VS2005.csproj | 65 -- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 3 +- SMBLibrary/Properties/AssemblyInfo.cs | 36 -- SMBLibrary/SMBLibrary.VS2005.csproj | 612 ------------------ SMBLibrary/SMBLibrary.csproj | 9 +- SMBServer.VS2005.sln | 56 -- SMBServer.VS2019.sln => SMBServer.sln | 0 SMBServer/Properties/AssemblyInfo.cs | 33 - SMBServer/SMBServer.VS2005.csproj | 85 --- SMBServer/SMBServer.csproj | 2 +- Utilities/Properties/AssemblyInfo.cs | 35 - Utilities/Utilities.VS2005.csproj | 67 -- Utilities/Utilities.csproj | 2 +- 23 files changed, 12 insertions(+), 1317 deletions(-) delete mode 100644 DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.VS2005.csproj delete mode 100644 DiskAccessLibrary.FileSystems.Abstractions/Properties/AssemblyInfo.cs delete mode 100644 SMBLibrary.Adapters/Properties/AssemblyInfo.cs delete mode 100644 SMBLibrary.Adapters/SMBLibrary.Adapters.VS2005.csproj delete mode 100644 SMBLibrary.Tests/Properties/AssemblyInfo.cs delete mode 100644 SMBLibrary.Tests/SMBLibrary.Tests.VS2005.csproj delete mode 100644 SMBLibrary.Win32/Properties/AssemblyInfo.cs delete mode 100644 SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj delete mode 100644 SMBLibrary/Properties/AssemblyInfo.cs delete mode 100644 SMBLibrary/SMBLibrary.VS2005.csproj delete mode 100644 SMBServer.VS2005.sln rename SMBServer.VS2019.sln => SMBServer.sln (100%) delete mode 100644 SMBServer/Properties/AssemblyInfo.cs delete mode 100644 SMBServer/SMBServer.VS2005.csproj delete mode 100644 Utilities/Properties/AssemblyInfo.cs delete mode 100644 Utilities/Utilities.VS2005.csproj diff --git a/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.VS2005.csproj b/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.VS2005.csproj deleted file mode 100644 index 0a41fefe..00000000 --- a/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.VS2005.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {9119EC7E-AF78-4814-BF03-F3823A29A471} - Library - Properties - DiskAccessLibrary.FileSystems.Abstractions - DiskAccessLibrary.FileSystems.Abstractions - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - \ No newline at end of file diff --git a/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj b/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj index e59ed0d1..dbd4cfaf 100644 --- a/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj +++ b/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj @@ -2,13 +2,13 @@ net20;net40;netstandard2.0 - false DiskAccessLibrary.FileSystems.Abstractions 1.0.0 1573;1591 DiskAccessLibrary.FileSystems.Abstractions false Tal Aloni + Copyright © Tal Aloni 2012-2023 DiskAccessLibrary abstractions and interfaces for FileSystem implementations LGPL-3.0-or-later https://github.com/TalAloni/DynamicDiskPartitioner diff --git a/DiskAccessLibrary.FileSystems.Abstractions/Properties/AssemblyInfo.cs b/DiskAccessLibrary.FileSystems.Abstractions/Properties/AssemblyInfo.cs deleted file mode 100644 index e9a83e4f..00000000 --- a/DiskAccessLibrary.FileSystems.Abstractions/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("DiskAccessLibrary.FileSystems.Abstractions")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Tal Aloni")] -[assembly: AssemblyProduct("DiskAccessLibrary.FileSystems.Abstractions")] -[assembly: AssemblyCopyright("Copyright © Tal Aloni 2012-2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e71e5d6b-84ac-4889-810a-d18c2f6fbcbe")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs b/SMBLibrary.Adapters/Properties/AssemblyInfo.cs deleted file mode 100644 index 7461c8f0..00000000 --- a/SMBLibrary.Adapters/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SMBLibrary.Adapters")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("none")] -[assembly: AssemblyProduct("SMBLibrary.Adapters")] -[assembly: AssemblyCopyright("Copyright © none 2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("359460ac-a179-40cf-8491-e51a198e438c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.VS2005.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.VS2005.csproj deleted file mode 100644 index 9a41e01c..00000000 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.VS2005.csproj +++ /dev/null @@ -1,60 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {DF51D33B-F030-4B25-803A-3BEBC35E5BEC} - Library - Properties - SMBLibrary.Adapters - SMBLibrary.Adapters - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - {9119EC7E-AF78-4814-BF03-F3823A29A471} - DiskAccessLibrary.FileSystems.Abstractions - - - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7} - SMBLibrary.VS2005 - - - - - \ No newline at end of file diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 31c0997c..d3f77076 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -2,13 +2,12 @@ net20;net40;netstandard2.0 - false SMBLibrary.Adapters 1.5.0 1573;1591 SMBLibrary.Adapters Tal Aloni - Tal Aloni + Copyright © Tal Aloni 2014-2023 FileSystem adapters for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary.Tests/Properties/AssemblyInfo.cs b/SMBLibrary.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index abd6321f..00000000 --- a/SMBLibrary.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SMBLibrary.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Tal Aloni")] -[assembly: AssemblyProduct("SMBLibrary.Tests")] -[assembly: AssemblyCopyright("Copyright © Tal Aloni 2014-2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8ab9297a-1e84-4d7e-a080-cf2fbaf9af1d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SMBLibrary.Tests/SMBLibrary.Tests.VS2005.csproj b/SMBLibrary.Tests/SMBLibrary.Tests.VS2005.csproj deleted file mode 100644 index 68280aca..00000000 --- a/SMBLibrary.Tests/SMBLibrary.Tests.VS2005.csproj +++ /dev/null @@ -1,73 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {C79B06EB-32C1-44CA-B7E1-A891B8135658} - Exe - Properties - SMBLibrary.Tests - SMBLibrary.Tests - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - False - Components\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll - - - - - - - - - - - - - - - - - - - - {8CE25496-A52B-4841-822F-74C469D10EE7} - SMBLibrary.Win32 - - - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7} - SMBLibrary - - - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0} - Utilities - - - - - \ No newline at end of file diff --git a/SMBLibrary.Tests/SMBLibrary.Tests.csproj b/SMBLibrary.Tests/SMBLibrary.Tests.csproj index 6386f32a..6958ab10 100644 --- a/SMBLibrary.Tests/SMBLibrary.Tests.csproj +++ b/SMBLibrary.Tests/SMBLibrary.Tests.csproj @@ -2,7 +2,6 @@ net40 - false SMBLibrary.Tests SMBLibrary.Tests Exe diff --git a/SMBLibrary.Win32/Properties/AssemblyInfo.cs b/SMBLibrary.Win32/Properties/AssemblyInfo.cs deleted file mode 100644 index 8ded8cd1..00000000 --- a/SMBLibrary.Win32/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SMBLibrary.Win32")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Tal Aloni")] -[assembly: AssemblyProduct("SMBLibrary.Win32")] -[assembly: AssemblyCopyright("Copyright © Tal Aloni 2014-2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a2cd5b5c-fb90-412f-9b0d-63967b9cb2ee")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj deleted file mode 100644 index 9d52da54..00000000 --- a/SMBLibrary.Win32/SMBLibrary.Win32.VS2005.csproj +++ /dev/null @@ -1,65 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {8CE25496-A52B-4841-822F-74C469D10EE7} - Library - Properties - SMBLibrary.Win32 - SMBLibrary.Win32 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7} - SMBLibrary - - - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0} - Utilities - - - - - \ No newline at end of file diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 2e227849..d1085912 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -2,13 +2,12 @@ net20;net40;netstandard2.0 - false SMBLibrary.Win32 1.5.0 1573;1591 SMBLibrary.Win32 Tal Aloni - Tal Aloni + Copyright © Tal Aloni 2014-2023 Windows specific extensions for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary/Properties/AssemblyInfo.cs b/SMBLibrary/Properties/AssemblyInfo.cs deleted file mode 100644 index c265fc17..00000000 --- a/SMBLibrary/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SMBLibrary")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Tal Aloni")] -[assembly: AssemblyProduct("SMBLibrary")] -[assembly: AssemblyCopyright("Copyright © Tal Aloni 2014-2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("301890e4-fc53-448e-8070-79d17086f922")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.5.0.3")] -[assembly: AssemblyFileVersion("1.5.0.3")] -[assembly: InternalsVisibleTo("SMBLibrary.Tests")] \ No newline at end of file diff --git a/SMBLibrary/SMBLibrary.VS2005.csproj b/SMBLibrary/SMBLibrary.VS2005.csproj deleted file mode 100644 index c4662e7b..00000000 --- a/SMBLibrary/SMBLibrary.VS2005.csproj +++ /dev/null @@ -1,612 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7} - Library - Properties - SMBLibrary - SMBLibrary - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0} - Utilities - - - - - \ No newline at end of file diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index b2c6e3c3..75d3689f 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -2,14 +2,13 @@ net20;net40;netstandard2.0 - false SMBLibrary 1.5.0.3 1573;1591 SMBLibrary false Tal Aloni - Tal Aloni + Copyright © Tal Aloni 2014-2023 SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary @@ -27,6 +26,12 @@ + + + <_Parameter1>SMBLibrary.Tests + + + diff --git a/SMBServer.VS2005.sln b/SMBServer.VS2005.sln deleted file mode 100644 index 587ab082..00000000 --- a/SMBServer.VS2005.sln +++ /dev/null @@ -1,56 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 9.00 -# Visual Studio 2005 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiskAccessLibrary.FileSystems.Abstractions.VS2005", "DiskAccessLibrary.FileSystems.Abstractions\DiskAccessLibrary.FileSystems.Abstractions.VS2005.csproj", "{9119EC7E-AF78-4814-BF03-F3823A29A471}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utilities.VS2005", "Utilities\Utilities.VS2005.csproj", "{6E0F2D1E-6167-4032-BA90-DEE3A99207D0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMBLibrary.VS2005", "SMBLibrary\SMBLibrary.VS2005.csproj", "{8D9E8F5D-FD13-4E4C-9723-A333DA2034A7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMBLibrary.Win32.VS2005", "SMBLibrary.Win32\SMBLibrary.Win32.VS2005.csproj", "{8CE25496-A52B-4841-822F-74C469D10EE7}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMBLibrary.Adapters.VS2005", "SMBLibrary.Adapters\SMBLibrary.Adapters.VS2005.csproj", "{DF51D33B-F030-4B25-803A-3BEBC35E5BEC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMBServer.VS2005", "SMBServer\SMBServer.VS2005.csproj", "{70D43E2A-26A2-4046-A472-5BA8C9437612}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SMBLibrary.Tests.VS2005", "SMBLibrary.Tests\SMBLibrary.Tests.VS2005.csproj", "{C79B06EB-32C1-44CA-B7E1-A891B8135658}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Release|Any CPU.Build.0 = Release|Any CPU - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Release|Any CPU.Build.0 = Release|Any CPU - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7}.Release|Any CPU.Build.0 = Release|Any CPU - {8CE25496-A52B-4841-822F-74C469D10EE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8CE25496-A52B-4841-822F-74C469D10EE7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8CE25496-A52B-4841-822F-74C469D10EE7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8CE25496-A52B-4841-822F-74C469D10EE7}.Release|Any CPU.Build.0 = Release|Any CPU - {DF51D33B-F030-4B25-803A-3BEBC35E5BEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DF51D33B-F030-4B25-803A-3BEBC35E5BEC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DF51D33B-F030-4B25-803A-3BEBC35E5BEC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DF51D33B-F030-4B25-803A-3BEBC35E5BEC}.Release|Any CPU.Build.0 = Release|Any CPU - {70D43E2A-26A2-4046-A472-5BA8C9437612}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70D43E2A-26A2-4046-A472-5BA8C9437612}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70D43E2A-26A2-4046-A472-5BA8C9437612}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70D43E2A-26A2-4046-A472-5BA8C9437612}.Release|Any CPU.Build.0 = Release|Any CPU - {C79B06EB-32C1-44CA-B7E1-A891B8135658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C79B06EB-32C1-44CA-B7E1-A891B8135658}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C79B06EB-32C1-44CA-B7E1-A891B8135658}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C79B06EB-32C1-44CA-B7E1-A891B8135658}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/SMBServer.VS2019.sln b/SMBServer.sln similarity index 100% rename from SMBServer.VS2019.sln rename to SMBServer.sln diff --git a/SMBServer/Properties/AssemblyInfo.cs b/SMBServer/Properties/AssemblyInfo.cs deleted file mode 100644 index f81215da..00000000 --- a/SMBServer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SMBServer")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Tal Aloni")] -[assembly: AssemblyProduct("SMBServer")] -[assembly: AssemblyCopyright("Copyright © Tal Aloni 2014-2020")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6efa6101-f82c-4798-99a6-11e4ee2f2588")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.5.0.0")] -[assembly: AssemblyFileVersion("1.5.0.0")] diff --git a/SMBServer/SMBServer.VS2005.csproj b/SMBServer/SMBServer.VS2005.csproj deleted file mode 100644 index ac421616..00000000 --- a/SMBServer/SMBServer.VS2005.csproj +++ /dev/null @@ -1,85 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {70D43E2A-26A2-4046-A472-5BA8C9437612} - WinExe - Properties - SMBServer - SMBServer - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - Form - - - ServerUI.cs - - - - - Designer - ServerUI.cs - - - - - - - - - {8CE25496-A52B-4841-822F-74C469D10EE7} - SMBLibrary.Win32 - - - {8D9E8F5D-FD13-4E4C-9723-A333DA2034A7} - SMBLibrary - - - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0} - Utilities - - - - - Always - - - - - \ No newline at end of file diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index 68a1edec..442d6b06 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -3,7 +3,7 @@ WinExe net20;net40;netcoreapp3.1 - false + Copyright © Tal Aloni 2014-2023 SMBServer SMBServer true diff --git a/Utilities/Properties/AssemblyInfo.cs b/Utilities/Properties/AssemblyInfo.cs deleted file mode 100644 index a0d0338d..00000000 --- a/Utilities/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Utilities")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Tal Aloni")] -[assembly: AssemblyProduct("Utilities")] -[assembly: AssemblyCopyright("Copyright © Tal Aloni 2005-2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a4607d70-de29-4ae9-8b5a-d0b9cb405727")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Utilities/Utilities.VS2005.csproj b/Utilities/Utilities.VS2005.csproj deleted file mode 100644 index 8fe889d9..00000000 --- a/Utilities/Utilities.VS2005.csproj +++ /dev/null @@ -1,67 +0,0 @@ - - - Debug - AnyCPU - 8.0.50727 - 2.0 - {6E0F2D1E-6167-4032-BA90-DEE3A99207D0} - Library - Properties - Utilities - Utilities - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Utilities/Utilities.csproj b/Utilities/Utilities.csproj index d915a62b..2cac2acf 100644 --- a/Utilities/Utilities.csproj +++ b/Utilities/Utilities.csproj @@ -2,9 +2,9 @@ net20;net40;netstandard2.0 - false Utilities Utilities + Copyright © Tal Aloni 2005-2023 From dbfab05f17e9d8f59228a66e2b2a66ee60a8c8b2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 12:06:08 +0200 Subject: [PATCH 047/247] Update Test project to MSTest v2 --- SMBLibrary.Tests/AesCcmTests.cs | 2 +- ...lStudio.QualityTools.UnitTestFramework.dll | Bin 77824 -> 0 bytes SMBLibrary.Tests/NetBiosTests.cs | 1 - SMBLibrary.Tests/Program.cs | 24 ------------------ SMBLibrary.Tests/RPCTests.cs | 4 --- SMBLibrary.Tests/SMB2SigningTests.cs | 3 --- SMBLibrary.Tests/SMBLibrary.Tests.csproj | 10 ++++---- 7 files changed, 6 insertions(+), 38 deletions(-) delete mode 100644 SMBLibrary.Tests/Components/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll delete mode 100644 SMBLibrary.Tests/Program.cs diff --git a/SMBLibrary.Tests/AesCcmTests.cs b/SMBLibrary.Tests/AesCcmTests.cs index 5482027b..6b4d75f4 100644 --- a/SMBLibrary.Tests/AesCcmTests.cs +++ b/SMBLibrary.Tests/AesCcmTests.cs @@ -4,12 +4,12 @@ * 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 Microsoft.VisualStudio.TestTools.UnitTesting; using Utilities; namespace SMBLibrary.Tests { + [TestClass] public class AesCcmTests { [TestMethod] diff --git a/SMBLibrary.Tests/Components/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll b/SMBLibrary.Tests/Components/Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll deleted file mode 100644 index 387ad78fabddc1de49a8af3223708f5af9fc241e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77824 zcmeFa34B|{)iyqN(UmOAi(>FVTj$$i`iESm49O96K zqy)-N+4r*VOMyb)Kq-ZirtDA(g|d|;r9c-7T_`Pu?>T4Y-YZ$Mp?%-)`~Sb+N5Dt( zoO9;PnVB=ooqKgP|LE@sLkMBw@5?WR*n>~{H=Sw!WGBRh!yhaZ_vJk^VvjcOnGws@ zwqeY= zm|faiqHsjM<^$A(2ubda!73fXrx3-=FP452gZ$@zt04#fq+%WQn(ko$?0#}e>Vk%0 zcPSy*-3aZhf73M)05esoi6L3Sei$Jks0Q{wUq}e2f?(FCVp~%9-2G1w$hfZTC#B`T zkPxfu6Ujs?Ao~U%scxbepK1T5qr2)8v5q(t$yWiNS&#J7{!JHRW*Yn^`ArLa(*obL zz&9=MO$&U}0^hX2H!bi@3w+Z8-?YFtE%5(P3#_K=YW$;XQ(bt(E!%{6`YIvvn2Srv zHX&jaLM#diu~YH-`?xJN^z<52h?#`iUfp z1mtHNm;3%2Jv@{?ZX)$zoIe4jjn6lsxx&N2h@)ZrDT31Q@uWEtJcAXLtx;fbNjka2 zg|l;ivkF2R%T*NJo zlSiu(P~+Q?!OY44FKU&4oFxA|;@9vGeHzry0&}pzG>oeHr#udc#FKZD5{n{RinK4v zNHsndN<|_K#KTm22JL4~JjN68dD7#mNQ7%4i6dnE;X05atv5sO#R%8A z72_r?RHH7T@=p&7|TQLCY&raYa2!8Ba&^l@$V zSE%+L@r7#UOAF8WY1QR)Ms*kUt}Zj2>N4W~TIJODBMk8V>svSRE8OlqUD$5BQvq%V z*b53L;e#pAU_4J5SOUkO!PPu(=~zFd~($8y&XJu*YN5}DM8E3O~mMQ+(-!!El~h22q9@<_cj%!!GKGogc3 zj+cB}=U7{Z6VkV7$~QLS*TdKp`Rj%mb$K$x{Ivskin9Z50>u8r_2fW!4m?U0!RFRE z;U%_r;gw*N)u8d1QBuURO$kVwG*(p1#O;O<>-Z z>rh=jcaqxu@rU06a}DxIrWKA-<*^oHEZ&cNSW1Gjwh>*gNn(-l`^)lcOLcF2A~k{6 z^2QHD!dt#F->b*5zW5v8wc@CfzpSB0G9+A?oC0Q`tiXz6dGVKcY9j+Y;i;4~eR^Kd z%sVQMR`$m++54+}iKozbG95I_R~A6`5D4m?db4nV=ZiA*IASp9x0QWu(} z36WpUlTblknI4}F!F727EAgN%HhN1-eQ|8S{8eunRc{I@9aE=Q>08MMA;yXUlZAm1 zr@2EBE2}*eIZ1ux5ePZiSBZCEI8kV1&wqv#Rptu|?~g{}*fA=`zd>jEdK4czkg z_FX=U`YAuc-LTj#pE6{Zzu7IHlQjM@DdMUb<3I6tDnDXoS5l83fjZ{b4)Dh3A>p?S z-sng4H^+M8SQhxL$Em&Py~# zq+c`PQonu;>E%}-L0MGM%EQ-lcvvInj*$X86ZXlu9Nb1O;nKD=%qeOoEMdx@Z+(?N zWwt+9YDrmLZ7NUR$>oXLJ|mlZ`Eruu3#Z7JU&0rQ`|QcSe39!&u~z0!E}+hneTeyZ zd(gwZ81U7Nx9ZGtf9(b%Vi@-2Bd_nZb|4ibsDM1VZ?B!{^~KT2{zxP(hfnUioGc|K z=LwoBQ-Rl4eij-}i!VY}6ixv%<%BQ4{PN9#K@SH_i50XW-k>Gp29vt%mnL!cBVr(` zniebHa$|*)Si8_3h)VvaCyG6YmBT=?2C5XT$RWbc;7XW9XHjlDV%_*W#VwPQy~|wR zcbP2er%YUPlS+1(r@3WvQqGsJpq5_h_tGyk1Ap~_#7%B?`=r3o>$Dm@jtf)2!L&a6 zN|gC$9f_uvDx3^Yw)@@BcGUzTO_5DwfHpuMV1%!Ncag=tw`l{?uLzf*^gIrY0qNI0 z#17Zk#UNJ5|6q3g&vfh0Nm>7UQGZ{0{mCaj9*K+M!m4d!yFv8`k3ns zDcT8n@6ayu%fVexyCaW>cD~`2oBHFukqNzhv{bJTK~H$ow$umNt_NzfS|dGAc&ekw zF5#Gf_SIJTN`GG3*e=bJL)#3R7}};YwAI)?M4bJ!D5XK`jqWEt{>z)T`VRv%f6&?<7k6? zie=RO3e+ecLxvLtmajoCtMSIM82ICudi*86+L{4Ax=6y+0fJ>JagaMXJ20cNm z!BbY^eRD80kAi01Gq^VJeNl$>PhA_7*95&a%9kfg0Qqt%dRvtv)fF)Q6Mvuv5Cj0-5M z33{CN8YiZ-OCM?f_ha~203XeS&x^5tFi7tKuI9KYSbcbck3O)bjBWv7U+c&5WcXNo;+_RAw@k24?@oCur8RoxgFCe>k%Y<#@~|n1GXa;OeX^d2W3F4 zntWyzz5@o~04Y&UlG7(E;M3+85?_w@spt?uTsu>3jl?3m^TMsLWhRc#z(yG7Kz_e& zndNaznf~o;2@SxR>+%!Zpq^}jWGfngg_s>mrA`Jevd}EVfiat>>*tG6 zZ=7`mjl`)=4cEZBCxTm0QYZ>d^OeCstX$7rFH}*qf|#Ljx7GjAgj=# z=RW=U-cqxa3W~P@6}&mo%EVOxA77By>0ea(!qBn4sD!6nztxm4{4iB8PmMDnriq3w zQG$)VHh8>O90H$DJ%eu4WkSrFZ?VOhyjB%M%B*=@t6xCHw__&<3sGBeLUgkNC$0F zf|$f2C`xj)jVID*hb0CPjiUAFuH2&tLnhu%oylVXrC=4>t17xp zjes!g0=j-8BHs-E0!<=$Xc#&x?S_xI@C)k64p`Vu+Q#q+@lF7LX}(oZR$|sJ!Y<)* zObK0J*7~f68e9K!FhrfO^a-exmdtD=z+d*F6BDa{8HsUuE1oa zbPIcy6JWQ*KZ&*w1N`AFK!NgtQk=BLagc(;*HTWR7nGi43yQ zb|754(?@+tSwZbkB?wyeCSYMOfb4-_AefK3-E>JkY z|3w)VZneL&oO7BN{^IVNz75AKC`Q!U`<;xH$PB-WjxxG29(0TwuaSJ6I?gOV8;g!c z)h`=fe-*AvPC}`)MG9fpdwFOAMuwbkqOQupO;&dv;5|-BZPO7@Mt~k6bb7A&+;=3U7zd16; zifimBL3J@Dg}+5QxI^(8SDE+UxSA9GX1Ok&ZzM9i1bZ58HG?lYWvgw?-Fw*Zvenz9 z*yNp&TT|PT8NUZ*OG}tA$$A{|?5}xMH|obf4=kT>ktK#Pqx&P}QqM?{RA$*tS;}h4 zX|qQ*YT){98BJ>!nC?21x#L5)a`&UnX)QkupOw`1i9MP5`IZS{Xw`nxH<$F0_q;jBl5XbG9=NH4j@TqV(j`;W2XIV_2${woef8RsyW2Yx8{crDk zc4Th??5*kt6cK4#RmQYco#1X&|42G&tIBem1CSi!;1489-={JoW1q?#XP?R_ZJ)}v zX`hM>t{9B|=bdrt z)aIX%P{n8?KGE}8Z9@Ca^gSGv|8MR$`^?|pese{U)mQHQzu(c@?1Gh6^4aK1~st4M*R$Il_ApS;!M z=MvLvg#!{|PGgua;dy}1qt?>xviSMH`Pg~Z;TFH1F1P@KfKLjrX-!d{0-s8hn0fI* zFvw>YB_5o|nT+EXB3B6DFu9lX2Ytaj z+DzsJWyP+`*c-kYw1U3)C2Tin)%f)q1MRX5F+~4Vy989b6mccpZi!z?evEh?Ltj?7 zRNpa>I`cB{!EE*5Fg0tpg&lEY;+tDge5aP0PMQ^VIK9S zxLl0S;Z*-~+VM6L!FwJ)%2?Ym0$!IUe*hzhDS0LXR3;KL{pLQChgq`E{E5&1gFGYseYJB} zVqUf^E$(vrusb_XTEFGa^Kid;`i{@H`pwsW{{-^;s`pp-ZB@T{bJuZ2zj?lTJe<~V z{{HLwlmABEFZ#^em8b}0wV{fSu0*6C+4O#7%leV6??<+yAKCeR%BZjVj<26&=Sz#_ zzVq$xGhbK2EaWFnVFq0tzG($#ngWz@0I_Jy!ZV# z^1jn&-mV00P4%-j=bj5{vh%3+>q@N6mN{eUj(%j1_al3!AKB;q$SR8as#{lLLO-$v z{m9n$DeFJxc4y~v+U)LZS=u_)e>}dLo%d_U^56T++m#3f`)<2=eacq2+q~rU*ckruc3b_82`ewLV$i+lvB#aZiNeMbZE_9n`0 zKe@ob+>ZSKJ)Up?eH5D8C*reafz-#ht8gY@hR>wFU7O3sfuxO6$QFH)G5REH^vTxn z$$E1VI9k}alRPQkNrrJvq49H22#;XmQV7F0Am~!4Gu_-MPQ2QiWhE~qU#yzS9S4zt z`tXIoJ30MAV&nh}1XNsIC?{{Eu$~6JCiqsA!#Eajo;`z zWC_82+hr|BwAyL*Cz%dC4 z!*>&(zSYVJgz`Pazz_=Giv-(Y^nC}dg^W@>OAC3L0ui@&~Z9^AJ=wF_juFQ(Pg%(@A_>pW+!L-kL3z zO+OP-+4Kh^wSx(j$4xIG*-wL%BMp*yr=R|ds$I$1sGrD?>?d-ZzVS*NgPsrZlbkGP z%8BF3$#$lkIIf&r$&?dE$>hmVUt1W>+`zYY@c^7&kWp66pL@gI>a?E$3ooddrk;1$O2 zJo>=+J)Azx?ep;g@8l^TUqeDqjDc_ZiKJRLb;knIJdqUjZN!TI7-iIrPUlN8FArn< zT!a;Wgj{ADG{C8J`lMoMTo`<Gq)BdiVi~{6~=~{u2;<`RG9~@Lk^m{>qW% z<(q{V(e=s4Am2$|@%hyt_=`ze-S128<;+(6r}zv%0u%0;r-X1~A?3yR+Rh2&r*p8u z_{6LpG`cZ4(X>JjnT3B03s#)y7n&T;MbN`EkMfA?$5Vd$P!!2@{tgoQ*s&2AD7Eqj zXwpddXQZ2W7TunF9LcRzw*0O$76WB-xPKK|ZV{ZUtJbaL6VTgE2;WE=DEUbWrudT} zRr1KG@wZaIPrONAN>zQ5KO#5rd;wLKYfNV}hQKJJ>kOQ?!U+mL`pCD=X~zj>J?f4W zzQ$BrVdY)UcfGyplVo}Ls#2b>aep}~OZBF*un+}tJeW@p!g}LRkuh)GC-RIDElTq@ zAwO=|^Wkcs z%wJpNvrOyq_%kqryU8p47KDwz^A+nx#ADzvH$QP%30gS{`PQ-mWY}&%ohs?v{BK%| zCY={Fy|RXM3MudHmmsknnk1pm`cbvDK@U)UWzh4yZqJYP0cOUZg-S$zIgFgR%M04% zH|VIh0EJ5T;L{;nad$tD&QbHqn@pqZ@|v;|3(s~^wdf!bXU6{8yr9przU$QJV|+oO z!3bKH2Yscm1&XIzo9#D6|T_;iouKB6zey_wb_|kt*Hor+kl_4tRY{hy$;0oL+$&$M}jL;=mNY zgMBR9c;wl_V=)jFdLkYZhrl>EE0^B0C6>vBoH0!74aj(@)z|s6R*sMbvnuV6{ z^6=BtPUzLy2FgXZ(M>acsbj4V_q+YIdbtd=LInFEZRi}!+cegy8wqbvWPK%EEkn8x zL!t?l08b_>P-9FGRCSJjY!T==HoU8VT17WW*#ND;O^WZ3M?Ql;BTFQXR4R54I$Ws}dmZFA*-+YkTykh^C6{M`rJ{yybr?WeRoF=F=CC&VY8 zzfw8;qPHT|FTC~B=4HQK+F1JG5$E6Z*Vi`w`HT6Vzx`$8rF#9Sn>Ts$Qmg!nE6yoC z=8ka#POm+_@(lWG-B6jgWkY4eS}-bj_Vk)#A84x`()dOFr;jXZOkGnt`SEA=9PtGH zu5Y?=K~3PM1>N|oskmvuX8av>`72AGYhHiUT|YZ$)lDCqxytJrk|f>tok_ zu{c(B*j?+A_*;7O_txFi^XfWt&x75Q-@dtL&4Zh^@AKfDuK2qN@w?V5#6kEw7U|n8 zA#Tot3{7$u+UGT3d~r!EM|^Jr9uC=iz&+5_@mB`^FTihtE&BH>vWGM_#o}JrJl-e7 zxA9kmzfXV-$adlL1?VR5*8u%9!P|s#CgE=g(l5d0@9}pt{vO8P8Tk7I`5(jIM3j|> zznhV_74#jxJlGfOxND)mxj0luM-LVHjJ z$^X8b`W~{3&YdOG_5gIWQd|m))W(OR(Kh3=3(sv(8Pt^U8jA3f`j;w|f>InRwl(Ue zA44TPlRaFImW?AX^&9oDR8GS4PP9S_Z9xn1#Qv3ShkAGa$H+SA>t$Vn^4(sb03C$U z5VAA(RRX?)D{6Apz-@W5Oz}#MaIUT1iRuxfMJzzG$@uI-{K;_(*^SU{5Vz!FcZoe>tPynhlUZKlN6U(^yK>VtSt_=h5{U zHf^5Uxh*`4P#cgRjba6|PO#O|-LZKx$g&Oe{AU>(#Z`S}w1Z481?eTqE_?hx%ZTGE ztXvTaTG`NDJiVn4Jy}ON?a_L5ATo^4T*4M1Qv!c9Q%W<-&>`ugZUZt^_0B}SM$N!5)Ee*TKnfQU zNJ62#D;YYmVS6av+S;8+#JXBz;be6vzBV-V#K!Fh*M}%`v^CWo?Z} zYr~tnH{wzxnu>+maeWh78}IIFn=%yz!6Ye6o2?wOnX{rv=q5vL?Q7S@;71C15_ZX` zP=9*ev$KbyPL9@Cs4E^?mx!U%L@2dB+J!naZ9f=4sgOD$7VC1`fXzV&Z$i0v%nI+Y;F_sLa;-MLd*wSccEL>e5T0z~Aq`27} zOHeRDyl~C&2&d2q?HwJVt{C|A3l=E^O%j>vL$l&t$#z`GhPsm!pD;Fi6Jm+1QIi#c zgw{seJ7R5MG$3$h##8IlA}tw;CJ?=y8&f?pM^!HcJBqoyP^=@?iP%ZjYqNUmC1M;` z7%icV@pi-)oVWcQSv(;jZqEj%v*pmIkY} z@k`|x9SfbYp|&_2p*B~e&>q2Kk$WskZ`@w5t)7ugqM_@#$uP#($Gbb)I1}2qJBbdU zF(Q>Ppp*5XMIA93N02FYz1`xWs!$xg(-Duh(G-DGhiF^9wvvW-S9j+c^bp3pT^aiM z5o#NEvcSNUA<`L5wXUxZ*+G^wrl@CE&X!#fil$(#Ew+Vc84Pf$GZke|NEB$Uy%0ed z<&fzgeOZ<*p4%2>9GRUF(U(y#ts!?O#V|{p(2mGd^K5!A6~;C-gkluc zNqjVFC4{M#ibrvvL8;MZKu$`HSxN(2*SD(q@Tdop8F90}wAMnv^2`3o~@_^LpszMwXz@TDPv@T=EG92QN|C~QNFQixHFXrd&i zWdLX+`%qzDl}>e*P9vLPoy>Nhm|YupC6IFw99`R}*N0XUcl*e!ajS;(ufZQHd#{yETPT z!xKgd%p~?+?Q7dHUnkqwbw%mW7K&+eId6~ZLr2BCL!IsG)~B$N%GEUyTNmq!C1@Yj zg9GBw#zgyO0#V-e#S;;`Y!pW&b;fuq?~L|PB^+%G-L}};_AVI3zAqYDLyJUf2Uh#~ z&|Hjv4TQ){jJhVvqDK`gWdD*m^rCu7) zuFq&U<+$5YVGN4uZ(6A-V^BJqhGZ(5piNiK6$uBNGnXDdQxiB;#6KMOip_M>;DGT5 zHqs$5-YnJuI=WeiQ9FeA4n8N~hTNx72ALp z;XcNwnH}_Qk-%LPQsHA| z(xX4z)1mK1h?V$j!5{H(iNl|i#wh})VS)|@7~^gW^9a3fzHM6K6Q%*rXGoWMh!bMk z29wqXC#OI5ya>Kw5k1{XSW>IPrYRS zM&B7eOAPcAe^5T9tMXSvx{m3Z0?Ks?)AN}&6n=)6^sFqLS2#eNRM=5iCJKv0ktKdz z^lXt|tf(a4P6n+MTZSAx#1g+7LZyU;5}iGiEI+_>^f0nqJ={Os5+{r#dge&tTsx9< z_Aq^k>7SVfMiGAu)9Fl)V!D~>m7|`84-Yeai#h*fI$?C_XiKC{}eP1=T!S8+Bcwl>X+b5WQ%ECtRn*cL-4!avdF-(mm4q~)(}8@rg__6cYK4jz{ZOHS zVi#VB^a@ZOGzW{FK)+UK4a?qRWa5Z{H2}Ir^VtPjY=;N_b~bqqayJFqo){^q9l5^z%TJP5F@@+qw;|h%s8L&{{@Y6=!iM*pSI@r+(zG+J~qdY@61*u+RPsJtqXU{tP9l2MaFDMs@Y>Sh#WG*+Cz zXd|Pr=wWm=qiS&?qw5*fi0zE-XH+XrVziG@1ieRfe}z$n>C81ot$r$_&#f1 z!02!w5h@lpD0Dwe7K`sOTFCbui^V=hi^V>aCIr18KM%A2vswx8b3lW%B|sXE;7i2f zo#oz@+TxvcpqiMRM|2C*lbD{9m41Uc?=$@~=;ECt{hJItc#yx!wA0%IA0gyU4Xgsa zDDa6H6xRnf8K!s{50I3J-v+3ZrwUeTD|Y@C)UwVkdfl^P=OsnI2Hk5B{Vu0(WO_H# zA25BG=|0dV%-Q*tCO#{pQucUU&#TKRy_jh?(<_+%lIfS_uX{8xyn^U>rb{YFe-+cM zOm~4!6hCG9tBRvg%E!#l8$_Bzn1%-t|3IW||MkkFtQ9*8K!c*RG6lN1@)WBJ59&N~NFYdpa8Nv3Zz{WH_x5R#4q)x?!UKJ&^x+9hrnLM=co z9~8F^`4{;6hfuvf9@5||(SBF`Wx?|yih;Pdcaqqwxhs(&ixX zIwNUwu=tIGMu{QfBPU-3-vs^4K}{kg^pVsqREG*NLiia;n+U45?6{=?4P8~m&COq>7due4dNsR?Z=+rJO}+z+$pYe(5KiF-0dJu`+-;z zmNwU0FX2rYYaH}C-bvWyp#9=8vC~0+6pxGZ9Q3JpT3q8GP5ZgX!+Jt~UTzg=FN+EX zm1@5dqa8FvdtH1-q1&PPmbltMrP@2<4hIdz!gKB^dbI>^L58^z9er`?FJ`y)Dx!Sn zf%ci0%IIQYd6#Pc5XBKGd)GQ%|ECzL&@AsJ{R=T(p|iYObWOXE(Z%8#?+#tpu6NKT z-PG=O&~DwURp7dhYI`d*^R=-K+N2j~bWJE_yY*u23We^2W~p|YgEr}9+8zh()(2_B z@$f0-d&hgWK2)oB(1rSNZK{K=)JJO9DfA&UtF(I@bRmAz=^+PQsn=>F@jxosyvBQz zK2B?J(4BgNHq}A*>l3s+3K_ne^hw&!9CW8XMSIaf_v_QNd3ZRJY!2}KSf8OC?Vz9P zGqpAcJ*_uuuP8JInuludIq0YQVcMS@^t67sw!cBzJka-&K41H@gWk~bx^}w2MR^DM z-qV+8D;1gv%@x`@2fd-M)KU(5Pd`R`TA@YIY|&nG&>MPGd*4Ct>22E6xMfZy90$$y z+G`GaLqA@7-$C!`o!U+GkT5jkzK``y+WijtOiyZ$I7l}(YmeYtBc!>?dI>*0@SKC* z(6?!?JLo3?RbS&Sq1n}q^%ArH_p{gbI?fR z0`136z5?S??KuaP8&_zrJ7}bFwN}a}8|R68edCN9w6hppfal|;8aHVblgP94#EZUW z;}-29g+BBxG49m9J(*;0YhU<|Gw#tYVkB4C?`u~%s0lmYJ0$Ye=1mg!Y4?GXo^WIU$5 zA(5ElKgHOq{lP(J8T+(I&U8h_NTRA`(33*%4P zLypWc|EzuP$coIrX;Y_3KhO6MGXJ3+snE6lVdg)zq$3+`{!6>kk=2@CYOg7DH}dKF z-xPWR`3!vkJ?4+P-|Zi5dh|&OJ>jo4z4{3Xy@M9?>nAJp7k`r((9d(wL1vMDy`#Cu zDAD&g=ux9gAB?ZlPzitW&oL|Yh=UfH!}NO@?eaC`Z!kyc&r2i@%Rk8&r7xRK`F4q( z{2p_({-Qz`wlR+GK%@D&3gUYGi`LEIbOfeL3iQ_>yI3?$2?FE%(68fF(>Pn zGTJ3>&VS0Bs{hE5J!MYQUw6>+=E1tRney!u_vgQCPS;Bn`nmNQ&=HJI^*x&ZmN`RT zs?gK0IYZx~(98K-jhXssjLsAL^J}!3`jFXVbC>um{~2SJzJ<}L!U~*X9I9XHptFp* z`YjH+*qEn3WOnO!C{!01WcKJkQ)psfn7LJd z*O84jx9Neyq|J$eT62d!SD{0YZYv|^Hlve3dPW7r|W-m z&{@Wr`o9#~6lgNf(eviY@-_wbo9F2j3dI6@jEnTq4tmtMR6oPfoMT?8U+SPm<~90= z!^z5d6*pH_X*cS1jCLai2YPPQr#fh`=O+CKM*H$8CT`YON+kXsm?Uo2V+v99+^nCk z5JlE4`qhjs7Fxjw&n^1*6)GyI_S~lbP@$nfx9fWuT`1~+?$CdA1bKF$IH+K}=T7}~ zg{BuA;JHhmI#0@$K=wWTScTRXO!eHYA3L99=ZVb)hkCxRKOhm;?*}?b@P=^IZ?VTl z`!p+Xq-T$k?bEI)IM(xkgYGDZc^=Y#%2p^}llYPTqC~zQ6igBivnIxo$5VKv$3;bj zP2v$pHWcVl2Ze!tVxxj-h1Yu?bI|I-Thh>3g?9qI&gJdPySi|acufC5q3<#Jn?gTi zWGtW>?#uf*qhdz8L@aPGtQ@OkZx%k_*{ioGR8sUXPzNLG_X@FBPcgdK_fb)?wO9X+ zM53U$4Cr}W)SDGr0?lXi-3qOP?C1I;3Y}QoV7;jO7Rh{< zLG~*hk9o0`JCN^neYZk;i>F#|>3bA<8R%{O_Y(PjUtDaxqx9 zeWOBCf+1_aev3kjf??}DeZNAhfIiSeOQe-<!JBi{aA%gW3)w~n;BiG&^|_YEA$&iPb*}Wkd^l(62nVEK%wQbyh$Zt z>tFh@3e5u&#-$3a1Tu^l6^a8{M)3;SE<0c|&xlAQE-9G`v|7pThsR$YbcI$hI#wdFzBB|Y7b@8f$O6XQN_I(U z80hy3-2_x*(9Z%O&U_C-bAZvT(2I;VGm<;i0mf=ZayA`c?8uOvnt{&AKo@7A?`EKz z9CVT~z_`yrbFC8NNr`-UBRfTBxJJ5JXp@wooA4-Jr-iCZ-M^XJ` z9mF+WXKVH~Su8z38Gw57=g8A&BO8L~>_#57r@ zafXup6tcm_`HpOnHQ2aT$v!E6*&JfrnkE}!>`}6kikHoy#v^I6p~iDcRt?!O<7G$I zB!(I9IjGGTZhWpZC*i_ngkc`dt>l}{C?FB92qTOt2ela^jj2lW0%(plniaZ|(L6`< zIBT@A%|UHO*!YfvnnaCpo65Iv&?Hf7+^bMKqX!k*!RRLnoi%8^Rckz{(6uakfsu^J zT4TRNqPDUJn!}Ev8eQz0#%K&9TeBeptz{%5q}J$SwAVMS@LX8wmNMU6l@|ehJ45qA zsVSbUycx196?(1m4xk@MP4PkHy?C|VBMSWisLpuBK~19Gcw3>rR91*_#s?Dl{!#gW zHO~0dL62DD4bQQ%kBq@Tvl@)yjP{Da;ODF+quN0)Srd%ujAU&m8ilJkAFqECjWR~E zeiMzMQs$#^G|?ER(A2?AK+_bWUOCWMsL*W44m4INboAg!Vv-S8r~|S|#&(6yLcYny zxeD!oY_f3`qmz9XAm0?@Mn=2Xvnj^ij4l>u4Ee}A#V9+Dyq2Xk zM;P>;Y&y%kZfKWpp>di*KO1_A??_{3tCYPm^t-+mqp6LMtlt`g?hXH8s7-b8S5E&E zR2N@5X-(+Eh~ov#O#i!i|99&bv4=!;RWCPfh(V526D~hJZR<>8O`^6PH~sJE9LyFD zWovHQP`>r%n99QiX}R3AovSy;&gELugqyZ=_2$^QTx*(e({`@j96Oh5?f+z1c)vzg zOA^P-(Vi~flhvxWo%Gtd`kC$E@@+eA-EBL)Isf1JvpTDNh?C17m!GR%Qo5lJYsB~D zXmdB0lv+7jo^&!>*!83@zU^Upf4ODl%9ZY6?p&E3%F&%<-`uo43VL(wQ6Tv{({fQd zGndP;bEVILaz@JLoB=z!IF~J7$Z68CXP)brLwrNr0xJ90Zlxc9qX|i+Ch6E-Wpf^4 zeOZ^E^h(>gT&d*Pb#eKcaMS-gb3L8rA*D0RaygkE{=2zegk@d)n#*$2c3HhSc3Cc8 z6K?wJ{72Sw({|~-IkwL(UlVTnt8%$B-oG>d|7W>mYsj(b zrfm;`cHF#X2LHn>o03EN zW=2`AW-be^z9)zM-cl>W+rBLSHLXT8uNoqqBXebQNYhNy)P!sS;@fG;o1Qj!-j*Dj z|8@2Cj8>9&+fs1}--z&f(O-1fD5xPiSh|@x+n942b1r1g6`W4bbsg)FR1>!|y_e}@p!P_4k~zOf zqZG-Sc!PEJgBs#@pr-hQbzFY7PBteym$cTGrbm3%tCarrrJcU0F3A7vvijm=^S{3T z>3P3Sy03br=j~r7T{Au1cP>0ciq;@%pQxm-O@%D2WEx^Rf$0pUhcjKmbh$%yaU7@D zIaKGTay79H9G#!EHN^EuoBS-T#vGkFns}IV$sK+=l@TZ@MY|?s%*$C?YD$hwOMW(e zk$v!DZa2hhT$ZdajWt8O$6o!7`JXfOj-;}roe^BFqyg}CQN&WYx(#4X1#?DlnmC52 z12xqeCv!D1{}ASr1&>$=YKdb&y{zw3>*fYj;wV$fTBM=_0pHi>qoo0#@6-Np2DrspxejOn#ZZ)JK9 z(+5Cr@jc4vCz$>m^iJOgp#SCj40MsD=~Q~2q=M6+5j?p~r3d-!H&4)AB;riP^Bl=B ztMl5$DPz{>4MMJXUY?*Qz1zi0V}{|$mEVk6iSMoaZp=hbdbTnsJXKqbT}WT+-z5sF zaK^0P2ua ztNN9{l>MyMZ?5_f=?^MD0{@<>m(5b~5NJERT4a@q$k4y~hlt-*S^3rar&am+?YcHL zm_J1L##ZF_=tIZi`z+uO$=?P2HauTJbQipxn4c%+kDZpkORO6^6L~Kmdo<{+-Zh~2 zfu4tFYLCx93ic23RcpViddtKc1z_`5Sf~)U>i?)b(sQf+L1j<=W$e!bT%*zKGw9gw z=Z_Y~;D~ipTK@jVBdnQ`Eyu1I52|KZ|mjZp1|9>r)p<_ylp}+3<;kd zAfKB=gH{!;$bUdIhrb(W!1D_?1<3P<15cojegV27{3bZDz=wek^i#%s0lFfbU+{$J z2$vL0W&7yUaD738wgYxP*6H2``0%rtrBl(u4T zL4Nj#Vg`N9mK%%_HD)37YYGZ?;fawNrew3Ifap-BQ)|fn!W!ZnSM##DOKf0H57Sd> z#uvV=pIz?*JfsNDh13$?pJU#(qa zk>*Plm2yVWTxHo9d{I%gZboW~dyJyU_~Jp@(8wf2_?XBPq#GiL8E(~O+_THLPkY3o z$bEiIi$zuzgAZ>{XPwiHc;sdC0iEv3JfN2i-BoxVdwVQ&7Fi>xM!sF*jL25wWBn4) zUE)@zKZ#Hq{vz@bdf~N5MSdZ^F?Wnsh;Mq#(tVimx(&R^R;)D(@x8YV9v`05e;0JU zcptPw`~h^M_yja1J_X$(J_p?%R|u*wj9>3I*O5if;^-Y6;K^?R3x~ z+WDX%?famkwHLkh_|Dvbih6N7zTs0ZzAwU{7l%|X61JYNBo|1ZTrg*WK((i$P zg|G$^J-w_R-`aQ<{92YaiC2+6i0Le*M~FetUn(Ym9wUwe-5|~fO)@=ET#xk0;_hIp z_L@FBa5891;CfC!#0?aaWomfHf{AZiTr?N-ouYP7qxc3t>FmuXx<5eFEF`+Sh|@t% z58!kurw0;kD<`_Yg6Q5#P7fw(4kg;gbgxVgWBzcW%_CSklIZSHL~j^Pbnh6V`>TkW zVdgO1T}|m5YM4_?bbo}XSxI=1(Kqd@%Ewjy{Ca zZ8M1OWqQL*md+yDJcp=xDATz_M;}3S?|e=#AiA5WxscM$i;0>`i0+qkDW{JlYGG@* zIS=3Q67((ZbNx5_N8~TekLRD1e{TLI`Pby{&Hq>apg>dL+kyK6&jfxI_)Wl5P+2gy zptaz7?ietqaiZ>N^7oS{wTJc52 zR}|k+yr=l#;wOrA?6>l<>j_{FQ-E@5R2P9$3{DWK5~NCzD#I_|4#9VehTLmlopp*q39T zpa)V$TJ$jK>^!1ZFujK9omuJ6nd8CF4&&9zO#S&$Q;7QlUxNNDP~gG)eFMai>0e?M z!yDd<9s_@6;p3p=i=F{Jq=@)OFkQ+t#<&hV)_Kr{Y<}PT2@9jYnd)!dMwj+rkk1WW_mu;tC`-x^g*W2G5rnG z!R1t6*@m+94l1YGwJ_bz^d6?aVd}3~j?#ZnMDzeoudg8eO-%1*dOy=24kG@~nZCoc z%cb&2@1jcICRb2|H39Z`h^SybY15V~#HZy$i9Zi|-*Wb#XVo;jH6( zPvxL5V85-4xA47eO}q`Ni+2#Ku&fOS9i)u_9j4JYABSsWKttMC&=FcS=t!*=bd**H zI$9eCTBS9Bj>T&gbbJ%~0MH5AfuIL#lR*#B4g#I7O#_{w9RfO2n*lmYn+4je%?3SG zI~4RVZ7%3s?Fi7rwfUe&XbVB-X^TOZXiGttY0E*EYe#~v(2fE}DcF>*LNub}-u$L33XeWc7s+|IQy7q0*Gqlq|&(Y2VJy$y$^gG(Qpyz4lfu65j z0D6&j5$I*wC7_pUmw{fVT>*N%b`|>K22fqxs$C6wn}+YZi`zkUafgO)yNf%Seowm* zoV%Fbt=$a%J)pX{SGyH-k9Irg1KORS4{F~7{jo;he}4qjj~+3^cm41Bzx3ac|91Xg z^G6m;EZA0XSn;;vod~NVcc&;_^6NRkggUJwg<~jVvIoE{b&h5h6ds;j2rn=2BS2kC@C5jzFKD@lg70Qg_uviD)_ax*C zR1|NP^P)w?kT1iT74O75q@cV!(XlK}MrWk(Ubr>gso1|&llmiLYfzhw3u38eymqf6 zzLDMmwltQ&OWs_IeJC6jPcL>>BG!hy?a>Z5qk4f4y*5f@yv#?;#Jhu*u8+3GPY~*T zYO~b)0MP)ziFS^Cx8Q{*;5AV$oRv(%IeIfAViIqo zLeORi=i)tKQ5roi7rh)$Upy5Pz8m&#s~eH!mt+rF+_x|hvaDqmhITzgIw+AlWEmI*|lYjgo-6}(P0 zwuVMaduvRM-sP#aDh|b=^wO>N)EWG$G#>vhWG!+d^QBSWl6YFBB7Q2z`Uz3oHQjf;uF~(sfGSJ+vwQ-spW6@5` z4fbqsXe=cgiMmqQR_7*{;r(M`ZgLLZr%0a*@Fq6;h^IJ(_gT{GNCiH@vXlJgL%gP0 zawSs!9g4S9(b$)Mso%_Ck9cyMo1BX`wng#Y+l6Zt19p4yXuS~W%Y%@wS`>%jt%w~l z7SCEAYu(^PjP33c42NiIj9gEOG%#y@G$H4(8Ew&x7>zQZC^X$5k~}pwa@t>*m`$%n zwF6~IY^{2^uULlHLdH;!&2hYi7X#gm%w_8n@e}5DA;MiA^$EQS7`=oSizSc5i^ULu zZ8D)6av7#@{5;9J?v5yOZcHFB=%u5m)wZi>@nH3E(kh-UWcOrJb+4+7>sYd0 zjds!8&hJHzCVEykVhTt(!w3cP0T$ z4ZI=1u$G@R#GJi>=xph>_X+NffrlZ73Mns&rq(ZuV>+9$aU)A)a!EX%LiR3-sLmF= z#?e*kY&n6oS%vLn*htII&Lm@rw8D}}?8w@L z%|ruYKA6)HU6&LDmc?gd_tLX$J?+$}Xv8zU3%WJdCWj`Xc|!~#in$WQk$~(A#X2;G z<&K0i5{YP!-6Qx_n;cw6cTRh(1HW`)D@m9sU6`;}`d)fz0w+hds>A7QNnx~PRB2v& z*M?Zzy!K>jbpz%=+Pm=Fv<|gbaVfd8q8MIcW;b3Co6VmSPjCY{(+_sr^uoymg>i=Z zoJ71+nu5f(jBaSdnv-#F39@6QlGq6A(( zx#PeGlSp82f8rVq2mO-9zJbVmwPel*j@t4r?V4vNG4&)7(1;D*uaVF8W%vSqjeD{gBDwprOPqmiAjW@|fE(i95;i)ULN1tU z%3ifJv@~)++Xz1;=Exdb8ZuD2taW`$I&4cvNs8xKLdDLE1U3mAmWqKsrdbS|3)y;mib9NE*vy>nCZA%bNAb_2M^dx@LaxP6!;Cv6z# zMUxgr8XwN`y(}&(hE1ove_goNj?cNWNbUi?F3P21`|b2oa~v#e#iU{Gr=)$dBZq1{ zF`wTa4ZS(^a~{g_xkfUSaT6<8?bdYwnBU@8-gF4os9^|^nRRkt5Id!QERs2L9 ztSX7@PPNqcJ@NH7E7;A4Ax1wXfDzTDQZ#-fuhHT0xRFd6Gn~pXUfgKR9txQTmGT{;yM5^=a@9}7aOH3v7x0iq1}WD=L{>Tr&;x)6C>K*iZdiYNp7 zWIkP_DMuzq!QNnChc3?=xs17O$k2}ch@1oLGe(vX;GJJ*%X)S82xUq#*U?SWqf1}t z;3RWnH|^-K4U8vx*cx5-aspen3?U18Gn->;qu4rQleC$B(uTKMibsXZaWAi_r~;Tw za3a=~sgEXecA{#&pvw~+yRo^>mS`*eo|9vx4Tj~$8&@dm7(Ica1w`{&MBu`3!G^=HG&zOVy0pXVkg+g z3x_%fMfl-ey0XD;xD!9MBk15>ZiO)_I{C7h6LbTG)ELO}-NutRBb4ZRoBNg$s6}?) zl7FrVQp?TBFiSm79%rf&M>Q{(IWv}uHl{Pu+9I7z%Fb~%=j`oirj*R2yOS;D3T3k0 z3o1HorlWi3YLKq1Y+gM=Ar)`MokTk1pViThxU*TfLhixo+LBlsPXyRGA_SszT!haA zdZ`81KYUfAu4V9xp&MJ~cG9nzwWl}~m*5q%Xgl6!Bn6O4$*vP~=`aVzP^x{Rz7!LV zoYJY<90{Djg0V50h%Jjd$M1B;P5~ve65_LX?j#{61qvVguuyWAwy&Gf)y9pEyoce~ zCz@zo--A0bnBDl4lBP!-&u@;kA#j&=V6Df&2l|-~i`7(P>n)YPRnn^1TsQi?8$Oll zt>jp@bMcC1-!f>YGiK3=i#HrTw9`F+R&^r~pKZ7$hsHFIevx!nJMU^ zm#-bT<=7F|9p<%nVNnoRI8cow4x;(@yP(6rvV+PvXWM8aGdzjN%*Z&=pv7+}hh!kU3@LWx6x#+!@mnT^Me`uT{Dy#$9Mz z^4C53J;6Qrbq;Meq9yoDR~^_=!_ZwxZi5sy!A?Tp94+ZK5;Z7(1rZciX9#vWCdaae zV_B*Ll5IRhF&g-sjm@kfu6^j+sg5Zz$krSWE!t|~+J~1wbT+Mo0y_u#$s2X_rdpc2 z5@%f-gdD6Q#V35sMsVR2nfeSxA@VxnC&Usn>53*Px;LU{R2oGh{FQH_)jkTt_^0zR z`;f~y_rmXBbw% zl(T=bH%izaIU68n`$Jt#@tU!h!5YZ>mCTI-S}AiONjVF6Td%oU5F>j+qU@L@W zeJvd}_nJBpEi|uoyZ2Sekmzg>os9x*iQRQ8UvwadR9JA_P(Z0DTbRH`OSLIa4b*-L zvrIL#BLjxGQM5LQ)<&8p@svqBc31A8nl*1J&AT16HFLRiSxhm%tUZ;kY_rog*x7m1 z9&iRsrag>cXXQ!0EA6(rEx?l0hFkO5_GZO5_TZd)eGbtI%qy5Sv-8kFYM(-yyD4~) zGun}*wG^YP0}tQezExK$fmtR?V?nGN0xX(xuqEe3yViB%pfB4E9GFFWupZ^`5IYNU z(#_LrCZu+PJmE}*_C;e-bs;7O=Si8YymTxPr60JOy(QM#%}dLY7&>H4J3Urp*UU8l zP7>=h?k)9*SqNQau(>^ol_ZH?z3k&mi}l0-;<_o1PvCtlOyj^6GvW5s>)*j){P_i zp87?x1Qrx|H`ivmJlA1ch67w$yRnH?_T<7Sk3XHWOnE3FuU~oEQy%f|$-Z?!_l?)% zfLR@<$U}5>lOCrzv>@Q-7Gl@AE0)d_OVJhd^n%pEh~k+JH=(=HI$V%5>)A%-69x8- zbD2@78*uArhs}59DK#(FwGPfPGkxu*8{zag1Ip(6aR{HZ4I8V=dI-8>EZYY6X7L-6 zVm>Ym7EpHF-$-tZCn>sc-72zvNhf3&m|DNui!VOdwi?2M!6DX2@ zf)`!o=9O-9KqlI0!!VcDNWQ<$Q!yX#@KS)I=r}zeMTa*{jkIq*)Q%qv<+Q6Tat9oo z-P^-+IXyArAeS%Ka44$DR?xK`g{9hTa>bGv_uVkJw$syF@RV+vWymlTR6wKSv971^ z92GX1G<{-A?UZy8xpa_hAv(^Z0bRa}AzEig(KcSGN&iS~+5O zS(1wTU+rCeY#hgRpIzQ@mlSWsTg8@S?I|nRw53?0?~})i&Bj-YvI&^BB$9R;kX282 zN0~82$~;N7>Z-m&V+3w$*ool)4r(C%LXi|mi~>bnGzOf)El?nTqybVOMGCY)Tf0Sy z)PE*z(*AyLcJKC%M}4?%4HUTM}-|mV7t9@CvqFIR&M7D4>FmClNX)1_Cg6htk ziQG|V@EV)AfcUzZDRsvk+S^M%GnK|)J$Edl0_&u2);GdSxqlnjrbq;wI1PbXmN)c9 z4R$1h^>%1UdM-tD*jRW{5Bf%MKQ*|Ld##dgs~$ypsc~_$6U-L&LeRBRQAFqDT7p2T zj5@@S!DOdnwY2oxm+Wd`1$6{AgsHY0N?loMJEEem9J#y$z?&K!Jwso2wn4&sYbYEn zyr~|Y{BR6(#$Di-Xie{cqb2}0;~cn!E5*CwI{Z;qQCww)rh(~TPlHLlufY^pk#JC* zSr&cW&d{S0swf*U2t`5^%bF~yH?SduCL?%>9AHhZIOf7Sxrss8BbyuHgcnsn776Zh zF3p9O9qj0~DKW9w$6bKGFSjl`GG$5fF5StC<8|~WZFw8AL|eLk1t2|920ahefn!69 zEzuFU_0s@KpkWwr=V7L>?X$zI&O1^%9J%dAO>T>SoeVwb21Bp+oX3}E&g1)S4g4w! z%V?zzl;x_fw(#WIB))yqK-3$*LXW)E_Pi4)w+@{CB_Bb#b$$!-(Dsu0bM$SD9@=Hp zS;y08@a9ST_U`gJS5s)?e7qmdEWU$tNqAHf{qi-92-fbZ@O%+Ra-i<%E=76FsSOt6*9Vl=ly_R*p9F3xMr!n-K8kiHOlR-Za8UF)utTR!68<|>N0D^>p{~> zHQ${FpbEw}k+li7*oBV)hJ~pJVF8d!cvKN11UZW+%7!fBYE!x~3eye3o+Tb*+CT}? zS^{oaJ%n=W(%db4#OdbhJQZ+4dRKaCUIWc&eIb97sIvzCFQ5z+lVYji$JSKP-X}q4 zOHx#sCy=V4Jx}8aBw}Eh4*qe2AGE2cTCpDh#)gzoLY#Xjud{%5N~?Qz zrlt8HU2F)7q@K8D-Lt5%7R5cXGwpeBg?%2hihZSaUyy4phYh82JC{6;7P5?1zYZOh zW7|0_v6ltf)CKt3z`TZZZlT)smQkO&KBaChx}~ljyngwz)H(+~5oHJtqeH)KJZ3*A z9j)0k5$N^e^{H_9F9f<#2TvUODiLZ6Au7o$ngVR;;RwjgjnNIwnby2E^&2RyWulE3 z4FdJJF50agxPIMHiT0)O_>It@ZuIt2ugPn4o67xl>?TrD)WSMOMB9kofQClf%=W6% zjc`emzRl$Km~2hCA2q~>bf%MJ6(iz2_^+U+#VYNupvT$g=V3e6Vci}vij;-l|f1+7Nr(W(9ov@Z5}5c)~5EC0n>I6JMK~I zw|7x+E~)Sim*vtjzo8Xpw(>?|`$q=XAf&-_4p+`;wcUb>e z7VqiE=nU-78Gcu88w@h*f@Oi1=>r4aEm3;(WX_=G?$R|4gRDb$oy!jDAT0~_olv4< zH>m5hXX?-_QuKCvG4wcut?XJ9Xiq0>CoPsLYD@D(S8XAz)2nA>j8him5b`L5I8iP` zSg*$wmi{26cB(7X5^W=+oCZDCm&0q9$|-e>lGZZkytIxPn{$$$qjkHqg`oXbneKj} z{ih#J>#S*TWfAo()m=L+pu%1WEaqO~by^kXlE2WSW!=<|=rSzC3#gI3 z8QfhUpuc_SF-Lg6JM`gi zKzEKrZ7)6VZtJK08_udKMW2JZ7+sNtx=bAKF%FFzLFe2pg6TKm) zEX^LLu4sP;mgp|_V+S`nYRF5t235Dy=V5PCV>%H@&x1oBv&5ZSYmjdW2+cXDbF4^Oyi(Bs7A((AxSv9#*LUxr38I!Y|H{W zPR@vm%UNe65(#GDBpE#G4978j(hImQ=kJs+J1^_gQ zo+O}?R}S*ZVII4AL)SVGvaw+8r5D69=B?5u&Ou~;#l3;fRpCOD~0aw>(Tja@0*&#K1#K(|HE&+s5 z?va1w3vzNvkdYS@Vr?wU8rsFcIR+L95Ii*OmScy}%vc@`PWXzlb8c~e(QsGB{kXd# zt#!*_Ia&FbAJ;iiAQ^!95Yb8dIN(UyLI%&H$ikUP+9mReF?%1(6I)<jNc~lL#b^OZiL(m*EW%DN- z@1yiQNY!2Q-1ATX@U)h`*=dMvfm%>ep5oyW4_AiM1}vk4baIh^tCa9lJY3@83J%CX z>Z&_k8LnlOx6M9y98z;BX{Lw-nLCx_>@5MjHwL_KwzFS;k4pBD!7fob?x0uZ2>LbI{I_KDeJVk+`QzqS)E&^?Ccl7- zx6e_%aeaEBoKZ2pEy_^VU;pIscfS7pfB)Ud&wg$9*MFM$m-l}9nIFFNx&Iity#DJC zzxKt0e{}D=U;h60e(PT2DWEFf9;pPvHP7b&D`nz zhWkK$pMU=CSMu|p{_ZOl%|@~{0+sY2U*WrzZ0(&inxDN!WD~-}b+}WX$fl!y_#&61 zeh~dOlbuZSchnDVFVAe~D7d5kNdyUL7 zqT4Fazco?9u|H-70gNfnK46X5x@2$yaZ|87d%tD%YMWY}&+j+X{pdVHEt&~D6TSJgydenDJa~EF4mk$r(AHD1-y+Gl#VhR-h%B|e zu+SMRNYb0}G#uW{2pCwwZ`O0PU!!FYoq#ZBWwwR?0*ZA`1oUMrBjw?9y>9E0?kVjt zBr6=1=qjoG61@ywv5;H0rDra|!HT#G;3ws-B$v<=^kH`S2BdqHouHCd99bGpo>yc} z!n;{*$BJn%f$oY{zUNWMf~cPd8K^SaILu@7^`lloV~34_}YsyIt zjaqT{{*+^TsiAQ{=C)pph!7f&HaRMel}uZLHr258qC3l3`UkdRgOjb-folu36uhh< z5QO%j%!F_gaoA9u;6R37Bp@MQ_>~f|lC&49j!SIR&fWz&d!a z7w*+z3w)(KXdP_C4*GcfCUcA!Tt0^?(tJ}KQSeT^hqrj26v+e?)}64$od)e>>rXkbLD%y^X9G_~;6;~a ze69f_ZY-Lka}B;90Z#p8UAMk(|-XwzA+?LF2g{e!aj2spvh1`4#m9<_!|q5=pirWvP~ zi}f119&fi)r5(bXt{t>2?{8GM8S0jHasPqMEyQL?U?T2h8S0Ks*5T7feEoJvU%wvG zCnTFrPa~qjP?LZDRlkEAp)={jRQeJ+-|!Ji#g`%0uwg@ODq%9urHk3pBsN3G;KdMt zAKrXvsCa$j*wTVhpD@(K>L-_LwdIR^5#@5Nez~@Gc^QvARo2_H{$vADhN|@iL$yBN zW@B>aw^=gstIsHI>rkjrOs82~f}Q~Y17 zWmYSNVl`i>*GjADN-kTU&Q~hw)k=M}maf%H_372xRHa^>UY#zko=xY9Q&ZLHT)tW= zm9m-o*=+7?y;!Q$O0`0*TFumINN00XnOruL&X&^Yv&C$#x>}k}mkPzzYQCN+7BlJT z)#_BbSS;pOv!I{Lrt76_y^=1}tMz<+I-9AcD+RpqXnHj>l`mBD)pWI#t7a-^QRQ@@ zHkHj*E0w827S*j*YE!vdF%Rf!wpK5!W{UMfy|S7{T_xmXr!v*)N+z4nO%*cJ+4NMc znl9yX`FyPaK1;Yc()v8Q^a&(Y*EXt;z+& zJGWz%NVC;_%&LpZME9d+_&ed-Lsg|MTGq z7SNN}WkgXgpOUEK<;A&^jpgtKyzN9g0vS_XyomZtDV>=_U?z*8qwVml??6*182@R!FOd@QfjZ{qtb zhJ+|G07C}{U`3B0jhnfZdnyR7*#2Nb`1r#jXfx2_gtUY;J&hJ_;qik>#0toBE(o=~ zwav=~X>TM9xGzt>aK42o0Md#|=C`C>p&S?l(U4oyd`u!LzmKAw)A(MF(eCZXz#GHe zJAGP$Ow#zD!?B1UAYNl#j6Hw$6}5vmYw+F*v=|Rz;YfLYG=0_tZ}_qW>OUvkFqG#4 zA~6_8at;A02qRQqg=4T?&O7zpB(Te(5q^XUV?-FFt3%8f&{9LqO6h)nG{9U7WD@p! zc#r9$kb$Y8GPd>hjbQ<49@?mE>AxplD?J#JmgOiofjBOYE z-6uNLU01JW?W9-JaCMD#0ACn@Pae88Gb~Ttr0p2|4J0s-z(4{62@E7Kkib9!0|^Wy z@R3RYw-U#|K=a;@l#4;-0|^WyFp$7N0s{#QBruS`Kmr2^3?wj+z(4{62@E9gzbJwK E2Js>;y8r+H diff --git a/SMBLibrary.Tests/NetBiosTests.cs b/SMBLibrary.Tests/NetBiosTests.cs index d3f8fc4b..8e7064ac 100644 --- a/SMBLibrary.Tests/NetBiosTests.cs +++ b/SMBLibrary.Tests/NetBiosTests.cs @@ -5,7 +5,6 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.NetBios; using Utilities; diff --git a/SMBLibrary.Tests/Program.cs b/SMBLibrary.Tests/Program.cs deleted file mode 100644 index db4b0bc3..00000000 --- a/SMBLibrary.Tests/Program.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace SMBLibrary.Tests -{ - class Program - { - static void Main(string[] args) - { - new NTLMAuthenticationTests().TestAll(); - new NTLMSigningTests().TestAll(); - new AesCcmTests().TestAll(); - new SMB2EncryptionTests().TestAll(); - new RC4Tests().TestAll(); - - new NetBiosTests().TestAll(); - new RPCTests().TestAll(); - new SMB2SigningTests().TestAll(); - - new NTDirectoryFileSystemTests().TestAll(); - } - } -} diff --git a/SMBLibrary.Tests/RPCTests.cs b/SMBLibrary.Tests/RPCTests.cs index bcda8757..85ccdbbb 100644 --- a/SMBLibrary.Tests/RPCTests.cs +++ b/SMBLibrary.Tests/RPCTests.cs @@ -4,12 +4,8 @@ * 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 Microsoft.VisualStudio.TestTools.UnitTesting; -using SMBLibrary.RPC; using SMBLibrary.Services; -using Utilities; namespace SMBLibrary.Tests { diff --git a/SMBLibrary.Tests/SMB2SigningTests.cs b/SMBLibrary.Tests/SMB2SigningTests.cs index a61c63fd..de9a2a3d 100644 --- a/SMBLibrary.Tests/SMB2SigningTests.cs +++ b/SMBLibrary.Tests/SMB2SigningTests.cs @@ -4,9 +4,6 @@ * 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 Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.SMB2; using Utilities; diff --git a/SMBLibrary.Tests/SMBLibrary.Tests.csproj b/SMBLibrary.Tests/SMBLibrary.Tests.csproj index 6958ab10..d32c24bf 100644 --- a/SMBLibrary.Tests/SMBLibrary.Tests.csproj +++ b/SMBLibrary.Tests/SMBLibrary.Tests.csproj @@ -1,10 +1,10 @@  - net40 + net472;net6.0 SMBLibrary.Tests SMBLibrary.Tests - Exe + Library @@ -17,9 +17,9 @@ - - Components\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll - + + + \ No newline at end of file From c5b432efe89998a3fb13d298a26cd7cfc540a3ef Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 12:39:06 +0200 Subject: [PATCH 048/247] Tests: Remove unused methods --- SMBLibrary.Tests/AesCcmTests.cs | 8 -------- SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs | 5 ----- SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs | 16 ---------------- SMBLibrary.Tests/NTLM/NTLMSigningTests.cs | 8 -------- SMBLibrary.Tests/NTLM/RC4Tests.cs | 7 ------- SMBLibrary.Tests/NetBiosTests.cs | 6 ------ SMBLibrary.Tests/RPCTests.cs | 9 --------- SMBLibrary.Tests/SMB2EncryptionTests.cs | 8 -------- SMBLibrary.Tests/SMB2SigningTests.cs | 7 ------- 9 files changed, 74 deletions(-) diff --git a/SMBLibrary.Tests/AesCcmTests.cs b/SMBLibrary.Tests/AesCcmTests.cs index 6b4d75f4..c9efdfec 100644 --- a/SMBLibrary.Tests/AesCcmTests.cs +++ b/SMBLibrary.Tests/AesCcmTests.cs @@ -121,13 +121,5 @@ public void TestDecryption() byte[] data = AesCcm.DecryptAndAuthenticate(key, nonce, encyrptedData, associatedData, signature); Assert.IsTrue(ByteUtils.AreByteArraysEqual(expectedData, data)); } - - public void TestAll() - { - TestEncryption_Rfc3610_Packet_Vector1(); - TestDecryption_Rfc3610_Packet_Vector1(); - TestEncryption(); - TestDecryption(); - } } } diff --git a/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs b/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs index 2eca2f6c..f483c712 100644 --- a/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs +++ b/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs @@ -52,10 +52,5 @@ private void OnNotifyChangeCompleted(NTStatus status, byte[] buffer, object cont { m_notifyChangeStatus = status; } - - public void TestAll() - { - TestCancel(); - } } } diff --git a/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs b/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs index a96979df..b5f38ef9 100644 --- a/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs @@ -5,8 +5,6 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -using System.Collections.Generic; -using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.Authentication.NTLM; using Utilities; @@ -160,19 +158,5 @@ public void NTLMv2AuthenticateMessageTest() // The payload entries may be distributed differently so we use cmp.GetBytes() Assert.IsTrue(ByteUtils.AreByteArraysEqual(messageBytes, cmp.GetBytes())); } - - public void TestAll() - { - LMv1HashTest(); - LMv1HashTestEmptyPassword(); - NTv1HashTest(); - NTv2HashTest(); - LMv1ResponseTest(); - NTLMv1ResponseTest(); - LMv2ResponseTest(); - NTLMv2ResponseTest(); - NTLMv2ChallengeMessageTest(); - NTLMv2AuthenticateMessageTest(); - } } } diff --git a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs index 50433d0c..e4a702aa 100644 --- a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs @@ -207,14 +207,6 @@ public void TestNTLMv2KeyExchangeMIC() Assert.IsTrue(ByteUtils.AreByteArraysEqual(mic, expected)); } - public void TestAll() - { - TestLMMIC(); - TestNTLMv1MIC(); - TestNTLMv1ExtendedSessionSecurityKeyExchangeMIC(); - TestNTLMv2KeyExchangeMIC(); - } - private static byte[] GetExportedSessionKey(byte[] sessionBaseKey, AuthenticateMessage message, byte[] serverChallenge, byte[] lmowf) { byte[] keyExchangeKey; diff --git a/SMBLibrary.Tests/NTLM/RC4Tests.cs b/SMBLibrary.Tests/NTLM/RC4Tests.cs index 4fa43b9d..8a5b2d0d 100644 --- a/SMBLibrary.Tests/NTLM/RC4Tests.cs +++ b/SMBLibrary.Tests/NTLM/RC4Tests.cs @@ -125,12 +125,5 @@ public void Test3() byte[] cipher = RC4.Encrypt(key, text); Assert.IsTrue(ByteUtils.AreByteArraysEqual(cipher, expectedCipher)); } - - public void TestAll() - { - Test1(); - Test2(); - Test3(); - } } } diff --git a/SMBLibrary.Tests/NetBiosTests.cs b/SMBLibrary.Tests/NetBiosTests.cs index 8e7064ac..33dcd29d 100644 --- a/SMBLibrary.Tests/NetBiosTests.cs +++ b/SMBLibrary.Tests/NetBiosTests.cs @@ -33,11 +33,5 @@ public void Test2() byte[] encodedName = NetBiosUtils.EncodeName(name, String.Empty); Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, encodedName)); } - - public void TestAll() - { - Test1(); - Test2(); - } } } diff --git a/SMBLibrary.Tests/RPCTests.cs b/SMBLibrary.Tests/RPCTests.cs index 85ccdbbb..5573edf1 100644 --- a/SMBLibrary.Tests/RPCTests.cs +++ b/SMBLibrary.Tests/RPCTests.cs @@ -93,14 +93,5 @@ public void Test5() byte[] requestBytes = request.GetBytes(); //Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, requestBytes)); } - - public void TestAll() - { - Test1(); - Test2(); - Test3(); - Test4(); - Test5(); - } } } diff --git a/SMBLibrary.Tests/SMB2EncryptionTests.cs b/SMBLibrary.Tests/SMB2EncryptionTests.cs index f5a243ad..92d01fee 100644 --- a/SMBLibrary.Tests/SMB2EncryptionTests.cs +++ b/SMBLibrary.Tests/SMB2EncryptionTests.cs @@ -102,13 +102,5 @@ public void TestDecryption() Assert.IsTrue(ByteUtils.AreByteArraysEqual(expectedDecryptedMessage, decryptedMessage)); } - - public void TestAll() - { - TestEncryptionKeyGeneration(); - TestDecryptionKeyGeneration(); - TestEncryption(); - TestDecryption(); - } } } diff --git a/SMBLibrary.Tests/SMB2SigningTests.cs b/SMBLibrary.Tests/SMB2SigningTests.cs index de9a2a3d..324b3158 100644 --- a/SMBLibrary.Tests/SMB2SigningTests.cs +++ b/SMBLibrary.Tests/SMB2SigningTests.cs @@ -70,12 +70,5 @@ public void TestSMB300SignatureCalculation() byte[] expected = new byte[] { 0x73, 0xF2, 0xCC, 0x56, 0x09, 0x3E, 0xD2, 0xB5, 0xD7, 0x10, 0x66, 0x6C, 0xE4, 0x28, 0x2D, 0xD1 }; Assert.IsTrue(ByteUtils.AreByteArraysEqual(signature, expected)); } - - public void TestAll() - { - TestSMB202SignatureCalculation(); - TestSMB210SignatureCalculation(); - TestSMB300SignatureCalculation(); - } } } From 856a2cc7d475dd4cf94c971e297581079faaae30 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 12:40:02 +0200 Subject: [PATCH 049/247] Update RPCTests --- SMBLibrary.Tests/RPCTests.cs | 37 +++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/SMBLibrary.Tests/RPCTests.cs b/SMBLibrary.Tests/RPCTests.cs index 5573edf1..53435fe9 100644 --- a/SMBLibrary.Tests/RPCTests.cs +++ b/SMBLibrary.Tests/RPCTests.cs @@ -13,7 +13,7 @@ namespace SMBLibrary.Tests public class RPCTests { [TestMethod] - public void Test1() + public void Decode_NetrWkstaGetInfoResponse() { byte[] buffer = new byte[]{ 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x08, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, @@ -23,22 +23,24 @@ public void Test1() 0x47, 0x00, 0x52, 0x00, 0x4f, 0x00, 0x55, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; NetrWkstaGetInfoResponse response = new NetrWkstaGetInfoResponse(buffer); - byte[] responseBytes = response.GetBytes(); - //Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, responseBytes)); + Assert.AreEqual((uint)100, response.WkstaInfo.Level); + Assert.IsInstanceOfType(response.WkstaInfo.Info, typeof(WorkstationInfo100)); + Assert.AreEqual("TAL2-VM7", ((WorkstationInfo100)response.WkstaInfo.Info).ComputerName.Value); } [TestMethod] - public void Test2() + public void Decode_NetrServerGetInfoResponse() { byte[] buffer = new byte[] { 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x90, 0x84, 0x00, 0x08, 0x00, 0x02, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x31, 0x00, 0x39, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x36, 0x00, 0x38, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x35, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00 }; NetrServerGetInfoResponse response = new NetrServerGetInfoResponse(buffer); - byte[] responseBytes = response.GetBytes(); - //Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, responseBytes)); + Assert.AreEqual((uint)101, response.InfoStruct.Level); + Assert.IsInstanceOfType(response.InfoStruct.Info, typeof(ServerInfo101)); + Assert.AreEqual("192.168.1.57", ((ServerInfo101)response.InfoStruct.Info).ServerName.Value); } [TestMethod] - public void Test3() + public void Decode_NetrShareEnumRequest() { byte[] buffer = new byte[] {0x00, 0x00, 0x02, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x5c, 0x00, 0x31, 0x00, 0x39, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x36, 0x00, @@ -47,12 +49,14 @@ public void Test3() 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00}; NetrShareEnumRequest request = new NetrShareEnumRequest(buffer); - byte[] requestBytes = request.GetBytes(); - //Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, requestBytes)); + Assert.AreEqual(@"\\192.168.1.57", request.ServerName); + Assert.AreEqual((uint)1, request.InfoStruct.Level); + Assert.IsInstanceOfType(request.InfoStruct.Info, typeof(ShareInfo1Container)); + Assert.AreEqual(0, ((ShareInfo1Container)request.InfoStruct.Info).Count); } [TestMethod] - public void Test4() + public void Decode_NetrShareEnumResponse() { byte[] buffer = new byte[] {0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, @@ -76,12 +80,14 @@ public void Test4() 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; NetrShareEnumResponse response = new NetrShareEnumResponse(buffer); - byte[] responseBytes = response.GetBytes(); - //Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, responseBytes)); + Assert.AreEqual((uint)4, response.TotalEntries); + Assert.AreEqual((uint)1, response.InfoStruct.Level); + Assert.IsInstanceOfType(response.InfoStruct.Info, typeof(ShareInfo1Container)); + Assert.AreEqual(4, ((ShareInfo1Container)response.InfoStruct.Info).Count); } [TestMethod] - public void Test5() + public void Decode_NetrShareGetInfoRequest() { byte[] buffer = new byte[] {0x00, 0x00, 0x02, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x5c, 0x00, 0x31, 0x00, 0x39, 0x00, 0x32, 0x00, 0x2e, 0x00, 0x31, 0x00, 0x36, 0x00, @@ -90,8 +96,9 @@ public void Test5() 0x61, 0x00, 0x72, 0x00, 0x65, 0x00, 0x64, 0x00, 0x00, 0x00, 0xb7, 0x6c, 0x02, 0x00, 0x00, 0x00}; NetrShareGetInfoRequest request = new NetrShareGetInfoRequest(buffer); - byte[] requestBytes = request.GetBytes(); - //Assert.IsTrue(ByteUtils.AreByteArraysEqual(buffer, requestBytes)); + Assert.AreEqual(@"\\192.168.1.52", request.ServerName); + Assert.AreEqual((uint)2, request.Level); + Assert.AreEqual("Shared", request.NetName); } } } From a80c2d10d051f7793279771bab2b8a3477b1b351 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 12:43:04 +0200 Subject: [PATCH 050/247] Server: Mark method allowing to set port as internal --- SMBLibrary/Server/SMBServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 95439676..991e3e93 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -81,7 +81,7 @@ public void Start(IPAddress serverAddress, SMBTransportType transport, bool enab Start(serverAddress, transport, port, enableSMB1, enableSMB2, enableSMB3, connectionInactivityTimeout); } - private void Start(IPAddress serverAddress, SMBTransportType transport, int port, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) + internal void Start(IPAddress serverAddress, SMBTransportType transport, int port, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) { if (!m_listening) { From 64721a2d4268e53cfb0d0a8a07e5298d33b6adae Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 12:43:12 +0200 Subject: [PATCH 051/247] Client: Mark method allowing to set port as internal --- SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 412e4148..9e67a79b 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -83,7 +83,7 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool fo return Connect(serverAddress, transport, port, forceExtendedSecurity, DefaultResponseTimeoutInMilliseconds); } - private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) + internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) { m_transport = transport; if (!m_isConnected) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index f7544e9c..dcbf53d2 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -94,7 +94,7 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport, int res return Connect(serverAddress, transport, port, responseTimeoutInMilliseconds); } - private bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) + internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) { if (m_serverName == null) { From 989b0515270452f2b36d7cf9faab26846ac25ca6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 12:40:36 +0200 Subject: [PATCH 052/247] Tests: Added LoginTests --- .../IntegrationTests/LoginTests.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 SMBLibrary.Tests/IntegrationTests/LoginTests.cs diff --git a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs new file mode 100644 index 00000000..5a0a6f4f --- /dev/null +++ b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs @@ -0,0 +1,62 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.Authentication.GSSAPI; +using SMBLibrary.Authentication.NTLM; +using SMBLibrary.Client; +using SMBLibrary.Server; +using System; +using System.Net; + +namespace SMBLibrary.Tests.IntegrationTests +{ + [TestClass] + public class LoginTests + { + private int m_serverPort; + private SMBServer m_server; + + [TestInitialize] + public void Initialize() + { + m_serverPort = 1000 + new Random().Next(50000); + SMBShareCollection shares = new SMBShareCollection(); + IGSSMechanism gssMechanism = new IndependentNTLMAuthenticationProvider((username) => "password"); + GSSProvider gssProvider = new GSSProvider(gssMechanism); + m_server = new SMBServer(shares, gssProvider); + m_server.Start(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, false, true, false, null); + } + + [TestCleanup] + public void Cleanup() + { + m_server.Stop(); + } + + [TestMethod] + public void When_ValidCredentialsProvided_LoginSucceed() + { + // Arrange + SMB2Client client = new SMB2Client(); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + + // Act + NTStatus status = client.Login("", "John", "password"); + + // Assert + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + } + + [TestMethod] + public void When_InvalidCredentialsProvided_LoginFails() + { + // Arrange + SMB2Client client = new SMB2Client(); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + + // Act + NTStatus status = client.Login("", "John", "Password"); + + // Assert + Assert.AreEqual(NTStatus.STATUS_LOGON_FAILURE, status); + } + } +} From d62c5913ec6929c35b988558efba1e2f3da0fda4 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 22 Dec 2023 13:03:26 +0200 Subject: [PATCH 053/247] Tests: LoginTests: Add test --- .../IntegrationTests/LoginTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs index 5a0a6f4f..e05bc32d 100644 --- a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs +++ b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs @@ -45,6 +45,26 @@ public void When_ValidCredentialsProvided_LoginSucceed() Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); } + [TestMethod] + public void When_ClientDisconnectAndReconnect_LoginSucceed() + { + // Arrange + SMB2Client client = new SMB2Client(); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + + // Act + NTStatus status = client.Login("", "John", "password"); + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + status = client.Logoff(); + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + client.Disconnect(); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + status = client.Login("", "John", "password"); + + // Assert + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + } + [TestMethod] public void When_InvalidCredentialsProvided_LoginFails() { From ce6921cb03e16171f16549496d9a0857454fccba Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 13:04:09 +0200 Subject: [PATCH 054/247] Client: ProcessConnectionBuffer: Improved logging --- SMBLibrary/Client/SMB1Client.cs | 1 + SMBLibrary/Client/SMB2Client.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 9e67a79b..89dc5c50 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -489,6 +489,7 @@ private void ProcessConnectionBuffer(ConnectionState state) } catch (Exception) { + Log("[ProcessConnectionBuffer] Invalid packet"); state.ClientSocket.Close(); state.ReceiveBuffer.Dispose(); break; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index dcbf53d2..9da498b0 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -427,6 +427,7 @@ private void ProcessConnectionBuffer(ConnectionState state) } catch (Exception) { + Log("[ProcessConnectionBuffer] Invalid packet"); state.ClientSocket.Close(); state.ReceiveBuffer.Dispose(); break; From a9f995c17285d7cab6f89a15cd12968d585ef3d7 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 13:30:20 +0200 Subject: [PATCH 055/247] NTLM: AuthenticateMessage: Added GetMicFieldOffset method and MicFieldLenght constant --- .../NTLM/Structures/AuthenticateMessage.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs b/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs index b97866c0..baebbd86 100644 --- a/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs +++ b/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs @@ -1,12 +1,10 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2023 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.NTLM @@ -17,6 +15,7 @@ namespace SMBLibrary.Authentication.NTLM public class AuthenticateMessage { public const string ValidSignature = "NTLMSSP\0"; + public const int MicFieldLenght = 16; public string Signature; // 8 bytes public MessageTypeName MessageType; @@ -59,7 +58,7 @@ public AuthenticateMessage(byte[] buffer) } if (HasMicField()) { - MIC = ByteReader.ReadBytes(buffer, offset, 16); + MIC = ByteReader.ReadBytes(buffer, offset, MicFieldLenght); } } @@ -142,5 +141,17 @@ public byte[] GetBytes() return buffer; } + + public static int GetMicFieldOffset(byte[] authenticateMessageBytes) + { + NegotiateFlags negotiateFlags = (NegotiateFlags)LittleEndianConverter.ToUInt32(authenticateMessageBytes, 60); + int offset = 64; + if ((negotiateFlags & NegotiateFlags.Version) > 0) + { + offset += NTLMVersion.Length; + } + + return offset; + } } } From 787df79c8d7e0f10e337f31b5f81af1e4e776771 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 13:31:17 +0200 Subject: [PATCH 056/247] NTLMCryptography: Added ValidateAuthenticateMessageMIC method and updated tests to use it --- SMBLibrary.Tests/NTLM/NTLMSigningTests.cs | 40 ++++--------------- .../NTLM/Helpers/NTLMCryptography.cs | 17 +++++++- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs index e4a702aa..1ab6d032 100644 --- a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs @@ -47,14 +47,8 @@ public void TestLMMIC() byte[] lmowf = NTLMCryptography.LMOWFv1(password); byte[] exportedSessionKey = GetExportedSessionKey(sessionBaseKey, authenticateMessage, serverChallenge, lmowf); - // https://msdn.microsoft.com/en-us/library/cc236695.aspx - const int micFieldOffset = 72; - ByteWriter.WriteBytes(type3, micFieldOffset, new byte[16]); - byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(type1, type2), type3); - byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); - byte[] expected = new byte[] { 0x4e, 0x65, 0x54, 0xe6, 0xb3, 0xdc, 0xdc, 0x16, 0xef, 0xc4, 0xd0, 0x03, 0x3b, 0x81, 0x61, 0x6f }; - - Assert.IsTrue(ByteUtils.AreByteArraysEqual(mic, expected)); + bool isMicValid = NTLMCryptography.ValidateAuthenticateMessageMIC(exportedSessionKey, type1, type2, type3); + Assert.IsTrue(isMicValid); } [TestMethod] @@ -87,15 +81,9 @@ public void TestNTLMv1MIC() byte[] sessionBaseKey = new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)); byte[] lmowf = NTLMCryptography.LMOWFv1(password); byte[] exportedSessionKey = GetExportedSessionKey(sessionBaseKey, authenticateMessage, serverChallenge, lmowf); - - // https://msdn.microsoft.com/en-us/library/cc236695.aspx - const int micFieldOffset = 72; - ByteWriter.WriteBytes(type3, micFieldOffset, new byte[16]); - byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(type1, type2), type3); - byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); - byte[] expected = new byte[] { 0xae, 0xa7, 0xba, 0x44, 0x4e, 0x93, 0xa7, 0xdb, 0xb3, 0x0c, 0x85, 0x49, 0xc2, 0x2b, 0xba, 0x9a }; - Assert.IsTrue(ByteUtils.AreByteArraysEqual(mic, expected)); + bool isMicValid = NTLMCryptography.ValidateAuthenticateMessageMIC(exportedSessionKey, type1, type2, type3); + Assert.IsTrue(isMicValid); } [TestMethod] @@ -135,14 +123,8 @@ public void TestNTLMv1ExtendedSessionSecurityKeyExchangeMIC() byte[] lmowf = NTLMCryptography.LMOWFv1(password); byte[] exportedSessionKey = GetExportedSessionKey(sessionBaseKey, authenticateMessage, serverChallenge, lmowf); - // https://msdn.microsoft.com/en-us/library/cc236695.aspx - const int micFieldOffset = 72; - ByteWriter.WriteBytes(type3, micFieldOffset, new byte[16]); - byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(type1, type2), type3); - byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); - byte[] expected = new byte[] { 0xc6, 0x21, 0x82, 0x59, 0x83, 0xda, 0xc7, 0xe7, 0xfa, 0x96, 0x44, 0x67, 0x16, 0xc3, 0xb3, 0x5b }; - - Assert.IsTrue(ByteUtils.AreByteArraysEqual(mic, expected)); + bool isMicValid = NTLMCryptography.ValidateAuthenticateMessageMIC(exportedSessionKey, type1, type2, type3); + Assert.IsTrue(isMicValid); } [TestMethod] @@ -197,14 +179,8 @@ public void TestNTLMv2KeyExchangeMIC() byte[] sessionBaseKey = new HMACMD5(responseKeyNT).ComputeHash(ntProofStr); byte[] exportedSessionKey = GetExportedSessionKey(sessionBaseKey, authenticateMessage, serverChallenge, null); - // https://msdn.microsoft.com/en-us/library/cc236695.aspx - const int micFieldOffset = 72; - ByteWriter.WriteBytes(type3, micFieldOffset, new byte[16]); - byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(type1, type2), type3); - byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); - byte[] expected = new byte[] { 0x82, 0x3c, 0xff, 0x48, 0xa9, 0x03, 0x13, 0x4c, 0x33, 0x3c, 0x09, 0x87, 0xf3, 0x16, 0x59, 0x89 }; - - Assert.IsTrue(ByteUtils.AreByteArraysEqual(mic, expected)); + bool isMicValid = NTLMCryptography.ValidateAuthenticateMessageMIC(exportedSessionKey, type1, type2, type3); + Assert.IsTrue(isMicValid); } private static byte[] GetExportedSessionKey(byte[] sessionBaseKey, AuthenticateMessage message, byte[] serverChallenge, byte[] lmowf) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 07545207..2516d281 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -5,7 +5,6 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -using System.Collections.Generic; using System.Globalization; using System.Reflection; using System.Security.Cryptography; @@ -61,6 +60,22 @@ public static byte[] ComputeNTLMv2Proof(byte[] serverChallenge, byte[] clientCha return _NTProof; } + /// + /// Caller must verify that the authenticate message has MIC before calling this method + /// + public static bool ValidateAuthenticateMessageMIC(byte[] exportedSessionKey, byte[] negotiateMessageBytes, byte[] challengeMessageBytes, byte[] authenticateMessageBytes) + { + // https://msdn.microsoft.com/en-us/library/cc236695.aspx + int micFieldOffset = AuthenticateMessage.GetMicFieldOffset(authenticateMessageBytes); + byte[] expectedMic = ByteReader.ReadBytes(authenticateMessageBytes, micFieldOffset, AuthenticateMessage.MicFieldLenght); + + ByteWriter.WriteBytes(authenticateMessageBytes, micFieldOffset, new byte[AuthenticateMessage.MicFieldLenght]); + byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(negotiateMessageBytes, challengeMessageBytes), authenticateMessageBytes); + byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); + + return ByteUtils.AreByteArraysEqual(mic, expectedMic); + } + public static byte[] DesEncrypt(byte[] key, byte[] plainText) { return DesEncrypt(key, plainText, 0, plainText.Length); From 2b92e1b5f65b8f3f735b3f4161790670fa8dc50a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 13:38:11 +0200 Subject: [PATCH 057/247] Client: NTLMAuthenticationClient.GetAuthenticateMessage: Renamed variable --- .../Client/Authentication/NTLMAuthenticationClient.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index 50c37392..a265c358 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -102,16 +102,16 @@ protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) challengeMessageBytes = securityBlob; } - byte[] authenticationMessageBytes = NTLMAuthenticationHelper.GetAuthenticateMessage(challengeMessageBytes, m_domainName, m_userName, m_password, m_spn, m_authenticationMethod, out m_sessionKey); - if (useGSSAPI && authenticationMessageBytes != null) + byte[] authenticateMessageBytes = NTLMAuthenticationHelper.GetAuthenticateMessage(challengeMessageBytes, m_domainName, m_userName, m_password, m_spn, m_authenticationMethod, out m_sessionKey); + if (useGSSAPI && authenticateMessageBytes != null) { SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); - outputToken.ResponseToken = authenticationMessageBytes; + outputToken.ResponseToken = authenticateMessageBytes; return outputToken.GetBytes(); } else { - return authenticationMessageBytes; + return authenticateMessageBytes; } } From 08748d33374e245ce380bc357286509b33ef3b99 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 16:01:23 +0200 Subject: [PATCH 058/247] NTLM: AuthenticateMessage: Add CalculateMIC method --- .../NTLM/Structures/AuthenticateMessage.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs b/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs index baebbd86..7b2921f7 100644 --- a/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs +++ b/SMBLibrary/Authentication/NTLM/Structures/AuthenticateMessage.cs @@ -5,6 +5,7 @@ * either version 3 of the License, or (at your option) any later version. */ using System; +using System.Security.Cryptography; using Utilities; namespace SMBLibrary.Authentication.NTLM @@ -142,6 +143,14 @@ public byte[] GetBytes() return buffer; } + public void CalculateMIC(byte[] sessionKey, byte[] negotiateMessage, byte[] challengeMessage) + { + MIC = new byte[MicFieldLenght]; + byte[] authenticateMessageBytes = GetBytes(); + byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(negotiateMessage, challengeMessage), authenticateMessageBytes); + MIC = new HMACMD5(sessionKey).ComputeHash(temp); + } + public static int GetMicFieldOffset(byte[] authenticateMessageBytes) { NegotiateFlags negotiateFlags = (NegotiateFlags)LittleEndianConverter.ToUInt32(authenticateMessageBytes, 60); From 7628bb6ba8b304b817d13db24fb192f3a6fc854b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 16:02:59 +0200 Subject: [PATCH 059/247] NTLM: NTLMAuthenticationClient: Store negotiate message as class variable --- .../Client/Authentication/NTLMAuthenticationClient.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index a265c358..a415039e 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -18,6 +18,7 @@ public class NTLMAuthenticationClient : IAuthenticationClient private string m_spn; private byte[] m_sessionKey; private AuthenticationMethod m_authenticationMethod; + private byte[] m_negotiateMessageBytes; private bool m_isNegotiationMessageAcquired = false; @@ -64,18 +65,18 @@ protected virtual byte[] GetNegotiateMessage(byte[] securityBlob) useGSSAPI = true; } - byte[] negotiateMessageBytes = NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); + m_negotiateMessageBytes = NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); if (useGSSAPI) { SimpleProtectedNegotiationTokenInit outputToken = new SimpleProtectedNegotiationTokenInit(); outputToken.MechanismTypeList = new List(); outputToken.MechanismTypeList.Add(GSSProvider.NTLMSSPIdentifier); - outputToken.MechanismToken = negotiateMessageBytes; + outputToken.MechanismToken = m_negotiateMessageBytes; return outputToken.GetBytes(true); } else { - return negotiateMessageBytes; + return m_negotiateMessageBytes; } } From c090de02ec34d7d1f611c49ae943e136fb2ad007 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 20:07:22 +0200 Subject: [PATCH 060/247] NTLMCryptography: Minor styling change --- .../NTLM/Helpers/NTLMCryptography.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 2516d281..ec7c8c86 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -60,22 +60,6 @@ public static byte[] ComputeNTLMv2Proof(byte[] serverChallenge, byte[] clientCha return _NTProof; } - /// - /// Caller must verify that the authenticate message has MIC before calling this method - /// - public static bool ValidateAuthenticateMessageMIC(byte[] exportedSessionKey, byte[] negotiateMessageBytes, byte[] challengeMessageBytes, byte[] authenticateMessageBytes) - { - // https://msdn.microsoft.com/en-us/library/cc236695.aspx - int micFieldOffset = AuthenticateMessage.GetMicFieldOffset(authenticateMessageBytes); - byte[] expectedMic = ByteReader.ReadBytes(authenticateMessageBytes, micFieldOffset, AuthenticateMessage.MicFieldLenght); - - ByteWriter.WriteBytes(authenticateMessageBytes, micFieldOffset, new byte[AuthenticateMessage.MicFieldLenght]); - byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(negotiateMessageBytes, challengeMessageBytes), authenticateMessageBytes); - byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); - - return ByteUtils.AreByteArraysEqual(mic, expectedMic); - } - public static byte[] DesEncrypt(byte[] key, byte[] plainText) { return DesEncrypt(key, plainText, 0, plainText.Length); @@ -273,5 +257,21 @@ public static byte[] KXKey(byte[] sessionBaseKey, NegotiateFlags negotiateFlags, return keyExchangeKey; } } + + /// + /// Caller must verify that the authenticate message has MIC before calling this method + /// + public static bool ValidateAuthenticateMessageMIC(byte[] exportedSessionKey, byte[] negotiateMessageBytes, byte[] challengeMessageBytes, byte[] authenticateMessageBytes) + { + // https://msdn.microsoft.com/en-us/library/cc236695.aspx + int micFieldOffset = AuthenticateMessage.GetMicFieldOffset(authenticateMessageBytes); + byte[] expectedMic = ByteReader.ReadBytes(authenticateMessageBytes, micFieldOffset, AuthenticateMessage.MicFieldLenght); + + ByteWriter.WriteBytes(authenticateMessageBytes, micFieldOffset, new byte[AuthenticateMessage.MicFieldLenght]); + byte[] temp = ByteUtils.Concatenate(ByteUtils.Concatenate(negotiateMessageBytes, challengeMessageBytes), authenticateMessageBytes); + byte[] mic = new HMACMD5(exportedSessionKey).ComputeHash(temp); + + return ByteUtils.AreByteArraysEqual(mic, expectedMic); + } } } From cb2c50a7c3388e3a38efa846529af312f579e5b5 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 20:09:24 +0200 Subject: [PATCH 061/247] NTLMCryptography: Add ComputeSignKey and ComputeSealKey methods and tests --- SMBLibrary.Tests/NTLM/NTLMSigningTests.cs | 30 +++++++++- .../NTLM/Helpers/NTLMCryptography.cs | 56 +++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs index 1ab6d032..eaf37dd1 100644 --- a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs @@ -4,8 +4,6 @@ * 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 Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.Authentication.NTLM; @@ -183,6 +181,34 @@ public void TestNTLMv2KeyExchangeMIC() Assert.IsTrue(isMicValid); } + [TestMethod] + public void Test_ComputeClientSignKey() + { + // Arrange + byte[] exportedSessionKey = new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }; + byte[] expected = new byte[] { 0x47, 0x88, 0xdc, 0x86, 0x1b, 0x47, 0x82, 0xf3, 0x5d, 0x43, 0xfd, 0x98, 0xfe, 0x1a, 0x2d, 0x39 }; + + // Act + byte[] signKey = NTLMCryptography.ComputeClientSignKey(exportedSessionKey); + + // Assert + Assert.IsTrue(ByteUtils.AreByteArraysEqual(expected, signKey)); + } + + [TestMethod] + public void Test_ComputeClientSealKey() + { + // Arrange + byte[] exportedSessionKey = new byte[] { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }; + byte[] expected = new byte[] { 0x59, 0xf6, 0x00, 0x97, 0x3c, 0xc4, 0x96, 0x0a, 0x25, 0x48, 0x0a, 0x7c, 0x19, 0x6e, 0x4c, 0x58 }; + + // Act + byte[] sealKey = NTLMCryptography.ComputeClientSealKey(exportedSessionKey); + + // Assert + Assert.IsTrue(ByteUtils.AreByteArraysEqual(expected, sealKey)); + } + private static byte[] GetExportedSessionKey(byte[] sessionBaseKey, AuthenticateMessage message, byte[] serverChallenge, byte[] lmowf) { byte[] keyExchangeKey; diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index ec7c8c86..d0da43ff 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -273,5 +273,61 @@ public static bool ValidateAuthenticateMessageMIC(byte[] exportedSessionKey, byt return ByteUtils.AreByteArraysEqual(mic, expectedMic); } + + public static byte[] ComputeClientSignKey(byte[] exportedSessionKey) + { + return ComputeSignKey(exportedSessionKey, true); + } + + public static byte[] ComputeServerSignKey(byte[] exportedSessionKey) + { + return ComputeSignKey(exportedSessionKey, false); + } + + private static byte[] ComputeSignKey(byte[] exportedSessionKey, bool isClient) + { + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/524cdccb-563e-4793-92b0-7bc321fce096 + string str; + if (isClient) + { + str = "session key to client-to-server signing key magic constant"; + } + else + { + str = "session key to server-to-client signing key magic constant"; + } + byte[] encodedString = Encoding.GetEncoding(28591).GetBytes(str); + byte[] nullTerminatedEncodedString = ByteUtils.Concatenate(encodedString, new byte[1]); + byte[] concatendated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); + return MD5.Create().ComputeHash(concatendated); + } + + public static byte[] ComputeClientSealKey(byte[] exportedSessionKey) + { + return ComputeSealKey(exportedSessionKey, true); + } + + public static byte[] ComputeServerSealKey(byte[] exportedSessionKey) + { + return ComputeSealKey(exportedSessionKey, false); + } + + private static byte[] ComputeSealKey(byte[] exportedSessionKey, bool isClient) + { + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/524cdccb-563e-4793-92b0-7bc321fce096 + string str; + if (isClient) + { + str = "session key to client-to-server sealing key magic constant"; + } + else + { + str = "session key to server-to-client sealing key magic constant"; + } + byte[] encodedString = Encoding.GetEncoding(28591).GetBytes(str); + byte[] nullTerminatedEncodedString = ByteUtils.Concatenate(encodedString, new byte[1]); + byte[] concatendated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); + return MD5.Create().ComputeHash(concatendated); + } } } From e31df2a79755a4f5a1348ee5295c016016876d07 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 20:43:09 +0200 Subject: [PATCH 062/247] Client: Fix ArgumentNullException following recent ArrayPool usage --- SMBLibrary/Client/SMB1Client.cs | 31 +++++++++++++++++-------------- SMBLibrary/Client/SMB2Client.cs | 31 +++++++++++++++++-------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 89dc5c50..b2775917 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -458,21 +458,24 @@ private void OnClientSocketReceive(IAsyncResult ar) buffer.SetNumberOfBytesReceived(numberOfBytesReceived); ProcessConnectionBuffer(state); - try - { - clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); - } - catch (ObjectDisposedException) + if (clientSocket.Connected) { - m_isConnected = false; - buffer.Dispose(); - Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); - } - catch (SocketException ex) - { - m_isConnected = false; - buffer.Dispose(); - Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + try + { + clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); + } + catch (ObjectDisposedException) + { + m_isConnected = false; + buffer.Dispose(); + Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); + } + catch (SocketException ex) + { + m_isConnected = false; + buffer.Dispose(); + Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + } } } } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 9da498b0..32b14104 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -396,21 +396,24 @@ private void OnClientSocketReceive(IAsyncResult ar) buffer.SetNumberOfBytesReceived(numberOfBytesReceived); ProcessConnectionBuffer(state); - try - { - clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); - } - catch (ObjectDisposedException) + if (clientSocket.Connected) { - m_isConnected = false; - Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); - buffer.Dispose(); - } - catch (SocketException ex) - { - m_isConnected = false; - Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); - buffer.Dispose(); + try + { + clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); + } + catch (ObjectDisposedException) + { + m_isConnected = false; + Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); + buffer.Dispose(); + } + catch (SocketException ex) + { + m_isConnected = false; + Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + buffer.Dispose(); + } } } } From d34f014eab30949a47eb680dce7d35e6295932a2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 20:46:17 +0200 Subject: [PATCH 063/247] Server: Dispose of ReceiveBuffer after receiving invalid packets --- SMBLibrary/Server/SMBServer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 991e3e93..978f802e 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -315,6 +315,7 @@ private void ProcessPacket(SessionPacket packet, ref ConnectionState state) { state.LogToServer(Severity.Verbose, "Rejected SMB1 message"); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); return; } @@ -327,6 +328,7 @@ private void ProcessPacket(SessionPacket packet, ref ConnectionState state) { state.LogToServer(Severity.Warning, "Invalid SMB1 message: " + ex.Message); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); return; } state.LogToServer(Severity.Verbose, "SMB1 message received: {0} requests, First request: {1}, Packet length: {2}", message.Commands.Count, message.Commands[0].CommandName.ToString(), packet.Length); @@ -365,6 +367,7 @@ private void ProcessPacket(SessionPacket packet, ref ConnectionState state) { state.LogToServer(Severity.Verbose, "Rejected SMB2 message"); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); return; } @@ -377,6 +380,7 @@ private void ProcessPacket(SessionPacket packet, ref ConnectionState state) { state.LogToServer(Severity.Warning, "Invalid SMB2 request chain: " + ex.Message); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); return; } state.LogToServer(Severity.Verbose, "SMB2 request chain received: {0} requests, First request: {1}, Packet length: {2}", requestChain.Count, requestChain[0].CommandName.ToString(), packet.Length); @@ -401,6 +405,7 @@ private void ProcessPacket(SessionPacket packet, ref ConnectionState state) { state.LogToServer(Severity.Warning, "Inappropriate NetBIOS session packet"); state.ClientSocket.Close(); + state.ReceiveBuffer.Dispose(); return; } } From dad516b223bdaa6460c78333c00aacd197a140c7 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 30 Dec 2023 20:48:43 +0200 Subject: [PATCH 064/247] NBTConnectionReceiveBuffer: Avoid exception when calling Dispose twice --- SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs index 074ca80c..284ef561 100644 --- a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs +++ b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs @@ -127,10 +127,13 @@ private void RemovePacketBytes() public void Dispose() { + if (m_buffer != null) + { #if NETSTANDARD2_0 - ArrayPool.Shared.Return(m_buffer); + ArrayPool.Shared.Return(m_buffer); #endif - m_buffer = null; + m_buffer = null; + } } public byte[] Buffer From 2667b5439e09d26e3e1dcd29c63f4ae3716c4f16 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 15:07:08 +0200 Subject: [PATCH 065/247] GSSAPI: SimpleProtectedNegotiationTokenInit: Extracted GetEncodedMechanismTypeListLength method --- .../SimpleProtectedNegotiationTokenInit.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs b/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs index f4e5dfe2..b05b8efe 100644 --- a/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs +++ b/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -102,16 +102,7 @@ public override byte[] GetBytes() protected virtual int GetTokenFieldsLength() { - int result = 0; - if (MechanismTypeList != null) - { - int typeListSequenceLength = GetMechanismTypeListSequenceLength(MechanismTypeList); - int typeListSequenceLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(typeListSequenceLength); - int typeListConstructionLength = 1 + typeListSequenceLengthFieldSize + typeListSequenceLength; - int typeListConstructionLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(typeListConstructionLength); - int entryLength = 1 + typeListConstructionLengthFieldSize + 1 + typeListSequenceLengthFieldSize + typeListSequenceLength; - result += entryLength; - } + int result = GetEncodedMechanismTypeListLength(MechanismTypeList); if (MechanismToken != null) { int mechanismTokenLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(MechanismToken.Length); @@ -229,5 +220,21 @@ protected static void WriteMechanismListMIC(byte[] buffer, ref int offset, byte[ DerEncodingHelper.WriteLength(buffer, ref offset, mechanismListMIC.Length); ByteWriter.WriteBytes(buffer, ref offset, mechanismListMIC); } + + private static int GetEncodedMechanismTypeListLength(List mechanismTypeList) + { + if (mechanismTypeList == null) + { + return 0; + } + else + { + int typeListSequenceLength = GetMechanismTypeListSequenceLength(mechanismTypeList); + int typeListSequenceLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(typeListSequenceLength); + int typeListConstructionLength = 1 + typeListSequenceLengthFieldSize + typeListSequenceLength; + int typeListConstructionLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(typeListConstructionLength); + return 1 + typeListConstructionLengthFieldSize + 1 + typeListSequenceLengthFieldSize + typeListSequenceLength; + } + } } } From ca718e343ea3e461e92486a9fb6c27ea816f21e9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 15:09:13 +0200 Subject: [PATCH 066/247] GSSAPI: SimpleProtectedNegotiationTokenInit: Add GetMechanismTypeListBytes method --- .../SimpleProtectedNegotiationTokenInit.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs b/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs index b05b8efe..a553ad60 100644 --- a/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs +++ b/SMBLibrary/Authentication/GSSAPI/SPNEGO/SimpleProtectedNegotiationTokenInit.cs @@ -191,6 +191,11 @@ protected static void WriteMechanismTypeList(byte[] buffer, ref int offset, List int constructionLength = 1 + sequenceLengthFieldSize + sequenceLength; ByteWriter.WriteByte(buffer, ref offset, MechanismTypeListTag); DerEncodingHelper.WriteLength(buffer, ref offset, constructionLength); + WriteMechanismTypeListSequence(buffer, ref offset, mechanismTypeList, sequenceLength); + } + + protected static void WriteMechanismTypeListSequence(byte[] buffer, ref int offset, List mechanismTypeList, int sequenceLength) + { ByteWriter.WriteByte(buffer, ref offset, (byte)DerEncodingTag.Sequence); DerEncodingHelper.WriteLength(buffer, ref offset, sequenceLength); foreach (byte[] mechanismType in mechanismTypeList) @@ -221,6 +226,17 @@ protected static void WriteMechanismListMIC(byte[] buffer, ref int offset, byte[ ByteWriter.WriteBytes(buffer, ref offset, mechanismListMIC); } + public static byte[] GetMechanismTypeListBytes(List mechanismTypeList) + { + int sequenceLength = GetMechanismTypeListSequenceLength(mechanismTypeList); + int sequenceLengthFieldSize = DerEncodingHelper.GetLengthFieldSize(sequenceLength); + int constructionLength = 1 + sequenceLengthFieldSize + sequenceLength; + byte[] buffer = new byte[constructionLength]; + int offset = 0; + WriteMechanismTypeListSequence(buffer, ref offset, mechanismTypeList, sequenceLength); + return buffer; + } + private static int GetEncodedMechanismTypeListLength(List mechanismTypeList) { if (mechanismTypeList == null) From 39fa4cdb3ef69082e8aea291db820f979e784114 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 15:13:35 +0200 Subject: [PATCH 067/247] NTLMCryptography: Add ComputeMechListMIC method --- SMBLibrary.Tests/NTLM/NTLMSigningTests.cs | 17 +++++++++++++- .../NTLM/Helpers/NTLMCryptography.cs | 23 ++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs index eaf37dd1..12ab8469 100644 --- a/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMSigningTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2019 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -209,6 +209,21 @@ public void Test_ComputeClientSealKey() Assert.IsTrue(ByteUtils.AreByteArraysEqual(expected, sealKey)); } + [TestMethod] + public void Test_ComputeMechListMIC() + { + // Arrange + byte[] exportedSessionKey = new byte[] { 0xBE, 0xC7, 0x33, 0xF6, 0x23, 0xB1, 0x2B, 0x98, 0xD4, 0x21, 0xFF, 0xCD, 0xD5, 0x42, 0xE3, 0xA2 }; + byte[] mechListMicBytes = new byte[] { 0x30, 0x0c, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a }; + byte[] expectedMic = new byte[] { 0x01, 0x00, 0x00, 0x00, 0xa1, 0xa6, 0x7b, 0x80, 0x35, 0xc1, 0x76, 0xd3, 0x00, 0x00, 0x00, 0x00 }; + + // Act + byte[] mic = NTLMCryptography.ComputeMechListMIC(exportedSessionKey, mechListMicBytes); + + // Assert + Assert.IsTrue(ByteUtils.AreByteArraysEqual(mic, expectedMic)); + } + private static byte[] GetExportedSessionKey(byte[] sessionBaseKey, AuthenticateMessage message, byte[] serverChallenge, byte[] lmowf) { byte[] keyExchangeKey; diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index d0da43ff..913df3fb 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -329,5 +329,26 @@ private static byte[] ComputeSealKey(byte[] exportedSessionKey, bool isClient) byte[] concatendated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); return MD5.Create().ComputeHash(concatendated); } + + public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] message) + { + return ComputeMechListMIC(exportedSessionKey, message, 0); + } + + public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] message, int seqNum) + { + // [MS-NLMP] 3.4.4.2 + byte[] signKey = ComputeClientSignKey(exportedSessionKey); + byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); + byte[] concatendated = ByteUtils.Concatenate(sequenceNumberBytes, message); + byte[] fullHash = new HMACMD5(signKey).ComputeHash(concatendated); + byte[] hash = ByteReader.ReadBytes(fullHash, 0, 8); + + byte[] sealKey = ComputeClientSealKey(exportedSessionKey); + byte[] encryptedHash = RC4.Encrypt(sealKey, hash); + + byte[] version = new byte[] { 0x01, 0x00, 0x00, 0x00 }; + return ByteUtils.Concatenate(ByteUtils.Concatenate(version, encryptedHash), sequenceNumberBytes); + } } } From 99ae36da554f5000256dabdf4d47295ab98e0b32 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 15:19:04 +0200 Subject: [PATCH 068/247] GetNegotiateMessage: Add negotiateMessageBytes parameter Motivation: This will allow us to calculate MechListMIC --- SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs | 4 ++-- SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index a415039e..869b9f8d 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -103,7 +103,7 @@ protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) challengeMessageBytes = securityBlob; } - byte[] authenticateMessageBytes = NTLMAuthenticationHelper.GetAuthenticateMessage(challengeMessageBytes, m_domainName, m_userName, m_password, m_spn, m_authenticationMethod, out m_sessionKey); + byte[] authenticateMessageBytes = NTLMAuthenticationHelper.GetAuthenticateMessage(m_negotiateMessageBytes, challengeMessageBytes, m_domainName, m_userName, m_password, m_spn, m_authenticationMethod, out m_sessionKey); if (useGSSAPI && authenticateMessageBytes != null) { SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 47dc960b..51ef25b8 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -48,7 +48,7 @@ public static byte[] GetNegotiateMessage(string domainName, string userName, str return negotiateMessage.GetBytes(); } - public static byte[] GetAuthenticateMessage(byte[] challengeMessageBytes, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) + public static byte[] GetAuthenticateMessage(byte[] negotiateMessageBytes, byte[] challengeMessageBytes, string domainName, string userName, string password, string spn, AuthenticationMethod authenticationMethod, out byte[] sessionKey) { sessionKey = null; From 64aca73d8a181007c6bfc6b5a0a6b5ecd24819d6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 15:56:13 +0200 Subject: [PATCH 069/247] NTLMAuthenticationHelper: Calculate Authentication message MIC --- SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 51ef25b8..56a5cb84 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -162,6 +162,7 @@ public static byte[] GetAuthenticateMessage(byte[] negotiateMessageBytes, byte[] sessionKey = keyExchangeKey; } + authenticateMessage.CalculateMIC(sessionKey, negotiateMessageBytes, challengeMessageBytes); return authenticateMessage.GetBytes(); } From 8b11c5712c07702746fac7d3a5a5b4f3b693ccdf Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 16:18:07 +0200 Subject: [PATCH 070/247] SMB1Client: Add ability to control response timeout --- SMBLibrary/Client/SMB1Client.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index b2775917..a8cc3500 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -78,9 +78,14 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport) } public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity) + { + return Connect(serverAddress, transport, forceExtendedSecurity, DefaultResponseTimeoutInMilliseconds); + } + + public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) { int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); - return Connect(serverAddress, transport, port, forceExtendedSecurity, DefaultResponseTimeoutInMilliseconds); + return Connect(serverAddress, transport, port, forceExtendedSecurity, responseTimeoutInMilliseconds); } internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) From 64a27f1d86c7cfa89360a182d7b28462ee429f83 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 16:18:55 +0200 Subject: [PATCH 071/247] SMBLibrary 1.5.1 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/RevisionHistory.txt | 8 ++++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/SMBServer.csproj | 1 + 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index d3f77076..55758929 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.0 + 1.5.1 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index d1085912..dccde576 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.0 + 1.5.1 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 5d043ef0..58d2be7f 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -486,3 +486,11 @@ Revision History: Client: Allow reusing client instance. Client: Prefer IPv4 when resolving DNS hostname. NTLMCryptography: Add .NET 5.0 \ 6.0 support. + +1.5.1 - Client: Support anonymous login. + Client: Added API to provide custom authentication. + Client: Improve disconnection detection. + Client: Add ability to control response timeout. + Client: Calculate Authentication message MIC. + NBTConnectionReceiveBuffer: Use ArrayPool rent buffers to reduce RAM usage. + Added access denied ace support. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 75d3689f..8408ae8e 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.0.3 + 1.5.1 1573;1591 SMBLibrary false diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index 442d6b06..0065d28d 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -5,6 +5,7 @@ net20;net40;netcoreapp3.1 Copyright © Tal Aloni 2014-2023 SMBServer + 1.5.1 SMBServer true From 3b683cd4ae632fc7ac0f9e5337c0f383b4e2750e Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 21:26:22 +0200 Subject: [PATCH 072/247] Server: ServerService: correctly handle unsupported ShareEnum levels --- .../Exceptions/InvalidLevelException.cs | 28 +++++++++++++++++++ .../Exceptions/UnsupportedLevelException.cs | 28 +++++++++++++++++++ .../Services/ServerService/ServerService.cs | 27 ++++++++++++++---- .../Structures/ServerInfo/ServerInfo.cs | 8 ++---- .../Structures/ShareInfo/ShareEnum.cs | 8 ++---- .../Structures/ShareInfo/ShareInfo.cs | 7 ++--- 6 files changed, 84 insertions(+), 22 deletions(-) create mode 100644 SMBLibrary/Services/Exceptions/InvalidLevelException.cs create mode 100644 SMBLibrary/Services/Exceptions/UnsupportedLevelException.cs diff --git a/SMBLibrary/Services/Exceptions/InvalidLevelException.cs b/SMBLibrary/Services/Exceptions/InvalidLevelException.cs new file mode 100644 index 00000000..03476acc --- /dev/null +++ b/SMBLibrary/Services/Exceptions/InvalidLevelException.cs @@ -0,0 +1,28 @@ +/* Copyright (C) 2024 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; + +namespace SMBLibrary.Services +{ + public class InvalidLevelException : Exception + { + private uint m_level; + + public InvalidLevelException(uint level) + { + m_level = level; + } + + public uint Level + { + get + { + return m_level; + } + } + } +} diff --git a/SMBLibrary/Services/Exceptions/UnsupportedLevelException.cs b/SMBLibrary/Services/Exceptions/UnsupportedLevelException.cs new file mode 100644 index 00000000..234a8fbf --- /dev/null +++ b/SMBLibrary/Services/Exceptions/UnsupportedLevelException.cs @@ -0,0 +1,28 @@ +/* Copyright (C) 2024 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; + +namespace SMBLibrary.Services +{ + public class UnsupportedLevelException : Exception + { + private uint m_level; + + public UnsupportedLevelException(uint level) + { + m_level = level; + } + + public uint Level + { + get + { + return m_level; + } + } + } +} diff --git a/SMBLibrary/Services/ServerService/ServerService.cs b/SMBLibrary/Services/ServerService/ServerService.cs index 5b843180..6ad5d8f9 100644 --- a/SMBLibrary/Services/ServerService/ServerService.cs +++ b/SMBLibrary/Services/ServerService/ServerService.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2018 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -6,8 +6,6 @@ */ using System; using System.Collections.Generic; -using System.Text; -using Utilities; namespace SMBLibrary.Services { @@ -47,8 +45,7 @@ public override byte[] GetResponseBytes(ushort opNum, byte[] requestBytes) { case ServerServiceOpName.NetrShareEnum: { - NetrShareEnumRequest request = new NetrShareEnumRequest(requestBytes); - NetrShareEnumResponse response = GetNetrShareEnumResponse(request); + NetrShareEnumResponse response = GetNetrShareEnumResponse(requestBytes); return response.GetBytes(); } case ServerServiceOpName.NetrShareGetInfo: @@ -68,9 +65,27 @@ public override byte[] GetResponseBytes(ushort opNum, byte[] requestBytes) } } - public NetrShareEnumResponse GetNetrShareEnumResponse(NetrShareEnumRequest request) + public NetrShareEnumResponse GetNetrShareEnumResponse(byte[] requestBytes) { + NetrShareEnumRequest request; NetrShareEnumResponse response = new NetrShareEnumResponse(); + try + { + request = new NetrShareEnumRequest(requestBytes); + } + catch (UnsupportedLevelException ex) + { + response.InfoStruct = new ShareEnum(ex.Level); + response.Result = Win32Error.ERROR_NOT_SUPPORTED; + return response; + } + catch (InvalidLevelException ex) + { + response.InfoStruct = new ShareEnum(ex.Level); + response.Result = Win32Error.ERROR_INVALID_LEVEL; + return response; + } + switch (request.InfoStruct.Level) { case 0: diff --git a/SMBLibrary/Services/ServerService/Structures/ServerInfo/ServerInfo.cs b/SMBLibrary/Services/ServerService/Structures/ServerInfo/ServerInfo.cs index 501783f9..7daef0ed 100644 --- a/SMBLibrary/Services/ServerService/Structures/ServerInfo/ServerInfo.cs +++ b/SMBLibrary/Services/ServerService/Structures/ServerInfo/ServerInfo.cs @@ -1,13 +1,10 @@ -/* Copyright (C) 2014-2018 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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 SMBLibrary.RPC; -using Utilities; namespace SMBLibrary.Services { @@ -56,9 +53,8 @@ public void Read(NDRParser parser) Info = info101; break; default: - throw new NotImplementedException(); + throw new InvalidLevelException(Level); } - ; parser.EndStructure(); // SERVER_INFO Union } diff --git a/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareEnum.cs b/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareEnum.cs index 5347839e..3cca6323 100644 --- a/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareEnum.cs +++ b/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareEnum.cs @@ -1,13 +1,11 @@ -/* Copyright (C) 2014-2018 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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 SMBLibrary.RPC; -using Utilities; namespace SMBLibrary.Services { @@ -64,9 +62,9 @@ public void Read(NDRParser parser) case 501: case 502: case 503: - throw new NotImplementedException(); + throw new UnsupportedLevelException(level); default: - break; + throw new InvalidLevelException(level); } parser.EndStructure(); // SHARE_ENUM_UNION parser.EndStructure(); // SHARE_ENUM_STRUCT diff --git a/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareInfo.cs b/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareInfo.cs index 22eb9d3d..3d9d27ca 100644 --- a/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareInfo.cs +++ b/SMBLibrary/Services/ServerService/Structures/ShareInfo/ShareInfo.cs @@ -1,13 +1,10 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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 SMBLibrary.RPC; -using Utilities; namespace SMBLibrary.Services { @@ -56,7 +53,7 @@ public void Read(NDRParser parser) Info = info1; break; default: - throw new NotImplementedException(); + throw new InvalidLevelException(Level); } parser.EndStructure(); // SHARE_INFO Union } From c284b057822956a913245cdb84b8ad9b00348035 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jan 2024 21:34:18 +0200 Subject: [PATCH 073/247] SMBLibrary/Readme.md: Correct URL --- SMBLibrary/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Readme.md b/SMBLibrary/Readme.md index 7f98fbee..83ba59c5 100644 --- a/SMBLibrary/Readme.md +++ b/SMBLibrary/Readme.md @@ -55,7 +55,7 @@ Using SMBLibrary: Any directory / filesystem / object you wish to share must implement the IFileSystem interface (or the lower-level INTFileStore interface). You can share anything from actual directories to custom objects, as long as they expose a directory structure. -Client code examples can be found [here](ClientExamples.md). +Client code examples can be found [here](../ClientExamples.md). Contact: ======== From 39388660d33143b6a4ec1dd070fd688dda64831b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 17 Jan 2024 22:34:44 +0200 Subject: [PATCH 074/247] FileRenameInformationType2: Bugfix: Ensure length is at least 24 bytes --- .../FileInformation/Set/FileRenameInformationType2.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/Set/FileRenameInformationType2.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/Set/FileRenameInformationType2.cs index 8af19219..9b1c5344 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/Set/FileRenameInformationType2.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/Set/FileRenameInformationType2.cs @@ -1,18 +1,16 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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 { /// - /// [MS-FSCC] 2.4.34.2 - FileRenameInformation Type 2 + /// [MS-FSCC] 2.4.37.2 - FileRenameInformation Type 2 /// /// /// [MS-FSA] 2.1.5.14.11 @@ -28,6 +26,7 @@ public class FileRenameInformationType2 : FileInformation public ulong RootDirectory; private uint FileNameLength; public string FileName = String.Empty; + // Padding - the number of bytes required to make the size of this structure at least 24. public FileRenameInformationType2() { @@ -62,7 +61,7 @@ public override int Length { get { - return FixedLength + FileName.Length * 2; + return Math.Max(FixedLength + FileName.Length * 2, 24); } } } From f366c78fc9de0c27a79ecd63379f60d88b851420 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 5 Feb 2024 21:26:56 +0200 Subject: [PATCH 075/247] NBTConnectionReceiveBuffer: Dispose: Fix thread-safety issue --- .../NetBios/NBTConnectionReceiveBuffer.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs index 284ef561..6cec8bed 100644 --- a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs +++ b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -15,6 +15,9 @@ namespace SMBLibrary.NetBios { public class NBTConnectionReceiveBuffer : IDisposable { +#if NETSTANDARD2_0 + private object m_bufferSyncLock = new object(); +#endif private byte[] m_buffer; private int m_readOffset = 0; private int m_bytesInBuffer = 0; @@ -127,13 +130,18 @@ private void RemovePacketBytes() public void Dispose() { - if (m_buffer != null) - { #if NETSTANDARD2_0 - ArrayPool.Shared.Return(m_buffer); -#endif - m_buffer = null; + lock (m_bufferSyncLock) + { + if (m_buffer != null) + { + ArrayPool.Shared.Return(m_buffer); + m_buffer = null; + } } +#else + m_buffer = null; +#endif } public byte[] Buffer From b03050028a4c558d898230a20e581ee977c83e4a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 5 Feb 2024 21:28:24 +0200 Subject: [PATCH 076/247] SMBLibrary 1.5.1.1 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 8408ae8e..ade51535 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.1 + 1.5.1.1 1573;1591 SMBLibrary false From 18275637d723ded36ae2b1f5dc87464c65b7f753 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 11 Feb 2024 00:29:06 +0200 Subject: [PATCH 077/247] SMB1Client, SMB2Client: Disconnect: Invoke Socket.Close --- SMBLibrary/Client/SMB1Client.cs | 1 + SMBLibrary/Client/SMB2Client.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index a8cc3500..72c3108d 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -171,6 +171,7 @@ public void Disconnect() if (m_isConnected) { m_clientSocket.Disconnect(false); + m_clientSocket.Close(); m_connectionState.ReceiveBuffer.Dispose(); m_isConnected = false; m_userID = 0; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 32b14104..ce87e293 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -181,6 +181,7 @@ public void Disconnect() if (m_isConnected) { m_clientSocket.Disconnect(false); + m_clientSocket.Close(); m_connectionState.ReceiveBuffer.Dispose(); m_isConnected = false; m_messageID = 0; From db1410b74d2605311b098d011d7aee8831ee399a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 11 Feb 2024 00:29:49 +0200 Subject: [PATCH 078/247] SMBLibrary 1.5.1.2 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index ade51535..172dc15e 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.1.1 + 1.5.1.2 1573;1591 SMBLibrary false From 5dff3875b6e3e82e3a95f86409f5ee1fe26d5e26 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 17 Feb 2024 14:28:19 +0200 Subject: [PATCH 079/247] Replace DiskAccessLibrary.FileSystems.Abstractions with NuGet package reference --- ...essLibrary.FileSystems.Abstractions.csproj | 19 --- .../FileSystem.cs | 154 ------------------ .../FileSystemEntry.cs | 52 ------ .../IFileSystem.cs | 96 ----------- .../SMBLibrary.Adapters.csproj | 5 +- SMBServer.sln | 6 - 6 files changed, 4 insertions(+), 328 deletions(-) delete mode 100644 DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj delete mode 100644 DiskAccessLibrary.FileSystems.Abstractions/FileSystem.cs delete mode 100644 DiskAccessLibrary.FileSystems.Abstractions/FileSystemEntry.cs delete mode 100644 DiskAccessLibrary.FileSystems.Abstractions/IFileSystem.cs diff --git a/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj b/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj deleted file mode 100644 index dbd4cfaf..00000000 --- a/DiskAccessLibrary.FileSystems.Abstractions/DiskAccessLibrary.FileSystems.Abstractions.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net20;net40;netstandard2.0 - DiskAccessLibrary.FileSystems.Abstractions - 1.0.0 - 1573;1591 - DiskAccessLibrary.FileSystems.Abstractions - false - Tal Aloni - Copyright © Tal Aloni 2012-2023 - DiskAccessLibrary abstractions and interfaces for FileSystem implementations - LGPL-3.0-or-later - https://github.com/TalAloni/DynamicDiskPartitioner - https://github.com/TalAloni/DynamicDiskPartitioner - true - - - diff --git a/DiskAccessLibrary.FileSystems.Abstractions/FileSystem.cs b/DiskAccessLibrary.FileSystems.Abstractions/FileSystem.cs deleted file mode 100644 index 8f29d383..00000000 --- a/DiskAccessLibrary.FileSystems.Abstractions/FileSystem.cs +++ /dev/null @@ -1,154 +0,0 @@ -/* Copyright (C) 2014-2020 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; - -namespace DiskAccessLibrary.FileSystems.Abstractions -{ - public abstract class FileSystem : IFileSystem - { - public abstract FileSystemEntry GetEntry(string path); - public abstract FileSystemEntry CreateFile(string path); - public abstract FileSystemEntry CreateDirectory(string path); - public abstract void Move(string source, string destination); - public abstract void Delete(string path); - public abstract List ListEntriesInDirectory(string path); - public abstract Stream OpenFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options); - public abstract void SetAttributes(string path, bool? isHidden, bool? isReadonly, bool? isArchived); - public abstract void SetDates(string path, DateTime? creationDT, DateTime? lastWriteDT, DateTime? lastAccessDT); - - public List ListEntriesInRootDirectory() - { - return ListEntriesInDirectory(@"\"); - } - - public virtual List> ListDataStreams(string path) - { - FileSystemEntry entry = GetEntry(path); - List> result = new List>(); - if (!entry.IsDirectory) - { - result.Add(new KeyValuePair("::$DATA", entry.Size)); - } - return result; - } - - public Stream OpenFile(string path, FileMode mode, FileAccess access, FileShare share) - { - return OpenFile(path, mode, access, share, FileOptions.None); - } - - public void CopyFile(string sourcePath, string destinationPath) - { - const int bufferLength = 1024 * 1024; - FileSystemEntry sourceFile = GetEntry(sourcePath); - FileSystemEntry destinationFile = GetEntry(destinationPath); - if (sourceFile == null | sourceFile.IsDirectory) - { - throw new FileNotFoundException(); - } - - if (destinationFile != null && destinationFile.IsDirectory) - { - throw new ArgumentException("Destination cannot be a directory"); - } - - if (destinationFile == null) - { - destinationFile = CreateFile(destinationPath); - } - Stream sourceStream = OpenFile(sourcePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, FileOptions.SequentialScan); - Stream destinationStream = OpenFile(destinationPath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite, FileOptions.None); - while (sourceStream.Position < sourceStream.Length) - { - int readSize = (int)Math.Max(bufferLength, sourceStream.Length - sourceStream.Position); - byte[] buffer = new byte[readSize]; - sourceStream.Read(buffer, 0, buffer.Length); - destinationStream.Write(buffer, 0, buffer.Length); - } - sourceStream.Close(); - destinationStream.Close(); - } - - public virtual bool Exists(string path) - { - try - { - GetEntry(path); - } - catch (FileNotFoundException) - { - return false; - } - catch (DirectoryNotFoundException) - { - return false; - } - - return true; - } - - public abstract string Name - { - get; - } - - public abstract long Size - { - get; - } - - public abstract long FreeSpace - { - get; - } - - public abstract bool SupportsNamedStreams - { - get; - } - - public static string GetParentDirectory(string path) - { - if (path == String.Empty) - { - path = @"\"; - } - - if (!path.StartsWith(@"\")) - { - throw new ArgumentException("Invalid path"); - } - - if (path.Length > 1 && path.EndsWith(@"\")) - { - path = path.Substring(0, path.Length - 1); - } - - int separatorIndex = path.LastIndexOf(@"\"); - return path.Substring(0, separatorIndex + 1); - } - - /// - /// Will append a trailing slash to a directory path if not already present - /// - /// - /// - public static string GetDirectoryPath(string path) - { - if (path.EndsWith(@"\")) - { - return path; - } - else - { - return path + @"\"; - } - } - } -} diff --git a/DiskAccessLibrary.FileSystems.Abstractions/FileSystemEntry.cs b/DiskAccessLibrary.FileSystems.Abstractions/FileSystemEntry.cs deleted file mode 100644 index 56e042db..00000000 --- a/DiskAccessLibrary.FileSystems.Abstractions/FileSystemEntry.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* Copyright (C) 2014-2020 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; - -namespace DiskAccessLibrary.FileSystems.Abstractions -{ - public class FileSystemEntry - { - /// - /// Full Path. Directory path should end with a trailing slash. - /// - public string FullName; - public string Name; - public bool IsDirectory; - public ulong Size; - public DateTime CreationTime; - public DateTime LastWriteTime; - public DateTime LastAccessTime; - public bool IsHidden; - public bool IsReadonly; - public bool IsArchived; - - public FileSystemEntry(string fullName, string name, bool isDirectory, ulong size, DateTime creationTime, DateTime lastWriteTime, DateTime lastAccessTime, bool isHidden, bool isReadonly, bool isArchived) - { - FullName = fullName; - Name = name; - IsDirectory = isDirectory; - Size = size; - CreationTime = creationTime; - LastWriteTime = lastWriteTime; - LastAccessTime = lastAccessTime; - IsHidden = isHidden; - IsReadonly = isHidden; - IsArchived = isHidden; - - if (isDirectory) - { - FullName = FileSystem.GetDirectoryPath(FullName); - } - } - - public FileSystemEntry Clone() - { - FileSystemEntry clone = (FileSystemEntry)MemberwiseClone(); - return clone; - } - } -} diff --git a/DiskAccessLibrary.FileSystems.Abstractions/IFileSystem.cs b/DiskAccessLibrary.FileSystems.Abstractions/IFileSystem.cs deleted file mode 100644 index 14f6e9d6..00000000 --- a/DiskAccessLibrary.FileSystems.Abstractions/IFileSystem.cs +++ /dev/null @@ -1,96 +0,0 @@ -/* Copyright (C) 2014-2020 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; - -namespace DiskAccessLibrary.FileSystems.Abstractions -{ - public interface IFileSystem - { - /// - /// - /// - /// - FileSystemEntry GetEntry(string path); - - /// - /// - /// - FileSystemEntry CreateFile(string path); - - /// - /// - /// - FileSystemEntry CreateDirectory(string path); - - /// - /// - /// - /// - void Move(string source, string destination); - - /// - /// - /// - /// - void Delete(string path); - - /// - /// - /// - List ListEntriesInDirectory(string path); - - /// - /// - /// - /// - List> ListDataStreams(string path); - - /// - /// - /// - /// - Stream OpenFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options); - - /// - /// - /// - void SetAttributes(string path, bool? isHidden, bool? isReadonly, bool? isArchived); - - /// - /// - /// - void SetDates(string path, DateTime? creationDT, DateTime? lastWriteDT, DateTime? lastAccessDT); - - string Name - { - get; - } - - /// - long Size - { - get; - } - - /// - long FreeSpace - { - get; - } - - /// - /// Indicates support for opening named streams (alternate data streams). - /// Named streams are opened using the filename:stream syntax. - /// - bool SupportsNamedStreams - { - get; - } - } -} diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 55758929..87591f13 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -16,8 +16,11 @@ - + + + + diff --git a/SMBServer.sln b/SMBServer.sln index f13b65c5..416bcca4 100644 --- a/SMBServer.sln +++ b/SMBServer.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.29728.190 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DiskAccessLibrary.FileSystems.Abstractions", "DiskAccessLibrary.FileSystems.Abstractions\DiskAccessLibrary.FileSystems.Abstractions.csproj", "{9119EC7E-AF78-4814-BF03-F3823A29A471}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Utilities", "Utilities\Utilities.csproj", "{6E0F2D1E-6167-4032-BA90-DEE3A99207D0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SMBLibrary", "SMBLibrary\SMBLibrary.csproj", "{8D9E8F5D-FD13-4E4C-9723-A333DA2034A7}" @@ -23,10 +21,6 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9119EC7E-AF78-4814-BF03-F3823A29A471}.Release|Any CPU.Build.0 = Release|Any CPU {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E0F2D1E-6167-4032-BA90-DEE3A99207D0}.Release|Any CPU.ActiveCfg = Release|Any CPU From a9f9ef95e8a1fc76c413a6f5ac55d0cbe2250a57 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 1 Mar 2024 10:55:52 +0200 Subject: [PATCH 080/247] SMB2Client: Support non-Microsoft servers returning MaxReadSize > MaxTransactSize --- SMBLibrary/Client/SMB2Client.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index ce87e293..4486e9ab 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -481,9 +481,12 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) NegotiateResponse negotiateResponse = (NegotiateResponse)command; if ((negotiateResponse.Capabilities & Capabilities.LargeMTU) > 0) { - // [MS-SMB2] 3.2.5.1 Receiving Any Message - If the message size received exceeds Connection.MaxTransactSize, the client MUST disconnect the connection. - // Note: Windows clients do not enforce the MaxTransactSize value, we add 256 bytes. - int maxPacketSize = SessionPacket.HeaderLength + (int)Math.Min(negotiateResponse.MaxTransactSize, ClientMaxTransactSize) + 256; + // [MS-SMB2] 3.2.5.1 Receiving Any Message - If the message size received exceeds Connection.MaxTransactSize, the client SHOULD disconnect the connection. + // Note: Windows clients do not enforce the MaxTransactSize value. + // We use a value that we have observed to work well with both Microsoft and non-Microsoft servers. + // see https://github.com/TalAloni/SMBLibrary/issues/239 + int serverMaxTransactSize = (int)Math.Max(negotiateResponse.MaxTransactSize, negotiateResponse.MaxReadSize); + int maxPacketSize = SessionPacket.HeaderLength + (int)Math.Min(serverMaxTransactSize, ClientMaxTransactSize) + 256; if (maxPacketSize > state.ReceiveBuffer.Buffer.Length) { state.ReceiveBuffer.IncreaseBufferSize(maxPacketSize); From 693269f02f9bb0affd42194b6d3677d4f1d99f98 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 1 Mar 2024 13:42:19 +0200 Subject: [PATCH 081/247] SMBLibrary 1.5.1.3 --- SMBLibrary/SMBLibrary.csproj | 4 ++-- SMBServer/SMBServer.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 172dc15e..d0d14e5b 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,12 +3,12 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.1.2 + 1.5.1.3 1573;1591 SMBLibrary false Tal Aloni - Copyright © Tal Aloni 2014-2023 + Copyright © Tal Aloni 2014-2024 SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index 0065d28d..6e3656dd 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -3,7 +3,7 @@ WinExe net20;net40;netcoreapp3.1 - Copyright © Tal Aloni 2014-2023 + Copyright © Tal Aloni 2014-2024 SMBServer 1.5.1 SMBServer From dd7b945ddc6f04ec51d0f9cc04f2f61be6ec6fb3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 9 Mar 2024 09:36:35 +0200 Subject: [PATCH 082/247] SMBIServer: Use CancellationToken for send keep-alive thread if available --- SMBLibrary/Server/SMBServer.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 978f802e..578760f4 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2021 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -32,6 +32,9 @@ public partial class SMBServer private ConnectionManager m_connectionManager; private Thread m_sendSMBKeepAliveThread; +#if !NET20 + private CancellationTokenSource m_sendSMBKeepAliveCancellationTokenSource; +#endif private IPAddress m_serverAddress; private SMBTransportType m_transport; @@ -106,11 +109,22 @@ internal void Start(IPAddress serverAddress, SMBTransportType transport, int por if (connectionInactivityTimeout.HasValue) { +#if !NET20 + m_sendSMBKeepAliveCancellationTokenSource = new CancellationTokenSource(); +#endif m_sendSMBKeepAliveThread = new Thread(delegate() { while (m_listening) { +#if NET20 Thread.Sleep(InactivityMonitoringInterval); +#else + bool cancelled = m_sendSMBKeepAliveCancellationTokenSource.Token.WaitHandle.WaitOne(InactivityMonitoringInterval); + if (cancelled) + { + return; + } +#endif m_connectionManager.SendSMBKeepAlive(connectionInactivityTimeout.Value); } }); @@ -126,7 +140,11 @@ public void Stop() m_listening = false; if (m_sendSMBKeepAliveThread != null) { +#if NET20 m_sendSMBKeepAliveThread.Abort(); +#else + m_sendSMBKeepAliveCancellationTokenSource?.Cancel(); +#endif } SocketUtils.ReleaseSocket(m_listenerSocket); m_connectionManager.ReleaseAllConnections(); From cf182e787fc4437bef01be6f275bae4d5cd856dc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Jun 2024 12:39:25 +0300 Subject: [PATCH 083/247] Client: NetBIOS over TCP: Apply timeout set by the client instead of hardcoded value --- SMBLibrary/Client/SMB1Client.cs | 3 +-- SMBLibrary/Client/SMB2Client.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 72c3108d..d083f892 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -585,10 +585,9 @@ internal SMB1Message WaitForMessage(CommandName commandName) internal SessionPacket WaitForSessionResponsePacket() { - const int TimeOut = 5000; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < TimeOut) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds) { if (m_sessionResponsePacket != null) { diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 4486e9ab..827a7057 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -555,10 +555,9 @@ internal SMB2Command WaitForCommand(ulong messageID) internal SessionPacket WaitForSessionResponsePacket() { - const int TimeOut = 5000; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < TimeOut) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds) { if (m_sessionResponsePacket != null) { From 1a8c94ba941362c4dbff2f2e120d7f731a6a6f55 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Jun 2024 17:21:15 +0300 Subject: [PATCH 084/247] Server: ConnectionState: Fix thread-safety issue --- .../Server/ConnectionState/SMB1ConnectionState.cs | 12 +++++++++--- .../Server/ConnectionState/SMB2ConnectionState.cs | 12 +++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs b/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs index 6c9903d3..f49adeed 100644 --- a/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs +++ b/SMBLibrary/Server/ConnectionState/SMB1ConnectionState.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2019 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -78,14 +78,20 @@ public SMB1Session CreateSession(string userName, string machineName, byte[] ses public SMB1Session GetSession(ushort userID) { SMB1Session session; - m_sessions.TryGetValue(userID, out session); + lock (m_sessions) + { + m_sessions.TryGetValue(userID, out session); + } return session; } public void RemoveSession(ushort userID) { SMB1Session session; - m_sessions.TryGetValue(userID, out session); + lock (m_sessions) + { + m_sessions.TryGetValue(userID, out session); + } if (session != null) { session.Close(); diff --git a/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs b/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs index 553a9ea2..724dd192 100644 --- a/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs +++ b/SMBLibrary/Server/ConnectionState/SMB2ConnectionState.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -56,14 +56,20 @@ public SMB2Session CreateSession(ulong sessionID, string userName, string machin public SMB2Session GetSession(ulong sessionID) { SMB2Session session; - m_sessions.TryGetValue(sessionID, out session); + lock (m_sessions) + { + m_sessions.TryGetValue(sessionID, out session); + } return session; } public void RemoveSession(ulong sessionID) { SMB2Session session; - m_sessions.TryGetValue(sessionID, out session); + lock (m_sessions) + { + m_sessions.TryGetValue(sessionID, out session); + } if (session != null) { session.Close(); From ac403baf415e36b699d9aee5c57c2bc3abc820e8 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 2 Jun 2024 10:11:09 +0300 Subject: [PATCH 085/247] Server, Client: Fix thread-safety violations related to NBTConnectionReceiveBuffer --- SMBLibrary/Client/SMB1Client.cs | 114 +++++++++--------- SMBLibrary/Client/SMB2Client.cs | 112 +++++++++-------- .../NetBios/NBTConnectionReceiveBuffer.cs | 15 +-- SMBLibrary/Server/ConnectionManager.cs | 5 +- SMBLibrary/Server/SMBServer.cs | 91 +++++++------- 5 files changed, 176 insertions(+), 161 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index d083f892..63ebcc7f 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -172,7 +172,10 @@ public void Disconnect() { m_clientSocket.Disconnect(false); m_clientSocket.Close(); - m_connectionState.ReceiveBuffer.Dispose(); + lock (m_connectionState.ReceiveBuffer) + { + m_connectionState.ReceiveBuffer.Dispose(); + } m_isConnected = false; m_userID = 0; } @@ -424,63 +427,66 @@ private void OnClientSocketReceive(IAsyncResult ar) ConnectionState state = (ConnectionState)ar.AsyncState; Socket clientSocket = state.ClientSocket; - if (!clientSocket.Connected) - { - state.ReceiveBuffer.Dispose(); - 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. - { - state.ReceiveBuffer.Dispose(); - return; - } - catch (ObjectDisposedException) - { - Log("[ReceiveCallback] EndReceive ObjectDisposedException"); - state.ReceiveBuffer.Dispose(); - return; - } - catch (SocketException ex) + lock (state.ReceiveBuffer) { - Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); - state.ReceiveBuffer.Dispose(); - return; - } + if (!clientSocket.Connected) + { + state.ReceiveBuffer.Dispose(); + return; + } - if (numberOfBytesReceived == 0) - { - m_isConnected = false; - state.ReceiveBuffer.Dispose(); - } - else - { - NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; - buffer.SetNumberOfBytesReceived(numberOfBytesReceived); - ProcessConnectionBuffer(state); + int numberOfBytesReceived = 0; + try + { + numberOfBytesReceived = clientSocket.EndReceive(ar); + } + catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class. + { + state.ReceiveBuffer.Dispose(); + return; + } + catch (ObjectDisposedException) + { + Log("[ReceiveCallback] EndReceive ObjectDisposedException"); + state.ReceiveBuffer.Dispose(); + return; + } + catch (SocketException ex) + { + Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); + state.ReceiveBuffer.Dispose(); + return; + } - if (clientSocket.Connected) + if (numberOfBytesReceived == 0) { - try - { - clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); - } - catch (ObjectDisposedException) - { - m_isConnected = false; - buffer.Dispose(); - Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); - } - catch (SocketException ex) + m_isConnected = false; + state.ReceiveBuffer.Dispose(); + } + else + { + NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; + buffer.SetNumberOfBytesReceived(numberOfBytesReceived); + ProcessConnectionBuffer(state); + + if (clientSocket.Connected) { - m_isConnected = false; - buffer.Dispose(); - Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + try + { + clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); + } + catch (ObjectDisposedException) + { + m_isConnected = false; + buffer.Dispose(); + Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); + } + catch (SocketException ex) + { + m_isConnected = false; + buffer.Dispose(); + Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + } } } } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 827a7057..d90cc0cd 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -182,7 +182,10 @@ public void Disconnect() { m_clientSocket.Disconnect(false); m_clientSocket.Close(); - m_connectionState.ReceiveBuffer.Dispose(); + lock (m_connectionState.ReceiveBuffer) + { + m_connectionState.ReceiveBuffer.Dispose(); + } m_isConnected = false; m_messageID = 0; m_sessionID = 0; @@ -357,63 +360,66 @@ private void OnClientSocketReceive(IAsyncResult ar) ConnectionState state = (ConnectionState)ar.AsyncState; Socket clientSocket = state.ClientSocket; - if (!clientSocket.Connected) - { - state.ReceiveBuffer.Dispose(); - 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. - { - state.ReceiveBuffer.Dispose(); - return; - } - catch (ObjectDisposedException) - { - Log("[ReceiveCallback] EndReceive ObjectDisposedException"); - state.ReceiveBuffer.Dispose(); - return; - } - catch (SocketException ex) + lock (state.ReceiveBuffer) { - Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); - state.ReceiveBuffer.Dispose(); - return; - } + if (!clientSocket.Connected) + { + state.ReceiveBuffer.Dispose(); + return; + } - if (numberOfBytesReceived == 0) - { - m_isConnected = false; - state.ReceiveBuffer.Dispose(); - } - else - { - NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; - buffer.SetNumberOfBytesReceived(numberOfBytesReceived); - ProcessConnectionBuffer(state); + int numberOfBytesReceived = 0; + try + { + numberOfBytesReceived = clientSocket.EndReceive(ar); + } + catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class. + { + state.ReceiveBuffer.Dispose(); + return; + } + catch (ObjectDisposedException) + { + Log("[ReceiveCallback] EndReceive ObjectDisposedException"); + state.ReceiveBuffer.Dispose(); + return; + } + catch (SocketException ex) + { + Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); + state.ReceiveBuffer.Dispose(); + return; + } - if (clientSocket.Connected) + if (numberOfBytesReceived == 0) { - try - { - clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); - } - catch (ObjectDisposedException) - { - m_isConnected = false; - Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); - buffer.Dispose(); - } - catch (SocketException ex) + m_isConnected = false; + state.ReceiveBuffer.Dispose(); + } + else + { + NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; + buffer.SetNumberOfBytesReceived(numberOfBytesReceived); + ProcessConnectionBuffer(state); + + if (clientSocket.Connected) { - m_isConnected = false; - Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); - buffer.Dispose(); + try + { + clientSocket.BeginReceive(buffer.Buffer, buffer.WriteOffset, buffer.AvailableLength, SocketFlags.None, new AsyncCallback(OnClientSocketReceive), state); + } + catch (ObjectDisposedException) + { + m_isConnected = false; + Log("[ReceiveCallback] BeginReceive ObjectDisposedException"); + buffer.Dispose(); + } + catch (SocketException ex) + { + m_isConnected = false; + Log("[ReceiveCallback] BeginReceive SocketException: " + ex.Message); + buffer.Dispose(); + } } } } diff --git a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs index 6cec8bed..c013fd1c 100644 --- a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs +++ b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs @@ -13,11 +13,11 @@ namespace SMBLibrary.NetBios { + /// + /// NBTConnectionReceiveBuffer is not thread-safe. + /// public class NBTConnectionReceiveBuffer : IDisposable { -#if NETSTANDARD2_0 - private object m_bufferSyncLock = new object(); -#endif private byte[] m_buffer; private int m_readOffset = 0; private int m_bytesInBuffer = 0; @@ -131,13 +131,10 @@ private void RemovePacketBytes() public void Dispose() { #if NETSTANDARD2_0 - lock (m_bufferSyncLock) + if (m_buffer != null) { - if (m_buffer != null) - { - ArrayPool.Shared.Return(m_buffer); - m_buffer = null; - } + ArrayPool.Shared.Return(m_buffer); + m_buffer = null; } #else m_buffer = null; diff --git a/SMBLibrary/Server/ConnectionManager.cs b/SMBLibrary/Server/ConnectionManager.cs index acafe368..cef64a1b 100644 --- a/SMBLibrary/Server/ConnectionManager.cs +++ b/SMBLibrary/Server/ConnectionManager.cs @@ -42,7 +42,10 @@ public void ReleaseConnection(ConnectionState connection) connection.SendQueue.Stop(); SocketUtils.ReleaseSocket(connection.ClientSocket); connection.CloseSessions(); - connection.ReceiveBuffer.Dispose(); + lock (connection.ReceiveBuffer) + { + connection.ReceiveBuffer.Dispose(); + } RemoveConnection(connection); } diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 578760f4..bcd3c390 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -230,63 +230,66 @@ private void ReceiveCallback(IAsyncResult result) ConnectionState state = (ConnectionState)result.AsyncState; Socket clientSocket = state.ClientSocket; - if (!m_listening) - { - clientSocket.Close(); - return; - } - - int numberOfBytesReceived; - try - { - numberOfBytesReceived = clientSocket.EndReceive(result); - } - catch (ObjectDisposedException) - { - state.LogToServer(Severity.Debug, "The connection was terminated"); - m_connectionManager.ReleaseConnection(state); - return; - } - catch (SocketException ex) + lock (state.ReceiveBuffer) { - const int WSAECONNRESET = 10054; - if (ex.ErrorCode == WSAECONNRESET) + if (!m_listening) { - state.LogToServer(Severity.Debug, "The connection was forcibly closed by the remote host"); - } - else - { - state.LogToServer(Severity.Debug, "The connection was terminated, Socket error code: {0}", ex.ErrorCode); + clientSocket.Close(); + return; } - m_connectionManager.ReleaseConnection(state); - return; - } - if (numberOfBytesReceived == 0) - { - state.LogToServer(Severity.Debug, "The client closed the connection"); - m_connectionManager.ReleaseConnection(state); - return; - } - - state.UpdateLastReceiveDT(); - NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer; - receiveBuffer.SetNumberOfBytesReceived(numberOfBytesReceived); - ProcessConnectionBuffer(ref state); - - if (clientSocket.Connected) - { + int numberOfBytesReceived; try { - clientSocket.BeginReceive(state.ReceiveBuffer.Buffer, state.ReceiveBuffer.WriteOffset, state.ReceiveBuffer.AvailableLength, 0, ReceiveCallback, state); + numberOfBytesReceived = clientSocket.EndReceive(result); } catch (ObjectDisposedException) { + state.LogToServer(Severity.Debug, "The connection was terminated"); m_connectionManager.ReleaseConnection(state); + return; } - catch (SocketException) + catch (SocketException ex) + { + const int WSAECONNRESET = 10054; + if (ex.ErrorCode == WSAECONNRESET) + { + state.LogToServer(Severity.Debug, "The connection was forcibly closed by the remote host"); + } + else + { + state.LogToServer(Severity.Debug, "The connection was terminated, Socket error code: {0}", ex.ErrorCode); + } + m_connectionManager.ReleaseConnection(state); + return; + } + + if (numberOfBytesReceived == 0) { + state.LogToServer(Severity.Debug, "The client closed the connection"); m_connectionManager.ReleaseConnection(state); + return; + } + + state.UpdateLastReceiveDT(); + NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer; + receiveBuffer.SetNumberOfBytesReceived(numberOfBytesReceived); + ProcessConnectionBuffer(ref state); + + if (clientSocket.Connected) + { + try + { + clientSocket.BeginReceive(state.ReceiveBuffer.Buffer, state.ReceiveBuffer.WriteOffset, state.ReceiveBuffer.AvailableLength, 0, ReceiveCallback, state); + } + catch (ObjectDisposedException) + { + m_connectionManager.ReleaseConnection(state); + } + catch (SocketException) + { + m_connectionManager.ReleaseConnection(state); + } } } } From fe226369870ed00608a630afabed59495fbbb355 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 2 Jun 2024 10:11:56 +0300 Subject: [PATCH 086/247] SMBServer: Delete unused variable --- SMBLibrary/Server/SMBServer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index bcd3c390..97f21d11 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -296,8 +296,6 @@ private void ReceiveCallback(IAsyncResult result) private void ProcessConnectionBuffer(ref ConnectionState state) { - Socket clientSocket = state.ClientSocket; - NBTConnectionReceiveBuffer receiveBuffer = state.ReceiveBuffer; while (receiveBuffer.HasCompletePacket()) { From 8d7a6e023869a733d58c65dcbbb8b29f94a6f12b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 3 Jun 2024 19:51:32 +0300 Subject: [PATCH 087/247] Client: Removed unneeded connectivity check before invoking EndReceive --- SMBLibrary/Client/SMB1Client.cs | 6 ------ SMBLibrary/Client/SMB2Client.cs | 6 ------ 2 files changed, 12 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 63ebcc7f..ab6e9b7d 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -429,12 +429,6 @@ private void OnClientSocketReceive(IAsyncResult ar) lock (state.ReceiveBuffer) { - if (!clientSocket.Connected) - { - state.ReceiveBuffer.Dispose(); - return; - } - int numberOfBytesReceived = 0; try { diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index d90cc0cd..c3531e94 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -362,12 +362,6 @@ private void OnClientSocketReceive(IAsyncResult ar) lock (state.ReceiveBuffer) { - if (!clientSocket.Connected) - { - state.ReceiveBuffer.Dispose(); - return; - } - int numberOfBytesReceived = 0; try { From 28c114ea05b177d32211b6379407a8f12e2e28f2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 5 Jun 2024 21:41:06 +0300 Subject: [PATCH 088/247] SMBLibrary 1.5.2 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 4 ++-- SMBLibrary/RevisionHistory.txt | 10 ++++++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/SMBServer.csproj | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 87591f13..087f6769 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,11 +3,11 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.1 + 1.5.2 1573;1591 SMBLibrary.Adapters Tal Aloni - Copyright © Tal Aloni 2014-2023 + Copyright © Tal Aloni 2014-2024 FileSystem adapters for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index dccde576..d95744c7 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,11 +3,11 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.1 + 1.5.2 1573;1591 SMBLibrary.Win32 Tal Aloni - Copyright © Tal Aloni 2014-2023 + Copyright © Tal Aloni 2014-2024 Windows specific extensions for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 58d2be7f..1207f73e 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -494,3 +494,13 @@ Revision History: Client: Calculate Authentication message MIC. NBTConnectionReceiveBuffer: Use ArrayPool rent buffers to reduce RAM usage. Added access denied ace support. + +1.5.2 - Server: ServerService: correctly handle unsupported ShareEnum levels. + Server: Use CancellationToken for send keep-alive thread if available. + Server: ConnectionState: Fix thread-safety issue. + Client: Disconnect: Invoke Socket.Close. + Client: NetBIOS over TCP: Apply timeout set by the client instead of hardcoded value. + Client: Removed unneeded connectivity check before invoking EndReceive. + SMB2Client: Support non-Microsoft servers returning MaxReadSize > MaxTransactSize + NBTConnectionReceiveBuffer: Fix thread-safety issues. + FileRenameInformationType2: Bugfix: Ensure length is at least 24 bytes. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index d0d14e5b..b7e03c3a 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.1.3 + 1.5.2 1573;1591 SMBLibrary false diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index 6e3656dd..cdd81149 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -5,7 +5,7 @@ net20;net40;netcoreapp3.1 Copyright © Tal Aloni 2014-2024 SMBServer - 1.5.1 + 1.5.2 SMBServer true From 6823b43d2ecc7f07d0087b770de81665379f15c9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Jun 2024 21:43:44 +0300 Subject: [PATCH 089/247] Server: SMBServer: Mark overloaded Start method as protected internal --- SMBLibrary/Server/SMBServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Server/SMBServer.cs b/SMBLibrary/Server/SMBServer.cs index 97f21d11..f8653ea4 100644 --- a/SMBLibrary/Server/SMBServer.cs +++ b/SMBLibrary/Server/SMBServer.cs @@ -84,7 +84,7 @@ public void Start(IPAddress serverAddress, SMBTransportType transport, bool enab Start(serverAddress, transport, port, enableSMB1, enableSMB2, enableSMB3, connectionInactivityTimeout); } - internal void Start(IPAddress serverAddress, SMBTransportType transport, int port, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) + protected internal void Start(IPAddress serverAddress, SMBTransportType transport, int port, bool enableSMB1, bool enableSMB2, bool enableSMB3, TimeSpan? connectionInactivityTimeout) { if (!m_listening) { From cfa568b8667252fa7dad15f106bd8d86807779dc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Jun 2024 21:43:55 +0300 Subject: [PATCH 090/247] Client: Mark overloaded Start method as protected internal --- SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index ab6e9b7d..7dafe569 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -88,7 +88,7 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool fo return Connect(serverAddress, transport, port, forceExtendedSecurity, responseTimeoutInMilliseconds); } - internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) + protected internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) { m_transport = transport; if (!m_isConnected) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index c3531e94..a7fcc375 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -94,7 +94,7 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport, int res return Connect(serverAddress, transport, port, responseTimeoutInMilliseconds); } - internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) + protected internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) { if (m_serverName == null) { From 0618bfff9e8889d079ab9451a92acb87953f319f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 12 Jun 2024 11:31:12 +0300 Subject: [PATCH 091/247] Client: Improve client response time when server disconnects or return invalid data --- SMBLibrary.Tests/Client/SMB2ClientTests.cs | 97 ++++++++++++++++++++++ SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 SMBLibrary.Tests/Client/SMB2ClientTests.cs diff --git a/SMBLibrary.Tests/Client/SMB2ClientTests.cs b/SMBLibrary.Tests/Client/SMB2ClientTests.cs new file mode 100644 index 00000000..48efb8e6 --- /dev/null +++ b/SMBLibrary.Tests/Client/SMB2ClientTests.cs @@ -0,0 +1,97 @@ +/* Copyright (C) 2024 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.Client; +using System; +using System.Diagnostics; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +namespace SMBLibrary.Tests.Client +{ + [TestClass] + public class SMB2ClientTests + { + private int m_serverPort; + private TcpListener m_tcpListener; + private bool m_clientConnected; + + [TestInitialize] + public void Initialize() + { + m_serverPort = 1000 + new Random().Next(50000); + m_tcpListener = new TcpListener(IPAddress.Loopback, m_serverPort); + m_tcpListener.Start(); + } + + private void AcceptTcpClient_DoNotReply(IAsyncResult ar) + { + TcpClient client = m_tcpListener.EndAcceptTcpClient(ar); + m_clientConnected = true; + } + + private void AcceptTcpClient_SendNonSmbData(IAsyncResult ar) + { + TcpClient client = m_tcpListener.EndAcceptTcpClient(ar); + m_clientConnected = true; + byte[] buffer = new byte[4]; + client.Client.Send(buffer); + } + + [TestMethod] + public void When_SMB2ClientConnectsAndServerDoesNotReply_ShouldReachTimeout() + { + m_tcpListener.BeginAcceptTcpClient(AcceptTcpClient_DoNotReply, null); + + SMB2Client client = new SMB2Client(); + int timeoutInMilliseconds = 1000; + + ManualResetEvent manualResetEvent = new ManualResetEvent(false); + Stopwatch stopwatch = new Stopwatch(); + bool isConnected = false; + new Thread(() => + { + stopwatch.Start(); + isConnected = client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, timeoutInMilliseconds); + stopwatch.Stop(); + }).Start(); + + while (!m_clientConnected) + { + Thread.Sleep(1); + } + Assert.IsFalse(isConnected); + Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200); + } + + [TestMethod] + public void When_SMB2ClientConnectsAndServerSendNonSmbData_ShouldNotReachTimeout() + { + m_tcpListener.BeginAcceptTcpClient(AcceptTcpClient_SendNonSmbData, null); + SMB2Client client = new SMB2Client(); + int timeoutInMilliseconds = 1000; + + ManualResetEvent manualResetEvent = new ManualResetEvent(false); + Stopwatch stopwatch = new Stopwatch(); + bool isConnected = false; + new Thread(() => + { + stopwatch.Start(); + isConnected = client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, timeoutInMilliseconds); + stopwatch.Stop(); + }).Start(); + + while (!m_clientConnected) + { + Thread.Sleep(1); + } + Assert.IsFalse(isConnected); + Assert.IsTrue(stopwatch.ElapsedMilliseconds < 200); + } + } +} diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 7dafe569..8e8b17fa 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -563,7 +563,7 @@ internal SMB1Message WaitForMessage(CommandName commandName) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds && m_clientSocket.Connected) { lock (m_incomingQueueLock) { diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index a7fcc375..9a0670c2 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -528,7 +528,7 @@ internal SMB2Command WaitForCommand(ulong messageID) { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds && m_clientSocket.Connected) { lock (m_incomingQueueLock) { From a2b96f638c3bcd775a2d28d73d0f0eae0000df0c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 12 Jun 2024 11:33:53 +0300 Subject: [PATCH 092/247] Add missing copyright notices --- SMBLibrary.Tests/IntegrationTests/LoginTests.cs | 8 +++++++- .../NTFileStore/NTDirectoryFileSystemTests.cs | 9 ++++++--- SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs | 10 ++++++---- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs index e05bc32d..a77ea063 100644 --- a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs +++ b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs @@ -1,4 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +/* Copyright (C) 2024 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 Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.Authentication.GSSAPI; using SMBLibrary.Authentication.NTLM; using SMBLibrary.Client; diff --git a/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs b/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs index 316fe4b5..cdfacbcc 100644 --- a/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs +++ b/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs @@ -1,8 +1,11 @@ -using System; -using System.Collections.Generic; +/* Copyright (C) 2019-2024 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.IO; using Microsoft.VisualStudio.TestTools.UnitTesting; -using SMBLibrary; using SMBLibrary.Win32; namespace SMBLibrary.Tests diff --git a/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs b/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs index f483c712..23d43495 100644 --- a/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs +++ b/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs @@ -1,9 +1,11 @@ -using System; -using System.Collections.Generic; +/* Copyright (C) 2019-2024 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.Threading; using Microsoft.VisualStudio.TestTools.UnitTesting; -using SMBLibrary; -using SMBLibrary.Win32; namespace SMBLibrary.Tests { From aeb8849683c9930bf0fc6eb2be49e28fd67cb17f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 12 Jun 2024 12:20:04 +0300 Subject: [PATCH 093/247] Client: Fix possible NullReferenceException when disconnection occur during directory enumeration --- SMBLibrary/Client/SMB1FileStore.cs | 8 ++++++-- SMBLibrary/Client/SMB2FileStore.cs | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Client/SMB1FileStore.cs b/SMBLibrary/Client/SMB1FileStore.cs index 6ac04abd..e7b8b881 100644 --- a/SMBLibrary/Client/SMB1FileStore.cs +++ b/SMBLibrary/Client/SMB1FileStore.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -181,7 +181,11 @@ public NTStatus QueryDirectory(out List result, string fileName TrySendMessage(request); reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); - if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) + if (reply == null) + { + return NTStatus.STATUS_INVALID_SMB; + } + else if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) { response = (Transaction2Response)reply.Commands[0]; Transaction2FindNext2Response nextSubcommandResponse = new Transaction2FindNext2Response(response.TransParameters, response.TransData, reply.Header.UnicodeFlag); diff --git a/SMBLibrary/Client/SMB2FileStore.cs b/SMBLibrary/Client/SMB2FileStore.cs index 563514c7..81aeec1c 100644 --- a/SMBLibrary/Client/SMB2FileStore.cs +++ b/SMBLibrary/Client/SMB2FileStore.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -165,6 +165,10 @@ public NTStatus QueryDirectory(out List result, o request.Reopen = false; TrySendCommand(request); response = m_client.WaitForCommand(request.MessageID); + if (response == null) + { + return NTStatus.STATUS_INVALID_SMB; + } } return response.Header.Status; } From 4ca34f04076ef3f92b52828d88fc8e7ccc06adc8 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 12 Jun 2024 12:21:08 +0300 Subject: [PATCH 094/247] NTStatus: Add STATUS_IO_TIMEOUT --- SMBLibrary/Enums/NTStatus.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/Enums/NTStatus.cs b/SMBLibrary/Enums/NTStatus.cs index 0a544ed6..113cd2fd 100644 --- a/SMBLibrary/Enums/NTStatus.cs +++ b/SMBLibrary/Enums/NTStatus.cs @@ -36,6 +36,7 @@ public enum NTStatus : uint STATUS_FILE_LOCK_CONFLICT = 0xC0000054, STATUS_LOCK_NOT_GRANTED = 0xC0000055, STATUS_DELETE_PENDING = 0xC0000056, + STATUS_IO_TIMEOUT = 0xC00000B5, STATUS_PRIVILEGE_NOT_HELD = 0xC0000061, STATUS_WRONG_PASSWORD = 0xC000006A, STATUS_LOGON_FAILURE = 0xC000006D, // Authentication failure. From d8b1791212fccf5cc5c2d46ad638f3f03bf531ba Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 12 Jun 2024 12:22:59 +0300 Subject: [PATCH 095/247] Client: Add WaitForCommand overload --- SMBLibrary/Client/SMB1Client.cs | 8 +++++++- SMBLibrary/Client/SMB2Client.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 8e8b17fa..3207b100 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -561,9 +561,15 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) internal SMB1Message WaitForMessage(CommandName commandName) { + return WaitForMessage(commandName, out bool _); + } + + internal SMB1Message WaitForMessage(CommandName commandName, out bool connectionTerminated) + { + connectionTerminated = false; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds && m_clientSocket.Connected) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds && !(connectionTerminated = !m_clientSocket.Connected)) { lock (m_incomingQueueLock) { diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 9a0670c2..bba3db97 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -526,9 +526,15 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) internal SMB2Command WaitForCommand(ulong messageID) { + return WaitForCommand(messageID, out bool _); + } + + internal SMB2Command WaitForCommand(ulong messageID, out bool connectionTerminated) + { + connectionTerminated = false; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds && m_clientSocket.Connected) + while (stopwatch.ElapsedMilliseconds < m_responseTimeoutInMilliseconds && !(connectionTerminated = !m_clientSocket.Connected)) { lock (m_incomingQueueLock) { From 7ecc9e667a6c387449bd8e20d33a7da5e96ec4ae Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 12 Jun 2024 12:27:26 +0300 Subject: [PATCH 096/247] SMB1FileStore, SMB2FileStore: Return STATUS_IO_TIMEOUT instead of STATUS_INVALID_SMB when server does not reply and there is no protocol violation --- SMBLibrary/Client/SMB1FileStore.cs | 60 +++++++++++++++--------------- SMBLibrary/Client/SMB2FileStore.cs | 48 ++++++++++++------------ 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/SMBLibrary/Client/SMB1FileStore.cs b/SMBLibrary/Client/SMB1FileStore.cs index e7b8b881..ae05e57f 100644 --- a/SMBLibrary/Client/SMB1FileStore.cs +++ b/SMBLibrary/Client/SMB1FileStore.cs @@ -36,7 +36,7 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string request.ImpersonationLevel = ImpersonationLevel.Impersonation; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_NT_CREATE_ANDX); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_NT_CREATE_ANDX, out bool connectionTerminated); if (reply != null) { if (reply.Commands[0] is NTCreateAndXResponse) @@ -51,7 +51,7 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string return reply.Header.Status; } } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus CloseFile(object handle) @@ -59,12 +59,12 @@ public NTStatus CloseFile(object handle) CloseRequest request = new CloseRequest(); request.FID = (ushort)handle; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_CLOSE); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_CLOSE, out bool connectionTerminated); if (reply != null) { return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount) @@ -76,7 +76,7 @@ public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCou request.MaxCountLarge = (uint)maxCount; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_READ_ANDX); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_READ_ANDX, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is ReadAndXResponse) @@ -85,7 +85,7 @@ public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCou } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data) @@ -97,7 +97,7 @@ public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offs request.Data = data; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_WRITE_ANDX); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_WRITE_ANDX, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is WriteAndXResponse) @@ -106,7 +106,7 @@ public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offs } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus FlushFileBuffers(object handle) @@ -150,7 +150,7 @@ public NTStatus QueryDirectory(out List result, string fileName request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) @@ -180,10 +180,10 @@ public NTStatus QueryDirectory(out List result, string fileName request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out connectionTerminated); if (reply == null) { - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } else if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) { @@ -201,7 +201,7 @@ public NTStatus QueryDirectory(out List result, string fileName } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass) @@ -224,7 +224,7 @@ public NTStatus GetFileInformation(out FileInformation result, object handle, Fi request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) @@ -244,7 +244,7 @@ public NTStatus GetFileInformation(out FileInformation result, object handle, Fi } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } else { @@ -277,7 +277,7 @@ public NTStatus GetFileInformation(out QueryInformation result, object handle, Q request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) @@ -288,7 +288,7 @@ public NTStatus GetFileInformation(out QueryInformation result, object handle, Q } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus SetFileInformation(object handle, FileInformation information) @@ -319,12 +319,12 @@ public NTStatus SetFileInformation(object handle, FileInformation information) request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } else { @@ -349,12 +349,12 @@ public NTStatus SetFileInformation(object handle, SetInformation information) request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass) @@ -376,7 +376,7 @@ public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileS request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) @@ -387,7 +387,7 @@ public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileS } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } else { @@ -412,7 +412,7 @@ public NTStatus GetFileSystemInformation(out QueryFSInformation result, QueryFSI request.MaxDataCount = (ushort)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION2, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is Transaction2Response) @@ -423,7 +423,7 @@ public NTStatus GetFileSystemInformation(out QueryFSInformation result, QueryFSI } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus SetFileSystemInformation(FileSystemInformation information) @@ -450,7 +450,7 @@ public NTStatus GetSecurityInformation(out SecurityDescriptor result, object han request.MaxDataCount = (uint)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_NT_TRANSACT); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_NT_TRANSACT, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is NTTransactResponse) @@ -461,7 +461,7 @@ public NTStatus GetSecurityInformation(out SecurityDescriptor result, object han } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor) @@ -504,7 +504,7 @@ public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out b request.MaxDataCount = (uint)maxOutputLength; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_NT_TRANSACT); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_NT_TRANSACT, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is NTTransactResponse) @@ -515,7 +515,7 @@ public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out b } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus FsCtlPipeTranscieve(object handle, byte[] input, out byte[] output, int maxOutputLength) @@ -536,7 +536,7 @@ public NTStatus FsCtlPipeTranscieve(object handle, byte[] input, out byte[] outp request.Name = @"\PIPE\"; TrySendMessage(request); - SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION); + SMB1Message reply = m_client.WaitForMessage(CommandName.SMB_COM_TRANSACTION, out bool connectionTerminated); if (reply != null) { if (reply.Header.Status == NTStatus.STATUS_SUCCESS && reply.Commands[0] is TransactionResponse) @@ -547,7 +547,7 @@ public NTStatus FsCtlPipeTranscieve(object handle, byte[] input, out byte[] outp } return reply.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus Disconnect() diff --git a/SMBLibrary/Client/SMB2FileStore.cs b/SMBLibrary/Client/SMB2FileStore.cs index 81aeec1c..f5f77ffe 100644 --- a/SMBLibrary/Client/SMB2FileStore.cs +++ b/SMBLibrary/Client/SMB2FileStore.cs @@ -40,7 +40,7 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string request.ImpersonationLevel = ImpersonationLevel.Impersonation; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is CreateResponse) @@ -52,7 +52,7 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus CloseFile(object handle) @@ -60,13 +60,13 @@ public NTStatus CloseFile(object handle) CloseRequest request = new CloseRequest(); request.FileId = (FileID)handle; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount) @@ -79,7 +79,7 @@ public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCou request.ReadLength = (uint)maxCount; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is ReadResponse) @@ -89,7 +89,7 @@ public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCou return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data) @@ -102,7 +102,7 @@ public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offs request.Data = data; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is WriteResponse) @@ -112,7 +112,7 @@ public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offs return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus FlushFileBuffers(object handle) @@ -121,7 +121,7 @@ public NTStatus FlushFileBuffers(object handle) request.FileId = (FileID) handle; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is FlushResponse) @@ -130,7 +130,7 @@ public NTStatus FlushFileBuffers(object handle) } } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus LockFile(object handle, long byteOffset, long length, bool exclusiveLock) @@ -155,7 +155,7 @@ public NTStatus QueryDirectory(out List result, o request.FileName = fileName; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { while (response.Header.Status == NTStatus.STATUS_SUCCESS && response is QueryDirectoryResponse) @@ -164,16 +164,16 @@ public NTStatus QueryDirectory(out List result, o result.AddRange(page); request.Reopen = false; TrySendCommand(request); - response = m_client.WaitForCommand(request.MessageID); + response = m_client.WaitForCommand(request.MessageID, out connectionTerminated); if (response == null) { - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } } return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass) @@ -186,7 +186,7 @@ public NTStatus GetFileInformation(out FileInformation result, object handle, Fi request.FileId = (FileID)handle; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is QueryInfoResponse) @@ -196,7 +196,7 @@ public NTStatus GetFileInformation(out FileInformation result, object handle, Fi return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus SetFileInformation(object handle, FileInformation information) @@ -208,13 +208,13 @@ public NTStatus SetFileInformation(object handle, FileInformation information) request.SetFileInformation(information); TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass) @@ -243,7 +243,7 @@ public NTStatus GetFileSystemInformation(out FileSystemInformation result, objec request.FileId = (FileID)handle; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is QueryInfoResponse) @@ -253,7 +253,7 @@ public NTStatus GetFileSystemInformation(out FileSystemInformation result, objec return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus SetFileSystemInformation(FileSystemInformation information) @@ -271,7 +271,7 @@ public NTStatus GetSecurityInformation(out SecurityDescriptor result, object han request.FileId = (FileID)handle; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is QueryInfoResponse) @@ -281,7 +281,7 @@ public NTStatus GetSecurityInformation(out SecurityDescriptor result, object han return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor) @@ -310,7 +310,7 @@ public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out b request.Input = input; request.MaxOutputResponse = (uint)maxOutputLength; TrySendCommand(request); - SMB2Command response = m_client.WaitForCommand(request.MessageID); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); if (response != null) { if ((response.Header.Status == NTStatus.STATUS_SUCCESS || response.Header.Status == NTStatus.STATUS_BUFFER_OVERFLOW) && response is IOCtlResponse) @@ -320,7 +320,7 @@ public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out b return response.Header.Status; } - return NTStatus.STATUS_INVALID_SMB; + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus Disconnect() From f446549dd3160428cbd7a503800d159463a2abb2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 25 Jun 2024 22:48:48 +0300 Subject: [PATCH 097/247] SMBLibrary 1.5.3 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/RevisionHistory.txt | 6 ++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/SMBServer.csproj | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 087f6769..0db89659 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.2 + 1.5.3 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index d95744c7..baa3801d 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.2 + 1.5.3 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 1207f73e..b9dfec26 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -504,3 +504,9 @@ Revision History: SMB2Client: Support non-Microsoft servers returning MaxReadSize > MaxTransactSize NBTConnectionReceiveBuffer: Fix thread-safety issues. FileRenameInformationType2: Bugfix: Ensure length is at least 24 bytes. + +1.5.3 - Server: SMBServer: Mark overloaded Start method as protected internal. + Client: Mark overloaded Start method as protected internal. + Client: Improve client response time when server disconnects or return invalid data. + Client: Fix possible NullReferenceException when disconnection occur during directory enumeration. + Client: SMB1FileStore, SMB2FileStore: Return STATUS_IO_TIMEOUT instead of STATUS_INVALID_SMB when server does not reply and there is no protocol violation. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index b7e03c3a..dba05b64 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.2 + 1.5.3 1573;1591 SMBLibrary false diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index cdd81149..5678f540 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -5,7 +5,7 @@ net20;net40;netcoreapp3.1 Copyright © Tal Aloni 2014-2024 SMBServer - 1.5.2 + 1.5.3 SMBServer true From 390961f25023ee1ebef8efe64a4ffe2c49fdd1e2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jul 2024 17:20:46 +0300 Subject: [PATCH 098/247] SMB2: CreateResponse: Renamed variable --- SMBLibrary/SMB2/Commands/CreateResponse.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/SMB2/Commands/CreateResponse.cs b/SMBLibrary/SMB2/Commands/CreateResponse.cs index 6ec9e2a4..aed13c16 100644 --- a/SMBLibrary/SMB2/Commands/CreateResponse.cs +++ b/SMBLibrary/SMB2/Commands/CreateResponse.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -30,7 +30,7 @@ public class CreateResponse : SMB2Command public FileAttributes FileAttributes; public uint Reserved2; public FileID FileId; - private uint CreateContextsOffsets; + private uint CreateContextsOffset; private uint CreateContextsLength; public List CreateContexts = new List(); @@ -55,11 +55,11 @@ public CreateResponse(byte[] buffer, int offset) : base(buffer, offset) FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 56); Reserved2 = LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 60); FileId = new FileID(buffer, offset + SMB2Header.Length + 64); - CreateContextsOffsets = LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 80); + CreateContextsOffset = LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 80); CreateContextsLength = LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 84); if (CreateContextsLength > 0) { - CreateContexts = CreateContext.ReadCreateContextList(buffer, offset + (int)CreateContextsOffsets); + CreateContexts = CreateContext.ReadCreateContextList(buffer, offset + (int)CreateContextsOffset); } } @@ -78,11 +78,11 @@ public override void WriteCommandBytes(byte[] buffer, int offset) LittleEndianWriter.WriteUInt32(buffer, offset + 56, (uint)FileAttributes); LittleEndianWriter.WriteUInt32(buffer, offset + 60, Reserved2); FileId.WriteBytes(buffer, offset + 64); - CreateContextsOffsets = 0; + CreateContextsOffset = 0; CreateContextsLength = (uint)CreateContext.GetCreateContextListLength(CreateContexts); if (CreateContexts.Count > 0) { - CreateContextsOffsets = SMB2Header.Length + 88; + CreateContextsOffset = SMB2Header.Length + 88; CreateContext.WriteCreateContextList(buffer, 88, CreateContexts); } } From c150a1265f6e229e6ef7800552a54cdd6e7cf3f9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jul 2024 17:22:24 +0300 Subject: [PATCH 099/247] SMB2: CreateResponse: Minor code refactoring --- SMBLibrary/SMB2/Commands/CreateResponse.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/SMB2/Commands/CreateResponse.cs b/SMBLibrary/SMB2/Commands/CreateResponse.cs index aed13c16..03877131 100644 --- a/SMBLibrary/SMB2/Commands/CreateResponse.cs +++ b/SMBLibrary/SMB2/Commands/CreateResponse.cs @@ -15,6 +15,7 @@ namespace SMBLibrary.SMB2 /// public class CreateResponse : SMB2Command { + public const int FixedLength = 88; public const int DeclaredSize = 89; private ushort StructureSize; @@ -82,8 +83,8 @@ public override void WriteCommandBytes(byte[] buffer, int offset) CreateContextsLength = (uint)CreateContext.GetCreateContextListLength(CreateContexts); if (CreateContexts.Count > 0) { - CreateContextsOffset = SMB2Header.Length + 88; - CreateContext.WriteCreateContextList(buffer, 88, CreateContexts); + CreateContextsOffset = SMB2Header.Length + FixedLength; + CreateContext.WriteCreateContextList(buffer, FixedLength, CreateContexts); } } @@ -91,7 +92,7 @@ public override int CommandLength { get { - return 88 + CreateContext.GetCreateContextListLength(CreateContexts); + return FixedLength + CreateContext.GetCreateContextListLength(CreateContexts); } } } From 019015ebb5c6e4c52290e4af42af4e3328d44138 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jul 2024 17:23:44 +0300 Subject: [PATCH 100/247] SMB2: CreateResponse: Bugfix: Write CreateContexts offset and Length --- SMBLibrary/SMB2/Commands/CreateResponse.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/SMB2/Commands/CreateResponse.cs b/SMBLibrary/SMB2/Commands/CreateResponse.cs index 03877131..5bdff92f 100644 --- a/SMBLibrary/SMB2/Commands/CreateResponse.cs +++ b/SMBLibrary/SMB2/Commands/CreateResponse.cs @@ -79,13 +79,11 @@ public override void WriteCommandBytes(byte[] buffer, int offset) LittleEndianWriter.WriteUInt32(buffer, offset + 56, (uint)FileAttributes); LittleEndianWriter.WriteUInt32(buffer, offset + 60, Reserved2); FileId.WriteBytes(buffer, offset + 64); - CreateContextsOffset = 0; + CreateContextsOffset = CreateContexts.Count > 0 ? (uint)(SMB2Header.Length + FixedLength) : 0; CreateContextsLength = (uint)CreateContext.GetCreateContextListLength(CreateContexts); - if (CreateContexts.Count > 0) - { - CreateContextsOffset = SMB2Header.Length + FixedLength; - CreateContext.WriteCreateContextList(buffer, FixedLength, CreateContexts); - } + LittleEndianWriter.WriteUInt32(buffer, offset + 80, CreateContextsOffset); + LittleEndianWriter.WriteUInt32(buffer, offset + 84, CreateContextsLength); + CreateContext.WriteCreateContextList(buffer, SMB2Header.Length + FixedLength, CreateContexts); } public override int CommandLength From 3247f0365e6f74e142851dc8f3138ab807167e8f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jul 2024 17:25:45 +0300 Subject: [PATCH 101/247] SMB2: CreateContext: Bugfix: Read and Write name as ANSI instead of UTF16 --- SMBLibrary/SMB2/Structures/CreateContext.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SMBLibrary/SMB2/Structures/CreateContext.cs b/SMBLibrary/SMB2/Structures/CreateContext.cs index b537a42c..f0037c13 100644 --- a/SMBLibrary/SMB2/Structures/CreateContext.cs +++ b/SMBLibrary/SMB2/Structures/CreateContext.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -44,7 +44,7 @@ public CreateContext(byte[] buffer, int offset) DataLength = LittleEndianConverter.ToUInt32(buffer, offset + 12); if (NameLength > 0) { - Name = ByteReader.ReadUTF16String(buffer, offset + NameOffset, NameLength / 2); + Name = ByteReader.ReadAnsiString(buffer, offset + NameOffset, NameLength); } if (DataLength > 0) { @@ -56,7 +56,7 @@ private void WriteBytes(byte[] buffer, int offset) { LittleEndianWriter.WriteUInt32(buffer, offset + 0, Next); NameOffset = 0; - NameLength = (ushort)(Name.Length * 2); + NameLength = (ushort)Name.Length; if (Name.Length > 0) { NameOffset = (ushort)FixedLength; @@ -68,11 +68,11 @@ private void WriteBytes(byte[] buffer, int offset) DataLength = (uint)Data.Length; if (Data.Length > 0) { - int paddedNameLength = (int)Math.Ceiling((double)(Name.Length * 2) / 8) * 8; + int paddedNameLength = (int)Math.Ceiling((double)Name.Length / 8) * 8; DataOffset = (ushort)(FixedLength + paddedNameLength); } LittleEndianWriter.WriteUInt16(buffer, offset + 10, DataOffset); - ByteWriter.WriteUTF16String(buffer, NameOffset, Name); + ByteWriter.WriteAnsiString(buffer, NameOffset, Name); ByteWriter.WriteBytes(buffer, DataOffset, Data); } From 773d42375dbce1ae64af6fdf2dbb04ad611f94d0 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jul 2024 17:29:20 +0300 Subject: [PATCH 102/247] SMB2: CreateContext: Bugfix: Correct Name and Data offset write location --- SMBLibrary/SMB2/Structures/CreateContext.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/SMB2/Structures/CreateContext.cs b/SMBLibrary/SMB2/Structures/CreateContext.cs index f0037c13..e071740f 100644 --- a/SMBLibrary/SMB2/Structures/CreateContext.cs +++ b/SMBLibrary/SMB2/Structures/CreateContext.cs @@ -59,12 +59,12 @@ private void WriteBytes(byte[] buffer, int offset) NameLength = (ushort)Name.Length; if (Name.Length > 0) { - NameOffset = (ushort)FixedLength; + NameOffset = FixedLength; } LittleEndianWriter.WriteUInt16(buffer, offset + 4, NameOffset); LittleEndianWriter.WriteUInt16(buffer, offset + 6, NameLength); LittleEndianWriter.WriteUInt16(buffer, offset + 8, Reserved); - DataOffset = 0; + DataOffset = (ushort)(FixedLength + NameLength); DataLength = (uint)Data.Length; if (Data.Length > 0) { @@ -72,8 +72,8 @@ private void WriteBytes(byte[] buffer, int offset) DataOffset = (ushort)(FixedLength + paddedNameLength); } LittleEndianWriter.WriteUInt16(buffer, offset + 10, DataOffset); - ByteWriter.WriteAnsiString(buffer, NameOffset, Name); - ByteWriter.WriteBytes(buffer, DataOffset, Data); + ByteWriter.WriteAnsiString(buffer, offset + NameOffset, Name); + ByteWriter.WriteBytes(buffer, offset + DataOffset, Data); } public int Length From 5c4e369896b0543c4a55c1080360cc8506f4f1d2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 6 Jul 2024 17:29:38 +0300 Subject: [PATCH 103/247] SMB2: CreateContext: Bugfix: Write DataLength --- SMBLibrary/SMB2/Structures/CreateContext.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/SMB2/Structures/CreateContext.cs b/SMBLibrary/SMB2/Structures/CreateContext.cs index e071740f..cd5d5b74 100644 --- a/SMBLibrary/SMB2/Structures/CreateContext.cs +++ b/SMBLibrary/SMB2/Structures/CreateContext.cs @@ -72,6 +72,7 @@ private void WriteBytes(byte[] buffer, int offset) DataOffset = (ushort)(FixedLength + paddedNameLength); } LittleEndianWriter.WriteUInt16(buffer, offset + 10, DataOffset); + LittleEndianWriter.WriteUInt32(buffer, offset + 12, DataLength); ByteWriter.WriteAnsiString(buffer, offset + NameOffset, Name); ByteWriter.WriteBytes(buffer, offset + DataOffset, Data); } From b0912850dd6c024aff6c573e2771debd9213a924 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 27 Jul 2024 19:51:42 +0300 Subject: [PATCH 104/247] Client: Update IsConnected on EndReceive socket exception --- SMBLibrary/Client/SMB1Client.cs | 3 +++ SMBLibrary/Client/SMB2Client.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 3207b100..6f6e785d 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -436,17 +436,20 @@ private void OnClientSocketReceive(IAsyncResult ar) } catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class. { + m_isConnected = false; state.ReceiveBuffer.Dispose(); return; } catch (ObjectDisposedException) { + m_isConnected = false; Log("[ReceiveCallback] EndReceive ObjectDisposedException"); state.ReceiveBuffer.Dispose(); return; } catch (SocketException ex) { + m_isConnected = false; Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); state.ReceiveBuffer.Dispose(); return; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index bba3db97..78b0d732 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -369,17 +369,20 @@ private void OnClientSocketReceive(IAsyncResult ar) } catch (ArgumentException) // The IAsyncResult object was not returned from the corresponding synchronous method on this class. { + m_isConnected = false; state.ReceiveBuffer.Dispose(); return; } catch (ObjectDisposedException) { + m_isConnected = false; Log("[ReceiveCallback] EndReceive ObjectDisposedException"); state.ReceiveBuffer.Dispose(); return; } catch (SocketException ex) { + m_isConnected = false; Log("[ReceiveCallback] EndReceive SocketException: " + ex.Message); state.ReceiveBuffer.Dispose(); return; From 42ea6a51241f1748de53f3bf2375dcda133b27a1 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 29 Jul 2024 21:41:40 +0300 Subject: [PATCH 105/247] SMB2Client: Minor code refactoring --- SMBLibrary/Client/SMB2Client.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 78b0d732..1d34e2d2 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -271,10 +271,11 @@ public NTStatus Login(IAuthenticationClient authenticationClient) m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS); if (m_isLoggedIn) { + SessionFlags sessionFlags = ((SessionSetupResponse)response).SessionFlags; m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, null); if (m_dialect == SMB2Dialect.SMB300) { - m_encryptSessionData = (((SessionSetupResponse)response).SessionFlags & SessionFlags.EncryptData) > 0; + m_encryptSessionData = (sessionFlags & SessionFlags.EncryptData) > 0; m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, SMB2Dialect.SMB300, null); m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, SMB2Dialect.SMB300, null); } From 9ff7adced48e42b2e39287989fa06cda57955a71 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 29 Jul 2024 21:56:57 +0300 Subject: [PATCH 106/247] SMB2Client: Correctly handle signing when authenticated as guest --- SMBLibrary/Client/SMB2Client.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 1d34e2d2..dd2f50b6 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -272,7 +272,17 @@ public NTStatus Login(IAuthenticationClient authenticationClient) if (m_isLoggedIn) { SessionFlags sessionFlags = ((SessionSetupResponse)response).SessionFlags; - m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, null); + if ((sessionFlags & SessionFlags.IsGuest) > 0) + { + // [MS-SMB2] 3.2.5.3.1 If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the SMB2 + // SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired MUST be set to FALSE. + m_signingRequired = false; + } + else + { + m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, null); + } + if (m_dialect == SMB2Dialect.SMB300) { m_encryptSessionData = (sessionFlags & SessionFlags.EncryptData) > 0; From 2ff91c301778ffec6714fabb58ccbb24ee87603d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 29 Jul 2024 22:00:17 +0300 Subject: [PATCH 107/247] SMBLibrary 1.5.3.1 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index dba05b64..4350c069 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.3 + 1.5.3.1 1573;1591 SMBLibrary false From 1aef438a8e118994f1b7f1d231eb85b7c876470c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 30 Jul 2024 20:03:47 +0300 Subject: [PATCH 108/247] NegotiateContext: Fix parsing bugs --- .../SMB2/Structures/NegotiateContext.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext.cs b/SMBLibrary/SMB2/Structures/NegotiateContext.cs index c36f64d3..4b81068b 100644 --- a/SMBLibrary/SMB2/Structures/NegotiateContext.cs +++ b/SMBLibrary/SMB2/Structures/NegotiateContext.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -31,7 +31,7 @@ public NegotiateContext(byte[] buffer, int offset) ContextType = (NegotiateContextType)LittleEndianConverter.ToUInt16(buffer, offset + 0); DataLength = LittleEndianConverter.ToUInt16(buffer, offset + 2); Reserved = LittleEndianConverter.ToUInt32(buffer, offset + 4); - ByteReader.ReadBytes(buffer, offset + 8, DataLength); + Data = ByteReader.ReadBytes(buffer, offset + 8, DataLength); } public void WriteBytes(byte[] buffer, int offset) @@ -51,6 +51,15 @@ public int Length } } + public int PaddedLength + { + get + { + int paddingLength = (8 - (Data.Length % 8)) % 8; + return this.Length + paddingLength; + } + } + public static List ReadNegotiateContextList(byte[] buffer, int offset, int count) { List result = new List(); @@ -58,7 +67,7 @@ public static List ReadNegotiateContextList(byte[] buffer, int { NegotiateContext context = new NegotiateContext(buffer, offset); result.Add(context); - offset += context.Length; + offset += context.PaddedLength; } return result; } @@ -69,10 +78,8 @@ public static void WriteNegotiateContextList(byte[] buffer, int offset, List negotiate for (int index = 0; index < negotiateContextList.Count; index++) { NegotiateContext context = negotiateContextList[index]; - int length = context.Length; if (index < negotiateContextList.Count - 1) { - int paddedLength = (int)Math.Ceiling((double)length / 8) * 8; - result += paddedLength; + result += context.PaddedLength; } else { - result += length; + result += context.Length; } } return result; From e40a5b2daf3c3bfa2aca8504d6a6c5010554d597 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 30 Jul 2024 20:06:52 +0300 Subject: [PATCH 109/247] NegotiateRequest: Parse NegotiateContextList (SMB 3.1.1) --- .../SMB2/NegotiateRequestParsingTests.cs | 46 +++++++++++++++ SMBLibrary/SMB2/Commands/NegotiateRequest.cs | 58 +++++++++++++++++-- 2 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs diff --git a/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs b/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs new file mode 100644 index 00000000..fea7b41a --- /dev/null +++ b/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs @@ -0,0 +1,46 @@ +/* Copyright (C) 2024 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.SMB2; + +namespace SMBLibrary.Tests.SMB2 +{ + [TestClass] + public class NegotiateRequestParsingTests + { + [TestMethod] + public void ParseNegotiateRequestWithNegotiateContextList() + { + byte[] negotiateRequestCommandBytes = new byte[] + { + 0xfe,0x53,0x4d,0x42,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x24,0x00,0x05,0x00,0x01,0x00,0x00,0x00,0x7f,0x00,0x00,0x00,0xd2,0xb6,0x2a,0x44, + 0x99,0x4d,0xef,0x11,0xb8,0x9d,0x00,0x22,0x48,0x39,0x02,0x34,0x70,0x00,0x00,0x00, + 0x04,0x00,0x00,0x00,0x02,0x02,0x10,0x02,0x00,0x03,0x02,0x03,0x11,0x03,0x00,0x00, + 0x01,0x00,0x26,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x20,0x00,0x01,0x00,0x6c,0xec, + 0x6e,0x4d,0x8e,0x58,0x93,0xd9,0xb3,0x47,0x24,0x09,0x12,0x7a,0xc8,0x4f,0x9b,0xf6, + 0x1d,0xaa,0xbc,0xab,0x22,0xf5,0xec,0xf6,0x3d,0xb5,0x3e,0xc3,0x76,0x85,0x00,0x00, + 0x02,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x01,0x00,0x00,0x00, + 0x03,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00, + 0x04,0x00,0x02,0x00,0x03,0x00,0x01,0x00,0x05,0x00,0x34,0x00,0x00,0x00,0x00,0x00, + 0x6d,0x00,0x69,0x00,0x72,0x00,0x61,0x00,0x67,0x00,0x65,0x00,0x2d,0x00,0x73,0x00, + 0x65,0x00,0x72,0x00,0x76,0x00,0x65,0x00,0x72,0x00,0x34,0x00,0x2e,0x00,0x75,0x00, + 0x73,0x00,0x65,0x00,0x72,0x00,0x73,0x00,0x2e,0x00,0x6c,0x00,0x6f,0x00,0x63,0x00, + 0x61,0x00,0x6c,0x00 + }; + + NegotiateRequest negotiateRequest = new NegotiateRequest(negotiateRequestCommandBytes, 0); + Assert.AreEqual(5, negotiateRequest.Dialects.Count); + Assert.AreEqual(4, negotiateRequest.NegotiateContextList.Count); + Assert.AreEqual(NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES, negotiateRequest.NegotiateContextList[0].ContextType); + Assert.AreEqual(NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES, negotiateRequest.NegotiateContextList[1].ContextType); + } + } +} diff --git a/SMBLibrary/SMB2/Commands/NegotiateRequest.cs b/SMBLibrary/SMB2/Commands/NegotiateRequest.cs index 58e0f5de..83f9dec5 100644 --- a/SMBLibrary/SMB2/Commands/NegotiateRequest.cs +++ b/SMBLibrary/SMB2/Commands/NegotiateRequest.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -23,8 +23,9 @@ public class NegotiateRequest : SMB2Command public ushort Reserved; public Capabilities Capabilities; // If the client does not implements the SMB 3.x dialect family, this field MUST be set to 0. public Guid ClientGuid; - public DateTime ClientStartTime; + public DateTime ClientStartTime; // If Dialects does not contain SMB311 public List Dialects = new List(); + public List NegotiateContextList = new List(); // If Dialects contains SMB311 public NegotiateRequest() : base(SMB2CommandName.Negotiate) { @@ -39,12 +40,28 @@ public NegotiateRequest(byte[] buffer, int offset) : base(buffer, offset) Reserved = LittleEndianConverter.ToUInt16(buffer, offset + SMB2Header.Length + 6); Capabilities = (Capabilities)LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 8); ClientGuid = LittleEndianConverter.ToGuid(buffer, offset + SMB2Header.Length + 12); - ClientStartTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + SMB2Header.Length + 28)); + bool containsNegotiateContextList = false; for (int index = 0; index < dialectCount; index++) { SMB2Dialect dialect = (SMB2Dialect)LittleEndianConverter.ToUInt16(buffer, offset + SMB2Header.Length + 36 + index * 2); Dialects.Add(dialect); + + if (dialect == SMB2Dialect.SMB311) + { + containsNegotiateContextList = true; + } + } + + if (containsNegotiateContextList) + { + uint negotiateContextOffset = LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 28); + ushort NegotiateContextCount = LittleEndianConverter.ToUInt16(buffer, offset + SMB2Header.Length + 32); + NegotiateContextList = NegotiateContext.ReadNegotiateContextList(buffer, offset + (int)negotiateContextOffset, NegotiateContextCount); + } + else + { + ClientStartTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + SMB2Header.Length + 28)); } } @@ -56,12 +73,31 @@ public override void WriteCommandBytes(byte[] buffer, int offset) LittleEndianWriter.WriteUInt16(buffer, offset + 6, Reserved); LittleEndianWriter.WriteUInt32(buffer, offset + 8, (uint)Capabilities); LittleEndianWriter.WriteGuid(buffer, offset + 12, ClientGuid); - LittleEndianWriter.WriteInt64(buffer, offset + 28, ClientStartTime.ToFileTimeUtc()); - + + bool containsNegotiateContextList = false; for (int index = 0; index < Dialects.Count; index++) { SMB2Dialect dialect = Dialects[index]; LittleEndianWriter.WriteUInt16(buffer, offset + 36 + index * 2, (ushort)dialect); + + if (dialect == SMB2Dialect.SMB311) + { + containsNegotiateContextList = true; + } + } + + int paddingLength = (8 - ((36 + Dialects.Count * 2) % 8)) % 8; + if (containsNegotiateContextList) + { + uint negotiateContextOffset = (uint)(SMB2Header.Length + 36 + Dialects.Count * 2 + paddingLength); + ushort negotiateContextCount = (ushort)NegotiateContextList.Count; + LittleEndianWriter.WriteUInt32(buffer, offset + 28, negotiateContextOffset); + LittleEndianWriter.WriteUInt16(buffer, offset + 32, negotiateContextCount); + NegotiateContext.WriteNegotiateContextList(buffer, offset - SMB2Header.Length + (int)negotiateContextOffset, NegotiateContextList); + } + else + { + LittleEndianWriter.WriteInt64(buffer, offset + 28, ClientStartTime.ToFileTimeUtc()); } } @@ -69,7 +105,17 @@ public override int CommandLength { get { - return 36 + Dialects.Count * 2; + bool containsNegotiateContextList = Dialects.Contains(SMB2Dialect.SMB311); + if (containsNegotiateContextList) + { + int paddingLength = (8 - ((36 + Dialects.Count * 2) % 8)) % 8; + int negotiateContextListLength = NegotiateContext.GetNegotiateContextListLength(NegotiateContextList); + return 36 + Dialects.Count * 2 + paddingLength + negotiateContextListLength; + } + else + { + return 36 + Dialects.Count * 2; + } } } } From 2044b092634a6950f5a33273b9381e1da3295081 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 30 Jul 2024 20:14:00 +0300 Subject: [PATCH 110/247] NegotiateRequest: Do not pad request if NegotiateContextList is empty --- SMBLibrary/SMB2/Commands/NegotiateRequest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/SMB2/Commands/NegotiateRequest.cs b/SMBLibrary/SMB2/Commands/NegotiateRequest.cs index 83f9dec5..eed46547 100644 --- a/SMBLibrary/SMB2/Commands/NegotiateRequest.cs +++ b/SMBLibrary/SMB2/Commands/NegotiateRequest.cs @@ -74,7 +74,7 @@ public override void WriteCommandBytes(byte[] buffer, int offset) LittleEndianWriter.WriteUInt32(buffer, offset + 8, (uint)Capabilities); LittleEndianWriter.WriteGuid(buffer, offset + 12, ClientGuid); - bool containsNegotiateContextList = false; + bool containsSMB311Dialect = false; for (int index = 0; index < Dialects.Count; index++) { SMB2Dialect dialect = Dialects[index]; @@ -82,13 +82,13 @@ public override void WriteCommandBytes(byte[] buffer, int offset) if (dialect == SMB2Dialect.SMB311) { - containsNegotiateContextList = true; + containsSMB311Dialect = true; } } - int paddingLength = (8 - ((36 + Dialects.Count * 2) % 8)) % 8; - if (containsNegotiateContextList) + if (containsSMB311Dialect) { + int paddingLength = (8 - ((36 + Dialects.Count * 2) % 8)) % 8; uint negotiateContextOffset = (uint)(SMB2Header.Length + 36 + Dialects.Count * 2 + paddingLength); ushort negotiateContextCount = (ushort)NegotiateContextList.Count; LittleEndianWriter.WriteUInt32(buffer, offset + 28, negotiateContextOffset); @@ -105,8 +105,8 @@ public override int CommandLength { get { - bool containsNegotiateContextList = Dialects.Contains(SMB2Dialect.SMB311); - if (containsNegotiateContextList) + bool containsSMB311Dialect = Dialects.Contains(SMB2Dialect.SMB311); + if (containsSMB311Dialect && NegotiateContextList.Count > 0) { int paddingLength = (8 - ((36 + Dialects.Count * 2) % 8)) % 8; int negotiateContextListLength = NegotiateContext.GetNegotiateContextListLength(NegotiateContextList); From dc52d4efc10664efd9cd1b92a02ba891f3a513f4 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 30 Jul 2024 20:28:53 +0300 Subject: [PATCH 111/247] Added NegotiateResponseParsingTests --- .../SMB2/NegotiateResponseParsingTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs diff --git a/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs b/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs new file mode 100644 index 00000000..502d7102 --- /dev/null +++ b/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs @@ -0,0 +1,59 @@ +/* Copyright (C) 2024 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.SMB2; + +namespace SMBLibrary.Tests.SMB2 +{ + [TestClass] + public class NegotiateResponseParsingTests + { + [TestMethod] + public void ParseNegotiateResponseWithNegotiateContextList() + { + byte[] negotiateResponseCommandBytes = new byte[] + { + 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x41, 0x00, 0x01, 0x00, 0x11, 0x03, 0x02, 0x00, 0xdc, 0x13, 0x82, 0xb7, 0x61, 0x5d, 0xd1, 0x49, + 0x93, 0x4c, 0xea, 0x51, 0xa2, 0x06, 0x6f, 0x20, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x33, 0xee, 0xd9, 0xe6, 0xa4, 0xe2, 0xda, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x01, 0xc0, 0x01, 0x00, 0x00, + 0x60, 0x82, 0x01, 0x3c, 0x06, 0x06, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x02, 0xa0, 0x82, 0x01, 0x30, + 0x30, 0x82, 0x01, 0x2c, 0xa0, 0x1a, 0x30, 0x18, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, + 0x37, 0x02, 0x02, 0x1e, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a, + 0xa2, 0x82, 0x01, 0x0c, 0x04, 0x82, 0x01, 0x08, 0x4e, 0x45, 0x47, 0x4f, 0x45, 0x58, 0x54, 0x53, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x43, 0x41, 0x62, 0x87, 0xb8, 0xbb, 0x52, 0xa8, 0xc2, 0x47, 0x80, 0x3f, 0x56, 0xf8, 0x2d, 0x16, + 0xcb, 0x62, 0x1f, 0x91, 0xe1, 0x46, 0xd3, 0x87, 0x1c, 0xec, 0xde, 0x67, 0x34, 0xf3, 0x8d, 0xb7, + 0xbe, 0xaa, 0x12, 0x08, 0x7f, 0x7e, 0xa0, 0xcc, 0xc6, 0xcf, 0x30, 0x7d, 0x85, 0x1b, 0xea, 0x48, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x33, 0x53, 0x0d, 0xea, 0xf9, 0x0d, 0x4d, + 0xb2, 0xec, 0x4a, 0xe3, 0x78, 0x6e, 0xc3, 0x08, 0x4e, 0x45, 0x47, 0x4f, 0x45, 0x58, 0x54, 0x53, + 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00, 0x00, + 0x43, 0x41, 0x62, 0x87, 0xb8, 0xbb, 0x52, 0xa8, 0xc2, 0x47, 0x80, 0x3f, 0x56, 0xf8, 0x2d, 0x16, + 0x5c, 0x33, 0x53, 0x0d, 0xea, 0xf9, 0x0d, 0x4d, 0xb2, 0xec, 0x4a, 0xe3, 0x78, 0x6e, 0xc3, 0x08, + 0x40, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x30, 0x56, 0xa0, 0x54, 0x30, 0x52, 0x30, 0x27, + 0x80, 0x25, 0x30, 0x23, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x20, 0x4b, 0x65, 0x79, 0x30, 0x27, 0x80, 0x25, 0x30, 0x23, 0x31, 0x21, 0x30, + 0x1f, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x18, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x20, 0x53, 0x69, + 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x20, 0x4b, 0x65, 0x79, + 0x01, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x01, 0x00, 0xa6, 0x4b, + 0x12, 0xc0, 0x02, 0xf7, 0xea, 0x63, 0xd9, 0x5a, 0xf1, 0x62, 0x95, 0x16, 0xf9, 0x73, 0xef, 0xa4, + 0xe5, 0x74, 0x26, 0x7b, 0x55, 0xe9, 0x14, 0x7e, 0x96, 0xea, 0x2e, 0x56, 0x41, 0xfe, 0x00, 0x00, + 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00 + }; + + NegotiateResponse negotiateResponse = new NegotiateResponse(negotiateResponseCommandBytes, 0); + Assert.AreEqual(SMB2Dialect.SMB311, negotiateResponse.DialectRevision); + Assert.AreEqual(2, negotiateResponse.NegotiateContextList.Count); + } + } +} From 1284a2168dc20bb082587d5373f0b32f0cfb2917 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 30 Jul 2024 22:57:28 +0300 Subject: [PATCH 112/247] NTLMAuthenticationClient: Set MechanismListMIC when applicable --- SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index 869b9f8d..9d563e0a 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -5,6 +5,7 @@ * either version 3 of the License, or (at your option) any later version. */ using SMBLibrary.Authentication.GSSAPI; +using SMBLibrary.Authentication.NTLM; using System.Collections.Generic; using Utilities; @@ -108,6 +109,9 @@ protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) { SimpleProtectedNegotiationTokenResponse outputToken = new SimpleProtectedNegotiationTokenResponse(); outputToken.ResponseToken = authenticateMessageBytes; + List mechanismTypeList = new List() { GSSProvider.NTLMSSPIdentifier }; + byte[] mechListBytes = SimpleProtectedNegotiationTokenInit.GetMechanismTypeListBytes(mechanismTypeList); + outputToken.MechanismListMIC = NTLMCryptography.ComputeMechListMIC(m_sessionKey, mechListBytes); return outputToken.GetBytes(); } else From 6f4a0c1c9f42da630e91d7a29691c42d298a05bc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 30 Jul 2024 23:01:01 +0300 Subject: [PATCH 113/247] SMBLibrary 1.5.3.2 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 4350c069..af2689d7 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.3.1 + 1.5.3.2 1573;1591 SMBLibrary false From 1485c365d509561b3a3cd57961547e572b89348a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 01:40:43 +0300 Subject: [PATCH 114/247] SMB2: NegotiateResponse: Bugfix: NegotiateContext was not read from the correct position for non-zero offset --- SMBLibrary/SMB2/Commands/NegotiateResponse.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/SMB2/Commands/NegotiateResponse.cs b/SMBLibrary/SMB2/Commands/NegotiateResponse.cs index af4edb32..d3a2e09a 100644 --- a/SMBLibrary/SMB2/Commands/NegotiateResponse.cs +++ b/SMBLibrary/SMB2/Commands/NegotiateResponse.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -58,7 +58,7 @@ public NegotiateResponse(byte[] buffer, int offset) : base(buffer, offset) SecurityBufferLength = LittleEndianConverter.ToUInt16(buffer, offset + SMB2Header.Length + 58); NegotiateContextOffset = LittleEndianConverter.ToUInt32(buffer, offset + SMB2Header.Length + 60); SecurityBuffer = ByteReader.ReadBytes(buffer, offset + SecurityBufferOffset, SecurityBufferLength); - NegotiateContextList = NegotiateContext.ReadNegotiateContextList(buffer, (int)NegotiateContextOffset, NegotiateContextCount); + NegotiateContextList = NegotiateContext.ReadNegotiateContextList(buffer, offset + (int)NegotiateContextOffset, NegotiateContextCount); } public override void WriteCommandBytes(byte[] buffer, int offset) From 3f4381b5f9d11fab313927c90bc8ce319c37d95e Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 01:42:00 +0300 Subject: [PATCH 115/247] Tests: Added tests to NegotiateRequestParsingTests and NegotiateResponseParsingTests --- .../SMB2/NegotiateRequestParsingTests.cs | 49 ++++++++++++++++--- .../SMB2/NegotiateResponseParsingTests.cs | 40 ++++++++++++--- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs b/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs index fea7b41a..a2602235 100644 --- a/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs +++ b/SMBLibrary.Tests/SMB2/NegotiateRequestParsingTests.cs @@ -6,6 +6,7 @@ */ using Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.SMB2; +using System; namespace SMBLibrary.Tests.SMB2 { @@ -13,9 +14,47 @@ namespace SMBLibrary.Tests.SMB2 public class NegotiateRequestParsingTests { [TestMethod] - public void ParseNegotiateRequestWithNegotiateContextList() + public void ParseNegotiateRequestWithNegotiateContextList_WhenOffsetIsZero() { - byte[] negotiateRequestCommandBytes = new byte[] + byte[] negotiateRequestCommandBytes = GetNegotiateRequestWithNegotiateContextListBytes(); + NegotiateRequest negotiateRequest = new NegotiateRequest(negotiateRequestCommandBytes, 0); + Assert.AreEqual(5, negotiateRequest.Dialects.Count); + Assert.AreEqual(4, negotiateRequest.NegotiateContextList.Count); + Assert.AreEqual(NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES, negotiateRequest.NegotiateContextList[0].ContextType); + Assert.AreEqual(NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES, negotiateRequest.NegotiateContextList[1].ContextType); + } + + [TestMethod] + public void ParseNegotiateRequestWithNegotiateContextList_WhenOffsetIsNonZero() + { + // Test non-zero offset + byte[] negotiateRequestCommandBytes = GetNegotiateRequestWithNegotiateContextListBytes(); + byte[] buffer = new byte[negotiateRequestCommandBytes.Length + 2]; + Array.Copy(negotiateRequestCommandBytes, 0, buffer, 2, negotiateRequestCommandBytes.Length); + + NegotiateRequest negotiateRequest = new NegotiateRequest(buffer, 2); + Assert.AreEqual(5, negotiateRequest.Dialects.Count); + Assert.AreEqual(4, negotiateRequest.NegotiateContextList.Count); + Assert.AreEqual(NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES, negotiateRequest.NegotiateContextList[0].ContextType); + Assert.AreEqual(NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES, negotiateRequest.NegotiateContextList[1].ContextType); + } + + [TestMethod] + public void ParseRewrittenNegotiateRequestWithNegotiateContextList() + { + byte[] negotiateRequestCommandBytes = GetNegotiateRequestWithNegotiateContextListBytes(); + NegotiateRequest negotiateRequest = new NegotiateRequest(negotiateRequestCommandBytes, 0); + negotiateRequestCommandBytes = negotiateRequest.GetBytes(); + negotiateRequest = new NegotiateRequest(negotiateRequestCommandBytes, 0); + Assert.AreEqual(5, negotiateRequest.Dialects.Count); + Assert.AreEqual(4, negotiateRequest.NegotiateContextList.Count); + Assert.AreEqual(NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES, negotiateRequest.NegotiateContextList[0].ContextType); + Assert.AreEqual(NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES, negotiateRequest.NegotiateContextList[1].ContextType); + } + + private static byte[] GetNegotiateRequestWithNegotiateContextListBytes() + { + return new byte[] { 0xfe,0x53,0x4d,0x42,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -35,12 +74,6 @@ public void ParseNegotiateRequestWithNegotiateContextList() 0x73,0x00,0x65,0x00,0x72,0x00,0x73,0x00,0x2e,0x00,0x6c,0x00,0x6f,0x00,0x63,0x00, 0x61,0x00,0x6c,0x00 }; - - NegotiateRequest negotiateRequest = new NegotiateRequest(negotiateRequestCommandBytes, 0); - Assert.AreEqual(5, negotiateRequest.Dialects.Count); - Assert.AreEqual(4, negotiateRequest.NegotiateContextList.Count); - Assert.AreEqual(NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES, negotiateRequest.NegotiateContextList[0].ContextType); - Assert.AreEqual(NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES, negotiateRequest.NegotiateContextList[1].ContextType); } } } diff --git a/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs b/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs index 502d7102..6a4bf99a 100644 --- a/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs +++ b/SMBLibrary.Tests/SMB2/NegotiateResponseParsingTests.cs @@ -6,6 +6,7 @@ */ using Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.SMB2; +using System; namespace SMBLibrary.Tests.SMB2 { @@ -13,9 +14,40 @@ namespace SMBLibrary.Tests.SMB2 public class NegotiateResponseParsingTests { [TestMethod] - public void ParseNegotiateResponseWithNegotiateContextList() + public void ParseNegotiateResponseWithNegotiateContextList_WhenOffsetIsZero() { - byte[] negotiateResponseCommandBytes = new byte[] + byte[] negotiateResponseCommandBytes = GetNegotiateResponseWithNegotiateContextListBytes(); + NegotiateResponse negotiateResponse = new NegotiateResponse(negotiateResponseCommandBytes, 0); + Assert.AreEqual(SMB2Dialect.SMB311, negotiateResponse.DialectRevision); + Assert.AreEqual(2, negotiateResponse.NegotiateContextList.Count); + } + + [TestMethod] + public void ParseNegotiateResponseWithNegotiateContextList_WhenOffsetIsNonZero() + { + byte[] negotiateResponseCommandBytes = GetNegotiateResponseWithNegotiateContextListBytes(); + byte[] buffer = new byte[negotiateResponseCommandBytes.Length + 2]; + Array.Copy(negotiateResponseCommandBytes, 0, buffer, 2, negotiateResponseCommandBytes.Length); + + NegotiateResponse negotiateResponse = new NegotiateResponse(buffer, 2); + Assert.AreEqual(SMB2Dialect.SMB311, negotiateResponse.DialectRevision); + Assert.AreEqual(2, negotiateResponse.NegotiateContextList.Count); + } + + [TestMethod] + public void ParseRewrittenNegotiateResponseWithNegotiateContextList() + { + byte[] negotiateResponseCommandBytes = GetNegotiateResponseWithNegotiateContextListBytes(); + NegotiateResponse negotiateResponse = new NegotiateResponse(negotiateResponseCommandBytes, 0); + negotiateResponseCommandBytes = negotiateResponse.GetBytes(); + negotiateResponse = new NegotiateResponse(negotiateResponseCommandBytes, 0); + Assert.AreEqual(SMB2Dialect.SMB311, negotiateResponse.DialectRevision); + Assert.AreEqual(2, negotiateResponse.NegotiateContextList.Count); + } + + private static byte[] GetNegotiateResponseWithNegotiateContextListBytes() + { + return new byte[] { 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -50,10 +82,6 @@ public void ParseNegotiateResponseWithNegotiateContextList() 0xe5, 0x74, 0x26, 0x7b, 0x55, 0xe9, 0x14, 0x7e, 0x96, 0xea, 0x2e, 0x56, 0x41, 0xfe, 0x00, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00 }; - - NegotiateResponse negotiateResponse = new NegotiateResponse(negotiateResponseCommandBytes, 0); - Assert.AreEqual(SMB2Dialect.SMB311, negotiateResponse.DialectRevision); - Assert.AreEqual(2, negotiateResponse.NegotiateContextList.Count); } } } From d3f69c2b0919976e9a33015ffd1f9c0551188593 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 01:42:56 +0300 Subject: [PATCH 116/247] SMBLibrary 1.5.3.3 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index af2689d7..cbca3e79 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.3.2 + 1.5.3.3 1573;1591 SMBLibrary false From 1d4484ff7ea549047530ec2f45615d6a6598ca3d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 10:44:37 +0300 Subject: [PATCH 117/247] NetBiosSuffix.FileServiceService: Corrected typo --- SMBLibrary/Client/NameServiceClient.cs | 2 +- SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- .../NetBios/NameServicePackets/Enums/NetBiosSuffix.cs | 8 ++------ SMBLibrary/Server/NameServer.cs | 6 +++--- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/SMBLibrary/Client/NameServiceClient.cs b/SMBLibrary/Client/NameServiceClient.cs index e67ecd0d..e4e6c47c 100644 --- a/SMBLibrary/Client/NameServiceClient.cs +++ b/SMBLibrary/Client/NameServiceClient.cs @@ -32,7 +32,7 @@ public string GetServerName() foreach (KeyValuePair entry in response.Names) { NetBiosSuffix suffix = NetBiosUtils.GetSuffixFromMSNetBiosName(entry.Key); - if (suffix == NetBiosSuffix.FileServiceService) + if (suffix == NetBiosSuffix.FileServerService) { return entry.Key; } diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 6f6e785d..b23ef96a 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -103,7 +103,7 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp if (transport == SMBTransportType.NetBiosOverTCP) { SessionRequestPacket sessionRequest = new SessionRequestPacket(); - sessionRequest.CalledName = NetBiosUtils.GetMSNetBiosName("*SMBSERVER", NetBiosSuffix.FileServiceService); + sessionRequest.CalledName = NetBiosUtils.GetMSNetBiosName("*SMBSERVER", NetBiosSuffix.FileServerService); sessionRequest.CallingName = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.WorkstationService); TrySendPacket(m_clientSocket, sessionRequest); diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index dd2f50b6..42a150a0 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -113,7 +113,7 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp if (transport == SMBTransportType.NetBiosOverTCP) { SessionRequestPacket sessionRequest = new SessionRequestPacket(); - sessionRequest.CalledName = NetBiosUtils.GetMSNetBiosName("*SMBSERVER", NetBiosSuffix.FileServiceService); + sessionRequest.CalledName = NetBiosUtils.GetMSNetBiosName("*SMBSERVER", NetBiosSuffix.FileServerService); sessionRequest.CallingName = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.WorkstationService); TrySendPacket(m_clientSocket, sessionRequest); diff --git a/SMBLibrary/NetBios/NameServicePackets/Enums/NetBiosSuffix.cs b/SMBLibrary/NetBios/NameServicePackets/Enums/NetBiosSuffix.cs index a7126fcc..9e11352e 100644 --- a/SMBLibrary/NetBios/NameServicePackets/Enums/NetBiosSuffix.cs +++ b/SMBLibrary/NetBios/NameServicePackets/Enums/NetBiosSuffix.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Text; - namespace SMBLibrary.NetBios { /// /// 16th character suffix for netbios name. - /// see http://support.microsoft.com/kb/163409/en-us + /// see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nbte/6dbf0972-bb15-4f29-afeb-baaae98416ed /// public enum NetBiosSuffix : byte { @@ -15,6 +11,6 @@ public enum NetBiosSuffix : byte DomainMasterBrowser = 0x1B, MasterBrowser = 0x1D, BrowserServiceElections = 0x1E, - FileServiceService = 0x20, + FileServerService = 0x20, } } diff --git a/SMBLibrary/Server/NameServer.cs b/SMBLibrary/Server/NameServer.cs index db42cf9c..86149ffd 100644 --- a/SMBLibrary/Server/NameServer.cs +++ b/SMBLibrary/Server/NameServer.cs @@ -111,7 +111,7 @@ private void ReceiveCallback(IAsyncResult result) bool nameMatch = String.Equals(name, Environment.MachineName, StringComparison.OrdinalIgnoreCase); - if (nameMatch && ((suffix == NetBiosSuffix.WorkstationService) || (suffix == NetBiosSuffix.FileServiceService))) + if (nameMatch && ((suffix == NetBiosSuffix.WorkstationService) || (suffix == NetBiosSuffix.FileServerService))) { PositiveNameQueryResponse response = new PositiveNameQueryResponse(); response.Header.TransactionID = request.Header.TransactionID; @@ -129,7 +129,7 @@ private void ReceiveCallback(IAsyncResult result) response.Resource.Name = request.Question.Name; NameFlags nameFlags = new NameFlags(); string name1 = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.WorkstationService); - string name2 = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.FileServiceService); + string name2 = NetBiosUtils.GetMSNetBiosName(Environment.MachineName, NetBiosSuffix.FileServerService); NameFlags nameFlags3 = new NameFlags(); nameFlags3.WorkGroup = true; string name3 = NetBiosUtils.GetMSNetBiosName(WorkgroupName, NetBiosSuffix.WorkstationService); @@ -164,7 +164,7 @@ private void ReceiveCallback(IAsyncResult result) private void RegisterNetBIOSName() { NameRegistrationRequest request1 = new NameRegistrationRequest(Environment.MachineName, NetBiosSuffix.WorkstationService, m_serverAddress); - NameRegistrationRequest request2 = new NameRegistrationRequest(Environment.MachineName, NetBiosSuffix.FileServiceService, m_serverAddress); + NameRegistrationRequest request2 = new NameRegistrationRequest(Environment.MachineName, NetBiosSuffix.FileServerService, m_serverAddress); NameRegistrationRequest request3 = new NameRegistrationRequest(WorkgroupName, NetBiosSuffix.WorkstationService, m_serverAddress); request3.NameFlags.WorkGroup = true; RegisterName(request1); From e6065a1870b00af98dba83511ffc19574716d17e Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 11:14:40 +0300 Subject: [PATCH 118/247] SMB2Client: Updated encryption and signing logic to support SMB 3.0.2 --- SMBLibrary/Client/SMB2Client.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 42a150a0..7601a66b 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -283,11 +283,11 @@ public NTStatus Login(IAuthenticationClient authenticationClient) m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, null); } - if (m_dialect == SMB2Dialect.SMB300) + if (m_dialect >= SMB2Dialect.SMB300) { m_encryptSessionData = (sessionFlags & SessionFlags.EncryptData) > 0; - m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, SMB2Dialect.SMB300, null); - m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, SMB2Dialect.SMB300, null); + m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, m_dialect, null); + m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, m_dialect, null); } } return response.Header.Status; @@ -463,7 +463,7 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) if (packet is SessionMessagePacket) { byte[] messageBytes; - if (m_dialect == SMB2Dialect.SMB300 && SMB2TransformHeader.IsTransformHeader(packet.Trailer, 0)) + if (m_dialect >= SMB2Dialect.SMB300 && SMB2TransformHeader.IsTransformHeader(packet.Trailer, 0)) { SMB2TransformHeader transformHeader = new SMB2TransformHeader(packet.Trailer, 0); byte[] encryptedMessage = ByteReader.ReadBytes(packet.Trailer, SMB2TransformHeader.Length, (int)transformHeader.OriginalMessageSize); @@ -636,7 +636,7 @@ internal void TrySendCommand(SMB2Command request, bool encryptData) if (m_signingRequired && !encryptData) { request.Header.IsSigned = (m_sessionID != 0 && ((request.CommandName == SMB2CommandName.TreeConnect || request.Header.TreeID != 0) || - (m_dialect == SMB2Dialect.SMB300 && request.CommandName == SMB2CommandName.Logoff))); + (m_dialect >= SMB2Dialect.SMB300 && request.CommandName == SMB2CommandName.Logoff))); if (request.Header.IsSigned) { request.Header.Signature = new byte[16]; // Request could be reused From 76c1ffa981a77039439661aad7e1847ba70c043d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 11:41:17 +0300 Subject: [PATCH 119/247] SMB2: Added PreAuthIntegrityCapabilities structure implementation --- .../SMB2/Structures/Enums/HashAlgorithm.cs | 7 +++ .../PreAuthIntegrityCapabilities.cs | 53 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 SMBLibrary/SMB2/Structures/Enums/HashAlgorithm.cs create mode 100644 SMBLibrary/SMB2/Structures/PreAuthIntegrityCapabilities.cs diff --git a/SMBLibrary/SMB2/Structures/Enums/HashAlgorithm.cs b/SMBLibrary/SMB2/Structures/Enums/HashAlgorithm.cs new file mode 100644 index 00000000..93e30f2d --- /dev/null +++ b/SMBLibrary/SMB2/Structures/Enums/HashAlgorithm.cs @@ -0,0 +1,7 @@ +namespace SMBLibrary.SMB2 +{ + public enum HashAlgorithm : ushort + { + SHA512 = 1 + } +} diff --git a/SMBLibrary/SMB2/Structures/PreAuthIntegrityCapabilities.cs b/SMBLibrary/SMB2/Structures/PreAuthIntegrityCapabilities.cs new file mode 100644 index 00000000..f5aa0909 --- /dev/null +++ b/SMBLibrary/SMB2/Structures/PreAuthIntegrityCapabilities.cs @@ -0,0 +1,53 @@ +/* Copyright (C) 2024 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.Collections.Generic; +using Utilities; + +namespace SMBLibrary.SMB2 +{ + /// + /// [MS-SMB2] 2.2.3.1.1 - SMB2_PREAUTH_INTEGRITY_CAPABILITIES + /// + public class PreAuthIntegrityCapabilities + { + // ushort HashAlgorithmCount; + // ushort SaltLength; + public List HashAlgorithms = new List(); + public byte[] Salt; + + public PreAuthIntegrityCapabilities() + { + } + + public PreAuthIntegrityCapabilities(byte[] buffer, int offset) + { + ushort hashAlgorithmCount = LittleEndianConverter.ToUInt16(buffer, offset + 0); + ushort saltLength = LittleEndianConverter.ToUInt16(buffer, offset + 2); + for (int index = 0; index < hashAlgorithmCount; index++) + { + HashAlgorithms.Add((HashAlgorithm)LittleEndianConverter.ToUInt16(buffer, offset + 4 + index * 2)); + } + Salt = ByteReader.ReadBytes(buffer, offset + 4 + hashAlgorithmCount * 2, saltLength); + } + + public byte[] GetBytes() + { + int length = 4 + HashAlgorithms.Count * 2 + Salt.Length; + + byte[] buffer = new byte[length]; + LittleEndianWriter.WriteUInt16(buffer, 0, (ushort)HashAlgorithms.Count); + LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)Salt.Length); + for (int index = 0; index < HashAlgorithms.Count; index++) + { + LittleEndianWriter.WriteUInt16(buffer, 4 + index * 2, (ushort)HashAlgorithms[index]); + } + ByteWriter.WriteBytes(buffer, 4 + HashAlgorithms.Count * 2, Salt); + + return buffer; + } + } +} From d8472308683cc724f0b94a1707fc778791a9a0be Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 12:26:32 +0300 Subject: [PATCH 120/247] SMB2Cryptography: Added ComputeHash method --- SMBLibrary/SMB2/SMB2Cryptography.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/SMB2/SMB2Cryptography.cs b/SMBLibrary/SMB2/SMB2Cryptography.cs index b88d8557..93b98394 100644 --- a/SMBLibrary/SMB2/SMB2Cryptography.cs +++ b/SMBLibrary/SMB2/SMB2Cryptography.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2020-2024 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, @@ -107,6 +107,18 @@ public static byte[] DecryptMessage(byte[] key, SMB2TransformHeader transformHea return AesCcm.DecryptAndAuthenticate(key, aesCcmNonce, encryptedMessage, associatedData, transformHeader.Signature); } + public static byte[] ComputeHash(HashAlgorithm hashAlgorithm, byte[] buffer) + { + if (hashAlgorithm == HashAlgorithm.SHA512) + { + return SHA512.Create().ComputeHash(buffer); + } + else + { + throw new NotSupportedException($"Hash algorithm {hashAlgorithm} is not supported"); + } + } + private static SMB2TransformHeader CreateTransformHeader(byte[] nonce, int originalMessageLength, ulong sessionID) { byte[] nonceWithPadding = new byte[SMB2TransformHeader.NonceLength]; From 3cf5fcbe9389b60184c32262a4f7028518b2d8a8 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 12:53:02 +0300 Subject: [PATCH 121/247] SMB2: Added EncryptionCapabilities structure implementation --- .../SMB2/Structures/EncryptionCapabilities.cs | 46 +++++++++++++++++++ .../SMB2/Structures/Enums/CipherAlgorithm.cs | 10 ++++ 2 files changed, 56 insertions(+) create mode 100644 SMBLibrary/SMB2/Structures/EncryptionCapabilities.cs create mode 100644 SMBLibrary/SMB2/Structures/Enums/CipherAlgorithm.cs diff --git a/SMBLibrary/SMB2/Structures/EncryptionCapabilities.cs b/SMBLibrary/SMB2/Structures/EncryptionCapabilities.cs new file mode 100644 index 00000000..cdd3585a --- /dev/null +++ b/SMBLibrary/SMB2/Structures/EncryptionCapabilities.cs @@ -0,0 +1,46 @@ +/* Copyright (C) 2024 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.Collections.Generic; +using Utilities; + +namespace SMBLibrary.SMB2 +{ + /// + /// [MS-SMB2] 2.2.3.1.2 SMB2_ENCRYPTION_CAPABILITIES + /// + public class EncryptionCapabilities + { + // ushort CipherCount; + public List Ciphers = new List(); + + public EncryptionCapabilities() + { + } + + public EncryptionCapabilities(byte[] buffer, int offset) + { + ushort cipherCount = LittleEndianConverter.ToUInt16(buffer, offset); + for (int index = 0; index < cipherCount; index++) + { + Ciphers.Add((CipherAlgorithm)LittleEndianConverter.ToUInt16(buffer, offset + index * 2)); + } + } + + public byte[] GetBytes() + { + int length = 2 * Ciphers.Count * 2; + byte[] buffer = new byte[length]; + LittleEndianWriter.WriteUInt16(buffer, 0, (ushort)Ciphers.Count); + for (int index = 0; index < Ciphers.Count; index++) + { + LittleEndianWriter.WriteUInt16(buffer, 2 + index * 2, (ushort)Ciphers[index]); + } + + return buffer; + } + } +} diff --git a/SMBLibrary/SMB2/Structures/Enums/CipherAlgorithm.cs b/SMBLibrary/SMB2/Structures/Enums/CipherAlgorithm.cs new file mode 100644 index 00000000..a9da60fa --- /dev/null +++ b/SMBLibrary/SMB2/Structures/Enums/CipherAlgorithm.cs @@ -0,0 +1,10 @@ +namespace SMBLibrary.SMB2 +{ + public enum CipherAlgorithm : ushort + { + Aes128Ccm = 0x0001, + Aes128Gcm = 0x0002, + Aes256Ccm = 0x0003, + Aes256Gcm = 0x0004, + } +} From fdc823442985fa86e7a7007369c6b7afd1a68ac0 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 12:54:30 +0300 Subject: [PATCH 122/247] NegotiateContext: Added constructors taking PreAuthIntegrityCapabilities and EncryptionCapabilities --- SMBLibrary/SMB2/Structures/NegotiateContext.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext.cs b/SMBLibrary/SMB2/Structures/NegotiateContext.cs index 4b81068b..00e897ee 100644 --- a/SMBLibrary/SMB2/Structures/NegotiateContext.cs +++ b/SMBLibrary/SMB2/Structures/NegotiateContext.cs @@ -26,6 +26,18 @@ public NegotiateContext() { } + public NegotiateContext(PreAuthIntegrityCapabilities preAuthIntegrityCapabilities) + { + ContextType = NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES; + Data = preAuthIntegrityCapabilities.GetBytes(); + } + + public NegotiateContext(EncryptionCapabilities encryptionCapabilities) + { + ContextType = NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES; + Data = encryptionCapabilities.GetBytes(); + } + public NegotiateContext(byte[] buffer, int offset) { ContextType = (NegotiateContextType)LittleEndianConverter.ToUInt16(buffer, offset + 0); From 18459859c740ef1345b190de9ef0443d59dfe239 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 12:57:34 +0300 Subject: [PATCH 123/247] SMB2Client: Updated encryption and signing logic to support SMB 3.1.1 --- SMBLibrary/Client/SMB2Client.cs | 43 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 7601a66b..5d22b791 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -56,6 +56,7 @@ public class SMB2Client : ISMBClient private ulong m_sessionID; private byte[] m_securityBlob; private byte[] m_sessionKey; + private byte[] m_preauthIntegrityHashValue; // SMB 3.1.1 private ushort m_availableCredits = 1; public SMB2Client() @@ -203,7 +204,14 @@ private bool NegotiateDialect() request.Dialects.Add(SMB2Dialect.SMB202); request.Dialects.Add(SMB2Dialect.SMB210); request.Dialects.Add(SMB2Dialect.SMB300); - +#if SMB302_CLIENT + request.Dialects.Add(SMB2Dialect.SMB302); +#endif +#if SMB311_CLIENT + request.Dialects.Add(SMB2Dialect.SMB311); + request.NegotiateContextList = GetNegotiateContextList(); + m_preauthIntegrityHashValue = new byte[64]; +#endif TrySendCommand(request); NegotiateResponse response = WaitForCommand(request.MessageID) as NegotiateResponse; if (response != null && response.Header.Status == NTStatus.STATUS_SUCCESS) @@ -280,14 +288,14 @@ public NTStatus Login(IAuthenticationClient authenticationClient) } else { - m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, null); + m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); } if (m_dialect >= SMB2Dialect.SMB300) { m_encryptSessionData = (sessionFlags & SessionFlags.EncryptData) > 0; - m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, m_dialect, null); - m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, m_dialect, null); + m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); + m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); } } return response.Header.Status; @@ -488,6 +496,11 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) return; } + if (m_preauthIntegrityHashValue != null && (command is NegotiateResponse || (command is SessionSetupResponse sessionSetupResponse && sessionSetupResponse.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED))) + { + m_preauthIntegrityHashValue = SMB2Cryptography.ComputeHash(HashAlgorithm.SHA512, ByteUtils.Concatenate(m_preauthIntegrityHashValue, messageBytes)); + } + m_availableCredits += command.Header.Credits; if (m_transport == SMBTransportType.DirectTCPTransport && command is NegotiateResponse) @@ -657,6 +670,24 @@ internal void TrySendCommand(SMB2Command request, bool encryptData) } } + /// SMB 3.1.1 only + private List GetNegotiateContextList() + { + PreAuthIntegrityCapabilities preAuthIntegrityCapabilities = new PreAuthIntegrityCapabilities(); + preAuthIntegrityCapabilities.HashAlgorithms.Add(HashAlgorithm.SHA512); + preAuthIntegrityCapabilities.Salt = new byte[32]; + new Random().NextBytes(preAuthIntegrityCapabilities.Salt); + + EncryptionCapabilities encryptionCapabilities = new EncryptionCapabilities(); + encryptionCapabilities.Ciphers.Add(CipherAlgorithm.Aes128Ccm); + + return new List() + { + new NegotiateContext(preAuthIntegrityCapabilities), + new NegotiateContext(encryptionCapabilities) + }; + } + public uint MaxTransactSize { get @@ -700,6 +731,10 @@ private void TrySendCommand(Socket socket, SMB2Command request, byte[] encryptio else { packet.Trailer = request.GetBytes(); + if (m_preauthIntegrityHashValue != null && (request is NegotiateRequest || request is SessionSetupRequest)) + { + m_preauthIntegrityHashValue = SMB2Cryptography.ComputeHash(HashAlgorithm.SHA512, ByteUtils.Concatenate(m_preauthIntegrityHashValue, packet.Trailer)); + } } TrySendPacket(socket, packet); } From 5f4af1e35b24cf92936bf467d291345f3ec4119a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 13:34:15 +0300 Subject: [PATCH 124/247] Move NegotiateContext structures to a separate directory --- .../Structures/{ => NegotiateContext}/EncryptionCapabilities.cs | 0 .../SMB2/Structures/{ => NegotiateContext}/NegotiateContext.cs | 0 .../{ => NegotiateContext}/PreAuthIntegrityCapabilities.cs | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename SMBLibrary/SMB2/Structures/{ => NegotiateContext}/EncryptionCapabilities.cs (100%) rename SMBLibrary/SMB2/Structures/{ => NegotiateContext}/NegotiateContext.cs (100%) rename SMBLibrary/SMB2/Structures/{ => NegotiateContext}/PreAuthIntegrityCapabilities.cs (100%) diff --git a/SMBLibrary/SMB2/Structures/EncryptionCapabilities.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs similarity index 100% rename from SMBLibrary/SMB2/Structures/EncryptionCapabilities.cs rename to SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/NegotiateContext.cs similarity index 100% rename from SMBLibrary/SMB2/Structures/NegotiateContext.cs rename to SMBLibrary/SMB2/Structures/NegotiateContext/NegotiateContext.cs diff --git a/SMBLibrary/SMB2/Structures/PreAuthIntegrityCapabilities.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/PreAuthIntegrityCapabilities.cs similarity index 100% rename from SMBLibrary/SMB2/Structures/PreAuthIntegrityCapabilities.cs rename to SMBLibrary/SMB2/Structures/NegotiateContext/PreAuthIntegrityCapabilities.cs From d7d65f0ef2cb0eb6301304f8348f76bacf9a3a0b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 13:50:39 +0300 Subject: [PATCH 125/247] SMB2: EncryptionCapabilities.GetBytes: Bugfix: Corrected Data length computation --- .../SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs index cdd3585a..21124fb9 100644 --- a/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs +++ b/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs @@ -32,7 +32,7 @@ public EncryptionCapabilities(byte[] buffer, int offset) public byte[] GetBytes() { - int length = 2 * Ciphers.Count * 2; + int length = 2 + Ciphers.Count * 2; byte[] buffer = new byte[length]; LittleEndianWriter.WriteUInt16(buffer, 0, (ushort)Ciphers.Count); for (int index = 0; index < Ciphers.Count; index++) From e0d8e8dfbd6823cffb6cd0e969b60958ccdf06b6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 14:03:04 +0300 Subject: [PATCH 126/247] Refactored PreAuthIntegrityCapabilities and EncryptionCapabilities to inherit from NegotiateContext --- SMBLibrary/Client/SMB2Client.cs | 4 +- .../EncryptionCapabilities.cs | 27 ++++++---- .../NegotiateContext/NegotiateContext.cs | 53 +++++++++++-------- .../PreAuthIntegrityCapabilities.cs | 36 +++++++------ 4 files changed, 71 insertions(+), 49 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 5d22b791..fc6f37ad 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -683,8 +683,8 @@ private List GetNegotiateContextList() return new List() { - new NegotiateContext(preAuthIntegrityCapabilities), - new NegotiateContext(encryptionCapabilities) + preAuthIntegrityCapabilities, + encryptionCapabilities }; } diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs index 21124fb9..ca8d7dc9 100644 --- a/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs +++ b/SMBLibrary/SMB2/Structures/NegotiateContext/EncryptionCapabilities.cs @@ -12,7 +12,7 @@ namespace SMBLibrary.SMB2 /// /// [MS-SMB2] 2.2.3.1.2 SMB2_ENCRYPTION_CAPABILITIES /// - public class EncryptionCapabilities + public class EncryptionCapabilities : NegotiateContext { // ushort CipherCount; public List Ciphers = new List(); @@ -21,26 +21,33 @@ public EncryptionCapabilities() { } - public EncryptionCapabilities(byte[] buffer, int offset) + public EncryptionCapabilities(byte[] buffer, int offset) : base(buffer, offset) { - ushort cipherCount = LittleEndianConverter.ToUInt16(buffer, offset); + ushort cipherCount = LittleEndianConverter.ToUInt16(Data, 0); for (int index = 0; index < cipherCount; index++) { - Ciphers.Add((CipherAlgorithm)LittleEndianConverter.ToUInt16(buffer, offset + index * 2)); + Ciphers.Add((CipherAlgorithm)LittleEndianConverter.ToUInt16(Data, index * 2)); } } - public byte[] GetBytes() + public override void WriteData() { - int length = 2 + Ciphers.Count * 2; - byte[] buffer = new byte[length]; - LittleEndianWriter.WriteUInt16(buffer, 0, (ushort)Ciphers.Count); + Data = new byte[DataLength]; + LittleEndianWriter.WriteUInt16(Data, 0, (ushort)Ciphers.Count); for (int index = 0; index < Ciphers.Count; index++) { - LittleEndianWriter.WriteUInt16(buffer, 2 + index * 2, (ushort)Ciphers[index]); + LittleEndianWriter.WriteUInt16(Data, 2 + index * 2, (ushort)Ciphers[index]); } + } - return buffer; + public override int DataLength + { + get + { + return 2 + Ciphers.Count * 2; + } } + + public override NegotiateContextType ContextType => NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES; } } diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext/NegotiateContext.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/NegotiateContext.cs index 00e897ee..922eb710 100644 --- a/SMBLibrary/SMB2/Structures/NegotiateContext/NegotiateContext.cs +++ b/SMBLibrary/SMB2/Structures/NegotiateContext/NegotiateContext.cs @@ -4,7 +4,6 @@ * 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; @@ -17,8 +16,8 @@ public class NegotiateContext { public const int FixedLength = 8; - public NegotiateContextType ContextType; - private ushort DataLength; + private NegotiateContextType m_contextType; + // ushort DataLength; public uint Reserved; public byte[] Data = new byte[0]; @@ -26,31 +25,23 @@ public NegotiateContext() { } - public NegotiateContext(PreAuthIntegrityCapabilities preAuthIntegrityCapabilities) - { - ContextType = NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES; - Data = preAuthIntegrityCapabilities.GetBytes(); - } - - public NegotiateContext(EncryptionCapabilities encryptionCapabilities) + public NegotiateContext(byte[] buffer, int offset) { - ContextType = NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES; - Data = encryptionCapabilities.GetBytes(); + m_contextType = (NegotiateContextType)LittleEndianConverter.ToUInt16(buffer, offset + 0); + int dataLength = LittleEndianConverter.ToUInt16(buffer, offset + 2); + Reserved = LittleEndianConverter.ToUInt32(buffer, offset + 4); + Data = ByteReader.ReadBytes(buffer, offset + 8, dataLength); } - public NegotiateContext(byte[] buffer, int offset) + public virtual void WriteData() { - ContextType = (NegotiateContextType)LittleEndianConverter.ToUInt16(buffer, offset + 0); - DataLength = LittleEndianConverter.ToUInt16(buffer, offset + 2); - Reserved = LittleEndianConverter.ToUInt32(buffer, offset + 4); - Data = ByteReader.ReadBytes(buffer, offset + 8, DataLength); } public void WriteBytes(byte[] buffer, int offset) { - DataLength = (ushort)Data.Length; + WriteData(); LittleEndianWriter.WriteUInt16(buffer, offset + 0, (ushort)ContextType); - LittleEndianWriter.WriteUInt16(buffer, offset + 2, DataLength); + LittleEndianWriter.WriteUInt16(buffer, offset + 2, (ushort)DataLength); LittleEndianWriter.WriteUInt32(buffer, offset + 4, Reserved); ByteWriter.WriteBytes(buffer, offset + 8, Data); } @@ -59,7 +50,7 @@ public int Length { get { - return FixedLength + Data.Length; + return FixedLength + DataLength; } } @@ -67,17 +58,31 @@ public int PaddedLength { get { - int paddingLength = (8 - (Data.Length % 8)) % 8; + int paddingLength = (8 - (DataLength % 8)) % 8; return this.Length + paddingLength; } } + public static NegotiateContext ReadNegotiateContext(byte[] buffer, int offset) + { + NegotiateContextType contextType = (NegotiateContextType)LittleEndianConverter.ToUInt16(buffer, offset + 0); + switch (contextType) + { + case NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES: + return new PreAuthIntegrityCapabilities(buffer, offset); + case NegotiateContextType.SMB2_ENCRYPTION_CAPABILITIES: + return new EncryptionCapabilities(buffer, offset); + default: + return new NegotiateContext(buffer, offset); + } + } + public static List ReadNegotiateContextList(byte[] buffer, int offset, int count) { List result = new List(); for (int index = 0; index < count; index++) { - NegotiateContext context = new NegotiateContext(buffer, offset); + NegotiateContext context = ReadNegotiateContext(buffer, offset); result.Add(context); offset += context.PaddedLength; } @@ -112,5 +117,9 @@ public static int GetNegotiateContextListLength(List negotiate } return result; } + + public virtual int DataLength => Data.Length; + + public virtual NegotiateContextType ContextType => m_contextType; } } diff --git a/SMBLibrary/SMB2/Structures/NegotiateContext/PreAuthIntegrityCapabilities.cs b/SMBLibrary/SMB2/Structures/NegotiateContext/PreAuthIntegrityCapabilities.cs index f5aa0909..ca877a46 100644 --- a/SMBLibrary/SMB2/Structures/NegotiateContext/PreAuthIntegrityCapabilities.cs +++ b/SMBLibrary/SMB2/Structures/NegotiateContext/PreAuthIntegrityCapabilities.cs @@ -12,7 +12,7 @@ namespace SMBLibrary.SMB2 /// /// [MS-SMB2] 2.2.3.1.1 - SMB2_PREAUTH_INTEGRITY_CAPABILITIES /// - public class PreAuthIntegrityCapabilities + public class PreAuthIntegrityCapabilities : NegotiateContext { // ushort HashAlgorithmCount; // ushort SaltLength; @@ -23,31 +23,37 @@ public PreAuthIntegrityCapabilities() { } - public PreAuthIntegrityCapabilities(byte[] buffer, int offset) + public PreAuthIntegrityCapabilities(byte[] buffer, int offset) : base(buffer, offset) { - ushort hashAlgorithmCount = LittleEndianConverter.ToUInt16(buffer, offset + 0); - ushort saltLength = LittleEndianConverter.ToUInt16(buffer, offset + 2); + ushort hashAlgorithmCount = LittleEndianConverter.ToUInt16(Data, 0); + ushort saltLength = LittleEndianConverter.ToUInt16(Data, 2); for (int index = 0; index < hashAlgorithmCount; index++) { - HashAlgorithms.Add((HashAlgorithm)LittleEndianConverter.ToUInt16(buffer, offset + 4 + index * 2)); + HashAlgorithms.Add((HashAlgorithm)LittleEndianConverter.ToUInt16(Data, 4 + index * 2)); } - Salt = ByteReader.ReadBytes(buffer, offset + 4 + hashAlgorithmCount * 2, saltLength); + Salt = ByteReader.ReadBytes(Data, 4 + hashAlgorithmCount * 2, saltLength); } - public byte[] GetBytes() + public override void WriteData() { - int length = 4 + HashAlgorithms.Count * 2 + Salt.Length; - - byte[] buffer = new byte[length]; - LittleEndianWriter.WriteUInt16(buffer, 0, (ushort)HashAlgorithms.Count); - LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)Salt.Length); + Data = new byte[DataLength]; + LittleEndianWriter.WriteUInt16(Data, 0, (ushort)HashAlgorithms.Count); + LittleEndianWriter.WriteUInt16(Data, 2, (ushort)Salt.Length); for (int index = 0; index < HashAlgorithms.Count; index++) { - LittleEndianWriter.WriteUInt16(buffer, 4 + index * 2, (ushort)HashAlgorithms[index]); + LittleEndianWriter.WriteUInt16(Data, 4 + index * 2, (ushort)HashAlgorithms[index]); } - ByteWriter.WriteBytes(buffer, 4 + HashAlgorithms.Count * 2, Salt); + ByteWriter.WriteBytes(Data, 4 + HashAlgorithms.Count * 2, Salt); + } - return buffer; + public override int DataLength + { + get + { + return 4 + HashAlgorithms.Count * 2 + Salt.Length; + } } + + public override NegotiateContextType ContextType => NegotiateContextType.SMB2_PREAUTH_INTEGRITY_CAPABILITIES; } } From 351093f78ed480f1adddadf347b56abe4c8d5196 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 14:05:27 +0300 Subject: [PATCH 127/247] SMB2: Updated NegotiateContextType enum --- SMBLibrary/SMB2/Enums/Negotiate/NegotiateContextType.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SMBLibrary/SMB2/Enums/Negotiate/NegotiateContextType.cs b/SMBLibrary/SMB2/Enums/Negotiate/NegotiateContextType.cs index f71cbde7..913b9ca8 100644 --- a/SMBLibrary/SMB2/Enums/Negotiate/NegotiateContextType.cs +++ b/SMBLibrary/SMB2/Enums/Negotiate/NegotiateContextType.cs @@ -5,5 +5,10 @@ public enum NegotiateContextType : ushort { SMB2_PREAUTH_INTEGRITY_CAPABILITIES = 0x0001, SMB2_ENCRYPTION_CAPABILITIES = 0x0002, + SMB2_COMPRESSION_CAPABILITIES = 0x0003, + SMB2_NETNAME_NEGOTIATE_CONTEXT_ID = 0x0005, + SMB2_TRANSPORT_CAPABILITIES = 0x0006, + SMB2_RDMA_TRANSFORM_CAPABILITIES = 0x0007, + SMB2_SIGNING_CAPABILITIES = 0x0008, } } From d64a5ff053bea16778e214515601a30f5f04ff55 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 3 Aug 2024 14:05:49 +0300 Subject: [PATCH 128/247] SMBLibrary 1.5.3.4 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index cbca3e79..53145f10 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.3.3 + 1.5.3.4 1573;1591 SMBLibrary false From 3c7588b5751ea26ede7b1687a17ad102b94c1cff Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 31 Aug 2024 12:27:10 +0300 Subject: [PATCH 129/247] ACE: Removed unused using statements --- .../NTFileStore/Structures/SecurityInformation/ACE/ACE.cs | 2 -- .../Structures/SecurityInformation/ACE/AccessAllowedACE.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs index b6707258..8f85234a 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs @@ -5,8 +5,6 @@ * 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 diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs index 0b934210..5f1d4459 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs @@ -4,8 +4,6 @@ * 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 From 3f054348d8dfa23b7b308528c53905667b4ce32a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 31 Aug 2024 12:27:59 +0300 Subject: [PATCH 130/247] ACE: Moved ACE Header to the ACE abstract class --- .../NTFileStore/Structures/SecurityInformation/ACE/ACE.cs | 4 +++- .../Structures/SecurityInformation/ACE/AccessAllowedACE.cs | 3 +-- .../Structures/SecurityInformation/ACE/AccessDeniedACE.cs | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs index 8f85234a..49866f80 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/ACE.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -14,6 +14,8 @@ namespace SMBLibrary /// public abstract class ACE { + public AceHeader Header; + public abstract void WriteBytes(byte[] buffer, ref int offset); public abstract int Length diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs index 5f1d4459..f4e77125 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessAllowedACE.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -15,7 +15,6 @@ public class AccessAllowedACE : ACE { public const int FixedLength = 8; - public AceHeader Header; public AccessMask Mask; public SID Sid; diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs index bcaa0e2b..9a82a9e3 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACE/AccessDeniedACE.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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, @@ -12,7 +12,6 @@ public class AccessDeniedACE : ACE { public const int FixedLength = 8; - public AceHeader Header; public AccessMask Mask; public SID Sid; From a64dc6c88be41eb0421768ef3c60a22718b77081 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 31 Aug 2024 12:30:11 +0300 Subject: [PATCH 131/247] ACL: Bugfix: Use AceSize when parsing ACL --- SMBLibrary/NTFileStore/Structures/SecurityInformation/ACL.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACL.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACL.cs index f7b71cb1..712f88a2 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACL.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/ACL.cs @@ -1,10 +1,9 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2024 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; @@ -41,7 +40,7 @@ public ACL(byte[] buffer, int offset) { ACE ace = ACE.GetAce(buffer, offset); this.Add(ace); - offset += ace.Length; + offset += ace.Header.AceSize; } } From 5de9bc122ce2a863ecd129dd596fb3cb2e74c50c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 31 Aug 2024 12:33:05 +0300 Subject: [PATCH 132/247] Tests: Added QueryInfoResponseParsingTests --- .../SMB2/QueryInfoResponseParsingTests.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 SMBLibrary.Tests/SMB2/QueryInfoResponseParsingTests.cs diff --git a/SMBLibrary.Tests/SMB2/QueryInfoResponseParsingTests.cs b/SMBLibrary.Tests/SMB2/QueryInfoResponseParsingTests.cs new file mode 100644 index 00000000..12160a5d --- /dev/null +++ b/SMBLibrary.Tests/SMB2/QueryInfoResponseParsingTests.cs @@ -0,0 +1,45 @@ +/* Copyright (C) 2024 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.SMB2; + +namespace SMBLibrary.Tests.SMB2 +{ + [TestClass] + public class QueryInfoResponseParsingTests + { + [TestMethod] + public void ParseQueryInfoResponseWithSecurityInfo() + { + byte[] queryInfoResponseCommandBytes = GetQueryInfoResponseWithSecurityInfo(); + QueryInfoResponse queryInfoResponse = new QueryInfoResponse(queryInfoResponseCommandBytes, 0); + SecurityDescriptor securityDescriptor = queryInfoResponse.GetSecurityInformation(); + Assert.AreEqual(2, securityDescriptor.Dacl.Count); + Assert.IsInstanceOfType(securityDescriptor.Dacl[1], typeof(AccessAllowedACE)); + Assert.AreEqual(1, ((AccessAllowedACE)securityDescriptor.Dacl[1]).Sid.Revision); + } + + + private static byte[] GetQueryInfoResponseWithSecurityInfo() + { + return new byte[] + { + 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x82, 0xe7, 0x2d, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x09, 0x00, 0x48, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x04, 0x80, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x05, 0x20, 0x00, 0x00, 0x00, 0x20, 0x02, 0x00, 0x00, 0x02, 0x00, 0x38, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xff, 0x01, 0x1f, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x18, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + } + } +} From 8b795d523349eb189877ad6e4a00950192d78d13 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 31 Aug 2024 12:34:06 +0300 Subject: [PATCH 133/247] SMBLibrary 1.5.3.5 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 53145f10..b40df0c1 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.3.4 + 1.5.3.5 1573;1591 SMBLibrary false From ab876da50f822ec3483e00ae5c166f40bbd6feae Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 25 Sep 2024 06:39:50 +0300 Subject: [PATCH 134/247] Client: Added support for single-stage and triple-stage session setup Applicable when working with custom IAuthenticationClient --- SMBLibrary/Client/SMB1Client.cs | 54 +++++++++++----------- SMBLibrary/Client/SMB2Client.cs | 79 ++++++++++++++++----------------- 2 files changed, 65 insertions(+), 68 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index b23ef96a..438a2b76 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -316,39 +316,39 @@ public NTStatus Login(string domainName, string userName, string password, Authe TrySendMessage(request); SMB1Message reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX); - if (reply != null) + while (reply != null && reply.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && reply.Commands[0] is SessionSetupAndXResponseExtended) { - if (reply.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && reply.Commands[0] is SessionSetupAndXResponseExtended) + SessionSetupAndXResponseExtended response = (SessionSetupAndXResponseExtended)reply.Commands[0]; + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(response.SecurityBlob); + if (authenticateMessage == null) { - SessionSetupAndXResponseExtended response = (SessionSetupAndXResponseExtended)reply.Commands[0]; - byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(response.SecurityBlob); - if (authenticateMessage == null) - { - return NTStatus.SEC_E_INVALID_TOKEN; - } - m_sessionKey = authenticationClient.GetSessionKey(); - - 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; - } + return NTStatus.SEC_E_INVALID_TOKEN; } - else + + 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 && reply.Commands[0] is SessionSetupAndXResponseExtended) + { + m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS); + if (m_isLoggedIn) { - return reply.Header.Status; + m_sessionKey = authenticationClient.GetSessionKey(); } + return reply.Header.Status; + } + else + { + return NTStatus.STATUS_INVALID_SMB; } - return NTStatus.STATUS_INVALID_SMB; } } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index fc6f37ad..df4e5738 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -257,56 +257,53 @@ public NTStatus Login(IAuthenticationClient authenticationClient) request.SecurityBuffer = negotiateMessage; TrySendCommand(request); SMB2Command response = WaitForCommand(request.MessageID); - if (response != null) + while (response is SessionSetupResponse && response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED) + { + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(((SessionSetupResponse)response).SecurityBuffer); + if (authenticateMessage == null) + { + return NTStatus.SEC_E_INVALID_TOKEN; + } + + m_sessionID = response.Header.SessionID; + request = new SessionSetupRequest(); + request.SecurityMode = SecurityMode.SigningEnabled; + request.SecurityBuffer = authenticateMessage; + TrySendCommand(request); + response = WaitForCommand(request.MessageID); + } + + if (response is SessionSetupResponse) { - if (response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED && response is SessionSetupResponse) + m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS); + if (m_isLoggedIn) { - byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(((SessionSetupResponse)response).SecurityBuffer); - if (authenticateMessage == null) + m_sessionKey = authenticationClient.GetSessionKey(); + SessionFlags sessionFlags = ((SessionSetupResponse)response).SessionFlags; + if ((sessionFlags & SessionFlags.IsGuest) > 0) { - return NTStatus.SEC_E_INVALID_TOKEN; + // [MS-SMB2] 3.2.5.3.1 If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the SMB2 + // SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired MUST be set to FALSE. + m_signingRequired = false; } - m_sessionKey = authenticationClient.GetSessionKey(); - - m_sessionID = response.Header.SessionID; - request = new SessionSetupRequest(); - request.SecurityMode = SecurityMode.SigningEnabled; - request.SecurityBuffer = authenticateMessage; - TrySendCommand(request); - response = WaitForCommand(request.MessageID); - if (response != null) + else { - m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS); - if (m_isLoggedIn) - { - SessionFlags sessionFlags = ((SessionSetupResponse)response).SessionFlags; - if ((sessionFlags & SessionFlags.IsGuest) > 0) - { - // [MS-SMB2] 3.2.5.3.1 If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the SMB2 - // SESSION_SETUP Response and if RequireMessageSigning is FALSE, Session.SigningRequired MUST be set to FALSE. - m_signingRequired = false; - } - else - { - m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); - } + m_signingKey = SMB2Cryptography.GenerateSigningKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); + } - if (m_dialect >= SMB2Dialect.SMB300) - { - m_encryptSessionData = (sessionFlags & SessionFlags.EncryptData) > 0; - m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); - m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); - } - } - return response.Header.Status; + if (m_dialect >= SMB2Dialect.SMB300) + { + m_encryptSessionData = (sessionFlags & SessionFlags.EncryptData) > 0; + m_encryptionKey = SMB2Cryptography.GenerateClientEncryptionKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); + m_decryptionKey = SMB2Cryptography.GenerateClientDecryptionKey(m_sessionKey, m_dialect, m_preauthIntegrityHashValue); } } - else - { - return response.Header.Status; - } + return response.Header.Status; + } + else + { + return NTStatus.STATUS_INVALID_SMB; } - return NTStatus.STATUS_INVALID_SMB; } public NTStatus Logoff() From e9b82af31b81f1acb0ad5b1c7b93d126f460779b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 18 Dec 2024 19:12:39 +0200 Subject: [PATCH 135/247] QueryInfoHelper: Correctly handle UnsupportedInformationLevelException and NotImplementedException --- SMBLibrary/Server/SMB2/QueryInfoHelper.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Server/SMB2/QueryInfoHelper.cs b/SMBLibrary/Server/SMB2/QueryInfoHelper.cs index 92b721ae..f62a5ff4 100644 --- a/SMBLibrary/Server/SMB2/QueryInfoHelper.cs +++ b/SMBLibrary/Server/SMB2/QueryInfoHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2019 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2024 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, @@ -36,7 +36,22 @@ internal static SMB2Command GetQueryInfoResponse(QueryInfoRequest request, ISMBS } FileInformation fileInformation; - NTStatus queryStatus = share.FileStore.GetFileInformation(out fileInformation, openFile.Handle, request.FileInformationClass); + NTStatus queryStatus; + try + { + queryStatus = share.FileStore.GetFileInformation(out fileInformation, openFile.Handle, request.FileInformationClass); + } + catch (UnsupportedInformationLevelException) + { + state.LogToServer(Severity.Verbose, $"GetFileInformation on '{share.Name}{openFile.Path}' failed. Information class: {request.FileInformationClass}, NTStatus: STATUS_INVALID_INFO_CLASS. (FileId: {request.FileId.Volatile})"); + return new ErrorResponse(request.CommandName, NTStatus.STATUS_INVALID_INFO_CLASS); + } + catch (NotImplementedException) + { + state.LogToServer(Severity.Verbose, $"GetFileInformation on '{share.Name}{openFile.Path}' failed. Information class: {request.FileInformationClass}, NTStatus: STATUS_NOT_IMPLEMENTED. (FileId: {request.FileId.Volatile})"); + return new ErrorResponse(request.CommandName, NTStatus.STATUS_NOT_IMPLEMENTED); + } + if (queryStatus != NTStatus.STATUS_SUCCESS) { state.LogToServer(Severity.Verbose, "GetFileInformation on '{0}{1}' failed. Information class: {2}, NTStatus: {3}. (FileId: {4})", share.Name, openFile.Path, request.FileInformationClass, queryStatus, request.FileId.Volatile); From ffacfa53fb698382951dd126fa6c6d651988c85d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Jan 2025 23:08:59 +0200 Subject: [PATCH 136/247] SMB2Client: Minor code readability improvement --- SMBLibrary/Client/SMB2Client.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index df4e5738..21603bb7 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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, @@ -257,9 +257,9 @@ public NTStatus Login(IAuthenticationClient authenticationClient) request.SecurityBuffer = negotiateMessage; TrySendCommand(request); SMB2Command response = WaitForCommand(request.MessageID); - while (response is SessionSetupResponse && response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED) + while (response is SessionSetupResponse sessionSetupResponse && response.Header.Status == NTStatus.STATUS_MORE_PROCESSING_REQUIRED) { - byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(((SessionSetupResponse)response).SecurityBuffer); + byte[] authenticateMessage = authenticationClient.InitializeSecurityContext(sessionSetupResponse.SecurityBuffer); if (authenticateMessage == null) { return NTStatus.SEC_E_INVALID_TOKEN; From 143c23c367aca168d301332061f5e95d31dcf7a6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Jan 2025 23:19:48 +0200 Subject: [PATCH 137/247] SMB2Client: Minor code readability improvement --- SMBLibrary/Client/SMB2Client.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 21603bb7..f06070b4 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -273,13 +273,13 @@ public NTStatus Login(IAuthenticationClient authenticationClient) response = WaitForCommand(request.MessageID); } - if (response is SessionSetupResponse) + if (response is SessionSetupResponse finalSessionSetupResponse) { m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS); if (m_isLoggedIn) { m_sessionKey = authenticationClient.GetSessionKey(); - SessionFlags sessionFlags = ((SessionSetupResponse)response).SessionFlags; + SessionFlags sessionFlags = finalSessionSetupResponse.SessionFlags; if ((sessionFlags & SessionFlags.IsGuest) > 0) { // [MS-SMB2] 3.2.5.3.1 If the SMB2_SESSION_FLAG_IS_GUEST bit is set in the SessionFlags field of the SMB2 From 63be8c442fd6a57fa1bf8c3067d5ec05fc3cd1b3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Jan 2025 23:30:10 +0200 Subject: [PATCH 138/247] SMB2Client: Correctly handle single stage login --- SMBLibrary/Client/SMB2Client.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index f06070b4..db0b11f0 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -278,6 +278,7 @@ public NTStatus Login(IAuthenticationClient authenticationClient) m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS); if (m_isLoggedIn) { + m_sessionID = response.Header.SessionID; m_sessionKey = authenticationClient.GetSessionKey(); SessionFlags sessionFlags = finalSessionSetupResponse.SessionFlags; if ((sessionFlags & SessionFlags.IsGuest) > 0) From f5d453f6b7e4161c5005172163167d82ebb75978 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Jan 2025 23:48:08 +0200 Subject: [PATCH 139/247] SMBLibrary 1.5.4 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 4 ++-- SMBLibrary/RevisionHistory.txt | 16 ++++++++++++++++ SMBLibrary/SMBLibrary.csproj | 4 ++-- SMBServer/SMBServer.csproj | 4 ++-- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 0db89659..77aed396 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,11 +3,11 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.3 + 1.5.4 1573;1591 SMBLibrary.Adapters Tal Aloni - Copyright © Tal Aloni 2014-2024 + Copyright © Tal Aloni 2014-2025 FileSystem adapters for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index baa3801d..16fb0227 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,11 +3,11 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.3 + 1.5.4 1573;1591 SMBLibrary.Win32 Tal Aloni - Copyright © Tal Aloni 2014-2024 + Copyright © Tal Aloni 2014-2025 Windows specific extensions for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index b9dfec26..5b5c49d2 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -510,3 +510,19 @@ Revision History: Client: Improve client response time when server disconnects or return invalid data. Client: Fix possible NullReferenceException when disconnection occur during directory enumeration. Client: SMB1FileStore, SMB2FileStore: Return STATUS_IO_TIMEOUT instead of STATUS_INVALID_SMB when server does not reply and there is no protocol violation. + +1.5.4 - Server: SMB2: QueryInfoHelper: Correctly handle UnsupportedInformationLevelException and NotImplementedException + Client: Update IsConnected on EndReceive socket exception. + Client: NTLMAuthenticationClient: Set MechanismListMIC when applicable. + SMB2Client: Correctly handle signing when authenticated as guest. + SMB2Client: Updated encryption and signing logic to support SMB 3.0.2. + SMB2Client: Added support for single-stage and triple-stage session setup. + SMB2: CreateResponse: Bugfix: Write CreateContexts offset and Length. + SMB2: CreateContext: Bugfix: Read and Write name as ANSI instead of UTF16. + SMB2: CreateContext: Bugfix: Correct Name and Data offset write location. + SMB2: CreateContext: Bugfix: Write DataLength. + SMB2: NegotiateContext: Fix parsing bugs. + SMB2: NegotiateRequest: Parse NegotiateContextList (SMB 3.1.1) + SMB2: NegotiateRequest: Do not pad request if NegotiateContextList is empty. + SMB2: NegotiateResponse: Bugfix: NegotiateContext was not read from the correct position for non-zero offset. + ACL: Bugfix: Use AceSize when parsing ACL. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index b40df0c1..c085f5d1 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,12 +3,12 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.3.5 + 1.5.4 1573;1591 SMBLibrary false Tal Aloni - Copyright © Tal Aloni 2014-2024 + Copyright © Tal Aloni 2014-2025 SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index 5678f540..ad0c31a8 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -3,9 +3,9 @@ WinExe net20;net40;netcoreapp3.1 - Copyright © Tal Aloni 2014-2024 + Copyright © Tal Aloni 2014-2025 SMBServer - 1.5.3 + 1.5.4 SMBServer true From 2ef0fb968e6fba1f41afbcfcd089efa3bb1f8111 Mon Sep 17 00:00:00 2001 From: philippkratzer Date: Fri, 14 Feb 2025 10:11:10 +0100 Subject: [PATCH 140/247] Improved documentation to have correct implementation to upload large files (#292) --- ClientExamples.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ClientExamples.md b/ClientExamples.md index ba923efb..ebf5c29b 100644 --- a/ClientExamples.md +++ b/ClientExamples.md @@ -137,7 +137,7 @@ FileStatus fileStatus; status = fileStore.CreateFile(out fileHandle, out fileStatus, remoteFilePath, AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_CREATE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); if (status == NTStatus.STATUS_SUCCESS) { - int writeOffset = 0; + long writeOffset = 0; while (localFileStream.Position < localFileStream.Length) { byte[] buffer = new byte[(int)client.MaxWriteSize]; From 2c4e42d956ea991dfad7fc86dac9d6e6c148d152 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 19 Apr 2025 14:26:04 +0300 Subject: [PATCH 141/247] SMB2Client: Always sign outgoing messages when dialect is 3.1.1 --- SMBLibrary/Client/SMB2Client.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index db0b11f0..7b5a8879 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -217,7 +217,10 @@ private bool NegotiateDialect() if (response != null && response.Header.Status == NTStatus.STATUS_SUCCESS) { m_dialect = response.DialectRevision; - m_signingRequired = (response.SecurityMode & SecurityMode.SigningRequired) > 0; + // [MS-SMB2] 3.3.5.7 If Connection.Dialect is "3.1.1" and Session.IsAnonymous and Session.IsGuest + // are set to FALSE and the request is not signed or not encrypted, then the server MUST disconnect the connection. + m_signingRequired = (response.SecurityMode & SecurityMode.SigningRequired) > 0 || + response.DialectRevision == SMB2Dialect.SMB311; m_maxTransactSize = Math.Min(response.MaxTransactSize, ClientMaxTransactSize); m_maxReadSize = Math.Min(response.MaxReadSize, ClientMaxReadSize); m_maxWriteSize = Math.Min(response.MaxWriteSize, ClientMaxWriteSize); From 1172dfb043a2006a26661a7137d17d22ed6f61a3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 19 Apr 2025 15:20:58 +0300 Subject: [PATCH 142/247] Client: Improved handling of invalid FILETIME --- SMBLibrary/Helpers/FileTimeHelper.cs | 18 ++++++++++++++++-- .../FileBothDirectoryInformation.cs | 11 +++++------ .../QueryDirectory/FileDirectoryInformation.cs | 11 +++++------ .../FileFullDirectoryInformation.cs | 11 +++++------ .../FileIdBothDirectoryInformation.cs | 11 +++++------ .../FileIdFullDirectoryInformation.cs | 11 +++++------ 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/SMBLibrary/Helpers/FileTimeHelper.cs b/SMBLibrary/Helpers/FileTimeHelper.cs index dc0307ae..f609d44a 100644 --- a/SMBLibrary/Helpers/FileTimeHelper.cs +++ b/SMBLibrary/Helpers/FileTimeHelper.cs @@ -1,11 +1,10 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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 @@ -14,6 +13,21 @@ public class FileTimeHelper { public static readonly DateTime MinFileTimeValue = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc); + private static readonly long MaxFileTimeIntegerValue = DateTime.MaxValue.ToFileTimeUtc(); + + public static DateTime ReadFileTimeSafe(byte[] buffer, int offset) + { + long span = LittleEndianConverter.ToInt64(buffer, offset); + if (span >= 0 && span <= MaxFileTimeIntegerValue) + { + return DateTime.FromFileTimeUtc(span); + } + else + { + return DateTime.MaxValue; + } + } + public static DateTime ReadFileTime(byte[] buffer, int offset) { long span = LittleEndianConverter.ToInt64(buffer, offset); diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileBothDirectoryInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileBothDirectoryInformation.cs index 6012be52..51b2a66d 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileBothDirectoryInformation.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileBothDirectoryInformation.cs @@ -1,11 +1,10 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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 @@ -37,10 +36,10 @@ public FileBothDirectoryInformation() public FileBothDirectoryInformation(byte[] buffer, int offset) : base(buffer, offset) { - CreationTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 8)); - LastAccessTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 16)); - LastWriteTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 24)); - ChangeTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 32)); + CreationTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 8); + LastAccessTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 16); + LastWriteTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 24); + ChangeTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 32); EndOfFile = LittleEndianConverter.ToInt64(buffer, offset + 40); AllocationSize = LittleEndianConverter.ToInt64(buffer, offset + 48); FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + 56); diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileDirectoryInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileDirectoryInformation.cs index e5acaf89..8bb18449 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileDirectoryInformation.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileDirectoryInformation.cs @@ -1,11 +1,10 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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 @@ -33,10 +32,10 @@ public FileDirectoryInformation() public FileDirectoryInformation(byte[] buffer, int offset) : base(buffer, offset) { - CreationTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 8)); - LastAccessTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 16)); - LastWriteTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 24)); - ChangeTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 32)); + CreationTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 8); + LastAccessTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 16); + LastWriteTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 24); + ChangeTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 32); EndOfFile = LittleEndianConverter.ToInt64(buffer, offset + 40); AllocationSize = LittleEndianConverter.ToInt64(buffer, offset + 48); FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + 56); diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileFullDirectoryInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileFullDirectoryInformation.cs index d8b01c9e..602f06b2 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileFullDirectoryInformation.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileFullDirectoryInformation.cs @@ -1,11 +1,10 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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 @@ -34,10 +33,10 @@ public FileFullDirectoryInformation() public FileFullDirectoryInformation(byte[] buffer, int offset) : base(buffer, offset) { - CreationTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 8)); - LastAccessTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 16)); - LastWriteTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 24)); - ChangeTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 32)); + CreationTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 8); + LastAccessTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 16); + LastWriteTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 24); + ChangeTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 32); EndOfFile = LittleEndianConverter.ToInt64(buffer, offset + 40); AllocationSize = LittleEndianConverter.ToInt64(buffer, offset + 48); FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + 56); diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdBothDirectoryInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdBothDirectoryInformation.cs index 9dbc1eff..cee51ead 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdBothDirectoryInformation.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdBothDirectoryInformation.cs @@ -1,11 +1,10 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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 @@ -39,10 +38,10 @@ public FileIdBothDirectoryInformation() public FileIdBothDirectoryInformation(byte[] buffer, int offset) : base(buffer, offset) { - CreationTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 8)); - LastAccessTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 16)); - LastWriteTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 24)); - ChangeTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 32)); + CreationTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 8); + LastAccessTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 16); + LastWriteTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 24); + ChangeTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 32); EndOfFile = LittleEndianConverter.ToInt64(buffer, offset + 40); AllocationSize = LittleEndianConverter.ToInt64(buffer, offset + 48); FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + 56); diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdFullDirectoryInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdFullDirectoryInformation.cs index 6edb7f8b..b09e22eb 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdFullDirectoryInformation.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/QueryDirectory/FileIdFullDirectoryInformation.cs @@ -1,11 +1,10 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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 @@ -36,10 +35,10 @@ public FileIdFullDirectoryInformation() public FileIdFullDirectoryInformation(byte[] buffer, int offset) : base(buffer, offset) { - CreationTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 8)); - LastAccessTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 16)); - LastWriteTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 24)); - ChangeTime = DateTime.FromFileTimeUtc(LittleEndianConverter.ToInt64(buffer, offset + 32)); + CreationTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 8); + LastAccessTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 16); + LastWriteTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 24); + ChangeTime = FileTimeHelper.ReadFileTimeSafe(buffer, offset + 32); EndOfFile = LittleEndianConverter.ToInt64(buffer, offset + 40); AllocationSize = LittleEndianConverter.ToInt64(buffer, offset + 48); FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + 56); From d40699d6dd04d27831fb5e57053f7c017040d7c8 Mon Sep 17 00:00:00 2001 From: tanlng Date: Wed, 23 Apr 2025 23:15:16 +0800 Subject: [PATCH 143/247] Implement FileAttributeTagInformation support for SMB file operations (#296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 胡正 --- .../FileInformation/FileInformation.cs | 2 +- .../Query/FileAttributeTagInformation.cs | 55 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 SMBLibrary/NTFileStore/Structures/FileInformation/Query/FileAttributeTagInformation.cs diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/FileInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/FileInformation.cs index b0b3843d..cc46f0a3 100644 --- a/SMBLibrary/NTFileStore/Structures/FileInformation/FileInformation.cs +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/FileInformation.cs @@ -82,7 +82,7 @@ public static FileInformation GetFileInformation(byte[] buffer, int offset, File case FileInformationClass.FileNetworkOpenInformation: return new FileNetworkOpenInformation(buffer, offset); case FileInformationClass.FileAttributeTagInformation: - throw new NotImplementedException(); + return new FileAttributeTagInformation(buffer, offset); case FileInformationClass.FileValidDataLengthInformation: return new FileValidDataLengthInformation(buffer, offset); case FileInformationClass.FileShortNameInformation: diff --git a/SMBLibrary/NTFileStore/Structures/FileInformation/Query/FileAttributeTagInformation.cs b/SMBLibrary/NTFileStore/Structures/FileInformation/Query/FileAttributeTagInformation.cs new file mode 100644 index 00000000..bcd1fba3 --- /dev/null +++ b/SMBLibrary/NTFileStore/Structures/FileInformation/Query/FileAttributeTagInformation.cs @@ -0,0 +1,55 @@ +/* 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 +{ + /// + /// [MS-FSCC] 2.4.6 - FileAttributeTagInformation + /// + public class FileAttributeTagInformation : FileInformation + { + public const int FixedLength = 8; + + public FileAttributes FileAttributes; + public uint ReparsePointTag; + + public FileAttributeTagInformation() + { + } + + public FileAttributeTagInformation(byte[] buffer, int offset) + { + FileAttributes = (FileAttributes)LittleEndianConverter.ToUInt32(buffer, offset + 0); + ReparsePointTag = LittleEndianConverter.ToUInt32(buffer, offset + 4); + } + + public override void WriteBytes(byte[] buffer, int offset) + { + LittleEndianWriter.WriteUInt32(buffer, offset + 0, (uint)FileAttributes); + LittleEndianWriter.WriteUInt32(buffer, offset + 4, ReparsePointTag); + } + + public override FileInformationClass FileInformationClass + { + get + { + return FileInformationClass.FileAttributeTagInformation; + } + } + + public override int Length + { + get + { + return FixedLength; + } + } + } +} From 351f23ead9e9c5bf592a305346993ed2ea84d1ed Mon Sep 17 00:00:00 2001 From: tanlng Date: Fri, 25 Apr 2025 16:43:33 +0800 Subject: [PATCH 144/247] Fix: Incorrect CommandLength calculation in NegotiateResponse (#298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SecurityBufferLength was used in the CommandLength calculation of NegotiateResponse, but it hadn't been assigned a value when creating the buffer length. Instead, SecurityBuffer.Length should be directly used. This change ensures the accurate calculation of the CommandLength and resolves the related issues. Co-authored-by: 胡正 --- SMBLibrary/SMB2/Commands/NegotiateResponse.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMB2/Commands/NegotiateResponse.cs b/SMBLibrary/SMB2/Commands/NegotiateResponse.cs index d3a2e09a..3b0bb769 100644 --- a/SMBLibrary/SMB2/Commands/NegotiateResponse.cs +++ b/SMBLibrary/SMB2/Commands/NegotiateResponse.cs @@ -105,7 +105,7 @@ public override int CommandLength } else { - int paddedSecurityBufferLength = (int)Math.Ceiling((double)SecurityBufferLength / 8) * 8; + int paddedSecurityBufferLength = (int)Math.Ceiling((double)SecurityBuffer.Length / 8) * 8; return FixedSize + paddedSecurityBufferLength + NegotiateContext.GetNegotiateContextListLength(NegotiateContextList); } } From 21313ac0303619e32509a7b645516e1d32fa9d8d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 1 May 2025 17:40:15 +0300 Subject: [PATCH 145/247] Utilities: BlockingQueue: Bugfix: TryDequeue would block infinitely if called after Stop was invoked --- Utilities/Generics/BlockingQueue.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Utilities/Generics/BlockingQueue.cs b/Utilities/Generics/BlockingQueue.cs index 93291145..8007d8df 100644 --- a/Utilities/Generics/BlockingQueue.cs +++ b/Utilities/Generics/BlockingQueue.cs @@ -1,13 +1,11 @@ -/* Copyright (C) 2016-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2016-2025 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.Threading; -using System.Text; namespace Utilities { @@ -57,7 +55,11 @@ public bool TryDequeue(out T item) { while (m_queue.Count == 0) { - Monitor.Wait(m_queue); + if (!m_stopping) + { + Monitor.Wait(m_queue); + } + if (m_stopping) { item = default(T); From a9ec1878d0b49360d4c4d9419d364afdc61b5ac9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 1 May 2025 21:37:48 +0300 Subject: [PATCH 146/247] Utilities: BlockingQueue.TryDequeue: Change behavior to return all items enqueued before Stop was invoked --- Utilities/Generics/BlockingQueue.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Utilities/Generics/BlockingQueue.cs b/Utilities/Generics/BlockingQueue.cs index 8007d8df..fffd1e4f 100644 --- a/Utilities/Generics/BlockingQueue.cs +++ b/Utilities/Generics/BlockingQueue.cs @@ -17,6 +17,11 @@ public class BlockingQueue public void Enqueue(T item) { + if (m_stopping) + { + return; + } + lock (m_queue) { m_queue.Enqueue(item); @@ -30,10 +35,11 @@ public void Enqueue(T item) public void Enqueue(List items) { - if (items.Count == 0) + if (m_stopping || items.Count == 0) { return; } + lock (m_queue) { foreach (T item in items) @@ -60,7 +66,7 @@ public bool TryDequeue(out T item) Monitor.Wait(m_queue); } - if (m_stopping) + if (m_stopping && m_queue.Count == 0) { item = default(T); return false; @@ -82,6 +88,15 @@ public void Stop() } } + public void Abort() + { + lock (m_queue) + { + m_queue.Clear(); + Stop(); + } + } + public int Count { get From ef4508271c4a9089114c9ad1ade2df8b0334b36b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 1 May 2025 21:45:33 +0300 Subject: [PATCH 147/247] ConnectionManager: Code refactoring: Use BlockingQueue.Abort which has replaced Stop for quick termination --- SMBLibrary/Server/ConnectionManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Server/ConnectionManager.cs b/SMBLibrary/Server/ConnectionManager.cs index cef64a1b..131b6f27 100644 --- a/SMBLibrary/Server/ConnectionManager.cs +++ b/SMBLibrary/Server/ConnectionManager.cs @@ -39,7 +39,7 @@ public bool RemoveConnection(ConnectionState connection) public void ReleaseConnection(ConnectionState connection) { - connection.SendQueue.Stop(); + connection.SendQueue.Abort(); SocketUtils.ReleaseSocket(connection.ClientSocket); connection.CloseSessions(); lock (connection.ReceiveBuffer) From d7695a629cc1e2613644e5f4870271aae647c7f7 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 22 Jun 2025 12:45:02 +0300 Subject: [PATCH 148/247] Client: Disconnect: Ensure ongoing reading operation is complete before closing the socket --- SMBLibrary/Client/SMB1Client.cs | 4 ++-- SMBLibrary/Client/SMB2Client.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 438a2b76..26e9ba71 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -170,10 +170,10 @@ public void Disconnect() { if (m_isConnected) { - m_clientSocket.Disconnect(false); - m_clientSocket.Close(); lock (m_connectionState.ReceiveBuffer) { + m_clientSocket.Disconnect(false); + m_clientSocket.Close(); m_connectionState.ReceiveBuffer.Dispose(); } m_isConnected = false; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 7b5a8879..67869612 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -181,10 +181,10 @@ public void Disconnect() { if (m_isConnected) { - m_clientSocket.Disconnect(false); - m_clientSocket.Close(); lock (m_connectionState.ReceiveBuffer) { + m_clientSocket.Disconnect(false); + m_clientSocket.Close(); m_connectionState.ReceiveBuffer.Dispose(); } m_isConnected = false; From 62c255ba6174a66b6aa3951aa224b589ab629194 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 22 Jun 2025 12:49:38 +0300 Subject: [PATCH 149/247] SMBLibrary 1.5.4.1 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index c085f5d1..57c695e4 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.4 + 1.5.4.1 1573;1591 SMBLibrary false From a13d560f9f4f892237035934d1d8419933908e87 Mon Sep 17 00:00:00 2001 From: Khelthal <80304879+Khelthal@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:38:06 -0600 Subject: [PATCH 150/247] Client: Bugfix: UserID was ignored when extended security was set to false (#313) --- SMBLibrary/Client/SMB1Client.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 26e9ba71..7de6246e 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -295,6 +295,10 @@ public NTStatus Login(string domainName, string userName, string password, Authe if (reply != null) { m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS); + if (m_isLoggedIn) + { + m_userID = reply.Header.UID; + } return reply.Header.Status; } return NTStatus.STATUS_INVALID_SMB; From ea031dfbb0db6b26df8406a500a46db9032c4133 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 31 Oct 2025 23:53:30 +0200 Subject: [PATCH 151/247] SMB2Client: Correctly report login error status instead of STATUS_INVALID_SMB --- SMBLibrary/Client/SMB2Client.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 67869612..ee2025f8 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -276,7 +276,11 @@ public NTStatus Login(IAuthenticationClient authenticationClient) response = WaitForCommand(request.MessageID); } - if (response is SessionSetupResponse finalSessionSetupResponse) + if (response is ErrorResponse) + { + return response.Header.Status; + } + else if (response is SessionSetupResponse finalSessionSetupResponse) { m_isLoggedIn = (response.Header.Status == NTStatus.STATUS_SUCCESS); if (m_isLoggedIn) From 4befcaec7fd47da387351cf8702c1421f566f4bc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 31 Oct 2025 23:58:47 +0200 Subject: [PATCH 152/247] SMB1Client, SMB2Client: Move responseTimeoutInMilliseconds from Connect method to constructor --- SMBLibrary.Tests/Client/SMB2ClientTests.cs | 10 ++++---- .../IntegrationTests/LoginTests.cs | 10 ++++---- SMBLibrary/Client/SMB1Client.cs | 19 +++++++-------- SMBLibrary/Client/SMB2Client.cs | 24 +++++++------------ 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/SMBLibrary.Tests/Client/SMB2ClientTests.cs b/SMBLibrary.Tests/Client/SMB2ClientTests.cs index 48efb8e6..40e88b3e 100644 --- a/SMBLibrary.Tests/Client/SMB2ClientTests.cs +++ b/SMBLibrary.Tests/Client/SMB2ClientTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2024-2025 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, @@ -48,8 +48,8 @@ public void When_SMB2ClientConnectsAndServerDoesNotReply_ShouldReachTimeout() { m_tcpListener.BeginAcceptTcpClient(AcceptTcpClient_DoNotReply, null); - SMB2Client client = new SMB2Client(); int timeoutInMilliseconds = 1000; + SMB2Client client = new SMB2Client(timeoutInMilliseconds); ManualResetEvent manualResetEvent = new ManualResetEvent(false); Stopwatch stopwatch = new Stopwatch(); @@ -57,7 +57,7 @@ public void When_SMB2ClientConnectsAndServerDoesNotReply_ShouldReachTimeout() new Thread(() => { stopwatch.Start(); - isConnected = client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, timeoutInMilliseconds); + isConnected = client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort); stopwatch.Stop(); }).Start(); @@ -73,8 +73,8 @@ public void When_SMB2ClientConnectsAndServerDoesNotReply_ShouldReachTimeout() public void When_SMB2ClientConnectsAndServerSendNonSmbData_ShouldNotReachTimeout() { m_tcpListener.BeginAcceptTcpClient(AcceptTcpClient_SendNonSmbData, null); - SMB2Client client = new SMB2Client(); int timeoutInMilliseconds = 1000; + SMB2Client client = new SMB2Client(timeoutInMilliseconds); ManualResetEvent manualResetEvent = new ManualResetEvent(false); Stopwatch stopwatch = new Stopwatch(); @@ -82,7 +82,7 @@ public void When_SMB2ClientConnectsAndServerSendNonSmbData_ShouldNotReachTimeout new Thread(() => { stopwatch.Start(); - isConnected = client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, timeoutInMilliseconds); + isConnected = client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort); stopwatch.Stop(); }).Start(); diff --git a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs index a77ea063..95ff49a2 100644 --- a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs +++ b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2024-2025 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, @@ -42,7 +42,7 @@ public void When_ValidCredentialsProvided_LoginSucceed() { // Arrange SMB2Client client = new SMB2Client(); - client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort); // Act NTStatus status = client.Login("", "John", "password"); @@ -56,7 +56,7 @@ public void When_ClientDisconnectAndReconnect_LoginSucceed() { // Arrange SMB2Client client = new SMB2Client(); - client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort); // Act NTStatus status = client.Login("", "John", "password"); @@ -64,7 +64,7 @@ public void When_ClientDisconnectAndReconnect_LoginSucceed() status = client.Logoff(); Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); client.Disconnect(); - client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort); status = client.Login("", "John", "password"); // Assert @@ -76,7 +76,7 @@ public void When_InvalidCredentialsProvided_LoginFails() { // Arrange SMB2Client client = new SMB2Client(); - client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort, 5000); + client.Connect(IPAddress.Loopback, SMBTransportType.DirectTCPTransport, m_serverPort); // Act NTStatus status = client.Login("", "John", "Password"); diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 7de6246e..5cceac11 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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, @@ -57,10 +57,15 @@ public class SMB1Client : ISMBClient private byte[] m_securityBlob; private byte[] m_sessionKey; - public SMB1Client() + public SMB1Client() : this(DefaultResponseTimeoutInMilliseconds) { } + public SMB1Client(int responseTimeoutInMilliseconds) + { + m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; + } + public bool Connect(string serverName, SMBTransportType transport) { IPAddress[] hostAddresses = Dns.GetHostAddresses(serverName); @@ -78,23 +83,17 @@ public bool Connect(IPAddress serverAddress, SMBTransportType transport) } public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity) - { - return Connect(serverAddress, transport, forceExtendedSecurity, DefaultResponseTimeoutInMilliseconds); - } - - public bool Connect(IPAddress serverAddress, SMBTransportType transport, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) { int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); - return Connect(serverAddress, transport, port, forceExtendedSecurity, responseTimeoutInMilliseconds); + return Connect(serverAddress, transport, port, forceExtendedSecurity); } - protected internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity, int responseTimeoutInMilliseconds) + protected internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, bool forceExtendedSecurity) { m_transport = transport; if (!m_isConnected) { m_forceExtendedSecurity = forceExtendedSecurity; - m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; if (!ConnectSocket(serverAddress, port)) { return false; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index ee2025f8..004cacc7 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -59,20 +59,20 @@ public class SMB2Client : ISMBClient private byte[] m_preauthIntegrityHashValue; // SMB 3.1.1 private ushort m_availableCredits = 1; - public SMB2Client() + public SMB2Client() : this(DefaultResponseTimeoutInMilliseconds) { } + public SMB2Client(int responseTimeoutInMilliseconds) + { + m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; + } + /// /// When a Windows Server host is using Failover Cluster and Cluster Shared Volumes, each of those CSV file shares is associated /// with a specific host name associated with the cluster and is not accessible using the node IP address or node host name. /// public bool Connect(string serverName, SMBTransportType transport) - { - return Connect(serverName, transport, DefaultResponseTimeoutInMilliseconds); - } - - public bool Connect(string serverName, SMBTransportType transport, int responseTimeoutInMilliseconds) { m_serverName = serverName; IPAddress[] hostAddresses = Dns.GetHostAddresses(serverName); @@ -81,21 +81,16 @@ public bool Connect(string serverName, SMBTransportType transport, int responseT throw new Exception(String.Format("Cannot resolve host name {0} to an IP address", serverName)); } IPAddress serverAddress = IPAddressHelper.SelectAddressPreferIPv4(hostAddresses); - return Connect(serverAddress, transport, responseTimeoutInMilliseconds); + return Connect(serverAddress, transport); } public bool Connect(IPAddress serverAddress, SMBTransportType transport) - { - return Connect(serverAddress, transport, DefaultResponseTimeoutInMilliseconds); - } - - public bool Connect(IPAddress serverAddress, SMBTransportType transport, int responseTimeoutInMilliseconds) { int port = (transport == SMBTransportType.DirectTCPTransport ? DirectTCPPort : NetBiosOverTCPPort); - return Connect(serverAddress, transport, port, responseTimeoutInMilliseconds); + return Connect(serverAddress, transport, port); } - protected internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port, int responseTimeoutInMilliseconds) + protected internal bool Connect(IPAddress serverAddress, SMBTransportType transport, int port) { if (m_serverName == null) { @@ -105,7 +100,6 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp m_transport = transport; if (!m_isConnected) { - m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; if (!ConnectSocket(serverAddress, port)) { return false; From f27ee13197886cd7bfab974c4b0942712e4448e3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 00:02:42 +0200 Subject: [PATCH 153/247] SMB2Client: Enabled SMB 3.0.2 support by default --- SMBLibrary/Client/SMB2Client.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 004cacc7..93fd1d24 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -198,9 +198,7 @@ private bool NegotiateDialect() request.Dialects.Add(SMB2Dialect.SMB202); request.Dialects.Add(SMB2Dialect.SMB210); request.Dialects.Add(SMB2Dialect.SMB300); -#if SMB302_CLIENT request.Dialects.Add(SMB2Dialect.SMB302); -#endif #if SMB311_CLIENT request.Dialects.Add(SMB2Dialect.SMB311); request.NegotiateContextList = GetNegotiateContextList(); From 82df8f129808719030bda891c2dd6cb2c5a0ea1d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 00:09:10 +0200 Subject: [PATCH 154/247] SMB2Client: Added constructor argument to enable SMB 3.1.1 --- SMBLibrary/Client/SMB2Client.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 93fd1d24..b718ea7c 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -35,6 +35,7 @@ public class SMB2Client : ISMBClient private Socket m_clientSocket; private ConnectionState m_connectionState; private int m_responseTimeoutInMilliseconds; + private bool m_enableSMB311Support = false; private object m_incomingQueueLock = new object(); private List m_incomingQueue = new List(); @@ -63,9 +64,14 @@ public SMB2Client() : this(DefaultResponseTimeoutInMilliseconds) { } - public SMB2Client(int responseTimeoutInMilliseconds) + public SMB2Client(int responseTimeoutInMilliseconds) : this(responseTimeoutInMilliseconds, false) + { + } + + public SMB2Client(int responseTimeoutInMilliseconds, bool enableSMB311Support) { m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; + m_enableSMB311Support = enableSMB311Support; } /// @@ -199,11 +205,13 @@ private bool NegotiateDialect() request.Dialects.Add(SMB2Dialect.SMB210); request.Dialects.Add(SMB2Dialect.SMB300); request.Dialects.Add(SMB2Dialect.SMB302); -#if SMB311_CLIENT - request.Dialects.Add(SMB2Dialect.SMB311); - request.NegotiateContextList = GetNegotiateContextList(); - m_preauthIntegrityHashValue = new byte[64]; -#endif + if (m_enableSMB311Support) + { + request.Dialects.Add(SMB2Dialect.SMB311); + request.NegotiateContextList = GetNegotiateContextList(); + m_preauthIntegrityHashValue = new byte[64]; + } + TrySendCommand(request); NegotiateResponse response = WaitForCommand(request.MessageID) as NegotiateResponse; if (response != null && response.Header.Status == NTStatus.STATUS_SUCCESS) From 950b84bc785f4e0f7f59020c04f1defc2384f00d Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 00:31:55 +0200 Subject: [PATCH 155/247] LoginTests: Fix same port being used concurrently causing test to fail --- SMBLibrary.Tests/IntegrationTests/LoginTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs index 95ff49a2..6ea1c7f3 100644 --- a/SMBLibrary.Tests/IntegrationTests/LoginTests.cs +++ b/SMBLibrary.Tests/IntegrationTests/LoginTests.cs @@ -17,13 +17,15 @@ namespace SMBLibrary.Tests.IntegrationTests [TestClass] public class LoginTests { + private static Random s_seedGenerator = new Random(); + private int m_serverPort; private SMBServer m_server; [TestInitialize] public void Initialize() { - m_serverPort = 1000 + new Random().Next(50000); + m_serverPort = 1000 + new Random(s_seedGenerator.Next()).Next(50000); SMBShareCollection shares = new SMBShareCollection(); IGSSMechanism gssMechanism = new IndependentNTLMAuthenticationProvider((username) => "password"); GSSProvider gssProvider = new GSSProvider(gssMechanism); From 6b47aaa8d3628e917ad6246f47fb0d013f970f3f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 07:46:52 +0200 Subject: [PATCH 156/247] SMB2: Added Notifications to Capabilities enum and improved documentation --- SMBLibrary/SMB2/Enums/Negotiate/Capabilities.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/SMB2/Enums/Negotiate/Capabilities.cs b/SMBLibrary/SMB2/Enums/Negotiate/Capabilities.cs index e7b6c66c..630b2b63 100644 --- a/SMBLibrary/SMB2/Enums/Negotiate/Capabilities.cs +++ b/SMBLibrary/SMB2/Enums/Negotiate/Capabilities.cs @@ -6,11 +6,12 @@ namespace SMBLibrary.SMB2 public enum Capabilities : uint { DFS = 0x00000001, // SMB2_GLOBAL_CAP_DFS - Leasing = 0x00000002, // SMB2_GLOBAL_CAP_LEASING - LargeMTU = 0x0000004, // SMB2_GLOBAL_CAP_LARGE_MTU - MultiChannel = 0x0000008, // SMB2_GLOBAL_CAP_MULTI_CHANNEL - PersistentHandles = 0x00000010, // SMB2_GLOBAL_CAP_PERSISTENT_HANDLES - DirectoryLeasing = 0x00000020, // SMB2_GLOBAL_CAP_DIRECTORY_LEASING - Encryption = 0x00000040, // SMB2_GLOBAL_CAP_ENCRYPTION (SMB 3.x) + Leasing = 0x00000002, // SMB2_GLOBAL_CAP_LEASING (SMB 2.1+) + LargeMTU = 0x0000004, // SMB2_GLOBAL_CAP_LARGE_MTU (SMB 2.1+) + MultiChannel = 0x0000008, // SMB2_GLOBAL_CAP_MULTI_CHANNEL (SMB 3.0+) + PersistentHandles = 0x00000010, // SMB2_GLOBAL_CAP_PERSISTENT_HANDLES (SMB 3.0+) + DirectoryLeasing = 0x00000020, // SMB2_GLOBAL_CAP_DIRECTORY_LEASING (SMB 3.0+) + Encryption = 0x00000040, // SMB2_GLOBAL_CAP_ENCRYPTION (SMB 3.0 / SMB 3.0.2) + Notifications = 0x00000080, // SMB2_GLOBAL_CAP_NOTIFICATIONS (SMB 3.1.1) } } From 07e8345c66db31b5fc9948089c3fc91783bb8e9f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 09:09:55 +0200 Subject: [PATCH 157/247] SMB2Client: Minor code refactoring --- SMBLibrary/Client/SMB2Client.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index b718ea7c..d56efc2c 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -508,9 +508,8 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) m_availableCredits += command.Header.Credits; - if (m_transport == SMBTransportType.DirectTCPTransport && command is NegotiateResponse) + if (m_transport == SMBTransportType.DirectTCPTransport && command is NegotiateResponse negotiateResponse) { - NegotiateResponse negotiateResponse = (NegotiateResponse)command; if ((negotiateResponse.Capabilities & Capabilities.LargeMTU) > 0) { // [MS-SMB2] 3.2.5.1 Receiving Any Message - If the message size received exceeds Connection.MaxTransactSize, the client SHOULD disconnect the connection. From c059c3d45255b5766e53b2f2342990566a12eac8 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 09:30:40 +0200 Subject: [PATCH 158/247] SMB2Client: Throw exception if attempting to read or write more data than allowed for the connection --- SMBLibrary/Client/SMB2Client.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index d56efc2c..66423361 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -59,6 +59,7 @@ public class SMB2Client : ISMBClient private byte[] m_sessionKey; private byte[] m_preauthIntegrityHashValue; // SMB 3.1.1 private ushort m_availableCredits = 1; + private bool m_connectionSupportsMultiCredit = false; public SMB2Client() : this(DefaultResponseTimeoutInMilliseconds) { @@ -191,6 +192,7 @@ public void Disconnect() m_messageID = 0; m_sessionID = 0; m_availableCredits = 1; + m_connectionSupportsMultiCredit = false; } } @@ -510,7 +512,8 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) if (m_transport == SMBTransportType.DirectTCPTransport && command is NegotiateResponse negotiateResponse) { - if ((negotiateResponse.Capabilities & Capabilities.LargeMTU) > 0) + m_connectionSupportsMultiCredit = (negotiateResponse.Capabilities & Capabilities.LargeMTU) > 0; + if (m_connectionSupportsMultiCredit) { // [MS-SMB2] 3.2.5.1 Receiving Any Message - If the message size received exceeds Connection.MaxTransactSize, the client SHOULD disconnect the connection. // Note: Windows clients do not enforce the MaxTransactSize value. @@ -621,6 +624,11 @@ internal void TrySendCommand(SMB2Command request) internal void TrySendCommand(SMB2Command request, bool encryptData) { + if (!m_connectionSupportsMultiCredit && request.Header.CreditCharge > 1) + { + throw new Exception("Attempted to read or write more data than allowed for this connection"); + } + if (m_dialect == SMB2Dialect.SMB202 || m_transport == SMBTransportType.NetBiosOverTCP) { request.Header.CreditCharge = 0; From 128a3b92d7229fd0dd5e5378e8dfb3105ea12d9b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 09:44:50 +0200 Subject: [PATCH 159/247] SMB2Client: Ignore request.Header.CreditCharge when computing next messageID if large MTU was not negotiated --- SMBLibrary/Client/SMB2Client.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 66423361..1d8499a7 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -672,7 +672,7 @@ internal void TrySendCommand(SMB2Command request, bool encryptData) } } TrySendCommand(m_clientSocket, request, encryptData ? m_encryptionKey : null); - if (m_dialect == SMB2Dialect.SMB202 || m_transport == SMBTransportType.NetBiosOverTCP) + if (!m_connectionSupportsMultiCredit) { m_messageID++; } From 410cdcefb473e7ef7ca2b7aec667bbdc466bfbdc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 10:02:54 +0200 Subject: [PATCH 160/247] SMB2Client: Correctly handle the uncommon case of a server not supporting large MTU over applicable connection --- SMBLibrary/Client/SMB2Client.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 1d8499a7..3d364c3a 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -629,8 +629,9 @@ internal void TrySendCommand(SMB2Command request, bool encryptData) throw new Exception("Attempted to read or write more data than allowed for this connection"); } - if (m_dialect == SMB2Dialect.SMB202 || m_transport == SMBTransportType.NetBiosOverTCP) + if (!m_connectionSupportsMultiCredit) { + // [MS-SMB2] 3.2.4.1.5 If [..] Connection.SupportsMultiCredit is FALSE, CreditCharge SHOULD be set to 0. request.Header.CreditCharge = 0; request.Header.Credits = 1; m_availableCredits -= 1; @@ -639,6 +640,9 @@ internal void TrySendCommand(SMB2Command request, bool encryptData) { if (request.Header.CreditCharge == 0) { + // [MS-SMB2] 3.2.4.1.5 If Connection.SupportsMultiCredit is TRUE: + // For READ, WRITE, IOCTL, and QUERY_DIRECTORY requests, CreditCharge field in the SMB2 header SHOULD be set to [..] the value computed. + // For all other requests, the client MUST set CreditCharge to 1. request.Header.CreditCharge = 1; } From a4c0c0e25ad85d4359413d5a1fb2f186f9a60026 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 13:26:03 +0200 Subject: [PATCH 161/247] Client: Added virtual GetNetBiosServerName method --- SMBLibrary/Client/SMB1Client.cs | 9 +++++++-- SMBLibrary/Client/SMB2Client.cs | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 5cceac11..011d168f 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -115,8 +115,7 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp return false; } - NameServiceClient nameServiceClient = new NameServiceClient(serverAddress); - string serverName = nameServiceClient.GetServerName(); + string serverName = GetNetBiosServerName(serverAddress); if (serverName == null) { return false; @@ -146,6 +145,12 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp return m_isConnected; } + protected virtual string GetNetBiosServerName(IPAddress serverAddress) + { + NameServiceClient nameServiceClient = new NameServiceClient(serverAddress); + return nameServiceClient.GetServerName(); + } + private bool ConnectSocket(IPAddress serverAddress, int port) { m_clientSocket = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 3d364c3a..a9631a79 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -128,8 +128,7 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp return false; } - NameServiceClient nameServiceClient = new NameServiceClient(serverAddress); - string serverName = nameServiceClient.GetServerName(); + string serverName = GetNetBiosServerName(serverAddress); if (serverName == null) { return false; @@ -159,6 +158,12 @@ protected internal bool Connect(IPAddress serverAddress, SMBTransportType transp return m_isConnected; } + protected virtual string GetNetBiosServerName(IPAddress serverAddress) + { + NameServiceClient nameServiceClient = new NameServiceClient(serverAddress); + return nameServiceClient.GetServerName(); + } + private bool ConnectSocket(IPAddress serverAddress, int port) { m_clientSocket = new Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); From 32a04848c99ba2df7d194a1912143ac5ec92b60e Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 14:15:36 +0200 Subject: [PATCH 162/247] Client: Add Echo command support --- SMBLibrary/Client/ISMBClient.cs | 4 +++- SMBLibrary/Client/SMB1Client.cs | 16 ++++++++++++++++ SMBLibrary/Client/SMB2Client.cs | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/ISMBClient.cs b/SMBLibrary/Client/ISMBClient.cs index 206fd257..52119a42 100644 --- a/SMBLibrary/Client/ISMBClient.cs +++ b/SMBLibrary/Client/ISMBClient.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2022 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2025 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, @@ -28,6 +28,8 @@ public interface ISMBClient ISMBFileStore TreeConnect(string shareName, out NTStatus status); + NTStatus Echo(); + uint MaxReadSize { get; diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 011d168f..b29a3338 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -430,6 +430,22 @@ public SMB1FileStore TreeConnect(string shareName, ServiceName serviceName, out return null; } + public NTStatus Echo() + { + EchoRequest request = new EchoRequest(); + request.EchoCount = 1; + TrySendMessage(request); + SMB1Message reply = WaitForMessage(CommandName.SMB_COM_ECHO); + if (reply != null) + { + return reply.Header.Status; + } + else + { + return NTStatus.STATUS_INVALID_SMB; + } + } + private void OnClientSocketReceive(IAsyncResult ar) { ConnectionState state = (ConnectionState)ar.AsyncState; diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index a9631a79..6cd59823 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -386,6 +386,21 @@ public ISMBFileStore TreeConnect(string shareName, out NTStatus status) return null; } + public NTStatus Echo() + { + EchoRequest request = new EchoRequest(); + TrySendCommand(request); + SMB2Command response = WaitForCommand(request.MessageID); + if (response != null) + { + return response.Header.Status; + } + else + { + return NTStatus.STATUS_INVALID_SMB; + } + } + private void OnClientSocketReceive(IAsyncResult ar) { ConnectionState state = (ConnectionState)ar.AsyncState; From c9067424630611afa9fbfe9edf408c538c19ccad Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 1 Nov 2025 14:31:27 +0200 Subject: [PATCH 163/247] SMBLibrary 1.5.5 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/RevisionHistory.txt | 15 +++++++++++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/SMBServer.csproj | 2 +- 5 files changed, 19 insertions(+), 4 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 77aed396..158a3bc3 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.4 + 1.5.5 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index 16fb0227..d9f5d1de 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.4 + 1.5.5 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 5b5c49d2..ed3a7a24 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -526,3 +526,18 @@ Revision History: SMB2: NegotiateRequest: Do not pad request if NegotiateContextList is empty. SMB2: NegotiateResponse: Bugfix: NegotiateContext was not read from the correct position for non-zero offset. ACL: Bugfix: Use AceSize when parsing ACL. + +1.5.5 - SMB1Client: Fix UserID being ignored when extended security is set to false. + SMB2Client: Correctly report login error status instead of STATUS_INVALID_SMB: Always sign outgoing messages when dialect is 3.1.1 + SMB2Client: Correctly report login error status instead of STATUS_INVALID_SMB + SMB2Client: Enabled SMB 3.0.2 support by default. + SMB2Client: Added constructor argument to enable SMB 3.1.1 + SMB2Client: Throw exception if attempting to read or write more data than allowed for the connection. + SMB2Client: Correctly handle the uncommon case of a server not supporting large MTU over applicable connection. + Client: Improved handling of invalid FILETIME. + Client: Disconnect: Ensure ongoing reading operation is complete before closing the socket. + Client: Move responseTimeoutInMilliseconds from Connect method to constructor. + Client: Added virtual GetNetBiosServerName method. + Client: Added Echo command support. + SMB2: Added FileAttributeTagInformation support. + Server: SMB2: NegotiateResponse: Corrected CommandLength calculation. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 57c695e4..9e7d3c01 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.4.1 + 1.5.5 1573;1591 SMBLibrary false diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index ad0c31a8..b391d502 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -5,7 +5,7 @@ net20;net40;netcoreapp3.1 Copyright © Tal Aloni 2014-2025 SMBServer - 1.5.4 + 1.5.5 SMBServer true From 34c344ae5b11c812c4efa4078637899a43bbe1c2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 2 Nov 2025 19:25:30 +0200 Subject: [PATCH 164/247] NTLM: NegotiateFlags: Renamed TargetNameSupplied to TargetNameNegotiated --- SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs | 4 ++-- .../NTLM/IndependentNTLMAuthenticationProvider.cs | 2 +- .../Authentication/NTLM/Structures/ChallengeMessage.cs | 2 +- .../Authentication/NTLM/Structures/Enums/NegotiateFlags.cs | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs b/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs index b5f38ef9..e32e767d 100644 --- a/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs +++ b/SMBLibrary.Tests/NTLM/NTLMAuthenticationTests.cs @@ -107,7 +107,7 @@ public void NTLMv2ChallengeMessageTest() ChallengeMessage message = new ChallengeMessage(); message.ServerChallenge = serverChallenge; message.Version = new NTLMVersion(6, 0, 6000, NTLMVersion.NTLMSSP_REVISION_W2K3); - message.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.OEMEncoding | NegotiateFlags.TargetNameSupplied | NegotiateFlags.Sign | NegotiateFlags.Seal | NegotiateFlags.NTLMSessionSecurity | NegotiateFlags.AlwaysSign | NegotiateFlags.TargetTypeServer | NegotiateFlags.ExtendedSessionSecurity | NegotiateFlags.TargetInfo | NegotiateFlags.Version | NegotiateFlags.Use128BitEncryption | NegotiateFlags.KeyExchange | NegotiateFlags.Use56BitEncryption; + message.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.OEMEncoding | NegotiateFlags.TargetNameNegotiated | NegotiateFlags.Sign | NegotiateFlags.Seal | NegotiateFlags.NTLMSessionSecurity | NegotiateFlags.AlwaysSign | NegotiateFlags.TargetTypeServer | NegotiateFlags.ExtendedSessionSecurity | NegotiateFlags.TargetInfo | NegotiateFlags.Version | NegotiateFlags.Use128BitEncryption | NegotiateFlags.KeyExchange | NegotiateFlags.Use56BitEncryption; message.TargetName = "Server"; message.TargetInfo = AVPairUtils.GetAVPairSequence("Domain", "Server"); @@ -147,7 +147,7 @@ public void NTLMv2AuthenticateMessageTest() AuthenticateMessage message = new AuthenticateMessage(); message.EncryptedRandomSessionKey = sessionKey; message.Version = new NTLMVersion(5, 1, 2600, NTLMVersion.NTLMSSP_REVISION_W2K3); - message.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.TargetNameSupplied | NegotiateFlags.Sign | NegotiateFlags.Seal | NegotiateFlags.NTLMSessionSecurity | NegotiateFlags.AlwaysSign | NegotiateFlags.ExtendedSessionSecurity | NegotiateFlags.TargetInfo | NegotiateFlags.Version | NegotiateFlags.Use128BitEncryption | NegotiateFlags.KeyExchange | NegotiateFlags.Use56BitEncryption; + message.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.TargetNameNegotiated | NegotiateFlags.Sign | NegotiateFlags.Seal | NegotiateFlags.NTLMSessionSecurity | NegotiateFlags.AlwaysSign | NegotiateFlags.ExtendedSessionSecurity | NegotiateFlags.TargetInfo | NegotiateFlags.Version | NegotiateFlags.Use128BitEncryption | NegotiateFlags.KeyExchange | NegotiateFlags.Use56BitEncryption; message.DomainName = "Domain"; message.WorkStation = "COMPUTER"; message.UserName = "User"; diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs index 194f91ac..2a4cea30 100644 --- a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -82,7 +82,7 @@ public override NTStatus GetChallengeMessage(out object context, byte[] negotiat // https://msdn.microsoft.com/en-us/library/cc236691.aspx challengeMessage.NegotiateFlags = NegotiateFlags.TargetTypeServer | NegotiateFlags.TargetInfo | - NegotiateFlags.TargetNameSupplied | + NegotiateFlags.TargetNameNegotiated | NegotiateFlags.Version; // [MS-NLMP] NTLMSSP_NEGOTIATE_NTLM MUST be set in the [..] CHALLENGE_MESSAGE to the client. challengeMessage.NegotiateFlags |= NegotiateFlags.NTLMSessionSecurity; diff --git a/SMBLibrary/Authentication/NTLM/Structures/ChallengeMessage.cs b/SMBLibrary/Authentication/NTLM/Structures/ChallengeMessage.cs index e7884a50..f0f58e23 100644 --- a/SMBLibrary/Authentication/NTLM/Structures/ChallengeMessage.cs +++ b/SMBLibrary/Authentication/NTLM/Structures/ChallengeMessage.cs @@ -52,7 +52,7 @@ public ChallengeMessage(byte[] buffer) public byte[] GetBytes() { - if ((NegotiateFlags & NegotiateFlags.TargetNameSupplied) == 0) + if ((NegotiateFlags & NegotiateFlags.TargetNameNegotiated) == 0) { TargetName = String.Empty; } diff --git a/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs b/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs index 0d09ea50..c67cac2d 100644 --- a/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs +++ b/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs @@ -7,7 +7,12 @@ public enum NegotiateFlags : uint { UnicodeEncoding = 0x00000001, // NTLMSSP_NEGOTIATE_UNICODE OEMEncoding = 0x00000002, // NTLM_NEGOTIATE_OEM - TargetNameSupplied = 0x00000004, // NTLMSSP_REQUEST_TARGET + + /// + /// If set in the Negotiate message, the server MUST supply target name. + /// Windows Server 2008 R2 will supply target name even if not requested. + /// + TargetNameNegotiated = 0x00000004, // NTLMSSP_REQUEST_TARGET Sign = 0x00000010, // NTLMSSP_NEGOTIATE_SIGN Seal = 0x00000020, // NTLMSSP_NEGOTIATE_SEAL Datagram = 0x00000040, // NTLMSSP_NEGOTIATE_DATAGRAM From 5018a8b168daba0ec1d1aff287b2cd862d6203de Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 2 Nov 2025 19:55:49 +0200 Subject: [PATCH 165/247] Client: NTLM: Set NTLMSSP_REQUEST_TARGET bit in negotiate message --- SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 56a5cb84..ffe52269 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -23,6 +23,7 @@ public static byte[] GetNegotiateMessage(string domainName, string userName, str NegotiateFlags.NTLMSessionSecurity | NegotiateFlags.DomainNameSupplied | NegotiateFlags.WorkstationNameSupplied | + NegotiateFlags.TargetNameNegotiated | NegotiateFlags.AlwaysSign | NegotiateFlags.Version | NegotiateFlags.Use128BitEncryption | From a729c69f13cbb3d87c28b78eea191ffb553918dc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 10:01:13 +0200 Subject: [PATCH 166/247] SMB1Client: Correctly report login error status instead of STATUS_INVALID_SMB --- SMBLibrary/Client/SMB1Client.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index b29a3338..491087de 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -344,7 +344,11 @@ public NTStatus Login(string domainName, string userName, string password, Authe reply = WaitForMessage(CommandName.SMB_COM_SESSION_SETUP_ANDX); } - if (reply != null && reply.Commands[0] is SessionSetupAndXResponseExtended) + if (reply != null && reply.Commands[0] is ErrorResponse) + { + return reply.Header.Status; + } + else if (reply != null && reply.Commands[0] is SessionSetupAndXResponseExtended) { m_isLoggedIn = (reply.Header.Status == NTStatus.STATUS_SUCCESS); if (m_isLoggedIn) From a7eeea67f861742ff2968f69edca0261e2426b31 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 12:48:53 +0200 Subject: [PATCH 167/247] Added SMB1Cryptography --- SMBLibrary/SMB1/SMB1Cryptography.cs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 SMBLibrary/SMB1/SMB1Cryptography.cs diff --git a/SMBLibrary/SMB1/SMB1Cryptography.cs b/SMBLibrary/SMB1/SMB1Cryptography.cs new file mode 100644 index 00000000..c6c08bee --- /dev/null +++ b/SMBLibrary/SMB1/SMB1Cryptography.cs @@ -0,0 +1,29 @@ +/* Copyright (C) 2025 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.Security.Cryptography; +using Utilities; + +namespace SMBLibrary.SMB1 +{ + internal class SMB1Cryptography + { + public static ulong CalculateSignature(byte[] signingKey, byte[] challengeResponse, byte[] buffer, int offset, int paddedLength) + { + byte[] temp; + if (challengeResponse != null) + { + temp = ByteUtils.Concatenate(ByteUtils.Concatenate(signingKey, challengeResponse), ByteReader.ReadBytes(buffer, offset, paddedLength)); + } + else + { + temp = ByteUtils.Concatenate(signingKey, ByteReader.ReadBytes(buffer, offset, paddedLength)); + } + byte[] hash = MD5.Create().ComputeHash(temp); + return LittleEndianConverter.ToUInt64(hash, 0); + } + } +} From 7d9e14993ad6884dbff9004d7a8372bd2fc41833 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 12:58:39 +0200 Subject: [PATCH 168/247] SMB1Client: Renamed variable --- SMBLibrary/Client/SMB1Client.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 491087de..7d9f4edf 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -288,9 +288,9 @@ public NTStatus Login(string domainName, string userName, string password, Authe // 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); + byte[] clientChallengeStructurePadded = clientChallengeStructure.GetBytesPadded(); + byte[] proofStr = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, clientChallengeStructurePadded, password, userName, domainName); + request.UnicodePassword = ByteUtils.Concatenate(proofStr, clientChallengeStructurePadded); } TrySendMessage(request); From 949650061429368ddfe0a9b16c1fec1f63601e67 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 13:06:10 +0200 Subject: [PATCH 169/247] SMB1Client: Compute session key when extended security is set to false --- SMBLibrary/Client/SMB1Client.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 7d9f4edf..2d3d0f1a 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -9,6 +9,7 @@ using System.Diagnostics; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography; using System.Threading; using SMBLibrary.Authentication.NTLM; using SMBLibrary.Client.Authentication; @@ -271,6 +272,7 @@ public NTStatus Login(string domainName, string userName, string password, Authe request.PrimaryDomain = domainName; byte[] clientChallenge = new byte[8]; new Random().NextBytes(clientChallenge); + byte[] proofStr = null; if (authenticationMethod == AuthenticationMethod.NTLMv1) { request.OEMPassword = NTLMCryptography.ComputeLMv1Response(m_serverChallenge, password); @@ -289,7 +291,7 @@ public NTStatus Login(string domainName, string userName, string password, Authe request.OEMPassword = NTLMCryptography.ComputeLMv2Response(m_serverChallenge, clientChallenge, password, userName, domainName); NTLMv2ClientChallenge clientChallengeStructure = new NTLMv2ClientChallenge(DateTime.UtcNow, clientChallenge, AVPairUtils.GetAVPairSequence(domainName, Environment.MachineName)); byte[] clientChallengeStructurePadded = clientChallengeStructure.GetBytesPadded(); - byte[] proofStr = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, clientChallengeStructurePadded, password, userName, domainName); + proofStr = NTLMCryptography.ComputeNTLMv2Proof(m_serverChallenge, clientChallengeStructurePadded, password, userName, domainName); request.UnicodePassword = ByteUtils.Concatenate(proofStr, clientChallengeStructurePadded); } @@ -302,6 +304,9 @@ public NTStatus Login(string domainName, string userName, string password, Authe if (m_isLoggedIn) { m_userID = reply.Header.UID; + m_sessionKey = (authenticationMethod == AuthenticationMethod.NTLMv1) ? + new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)) : + new HMACMD5(NTLMCryptography.NTOWFv2(password, userName, domainName)).ComputeHash(proofStr); } return reply.Header.Status; } From 086f967c0a2205d998bc6de6373ece1b665cf2f3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 13:38:21 +0200 Subject: [PATCH 170/247] NTLM: NegotiateFlags: Renamed RequestLMSessionKey to RequestNonNTSessionKey --- SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs | 2 +- .../Authentication/NTLM/Structures/Enums/NegotiateFlags.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 913df3fb..e47bc4ef 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -239,7 +239,7 @@ public static byte[] KXKey(byte[] sessionBaseKey, NegotiateFlags negotiateFlags, } else { - if ((negotiateFlags & NegotiateFlags.RequestLMSessionKey) > 0) + if ((negotiateFlags & NegotiateFlags.RequestNonNTSessionKey) > 0) { byte[] keyExchangeKey = ByteUtils.Concatenate(ByteReader.ReadBytes(lmowf, 0, 8), new byte[8]); return keyExchangeKey; diff --git a/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs b/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs index c67cac2d..6e776041 100644 --- a/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs +++ b/SMBLibrary/Authentication/NTLM/Structures/Enums/NegotiateFlags.cs @@ -42,7 +42,7 @@ public enum NegotiateFlags : uint /// ExtendedSessionSecurity = 0x00080000, // NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY Identify = 0x00100000, // NTLMSSP_NEGOTIATE_IDENTIFY - RequestLMSessionKey = 0x00400000, // NTLMSSP_REQUEST_NON_NT_SESSION_KEY + RequestNonNTSessionKey = 0x00400000, // NTLMSSP_REQUEST_NON_NT_SESSION_KEY TargetInfo = 0x00800000, // NTLMSSP_NEGOTIATE_TARGET_INFO Version = 0x02000000, // NTLMSSP_NEGOTIATE_VERSION Use128BitEncryption = 0x20000000, // NTLMSSP_NEGOTIATE_128 From 885886e4d38969d6f10e286d93922fea62df7dff Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 14:40:04 +0200 Subject: [PATCH 171/247] Server: SessionSetupHelper: Correctly handle NTLM v1 non-extended-session-security with LmChallengeResponse set to 0 bytes --- SMBLibrary/Server/SMB1/SessionSetupHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs index c63b4b1e..5e860821 100644 --- a/SMBLibrary/Server/SMB1/SessionSetupHelper.cs +++ b/SMBLibrary/Server/SMB1/SessionSetupHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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, @@ -168,7 +168,7 @@ private static AuthenticateMessage CreateAuthenticateMessage(string accountNameT { authenticateMessage.NegotiateFlags |= NegotiateFlags.ExtendedSessionSecurity; } - else + else if (lmChallengeResponse.Length == 24) // NTLM v1 non-extended-session-security: if LmChallengeResponse is set to 0 bytes then LanManagerSessionKey is not applicable. { authenticateMessage.NegotiateFlags |= NegotiateFlags.LanManagerSessionKey; } From ee635a610243b1e2e13492b398bcebdd1bb08db3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 15:33:17 +0200 Subject: [PATCH 172/247] SMB2Client: Simplify enabling SMB 3.1.1 --- SMBLibrary/Client/SMB2Client.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 6cd59823..5df77c24 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -69,6 +69,10 @@ public SMB2Client(int responseTimeoutInMilliseconds) : this(responseTimeoutInMil { } + public SMB2Client(bool enableSMB311Support) : this(DefaultResponseTimeoutInMilliseconds, enableSMB311Support) + { + } + public SMB2Client(int responseTimeoutInMilliseconds, bool enableSMB311Support) { m_responseTimeoutInMilliseconds = responseTimeoutInMilliseconds; From 0c4dab275528c823d1f79350f4cc712373c8314a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 8 Nov 2025 20:46:05 +0200 Subject: [PATCH 173/247] SMBLibrary 1.5.5.1 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 9e7d3c01..0ccea3af 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.5 + 1.5.5.1 1573;1591 SMBLibrary false From 538b99bf6755c5deaa85c244fd4d5e522288052e Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 26 Nov 2025 00:44:42 +0200 Subject: [PATCH 174/247] Updated Readme.md: Added contribution terms --- Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Readme.md b/Readme.md index fb779449..7abdb67b 100644 --- a/Readme.md +++ b/Readme.md @@ -69,6 +69,10 @@ A commercial license of SMBLibrary is available for a fee. This is intended for companies who are unable to use the LGPL version. Please contact me for additional details. +Contributions: +============== +If you choose to make a contribution to this project, you must agree to irrevocably assign to SMBLibrary and/or Tal Aloni all worldwide copyright and intellectual property rights in and to your contribution, effective upon submission. + Contact: ======== If you have any question, feel free to contact me. From 4a4430c1944bfd4d5e109c35d1fc8562a8553148 Mon Sep 17 00:00:00 2001 From: paraboll <34540600+paraboll@users.noreply.github.com> Date: Wed, 26 Nov 2025 21:10:06 +0300 Subject: [PATCH 175/247] SMB2Client: Implemented SetSecurityInformation (#324) Co-authored-by: Nikolay Gorbunov --- SMBLibrary/Client/SMB2FileStore.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2FileStore.cs b/SMBLibrary/Client/SMB2FileStore.cs index f5f77ffe..59a30f77 100644 --- a/SMBLibrary/Client/SMB2FileStore.cs +++ b/SMBLibrary/Client/SMB2FileStore.cs @@ -286,7 +286,20 @@ public NTStatus GetSecurityInformation(out SecurityDescriptor result, object han public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor) { - return NTStatus.STATUS_NOT_SUPPORTED; + SetInfoRequest request = new SetInfoRequest(); + request.InfoType = InfoType.Security; + request.SecurityInformation = securityInformation; + request.FileId = (FileID)handle; + request.SetSecurityInformation(securityDescriptor); + + TrySendCommand(request); + SMB2Command response = m_client.WaitForCommand(request.MessageID, out bool connectionTerminated); + if (response != null) + { + return response.Header.Status; + } + + return connectionTerminated ? NTStatus.STATUS_INVALID_SMB : NTStatus.STATUS_IO_TIMEOUT; } public NTStatus NotifyChange(out object ioRequest, object handle, NotifyChangeFilter completionFilter, bool watchTree, int outputBufferSize, OnNotifyChangeCompleted onNotifyChangeCompleted, object context) From 5c72d60b420f33f419e9815bf8d3065992f30ab9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 29 Nov 2025 21:27:25 +0200 Subject: [PATCH 176/247] Code refactoring: Use SMBLibrary.DFS namespace for DFS data structures --- SMBLibrary/DFS/DfsReferralEntry.cs | 5 +---- SMBLibrary/DFS/RequestGetDfsReferral.cs | 5 +---- SMBLibrary/DFS/ResponseGetDfsReferral.cs | 3 +-- .../Transaction2GetDfsReferralRequest.cs | 3 +-- .../Transaction2GetDfsReferralResponse.cs | 4 +--- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/SMBLibrary/DFS/DfsReferralEntry.cs b/SMBLibrary/DFS/DfsReferralEntry.cs index 2751d1f0..a97ca748 100644 --- a/SMBLibrary/DFS/DfsReferralEntry.cs +++ b/SMBLibrary/DFS/DfsReferralEntry.cs @@ -4,12 +4,9 @@ * 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 +namespace SMBLibrary.DFS { public abstract class DfsReferralEntry { diff --git a/SMBLibrary/DFS/RequestGetDfsReferral.cs b/SMBLibrary/DFS/RequestGetDfsReferral.cs index 1f3e7760..d46ad515 100644 --- a/SMBLibrary/DFS/RequestGetDfsReferral.cs +++ b/SMBLibrary/DFS/RequestGetDfsReferral.cs @@ -4,12 +4,9 @@ * 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 +namespace SMBLibrary.DFS { /// /// [MS-DFSC] REQ_GET_DFS_REFERRAL diff --git a/SMBLibrary/DFS/ResponseGetDfsReferral.cs b/SMBLibrary/DFS/ResponseGetDfsReferral.cs index 09cbec7c..607697eb 100644 --- a/SMBLibrary/DFS/ResponseGetDfsReferral.cs +++ b/SMBLibrary/DFS/ResponseGetDfsReferral.cs @@ -6,10 +6,9 @@ */ using System; using System.Collections.Generic; -using System.Text; using Utilities; -namespace SMBLibrary +namespace SMBLibrary.DFS { /// /// [MS-DFSC] RESP_GET_DFS_REFERRAL diff --git a/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralRequest.cs b/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralRequest.cs index fb4146e0..4f2c31bd 100644 --- a/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralRequest.cs +++ b/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralRequest.cs @@ -4,8 +4,7 @@ * 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 SMBLibrary.DFS; using Utilities; namespace SMBLibrary.SMB1 diff --git a/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralResponse.cs b/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralResponse.cs index c01dcfdf..32d05fc7 100644 --- a/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralResponse.cs +++ b/SMBLibrary/SMB1/Transaction2Subcommands/Transaction2GetDfsReferralResponse.cs @@ -4,9 +4,7 @@ * 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; +using SMBLibrary.DFS; namespace SMBLibrary.SMB1 { From 5c5c76449eb81e24b83d52b97aa761832e06f3f9 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 29 Nov 2025 21:29:57 +0200 Subject: [PATCH 177/247] DFS: Implement ResponseGetDfsReferral Co-authored-by: Jacob Hales <5454918+jahales@users.noreply.github.com> --- .../DFS/ResponseGetDfsReferralTests.cs | 97 ++++++++++ SMBLibrary/DFS/DfsReferralEntry.cs | 27 ++- SMBLibrary/DFS/DfsReferralEntryV1.cs | 59 ++++++ SMBLibrary/DFS/DfsReferralEntryV2.cs | 86 +++++++++ SMBLibrary/DFS/DfsReferralEntryV3.cs | 169 ++++++++++++++++++ SMBLibrary/DFS/DfsReferralEntryV4.cs | 36 ++++ SMBLibrary/DFS/Enums/DfsReferralEntryFlags.cs | 28 +++ .../DFS/Enums/DfsReferralHeaderFlags.cs | 12 ++ SMBLibrary/DFS/Enums/DfsServerType.cs | 19 ++ SMBLibrary/DFS/ResponseGetDfsReferral.cs | 64 ++++++- 10 files changed, 589 insertions(+), 8 deletions(-) create mode 100644 SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs create mode 100644 SMBLibrary/DFS/DfsReferralEntryV1.cs create mode 100644 SMBLibrary/DFS/DfsReferralEntryV2.cs create mode 100644 SMBLibrary/DFS/DfsReferralEntryV3.cs create mode 100644 SMBLibrary/DFS/DfsReferralEntryV4.cs create mode 100644 SMBLibrary/DFS/Enums/DfsReferralEntryFlags.cs create mode 100644 SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs create mode 100644 SMBLibrary/DFS/Enums/DfsServerType.cs diff --git a/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs b/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs new file mode 100644 index 00000000..3896e954 --- /dev/null +++ b/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs @@ -0,0 +1,97 @@ +/* Copyright (C) 2025 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.DFS; +using System; +using System.Collections.Generic; + +namespace SMBLibrary.Tests.DFS +{ + [TestClass] + public class ResponseGetDfsReferralTests + { + [TestMethod] + public void ParseResponseGetDfsReferralWithSingleDfsReferralEntryV4() + { + // Arrange + // Returned by Windows Server 2008 R2 SP1 + byte[] buffer = new byte[] + { + 0x3e ,0x00 ,0x01 ,0x00 ,0x03 ,0x00 ,0x00 ,0x00 ,0x04 ,0x00 ,0x22 ,0x00 ,0x01 ,0x00 ,0x04 ,0x00, + 0x2c ,0x01 ,0x00 ,0x00 ,0x22 ,0x00 ,0x62 ,0x00 ,0xa2 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, + 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x5c ,0x00 ,0x57 ,0x00 ,0x49 ,0x00, + 0x4e ,0x00 ,0x2d ,0x00 ,0x4d ,0x00 ,0x51 ,0x00 ,0x38 ,0x00 ,0x33 ,0x00 ,0x44 ,0x00 ,0x45 ,0x00, + 0x35 ,0x00 ,0x4e ,0x00 ,0x47 ,0x00 ,0x37 ,0x00 ,0x32 ,0x00 ,0x5c ,0x00 ,0x44 ,0x00 ,0x66 ,0x00, + 0x73 ,0x00 ,0x20 ,0x00 ,0x4e ,0x00 ,0x61 ,0x00 ,0x6d ,0x00 ,0x65 ,0x00 ,0x73 ,0x00 ,0x70 ,0x00, + 0x61 ,0x00 ,0x63 ,0x00 ,0x65 ,0x00 ,0x31 ,0x00 ,0x00 ,0x00 ,0x5c ,0x00 ,0x57 ,0x00 ,0x49 ,0x00, + 0x4e ,0x00 ,0x2d ,0x00 ,0x4d ,0x00 ,0x51 ,0x00 ,0x38 ,0x00 ,0x33 ,0x00 ,0x44 ,0x00 ,0x45 ,0x00, + 0x35 ,0x00 ,0x4e ,0x00 ,0x47 ,0x00 ,0x37 ,0x00 ,0x32 ,0x00 ,0x5c ,0x00 ,0x44 ,0x00 ,0x66 ,0x00, + 0x73 ,0x00 ,0x20 ,0x00 ,0x4e ,0x00 ,0x61 ,0x00 ,0x6d ,0x00 ,0x65 ,0x00 ,0x73 ,0x00 ,0x70 ,0x00, + 0x61 ,0x00 ,0x63 ,0x00 ,0x65 ,0x00 ,0x31 ,0x00 ,0x00 ,0x00 ,0x5c ,0x00 ,0x57 ,0x00 ,0x49 ,0x00, + 0x4e ,0x00 ,0x2d ,0x00 ,0x4d ,0x00 ,0x51 ,0x00 ,0x38 ,0x00 ,0x33 ,0x00 ,0x44 ,0x00 ,0x45 ,0x00, + 0x35 ,0x00 ,0x4e ,0x00 ,0x47 ,0x00 ,0x37 ,0x00 ,0x32 ,0x00 ,0x5c ,0x00 ,0x44 ,0x00 ,0x66 ,0x00, + 0x73 ,0x00 ,0x20 ,0x00 ,0x4e ,0x00 ,0x61 ,0x00 ,0x6d ,0x00 ,0x65 ,0x00 ,0x73 ,0x00 ,0x70 ,0x00, + 0x61 ,0x00 ,0x63 ,0x00 ,0x65 ,0x00 ,0x31 ,0x00 ,0x00 ,0x00 + }; + + // Act + ResponseGetDfsReferral response = new ResponseGetDfsReferral(buffer); + + // Assert + Assert.AreEqual(62, response.PathConsumed); + Assert.AreEqual(DfsReferralHeaderFlags.ReferalServers | DfsReferralHeaderFlags.StorageServers , response.ReferralHeaderFlags); + Assert.AreEqual(1, response.ReferralEntries.Count); + Assert.IsInstanceOfType(response.ReferralEntries[0], typeof(DfsReferralEntryV4)); + + DfsReferralEntryV4 entry = (DfsReferralEntryV4)response.ReferralEntries[0]; + Assert.AreEqual((uint)300, entry.TimeToLive); + Assert.AreEqual(DfsReferralEntryFlags.TargetSetBoundary, entry.ReferralEntryFlags); + Assert.AreEqual("\\WIN-MQ83DE5NG72\\Dfs Namespace1", entry.DfsPath); + Assert.AreEqual("\\WIN-MQ83DE5NG72\\Dfs Namespace1", entry.DfsAlternatePath); + Assert.AreEqual("\\WIN-MQ83DE5NG72\\Dfs Namespace1", entry.NetworkAddress); + Assert.AreEqual(Guid.Empty, entry.ServiceSiteGuid); + } + + [TestMethod] + public void Parse_ResponseGetDfsReferralWithSingleDfsReferralEntryV4_GetBytes() + { + // Arrange + ResponseGetDfsReferral response = new ResponseGetDfsReferral(); + response.PathConsumed = 62; + response.ReferralHeaderFlags = DfsReferralHeaderFlags.ReferalServers | DfsReferralHeaderFlags.StorageServers; + response.ReferralEntries = new List() + { + new DfsReferralEntryV4() + { + TimeToLive = 300, + ReferralEntryFlags = DfsReferralEntryFlags.TargetSetBoundary, + DfsPath = "\\WIN-MQ83DE5NG72\\Dfs Namespace1", + DfsAlternatePath = "\\WIN-MQ83DE5NG72\\Dfs Namespace1", + NetworkAddress = "\\WIN-MQ83DE5NG72\\Dfs Namespace1", + ServiceSiteGuid = Guid.Empty + } + }; + + // Act + response = new ResponseGetDfsReferral(response.GetBytes()); + + // Assert + Assert.AreEqual(62, response.PathConsumed); + Assert.AreEqual(DfsReferralHeaderFlags.ReferalServers | DfsReferralHeaderFlags.StorageServers, response.ReferralHeaderFlags); + Assert.AreEqual(1, response.ReferralEntries.Count); + Assert.IsInstanceOfType(response.ReferralEntries[0], typeof(DfsReferralEntryV4)); + + DfsReferralEntryV4 entry = (DfsReferralEntryV4)response.ReferralEntries[0]; + Assert.AreEqual((uint)300, entry.TimeToLive); + Assert.AreEqual(DfsReferralEntryFlags.TargetSetBoundary, entry.ReferralEntryFlags); + Assert.AreEqual("\\WIN-MQ83DE5NG72\\Dfs Namespace1", entry.DfsPath); + Assert.AreEqual("\\WIN-MQ83DE5NG72\\Dfs Namespace1", entry.DfsAlternatePath); + Assert.AreEqual("\\WIN-MQ83DE5NG72\\Dfs Namespace1", entry.NetworkAddress); + Assert.AreEqual(Guid.Empty, entry.ServiceSiteGuid); + } + } +} diff --git a/SMBLibrary/DFS/DfsReferralEntry.cs b/SMBLibrary/DFS/DfsReferralEntry.cs index a97ca748..23ee98c6 100644 --- a/SMBLibrary/DFS/DfsReferralEntry.cs +++ b/SMBLibrary/DFS/DfsReferralEntry.cs @@ -1,14 +1,39 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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.IO; using Utilities; namespace SMBLibrary.DFS { public abstract class DfsReferralEntry { + public abstract byte[] GetBytes(); + + public abstract int Length + { + get; + } + + public static DfsReferralEntry ReadEntry(byte[] buffer, ref int offset) + { + ushort versionNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0); + switch (versionNumber) + { + case 1: + return new DfsReferralEntryV1(buffer, ref offset); + case 2: + return new DfsReferralEntryV2(buffer, ref offset); + case 3: + return new DfsReferralEntryV3(buffer, ref offset); + case 4: + return new DfsReferralEntryV4(buffer, ref offset); + default: + throw new InvalidDataException($"DfsReferralEntry version {versionNumber} is invalid"); + } + } } } diff --git a/SMBLibrary/DFS/DfsReferralEntryV1.cs b/SMBLibrary/DFS/DfsReferralEntryV1.cs new file mode 100644 index 00000000..2a29227b --- /dev/null +++ b/SMBLibrary/DFS/DfsReferralEntryV1.cs @@ -0,0 +1,59 @@ +/* Copyright (C) 2025 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 Utilities; + +namespace SMBLibrary.DFS +{ + /// + /// [MS-DFSC] 2.2.5.1 DFS_REFERRAL_V1 + /// + public class DfsReferralEntryV1 : DfsReferralEntry + { + public const int FixedLength = 8; + + public ushort VersionNumber; + public ushort Size; + public DfsServerType ServerType; + public DfsReferralEntryFlags ReferralEntryFlags; + public string ShareName; + + public DfsReferralEntryV1() + { + VersionNumber = 1; + } + + public DfsReferralEntryV1(byte[] buffer, ref int offset) + { + VersionNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0); + Size = LittleEndianConverter.ToUInt16(buffer, offset + 2); + ServerType = (DfsServerType)LittleEndianConverter.ToUInt16(buffer, offset + 4); + ReferralEntryFlags = (DfsReferralEntryFlags)LittleEndianConverter.ToUInt16(buffer, offset + 6); + ShareName = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + 8); + + offset += Size; + } + + public override byte[] GetBytes() + { + byte[] buffer = new byte[this.Length]; + LittleEndianWriter.WriteUInt16(buffer, 0, VersionNumber); + LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)buffer.Length); + LittleEndianWriter.WriteUInt16(buffer, 4, (ushort)ServerType); + LittleEndianWriter.WriteUInt16(buffer, 6, (ushort)ReferralEntryFlags); + ByteWriter.WriteNullTerminatedUTF16String(buffer, 8, ShareName); + return buffer; + } + + public override int Length + { + get + { + return FixedLength + (ShareName.Length + 1) * 2; + } + } + } +} diff --git a/SMBLibrary/DFS/DfsReferralEntryV2.cs b/SMBLibrary/DFS/DfsReferralEntryV2.cs new file mode 100644 index 00000000..b919a70d --- /dev/null +++ b/SMBLibrary/DFS/DfsReferralEntryV2.cs @@ -0,0 +1,86 @@ +/* Copyright (C) 2025 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 Utilities; + +namespace SMBLibrary.DFS +{ + /// + /// [MS-DFSC] 2.2.5.2 DFS_REFERRAL_V2 + /// + public class DfsReferralEntryV2 : DfsReferralEntry + { + public const int FixedLength = 22; + + public ushort VersionNumber; + public ushort Size; + public DfsServerType ServerType; + public DfsReferralEntryFlags ReferralEntryFlags; + public uint Proximity; + public uint TimeToLive; + public string DfsPath; + public string DfsAlternatePath; + public string NetworkAddress; + + public DfsReferralEntryV2() + { + VersionNumber = 2; + } + + public DfsReferralEntryV2(byte[] buffer, ref int offset) + { + VersionNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0); + Size = LittleEndianConverter.ToUInt16(buffer, offset + 2); + ServerType = (DfsServerType)LittleEndianConverter.ToUInt16(buffer, offset + 4); + ReferralEntryFlags = (DfsReferralEntryFlags)LittleEndianConverter.ToUInt16(buffer, offset + 6); + + Proximity = LittleEndianConverter.ToUInt32(buffer, offset + 8); + TimeToLive = LittleEndianConverter.ToUInt32(buffer, offset + 12); + + ushort dfsPathOffset = LittleEndianConverter.ToUInt16(buffer, offset + 16); + ushort dfsAlternatePathOffset = LittleEndianConverter.ToUInt16(buffer, offset + 18); + ushort networkAddressOffset = LittleEndianConverter.ToUInt16(buffer, offset + 20); + + DfsPath = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + dfsPathOffset); + DfsAlternatePath = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + dfsAlternatePathOffset); + NetworkAddress = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + networkAddressOffset); + + offset += Size; + } + + public override byte[] GetBytes() + { + byte[] buffer = new byte[Length]; + LittleEndianWriter.WriteUInt16(buffer, 0, VersionNumber); + LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)buffer.Length); + LittleEndianWriter.WriteUInt16(buffer, 4, (ushort)ServerType); + LittleEndianWriter.WriteUInt16(buffer, 6, (ushort)ReferralEntryFlags); + LittleEndianWriter.WriteUInt32(buffer, 8, Proximity); + LittleEndianWriter.WriteUInt32(buffer, 12, TimeToLive); + + ushort dfsPathOffset = FixedLength; + ushort dfsAlternatePathOffset = (ushort)(dfsPathOffset + (DfsPath.Length + 1) * 2); + ushort networkAddressOffset = (ushort)(dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2); + LittleEndianWriter.WriteUInt16(buffer, 16, dfsPathOffset); + LittleEndianWriter.WriteUInt16(buffer, 18, dfsAlternatePathOffset); + LittleEndianWriter.WriteUInt16(buffer, 20, networkAddressOffset); + + ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsPathOffset, DfsPath); + ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsAlternatePathOffset, DfsAlternatePath); + ByteWriter.WriteNullTerminatedUTF16String(buffer, networkAddressOffset, NetworkAddress); + + return buffer; + } + + public override int Length + { + get + { + return FixedLength + (DfsPath.Length + 1 + DfsAlternatePath.Length + 1 + NetworkAddress.Length + 1) * 2; + } + } + } +} diff --git a/SMBLibrary/DFS/DfsReferralEntryV3.cs b/SMBLibrary/DFS/DfsReferralEntryV3.cs new file mode 100644 index 00000000..886b15c1 --- /dev/null +++ b/SMBLibrary/DFS/DfsReferralEntryV3.cs @@ -0,0 +1,169 @@ +/* Copyright (C) 2025 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.DFS +{ + /// + /// [MS-DFSC] 2.2.5.3 DFS_REFERRAL_V3 + /// V3 supports both normal referrals and NameListReferrals (for SYSVOL/NETLOGON). + /// + public class DfsReferralEntryV3 : DfsReferralEntry + { + private const int FixedLength = 12; + + public ushort VersionNumber; + public ushort Size; + public DfsServerType ServerType; + public DfsReferralEntryFlags ReferralEntryFlags; + public uint TimeToLive; + + // Normal referral fields (when IsNameListReferral is false) + public string DfsPath; + public string DfsAlternatePath; + public string NetworkAddress; + public Guid ServiceSiteGuid; + + // NameListReferral fields (when IsNameListReferral is true) + public string SpecialName; + public List ExpandedNames; + + public DfsReferralEntryV3() + { + VersionNumber = 3; + } + + public DfsReferralEntryV3(byte[] buffer, ref int offset) + { + VersionNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0); + Size = LittleEndianConverter.ToUInt16(buffer, offset + 2); + ServerType = (DfsServerType)LittleEndianConverter.ToUInt16(buffer, offset + 4); + ReferralEntryFlags = (DfsReferralEntryFlags)LittleEndianConverter.ToUInt16(buffer, offset + 6); + + TimeToLive = LittleEndianConverter.ToUInt32(buffer, offset + 8); + + bool isNameListReferral = (ReferralEntryFlags & DfsReferralEntryFlags.NameListReferral) != 0; + if (!isNameListReferral) + { + ushort dfsPathOffset = LittleEndianConverter.ToUInt16(buffer, offset + 12); + ushort dfsAlternatePathOffset = LittleEndianConverter.ToUInt16(buffer, offset + 14); + ushort networkAddressOffset = LittleEndianConverter.ToUInt16(buffer, offset + 16); + ServiceSiteGuid = LittleEndianConverter.ToGuid(buffer, offset + 18); + + DfsPath = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + dfsPathOffset); + DfsAlternatePath = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + dfsAlternatePathOffset); + NetworkAddress = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + networkAddressOffset); + } + else + { + ushort specialNameOffset = LittleEndianConverter.ToUInt16(buffer, offset + 12); + ushort numberOfExpandedNames = LittleEndianConverter.ToUInt16(buffer, offset + 14); + ushort expandedNameOffset = LittleEndianConverter.ToUInt16(buffer, offset + 16); + + SpecialName = ByteReader.ReadNullTerminatedUTF16String(buffer, specialNameOffset); + ExpandedNames = new List(); + int currentOffset = expandedNameOffset; + for (int nameIndex = 0; nameIndex < numberOfExpandedNames; nameIndex++) + { + if (currentOffset >= buffer.Length) + { + break; + } + + string expandedName = ByteReader.ReadNullTerminatedUTF16String(buffer, currentOffset); + if (expandedName != null) + { + ExpandedNames.Add(expandedName); + currentOffset += (expandedName.Length + 1) * 2; + } + else + { + currentOffset += 2; + } + } + } + + offset += Size; + } + + public override byte[] GetBytes() + { + byte[] buffer = new byte[Length]; + LittleEndianWriter.WriteUInt16(buffer, 0, VersionNumber); + LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)buffer.Length); + LittleEndianWriter.WriteUInt16(buffer, 4, (ushort)ServerType); + LittleEndianWriter.WriteUInt16(buffer, 6, (ushort)ReferralEntryFlags); + LittleEndianWriter.WriteUInt32(buffer, 8, TimeToLive); + + if (!IsNameListReferral) + { + ushort dfsPathOffset = FixedLength + 6 + 16; + ushort dfsAlternatePathOffset = (ushort)(dfsPathOffset + (DfsPath.Length + 1) * 2); + ushort networkAddressOffset = (ushort)(dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2); + LittleEndianWriter.WriteUInt16(buffer, 12, dfsPathOffset); + LittleEndianWriter.WriteUInt16(buffer, 14, dfsAlternatePathOffset); + LittleEndianWriter.WriteUInt16(buffer, 16, networkAddressOffset); + LittleEndianWriter.WriteGuid(buffer, 18, ServiceSiteGuid); + + ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsPathOffset, DfsPath); + ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsAlternatePathOffset, DfsAlternatePath); + ByteWriter.WriteNullTerminatedUTF16String(buffer, networkAddressOffset, NetworkAddress); + } + else + { + ushort specialNameOffset = FixedLength + 6; + ushort expandedNameOffset = (ushort)(specialNameOffset + (SpecialName.Length + 1) * 2); + LittleEndianWriter.WriteUInt16(buffer, 12, specialNameOffset); + LittleEndianWriter.WriteUInt16(buffer, 14, (ushort)ExpandedNames.Count); + LittleEndianWriter.WriteUInt16(buffer, 16, expandedNameOffset); + + ByteWriter.WriteNullTerminatedUTF16String(buffer, specialNameOffset, SpecialName); + int currentOffset = expandedNameOffset; + for (int nameIndex = 0; nameIndex < ExpandedNames.Count; nameIndex++) + { + ByteWriter.WriteNullTerminatedUTF16String(buffer, currentOffset, ExpandedNames[nameIndex]); + currentOffset += (ExpandedNames[nameIndex].Length + 1) * 2; + } + } + + return buffer; + } + + public override int Length + { + get + { + if (!IsNameListReferral) + { + return FixedLength + 6 + 16 + (DfsPath.Length + 1 + DfsAlternatePath.Length + 1 + NetworkAddress.Length + 1) * 2; + } + else + { + int length = FixedLength + 6 + (SpecialName.Length + 1) * 2; + for (int nameIndex = 0; nameIndex < ExpandedNames.Count; nameIndex++) + { + length += (ExpandedNames[nameIndex].Length + 1) * 2; + } + return length; + } + } + } + + /// + /// Returns true if this is a NameListReferral (used for SYSVOL/NETLOGON DC lists). + /// + public bool IsNameListReferral + { + get + { + return (ReferralEntryFlags & DfsReferralEntryFlags.NameListReferral) != 0; + } + } + } +} \ No newline at end of file diff --git a/SMBLibrary/DFS/DfsReferralEntryV4.cs b/SMBLibrary/DFS/DfsReferralEntryV4.cs new file mode 100644 index 00000000..c9a9b696 --- /dev/null +++ b/SMBLibrary/DFS/DfsReferralEntryV4.cs @@ -0,0 +1,36 @@ +/* Copyright (C) 2025 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. + */ + +namespace SMBLibrary.DFS +{ + /// + /// [MS-DFSC] 2.2.5.4 DFS_REFERRAL_V4 + /// V4 is structurally identical to V3 but adds the TargetSetBoundary flag semantics. + /// The TargetSetBoundary flag (bit 0x0004) indicates the first target in a target set, + /// allowing clients to group targets for failover purposes. + /// + public class DfsReferralEntryV4 : DfsReferralEntryV3 + { + public DfsReferralEntryV4() + { + VersionNumber = 4; + } + + public DfsReferralEntryV4(byte[] buffer, ref int offset) : base(buffer, ref offset) + { + } + + /// + /// Returns true if this entry marks the boundary of a target set (V4 only). + /// When true, this is the first target in a new target set. + /// + public bool IsTargetSetBoundary + { + get { return (ReferralEntryFlags & DfsReferralEntryFlags.TargetSetBoundary) != 0; } + } + } +} diff --git a/SMBLibrary/DFS/Enums/DfsReferralEntryFlags.cs b/SMBLibrary/DFS/Enums/DfsReferralEntryFlags.cs new file mode 100644 index 00000000..8ae76ae4 --- /dev/null +++ b/SMBLibrary/DFS/Enums/DfsReferralEntryFlags.cs @@ -0,0 +1,28 @@ +using System; + +namespace SMBLibrary.DFS +{ + /// + /// [MS-DFSC] 2.2.4.x DFS_REFERRAL_Vx - ReferralEntryFlags + /// + [Flags] + public enum DfsReferralEntryFlags : ushort + { + /// + /// No flags set. + /// + None = 0x0000, + + /// + /// NameListReferral bit - The referral entry is a NameListReferral containing + /// a list of target names (e.g., domain controller list for SYSVOL/NETLOGON). + /// + NameListReferral = 0x0002, + + /// + /// TargetSetBoundary bit (V4 only) - The first target in a target set. + /// Used for target set grouping in V4 referrals. + /// + TargetSetBoundary = 0x0004, + } +} diff --git a/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs b/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs new file mode 100644 index 00000000..1184633d --- /dev/null +++ b/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs @@ -0,0 +1,12 @@ +using System; + +namespace SMBLibrary.DFS +{ + [Flags] + public enum DfsReferralHeaderFlags : uint + { + ReferalServers = 0x00000001, + StorageServers = 0x00000002, + TargetFailback = 0x00000004, + } +} diff --git a/SMBLibrary/DFS/Enums/DfsServerType.cs b/SMBLibrary/DFS/Enums/DfsServerType.cs new file mode 100644 index 00000000..0c21a4db --- /dev/null +++ b/SMBLibrary/DFS/Enums/DfsServerType.cs @@ -0,0 +1,19 @@ + +namespace SMBLibrary.DFS +{ + /// + /// [MS-DFSC] 2.2.4.x DFS_REFERRAL_Vx - ServerType + /// + public enum DfsServerType : ushort + { + /// + /// The target is a non-root DFS server (link target or storage server). + /// + NonRoot = 0x0000, + + /// + /// The target is a root DFS server (namespace server). + /// + Root = 0x0001, + } +} diff --git a/SMBLibrary/DFS/ResponseGetDfsReferral.cs b/SMBLibrary/DFS/ResponseGetDfsReferral.cs index 607697eb..6a223e03 100644 --- a/SMBLibrary/DFS/ResponseGetDfsReferral.cs +++ b/SMBLibrary/DFS/ResponseGetDfsReferral.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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, @@ -15,26 +15,76 @@ namespace SMBLibrary.DFS /// public class ResponseGetDfsReferral { + private const int HeaderSize = 8; + private const int MinReferralEntryHeaderSize = 16; + public ushort PathConsumed; - public ushort NumberOfReferrals; - public uint ReferralHeaderFlags; + // ushort NumberOfReferrals; + public DfsReferralHeaderFlags ReferralHeaderFlags; public List ReferralEntries; - public List StringBuffer; + // StringBuffer; // Padding public ResponseGetDfsReferral() { - throw new NotImplementedException(); + ReferralEntries = new List(); } public ResponseGetDfsReferral(byte[] buffer) { - throw new NotImplementedException(); + if (buffer.Length < HeaderSize) + { + throw new ArgumentException("Buffer too small for DFS referral response header", nameof(buffer)); + } + + PathConsumed = LittleEndianConverter.ToUInt16(buffer, 0); + ushort numberOfReferrals = LittleEndianConverter.ToUInt16(buffer, 2); + ReferralHeaderFlags = (DfsReferralHeaderFlags)LittleEndianConverter.ToUInt32(buffer, 4); + + if (numberOfReferrals > 0 && buffer.Length == HeaderSize) + { + throw new ArgumentException("Buffer too small for DFS referral entries", nameof(buffer)); + } + + ReferralEntries = new List(); + int entryOffset = HeaderSize; + for (int index = 0; index < numberOfReferrals; index++) + { + if (buffer.Length < entryOffset + MinReferralEntryHeaderSize) + { + throw new ArgumentException("Buffer too small for DFS referral entry header", nameof(buffer)); + } + + DfsReferralEntry entry = DfsReferralEntry.ReadEntry(buffer, ref entryOffset); + ReferralEntries.Add(entry); + + if (entryOffset > buffer.Length) + { + throw new ArgumentException("Buffer too small for next DFS referral", nameof(buffer)); + } + } } public byte[] GetBytes() { - throw new NotImplementedException(); + int length = HeaderSize; + foreach (DfsReferralEntry entry in ReferralEntries) + { + length += entry.Length; + } + + byte[] buffer = new byte[length]; + LittleEndianWriter.WriteUInt16(buffer, 0, PathConsumed); + LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)ReferralEntries.Count); + LittleEndianWriter.WriteUInt32(buffer, 4, (uint)ReferralHeaderFlags); + + int offset = HeaderSize; + foreach (DfsReferralEntry entry in ReferralEntries) + { + ByteWriter.WriteBytes(buffer, offset, entry.GetBytes()); + offset += entry.Length; + } + return buffer; } } } From 8ffb0440c589074e27f035cc092ac99b176d3737 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 30 Nov 2025 00:35:16 +0200 Subject: [PATCH 178/247] DFS: DfsReferralHeaderFlags: Corrected typo Co-authored-by: Jacob Hales <5454918+jahales@users.noreply.github.com> --- SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs | 6 +++--- SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs b/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs index 3896e954..c6504d97 100644 --- a/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs +++ b/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs @@ -43,7 +43,7 @@ public void ParseResponseGetDfsReferralWithSingleDfsReferralEntryV4() // Assert Assert.AreEqual(62, response.PathConsumed); - Assert.AreEqual(DfsReferralHeaderFlags.ReferalServers | DfsReferralHeaderFlags.StorageServers , response.ReferralHeaderFlags); + Assert.AreEqual(DfsReferralHeaderFlags.ReferralServers | DfsReferralHeaderFlags.StorageServers , response.ReferralHeaderFlags); Assert.AreEqual(1, response.ReferralEntries.Count); Assert.IsInstanceOfType(response.ReferralEntries[0], typeof(DfsReferralEntryV4)); @@ -62,7 +62,7 @@ public void Parse_ResponseGetDfsReferralWithSingleDfsReferralEntryV4_GetBytes() // Arrange ResponseGetDfsReferral response = new ResponseGetDfsReferral(); response.PathConsumed = 62; - response.ReferralHeaderFlags = DfsReferralHeaderFlags.ReferalServers | DfsReferralHeaderFlags.StorageServers; + response.ReferralHeaderFlags = DfsReferralHeaderFlags.ReferralServers | DfsReferralHeaderFlags.StorageServers; response.ReferralEntries = new List() { new DfsReferralEntryV4() @@ -81,7 +81,7 @@ public void Parse_ResponseGetDfsReferralWithSingleDfsReferralEntryV4_GetBytes() // Assert Assert.AreEqual(62, response.PathConsumed); - Assert.AreEqual(DfsReferralHeaderFlags.ReferalServers | DfsReferralHeaderFlags.StorageServers, response.ReferralHeaderFlags); + Assert.AreEqual(DfsReferralHeaderFlags.ReferralServers | DfsReferralHeaderFlags.StorageServers, response.ReferralHeaderFlags); Assert.AreEqual(1, response.ReferralEntries.Count); Assert.IsInstanceOfType(response.ReferralEntries[0], typeof(DfsReferralEntryV4)); diff --git a/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs b/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs index 1184633d..fd9184d7 100644 --- a/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs +++ b/SMBLibrary/DFS/Enums/DfsReferralHeaderFlags.cs @@ -5,7 +5,7 @@ namespace SMBLibrary.DFS [Flags] public enum DfsReferralHeaderFlags : uint { - ReferalServers = 0x00000001, + ReferralServers = 0x00000001, StorageServers = 0x00000002, TargetFailback = 0x00000004, } From 69cea38cb4d04ef87009a131dfe577af8d69230c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 30 Nov 2025 00:36:27 +0200 Subject: [PATCH 179/247] DFS: DfsReferralEntryV3: Bugfix: Read SpecialName and ExpandedNames from the correct offset Co-authored-by: Jacob Hales <5454918+jahales@users.noreply.github.com> --- SMBLibrary/DFS/DfsReferralEntryV3.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/DFS/DfsReferralEntryV3.cs b/SMBLibrary/DFS/DfsReferralEntryV3.cs index 886b15c1..70fa29fb 100644 --- a/SMBLibrary/DFS/DfsReferralEntryV3.cs +++ b/SMBLibrary/DFS/DfsReferralEntryV3.cs @@ -66,9 +66,9 @@ public DfsReferralEntryV3(byte[] buffer, ref int offset) ushort numberOfExpandedNames = LittleEndianConverter.ToUInt16(buffer, offset + 14); ushort expandedNameOffset = LittleEndianConverter.ToUInt16(buffer, offset + 16); - SpecialName = ByteReader.ReadNullTerminatedUTF16String(buffer, specialNameOffset); + SpecialName = ByteReader.ReadNullTerminatedUTF16String(buffer, offset + specialNameOffset); ExpandedNames = new List(); - int currentOffset = expandedNameOffset; + int currentOffset = offset + expandedNameOffset; for (int nameIndex = 0; nameIndex < numberOfExpandedNames; nameIndex++) { if (currentOffset >= buffer.Length) From c191b2593263a7cf25258ff0dcbb836124a52506 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 30 Nov 2025 02:00:04 +0200 Subject: [PATCH 180/247] DFS: ResponseGetDfsReferral: Corrected GetBytes implementation Ensure strings referenced from the fields of a referral entry follow the last referral entry --- SMBLibrary/DFS/DfsReferralEntry.cs | 14 +++++- SMBLibrary/DFS/DfsReferralEntryV1.cs | 21 ++++++--- SMBLibrary/DFS/DfsReferralEntryV2.cs | 38 ++++++++++------ SMBLibrary/DFS/DfsReferralEntryV3.cs | 58 +++++++++++++++--------- SMBLibrary/DFS/ResponseGetDfsReferral.cs | 11 ++++- 5 files changed, 96 insertions(+), 46 deletions(-) diff --git a/SMBLibrary/DFS/DfsReferralEntry.cs b/SMBLibrary/DFS/DfsReferralEntry.cs index 23ee98c6..40a2206e 100644 --- a/SMBLibrary/DFS/DfsReferralEntry.cs +++ b/SMBLibrary/DFS/DfsReferralEntry.cs @@ -11,13 +11,25 @@ namespace SMBLibrary.DFS { public abstract class DfsReferralEntry { - public abstract byte[] GetBytes(); + /// + /// [MS-DFSC] 2.2.5 - Referral Entry Types + /// The strings referenced from the fields of a referral entry MUST follow the last referral entry in the RESP_GET_DFS_REFERRAL message. + /// + public abstract byte[] WriteBytes(byte[] buffer, int offset, int stringsOffset); public abstract int Length { get; } + /// + /// Length of referenced strings + /// + public abstract int StringsLength + { + get; + } + public static DfsReferralEntry ReadEntry(byte[] buffer, ref int offset) { ushort versionNumber = LittleEndianConverter.ToUInt16(buffer, offset + 0); diff --git a/SMBLibrary/DFS/DfsReferralEntryV1.cs b/SMBLibrary/DFS/DfsReferralEntryV1.cs index 2a29227b..b0137d7e 100644 --- a/SMBLibrary/DFS/DfsReferralEntryV1.cs +++ b/SMBLibrary/DFS/DfsReferralEntryV1.cs @@ -37,14 +37,13 @@ public DfsReferralEntryV1(byte[] buffer, ref int offset) offset += Size; } - public override byte[] GetBytes() + public override byte[] WriteBytes(byte[] buffer, int offset, int stringsOffset) { - byte[] buffer = new byte[this.Length]; - LittleEndianWriter.WriteUInt16(buffer, 0, VersionNumber); - LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)buffer.Length); - LittleEndianWriter.WriteUInt16(buffer, 4, (ushort)ServerType); - LittleEndianWriter.WriteUInt16(buffer, 6, (ushort)ReferralEntryFlags); - ByteWriter.WriteNullTerminatedUTF16String(buffer, 8, ShareName); + LittleEndianWriter.WriteUInt16(buffer, offset + 0, VersionNumber); + LittleEndianWriter.WriteUInt16(buffer, offset + 2, (ushort)this.Length); + LittleEndianWriter.WriteUInt16(buffer, offset + 4, (ushort)ServerType); + LittleEndianWriter.WriteUInt16(buffer, offset + 6, (ushort)ReferralEntryFlags); + ByteWriter.WriteNullTerminatedUTF16String(buffer, offset + 8, ShareName); return buffer; } @@ -55,5 +54,13 @@ public override int Length return FixedLength + (ShareName.Length + 1) * 2; } } + + public override int StringsLength + { + get + { + return 0; + } + } } } diff --git a/SMBLibrary/DFS/DfsReferralEntryV2.cs b/SMBLibrary/DFS/DfsReferralEntryV2.cs index b919a70d..26401084 100644 --- a/SMBLibrary/DFS/DfsReferralEntryV2.cs +++ b/SMBLibrary/DFS/DfsReferralEntryV2.cs @@ -51,22 +51,22 @@ public DfsReferralEntryV2(byte[] buffer, ref int offset) offset += Size; } - public override byte[] GetBytes() + public override byte[] WriteBytes(byte[] buffer, int offset, int stringsOffset) { - byte[] buffer = new byte[Length]; - LittleEndianWriter.WriteUInt16(buffer, 0, VersionNumber); - LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)buffer.Length); - LittleEndianWriter.WriteUInt16(buffer, 4, (ushort)ServerType); - LittleEndianWriter.WriteUInt16(buffer, 6, (ushort)ReferralEntryFlags); - LittleEndianWriter.WriteUInt32(buffer, 8, Proximity); - LittleEndianWriter.WriteUInt32(buffer, 12, TimeToLive); + LittleEndianWriter.WriteUInt16(buffer, offset + 0, VersionNumber); + LittleEndianWriter.WriteUInt16(buffer, offset + 2, (ushort)this.Length); + LittleEndianWriter.WriteUInt16(buffer, offset + 4, (ushort)ServerType); + LittleEndianWriter.WriteUInt16(buffer, offset + 6, (ushort)ReferralEntryFlags); + LittleEndianWriter.WriteUInt32(buffer, offset + 8, Proximity); + LittleEndianWriter.WriteUInt32(buffer, offset + 12, TimeToLive); - ushort dfsPathOffset = FixedLength; - ushort dfsAlternatePathOffset = (ushort)(dfsPathOffset + (DfsPath.Length + 1) * 2); - ushort networkAddressOffset = (ushort)(dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2); - LittleEndianWriter.WriteUInt16(buffer, 16, dfsPathOffset); - LittleEndianWriter.WriteUInt16(buffer, 18, dfsAlternatePathOffset); - LittleEndianWriter.WriteUInt16(buffer, 20, networkAddressOffset); + int dfsPathOffset = stringsOffset; + int dfsAlternatePathOffset = (ushort)(dfsPathOffset + (DfsPath.Length + 1) * 2); + int networkAddressOffset = (ushort)(dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2); + // offsets are relative to the start of the referral entry + LittleEndianWriter.WriteUInt16(buffer, 16, (ushort)(dfsPathOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, 18, (ushort)(dfsAlternatePathOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, 20, (ushort)(networkAddressOffset - offset)); ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsPathOffset, DfsPath); ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsAlternatePathOffset, DfsAlternatePath); @@ -79,7 +79,15 @@ public override int Length { get { - return FixedLength + (DfsPath.Length + 1 + DfsAlternatePath.Length + 1 + NetworkAddress.Length + 1) * 2; + return FixedLength; + } + } + + public override int StringsLength + { + get + { + return (DfsPath.Length + 1 + DfsAlternatePath.Length + 1 + NetworkAddress.Length + 1) * 2; } } } diff --git a/SMBLibrary/DFS/DfsReferralEntryV3.cs b/SMBLibrary/DFS/DfsReferralEntryV3.cs index 70fa29fb..9127d1d3 100644 --- a/SMBLibrary/DFS/DfsReferralEntryV3.cs +++ b/SMBLibrary/DFS/DfsReferralEntryV3.cs @@ -92,24 +92,24 @@ public DfsReferralEntryV3(byte[] buffer, ref int offset) offset += Size; } - public override byte[] GetBytes() + public override byte[] WriteBytes(byte[] buffer, int offset, int stringsOffset) { - byte[] buffer = new byte[Length]; - LittleEndianWriter.WriteUInt16(buffer, 0, VersionNumber); - LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)buffer.Length); - LittleEndianWriter.WriteUInt16(buffer, 4, (ushort)ServerType); - LittleEndianWriter.WriteUInt16(buffer, 6, (ushort)ReferralEntryFlags); - LittleEndianWriter.WriteUInt32(buffer, 8, TimeToLive); + LittleEndianWriter.WriteUInt16(buffer, offset + 0, VersionNumber); + LittleEndianWriter.WriteUInt16(buffer, offset + 2, (ushort)this.Length); + LittleEndianWriter.WriteUInt16(buffer, offset + 4, (ushort)ServerType); + LittleEndianWriter.WriteUInt16(buffer, offset + 6, (ushort)ReferralEntryFlags); + LittleEndianWriter.WriteUInt32(buffer, offset + 8, TimeToLive); if (!IsNameListReferral) { - ushort dfsPathOffset = FixedLength + 6 + 16; - ushort dfsAlternatePathOffset = (ushort)(dfsPathOffset + (DfsPath.Length + 1) * 2); - ushort networkAddressOffset = (ushort)(dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2); - LittleEndianWriter.WriteUInt16(buffer, 12, dfsPathOffset); - LittleEndianWriter.WriteUInt16(buffer, 14, dfsAlternatePathOffset); - LittleEndianWriter.WriteUInt16(buffer, 16, networkAddressOffset); - LittleEndianWriter.WriteGuid(buffer, 18, ServiceSiteGuid); + int dfsPathOffset = stringsOffset; + int dfsAlternatePathOffset = dfsPathOffset + (DfsPath.Length + 1) * 2; + int networkAddressOffset = dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2; + // offsets are relative to the start of the referral entry + LittleEndianWriter.WriteUInt16(buffer, offset + 12, (ushort)(dfsPathOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, offset + 14, (ushort)(dfsAlternatePathOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, offset + 16, (ushort)(networkAddressOffset - offset)); + LittleEndianWriter.WriteGuid(buffer, offset + 18, ServiceSiteGuid); ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsPathOffset, DfsPath); ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsAlternatePathOffset, DfsAlternatePath); @@ -117,11 +117,12 @@ public override byte[] GetBytes() } else { - ushort specialNameOffset = FixedLength + 6; - ushort expandedNameOffset = (ushort)(specialNameOffset + (SpecialName.Length + 1) * 2); - LittleEndianWriter.WriteUInt16(buffer, 12, specialNameOffset); - LittleEndianWriter.WriteUInt16(buffer, 14, (ushort)ExpandedNames.Count); - LittleEndianWriter.WriteUInt16(buffer, 16, expandedNameOffset); + int specialNameOffset = stringsOffset; + int expandedNameOffset = specialNameOffset + (SpecialName.Length + 1) * 2; + // offsets are relative to the start of the referral entry + LittleEndianWriter.WriteUInt16(buffer, offset + 12, (ushort)(specialNameOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, offset + 14, (ushort)ExpandedNames.Count); + LittleEndianWriter.WriteUInt16(buffer, offset + 16, (ushort)(expandedNameOffset - offset)); ByteWriter.WriteNullTerminatedUTF16String(buffer, specialNameOffset, SpecialName); int currentOffset = expandedNameOffset; @@ -141,11 +142,26 @@ public override int Length { if (!IsNameListReferral) { - return FixedLength + 6 + 16 + (DfsPath.Length + 1 + DfsAlternatePath.Length + 1 + NetworkAddress.Length + 1) * 2; + return FixedLength + 6 + 16; } else { - int length = FixedLength + 6 + (SpecialName.Length + 1) * 2; + return FixedLength + 6; + } + } + } + + public override int StringsLength + { + get + { + if (!IsNameListReferral) + { + return (DfsPath.Length + 1 + DfsAlternatePath.Length + 1 + NetworkAddress.Length + 1) * 2; + } + else + { + int length = (SpecialName.Length + 1) * 2; for (int nameIndex = 0; nameIndex < ExpandedNames.Count; nameIndex++) { length += (ExpandedNames[nameIndex].Length + 1) * 2; diff --git a/SMBLibrary/DFS/ResponseGetDfsReferral.cs b/SMBLibrary/DFS/ResponseGetDfsReferral.cs index 6a223e03..25405644 100644 --- a/SMBLibrary/DFS/ResponseGetDfsReferral.cs +++ b/SMBLibrary/DFS/ResponseGetDfsReferral.cs @@ -72,7 +72,13 @@ public byte[] GetBytes() { length += entry.Length; } - + + int stringsOffset = length; + foreach (DfsReferralEntry entry in ReferralEntries) + { + length += entry.StringsLength; + } + byte[] buffer = new byte[length]; LittleEndianWriter.WriteUInt16(buffer, 0, PathConsumed); LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)ReferralEntries.Count); @@ -81,8 +87,9 @@ public byte[] GetBytes() int offset = HeaderSize; foreach (DfsReferralEntry entry in ReferralEntries) { - ByteWriter.WriteBytes(buffer, offset, entry.GetBytes()); + entry.WriteBytes(buffer, offset, stringsOffset); offset += entry.Length; + stringsOffset += entry.StringsLength; } return buffer; } From bc99e21b26b2a9ecbcb72466ecadf44bd7f785c5 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 15 Dec 2025 23:15:57 +0200 Subject: [PATCH 181/247] DFS: Implement RequestGetDfsReferralEx --- .../DFS/RequestGetDfsReferralExTests.cs | 45 +++++++++++++ .../DFS/Enums/RequestGetDfsReferralExFlags.cs | 10 +++ SMBLibrary/DFS/RequestGetDfsReferralEx.cs | 65 +++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 SMBLibrary.Tests/DFS/RequestGetDfsReferralExTests.cs create mode 100644 SMBLibrary/DFS/Enums/RequestGetDfsReferralExFlags.cs create mode 100644 SMBLibrary/DFS/RequestGetDfsReferralEx.cs diff --git a/SMBLibrary.Tests/DFS/RequestGetDfsReferralExTests.cs b/SMBLibrary.Tests/DFS/RequestGetDfsReferralExTests.cs new file mode 100644 index 00000000..9b51b3ef --- /dev/null +++ b/SMBLibrary.Tests/DFS/RequestGetDfsReferralExTests.cs @@ -0,0 +1,45 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.DFS; + +namespace SMBLibrary.Tests.DFS +{ + [TestClass] + public class RequestGetDfsReferralExTests + { + [TestMethod] + public void ParseRequestGetDfsReferralEx() + { + byte[] buffer = new byte[] + { + 0x04, 0x00, 0x01, 0x00, 0x56, 0x00, 0x00, 0x00, 0x22, 0x00, 0x5c, 0x00, 0x4c, 0x00, 0x41, 0x00, + 0x42, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x5c, 0x00, + 0x46, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x30, 0x00, 0x44, 0x00, + 0x65, 0x00, 0x66, 0x00, 0x61, 0x00, 0x75, 0x00, 0x6c, 0x00, 0x74, 0x00, 0x2d, 0x00, 0x46, 0x00, + 0x69, 0x00, 0x72, 0x00, 0x73, 0x00, 0x74, 0x00, 0x2d, 0x00, 0x53, 0x00, 0x69, 0x00, 0x74, 0x00, + 0x65, 0x00, 0x2d, 0x00, 0x4e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00 + }; + + RequestGetDfsReferralEx request = new RequestGetDfsReferralEx(buffer); + Assert.AreEqual(4, request.MaxReferralLevel); + Assert.AreEqual(RequestGetDfsReferralExFlags.SiteName, request.Flags); + Assert.AreEqual(@"\LAB.LOCAL\Files", request.RequestFileName); + Assert.AreEqual("Default-First-Site-Name", request.SiteName); + } + + [TestMethod] + public void Parse_RequestGetDfsReferralEx_GetBytes() + { + RequestGetDfsReferralEx request = new RequestGetDfsReferralEx(); + request.MaxReferralLevel = 4; + request.Flags = RequestGetDfsReferralExFlags.SiteName; + request.RequestFileName = @"\LAB.LOCAL\Files"; + request.SiteName = @"Default-First-Site-Name"; + + request = new RequestGetDfsReferralEx(request.GetBytes()); + Assert.AreEqual(4, request.MaxReferralLevel); + Assert.AreEqual(RequestGetDfsReferralExFlags.SiteName, request.Flags); + Assert.AreEqual(@"\LAB.LOCAL\Files", request.RequestFileName); + Assert.AreEqual("Default-First-Site-Name", request.SiteName); + } + } +} diff --git a/SMBLibrary/DFS/Enums/RequestGetDfsReferralExFlags.cs b/SMBLibrary/DFS/Enums/RequestGetDfsReferralExFlags.cs new file mode 100644 index 00000000..d3259150 --- /dev/null +++ b/SMBLibrary/DFS/Enums/RequestGetDfsReferralExFlags.cs @@ -0,0 +1,10 @@ +using System; + +namespace SMBLibrary.DFS +{ + [Flags] + public enum RequestGetDfsReferralExFlags : ushort + { + SiteName = 0x0001, + } +} diff --git a/SMBLibrary/DFS/RequestGetDfsReferralEx.cs b/SMBLibrary/DFS/RequestGetDfsReferralEx.cs new file mode 100644 index 00000000..01527774 --- /dev/null +++ b/SMBLibrary/DFS/RequestGetDfsReferralEx.cs @@ -0,0 +1,65 @@ +/* Copyright (C) 2025 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 Utilities; + +namespace SMBLibrary.DFS +{ + /// + /// [MS-DFSC] 2.2.3 REQ_GET_DFS_REFERRAL_EX + /// Extended DFS referral request that supports site-aware referrals. + /// + public class RequestGetDfsReferralEx + { + public ushort MaxReferralLevel; + public RequestGetDfsReferralExFlags Flags; + public string RequestFileName; // Unicode + public string SiteName; // Optional, Unicode, null-terminated (when flag is set) + + public RequestGetDfsReferralEx() + { + } + + public RequestGetDfsReferralEx(byte[] buffer) + { + MaxReferralLevel = LittleEndianConverter.ToUInt16(buffer, 0); + Flags = (RequestGetDfsReferralExFlags)LittleEndianConverter.ToUInt16(buffer, 2); + uint requestDataLength = LittleEndianConverter.ToUInt32(buffer, 4); + ushort requestFileNameLength = LittleEndianConverter.ToUInt16(buffer, 8); + int dataOffset = 10; + RequestFileName = ByteReader.ReadNullTerminatedUTF16String(buffer, ref dataOffset); + if ((Flags & RequestGetDfsReferralExFlags.SiteName) != 0) + { + ushort siteNameLength = LittleEndianReader.ReadUInt16(buffer, ref dataOffset); + SiteName = ByteReader.ReadNullTerminatedUTF16String(buffer, ref dataOffset); + } + } + + public byte[] GetBytes() + { + int length = 10 + RequestFileName.Length * 2 + 2; + if ((Flags & RequestGetDfsReferralExFlags.SiteName) != 0) + { + length += 2 + SiteName.Length * 2 + 2; + } + + byte[] buffer = new byte[length]; + LittleEndianWriter.WriteUInt16(buffer, 0, MaxReferralLevel); + LittleEndianWriter.WriteUInt16(buffer, 2, (ushort)Flags); + LittleEndianWriter.WriteUInt32(buffer, 4, (uint)(length - 8)); + LittleEndianWriter.WriteUInt16(buffer, 8, (ushort)RequestFileName.Length); + int dataOffset = 10; + ByteWriter.WriteNullTerminatedUTF16String(buffer, ref dataOffset, RequestFileName); + if ((Flags & RequestGetDfsReferralExFlags.SiteName) != 0) + { + LittleEndianWriter.WriteUInt16(buffer, ref dataOffset, (ushort)SiteName.Length); + ByteWriter.WriteNullTerminatedUTF16String(buffer, ref dataOffset, SiteName); + } + + return buffer; + } + } +} From 38d9e2ba71f99abf0bdd72899e15e614c22b2559 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 15 Dec 2025 23:26:22 +0200 Subject: [PATCH 182/247] DFS: ResponseGetDfsReferralTests: Added test for response with multiple referral entries --- .../DFS/ResponseGetDfsReferralTests.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs b/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs index c6504d97..0ec6b103 100644 --- a/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs +++ b/SMBLibrary.Tests/DFS/ResponseGetDfsReferralTests.cs @@ -56,6 +56,59 @@ public void ParseResponseGetDfsReferralWithSingleDfsReferralEntryV4() Assert.AreEqual(Guid.Empty, entry.ServiceSiteGuid); } + [TestMethod] + public void ParseResponseGetDfsReferralWithMultipleDfsReferralEntryV4() + { + // Arrange + byte[] buffer = new byte[] + { + 0x3c, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x2c, 0x01, 0x00, 0x00, 0x44, 0x00, 0x82, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x2c, 0x01, 0x00, 0x00, 0x22, 0x00, 0x60, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x4c, 0x00, + 0x41, 0x00, 0x42, 0x00, 0x2d, 0x00, 0x44, 0x00, 0x43, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x4c, 0x00, + 0x41, 0x00, 0x42, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, + 0x5c, 0x00, 0x46, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x73, 0x00, 0x5c, 0x00, 0x53, 0x00, + 0x61, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x4c, 0x00, 0x41, 0x00, + 0x42, 0x00, 0x2d, 0x00, 0x44, 0x00, 0x43, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x41, 0x00, + 0x42, 0x00, 0x2e, 0x00, 0x4c, 0x00, 0x4f, 0x00, 0x43, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x5c, 0x00, + 0x46, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x65, 0x00, 0x73, 0x00, 0x5c, 0x00, 0x53, 0x00, 0x61, 0x00, + 0x6c, 0x00, 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x42, 0x00, + 0x2d, 0x00, 0x46, 0x00, 0x53, 0x00, 0x32, 0x00, 0x5c, 0x00, 0x53, 0x00, 0x61, 0x00, 0x6c, 0x00, + 0x65, 0x00, 0x73, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x4c, 0x00, 0x41, 0x00, 0x42, 0x00, 0x2d, 0x00, + 0x46, 0x00, 0x53, 0x00, 0x31, 0x00, 0x5c, 0x00, 0x53, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x65, 0x00, + 0x73, 0x00, 0x00, 0x00 + + }; + + // Act + ResponseGetDfsReferral response = new ResponseGetDfsReferral(buffer); + + // Assert + Assert.AreEqual(60, response.PathConsumed); + Assert.AreEqual(DfsReferralHeaderFlags.StorageServers, response.ReferralHeaderFlags); + Assert.AreEqual(2, response.ReferralEntries.Count); + Assert.IsInstanceOfType(response.ReferralEntries[0], typeof(DfsReferralEntryV4)); + Assert.IsInstanceOfType(response.ReferralEntries[1], typeof(DfsReferralEntryV4)); + + DfsReferralEntryV4 entry1 = (DfsReferralEntryV4)response.ReferralEntries[0]; + Assert.AreEqual((uint)300, entry1.TimeToLive); + Assert.AreEqual(DfsReferralEntryFlags.TargetSetBoundary, entry1.ReferralEntryFlags); + Assert.AreEqual("\\LAB-DC1.LAB.LOCAL\\Files\\Sales", entry1.DfsPath); + Assert.AreEqual("\\LAB-DC1.LAB.LOCAL\\Files\\Sales", entry1.DfsAlternatePath); + Assert.AreEqual("\\LAB-FS2\\Sales", entry1.NetworkAddress); + Assert.AreEqual(Guid.Empty, entry1.ServiceSiteGuid); + + DfsReferralEntryV4 entry2 = (DfsReferralEntryV4)response.ReferralEntries[1]; + Assert.AreEqual((uint)300, entry2.TimeToLive); + Assert.AreEqual(DfsReferralEntryFlags.None, entry2.ReferralEntryFlags); + Assert.AreEqual("\\LAB-DC1.LAB.LOCAL\\Files\\Sales", entry2.DfsPath); + Assert.AreEqual("\\LAB-DC1.LAB.LOCAL\\Files\\Sales", entry2.DfsAlternatePath); + Assert.AreEqual("\\LAB-FS1\\Sales", entry2.NetworkAddress); + Assert.AreEqual(Guid.Empty, entry2.ServiceSiteGuid); + } + [TestMethod] public void Parse_ResponseGetDfsReferralWithSingleDfsReferralEntryV4_GetBytes() { From 58b3d1acf635e14e3d4c919caa3727b25e129f63 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 16 Dec 2025 10:33:46 +0200 Subject: [PATCH 183/247] DFS: DfsReferralEntryV2.WriteBytes: Correct write offset --- SMBLibrary/DFS/DfsReferralEntryV2.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/DFS/DfsReferralEntryV2.cs b/SMBLibrary/DFS/DfsReferralEntryV2.cs index 26401084..c29474d0 100644 --- a/SMBLibrary/DFS/DfsReferralEntryV2.cs +++ b/SMBLibrary/DFS/DfsReferralEntryV2.cs @@ -64,9 +64,9 @@ public override byte[] WriteBytes(byte[] buffer, int offset, int stringsOffset) int dfsAlternatePathOffset = (ushort)(dfsPathOffset + (DfsPath.Length + 1) * 2); int networkAddressOffset = (ushort)(dfsAlternatePathOffset + (DfsAlternatePath.Length + 1) * 2); // offsets are relative to the start of the referral entry - LittleEndianWriter.WriteUInt16(buffer, 16, (ushort)(dfsPathOffset - offset)); - LittleEndianWriter.WriteUInt16(buffer, 18, (ushort)(dfsAlternatePathOffset - offset)); - LittleEndianWriter.WriteUInt16(buffer, 20, (ushort)(networkAddressOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, offset + 16, (ushort)(dfsPathOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, offset + 18, (ushort)(dfsAlternatePathOffset - offset)); + LittleEndianWriter.WriteUInt16(buffer, offset + 20, (ushort)(networkAddressOffset - offset)); ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsPathOffset, DfsPath); ByteWriter.WriteNullTerminatedUTF16String(buffer, dfsAlternatePathOffset, DfsAlternatePath); From b349a07015ff1aff97cbfc2d690d0d26b07bb397 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 9 Jan 2026 10:54:00 +0200 Subject: [PATCH 184/247] NTDirectoryFileSystemTests.TestCancel: Skip test when platform is not Windows --- .../NTFileStore/NTDirectoryFileSystemTests.cs | 14 ++++++++++- .../NTFileStore/NTFileStoreTests.cs | 23 +++++++++++-------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs b/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs index cdfacbcc..591572b5 100644 --- a/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs +++ b/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs @@ -1,10 +1,11 @@ -/* Copyright (C) 2019-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2019-2025 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.IO; +using System.Runtime.InteropServices; using Microsoft.VisualStudio.TestTools.UnitTesting; using SMBLibrary.Win32; @@ -26,5 +27,16 @@ static NTDirectoryFileSystemTests() public NTDirectoryFileSystemTests() : base(new NTDirectoryFileSystem(TestDirectoryPath)) { } + + [TestMethod] + public override void TestCancel() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Assert.Inconclusive(); + } + + base.TestCancel(); + } } } diff --git a/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs b/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs index 23d43495..a99142f7 100644 --- a/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs +++ b/SMBLibrary.Tests/NTFileStore/NTFileStoreTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2019-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2019-2025 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, @@ -20,18 +20,13 @@ public abstract class NTFileStoreTests public NTFileStoreTests(INTFileStore fileStore) { m_fileStore = fileStore; - - object handle; - FileStatus fileStatus; - NTStatus status = m_fileStore.CreateFile(out handle, out fileStatus, TestDirName, AccessMask.GENERIC_ALL, FileAttributes.Directory, ShareAccess.Read, CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_DIRECTORY_FILE, null); - Assert.IsTrue(status == NTStatus.STATUS_SUCCESS); - status = m_fileStore.CloseFile(handle); - Assert.IsTrue(status == NTStatus.STATUS_SUCCESS); } [TestMethod] - public void TestCancel() + public virtual void TestCancel() { + CreateTestDirectory(); + object handle; FileStatus fileStatus; m_fileStore.CreateFile(out handle, out fileStatus, TestDirName, AccessMask.GENERIC_ALL, FileAttributes.Directory, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); @@ -54,5 +49,15 @@ private void OnNotifyChangeCompleted(NTStatus status, byte[] buffer, object cont { m_notifyChangeStatus = status; } + + private void CreateTestDirectory() + { + object handle; + FileStatus fileStatus; + NTStatus status = m_fileStore.CreateFile(out handle, out fileStatus, TestDirName, AccessMask.GENERIC_ALL, FileAttributes.Directory, ShareAccess.Read, CreateDisposition.FILE_OPEN_IF, CreateOptions.FILE_DIRECTORY_FILE, null); + Assert.IsTrue(status == NTStatus.STATUS_SUCCESS); + status = m_fileStore.CloseFile(handle); + Assert.IsTrue(status == NTStatus.STATUS_SUCCESS); + } } } From 254835cd5b5e331096ad5555cb9e29a6a98391fc Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 12 Jan 2026 22:06:48 +0200 Subject: [PATCH 185/247] NTLMCryptography: Delete trailing whitespace --- SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index e47bc4ef..a829c8ee 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -78,7 +78,7 @@ public static ICryptoTransform CreateWeakDesEncryptor(CipherMode mode, byte[] rg DES des = DES.Create(); des.Mode = mode; ICryptoTransform transform; - if (DES.IsWeakKey(rgbKey) || DES.IsSemiWeakKey(rgbKey)) + if (DES.IsWeakKey(rgbKey) || DES.IsSemiWeakKey(rgbKey)) { #if NETSTANDARD2_0 MethodInfo getTransformCoreMethodInfo = des.GetType().GetMethod("CreateTransformCore", BindingFlags.NonPublic | BindingFlags.Static); From e4411d3f2cb690f0118197d02d5e5ee6029a9945 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 12 Jan 2026 22:10:15 +0200 Subject: [PATCH 186/247] SMB1: Implemented SMB_COM_QUERY_INFORMATION_DISK request and response --- .../Commands/QueryInformationDiskRequest.cs | 32 ++++++++++ .../Commands/QueryInformationDiskResponse.cs | 59 +++++++++++++++++++ SMBLibrary/SMB1/Commands/SMB1Command.cs | 19 +++++- SMBLibrary/SMB1/Enums/CommandName.cs | 1 + 4 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 SMBLibrary/SMB1/Commands/QueryInformationDiskRequest.cs create mode 100644 SMBLibrary/SMB1/Commands/QueryInformationDiskResponse.cs diff --git a/SMBLibrary/SMB1/Commands/QueryInformationDiskRequest.cs b/SMBLibrary/SMB1/Commands/QueryInformationDiskRequest.cs new file mode 100644 index 00000000..ebfaec61 --- /dev/null +++ b/SMBLibrary/SMB1/Commands/QueryInformationDiskRequest.cs @@ -0,0 +1,32 @@ +/* Copyright (C) 2025 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. + */ +namespace SMBLibrary.SMB1 +{ + /// + /// SMB_COM_QUERY_INFORMATION_DISK Request. + /// This command is deprecated. + /// This command is used by Windows 98 SE. + /// + public class QueryInformationDiskRequest : SMB1Command + { + public QueryInformationDiskRequest() + { + } + + public QueryInformationDiskRequest(byte[] buffer, int offset) : base(buffer, offset, false) + { + } + + public override CommandName CommandName + { + get + { + return CommandName.SMB_COM_QUERY_INFORMATION_DISK; + } + } + } +} diff --git a/SMBLibrary/SMB1/Commands/QueryInformationDiskResponse.cs b/SMBLibrary/SMB1/Commands/QueryInformationDiskResponse.cs new file mode 100644 index 00000000..e443a438 --- /dev/null +++ b/SMBLibrary/SMB1/Commands/QueryInformationDiskResponse.cs @@ -0,0 +1,59 @@ +/* Copyright (C) 2025 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 Utilities; + +namespace SMBLibrary.SMB1 +{ + /// + /// SMB_COM_QUERY_INFORMATION_DISK Response. + /// This command is deprecated. + /// This command is used by Windows 98 SE. + /// + public class QueryInformationDiskResponse : SMB1Command + { + public const int ParameterLength = 10; + // Parameters: + public ushort TotalUnits; + public ushort BlocksPerUnit; + public ushort BlockSize; + public ushort FreeUnits; + public ushort Reserved; + + public QueryInformationDiskResponse() + { + } + + public QueryInformationDiskResponse(byte[] buffer, int offset) : base(buffer, offset, false) + { + TotalUnits = LittleEndianConverter.ToUInt16(this.SMBParameters, 0); + BlocksPerUnit = LittleEndianConverter.ToUInt16(this.SMBParameters, 2); + BlockSize = LittleEndianConverter.ToUInt16(this.SMBParameters, 4); + FreeUnits = LittleEndianConverter.ToUInt16(this.SMBParameters, 6); + Reserved = LittleEndianConverter.ToUInt16(this.SMBParameters, 8); + } + + public override byte[] GetBytes(bool isUnicode) + { + this.SMBParameters = new byte[ParameterLength]; + LittleEndianWriter.WriteUInt16(this.SMBParameters, 0, TotalUnits); + LittleEndianWriter.WriteUInt16(this.SMBParameters, 2, BlocksPerUnit); + LittleEndianWriter.WriteUInt16(this.SMBParameters, 4, BlockSize); + LittleEndianWriter.WriteUInt16(this.SMBParameters, 6, FreeUnits); + LittleEndianWriter.WriteUInt16(this.SMBParameters, 8, Reserved); + + return base.GetBytes(isUnicode); + } + + public override CommandName CommandName + { + get + { + return CommandName.SMB_COM_QUERY_INFORMATION_DISK; + } + } + } +} diff --git a/SMBLibrary/SMB1/Commands/SMB1Command.cs b/SMBLibrary/SMB1/Commands/SMB1Command.cs index 6b06cefc..fccc9f02 100644 --- a/SMBLibrary/SMB1/Commands/SMB1Command.cs +++ b/SMBLibrary/SMB1/Commands/SMB1Command.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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, @@ -154,6 +154,8 @@ public static SMB1Command ReadCommandRequest(byte[] buffer, int offset, CommandN return new LogoffAndXRequest(buffer, offset); case CommandName.SMB_COM_TREE_CONNECT_ANDX: return new TreeConnectAndXRequest(buffer, offset, isUnicode); + case CommandName.SMB_COM_QUERY_INFORMATION_DISK: + return new QueryInformationDiskRequest(buffer, offset); case CommandName.SMB_COM_NT_TRANSACT: return new NTTransactRequest(buffer, offset); case CommandName.SMB_COM_NT_TRANSACT_SECONDARY: @@ -443,6 +445,21 @@ public static SMB1Command ReadCommandResponse(byte[] buffer, int offset, Command throw new InvalidDataException(); } } + case CommandName.SMB_COM_QUERY_INFORMATION_DISK: + { + if (wordCount * 2 == QueryInformationResponse.ParameterLength) + { + return new QueryInformationDiskResponse(buffer, offset); + } + else if (wordCount == 0) + { + return new ErrorResponse(commandName); + } + else + { + throw new InvalidDataException(); + } + } case CommandName.SMB_COM_NT_TRANSACT: { if (wordCount * 2 == NTTransactInterimResponse.ParametersLength) diff --git a/SMBLibrary/SMB1/Enums/CommandName.cs b/SMBLibrary/SMB1/Enums/CommandName.cs index 591b5860..41af975b 100644 --- a/SMBLibrary/SMB1/Enums/CommandName.cs +++ b/SMBLibrary/SMB1/Enums/CommandName.cs @@ -32,6 +32,7 @@ public enum CommandName : byte SMB_COM_SESSION_SETUP_ANDX = 0x73, SMB_COM_LOGOFF_ANDX = 0x74, SMB_COM_TREE_CONNECT_ANDX = 0x75, + SMB_COM_QUERY_INFORMATION_DISK = 0x80, SMB_COM_NT_TRANSACT = 0xA0, SMB_COM_NT_TRANSACT_SECONDARY = 0xA1, SMB_COM_NT_CREATE_ANDX = 0xA2, From 7d4b66bda1c74ce45c343f3c3ca3b9e9f54dc310 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 12 Jan 2026 22:11:14 +0200 Subject: [PATCH 187/247] SMB1: FileStoreResponseHelper: Remove unused using statements --- SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs b/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs index 324b9ca7..64b9fba7 100644 --- a/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs +++ b/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs @@ -1,13 +1,10 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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 System.Text; using SMBLibrary.SMB1; using Utilities; From 0e70ed4ad104b8f103b947c451aad9ce9387a031 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 12 Jan 2026 22:11:47 +0200 Subject: [PATCH 188/247] SMBServer.SMB1: Remove unused using statement --- SMBLibrary/Server/SMBServer.SMB1.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/SMBLibrary/Server/SMBServer.SMB1.cs b/SMBLibrary/Server/SMBServer.SMB1.cs index 0d76dd33..27181dca 100644 --- a/SMBLibrary/Server/SMBServer.SMB1.cs +++ b/SMBLibrary/Server/SMBServer.SMB1.cs @@ -1,10 +1,9 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2025 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 SMBLibrary.NetBios; using SMBLibrary.Server.SMB1; From 432e879d31b3e3816a09def4e857c1a5f55a5ba1 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 12 Jan 2026 22:13:19 +0200 Subject: [PATCH 189/247] Server: SMB1: Add support for SMB_COM_QUERY_INFORMATION_DISK used by Windows 98 --- .../Server/SMB1/FileStoreResponseHelper.cs | 19 +++++++++++++++++++ SMBLibrary/Server/SMBServer.SMB1.cs | 4 ++++ 2 files changed, 23 insertions(+) diff --git a/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs b/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs index 64b9fba7..c4da3ff4 100644 --- a/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs +++ b/SMBLibrary/Server/SMB1/FileStoreResponseHelper.cs @@ -228,5 +228,24 @@ internal static SMB1Command GetSetInformation2Response(SMB1Header header, SetInf state.LogToServer(Severity.Verbose, "Set Information 2 on '{0}{1}' succeeded.", share.Name, openFile.Path); return new SetInformation2Response(); } + + internal static SMB1Command GetQueryInformationDiskResponse(SMB1Header header, QueryInformationDiskRequest request, ISMBShare share, SMB1ConnectionState state) + { + header.Status = share.FileStore.GetFileSystemInformation(out FileSystemInformation fileSystemInfo, FileSystemInformationClass.FileFsSizeInformation); + if (header.Status != NTStatus.STATUS_SUCCESS) + { + state.LogToServer(Severity.Verbose, "GetFileSystemInformation on '{0}' failed. NTStatus: {1}", share.Name, header.Status); + return new ErrorResponse(request.CommandName); + } + + FileFsSizeInformation sizeInformation = (FileFsSizeInformation)fileSystemInfo; + + QueryInformationDiskResponse response = new QueryInformationDiskResponse(); + response.TotalUnits = (ushort)Math.Min(sizeInformation.TotalAllocationUnits, UInt16.MaxValue); + response.BlocksPerUnit = (ushort)sizeInformation.SectorsPerAllocationUnit; + response.BlockSize = (ushort)sizeInformation.BytesPerSector; + response.FreeUnits = (ushort)Math.Min(sizeInformation.AvailableAllocationUnits, UInt16.MaxValue); + return response; + } } } diff --git a/SMBLibrary/Server/SMBServer.SMB1.cs b/SMBLibrary/Server/SMBServer.SMB1.cs index 27181dca..b23d22e4 100644 --- a/SMBLibrary/Server/SMBServer.SMB1.cs +++ b/SMBLibrary/Server/SMBServer.SMB1.cs @@ -243,6 +243,10 @@ private List ProcessSMB1Command(SMB1Header header, SMB1Command comm { return TreeConnectHelper.GetTreeDisconnectResponse(header, (TreeDisconnectRequest)command, share, state); } + else if (command is QueryInformationDiskRequest) + { + return FileStoreResponseHelper.GetQueryInformationDiskResponse(header, (QueryInformationDiskRequest)command, share, state); + } else if (command is TransactionRequest) // Both TransactionRequest and Transaction2Request { return TransactionHelper.GetTransactionResponse(header, (TransactionRequest)command, share, state); From 85ffc2bc0eb3b70c6d6c648d1b86a2fa70589b64 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 16 Feb 2026 21:49:48 +0200 Subject: [PATCH 190/247] SecurityDescriptor: Remove unused using statements --- .../Structures/SecurityInformation/SecurityDescriptor.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs index 4657e10a..a3a9464e 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs @@ -1,11 +1,9 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2026 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 From d08b9de171992a10ebf5a48bc0f1e9e22be0e7cf Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 16 Feb 2026 21:50:30 +0200 Subject: [PATCH 191/247] SecurityDescriptor: Set SelfRelative bit when applicable --- .../Structures/SecurityInformation/SecurityDescriptor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs b/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs index a3a9464e..666303a9 100644 --- a/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs +++ b/SMBLibrary/NTFileStore/Structures/SecurityInformation/SecurityDescriptor.cs @@ -30,6 +30,7 @@ public class SecurityDescriptor public SecurityDescriptor() { Revision = 0x01; + Control = SecurityDescriptorControl.SelfRelative; } public SecurityDescriptor(byte[] buffer, int offset) @@ -97,7 +98,7 @@ public byte[] GetBytes() offset = 0; ByteWriter.WriteByte(buffer, ref offset, Revision); ByteWriter.WriteByte(buffer, ref offset, Sbz1); - LittleEndianWriter.WriteUInt16(buffer, ref offset, (ushort)Control); + LittleEndianWriter.WriteUInt16(buffer, ref offset, (ushort)(Control | SecurityDescriptorControl.SelfRelative)); LittleEndianWriter.WriteUInt32(buffer, ref offset, offsetOwner); LittleEndianWriter.WriteUInt32(buffer, ref offset, offsetGroup); LittleEndianWriter.WriteUInt32(buffer, ref offset, offsetSacl); From 0a185f87a9025323a5e59530ca682914337c7079 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 22 Feb 2026 21:33:19 +0200 Subject: [PATCH 192/247] SMBLibrary 1.5.5.2 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 0ccea3af..1dc84bd3 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.5.1 + 1.5.5.2 1573;1591 SMBLibrary false From 45cc7097bb472b7f1e3212077beba604211cc99f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 26 Feb 2026 12:07:44 +0200 Subject: [PATCH 193/247] FileSystemAttributes: Fix typo, Rename CasePreservedNamed to CasePreservedNames --- .../NTFileSystemAdapter.QueryFileSystem.cs | 7 ++----- .../Enums/FileSystemInformation/FileSystemAttributes.cs | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryFileSystem.cs b/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryFileSystem.cs index ff062746..8c20d99a 100644 --- a/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryFileSystem.cs +++ b/SMBLibrary.Adapters/NTFileSystemAdapter/NTFileSystemAdapter.QueryFileSystem.cs @@ -1,13 +1,10 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2026 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.Adapters { @@ -45,7 +42,7 @@ public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileS case FileSystemInformationClass.FileFsAttributeInformation: { FileFsAttributeInformation information = new FileFsAttributeInformation(); - information.FileSystemAttributes = FileSystemAttributes.CasePreservedNamed | FileSystemAttributes.UnicodeOnDisk; + information.FileSystemAttributes = FileSystemAttributes.CasePreservedNames | FileSystemAttributes.UnicodeOnDisk; information.MaximumComponentNameLength = 255; information.FileSystemName = m_fileSystem.Name; result = information; diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs index ee69af46..c2c295f1 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs @@ -6,7 +6,7 @@ namespace SMBLibrary public enum FileSystemAttributes : uint { CaseSensitiveSearch = 0x0001, // FILE_CASE_SENSITIVE_SEARCH - CasePreservedNamed = 0x0002, // FILE_CASE_PRESERVED_NAMES + CasePreservedNames = 0x0002, // FILE_CASE_PRESERVED_NAMES UnicodeOnDisk = 0x0004, // FILE_UNICODE_ON_DISK PersistentACLs = 0x0008, // FILE_PERSISTENT_ACLS FileCompression = 0x0010, // FILE_FILE_COMPRESSION From 46d47812e15b4ddffbc40baf8f071105907f85af Mon Sep 17 00:00:00 2001 From: Logan Stromberg Date: Thu, 26 Feb 2026 10:21:05 -0800 Subject: [PATCH 194/247] FileAccessMask: Add missing FILE_DELETE_CHILD (#333) --- SMBLibrary/NTFileStore/Enums/AccessMask/FileAccessMask.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/SMBLibrary/NTFileStore/Enums/AccessMask/FileAccessMask.cs b/SMBLibrary/NTFileStore/Enums/AccessMask/FileAccessMask.cs index 7c032650..99795bf5 100644 --- a/SMBLibrary/NTFileStore/Enums/AccessMask/FileAccessMask.cs +++ b/SMBLibrary/NTFileStore/Enums/AccessMask/FileAccessMask.cs @@ -15,6 +15,7 @@ public enum FileAccessMask : uint FILE_READ_EA = 0x00000008, FILE_WRITE_EA = 0x00000010, FILE_EXECUTE = 0x00000020, + FILE_DELETE_CHILD = 0x00000040, FILE_READ_ATTRIBUTES = 0x00000080, FILE_WRITE_ATTRIBUTES = 0x00000100, DELETE = 0x00010000, From 458f30b656dbb7e9ce20846d9456c6c178d4d59b Mon Sep 17 00:00:00 2001 From: Logan Stromberg Date: Thu, 26 Feb 2026 10:21:52 -0800 Subject: [PATCH 195/247] FileSystemAttributes: Add missing enum values (#335) --- .../Enums/FileSystemInformation/FileSystemAttributes.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs index c2c295f1..2a2647fa 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs @@ -14,6 +14,8 @@ public enum FileSystemAttributes : uint SupportsSparseFiles = 0x0040, // FILE_SUPPORTS_SPARSE_FILES SupportsReparsePoints = 0x0080, // FILE_SUPPORTS_REPARSE_POINTS SupportsRemoteStorage = 0x0100, // FILE_SUPPORTS_REMOTE_STORAGE + ReturnsCleanupResultInfo = 0x0200, // FILE_RETURNS_CLEANUP_RESULT_INFO + SupportsPOSIXUnlinkRename = 0x0400, // FILE_SUPPORTS_POSIX_UNLINK_RENAME VolumeIsCompressed = 0x8000, // FILE_VOLUME_IS_COMPRESSED SupportsObjectIDs = 0x00010000, // FILE_SUPPORTS_OBJECT_IDS SupportsEncryption = 0x00020000, // FILE_SUPPORTS_ENCRYPTION @@ -25,5 +27,8 @@ public enum FileSystemAttributes : uint SupportsExtendedAttributes = 0x00800000, // FILE_SUPPORTS_EXTENDED_ATTRIBUTES SupportsOpenByFileID = 0x01000000, // FILE_SUPPORTS_OPEN_BY_FILE_ID SupportsUSNJournal = 0x02000000, // FILE_SUPPORTS_USN_JOURNAL + SupportsIntegrityStreams = 0x04000000, // FILE_SUPPORT_INTEGRITY_STREAMS + SupportsBlockRefCounting = 0x08000000, // FILE_SUPPORTS_BLOCK_REFCOUNTING + SupportsSparseVDL = 0x10000000, // FILE_SUPPORTS_SPARSE_VDL } } From 2a0b2ce04708d97fd93e8f25271ae25843e59a10 Mon Sep 17 00:00:00 2001 From: Logan Stromberg Date: Thu, 26 Feb 2026 10:23:16 -0800 Subject: [PATCH 196/247] DeviceCharacteristics: Add additional enum values (#334) --- .../DeviceCharacteristics.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs index 4cc1dddc..e8abc633 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs @@ -5,12 +5,17 @@ namespace SMBLibrary [Flags] public enum DeviceCharacteristics : uint { - RemovableMedia = 0x0001, // FILE_REMOVABLE_MEDIA - ReadOnlyDevice = 0x0002, // FILE_READ_ONLY_DEVICE - FloppyDiskette = 0x0004, // FILE_FLOPPY_DISKETTE - WriteOnceMedia = 0x0008, // FILE_WRITE_ONCE_MEDIA - RemoteDevice = 0x0010, // FILE_REMOTE_DEVICE - IsMounted = 0x0020, // FILE_DEVICE_IS_MOUNTED - VirtualVolume = 0x0040, // FILE_VIRTUAL_VOLUME + RemovableMedia = 0x0001, // FILE_REMOVABLE_MEDIA + ReadOnlyDevice = 0x0002, // FILE_READ_ONLY_DEVICE + FloppyDiskette = 0x0004, // FILE_FLOPPY_DISKETTE + WriteOnceMedia = 0x0008, // FILE_WRITE_ONCE_MEDIA + RemoteDevice = 0x0010, // FILE_REMOTE_DEVICE + IsMounted = 0x0020, // FILE_DEVICE_IS_MOUNTED + VirtualVolume = 0x0040, // FILE_VIRTUAL_VOLUME + SecureOpen = 0x0100, // FILE_DEVICE_SECURE_OPEN + TerminalServicesDevice = 0x1000, // FILE_CHARACTERISTIC_TS_DEVICE + WebDAVDevice = 0x2000, // FILE_CHARACTERISTIC_WEBDAV_DEVICE + PortableDevice = 0x4000, // FILE_PORTABLE_DEVICE + AllowAppContainerTraversal = 0x20000, // FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL } } From 49102521c8fc785042ce71d32cfc3f35d23ba536 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Thu, 26 Feb 2026 21:28:09 +0200 Subject: [PATCH 197/247] ClientExamples: Add Kerberos authentication example --- ClientExamples.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/ClientExamples.md b/ClientExamples.md index ebf5c29b..dd9a5172 100644 --- a/ClientExamples.md +++ b/ClientExamples.md @@ -182,3 +182,36 @@ if (status == NTStatus.STATUS_SUCCESS) } status = fileStore.Disconnect(); ``` + +Cross-platform Kerberos authentication: +======================================= +You can have cross-platform Kerberos login support by creating a class that implements IAuthenticationClient. +[Kerberos.NET](https://github.com/dotnet/Kerberos.NET) can easily be used to implement IAuthenticationClient. +Note that in order for Kerberos.NET to work on non-Windows platforms, you must provide a cross-platform implementation of IKerberosDnsQuery (and register it using DnsQuery.RegisterImplementation) +[DnsClient.NET](https://github.com/MichaCo/DnsClient.NET) can easily be used to implement IKerberosDnsQuery. + +```cs +public class KerberosNetAuthenticationClient : IAuthenticationClient +{ + private readonly KerberosClient m_kerberosClient; + private readonly string m_spn; + private byte[] m_sessionKey; + + public KerberosNetAuthenticationClient(string user, string password, string domain, string host) + { + m_kerberosClient = new KerberosClient(); + m_kerberosClient.Authenticate(new KerberosPasswordCredential(user, password, domain)).Wait(); + m_spn = $"cifs/{host}"; + } + + public byte[] InitializeSecurityContext(byte[] inputToken) + { + KrbApReq ticket = m_kerberosClient.GetServiceTicket(m_spn).GetAwaiter().GetResult(); + KerberosClientCacheEntry cachedItem = (KerberosClientCacheEntry)m_kerberosClient.Cache.GetCacheItem(m_spn); + m_sessionKey = cachedItem.SessionKey.KeyValue.ToArray(); + return ticket.EncodeGssApi().ToArray(); + } + + public byte[] GetSessionKey() => m_sessionKey; +} +``` \ No newline at end of file From 15a4d6bace709bdf2fe91e81217cdf083ded5131 Mon Sep 17 00:00:00 2001 From: Logan Stromberg Date: Thu, 26 Feb 2026 11:35:03 -0800 Subject: [PATCH 198/247] NTFileStore: Added summary to several enums (#336) --- .../Enums/FileSystemInformation/DeviceCharacteristics.cs | 3 +++ .../Enums/FileSystemInformation/FileSystemAttributes.cs | 3 +++ .../Enums/FileSystemInformation/FileSystemControlFlags.cs | 3 +++ .../Enums/FileSystemInformation/SectorSizeInformationFlags.cs | 3 +++ SMBLibrary/NTFileStore/Enums/NotifyChangeFilter.cs | 4 ++++ 5 files changed, 16 insertions(+) diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs index e8abc633..8b139783 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/DeviceCharacteristics.cs @@ -2,6 +2,9 @@ namespace SMBLibrary { + /// + /// [MS-FSCC] 2.5.10 - FileFsDeviceInformation + /// [Flags] public enum DeviceCharacteristics : uint { diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs index 2a2647fa..f62908be 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemAttributes.cs @@ -2,6 +2,9 @@ namespace SMBLibrary { + /// + /// [MS-FSCC] 2.5.1 - FileFsAttributeInformation + /// [Flags] public enum FileSystemAttributes : uint { diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemControlFlags.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemControlFlags.cs index 9e97e064..6f985102 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemControlFlags.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/FileSystemControlFlags.cs @@ -1,6 +1,9 @@ namespace SMBLibrary { + /// + /// [MS-FSCC] 2.5.2 - FileFsControlInformation + /// public enum FileSystemControlFlags : uint { QuotaTrack = 0x00000001, // FILE_VC_QUOTA_TRACK diff --git a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/SectorSizeInformationFlags.cs b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/SectorSizeInformationFlags.cs index 7720a44b..1080f614 100644 --- a/SMBLibrary/NTFileStore/Enums/FileSystemInformation/SectorSizeInformationFlags.cs +++ b/SMBLibrary/NTFileStore/Enums/FileSystemInformation/SectorSizeInformationFlags.cs @@ -2,6 +2,9 @@ namespace SMBLibrary { + /// + /// [MS-FSCC] 2.5.7 - FileFsSectorSizeInformation + /// [Flags] public enum SectorSizeInformationFlags : uint { diff --git a/SMBLibrary/NTFileStore/Enums/NotifyChangeFilter.cs b/SMBLibrary/NTFileStore/Enums/NotifyChangeFilter.cs index 4ef3b42f..82edad51 100644 --- a/SMBLibrary/NTFileStore/Enums/NotifyChangeFilter.cs +++ b/SMBLibrary/NTFileStore/Enums/NotifyChangeFilter.cs @@ -2,6 +2,10 @@ namespace SMBLibrary { + /// + /// [MS-CIFS] 2.2.7.4.1 CompletionFilter + /// [MS-SMB2] 2.2.35 CompletionFilter + /// [Flags] public enum NotifyChangeFilter : uint { From 039dd088d2cb74a1d1150fb0d8959cd3181e704c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Feb 2026 18:12:31 +0200 Subject: [PATCH 199/247] SMB2Header: Remove unused using statement --- SMBLibrary/SMB2/SMB2Header.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SMBLibrary/SMB2/SMB2Header.cs b/SMBLibrary/SMB2/SMB2Header.cs index bcb1b847..5d62967f 100644 --- a/SMBLibrary/SMB2/SMB2Header.cs +++ b/SMBLibrary/SMB2/SMB2Header.cs @@ -4,7 +4,6 @@ * 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 Utilities; namespace SMBLibrary.SMB2 From 049b7fc9eb0bd24a61d8ce2390d50f7c2cf38920 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Feb 2026 18:13:25 +0200 Subject: [PATCH 200/247] SMB2Header: Add SignatureLength constant --- SMBLibrary/SMB2/SMB2Header.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/SMB2/SMB2Header.cs b/SMBLibrary/SMB2/SMB2Header.cs index 5d62967f..4ab8c588 100644 --- a/SMBLibrary/SMB2/SMB2Header.cs +++ b/SMBLibrary/SMB2/SMB2Header.cs @@ -12,6 +12,7 @@ public class SMB2Header { public const int Length = 64; public const int SignatureOffset = 48; + public const int SignatureLength = 16; public static readonly byte[] ProtocolSignature = new byte[] { 0xFE, 0x53, 0x4D, 0x42 }; @@ -35,7 +36,7 @@ public SMB2Header(SMB2CommandName commandName) ProtocolId = ProtocolSignature; StructureSize = Length; Command = commandName; - Signature = new byte[16]; + Signature = new byte[SignatureLength]; } public SMB2Header(byte[] buffer, int offset) @@ -61,7 +62,7 @@ public SMB2Header(byte[] buffer, int offset) SessionID = LittleEndianConverter.ToUInt64(buffer, offset + 40); if ((Flags & SMB2PacketHeaderFlags.Signed) > 0) { - Signature = ByteReader.ReadBytes(buffer, offset + 48, 16); + Signature = ByteReader.ReadBytes(buffer, offset + 48, SignatureLength); } } From 5f5d8b9941b2d361865af0d637c6e2b507050ff3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Feb 2026 18:18:48 +0200 Subject: [PATCH 201/247] SMB2Cryptography: CalculateSignature: Return 16 bytes signature instead of entire hash --- SMBLibrary.Tests/SMB2SigningTests.cs | 5 +---- SMBLibrary/SMB2/Commands/SMB2Command.cs | 3 +-- SMBLibrary/SMB2/SMB2Cryptography.cs | 10 +++++++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/SMBLibrary.Tests/SMB2SigningTests.cs b/SMBLibrary.Tests/SMB2SigningTests.cs index 324b3158..6fcd4c87 100644 --- a/SMBLibrary.Tests/SMB2SigningTests.cs +++ b/SMBLibrary.Tests/SMB2SigningTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2019 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2026 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,6 @@ public void TestSMB202SignatureCalculation() ByteWriter.WriteBytes(message, 48, new byte[16]); byte[] signature = SMB2Cryptography.CalculateSignature(exportedSessionKey, SMB2Dialect.SMB202, message, 0, message.Length); - signature = ByteReader.ReadBytes(signature, 0, 16); byte[] expected = new byte[] { 0xfb, 0xd2, 0x84, 0x34, 0x03, 0x24, 0xc6, 0x2f, 0xbe, 0xbb, 0x65, 0xdd, 0x10, 0x51, 0xf3, 0xae }; Assert.IsTrue(ByteUtils.AreByteArraysEqual(signature, expected)); } @@ -46,7 +45,6 @@ public void TestSMB210SignatureCalculation() ByteWriter.WriteBytes(message, 48, new byte[16]); byte[] signature = SMB2Cryptography.CalculateSignature(exportedSessionKey, SMB2Dialect.SMB210, message, 0, message.Length); - signature = ByteReader.ReadBytes(signature, 0, 16); byte[] expected = new byte[] { 0xa1, 0x64, 0xff, 0xe5, 0x3d, 0x68, 0x11, 0x98, 0x1f, 0x38, 0x67, 0x72, 0xe3, 0x87, 0xe0, 0x6f }; Assert.IsTrue(ByteUtils.AreByteArraysEqual(signature, expected)); } @@ -66,7 +64,6 @@ public void TestSMB300SignatureCalculation() ByteWriter.WriteBytes(message, 48, new byte[16]); byte[] signature = SMB2Cryptography.CalculateSignature(signingKey, SMB2Dialect.SMB300, message, 0, message.Length); - signature = ByteReader.ReadBytes(signature, 0, 16); byte[] expected = new byte[] { 0x73, 0xF2, 0xCC, 0x56, 0x09, 0x3E, 0xD2, 0xB5, 0xD7, 0x10, 0x66, 0x6C, 0xE4, 0x28, 0x2D, 0xD1 }; Assert.IsTrue(ByteUtils.AreByteArraysEqual(signature, expected)); } diff --git a/SMBLibrary/SMB2/Commands/SMB2Command.cs b/SMBLibrary/SMB2/Commands/SMB2Command.cs index 479aa361..fd2b7988 100644 --- a/SMBLibrary/SMB2/Commands/SMB2Command.cs +++ b/SMBLibrary/SMB2/Commands/SMB2Command.cs @@ -179,8 +179,7 @@ public static byte[] GetCommandChainBytes(List commands, byte[] sig { // [MS-SMB2] Any padding at the end of the message MUST be used in the hash computation. byte[] signature = SMB2Cryptography.CalculateSignature(signingKey, dialect, buffer, offset, paddedLength); - // [MS-SMB2] The first 16 bytes of the hash MUST be copied into the 16-byte signature field of the SMB2 Header. - ByteWriter.WriteBytes(buffer, offset + SMB2Header.SignatureOffset, signature, 16); + ByteWriter.WriteBytes(buffer, offset + SMB2Header.SignatureOffset, signature, SMB2Header.SignatureLength); } offset += paddedLength; } diff --git a/SMBLibrary/SMB2/SMB2Cryptography.cs b/SMBLibrary/SMB2/SMB2Cryptography.cs index 93b98394..bce6b331 100644 --- a/SMBLibrary/SMB2/SMB2Cryptography.cs +++ b/SMBLibrary/SMB2/SMB2Cryptography.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2020-2026 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,14 +16,18 @@ internal class SMB2Cryptography public static byte[] CalculateSignature(byte[] signingKey, SMB2Dialect dialect, byte[] buffer, int offset, int paddedLength) { + byte[] hash; if (dialect == SMB2Dialect.SMB202 || dialect == SMB2Dialect.SMB210) { - return new HMACSHA256(signingKey).ComputeHash(buffer, offset, paddedLength); + hash = new HMACSHA256(signingKey).ComputeHash(buffer, offset, paddedLength); } else { - return AesCmac.CalculateAesCmac(signingKey, buffer, offset, paddedLength); + hash = AesCmac.CalculateAesCmac(signingKey, buffer, offset, paddedLength); } + + // [MS-SMB2] The first 16 bytes of the hash MUST be copied into the 16-byte signature field of the SMB2 Header. + return ByteReader.ReadBytes(hash, 0, SMB2Header.SignatureLength); } public static byte[] GenerateSigningKey(byte[] sessionKey, SMB2Dialect dialect, byte[] preauthIntegrityHashValue) From 301b089d76a8bd92445e9c7b8c34d66215d71698 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Feb 2026 18:22:04 +0200 Subject: [PATCH 202/247] SMB2Cryptography: Added VerifySignature method --- SMBLibrary/SMB2/SMB2Cryptography.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/SMBLibrary/SMB2/SMB2Cryptography.cs b/SMBLibrary/SMB2/SMB2Cryptography.cs index bce6b331..ba89d1e7 100644 --- a/SMBLibrary/SMB2/SMB2Cryptography.cs +++ b/SMBLibrary/SMB2/SMB2Cryptography.cs @@ -30,6 +30,14 @@ public static byte[] CalculateSignature(byte[] signingKey, SMB2Dialect dialect, return ByteReader.ReadBytes(hash, 0, SMB2Header.SignatureLength); } + public static bool VerifySignature(byte[] messageBytes, SMB2Dialect dialect, byte[] signingKey) + { + byte[] signature = ByteReader.ReadBytes(messageBytes, SMB2Header.SignatureOffset, SMB2Header.SignatureLength); + Array.Clear(messageBytes, SMB2Header.SignatureOffset, SMB2Header.SignatureLength); + byte[] expectedSignature = CalculateSignature(signingKey, dialect, messageBytes, 0, messageBytes.Length); + return ByteUtils.AreByteArraysEqual(signature, expectedSignature); + } + public static byte[] GenerateSigningKey(byte[] sessionKey, SMB2Dialect dialect, byte[] preauthIntegrityHashValue) { if (dialect == SMB2Dialect.SMB202 || dialect == SMB2Dialect.SMB210) From 0bb09389d419e8d92b577edef777c2329882e6d1 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Feb 2026 19:20:43 +0200 Subject: [PATCH 203/247] RevisionHistory: Removed unintended duplication --- SMBLibrary/RevisionHistory.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index ed3a7a24..3d188347 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -528,8 +528,8 @@ Revision History: ACL: Bugfix: Use AceSize when parsing ACL. 1.5.5 - SMB1Client: Fix UserID being ignored when extended security is set to false. - SMB2Client: Correctly report login error status instead of STATUS_INVALID_SMB: Always sign outgoing messages when dialect is 3.1.1 - SMB2Client: Correctly report login error status instead of STATUS_INVALID_SMB + SMB2Client: Always sign outgoing messages when dialect is 3.1.1 + SMB2Client: Correctly report login error status instead of STATUS_INVALID_SMB. SMB2Client: Enabled SMB 3.0.2 support by default. SMB2Client: Added constructor argument to enable SMB 3.1.1 SMB2Client: Throw exception if attempting to read or write more data than allowed for the connection. From 65952a9a82270fe4878506eaf7e587d6090795fb Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 27 Feb 2026 19:21:26 +0200 Subject: [PATCH 204/247] SMBLibrary 1.5.6 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 4 ++-- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 4 ++-- SMBLibrary/RevisionHistory.txt | 18 ++++++++++++++++++ SMBLibrary/SMBLibrary.csproj | 4 ++-- SMBServer/SMBServer.csproj | 4 ++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 158a3bc3..dc821efa 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,11 +3,11 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.5 + 1.5.6 1573;1591 SMBLibrary.Adapters Tal Aloni - Copyright © Tal Aloni 2014-2025 + Copyright © Tal Aloni 2014-2026 FileSystem adapters for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index d9f5d1de..cfbff886 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,11 +3,11 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.5 + 1.5.6 1573;1591 SMBLibrary.Win32 Tal Aloni - Copyright © Tal Aloni 2014-2025 + Copyright © Tal Aloni 2014-2026 Windows specific extensions for SMBLibrary LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 3d188347..1c942bf0 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -541,3 +541,21 @@ Revision History: Client: Added Echo command support. SMB2: Added FileAttributeTagInformation support. Server: SMB2: NegotiateResponse: Corrected CommandLength calculation. + +1.5.6 - Client: NTLM: Set NTLMSSP_REQUEST_TARGET bit in negotiate message. + SMB1Client: Correctly report login error status instead of STATUS_INVALID_SMB. + SMB1Client: Compute session key when extended security is set to false. + SMB2Client: Simplified enabling SMB 3.1.1 + SMB2Client: Implemented SetSecurityInformation. + NTLM: NegotiateFlags: Renamed TargetNameSupplied to TargetNameNegotiated. + NTLM: NegotiateFlags: Renamed RequestLMSessionKey to RequestNonNTSessionKey. + DFS: Implemented ResponseGetDfsReferral. + DFS: Implemented RequestGetDfsReferralEx. + SMB1: Implemented SMB_COM_QUERY_INFORMATION_DISK request and response. + Server: SMB1: Add support for SMB_COM_QUERY_INFORMATION_DISK used by Windows 98. + Server: SessionSetupHelper: Correctly handle NTLM v1 non-extended-session-security with LmChallengeResponse set to 0 bytes. + NTFileStore: SecurityDescriptor: Set SelfRelative bit when applicable. + NTFileStore: FileSystemAttributes: Fixed typo, Renamed CasePreservedNamed to CasePreservedNames. + NTFileStore: FileAccessMask: Added missing FILE_DELETE_CHILD. + NTFileStore: FileSystemAttributes: Added missing enum values. + NTFileStore: DeviceCharacteristics: Added additional enum values. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 1dc84bd3..eac55e4e 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,12 +3,12 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.5.2 + 1.5.6 1573;1591 SMBLibrary false Tal Aloni - Copyright © Tal Aloni 2014-2025 + Copyright © Tal Aloni 2014-2026 SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation LGPL-3.0-or-later https://github.com/TalAloni/SMBLibrary diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index b391d502..db8d6b55 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -3,9 +3,9 @@ WinExe net20;net40;netcoreapp3.1 - Copyright © Tal Aloni 2014-2025 + Copyright © Tal Aloni 2014-2026 SMBServer - 1.5.5 + 1.5.6 SMBServer true From 4db9687b1457927ab4a43b71f7761e7c8f49bee3 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 00:22:34 +0200 Subject: [PATCH 205/247] Updated SMBLibrary\Readme.md --- SMBLibrary/Readme.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/SMBLibrary/Readme.md b/SMBLibrary/Readme.md index 83ba59c5..6a488235 100644 --- a/SMBLibrary/Readme.md +++ b/SMBLibrary/Readme.md @@ -57,6 +57,22 @@ You can share anything from actual directories to custom objects, as long as the Client code examples can be found [here](../ClientExamples.md). +NuGet Packages: +=============== +[SMBLibrary](https://www.nuget.org/packages/SMBLibrary/) - Cross-platform server and client implementation. +[SMBLibrary.Win32](https://www.nuget.org/packages/SMBLibrary.Win32/) - Allows utilizing Integrated Windows Authentication and/or the Windows storage subsystem on a Windows host. +[SMBLibrary.Adapters](https://www.nuget.org/packages/SMBLibrary.Adapters/) - IFileSystem to INTFileStore adapter for SMBLibrary. + +Licensing: +========== +A commercial license of SMBLibrary is available for a fee. +This is intended for companies who are unable to use the LGPL version. +Please contact me for additional details. + +Contributions: +============== +If you choose to make a contribution to this project, you must agree to irrevocably assign to SMBLibrary and/or Tal Aloni all worldwide copyright and intellectual property rights in and to your contribution, effective upon submission. + Contact: ======== If you have any question, feel free to contact me. From 90bac08e09ac5297f493732171a1112c9ab97e04 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 00:23:58 +0200 Subject: [PATCH 206/247] Client: NTLMAuthenticationClient: Added virtual CreateNegotiateMessage method --- .../Client/Authentication/NTLMAuthenticationClient.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index 9d563e0a..d95b9465 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2026 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, @@ -66,7 +66,7 @@ protected virtual byte[] GetNegotiateMessage(byte[] securityBlob) useGSSAPI = true; } - m_negotiateMessageBytes = NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); + m_negotiateMessageBytes = CreateNegotiateMessage(); if (useGSSAPI) { SimpleProtectedNegotiationTokenInit outputToken = new SimpleProtectedNegotiationTokenInit(); @@ -81,6 +81,11 @@ protected virtual byte[] GetNegotiateMessage(byte[] securityBlob) } } + protected virtual byte[] CreateNegotiateMessage() + { + return NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); + } + protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) { bool useGSSAPI = false; From 6fefa47da28dcfa9af24a849f00e8ea9e0ad4d3c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 00:26:12 +0200 Subject: [PATCH 207/247] NTLMAuthenticationHelper: Removed unused using statement --- SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index ffe52269..422cd4a9 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -5,7 +5,6 @@ * 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.NTLM; using Utilities; From c52da674c94aeefd8f8ef9074590d6571930c48c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 00:30:09 +0200 Subject: [PATCH 208/247] Client: NTLMAuthenticationHelper: Added GetNegotiateMessage overload --- SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 422cd4a9..b178a6ff 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2024 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2026 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, @@ -14,6 +14,12 @@ namespace SMBLibrary.Client public class NTLMAuthenticationHelper { public static byte[] GetNegotiateMessage(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + { + bool isAnonymous = (userName == String.Empty && password == String.Empty); + return GetNegotiateMessage(domainName, isAnonymous, authenticationMethod); + } + + public static byte[] GetNegotiateMessage(string domainName, bool isAnonymous, AuthenticationMethod authenticationMethod) { NegotiateMessage negotiateMessage = new NegotiateMessage(); negotiateMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | @@ -28,7 +34,7 @@ public static byte[] GetNegotiateMessage(string domainName, string userName, str NegotiateFlags.Use128BitEncryption | NegotiateFlags.Use56BitEncryption; - if (!(userName == String.Empty && password == String.Empty)) + if (!isAnonymous) { negotiateMessage.NegotiateFlags |= NegotiateFlags.KeyExchange; } From 479c28ead16f840a2474cf6a79c401b7de2a6919 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 00:32:56 +0200 Subject: [PATCH 209/247] Client: NTLMAuthenticationHelper: GetNegotiateMessage: Added ability to set NTLMSSP_NEGOTIATE_SEAL --- .../Client/Helpers/NTLMAuthenticationHelper.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index b178a6ff..07977f49 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -16,10 +16,10 @@ public class NTLMAuthenticationHelper public static byte[] GetNegotiateMessage(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) { bool isAnonymous = (userName == String.Empty && password == String.Empty); - return GetNegotiateMessage(domainName, isAnonymous, authenticationMethod); + return GetNegotiateMessage(domainName, isAnonymous, authenticationMethod, false); } - public static byte[] GetNegotiateMessage(string domainName, bool isAnonymous, AuthenticationMethod authenticationMethod) + public static byte[] GetNegotiateMessage(string domainName, bool isAnonymous, AuthenticationMethod authenticationMethod, bool requestSeal) { NegotiateMessage negotiateMessage = new NegotiateMessage(); negotiateMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | @@ -34,6 +34,11 @@ public static byte[] GetNegotiateMessage(string domainName, bool isAnonymous, Au NegotiateFlags.Use128BitEncryption | NegotiateFlags.Use56BitEncryption; + if (requestSeal) + { + negotiateMessage.NegotiateFlags |= NegotiateFlags.Seal; + } + if (!isAnonymous) { negotiateMessage.NegotiateFlags |= NegotiateFlags.KeyExchange; @@ -85,6 +90,11 @@ public static byte[] GetAuthenticateMessage(byte[] negotiateMessageBytes, byte[] authenticateMessage.NegotiateFlags |= NegotiateFlags.OEMEncoding; } + if ((challengeMessage.NegotiateFlags & NegotiateFlags.Seal) > 0) + { + authenticateMessage.NegotiateFlags |= NegotiateFlags.Seal; + } + if ((challengeMessage.NegotiateFlags & NegotiateFlags.KeyExchange) > 0) { authenticateMessage.NegotiateFlags |= NegotiateFlags.KeyExchange; From c944922da22fdf8cef69abeefea5c2a128f61ed6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 00:57:22 +0200 Subject: [PATCH 210/247] Client: NTLMAuthenticationClient: Bugfix: DomainName and Workstation must be empty when Version is set --- .../Client/Authentication/NTLMAuthenticationClient.cs | 2 +- SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index d95b9465..d6ca8531 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -83,7 +83,7 @@ protected virtual byte[] GetNegotiateMessage(byte[] securityBlob) protected virtual byte[] CreateNegotiateMessage() { - return NTLMAuthenticationHelper.GetNegotiateMessage(m_domainName, m_userName, m_password, m_authenticationMethod); + return NTLMAuthenticationHelper.GetNegotiateMessage(m_userName, m_password, m_authenticationMethod); } protected virtual byte[] GetAuthenticateMessage(byte[] securityBlob) diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index 07977f49..b37fb4c4 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -13,21 +13,19 @@ namespace SMBLibrary.Client { public class NTLMAuthenticationHelper { - public static byte[] GetNegotiateMessage(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) + public static byte[] GetNegotiateMessage(string userName, string password, AuthenticationMethod authenticationMethod) { bool isAnonymous = (userName == String.Empty && password == String.Empty); - return GetNegotiateMessage(domainName, isAnonymous, authenticationMethod, false); + return GetNegotiateMessage(isAnonymous, authenticationMethod, false); } - public static byte[] GetNegotiateMessage(string domainName, bool isAnonymous, AuthenticationMethod authenticationMethod, bool requestSeal) + public static byte[] GetNegotiateMessage(bool isAnonymous, AuthenticationMethod authenticationMethod, bool requestSeal) { NegotiateMessage negotiateMessage = new NegotiateMessage(); negotiateMessage.NegotiateFlags = NegotiateFlags.UnicodeEncoding | NegotiateFlags.OEMEncoding | NegotiateFlags.Sign | NegotiateFlags.NTLMSessionSecurity | - NegotiateFlags.DomainNameSupplied | - NegotiateFlags.WorkstationNameSupplied | NegotiateFlags.TargetNameNegotiated | NegotiateFlags.AlwaysSign | NegotiateFlags.Version | @@ -54,8 +52,6 @@ public static byte[] GetNegotiateMessage(string domainName, bool isAnonymous, Au } negotiateMessage.Version = NTLMVersion.Server2003; - negotiateMessage.DomainName = domainName; - negotiateMessage.Workstation = Environment.MachineName; return negotiateMessage.GetBytes(); } From 1fcd21ec2343cd8d90063ff2e575deb0adfbd78b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 23 Mar 2026 01:02:11 +0200 Subject: [PATCH 211/247] SMBLibrary 1.5.6.1 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index eac55e4e..138a16e7 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.6 + 1.5.6.1 1573;1591 SMBLibrary false From 6057c94e6fb1eea84fc6a7a6dcdf67a88a9367db Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 12:39:29 +0200 Subject: [PATCH 212/247] NTLMCryptography: Correct typo --- .../Authentication/NTLM/Helpers/NTLMCryptography.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index a829c8ee..5a39ca8b 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -298,8 +298,8 @@ private static byte[] ComputeSignKey(byte[] exportedSessionKey, bool isClient) } byte[] encodedString = Encoding.GetEncoding(28591).GetBytes(str); byte[] nullTerminatedEncodedString = ByteUtils.Concatenate(encodedString, new byte[1]); - byte[] concatendated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); - return MD5.Create().ComputeHash(concatendated); + byte[] concatenated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); + return MD5.Create().ComputeHash(concatenated); } public static byte[] ComputeClientSealKey(byte[] exportedSessionKey) @@ -326,8 +326,8 @@ private static byte[] ComputeSealKey(byte[] exportedSessionKey, bool isClient) } byte[] encodedString = Encoding.GetEncoding(28591).GetBytes(str); byte[] nullTerminatedEncodedString = ByteUtils.Concatenate(encodedString, new byte[1]); - byte[] concatendated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); - return MD5.Create().ComputeHash(concatendated); + byte[] concatenated = ByteUtils.Concatenate(exportedSessionKey, nullTerminatedEncodedString); + return MD5.Create().ComputeHash(concatenated); } public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] message) @@ -340,8 +340,8 @@ public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] messag // [MS-NLMP] 3.4.4.2 byte[] signKey = ComputeClientSignKey(exportedSessionKey); byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); - byte[] concatendated = ByteUtils.Concatenate(sequenceNumberBytes, message); - byte[] fullHash = new HMACMD5(signKey).ComputeHash(concatendated); + byte[] concatenated = ByteUtils.Concatenate(sequenceNumberBytes, message); + byte[] fullHash = new HMACMD5(signKey).ComputeHash(concatenated); byte[] hash = ByteReader.ReadBytes(fullHash, 0, 8); byte[] sealKey = ComputeClientSealKey(exportedSessionKey); From ada15eef1d6b2291a36930f7ecfc077d1e834875 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 12:46:08 +0200 Subject: [PATCH 213/247] RC4: Added Encrypt/Decrypt overload accepting RC4KeyState to allow encrypting streams --- SMBLibrary.Tests/NTLM/RC4Tests.cs | 15 +++++ SMBLibrary/Authentication/NTLM/Helpers/RC4.cs | 58 +++++++++++-------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/SMBLibrary.Tests/NTLM/RC4Tests.cs b/SMBLibrary.Tests/NTLM/RC4Tests.cs index 8a5b2d0d..ddb52514 100644 --- a/SMBLibrary.Tests/NTLM/RC4Tests.cs +++ b/SMBLibrary.Tests/NTLM/RC4Tests.cs @@ -125,5 +125,20 @@ public void Test3() byte[] cipher = RC4.Encrypt(key, text); Assert.IsTrue(ByteUtils.AreByteArraysEqual(cipher, expectedCipher)); } + + [TestMethod] + public void TestStreaming() + { + byte[] key = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; + byte[] text = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + byte[] expectedCipher = new byte[] { 0x74, 0x94, 0xC2, 0xE7, 0x10, 0x4B, 0x08, 0x79 }; + + RC4KeyState keyState = RC4.InitializeStateFromKey(key); + byte[] cipherPart1 = RC4.Encrypt(keyState, ByteReader.ReadBytes(text, 0, 2)); + byte[] cipherPart2 = RC4.Encrypt(keyState, ByteReader.ReadBytes(text, 2, text.Length - 2)); + + byte[] cipher = ByteUtils.Concatenate(cipherPart1, cipherPart2); + Assert.IsTrue(ByteUtils.AreByteArraysEqual(cipher, expectedCipher)); + } } } diff --git a/SMBLibrary/Authentication/NTLM/Helpers/RC4.cs b/SMBLibrary/Authentication/NTLM/Helpers/RC4.cs index 64c0607a..55c94b3c 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/RC4.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/RC4.cs @@ -1,30 +1,26 @@ -/* Copyright (C) 2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2026 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. - * - * Based on: https://bitlush.com/blog/rc4-encryption-in-c-sharp */ -using System; -using System.Collections.Generic; -using System.Text; - namespace System.Security.Cryptography { public class RC4 { public static byte[] Encrypt(byte[] key, byte[] data) { - return EncryptOutput(key, data); + RC4KeyState state = InitializeStateFromKey(key); + return Encrypt(state, data); } public static byte[] Decrypt(byte[] key, byte[] data) { - return EncryptOutput(key, data); + RC4KeyState state = InitializeStateFromKey(key); + return Encrypt(state, data); } - private static byte[] EncryptInitalize(byte[] key) + public static RC4KeyState InitializeStateFromKey(byte[] key) { byte[] s = new byte[256]; for (int index = 0; index < 256; index++) @@ -39,34 +35,48 @@ private static byte[] EncryptInitalize(byte[] key) Swap(s, i, j); } - return s; + return new RC4KeyState(s); } - private static byte[] EncryptOutput(byte[] key, byte[] data) + public static byte[] Encrypt(RC4KeyState state, byte[] data) { - byte[] s = EncryptInitalize(key); - - int i = 0; - int j = 0; + byte[] s = state.S; byte[] output = new byte[data.Length]; for (int index = 0; index < data.Length; index++) { - i = (i + 1) & 255; - j = (j + s[i]) & 255; + state.I = (state.I + 1) & 255; + state.J = (state.J + state.S[state.I]) & 255; - Swap(s, i, j); - output[index] = (byte)(data[index] ^ s[(s[i] + s[j]) & 255]); + Swap(state.S, state.I, state.J); + output[index] = (byte)(data[index] ^ s[(s[state.I] + s[state.J]) & 255]); } return output; } - private static void Swap(byte[] s, int i, int j) + public static byte[] Decrypt(RC4KeyState state, byte[] data) + { + return Encrypt(state, data); + } + + private static void Swap(byte[] state, int i, int j) { - byte c = s[i]; + byte c = state[i]; - s[i] = s[j]; - s[j] = c; + state[i] = state[j]; + state[j] = c; + } + } + + public class RC4KeyState + { + internal byte[] S; + internal int I; + internal int J; + + internal RC4KeyState(byte[] s) + { + S = s; } } } From ce80ddce0fd8cbc4f9097f733118656f0992d7d2 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 12:58:27 +0200 Subject: [PATCH 214/247] NTLMCryptography: ComputeMechListMIC: Update seqNum type to uint --- SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 5a39ca8b..5760a725 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -335,7 +335,7 @@ public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] messag return ComputeMechListMIC(exportedSessionKey, message, 0); } - public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] message, int seqNum) + public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] message, uint seqNum) { // [MS-NLMP] 3.4.4.2 byte[] signKey = ComputeClientSignKey(exportedSessionKey); From 2cbfe6b67aa6440ec72be4602764c082eb9f1747 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 12:59:46 +0200 Subject: [PATCH 215/247] NTLMCryptography: Extracted ComputeMessageHash method --- .../NTLM/Helpers/NTLMCryptography.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 5760a725..04e4d21d 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -339,16 +339,22 @@ public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] messag { // [MS-NLMP] 3.4.4.2 byte[] signKey = ComputeClientSignKey(exportedSessionKey); - byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); - byte[] concatenated = ByteUtils.Concatenate(sequenceNumberBytes, message); - byte[] fullHash = new HMACMD5(signKey).ComputeHash(concatenated); - byte[] hash = ByteReader.ReadBytes(fullHash, 0, 8); + byte[] hash = ComputeMessageHash(signKey, message, seqNum); byte[] sealKey = ComputeClientSealKey(exportedSessionKey); byte[] encryptedHash = RC4.Encrypt(sealKey, hash); byte[] version = new byte[] { 0x01, 0x00, 0x00, 0x00 }; + byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); return ByteUtils.Concatenate(ByteUtils.Concatenate(version, encryptedHash), sequenceNumberBytes); } + + private static byte[] ComputeMessageHash(byte[] signKey, byte[] message, uint seqNum) + { + byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); + byte[] concatenated = ByteUtils.Concatenate(sequenceNumberBytes, message); + byte[] fullHash = new HMACMD5(signKey).ComputeHash(concatenated); + return ByteReader.ReadBytes(fullHash, 0, 8); + } } } From 4ff7492ce2fa8a2616a003a5b00ccecf7dbbcb48 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 13:01:56 +0200 Subject: [PATCH 216/247] NTLMCryptography: Extracted ComputeMessageSignature --- .../Authentication/NTLM/Helpers/NTLMCryptography.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 04e4d21d..cdc36ba9 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -337,12 +337,17 @@ public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] messag public static byte[] ComputeMechListMIC(byte[] exportedSessionKey, byte[] message, uint seqNum) { - // [MS-NLMP] 3.4.4.2 byte[] signKey = ComputeClientSignKey(exportedSessionKey); - byte[] hash = ComputeMessageHash(signKey, message, seqNum); - byte[] sealKey = ComputeClientSealKey(exportedSessionKey); - byte[] encryptedHash = RC4.Encrypt(sealKey, hash); + RC4KeyState sealKeyState = RC4.InitializeStateFromKey(sealKey); + return ComputeMessageSignature(signKey, sealKeyState, message, seqNum); + } + + public static byte[] ComputeMessageSignature(byte[] signKey, RC4KeyState sealKeyState, byte[] message, uint seqNum) + { + // [MS-NLMP] 3.4.4.2 + byte[] hash = ComputeMessageHash(signKey, message, seqNum); + byte[] encryptedHash = RC4.Encrypt(sealKeyState, hash); byte[] version = new byte[] { 0x01, 0x00, 0x00, 0x00 }; byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); From 995e936bea64b343dfa7a20dad895716378a5a8c Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 13:05:16 +0200 Subject: [PATCH 217/247] NTLMCryptography: Added VerifyMessageHash method --- SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index cdc36ba9..4c7e55f2 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -354,6 +354,12 @@ public static byte[] ComputeMessageSignature(byte[] signKey, RC4KeyState sealKey return ByteUtils.Concatenate(ByteUtils.Concatenate(version, encryptedHash), sequenceNumberBytes); } + public static bool VerifyMessageHash(byte[] signKey, byte[] message, uint seqNum, byte[] expectedHash) + { + byte[] hash = ComputeMessageHash(signKey, message, seqNum); + return ByteUtils.AreByteArraysEqual(hash, expectedHash); + } + private static byte[] ComputeMessageHash(byte[] signKey, byte[] message, uint seqNum) { byte[] sequenceNumberBytes = LittleEndianConverter.GetBytes(seqNum); From d136f76c93c43155ecbe584f52b85e33735f23bb Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 13:15:49 +0200 Subject: [PATCH 218/247] IndependentNTLMAuthenticationProvider: Extracted virtual CreateChallengeMessage method --- .../NTLM/IndependentNTLMAuthenticationProvider.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs index 2a4cea30..73367f89 100644 --- a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2020 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2026 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, @@ -78,6 +78,13 @@ public override NTStatus GetChallengeMessage(out object context, byte[] negotiat byte[] serverChallenge = GenerateServerChallenge(); context = new AuthContext(serverChallenge); + ChallengeMessage challengeMessage = CreateChallengeMessage(negotiateMessage, serverChallenge); + challengeMessageBytes = challengeMessage.GetBytes(); + return NTStatus.SEC_I_CONTINUE_NEEDED; + } + + protected virtual ChallengeMessage CreateChallengeMessage(NegotiateMessage negotiateMessage, byte[] serverChallenge) + { ChallengeMessage challengeMessage = new ChallengeMessage(); // https://msdn.microsoft.com/en-us/library/cc236691.aspx challengeMessage.NegotiateFlags = NegotiateFlags.TargetTypeServer | @@ -147,8 +154,7 @@ public override NTStatus GetChallengeMessage(out object context, byte[] negotiat challengeMessage.ServerChallenge = serverChallenge; challengeMessage.TargetInfo = AVPairUtils.GetAVPairSequence(Environment.MachineName, Environment.MachineName); challengeMessage.Version = NTLMVersion.Server2003; - challengeMessageBytes = challengeMessage.GetBytes(); - return NTStatus.SEC_I_CONTINUE_NEEDED; + return challengeMessage; } public override NTStatus Authenticate(object context, byte[] authenticateMessageBytes) From 023cc5c114760cb42ab414948d12e037f7aa4925 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Tue, 24 Mar 2026 13:27:06 +0200 Subject: [PATCH 219/247] SMBLibrary 1.5.6.2 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 138a16e7..6c6655cb 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.6.1 + 1.5.6.2 1573;1591 SMBLibrary false From 82448879b98aab14b6dd4fbf7b49ece2dd4436ec Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Wed, 25 Mar 2026 10:23:19 +0200 Subject: [PATCH 220/247] NTLMCryptography: Marked ComputeSignKey and ComputeSealKey as public --- SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 4c7e55f2..0e2d8eff 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -284,7 +284,7 @@ public static byte[] ComputeServerSignKey(byte[] exportedSessionKey) return ComputeSignKey(exportedSessionKey, false); } - private static byte[] ComputeSignKey(byte[] exportedSessionKey, bool isClient) + public static byte[] ComputeSignKey(byte[] exportedSessionKey, bool isClient) { // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/524cdccb-563e-4793-92b0-7bc321fce096 string str; @@ -312,7 +312,7 @@ public static byte[] ComputeServerSealKey(byte[] exportedSessionKey) return ComputeSealKey(exportedSessionKey, false); } - private static byte[] ComputeSealKey(byte[] exportedSessionKey, bool isClient) + public static byte[] ComputeSealKey(byte[] exportedSessionKey, bool isClient) { // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-nlmp/524cdccb-563e-4793-92b0-7bc321fce096 string str; From a90c3d0bb43bc18e392dca6407c6f7b69fd5e6c0 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 5 Apr 2026 17:58:08 +0300 Subject: [PATCH 221/247] IndependentNTLMAuthenticationProvider: Remove unused using statement --- .../Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs index 73367f89..d790d69b 100644 --- a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -5,7 +5,6 @@ * 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 Utilities; From 008ed45a12f1a18a041d29f414107f22ec774cc6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 5 Apr 2026 17:59:03 +0300 Subject: [PATCH 222/247] IndependentNTLMAuthenticationProvider: Extract GetServerName method --- .../NTLM/IndependentNTLMAuthenticationProvider.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs index d790d69b..c9687de2 100644 --- a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -149,13 +149,19 @@ protected virtual ChallengeMessage CreateChallengeMessage(NegotiateMessage negot challengeMessage.NegotiateFlags |= NegotiateFlags.KeyExchange; } - challengeMessage.TargetName = Environment.MachineName; + string serverName = GetServerName(); + challengeMessage.TargetName = serverName; challengeMessage.ServerChallenge = serverChallenge; - challengeMessage.TargetInfo = AVPairUtils.GetAVPairSequence(Environment.MachineName, Environment.MachineName); + challengeMessage.TargetInfo = AVPairUtils.GetAVPairSequence(serverName, serverName); challengeMessage.Version = NTLMVersion.Server2003; return challengeMessage; } + public virtual string GetServerName() + { + return Environment.MachineName; + } + public override NTStatus Authenticate(object context, byte[] authenticateMessageBytes) { AuthenticateMessage message; From 1750c336e254c557a6034090d5107a1b6e46b762 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 5 Apr 2026 18:46:15 +0300 Subject: [PATCH 223/247] SMB2Client: Verify message signature when applicable --- SMBLibrary/Client/SMB2Client.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 5df77c24..7cafe9a6 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2025 Tal Aloni . All rights reserved. +/* Copyright (C) 2017-2026 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, @@ -558,6 +558,16 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) // Otherwise, the response MUST be discarded as invalid. if (command.Header.MessageID != 0xFFFFFFFFFFFFFFFF || command.Header.Command == SMB2CommandName.OplockBreak) { + bool isInterimResponse = ((command.Header.Flags & SMB2PacketHeaderFlags.AsyncCommand) != 0) && command.Header.Status == NTStatus.STATUS_PENDING; + bool shouldBeSigned = m_isLoggedIn && m_signingRequired && !m_encryptSessionData && !isInterimResponse; + + // [MS-SMB2] 3.2.5.1.3 If signature verification fails, the client MUST discard the received message. The client MAY also choose to disconnect the connection + if (shouldBeSigned && !SMB2Cryptography.VerifySignature(messageBytes, m_dialect, m_signingKey)) + { + Log("Invalid SMB2 response signature"); + return; + } + lock (m_incomingQueueLock) { m_incomingQueue.Add(command); From d4af2d10e4fb5bbd748be284b505abf6860b188b Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 5 Apr 2026 19:05:55 +0300 Subject: [PATCH 224/247] SMBLibrary 1.5.6.3 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 6c6655cb..2fafa0b8 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.6.2 + 1.5.6.3 1573;1591 SMBLibrary false From df82ee9bdd4efa69abcc1dffa70e8ed3d1cdcc68 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 6 Apr 2026 20:00:08 +0300 Subject: [PATCH 225/247] SMB2Client: TreeConnect: Minor code refactoring --- SMBLibrary/Client/SMB2Client.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 7cafe9a6..2fdd03d7 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -377,9 +377,9 @@ public ISMBFileStore TreeConnect(string shareName, out NTStatus status) if (response != null) { status = response.Header.Status; - if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is TreeConnectResponse) + if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is TreeConnectResponse treeConnectResponse) { - bool encryptShareData = (((TreeConnectResponse)response).ShareFlags & ShareFlags.EncryptData) > 0; + bool encryptShareData = (treeConnectResponse.ShareFlags & ShareFlags.EncryptData) > 0; return new SMB2FileStore(this, response.Header.TreeID, m_encryptSessionData || encryptShareData); } } From 9ae4f8df9de41d2319682ab55a599a973100ff50 Mon Sep 17 00:00:00 2001 From: Florin <119594712+uflowie@users.noreply.github.com> Date: Fri, 17 Apr 2026 04:53:01 +0200 Subject: [PATCH 226/247] Client: Fix rare racing condition when Disconnect is called before response has been received (#345) --- SMBLibrary/Client/SMB1Client.cs | 2 +- SMBLibrary/Client/SMB2Client.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 2d3d0f1a..5a859700 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -493,7 +493,7 @@ private void OnClientSocketReceive(IAsyncResult ar) m_isConnected = false; state.ReceiveBuffer.Dispose(); } - else + else if (clientSocket.Connected) { NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; buffer.SetNumberOfBytesReceived(numberOfBytesReceived); diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 2fdd03d7..5ff53646 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -443,7 +443,7 @@ private void OnClientSocketReceive(IAsyncResult ar) m_isConnected = false; state.ReceiveBuffer.Dispose(); } - else + else if (clientSocket.Connected) { NBTConnectionReceiveBuffer buffer = state.ReceiveBuffer; buffer.SetNumberOfBytesReceived(numberOfBytesReceived); From 9dc1c891369e0d06066d22a34072f0e28ba10768 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 17 Apr 2026 06:32:39 +0300 Subject: [PATCH 227/247] SMBLibrary.Adapters: Increment DiskAccessLibrary to v1.6.6 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index dc821efa..3fa7b1f0 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -20,7 +20,7 @@ - + From bb4854a97d888c6ccfa2756b60157558cac8ec3f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 17 Apr 2026 06:38:22 +0300 Subject: [PATCH 228/247] SMBLibrary 1.5.7 --- SMBLibrary.Adapters/SMBLibrary.Adapters.csproj | 2 +- SMBLibrary.Win32/SMBLibrary.Win32.csproj | 2 +- SMBLibrary/RevisionHistory.txt | 14 ++++++++++++++ SMBLibrary/SMBLibrary.csproj | 2 +- SMBServer/SMBServer.csproj | 2 +- 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj index 3fa7b1f0..38c65336 100644 --- a/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj +++ b/SMBLibrary.Adapters/SMBLibrary.Adapters.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Adapters - 1.5.6 + 1.5.7 1573;1591 SMBLibrary.Adapters Tal Aloni diff --git a/SMBLibrary.Win32/SMBLibrary.Win32.csproj b/SMBLibrary.Win32/SMBLibrary.Win32.csproj index cfbff886..ad494218 100644 --- a/SMBLibrary.Win32/SMBLibrary.Win32.csproj +++ b/SMBLibrary.Win32/SMBLibrary.Win32.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary.Win32 - 1.5.6 + 1.5.7 1573;1591 SMBLibrary.Win32 Tal Aloni diff --git a/SMBLibrary/RevisionHistory.txt b/SMBLibrary/RevisionHistory.txt index 1c942bf0..11e1d4c8 100644 --- a/SMBLibrary/RevisionHistory.txt +++ b/SMBLibrary/RevisionHistory.txt @@ -559,3 +559,17 @@ Revision History: NTFileStore: FileAccessMask: Added missing FILE_DELETE_CHILD. NTFileStore: FileSystemAttributes: Added missing enum values. NTFileStore: DeviceCharacteristics: Added additional enum values. + +1.5.7 - Client: NTLMAuthenticationClient: Added virtual CreateNegotiateMessage method. + Client: NTLMAuthenticationHelper: Added GetNegotiateMessage overload. + Client: NTLMAuthenticationHelper: GetNegotiateMessage: Added ability to set NTLMSSP_NEGOTIATE_SEAL. + Client: NTLMAuthenticationClient: Bugfix: DomainName and Workstation must be empty when Version is set. + RC4: Added Encrypt/Decrypt overload accepting RC4KeyState to allow encrypting streams. + NTLMCryptography: ComputeMechListMIC: Update seqNum type to uint. + NTLMCryptography: Extracted ComputeMessageHash method. + NTLMCryptography: Extracted ComputeMessageSignature. + NTLMCryptography: Added VerifyMessageHash method. + NTLMCryptography: Marked ComputeSignKey and ComputeSealKey as public. + IndependentNTLMAuthenticationProvider: Extracted virtual CreateChallengeMessage method. + IndependentNTLMAuthenticationProvider: Extract GetServerName method. + Client: Fix rare racing condition when Disconnect is called before response has been received. diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 2fafa0b8..9ae86b30 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.6.3 + 1.5.7 1573;1591 SMBLibrary false diff --git a/SMBServer/SMBServer.csproj b/SMBServer/SMBServer.csproj index db8d6b55..2803f63d 100644 --- a/SMBServer/SMBServer.csproj +++ b/SMBServer/SMBServer.csproj @@ -5,7 +5,7 @@ net20;net40;netcoreapp3.1 Copyright © Tal Aloni 2014-2026 SMBServer - 1.5.6 + 1.5.7 SMBServer true From 340a73f2a12b63b58c619e02e76ff27cff789184 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 17 Apr 2026 17:44:51 +0300 Subject: [PATCH 229/247] Updated documentation --- Readme.md | 42 +++--------------------------------------- SMBLibrary/Readme.md | 42 +++--------------------------------------- ServerNotes.md | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 78 deletions(-) create mode 100644 ServerNotes.md diff --git a/Readme.md b/Readme.md index 7abdb67b..aacf20d2 100644 --- a/Readme.md +++ b/Readme.md @@ -1,9 +1,9 @@ About SMBLibrary: ================= SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation. -SMBLibrary gives .NET developers an easy way to share a directory / file system / virtual file system, with any operating system that supports the SMB protocol. +SMBLibrary gives .NET developers an easy way to share a directory / file system / virtual file system or to connect to an existing share, with any operating system that supports the SMB protocol. SMBLibrary is modular, you can take advantage of Integrated Windows Authentication and the Windows storage subsystem on a Windows host or use independent implementations that allow for cross-platform compatibility. -SMBLibrary shares can be accessed from any Windows version since Windows NT 4.0. +SMBLibrary can communicate with any Windows version since Windows NT 4.0. Supported SMB / CIFS transport methods: ======================================= @@ -15,45 +15,9 @@ Supported SMB / CIFS transport methods: - A 'keep alive' packet is sent from time to time over NBT connections. - SMB2: Direct TCP hosting supports large MTUs. -Notes: -====== -By default, Windows already use ports 139 and 445. there are several techniques to free / utilize those ports: - -##### Method 1: Disable Windows File and Printer Sharing server completely: -###### Windows XP/2003: -1. For every network adapter: Uncheck 'File and Printer Sharing for Microsoft Networks". -2. Navigate to 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' and set 'SMBDeviceEnabled' to '0' (this will free port 445). -3. Reboot. - -###### Windows 7/8/2008/2012: -Disable the "Server" service (p.s. "TCP\IP NETBIOS Helper" should be enabled). - -##### Method 2: Use Windows File Sharing AND SMBLibrary: -Windows bind port 139 to the first IP addres of every adapter, while port 445 is bound globally. -This means that if you'll disable port 445 (or block it using a firewall), you'll be able to use a different service on port 139 for every IP address. - -###### Additional Notes: -* To free port 139 for a given adapter, go to 'Internet Protocol (TCP/IP) Properties' > Advanced > WINS, and select 'Disable NetBIOS over TCP/IP'. -Uncheck 'File and Printer Sharing for Microsoft Networks' to ensure Windows will not answer to SMB traffic on port 445 for this adapter. - -* It's important to note that disabling NetBIOS over TCP/IP will also disable NetBIOS name service for that adapter (a.k.a. WINS), This service uses UDP port 137. -SMBLibrary offers a name service of its own. - -* You can install a virtual network adapter driver for Windows to be used solely with SMBLibrary: - - You can install the 'Microsoft Loopback adapter' and use it for server-only communication with SMBLibrary. - -###### Windows 7/8/2008/2012: -* It's possible to prevent Windows from using port 445 by removing all of the '\Device\Tcpip_{..}' and '\Device\Tcpip6_{..}' entries from the `Bind' registry key under 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Linkage'. - -* if you want localhost access from Windows explorer to work as expected, you must specify the IP address that you selected (\\\\127.0.0.1 or \\\\localhost will not work as expected), in addition, I have observed that when connecting to the first IP address of a given adapter, Windows will only attempt to connect to port 445. - -##### Method 3: Use an IP address that is invisible to Windows File Sharing: -Using PCap.Net you can programmatically setup a virtual Network adapter and intercept SMB traffic (similar to how a virtual machine operates), You should use the ARP protocol to notify the network about the new IP address, and then process the incoming SMB traffic using SMBLibrary, good luck! - Using SMBLibrary: ================= -Any directory / filesystem / object you wish to share must implement the IFileSystem interface (or the lower-level INTFileStore interface). -You can share anything from actual directories to custom objects, as long as they expose a directory structure. +Server notes can be found [here](ServerNotes.md). Client code examples can be found [here](ClientExamples.md). diff --git a/SMBLibrary/Readme.md b/SMBLibrary/Readme.md index 6a488235..9f3ed60d 100644 --- a/SMBLibrary/Readme.md +++ b/SMBLibrary/Readme.md @@ -1,9 +1,9 @@ About SMBLibrary: ================= SMBLibrary is an open-source C# SMB 1.0/CIFS, SMB 2.0, SMB 2.1 and SMB 3.0 server and client implementation. -SMBLibrary gives .NET developers an easy way to share a directory / file system / virtual file system, with any operating system that supports the SMB protocol. +SMBLibrary gives .NET developers an easy way to share a directory / file system / virtual file system or to connect to an existing share, with any operating system that supports the SMB protocol. SMBLibrary is modular, you can take advantage of Integrated Windows Authentication and the Windows storage subsystem on a Windows host or use independent implementations that allow for cross-platform compatibility. -SMBLibrary shares can be accessed from any Windows version since Windows NT 4.0. +SMBLibrary can communicate with any Windows version since Windows NT 4.0. Supported SMB / CIFS transport methods: ======================================= @@ -15,45 +15,9 @@ Supported SMB / CIFS transport methods: - A 'keep alive' packet is sent from time to time over NBT connections. - SMB2: Direct TCP hosting supports large MTUs. -Notes: -====== -By default, Windows already use ports 139 and 445. there are several techniques to free / utilize those ports: - -##### Method 1: Disable Windows File and Printer Sharing server completely: -###### Windows XP/2003: -1. For every network adapter: Uncheck 'File and Printer Sharing for Microsoft Networks". -2. Navigate to 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' and set 'SMBDeviceEnabled' to '0' (this will free port 445). -3. Reboot. - -###### Windows 7/8/2008/2012: -Disable the "Server" service (p.s. "TCP\IP NETBIOS Helper" should be enabled). - -##### Method 2: Use Windows File Sharing AND SMBLibrary: -Windows bind port 139 to the first IP addres of every adapter, while port 445 is bound globally. -This means that if you'll disable port 445 (or block it using a firewall), you'll be able to use a different service on port 139 for every IP address. - -###### Additional Notes: -* To free port 139 for a given adapter, go to 'Internet Protocol (TCP/IP) Properties' > Advanced > WINS, and select 'Disable NetBIOS over TCP/IP'. -Uncheck 'File and Printer Sharing for Microsoft Networks' to ensure Windows will not answer to SMB traffic on port 445 for this adapter. - -* It's important to note that disabling NetBIOS over TCP/IP will also disable NetBIOS name service for that adapter (a.k.a. WINS), This service uses UDP port 137. -SMBLibrary offers a name service of its own. - -* You can install a virtual network adapter driver for Windows to be used solely with SMBLibrary: - - You can install the 'Microsoft Loopback adapter' and use it for server-only communication with SMBLibrary. - -###### Windows 7/8/2008/2012: -* It's possible to prevent Windows from using port 445 by removing all of the '\Device\Tcpip_{..}' and '\Device\Tcpip6_{..}' entries from the `Bind' registry key under 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Linkage'. - -* if you want localhost access from Windows explorer to work as expected, you must specify the IP address that you selected (\\\\127.0.0.1 or \\\\localhost will not work as expected), in addition, I have observed that when connecting to the first IP address of a given adapter, Windows will only attempt to connect to port 445. - -##### Method 3: Use an IP address that is invisible to Windows File Sharing: -Using PCap.Net you can programmatically setup a virtual Network adapter and intercept SMB traffic (similar to how a virtual machine operates), You should use the ARP protocol to notify the network about the new IP address, and then process the incoming SMB traffic using SMBLibrary, good luck! - Using SMBLibrary: ================= -Any directory / filesystem / object you wish to share must implement the IFileSystem interface (or the lower-level INTFileStore interface). -You can share anything from actual directories to custom objects, as long as they expose a directory structure. +Server notes can be found [here](../ServerNotes.md). Client code examples can be found [here](../ClientExamples.md). diff --git a/ServerNotes.md b/ServerNotes.md new file mode 100644 index 00000000..34ccc847 --- /dev/null +++ b/ServerNotes.md @@ -0,0 +1,39 @@ +SMBLibrary Server Notes: +======================== +By default, Windows already use ports 139 and 445. there are several techniques to free / utilize those ports: + +##### Method 1: Disable Windows File and Printer Sharing server completely: +###### Windows XP/2003: +1. For every network adapter: Uncheck 'File and Printer Sharing for Microsoft Networks". +2. Navigate to 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\NetBT\Parameters' and set 'SMBDeviceEnabled' to '0' (this will free port 445). +3. Reboot. + +###### Windows 7/8/2008/2012: +Disable the "Server" service (p.s. "TCP\IP NETBIOS Helper" should be enabled). + +##### Method 2: Use Windows File Sharing AND SMBLibrary: +Windows bind port 139 to the first IP addres of every adapter, while port 445 is bound globally. +This means that if you'll disable port 445 (or block it using a firewall), you'll be able to use a different service on port 139 for every IP address. + +###### Additional Notes: +* To free port 139 for a given adapter, go to 'Internet Protocol (TCP/IP) Properties' > Advanced > WINS, and select 'Disable NetBIOS over TCP/IP'. +Uncheck 'File and Printer Sharing for Microsoft Networks' to ensure Windows will not answer to SMB traffic on port 445 for this adapter. + +* It's important to note that disabling NetBIOS over TCP/IP will also disable NetBIOS name service for that adapter (a.k.a. WINS), This service uses UDP port 137. +SMBLibrary offers a name service of its own. + +* You can install a virtual network adapter driver for Windows to be used solely with SMBLibrary: + - You can install the 'Microsoft Loopback adapter' and use it for server-only communication with SMBLibrary. + +###### Windows 7/8/2008/2012: +* It's possible to prevent Windows from using port 445 by removing all of the '\Device\Tcpip_{..}' and '\Device\Tcpip6_{..}' entries from the `Bind' registry key under 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanServer\Linkage'. + +* if you want localhost access from Windows explorer to work as expected, you must specify the IP address that you selected (\\\\127.0.0.1 or \\\\localhost will not work as expected), in addition, I have observed that when connecting to the first IP address of a given adapter, Windows will only attempt to connect to port 445. + +##### Method 3: Use an IP address that is invisible to Windows File Sharing: +Using PCap.Net you can programmatically setup a virtual Network adapter and intercept SMB traffic (similar to how a virtual machine operates), You should use the ARP protocol to notify the network about the new IP address, and then process the incoming SMB traffic using SMBLibrary, good luck! + +Using SMBLibrary Server implementation: +======================================= +Any directory / filesystem / object you wish to share must implement the IFileSystem interface (or the lower-level INTFileStore interface). +You can share anything from actual directories to custom objects, as long as they expose a directory structure. From 4c8a1645047abe5db09122a259d67ae4c0b025bd Mon Sep 17 00:00:00 2001 From: Jacob Hales Date: Sat, 18 Apr 2026 07:43:30 -0600 Subject: [PATCH 230/247] Client: DFS: Add helper classes (#346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add DfsReferralHelper and DfsPath under SMBLibrary/Client/DFS/ as the transport layer building on the already-merged DFS protocol types. DfsReferralHelper sends FSCTL_DFS_GET_REFERRALS via DeviceIOControl with the sentinel FileId (MS-SMB2 §2.2.31) and returns a parsed ResponseGetDfsReferral. DfsPath parses UNC paths into components for referral request construction and future path replacement. These are the building blocks for SMB2DfsFileStore. --- SMBLibrary/Client/DFS/DfsPath.cs | 139 +++++++++++++++++++++ SMBLibrary/Client/DFS/DfsReferralHelper.cs | 44 +++++++ 2 files changed, 183 insertions(+) create mode 100644 SMBLibrary/Client/DFS/DfsPath.cs create mode 100644 SMBLibrary/Client/DFS/DfsReferralHelper.cs diff --git a/SMBLibrary/Client/DFS/DfsPath.cs b/SMBLibrary/Client/DFS/DfsPath.cs new file mode 100644 index 00000000..ac87c214 --- /dev/null +++ b/SMBLibrary/Client/DFS/DfsPath.cs @@ -0,0 +1,139 @@ +/* Copyright (C) 2026 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; + +namespace SMBLibrary.Client.DFS +{ + public class DfsPath + { + private List m_components; + + public DfsPath(string uncPath) + { + if (uncPath == null) + { + throw new ArgumentNullException(nameof(uncPath)); + } + + if (uncPath.Length == 0) + { + throw new ArgumentException("UNC path must not be empty.", nameof(uncPath)); + } + + m_components = new List(); + string[] parts = uncPath.Split(new char[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string part in parts) + { + m_components.Add(part); + } + + if (m_components.Count == 0) + { + throw new ArgumentException("UNC path contains no components.", nameof(uncPath)); + } + } + + private DfsPath(List components) + { + m_components = components; + } + + public string ToUncPath() + { + return @"\\" + String.Join(@"\", m_components.ToArray()); + } + + public DfsPath ReplacePrefix(DfsPath oldPrefix, DfsPath newPrefix) + { + List oldComponents = oldPrefix.m_components; + List newComponents = newPrefix.m_components; + + if (oldComponents.Count > m_components.Count) + { + return this; + } + + for (int i = 0; i < oldComponents.Count; i++) + { + if (!String.Equals(m_components[i], oldComponents[i], StringComparison.OrdinalIgnoreCase)) + { + return this; + } + } + + List result = new List(newComponents); + for (int i = oldComponents.Count; i < m_components.Count; i++) + { + result.Add(m_components[i]); + } + + return new DfsPath(result); + } + + public override string ToString() + { + return ToUncPath(); + } + + public string ServerName + { + get + { + return m_components[0]; + } + } + + public string ShareName + { + get + { + if (m_components.Count > 1) + { + return m_components[1]; + } + return null; + } + } + + public bool HasOnlyOneComponent + { + get + { + return m_components.Count == 1; + } + } + + public bool IsSysVolOrNetLogon + { + get + { + if (m_components.Count < 2) + { + return false; + } + + string share = m_components[1]; + return String.Equals(share, "SYSVOL", StringComparison.OrdinalIgnoreCase) || + String.Equals(share, "NETLOGON", StringComparison.OrdinalIgnoreCase); + } + } + + public bool IsIpc + { + get + { + if (m_components.Count < 2) + { + return false; + } + + return String.Equals(m_components[1], "IPC$", StringComparison.OrdinalIgnoreCase); + } + } + } +} diff --git a/SMBLibrary/Client/DFS/DfsReferralHelper.cs b/SMBLibrary/Client/DFS/DfsReferralHelper.cs new file mode 100644 index 00000000..ca574bd0 --- /dev/null +++ b/SMBLibrary/Client/DFS/DfsReferralHelper.cs @@ -0,0 +1,44 @@ +/* Copyright (C) 2026 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 SMBLibrary.DFS; +using SMBLibrary.SMB2; + +namespace SMBLibrary.Client.DFS +{ + public class DfsReferralHelper + { + // MS-SMB2 §2.2.31: sentinel FileId for DFS referral requests + public static readonly FileID DfsReferralFileId = new FileID() + { + Persistent = 0xFFFFFFFFFFFFFFFF, + Volatile = 0xFFFFFFFFFFFFFFFF + }; + + public static readonly int MaxOutputBufferSize = 8192; + + public static NTStatus GetDfsReferral(ISMBFileStore fileStore, string dfsPath, out ResponseGetDfsReferral referralResponse) + { + referralResponse = null; + + // MS-DFSC §2.2.2: request V4 referrals (highest version) + RequestGetDfsReferral request = new RequestGetDfsReferral(); + request.MaxReferralLevel = 4; + request.RequestFileName = dfsPath; + byte[] inputBytes = request.GetBytes(); + + byte[] outputBytes; + NTStatus status = fileStore.DeviceIOControl(DfsReferralFileId, (uint)IoControlCode.FSCTL_DFS_GET_REFERRALS, inputBytes, out outputBytes, MaxOutputBufferSize); + + if (status == NTStatus.STATUS_SUCCESS && outputBytes != null) + { + referralResponse = new ResponseGetDfsReferral(outputBytes); + } + + return status; + } + } +} From 5ed3bb6501a8f905095ac149222dc0993272986a Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 19 Apr 2026 00:20:13 +0300 Subject: [PATCH 231/247] IAuthenticationClient: Added ResetSecurityContext in order to support DFS --- SMBLibrary/Client/Authentication/IAuthenticationClient.cs | 8 +++++++- .../Client/Authentication/NTLMAuthenticationClient.cs | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/Authentication/IAuthenticationClient.cs b/SMBLibrary/Client/Authentication/IAuthenticationClient.cs index fe9115cb..89bf8dd4 100644 --- a/SMBLibrary/Client/Authentication/IAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/IAuthenticationClient.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2023-2023 Tal Aloni . All rights reserved. +/* Copyright (C) 2023-2026 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, @@ -12,5 +12,11 @@ public interface IAuthenticationClient byte[] InitializeSecurityContext(byte[] securityBlob); byte[] GetSessionKey(); + + /// + /// Used when the client needs to perform login to an additional SMB server, + /// Only used when connecting to a DFS Namespace Server (DFS root). + /// + void ResetSecurityContext(string spn); } } diff --git a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs index d6ca8531..0c31127c 100644 --- a/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs +++ b/SMBLibrary/Client/Authentication/NTLMAuthenticationClient.cs @@ -130,6 +130,14 @@ public virtual byte[] GetSessionKey() return m_sessionKey; } + public virtual void ResetSecurityContext(string spn) + { + m_spn = spn; + m_isNegotiationMessageAcquired = false; + m_negotiateMessageBytes = null; + m_sessionKey = null; + } + private static bool ContainsMechanism(SimpleProtectedNegotiationTokenInit token, byte[] mechanismIdentifier) { for (int index = 0; index < token.MechanismTypeList.Count; index++) From 15f85fce093fbba37fc5925c70375f0778891a61 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 19 Apr 2026 00:24:12 +0300 Subject: [PATCH 232/247] SMB1Client, SMB2Client: Added Transport property --- SMBLibrary/Client/SMB1Client.cs | 8 ++++++++ SMBLibrary/Client/SMB2Client.cs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 5a859700..c8cbbd8f 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -743,6 +743,14 @@ public uint MaxWriteSize } } + public SMBTransportType Transport + { + get + { + return m_transport; + } + } + public bool IsConnected { get diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 5ff53646..2ac84c40 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -762,6 +762,14 @@ public uint MaxWriteSize } } + public SMBTransportType Transport + { + get + { + return m_transport; + } + } + public bool IsConnected { get From 356a35175b36d8acda7bca2df2807895c1ab53f2 Mon Sep 17 00:00:00 2001 From: "Francisco A. Lozano" Date: Sun, 19 Apr 2026 20:25:50 +0200 Subject: [PATCH 233/247] Replace #if NETSTANDARD2_0 with #if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER (#349) Allow compilation to work when utilizing modern .NET targets --- .../Authentication/NTLM/Helpers/NTLMCryptography.cs | 4 ++-- SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs | 10 +++++----- SMBLibrary/Utilities/SocketUtils.cs | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 0e2d8eff..9e929ac9 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -80,7 +80,7 @@ public static ICryptoTransform CreateWeakDesEncryptor(CipherMode mode, byte[] rg ICryptoTransform transform; if (DES.IsWeakKey(rgbKey) || DES.IsSemiWeakKey(rgbKey)) { -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER MethodInfo getTransformCoreMethodInfo = des.GetType().GetMethod("CreateTransformCore", BindingFlags.NonPublic | BindingFlags.Static); object[] getTransformCoreParameters = { mode, des.Padding, rgbKey, rgbIV, des.BlockSize / 8 , des.FeedbackSize / 8, des.BlockSize / 8, true }; transform = getTransformCoreMethodInfo.Invoke(null, getTransformCoreParameters) as ICryptoTransform; @@ -137,7 +137,7 @@ public static byte[] DesLongEncrypt(byte[] key, byte[] plainText) public static Encoding GetOEMEncoding() { -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER return ASCIIEncoding.GetEncoding(28591); #else return Encoding.GetEncoding(CultureInfo.CurrentCulture.TextInfo.OEMCodePage); diff --git a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs index c013fd1c..53ca9ab7 100644 --- a/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs +++ b/SMBLibrary/NetBios/NBTConnectionReceiveBuffer.cs @@ -5,7 +5,7 @@ * either version 3 of the License, or (at your option) any later version. */ using System; -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER using System.Buffers; #endif using System.IO; @@ -35,7 +35,7 @@ public NBTConnectionReceiveBuffer(int bufferLength) throw new ArgumentException("bufferLength must be large enough to hold the largest possible NBT packet"); } -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER m_buffer = ArrayPool.Shared.Rent(bufferLength); #else m_buffer = new byte[bufferLength]; @@ -44,7 +44,7 @@ public NBTConnectionReceiveBuffer(int bufferLength) public void IncreaseBufferSize(int bufferLength) { -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER byte[] buffer = ArrayPool.Shared.Rent(bufferLength); #else byte[] buffer = new byte[bufferLength]; @@ -55,7 +55,7 @@ public void IncreaseBufferSize(int bufferLength) m_readOffset = 0; } -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER ArrayPool.Shared.Return(m_buffer); #endif m_buffer = buffer; @@ -130,7 +130,7 @@ private void RemovePacketBytes() public void Dispose() { -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER if (m_buffer != null) { ArrayPool.Shared.Return(m_buffer); diff --git a/SMBLibrary/Utilities/SocketUtils.cs b/SMBLibrary/Utilities/SocketUtils.cs index 4e7d5542..57766205 100644 --- a/SMBLibrary/Utilities/SocketUtils.cs +++ b/SMBLibrary/Utilities/SocketUtils.cs @@ -6,7 +6,7 @@ */ using System; using System.Net.Sockets; -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER using System.Runtime.InteropServices; #endif @@ -14,7 +14,7 @@ namespace Utilities { public class SocketUtils { -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER private static bool IsDotNetFramework() { const string DotnetFrameworkDescription = ".NET Framework"; @@ -34,7 +34,7 @@ public static void SetKeepAlive(Socket socket, TimeSpan timeout) public static void SetKeepAlive(Socket socket, bool enable, TimeSpan timeout, TimeSpan interval) { socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER if (IsDotNetFramework()) { #endif @@ -44,7 +44,7 @@ public static void SetKeepAlive(Socket socket, bool enable, TimeSpan timeout, Ti LittleEndianWriter.WriteUInt32(tcp_keepalive, 4, (uint)timeout.TotalMilliseconds); LittleEndianWriter.WriteUInt32(tcp_keepalive, 8, (uint)interval.TotalMilliseconds); socket.IOControl(IOControlCode.KeepAliveValues, tcp_keepalive, null); -#if NETSTANDARD2_0 +#if NETSTANDARD2_0_OR_GREATER || NET5_0_OR_GREATER } else { From 336c47ff5138df3caad8d403b027514234c80b2f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 20 Apr 2026 23:45:49 +0300 Subject: [PATCH 234/247] SMB2Client: Moved CIFS SPN creation to a separate method --- SMBLibrary/Client/SMB2Client.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 2ac84c40..b6ac9391 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -248,7 +248,7 @@ public NTStatus Login(string domainName, string userName, string password) public NTStatus Login(string domainName, string userName, string password, AuthenticationMethod authenticationMethod) { - string spn = string.Format("cifs/{0}", m_serverName); + string spn = CreateSpn(m_serverName); NTLMAuthenticationClient authenticationClient = new NTLMAuthenticationClient(domainName, userName, password, spn, authenticationMethod); return Login(authenticationClient); } @@ -813,5 +813,10 @@ private void TrySendPacket(Socket socket, SessionPacket packet) m_isConnected = false; } } + + private static string CreateSpn(string serverAddress) + { + return $"cifs/{serverAddress}"; + } } } From 196d64e390aa0aed3051599ae41331bdc8ff62db Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 24 May 2026 19:36:41 +0300 Subject: [PATCH 235/247] SMBLibrary.Win32: SSPIHelper: Extract GetInitialMessage method that can be used by both NTLM and Kerberos --- SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs | 28 ++------------- SMBLibrary.Win32/Security/SSPIHelper.cs | 37 +++++++++++++++++++- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs b/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs index c43d8634..0fc6de32 100644 --- a/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs +++ b/SMBLibrary.Win32/Security/SSPIHelper.NTLM.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2026 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, @@ -59,32 +59,8 @@ public static byte[] GetType1Message(string userName, string password, out SecHa public static byte[] GetType1Message(string domainName, string userName, string password, out SecHandle clientContext) { SecHandle credentialsHandle = AcquireNTLMCredentialsHandle(domainName, userName, password); - clientContext = new SecHandle(); - SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); - SecBufferDesc output = new SecBufferDesc(outputBuffer); - uint contextAttributes; - SECURITY_INTEGER expiry; - - uint result = InitializeSecurityContext(ref credentialsHandle, IntPtr.Zero, null, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, IntPtr.Zero, 0, ref clientContext, ref output, out contextAttributes, out expiry); - if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) - { - if (result == SEC_E_INVALID_HANDLE) - { - throw new Exception("InitializeSecurityContext failed, Invalid handle"); - } - else if (result == SEC_E_BUFFER_TOO_SMALL) - { - throw new Exception("InitializeSecurityContext failed, Buffer too small"); - } - else - { - throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8")); - } - } + byte[] messageBytes = GetInitialMessage(credentialsHandle, out clientContext); FreeCredentialsHandle(ref credentialsHandle); - byte[] messageBytes = output.GetBufferBytes(0); - outputBuffer.Dispose(); - output.Dispose(); return messageBytes; } diff --git a/SMBLibrary.Win32/Security/SSPIHelper.cs b/SMBLibrary.Win32/Security/SSPIHelper.cs index dbd7760f..326de570 100644 --- a/SMBLibrary.Win32/Security/SSPIHelper.cs +++ b/SMBLibrary.Win32/Security/SSPIHelper.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2026 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, @@ -178,6 +178,41 @@ public extern static uint DeleteSecurityContext( ref SecHandle phContext ); + public static byte[] GetInitialMessage(SecHandle credentialsHandle, out SecHandle clientContext) + { + return GetInitialMessage(credentialsHandle, null, out clientContext); + } + + public static byte[] GetInitialMessage(SecHandle credentialsHandle, string targetName, out SecHandle clientContext) + { + clientContext = new SecHandle(); + SecBuffer outputBuffer = new SecBuffer(MAX_TOKEN_SIZE); + SecBufferDesc output = new SecBufferDesc(outputBuffer); + uint contextAttributes; + SECURITY_INTEGER expiry; + + uint result = InitializeSecurityContext(ref credentialsHandle, IntPtr.Zero, targetName, ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY, 0, SECURITY_NATIVE_DREP, IntPtr.Zero, 0, ref clientContext, ref output, out contextAttributes, out expiry); + if (result != SEC_E_OK && result != SEC_I_CONTINUE_NEEDED) + { + if (result == SEC_E_INVALID_HANDLE) + { + throw new Exception("InitializeSecurityContext failed, Invalid handle"); + } + else if (result == SEC_E_BUFFER_TOO_SMALL) + { + throw new Exception("InitializeSecurityContext failed, Buffer too small"); + } + else + { + throw new Exception("InitializeSecurityContext failed, Error code 0x" + result.ToString("X8")); + } + } + byte[] messageBytes = output.GetBufferBytes(0); + outputBuffer.Dispose(); + output.Dispose(); + return messageBytes; + } + public static string GetUserName(SecHandle context) { string userName; From 7742c28d3fc598a6a7140b8c38771d79fdbfcc14 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sun, 24 May 2026 19:50:38 +0300 Subject: [PATCH 236/247] SMBLibrary.Win32: SSPIHelper: Added AcquireKerberosCredentialsHandle --- .../Security/SSPIHelper.Kerberos.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 SMBLibrary.Win32/Security/SSPIHelper.Kerberos.cs diff --git a/SMBLibrary.Win32/Security/SSPIHelper.Kerberos.cs b/SMBLibrary.Win32/Security/SSPIHelper.Kerberos.cs new file mode 100644 index 00000000..cc75071e --- /dev/null +++ b/SMBLibrary.Win32/Security/SSPIHelper.Kerberos.cs @@ -0,0 +1,55 @@ +/* Copyright (C) 2026 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.Runtime.InteropServices; + +namespace SMBLibrary.Win32.Security +{ + public partial class SSPIHelper + { + public static SecHandle AcquireKerberosCredentialsHandle(string serverPrincipalName) + { + return AcquireKerberosCredentialsHandle(serverPrincipalName, null); + } + + public static SecHandle AcquireKerberosCredentialsHandle(string serverPrincipalName, string domainName, string userName, string password) + { + SEC_WINNT_AUTH_IDENTITY auth = GetWinNTAuthIdentity(domainName, userName, password); + return AcquireKerberosCredentialsHandle(serverPrincipalName, auth); + } + + private static SecHandle AcquireKerberosCredentialsHandle(string serverPrincipalName, SEC_WINNT_AUTH_IDENTITY? auth) + { + SecHandle credential; + SECURITY_INTEGER expiry; + + IntPtr pAuthData; + if (auth.HasValue) + { + pAuthData = Marshal.AllocHGlobal(Marshal.SizeOf(auth.Value)); + Marshal.StructureToPtr(auth.Value, pAuthData, false); + } + else + { + pAuthData = IntPtr.Zero; + } + + uint result = AcquireCredentialsHandle(serverPrincipalName, "Kerberos", SECPKG_CRED_BOTH, IntPtr.Zero, pAuthData, IntPtr.Zero, IntPtr.Zero, out credential, out expiry); + if (pAuthData != IntPtr.Zero) + { + Marshal.FreeHGlobal(pAuthData); + } + + if (result != SEC_E_OK) + { + throw new Exception("AcquireCredentialsHandle failed, Error code 0x" + result.ToString("X8")); + } + + return credential; + } + } +} From 71e67aeb241ac98f0ff3c446c8cfa9353c277829 Mon Sep 17 00:00:00 2001 From: Jacob Hales Date: Fri, 10 Jul 2026 06:14:56 -0600 Subject: [PATCH 237/247] Client: Add SMB2DfsFileStore to follow DFS referrals (#352) * SMB2Client: Retain IAuthenticationClient instance after login in order to support DFS * Client: DFS: Add SMB2DfsFileStore to follow DFS referrals --- ClientExamples.md | 8 +- .../Client/SMB2DfsFileStoreTests.cs | 217 ++++++++++ SMBLibrary/Client/DFS/DfsPath.cs | 12 + SMBLibrary/Client/DFS/SMB2DfsFileStore.cs | 405 ++++++++++++++++++ SMBLibrary/Client/SMB2Client.cs | 47 +- 5 files changed, 687 insertions(+), 2 deletions(-) create mode 100644 SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs create mode 100644 SMBLibrary/Client/DFS/SMB2DfsFileStore.cs diff --git a/ClientExamples.md b/ClientExamples.md index dd9a5172..1592e7c4 100644 --- a/ClientExamples.md +++ b/ClientExamples.md @@ -194,7 +194,7 @@ Note that in order for Kerberos.NET to work on non-Windows platforms, you must p public class KerberosNetAuthenticationClient : IAuthenticationClient { private readonly KerberosClient m_kerberosClient; - private readonly string m_spn; + private string m_spn; private byte[] m_sessionKey; public KerberosNetAuthenticationClient(string user, string password, string domain, string host) @@ -213,5 +213,11 @@ public class KerberosNetAuthenticationClient : IAuthenticationClient } public byte[] GetSessionKey() => m_sessionKey; + + public void ResetSecurityContext(string spn) + { + m_spn = spn; + m_sessionKey = null; + } } ``` \ No newline at end of file diff --git a/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs b/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs new file mode 100644 index 00000000..c3438abe --- /dev/null +++ b/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs @@ -0,0 +1,217 @@ +/* Copyright (C) 2026 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.Client; +using SMBLibrary.Client.DFS; +using SMBLibrary.DFS; + +namespace SMBLibrary.Tests.Client +{ + [TestClass] + public class SMB2DfsFileStoreTests + { + [TestMethod] + public void CreateFile_WhenNotCovered_FollowsReferralToTargetAndRoutesHandle() + { + // Arrange: a link referral \SERVER1\DfsRoot\Link -> \SERVER2\Share + byte[] referralBytes = BuildReferral(@"\SERVER1\DfsRoot\Link", @"\SERVER2\Share"); + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_PATH_NOT_COVERED, ReferralResponseBytes = referralBytes }; + FakeFileStore targetStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + Dictionary targets = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { @"SERVER2\Share", targetStore } + }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, targets); + + // Act + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, @"Link\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + // Assert: resolved to the target, remainder path preserved + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + Assert.IsNotNull(handle); + CollectionAssert.Contains(dfsFileStore.ConnectRequests, @"SERVER2\Share"); + Assert.AreEqual("file.txt", targetStore.LastCreateFilePath); + + // The returned handle must route subsequent operations to the target it was opened against. + byte[] data; + dfsFileStore.ReadFile(out data, handle, 0, 3); + Assert.AreEqual(1, targetStore.ReadFileCount); + Assert.AreEqual(0, rootStore.ReadFileCount); + } + + [TestMethod] + public void CreateFile_WhenCovered_UsesDfsRootStoreWithoutReferral() + { + // Arrange + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, new Dictionary()); + + // Act + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, @"folder\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + // Assert + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + Assert.AreEqual(0, dfsFileStore.ConnectRequests.Count); + Assert.AreEqual(@"folder\file.txt", rootStore.LastCreateFilePath); + + byte[] data; + dfsFileStore.ReadFile(out data, handle, 0, 3); + Assert.AreEqual(1, rootStore.ReadFileCount); + } + + [TestMethod] + public void CreateFile_WhenReferralTargetUnreachable_ReturnsPathNotCovered() + { + // Arrange: no target registered => ConnectToTarget returns null. + byte[] referralBytes = BuildReferral(@"\SERVER1\DfsRoot\Link", @"\SERVER2\Share"); + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_PATH_NOT_COVERED, ReferralResponseBytes = referralBytes }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, new Dictionary()); + + // Act + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, @"Link\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + // Assert + Assert.AreEqual(NTStatus.STATUS_PATH_NOT_COVERED, status); + Assert.IsNull(handle); + } + + [TestMethod] + public void CreateFile_WhenFirstReferralTargetUnreachable_FailsOverToNextTarget() + { + // Arrange: two referral targets, only the second is reachable + byte[] referralBytes = BuildReferral(@"\SERVER1\DfsRoot\Link", @"\SERVER2\Share", @"\SERVER3\Share"); + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_PATH_NOT_COVERED, ReferralResponseBytes = referralBytes }; + FakeFileStore targetStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + Dictionary targets = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { @"SERVER3\Share", targetStore } + }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, targets); + + // Act + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, @"Link\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + // Assert + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + CollectionAssert.Contains(dfsFileStore.ConnectRequests, @"SERVER2\Share"); + CollectionAssert.Contains(dfsFileStore.ConnectRequests, @"SERVER3\Share"); + Assert.AreEqual("file.txt", targetStore.LastCreateFilePath); + } + + private static byte[] BuildReferral(string dfsPath, params string[] networkAddresses) + { + ResponseGetDfsReferral referral = new ResponseGetDfsReferral(); + referral.PathConsumed = (ushort)(dfsPath.Length * 2); + referral.ReferralHeaderFlags = DfsReferralHeaderFlags.StorageServers; + foreach (string networkAddress in networkAddresses) + { + referral.ReferralEntries.Add(new DfsReferralEntryV4() + { + TimeToLive = 300, + ReferralEntryFlags = DfsReferralEntryFlags.None, + DfsPath = dfsPath, + DfsAlternatePath = dfsPath, + NetworkAddress = networkAddress, + ServiceSiteGuid = Guid.Empty + }); + } + return referral.GetBytes(); + } + + /// + /// SMB2DfsFileStore subclass that intercepts target connections so the referral-following logic + /// can be exercised without a live server. + /// + private class TestableDfsFileStore : SMB2DfsFileStore + { + private Dictionary m_targets; + public List ConnectRequests = new List(); + + public TestableDfsFileStore(SMB2Client client, string serverName, string shareName, ISMBFileStore dfsFileStore, Dictionary targets) + : base(client, serverName, shareName, dfsFileStore) + { + m_targets = targets; + } + + protected override ISMBFileStore ConnectToTarget(string serverName, string shareName) + { + string key = serverName + @"\" + shareName; + ConnectRequests.Add(key); + ISMBFileStore fileStore; + if (m_targets.TryGetValue(key, out fileStore)) + { + return fileStore; + } + return null; + } + } + + private class FakeFileStore : ISMBFileStore + { + public NTStatus CreateFileStatus = NTStatus.STATUS_SUCCESS; + public byte[] ReferralResponseBytes; + public string LastCreateFilePath; + public int ReadFileCount; + + public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext) + { + LastCreateFilePath = path; + if (CreateFileStatus == NTStatus.STATUS_SUCCESS) + { + handle = new object(); + fileStatus = FileStatus.FILE_OPENED; + return NTStatus.STATUS_SUCCESS; + } + handle = null; + fileStatus = FileStatus.FILE_DOES_NOT_EXIST; + return CreateFileStatus; + } + + public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength) + { + output = ReferralResponseBytes; + return (ReferralResponseBytes != null) ? NTStatus.STATUS_SUCCESS : NTStatus.STATUS_NOT_SUPPORTED; + } + + public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount) + { + ReadFileCount++; + data = new byte[maxCount]; + return NTStatus.STATUS_SUCCESS; + } + + public NTStatus CloseFile(object handle) { return NTStatus.STATUS_SUCCESS; } + public NTStatus Disconnect() { return NTStatus.STATUS_SUCCESS; } + public uint MaxReadSize { get { return 65536; } } + public uint MaxWriteSize { get { return 65536; } } + + public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data) { throw new NotImplementedException(); } + public NTStatus FlushFileBuffers(object handle) { throw new NotImplementedException(); } + public NTStatus LockFile(object handle, long byteOffset, long length, bool exclusiveLock) { throw new NotImplementedException(); } + public NTStatus UnlockFile(object handle, long byteOffset, long length) { throw new NotImplementedException(); } + public NTStatus QueryDirectory(out List result, object handle, string fileName, FileInformationClass informationClass) { throw new NotImplementedException(); } + public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass) { throw new NotImplementedException(); } + public NTStatus SetFileInformation(object handle, FileInformation information) { throw new NotImplementedException(); } + public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass) { throw new NotImplementedException(); } + public NTStatus SetFileSystemInformation(FileSystemInformation information) { throw new NotImplementedException(); } + public NTStatus GetSecurityInformation(out SecurityDescriptor result, object handle, SecurityInformation securityInformation) { throw new NotImplementedException(); } + public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor) { throw new NotImplementedException(); } + public NTStatus NotifyChange(out object ioRequest, object handle, NotifyChangeFilter completionFilter, bool watchTree, int outputBufferSize, OnNotifyChangeCompleted onNotifyChangeCompleted, object context) { throw new NotImplementedException(); } + public NTStatus Cancel(object ioRequest) { throw new NotImplementedException(); } + } + } +} diff --git a/SMBLibrary/Client/DFS/DfsPath.cs b/SMBLibrary/Client/DFS/DfsPath.cs index ac87c214..39fc2a7c 100644 --- a/SMBLibrary/Client/DFS/DfsPath.cs +++ b/SMBLibrary/Client/DFS/DfsPath.cs @@ -100,6 +100,18 @@ public string ShareName } } + public string PathWithinShare + { + get + { + if (m_components.Count > 2) + { + return String.Join(@"\", m_components.GetRange(2, m_components.Count - 2).ToArray()); + } + return String.Empty; + } + } + public bool HasOnlyOneComponent { get diff --git a/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs b/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs new file mode 100644 index 00000000..943a3ccb --- /dev/null +++ b/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs @@ -0,0 +1,405 @@ +/* Copyright (C) 2026 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 SMBLibrary.DFS; + +namespace SMBLibrary.Client.DFS +{ + /// + /// ISMBFileStore wrapper for a DFS root share. + /// When a CreateFile is not covered by the DFS root (STATUS_PATH_NOT_COVERED), a DFS referral is + /// requested and the operation is retried against a referral target, connecting to another server + /// when necessary (reusing the authentication client via IAuthenticationClient.ResetSecurityContext). + /// + internal class SMB2DfsFileStore : ISMBFileStore + { + // Guards against referral loops when chaining interlinks. + private const int MaxReferralHopCount = 8; + + private SMB2Client m_client; + private string m_serverName; + private string m_shareName; + private ISMBFileStore m_dfsFileStore; + + // Connections and tree connections established while following referrals to other targets. + private Dictionary m_targetClients; + private Dictionary m_targetFileStores; + + internal SMB2DfsFileStore(SMB2Client client, string serverName, string shareName, ISMBFileStore dfsFileStore) + { + m_client = client; + m_serverName = serverName; + m_shareName = shareName; + m_dfsFileStore = dfsFileStore; + m_targetClients = new Dictionary(StringComparer.OrdinalIgnoreCase); + m_targetFileStores = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext) + { + handle = null; + fileStatus = FileStatus.FILE_DOES_NOT_EXIST; + + ISMBFileStore fileStore = m_dfsFileStore; + string currentServer = m_serverName; + string currentShare = m_shareName; + string effectivePath = path; + + for (int hopCount = 0; ; hopCount++) + { + object innerHandle; + NTStatus status = fileStore.CreateFile(out innerHandle, out fileStatus, effectivePath, desiredAccess, fileAttributes, shareAccess, createDisposition, createOptions, securityContext); + if (status != NTStatus.STATUS_PATH_NOT_COVERED) + { + if (status == NTStatus.STATUS_SUCCESS) + { + handle = new DfsHandle(fileStore, innerHandle); + } + return status; + } + + if (hopCount >= MaxReferralHopCount) + { + return status; + } + + string dfsPath = BuildUncPath(currentServer, currentShare, effectivePath); + List targets; + if (!TryGetReferralTargets(fileStore, dfsPath, out targets)) + { + return status; + } + + // MS-DFSC 3.1.5.4.3: targets are listed in order of preference, try the next target when a target is unreachable + ISMBFileStore targetFileStore = null; + DfsPath target = null; + foreach (DfsPath referralTarget in targets) + { + targetFileStore = GetOrConnectFileStore(referralTarget.ServerName, referralTarget.ShareName); + if (targetFileStore != null) + { + target = referralTarget; + break; + } + } + + if (targetFileStore == null) + { + return status; + } + + fileStore = targetFileStore; + currentServer = target.ServerName; + currentShare = target.ShareName; + effectivePath = target.PathWithinShare; + } + } + + private static bool TryGetReferralTargets(ISMBFileStore fileStore, string dfsPath, out List targets) + { + targets = new List(); + ResponseGetDfsReferral referralResponse; + try + { + NTStatus status = DfsReferralHelper.GetDfsReferral(fileStore, dfsPath, out referralResponse); + if (status != NTStatus.STATUS_SUCCESS || referralResponse == null) + { + return false; + } + } + catch + { + // e.g. a malformed referral response buffer + return false; + } + + DfsPath requestedPath = new DfsPath(dfsPath); + foreach (DfsReferralEntry referralEntry in referralResponse.ReferralEntries) + { + // A STATUS_PATH_NOT_COVERED referral is always a V1-V4 link/root referral; only V3/V4 are handled. + DfsReferralEntryV3 entry = referralEntry as DfsReferralEntryV3; + if (entry == null || String.IsNullOrEmpty(entry.DfsPath) || String.IsNullOrEmpty(entry.NetworkAddress)) + { + continue; + } + + DfsPath target = requestedPath.ReplacePrefix(new DfsPath(entry.DfsPath), new DfsPath(entry.NetworkAddress)); + // ReplacePrefix returns the path unchanged when the referral does not cover it + if (ReferenceEquals(target, requestedPath) || + String.IsNullOrEmpty(target.ShareName) || + String.Equals(target.ToUncPath(), requestedPath.ToUncPath(), StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + targets.Add(target); + } + + return targets.Count > 0; + } + + private ISMBFileStore GetOrConnectFileStore(string serverName, string shareName) + { + string key = BuildUncPath(serverName, shareName, null); + ISMBFileStore fileStore; + if (m_targetFileStores.TryGetValue(key, out fileStore)) + { + return fileStore; + } + + fileStore = ConnectToTarget(serverName, shareName); + if (fileStore == null) + { + return null; + } + + if (fileStore is SMB2DfsFileStore dfsFileStore) + { + // Use the underlying file store, referrals from the target are followed (and hop-limited) by this instance + fileStore = dfsFileStore.m_dfsFileStore; + } + + m_targetFileStores.Add(key, fileStore); + return fileStore; + } + + /// + /// Tree connects to a referral target share, reusing an existing connection to the target server when possible. + /// + protected virtual ISMBFileStore ConnectToTarget(string serverName, string shareName) + { + SMB2Client client = GetOrConnectClient(serverName); + if (client == null) + { + return null; + } + + NTStatus status; + ISMBFileStore fileStore = client.TreeConnect(shareName, out status); + if (status != NTStatus.STATUS_SUCCESS) + { + return null; + } + return fileStore; + } + + private SMB2Client GetOrConnectClient(string serverName) + { + if (String.Equals(serverName, m_serverName, StringComparison.OrdinalIgnoreCase)) + { + return m_client; + } + + SMB2Client client; + if (m_targetClients.TryGetValue(serverName, out client)) + { + return client; + } + + client = m_client.ConnectAndLoginToDfsTarget(serverName); + if (client == null) + { + return null; + } + + m_targetClients.Add(serverName, client); + return client; + } + + private static string BuildUncPath(string serverName, string shareName, string pathWithinShare) + { + string uncPath = @"\\" + serverName + @"\" + shareName; + if (!String.IsNullOrEmpty(pathWithinShare)) + { + uncPath += @"\" + pathWithinShare; + } + return uncPath; + } + + public NTStatus CloseFile(object handle) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.CloseFile(dfsHandle.Handle); + } + + public NTStatus ReadFile(out byte[] data, object handle, long offset, int maxCount) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.ReadFile(out data, dfsHandle.Handle, offset, maxCount); + } + + public NTStatus WriteFile(out int numberOfBytesWritten, object handle, long offset, byte[] data) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.WriteFile(out numberOfBytesWritten, dfsHandle.Handle, offset, data); + } + + public NTStatus FlushFileBuffers(object handle) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.FlushFileBuffers(dfsHandle.Handle); + } + + public NTStatus LockFile(object handle, long byteOffset, long length, bool exclusiveLock) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.LockFile(dfsHandle.Handle, byteOffset, length, exclusiveLock); + } + + public NTStatus UnlockFile(object handle, long byteOffset, long length) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.UnlockFile(dfsHandle.Handle, byteOffset, length); + } + + public NTStatus QueryDirectory(out List result, object handle, string fileName, FileInformationClass informationClass) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.QueryDirectory(out result, dfsHandle.Handle, fileName, informationClass); + } + + public NTStatus GetFileInformation(out FileInformation result, object handle, FileInformationClass informationClass) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.GetFileInformation(out result, dfsHandle.Handle, informationClass); + } + + public NTStatus SetFileInformation(object handle, FileInformation information) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.SetFileInformation(dfsHandle.Handle, information); + } + + public NTStatus GetFileSystemInformation(out FileSystemInformation result, FileSystemInformationClass informationClass) + { + return m_dfsFileStore.GetFileSystemInformation(out result, informationClass); + } + + public NTStatus SetFileSystemInformation(FileSystemInformation information) + { + return m_dfsFileStore.SetFileSystemInformation(information); + } + + public NTStatus GetSecurityInformation(out SecurityDescriptor result, object handle, SecurityInformation securityInformation) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.GetSecurityInformation(out result, dfsHandle.Handle, securityInformation); + } + + public NTStatus SetSecurityInformation(object handle, SecurityInformation securityInformation, SecurityDescriptor securityDescriptor) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.SetSecurityInformation(dfsHandle.Handle, securityInformation, securityDescriptor); + } + + public NTStatus NotifyChange(out object ioRequest, object handle, NotifyChangeFilter completionFilter, bool watchTree, int outputBufferSize, OnNotifyChangeCompleted onNotifyChangeCompleted, object context) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + object innerIoRequest; + NTStatus status = dfsHandle.FileStore.NotifyChange(out innerIoRequest, dfsHandle.Handle, completionFilter, watchTree, outputBufferSize, onNotifyChangeCompleted, context); + ioRequest = (innerIoRequest != null) ? new DfsHandle(dfsHandle.FileStore, innerIoRequest) : null; + return status; + } + + public NTStatus Cancel(object ioRequest) + { + DfsHandle dfsHandle = GetDfsHandle(ioRequest); + return dfsHandle.FileStore.Cancel(dfsHandle.Handle); + } + + public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength) + { + DfsHandle dfsHandle = GetDfsHandle(handle); + return dfsHandle.FileStore.DeviceIOControl(dfsHandle.Handle, ctlCode, input, out output, maxOutputLength); + } + + public NTStatus Disconnect() + { + foreach (ISMBFileStore fileStore in m_targetFileStores.Values) + { + try + { + fileStore.Disconnect(); + } + catch (InvalidOperationException) + { + // The connection to the target has already been lost + } + } + m_targetFileStores.Clear(); + + foreach (SMB2Client client in m_targetClients.Values) + { + try + { + client.Logoff(); + } + catch (InvalidOperationException) + { + } + client.Disconnect(); + } + m_targetClients.Clear(); + + return m_dfsFileStore.Disconnect(); + } + + private DfsHandle GetDfsHandle(object handle) + { + DfsHandle dfsHandle = handle as DfsHandle; + if (dfsHandle == null) + { + // A handle that did not originate from this instance (e.g. the FileID used for DFS referral requests) is directed to the DFS root file store + return new DfsHandle(m_dfsFileStore, handle); + } + return dfsHandle; + } + + public uint MaxReadSize + { + get + { + uint maxReadSize = m_dfsFileStore.MaxReadSize; + foreach (ISMBFileStore fileStore in m_targetFileStores.Values) + { + maxReadSize = Math.Min(maxReadSize, fileStore.MaxReadSize); + } + return maxReadSize; + } + } + + public uint MaxWriteSize + { + get + { + uint maxWriteSize = m_dfsFileStore.MaxWriteSize; + foreach (ISMBFileStore fileStore in m_targetFileStores.Values) + { + maxWriteSize = Math.Min(maxWriteSize, fileStore.MaxWriteSize); + } + return maxWriteSize; + } + } + + /// + /// Associates a handle (or NotifyChange ioRequest) with the file store that produced it, so that + /// subsequent operations are routed to the referral target the handle was opened against. + /// + private class DfsHandle + { + public readonly ISMBFileStore FileStore; + public readonly object Handle; + + public DfsHandle(ISMBFileStore fileStore, object handle) + { + FileStore = fileStore; + Handle = handle; + } + } + } +} diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index b6ac9391..6305d0b3 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -11,6 +11,7 @@ using System.Net.Sockets; using System.Threading; using SMBLibrary.Client.Authentication; +using SMBLibrary.Client.DFS; using SMBLibrary.NetBios; using SMBLibrary.SMB2; using Utilities; @@ -60,6 +61,7 @@ public class SMB2Client : ISMBClient private byte[] m_preauthIntegrityHashValue; // SMB 3.1.1 private ushort m_availableCredits = 1; private bool m_connectionSupportsMultiCredit = false; + private IAuthenticationClient m_authenticationClient; public SMB2Client() : this(DefaultResponseTimeoutInMilliseconds) { @@ -298,6 +300,7 @@ public NTStatus Login(IAuthenticationClient authenticationClient) { m_sessionID = response.Header.SessionID; m_sessionKey = authenticationClient.GetSessionKey(); + m_authenticationClient = authenticationClient; SessionFlags sessionFlags = finalSessionSetupResponse.SessionFlags; if ((sessionFlags & SessionFlags.IsGuest) > 0) { @@ -380,7 +383,13 @@ public ISMBFileStore TreeConnect(string shareName, out NTStatus status) if (response.Header.Status == NTStatus.STATUS_SUCCESS && response is TreeConnectResponse treeConnectResponse) { bool encryptShareData = (treeConnectResponse.ShareFlags & ShareFlags.EncryptData) > 0; - return new SMB2FileStore(this, response.Header.TreeID, m_encryptSessionData || encryptShareData); + SMB2FileStore fileStore = new SMB2FileStore(this, response.Header.TreeID, m_encryptSessionData || encryptShareData); + if ((treeConnectResponse.ShareFlags & ShareFlags.DfsRoot) > 0) + { + // [MS-DFSC] The share is a DFS namespace root; wrap the file store so that DFS referrals are followed transparently. + return new SMB2DfsFileStore(this, m_serverName, shareName, fileStore); + } + return fileStore; } } else @@ -818,5 +827,41 @@ private static string CreateSpn(string serverAddress) { return $"cifs/{serverAddress}"; } + + /// + /// Connects to a DFS referral target server and logs in by reusing this client's authentication client, + /// rebinding its security context to the target server (see IAuthenticationClient.ResetSecurityContext). + /// + internal SMB2Client ConnectAndLoginToDfsTarget(string serverName) + { + if (m_authenticationClient == null) + { + return null; + } + + SMB2Client targetClient = new SMB2Client(m_responseTimeoutInMilliseconds, m_enableSMB311Support); + try + { + if (!targetClient.Connect(serverName, m_transport)) + { + return null; + } + } + catch + { + // Connect throws when the server name cannot be resolved + return null; + } + + m_authenticationClient.ResetSecurityContext(CreateSpn(serverName)); + NTStatus loginStatus = targetClient.Login(m_authenticationClient); + if (loginStatus != NTStatus.STATUS_SUCCESS) + { + targetClient.Disconnect(); + return null; + } + + return targetClient; + } } } From 583bccf81014c8b5806098fe4f75987da967243f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Jul 2026 15:28:03 +0300 Subject: [PATCH 238/247] SMB2ClientTests: Reduce likelyhood of two TCP listeners attempting to use the same port --- SMBLibrary.Tests/Client/SMB2ClientTests.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SMBLibrary.Tests/Client/SMB2ClientTests.cs b/SMBLibrary.Tests/Client/SMB2ClientTests.cs index 40e88b3e..5396bad4 100644 --- a/SMBLibrary.Tests/Client/SMB2ClientTests.cs +++ b/SMBLibrary.Tests/Client/SMB2ClientTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2024-2025 Tal Aloni . All rights reserved. +/* Copyright (C) 2024-2026 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, @@ -17,6 +17,10 @@ namespace SMBLibrary.Tests.Client [TestClass] public class SMB2ClientTests { + private static readonly int s_minPort = 1025; + private static readonly int s_maxPort = 50000; + private static int s_nextServerPort = s_minPort + new Random().Next(s_maxPort - s_minPort); + private int m_serverPort; private TcpListener m_tcpListener; private bool m_clientConnected; @@ -24,7 +28,7 @@ public class SMB2ClientTests [TestInitialize] public void Initialize() { - m_serverPort = 1000 + new Random().Next(50000); + m_serverPort = Interlocked.Increment(ref s_nextServerPort); m_tcpListener = new TcpListener(IPAddress.Loopback, m_serverPort); m_tcpListener.Start(); } From 6d0134c350adbcf42774a48612a742a47fb0a024 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Fri, 10 Jul 2026 15:31:32 +0300 Subject: [PATCH 239/247] NTDirectoryFileSystemTests: Use temp folder instead of 'C:\Tests' --- SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs b/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs index 591572b5..9b5b27b8 100644 --- a/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs +++ b/SMBLibrary.Tests/NTFileStore/NTDirectoryFileSystemTests.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2019-2025 Tal Aloni . All rights reserved. +/* Copyright (C) 2019-2026 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, @@ -14,7 +14,7 @@ namespace SMBLibrary.Tests [TestClass] public class NTDirectoryFileSystemTests : NTFileStoreTests { - private static readonly string TestDirectoryPath = @"C:\Tests"; + private static readonly string TestDirectoryPath = Path.Combine(Path.GetTempPath(), "SMBLibraryTests"); static NTDirectoryFileSystemTests() { From 6062dd828eb472600916b585a6abdeb24f65f21a Mon Sep 17 00:00:00 2001 From: Johannes Roos <126152533+jonahhes@users.noreply.github.com> Date: Mon, 13 Jul 2026 22:48:41 +0200 Subject: [PATCH 240/247] SMB2Client: Fix incorrect signature verification on share-level encrypted packets (#353) --- SMBLibrary/Client/SMB2Client.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 6305d0b3..05cd549d 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -511,7 +511,8 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) if (packet is SessionMessagePacket) { byte[] messageBytes; - if (m_dialect >= SMB2Dialect.SMB300 && SMB2TransformHeader.IsTransformHeader(packet.Trailer, 0)) + bool isEncrypted = m_dialect >= SMB2Dialect.SMB300 && SMB2TransformHeader.IsTransformHeader(packet.Trailer, 0); + if (isEncrypted) { SMB2TransformHeader transformHeader = new SMB2TransformHeader(packet.Trailer, 0); byte[] encryptedMessage = ByteReader.ReadBytes(packet.Trailer, SMB2TransformHeader.Length, (int)transformHeader.OriginalMessageSize); @@ -568,7 +569,7 @@ private void ProcessPacket(SessionPacket packet, ConnectionState state) if (command.Header.MessageID != 0xFFFFFFFFFFFFFFFF || command.Header.Command == SMB2CommandName.OplockBreak) { bool isInterimResponse = ((command.Header.Flags & SMB2PacketHeaderFlags.AsyncCommand) != 0) && command.Header.Status == NTStatus.STATUS_PENDING; - bool shouldBeSigned = m_isLoggedIn && m_signingRequired && !m_encryptSessionData && !isInterimResponse; + bool shouldBeSigned = m_isLoggedIn && m_signingRequired && !isEncrypted && !isInterimResponse; // [MS-SMB2] 3.2.5.1.3 If signature verification fails, the client MUST discard the received message. The client MAY also choose to disconnect the connection if (shouldBeSigned && !SMB2Cryptography.VerifySignature(messageBytes, m_dialect, m_signingKey)) From 255339717ccc9a278579d563f42939d9f2668506 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Mon, 13 Jul 2026 23:52:53 +0300 Subject: [PATCH 241/247] SMBLibrary 1.5.7.1 --- SMBLibrary/SMBLibrary.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 9ae86b30..038b16e6 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -3,7 +3,7 @@ net20;net40;netstandard2.0 SMBLibrary - 1.5.7 + 1.5.7.1 1573;1591 SMBLibrary false From 07018f0e53e159794660eafc68e27cb504d52b32 Mon Sep 17 00:00:00 2001 From: Jacob Hales Date: Wed, 5 Aug 2026 13:14:03 -0700 Subject: [PATCH 242/247] SMB2Client: DFS: Set SMB2_FLAGS_DFS_OPERATIONS on CREATE for a DFS root share (#356) --- .../Client/SMB2DfsFileStoreTests.cs | 139 +++++++++++++++++- SMBLibrary/Client/DFS/SMB2DfsFileStore.cs | 76 +++++++++- SMBLibrary/Client/SMB2FileStore.cs | 24 +++ 3 files changed, 233 insertions(+), 6 deletions(-) diff --git a/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs b/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs index c3438abe..e30e1808 100644 --- a/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs +++ b/SMBLibrary.Tests/Client/SMB2DfsFileStoreTests.cs @@ -10,6 +10,7 @@ using SMBLibrary.Client; using SMBLibrary.Client.DFS; using SMBLibrary.DFS; +using SMBLibrary.SMB2; namespace SMBLibrary.Tests.Client { @@ -62,13 +63,144 @@ public void CreateFile_WhenCovered_UsesDfsRootStoreWithoutReferral() // Assert Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); Assert.AreEqual(0, dfsFileStore.ConnectRequests.Count); - Assert.AreEqual(@"folder\file.txt", rootStore.LastCreateFilePath); + // [MS-SMB2] 2.2.13 - a request against the namespace root is a DFS operation, so the name is subject to + // DFS name normalization and must be a full path rather than a share-relative one. + Assert.AreEqual(@"SERVER1\DfsRoot\folder\file.txt", rootStore.LastCreateFilePath); byte[] data; dfsFileStore.ReadFile(out data, handle, 0, 3); Assert.AreEqual(1, rootStore.ReadFileCount); } + [TestMethod] + public void CreateFile_WhenPathIsShareRoot_SendsSharePathWithoutTrailingSeparator() + { + // Opening the share root using a single backslash is the idiom used in ClientExamples.md; + // String.Empty is the other spelling. Neither may produce a trailing separator. + foreach (string shareRoot in new string[] { @"\", String.Empty }) + { + string spelling = (shareRoot.Length == 0) ? "String.Empty" : "a single backslash"; + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, new Dictionary()); + + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, shareRoot, (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status, "Share root expressed as " + spelling); + Assert.AreEqual(@"SERVER1\DfsRoot", rootStore.LastCreateFilePath, "Share root expressed as " + spelling); + } + } + + [TestMethod] + public void CreateFile_WhenPathHasLeadingBackslash_RequestsReferralForTheNormalizedPath() + { + // The CREATE name and the referral path are built from the same caller-supplied value. If only the + // former is normalized the server is asked to resolve \\SERVER1\DfsRoot\\Link\file.txt and the + // referral fails, so the link is never followed. + byte[] referralBytes = BuildReferral(@"\SERVER1\DfsRoot\Link", @"\SERVER2\Share"); + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_PATH_NOT_COVERED, ReferralResponseBytes = referralBytes }; + FakeFileStore targetStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + Dictionary targets = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { @"SERVER2\Share", targetStore } + }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, targets); + + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, @"\Link\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + Assert.AreEqual(@"\\SERVER1\DfsRoot\Link\file.txt", rootStore.LastReferralRequestPath); + Assert.AreEqual("file.txt", targetStore.LastCreateFilePath); + } + + [TestMethod] + public void CreateFile_WhenReferralTargetIsItselfADfsRoot_SendsDfsPathToTheTarget() + { + // An interlink: the referral target is a namespace root of its own, so requests against it are DFS + // operations too and must carry a full path rather than a share-relative one. + byte[] referralBytes = BuildReferral(@"\SERVER1\DfsRoot\Link", @"\SERVER2\Nested"); + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_PATH_NOT_COVERED, ReferralResponseBytes = referralBytes }; + FakeFileStore nestedRootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + // TreeConnect returns an SMB2DfsFileStore when the target share is a namespace root. + SMB2DfsFileStore nestedDfsStore = new SMB2DfsFileStore(new SMB2Client(), "SERVER2", "Nested", nestedRootStore); + Dictionary targets = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { @"SERVER2\Nested", nestedDfsStore } + }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, targets); + + object handle; + FileStatus fileStatus; + NTStatus status = dfsFileStore.CreateFile(out handle, out fileStatus, @"Link\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + Assert.AreEqual(NTStatus.STATUS_SUCCESS, status); + Assert.AreEqual(@"SERVER2\Nested\file.txt", nestedRootStore.LastCreateFilePath); + } + + [TestMethod] + public void SMB2FileStore_ByDefault_IsNotADfsOperation() + { + // A share that is not a DFS namespace root is never wrapped, so its store must leave the flag clear + // and keep sending share-relative names. + SMB2FileStore fileStore = new SMB2FileStore(new SMB2Client(), 1, false); + + Assert.IsFalse(fileStore.IsDfsOperation); + } + + [TestMethod] + public void CreateFile_WhenPathHasLeadingBackslash_DoesNotDoubleSeparator() + { + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "SERVER1", "DfsRoot", rootStore, new Dictionary()); + + object handle; + FileStatus fileStatus; + dfsFileStore.CreateFile(out handle, out fileStatus, @"\folder\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + Assert.AreEqual(@"SERVER1\DfsRoot\folder\file.txt", rootStore.LastCreateFilePath); + } + + [TestMethod] + public void CreateFile_WhenConnectedByIPAddress_KeepsPathShareRelative() + { + // [MS-DFSC] The server normalizes a DFS path against the namespace name, which an IP address can never + // match. Sending a DFS path here would break callers that connect by address to a share that happens to + // carry SMB2_SHAREFLAG_DFS_ROOT and work today. + FakeFileStore rootStore = new FakeFileStore() { CreateFileStatus = NTStatus.STATUS_SUCCESS }; + TestableDfsFileStore dfsFileStore = new TestableDfsFileStore(new SMB2Client(), "10.0.0.5", "Namespace", rootStore, new Dictionary()); + + object handle; + FileStatus fileStatus; + dfsFileStore.CreateFile(out handle, out fileStatus, @"folder\file.txt", (AccessMask)0, (FileAttributes)0, (ShareAccess)0, (CreateDisposition)0, (CreateOptions)0, null); + + Assert.AreEqual(@"folder\file.txt", rootStore.LastCreateFilePath); + } + + [TestMethod] + public void Constructor_WhenServerIsName_MarksUnderlyingStoreAsDfsOperation() + { + SMB2Client client = new SMB2Client(); + SMB2FileStore fileStore = new SMB2FileStore(client, 1, false); + + new SMB2DfsFileStore(client, "SERVER1", "DfsRoot", fileStore); + + Assert.IsTrue(fileStore.IsDfsOperation, "Requests against a DFS namespace root must be marked as DFS operations."); + } + + [TestMethod] + public void Constructor_WhenServerIsIPAddress_DoesNotMarkUnderlyingStoreAsDfsOperation() + { + SMB2Client client = new SMB2Client(); + SMB2FileStore fileStore = new SMB2FileStore(client, 1, false); + + new SMB2DfsFileStore(client, "10.0.0.5", "Namespace", fileStore); + + Assert.IsFalse(fileStore.IsDfsOperation); + } + [TestMethod] public void CreateFile_WhenReferralTargetUnreachable_ReturnsPathNotCovered() { @@ -165,6 +297,7 @@ private class FakeFileStore : ISMBFileStore public NTStatus CreateFileStatus = NTStatus.STATUS_SUCCESS; public byte[] ReferralResponseBytes; public string LastCreateFilePath; + public string LastReferralRequestPath; public int ReadFileCount; public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext) @@ -183,6 +316,10 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string public NTStatus DeviceIOControl(object handle, uint ctlCode, byte[] input, out byte[] output, int maxOutputLength) { + if (ctlCode == (uint)IoControlCode.FSCTL_DFS_GET_REFERRALS && input != null) + { + LastReferralRequestPath = new RequestGetDfsReferral(input).RequestFileName; + } output = ReferralResponseBytes; return (ReferralResponseBytes != null) ? NTStatus.STATUS_SUCCESS : NTStatus.STATUS_NOT_SUPPORTED; } diff --git a/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs b/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs index 943a3ccb..909fe0e4 100644 --- a/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs +++ b/SMBLibrary/Client/DFS/SMB2DfsFileStore.cs @@ -6,6 +6,7 @@ */ using System; using System.Collections.Generic; +using System.Net; using SMBLibrary.DFS; namespace SMBLibrary.Client.DFS @@ -25,10 +26,13 @@ internal class SMB2DfsFileStore : ISMBFileStore private string m_serverName; private string m_shareName; private ISMBFileStore m_dfsFileStore; + private bool m_useDfsPaths; // Connections and tree connections established while following referrals to other targets. private Dictionary m_targetClients; private Dictionary m_targetFileStores; + // Whether each target above is itself a DFS namespace root, and so expects DFS paths too. + private Dictionary m_dfsRootTargets; internal SMB2DfsFileStore(SMB2Client client, string serverName, string shareName, ISMBFileStore dfsFileStore) { @@ -38,6 +42,53 @@ internal SMB2DfsFileStore(SMB2Client client, string serverName, string shareName m_dfsFileStore = dfsFileStore; m_targetClients = new Dictionary(StringComparer.OrdinalIgnoreCase); m_targetFileStores = new Dictionary(StringComparer.OrdinalIgnoreCase); + m_dfsRootTargets = new Dictionary(StringComparer.OrdinalIgnoreCase); + + // [MS-DFSC] The server normalizes a DFS path against the namespace name, which an IP address can never + // match. A caller that connected by address therefore keeps using share-relative paths, so that access + // to a share that happens to carry SMB2_SHAREFLAG_DFS_ROOT keeps working exactly as it did before. + m_useDfsPaths = !IsIPAddress(serverName); + SetDfsOperations(m_dfsFileStore, m_useDfsPaths); + } + + private static bool IsIPAddress(string serverName) + { + IPAddress serverAddress; + return IPAddress.TryParse(serverName, out serverAddress); + } + + private static void SetDfsOperations(ISMBFileStore fileStore, bool isDfsOperation) + { + SMB2FileStore smb2FileStore = fileStore as SMB2FileStore; + if (smb2FileStore != null) + { + smb2FileStore.IsDfsOperation = isDfsOperation; + } + } + + /// + /// A caller may express the share root as an empty string or as a single backslash, and may include a + /// leading or trailing separator on any path. The CREATE name and the DFS referral path are built from + /// the same caller-supplied value, so both go through this to stay consistent with each other. + /// + private static string NormalizePathWithinShare(string pathWithinShare) + { + if (pathWithinShare == null) + { + return String.Empty; + } + return pathWithinShare.Trim('\\'); + } + + /// + /// [MS-SMB2] 2.2.13 - When SMB2_FLAGS_DFS_OPERATIONS is set the name is subject to DFS name normalization + /// and must be a full path in the form 'server\share\path', with no leading separator. + /// + private static string GetDfsName(string serverName, string shareName, string pathWithinShare) + { + string sharePath = serverName + @"\" + shareName; + string relativePath = NormalizePathWithinShare(pathWithinShare); + return (relativePath.Length > 0) ? sharePath + @"\" + relativePath : sharePath; } public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string path, AccessMask desiredAccess, FileAttributes fileAttributes, ShareAccess shareAccess, CreateDisposition createDisposition, CreateOptions createOptions, SecurityContext securityContext) @@ -49,11 +100,15 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string string currentServer = m_serverName; string currentShare = m_shareName; string effectivePath = path; + // This instance only ever wraps a DFS namespace root, so the first hop takes DFS paths unless the + // caller connected by IP address. A referral target only does when it is a namespace root itself. + bool currentIsDfsRoot = m_useDfsPaths; for (int hopCount = 0; ; hopCount++) { object innerHandle; - NTStatus status = fileStore.CreateFile(out innerHandle, out fileStatus, effectivePath, desiredAccess, fileAttributes, shareAccess, createDisposition, createOptions, securityContext); + string name = currentIsDfsRoot ? GetDfsName(currentServer, currentShare, effectivePath) : effectivePath; + NTStatus status = fileStore.CreateFile(out innerHandle, out fileStatus, name, desiredAccess, fileAttributes, shareAccess, createDisposition, createOptions, securityContext); if (status != NTStatus.STATUS_PATH_NOT_COVERED) { if (status == NTStatus.STATUS_SUCCESS) @@ -68,7 +123,9 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string return status; } - string dfsPath = BuildUncPath(currentServer, currentShare, effectivePath); + // Same normalization as the CREATE name above: a leading separator here would produce + // \\server\share\\path and the server would fail to resolve the referral. + string dfsPath = BuildUncPath(currentServer, currentShare, NormalizePathWithinShare(effectivePath)); List targets; if (!TryGetReferralTargets(fileStore, dfsPath, out targets)) { @@ -78,9 +135,10 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string // MS-DFSC 3.1.5.4.3: targets are listed in order of preference, try the next target when a target is unreachable ISMBFileStore targetFileStore = null; DfsPath target = null; + bool targetIsDfsRoot = false; foreach (DfsPath referralTarget in targets) { - targetFileStore = GetOrConnectFileStore(referralTarget.ServerName, referralTarget.ShareName); + targetFileStore = GetOrConnectFileStore(referralTarget.ServerName, referralTarget.ShareName, out targetIsDfsRoot); if (targetFileStore != null) { target = referralTarget; @@ -97,6 +155,7 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string currentServer = target.ServerName; currentShare = target.ShareName; effectivePath = target.PathWithinShare; + currentIsDfsRoot = targetIsDfsRoot; } } @@ -143,28 +202,35 @@ private static bool TryGetReferralTargets(ISMBFileStore fileStore, string dfsPat return targets.Count > 0; } - private ISMBFileStore GetOrConnectFileStore(string serverName, string shareName) + private ISMBFileStore GetOrConnectFileStore(string serverName, string shareName, out bool isDfsRoot) { string key = BuildUncPath(serverName, shareName, null); ISMBFileStore fileStore; if (m_targetFileStores.TryGetValue(key, out fileStore)) { + isDfsRoot = m_dfsRootTargets[key]; return fileStore; } fileStore = ConnectToTarget(serverName, shareName); if (fileStore == null) { + isDfsRoot = false; return null; } + isDfsRoot = false; if (fileStore is SMB2DfsFileStore dfsFileStore) { - // Use the underlying file store, referrals from the target are followed (and hop-limited) by this instance + // Use the underlying file store, referrals from the target are followed (and hop-limited) by this instance. + // The target is a namespace root of its own, so it expects DFS paths just like the root we started from, + // unless it decided otherwise (a target addressed by IP). + isDfsRoot = dfsFileStore.m_useDfsPaths; fileStore = dfsFileStore.m_dfsFileStore; } m_targetFileStores.Add(key, fileStore); + m_dfsRootTargets.Add(key, isDfsRoot); return fileStore; } diff --git a/SMBLibrary/Client/SMB2FileStore.cs b/SMBLibrary/Client/SMB2FileStore.cs index 59a30f77..a6bc4218 100644 --- a/SMBLibrary/Client/SMB2FileStore.cs +++ b/SMBLibrary/Client/SMB2FileStore.cs @@ -18,6 +18,7 @@ public class SMB2FileStore : ISMBFileStore private SMB2Client m_client; private uint m_treeID; private bool m_encryptShareData; + private bool m_isDfsOperation; public SMB2FileStore(SMB2Client client, uint treeID, bool encryptShareData) { @@ -31,6 +32,12 @@ public NTStatus CreateFile(out object handle, out FileStatus fileStatus, string handle = null; fileStatus = FileStatus.FILE_DOES_NOT_EXIST; CreateRequest request = new CreateRequest(); + if (m_isDfsOperation) + { + // [MS-SMB2] 3.2.4.1.4 - The client MUST set SMB2_FLAGS_DFS_OPERATIONS when sending a DFS operation, + // otherwise the server will not return STATUS_PATH_NOT_COVERED and no DFS referral will be requested. + request.Header.Flags |= SMB2PacketHeaderFlags.DfsOperations; + } request.Name = path; request.DesiredAccess = desiredAccess; request.FileAttributes = fileAttributes; @@ -375,6 +382,23 @@ public uint MaxWriteSize } } + /// + /// When set, requests are marked with SMB2_FLAGS_DFS_OPERATIONS. + /// Set by SMB2DfsFileStore for a DFS namespace root; the name passed to CreateFile is then expected + /// to already be in the form required by [MS-SMB2] 2.2.13. + /// + internal bool IsDfsOperation + { + get + { + return m_isDfsOperation; + } + set + { + m_isDfsOperation = value; + } + } + private static FileStatus ToFileStatus(CreateAction createAction) { switch (createAction) From 87ecb88b59abb48c1bdbc7627f7c45951e84b413 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 5 Sep 2026 00:33:12 +0300 Subject: [PATCH 243/247] NTLMCryptography: Change method order to group NTLM v1 method together --- .../NTLM/Helpers/NTLMCryptography.cs | 84 +++++++++---------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 9e929ac9..8690ba93 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -36,30 +36,6 @@ public static byte[] ComputeNTLMv1ExtendedSessionSecurityResponse(byte[] serverC return DesLongEncrypt(passwordHash, challengeHashShort); } - public static byte[] ComputeLMv2Response(byte[] serverChallenge, byte[] clientChallenge, string password, string user, string domain) - { - byte[] key = LMOWFv2(password, user, domain); - byte[] bytes = ByteUtils.Concatenate(serverChallenge, clientChallenge); - HMACMD5 hmac = new HMACMD5(key); - byte[] hash = hmac.ComputeHash(bytes, 0, bytes.Length); - - return ByteUtils.Concatenate(hash, clientChallenge); - } - - /// - /// [MS-NLMP] https://msdn.microsoft.com/en-us/library/cc236700.aspx - /// - /// ClientChallengeStructure with 4 zero bytes padding, a.k.a. temp - public static byte[] ComputeNTLMv2Proof(byte[] serverChallenge, byte[] clientChallengeStructurePadded, string password, string user, string domain) - { - byte[] key = NTOWFv2(password, user, domain); - byte[] temp = clientChallengeStructurePadded; - - HMACMD5 hmac = new HMACMD5(key); - byte[] _NTProof = hmac.ComputeHash(ByteUtils.Concatenate(serverChallenge, temp), 0, serverChallenge.Length + temp.Length); - return _NTProof; - } - public static byte[] DesEncrypt(byte[] key, byte[] plainText) { return DesEncrypt(key, plainText, 0, plainText.Length); @@ -174,24 +150,6 @@ public static byte[] NTOWFv1(string password) return new MD4().GetByteHashFromBytes(passwordBytes); } - /// - /// LMOWFv2 is identical to NTOWFv2 - /// - public static byte[] LMOWFv2(string password, string user, string domain) - { - return NTOWFv2(password, user, domain); - } - - public static byte[] NTOWFv2(string password, string user, string domain) - { - byte[] passwordBytes = UnicodeEncoding.Unicode.GetBytes(password); - byte[] key = new MD4().GetByteHashFromBytes(passwordBytes); - string text = user.ToUpper() + domain; - byte[] bytes = UnicodeEncoding.Unicode.GetBytes(text); - HMACMD5 hmac = new HMACMD5(key); - return hmac.ComputeHash(bytes, 0, bytes.Length); - } - /// /// Extends a 7-byte key into an 8-byte key. /// Note: The DES key ostensibly consists of 64 bits, however, only 56 of these are actually used by the algorithm. @@ -258,6 +216,48 @@ public static byte[] KXKey(byte[] sessionBaseKey, NegotiateFlags negotiateFlags, } } + public static byte[] ComputeLMv2Response(byte[] serverChallenge, byte[] clientChallenge, string password, string user, string domain) + { + byte[] key = LMOWFv2(password, user, domain); + byte[] bytes = ByteUtils.Concatenate(serverChallenge, clientChallenge); + HMACMD5 hmac = new HMACMD5(key); + byte[] hash = hmac.ComputeHash(bytes, 0, bytes.Length); + + return ByteUtils.Concatenate(hash, clientChallenge); + } + + /// + /// [MS-NLMP] https://msdn.microsoft.com/en-us/library/cc236700.aspx + /// + /// ClientChallengeStructure with 4 zero bytes padding, a.k.a. temp + public static byte[] ComputeNTLMv2Proof(byte[] serverChallenge, byte[] clientChallengeStructurePadded, string password, string user, string domain) + { + byte[] key = NTOWFv2(password, user, domain); + byte[] temp = clientChallengeStructurePadded; + + HMACMD5 hmac = new HMACMD5(key); + byte[] _NTProof = hmac.ComputeHash(ByteUtils.Concatenate(serverChallenge, temp), 0, serverChallenge.Length + temp.Length); + return _NTProof; + } + + /// + /// LMOWFv2 is identical to NTOWFv2 + /// + public static byte[] LMOWFv2(string password, string user, string domain) + { + return NTOWFv2(password, user, domain); + } + + public static byte[] NTOWFv2(string password, string user, string domain) + { + byte[] passwordBytes = UnicodeEncoding.Unicode.GetBytes(password); + byte[] key = new MD4().GetByteHashFromBytes(passwordBytes); + string text = user.ToUpper() + domain; + byte[] bytes = UnicodeEncoding.Unicode.GetBytes(text); + HMACMD5 hmac = new HMACMD5(key); + return hmac.ComputeHash(bytes, 0, bytes.Length); + } + /// /// Caller must verify that the authenticate message has MIC before calling this method /// From 3bc74334cbe1c251f12e8c02b09c365384a68da6 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 5 Sep 2026 00:36:47 +0300 Subject: [PATCH 244/247] Added ENABLE_NTLMV1 compilation flag --- .../Authentication/NTLM/Helpers/NTLMCryptography.cs | 2 ++ .../NTLM/IndependentNTLMAuthenticationProvider.cs | 10 ++++++++++ SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs | 4 ++++ SMBLibrary/Client/SMB1Client.cs | 8 ++++++++ SMBLibrary/SMBLibrary.csproj | 1 + 5 files changed, 25 insertions(+) diff --git a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs index 8690ba93..1e1deea7 100644 --- a/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs +++ b/SMBLibrary/Authentication/NTLM/Helpers/NTLMCryptography.cs @@ -15,6 +15,7 @@ namespace SMBLibrary.Authentication.NTLM { public class NTLMCryptography { +#if ENABLE_NTLMV1 public static byte[] ComputeLMv1Response(byte[] challenge, string password) { byte[] hash = LMOWFv1(password); @@ -215,6 +216,7 @@ public static byte[] KXKey(byte[] sessionBaseKey, NegotiateFlags negotiateFlags, return keyExchangeKey; } } +#endif public static byte[] ComputeLMv2Response(byte[] serverChallenge, byte[] clientChallenge, string password, string user, string domain) { diff --git a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs index c9687de2..5682ea26 100644 --- a/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs +++ b/SMBLibrary/Authentication/NTLM/IndependentNTLMAuthenticationProvider.cs @@ -240,6 +240,7 @@ public override NTStatus Authenticate(object context, byte[] authenticateMessage { if (AuthenticationMessageUtils.IsNTLMv1ExtendedSessionSecurity(message.LmChallengeResponse)) { +#if ENABLE_NTLMV1 // NTLM v1 Extended Session Security: success = AuthenticateV1Extended(password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); if (success) @@ -249,6 +250,9 @@ public override NTStatus Authenticate(object context, byte[] authenticateMessage byte[] lmowf = NTLMCryptography.LMOWFv1(password); keyExchangeKey = NTLMCryptography.KXKey(sessionBaseKey, message.NegotiateFlags, message.LmChallengeResponse, serverChallenge, lmowf); } +#else + success = false; +#endif } else { @@ -266,6 +270,7 @@ public override NTStatus Authenticate(object context, byte[] authenticateMessage } else { +#if ENABLE_NTLMV1 success = AuthenticateV1(password, serverChallenge, message.LmChallengeResponse, message.NtChallengeResponse); if (success) { @@ -274,6 +279,9 @@ public override NTStatus Authenticate(object context, byte[] authenticateMessage byte[] lmowf = NTLMCryptography.LMOWFv1(password); keyExchangeKey = NTLMCryptography.KXKey(sessionBaseKey, message.NegotiateFlags, message.LmChallengeResponse, serverChallenge, lmowf); } +#else + success = false; +#endif } if (success) @@ -342,6 +350,7 @@ private bool EnableGuestLogin } } +#if ENABLE_NTLMV1 /// /// LM v1 / NTLM v1 /// @@ -367,6 +376,7 @@ private static bool AuthenticateV1Extended(string password, byte[] serverChallen return ByteUtils.AreByteArraysEqual(expectedNTLMv1Response, ntResponse); } +#endif /// /// LM v2 / NTLM v2 diff --git a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs index b37fb4c4..9a805ace 100644 --- a/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs +++ b/SMBLibrary/Client/Helpers/NTLMAuthenticationHelper.cs @@ -117,6 +117,7 @@ public static byte[] GetAuthenticateMessage(byte[] negotiateMessageBytes, byte[] byte[] keyExchangeKey; if (authenticationMethod == AuthenticationMethod.NTLMv1 || authenticationMethod == AuthenticationMethod.NTLMv1ExtendedSessionSecurity) { +#if ENABLE_NTLMV1 // https://msdn.microsoft.com/en-us/library/cc236699.aspx if (userName == String.Empty && password == String.Empty) { @@ -137,6 +138,9 @@ public static byte[] GetAuthenticateMessage(byte[] negotiateMessageBytes, byte[] sessionBaseKey = new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)); byte[] lmowf = NTLMCryptography.LMOWFv1(password); keyExchangeKey = NTLMCryptography.KXKey(sessionBaseKey, authenticateMessage.NegotiateFlags, authenticateMessage.LmChallengeResponse, challengeMessage.ServerChallenge, lmowf); +#else + throw new NotSupportedException("NTLM v1 support has not been enabled"); +#endif } else // NTLMv2 { diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index c8cbbd8f..70440fc2 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -275,8 +275,12 @@ public NTStatus Login(string domainName, string userName, string password, Authe byte[] proofStr = null; if (authenticationMethod == AuthenticationMethod.NTLMv1) { +#if ENABLE_NTLMV1 request.OEMPassword = NTLMCryptography.ComputeLMv1Response(m_serverChallenge, password); request.UnicodePassword = NTLMCryptography.ComputeNTLMv1Response(m_serverChallenge, password); +#else + throw new NotSupportedException("NTLM v1 support has not been enabled"); +#endif } else if (authenticationMethod == AuthenticationMethod.NTLMv1ExtendedSessionSecurity) { @@ -304,9 +308,13 @@ public NTStatus Login(string domainName, string userName, string password, Authe if (m_isLoggedIn) { m_userID = reply.Header.UID; +#if ENABLE_NTLMV1 m_sessionKey = (authenticationMethod == AuthenticationMethod.NTLMv1) ? new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)) : new HMACMD5(NTLMCryptography.NTOWFv2(password, userName, domainName)).ComputeHash(proofStr); +#else + m_sessionKey = new HMACMD5(NTLMCryptography.NTOWFv2(password, userName, domainName)).ComputeHash(proofStr); +#endif } return reply.Header.Status; } diff --git a/SMBLibrary/SMBLibrary.csproj b/SMBLibrary/SMBLibrary.csproj index 038b16e6..4570a37b 100644 --- a/SMBLibrary/SMBLibrary.csproj +++ b/SMBLibrary/SMBLibrary.csproj @@ -14,6 +14,7 @@ https://github.com/TalAloni/SMBLibrary https://github.com/TalAloni/SMBLibrary true + ENABLE_NTLMV1 From e2cbf53cc895f54f93e9c42aa3cc43b6700b01f0 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 5 Sep 2026 13:04:28 +0300 Subject: [PATCH 245/247] SMB1: NegotiateResponse: Fix IndexOutOfRangeException if ServerName is not present --- SMBLibrary/SMB1/Commands/NegotiateResponse.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/SMBLibrary/SMB1/Commands/NegotiateResponse.cs b/SMBLibrary/SMB1/Commands/NegotiateResponse.cs index f80b73b8..21500cde 100644 --- a/SMBLibrary/SMB1/Commands/NegotiateResponse.cs +++ b/SMBLibrary/SMB1/Commands/NegotiateResponse.cs @@ -1,4 +1,4 @@ -/* Copyright (C) 2014-2017 Tal Aloni . All rights reserved. +/* Copyright (C) 2014-2026 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, @@ -60,7 +60,13 @@ public NegotiateResponse(byte[] buffer, int offset, bool isUnicode) : base(buffe // [MS-CIFS] <90> Padding is not added before DomainName // DomainName and ServerName are always in Unicode DomainName = SMB1Helper.ReadSMBString(this.SMBData, ref dataOffset, true); - ServerName = SMB1Helper.ReadSMBString(this.SMBData, ref dataOffset, true); + // [MS-SMB] 2.2.4.5.2.2 In order to determine whether the SMB_Data.Bytes.ServerName field is present, + // the client MUST check the SMB_Data.ByteCount field to determine whether additional data is present + // beyond the NULL terminator of the SMB_Data.Bytes.DomainName string. + if (dataOffset < this.SMBData.Length) + { + ServerName = SMB1Helper.ReadSMBString(this.SMBData, ref dataOffset, true); + } } public override byte[] GetBytes(bool isUnicode) From 8268e00c961c8dc7f3cf58403c47920266b1f9c7 Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 5 Sep 2026 13:06:15 +0300 Subject: [PATCH 246/247] Tests: SMB1: Added NegotiateResponseParsingTests --- .../SMB1/NegotiateResponseParsingTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 SMBLibrary.Tests/SMB1/NegotiateResponseParsingTests.cs diff --git a/SMBLibrary.Tests/SMB1/NegotiateResponseParsingTests.cs b/SMBLibrary.Tests/SMB1/NegotiateResponseParsingTests.cs new file mode 100644 index 00000000..5b13d1c4 --- /dev/null +++ b/SMBLibrary.Tests/SMB1/NegotiateResponseParsingTests.cs @@ -0,0 +1,29 @@ +/* Copyright (C) 2026 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 Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.SMB1; + +namespace SMBLibrary.Tests.SMB1 +{ + [TestClass] + public class NegotiateResponseParsingTests + { + [TestMethod] + public void ParseSMB1MessageWithNegotiateResponseFromWindowsNT4() + { + // Arrange + byte[] responseBytes = new byte[] { 0xFF, 0x53, 0x4D, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x80, 0x41, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x07, 0x32, 0x00, 0x01, 0x00, 0x04, 0x11, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x43, 0x00, 0x00, 0xC0, 0xDB, 0x1E, 0x78, 0x2B, 0x2B, 0xDD, 0x01, 0x88, 0xFF, 0x08, 0x14, 0x00, 0x9D, 0xBE, 0xDA, 0x52, 0x8D, 0xEB, 0xEA, 0xDE, 0x4F, 0x00, 0x50, 0x00, 0x43, 0x00, 0x4F, 0x00, 0x4E, 0x00, 0x00, 0x00 }; + + // Act + SMB1Message smb1Message = SMB1Message.GetSMB1Message(responseBytes); + + // Assert + Assert.AreEqual(1, smb1Message.Commands.Count); + Assert.IsInstanceOfType(smb1Message.Commands[0], typeof(NegotiateResponse)); + } + } +} From 2a8911f12a77e958f8ac78c594e64360623e5a7f Mon Sep 17 00:00:00 2001 From: Tal Aloni Date: Sat, 5 Sep 2026 17:51:27 +0300 Subject: [PATCH 247/247] SMB1Client: Minor code deduplication --- SMBLibrary/Client/SMB1Client.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/SMBLibrary/Client/SMB1Client.cs b/SMBLibrary/Client/SMB1Client.cs index 70440fc2..500a2a3e 100644 --- a/SMBLibrary/Client/SMB1Client.cs +++ b/SMBLibrary/Client/SMB1Client.cs @@ -308,13 +308,11 @@ public NTStatus Login(string domainName, string userName, string password, Authe if (m_isLoggedIn) { m_userID = reply.Header.UID; + m_sessionKey = #if ENABLE_NTLMV1 - m_sessionKey = (authenticationMethod == AuthenticationMethod.NTLMv1) ? - new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)) : - new HMACMD5(NTLMCryptography.NTOWFv2(password, userName, domainName)).ComputeHash(proofStr); -#else - m_sessionKey = new HMACMD5(NTLMCryptography.NTOWFv2(password, userName, domainName)).ComputeHash(proofStr); + (authenticationMethod == AuthenticationMethod.NTLMv1) ? new MD4().GetByteHashFromBytes(NTLMCryptography.NTOWFv1(password)) : #endif + new HMACMD5(NTLMCryptography.NTOWFv2(password, userName, domainName)).ComputeHash(proofStr); } return reply.Header.Status; }