From e3bbf1708ab09f41b855cd7068c3e453aaaadeed Mon Sep 17 00:00:00 2001 From: TalAloni Date: Fri, 20 Nov 2020 14:38:20 +0200 Subject: [PATCH] Added SMB 3.0 message signing logic --- SMBLibrary.Tests/SMB2SigningTests.cs | 34 +++++++-- SMBLibrary/Client/SMB2Client.cs | 2 +- SMBLibrary/SMB2/Commands/SMB2Command.cs | 11 +-- SMBLibrary/SMB2/SMB2Cryptography.cs | 32 +++++++++ SMBLibrary/Server/SMBServer.SMB2.cs | 16 ++++- Utilities/Cryptography/AesCmac.cs | 96 +++++++++++++++++++++++++ Utilities/Utilities.VS2005.csproj | 1 + 7 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 Utilities/Cryptography/AesCmac.cs diff --git a/SMBLibrary.Tests/SMB2SigningTests.cs b/SMBLibrary.Tests/SMB2SigningTests.cs index 8a6890b..a61c63f 100644 --- a/SMBLibrary.Tests/SMB2SigningTests.cs +++ b/SMBLibrary.Tests/SMB2SigningTests.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Security.Cryptography; using Microsoft.VisualStudio.TestTools.UnitTesting; +using SMBLibrary.SMB2; using Utilities; namespace SMBLibrary.Tests @@ -16,7 +17,7 @@ namespace SMBLibrary.Tests public class SMB2SigningTests { [TestMethod] - public void Test1() + public void TestSMB202SignatureCalculation() { byte[] exportedSessionKey = new byte[] { 0xD3, 0x83, 0x54, 0xCC, 0x37, 0x43, 0x39, 0xF0, 0x52, 0x4F, 0x78, 0x91, 0x46, 0x78, 0x99, 0x21 }; @@ -28,14 +29,14 @@ namespace SMBLibrary.Tests ByteWriter.WriteBytes(message, 48, new byte[16]); - byte[] signature = new HMACSHA256(exportedSessionKey).ComputeHash(message); + 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)); } [TestMethod] - public void Test2() + public void TestSMB210SignatureCalculation() { byte[] exportedSessionKey = new byte[] { 0x04, 0xE7, 0x07, 0x57, 0x1F, 0x8E, 0x03, 0x53, 0xB7, 0x7A, 0x94, 0xC3, 0x65, 0x3B, 0x87, 0xB5 }; @@ -47,16 +48,37 @@ namespace SMBLibrary.Tests ByteWriter.WriteBytes(message, 48, new byte[16]); - byte[] signature = new HMACSHA256(exportedSessionKey).ComputeHash(message); + 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)); } + [TestMethod] + public void TestSMB300SignatureCalculation() + { + byte[] exportedSessionKey = new byte[] { 0x35, 0x40, 0x24, 0xCB, 0xCA, 0x4F, 0x94, 0xAA, 0x51, 0xD4, 0x03, 0x3E, 0x6E, 0x9B, 0x2F, 0x98 }; + byte[] signingKey = SMB2Cryptography.GenerateSigningKey(exportedSessionKey, SMB2Dialect.SMB300, null); + + byte[] message = new byte[]{ 0xFE, 0x53, 0x4D, 0x42, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x20, 0x00, 0x70, 0x00, 0x00, + 0x73, 0xF2, 0xCC, 0x56, 0x09, 0x3E, 0xD2, 0xB5, 0xD7, 0x10, 0x66, 0x6C, 0xE4, 0x28, 0x2D, 0xD1, + 0x09, 0x00, 0x00, 0x00, 0x48, 0x00, 0x09, 0x00, 0xA1, 0x07, 0x30, 0x05, 0xA0, 0x03, 0x0A, 0x01, 0x00}; + + 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)); + } + public void TestAll() { - Test1(); - Test2(); + TestSMB202SignatureCalculation(); + TestSMB210SignatureCalculation(); + TestSMB300SignatureCalculation(); } } } diff --git a/SMBLibrary/Client/SMB2Client.cs b/SMBLibrary/Client/SMB2Client.cs index 7c3bdf4..bb1d436 100644 --- a/SMBLibrary/Client/SMB2Client.cs +++ b/SMBLibrary/Client/SMB2Client.cs @@ -523,7 +523,7 @@ namespace SMBLibrary.Client { request.Header.Signature = new byte[16]; // Request could be reused byte[] buffer = request.GetBytes(); - byte[] signature = new HMACSHA256(m_sessionKey).ComputeHash(buffer, 0, buffer.Length); + byte[] signature = SMB2Cryptography.CalculateSignature(m_sessionKey, m_dialect, buffer, 0, buffer.Length); // [MS-SMB2] The first 16 bytes of the hash MUST be copied into the 16-byte signature field of the SMB2 Header. request.Header.Signature = ByteReader.ReadBytes(signature, 0, 16); } diff --git a/SMBLibrary/SMB2/Commands/SMB2Command.cs b/SMBLibrary/SMB2/Commands/SMB2Command.cs index eb46446..041a43b 100644 --- a/SMBLibrary/SMB2/Commands/SMB2Command.cs +++ b/SMBLibrary/SMB2/Commands/SMB2Command.cs @@ -124,13 +124,16 @@ namespace SMBLibrary.SMB2 public static byte[] GetCommandChainBytes(List commands) { - return GetCommandChainBytes(commands, null); + return GetCommandChainBytes(commands, null, SMB2Dialect.SMB2xx); } /// - /// command will be signed using this key if (not null and) SMB2_FLAGS_SIGNED is set. + /// Message will be signed using this key if (not null and) SMB2_FLAGS_SIGNED is set. /// - public static byte[] GetCommandChainBytes(List commands, byte[] sessionKey) + /// + /// Used for signature calculation when applicable. + /// + public static byte[] GetCommandChainBytes(List commands, byte[] sessionKey, SMB2Dialect dialect) { int totalLength = 0; for (int index = 0; index < commands.Count; index++) @@ -167,7 +170,7 @@ namespace SMBLibrary.SMB2 if (command.Header.IsSigned && sessionKey != null) { // [MS-SMB2] Any padding at the end of the message MUST be used in the hash computation. - byte[] signature = new HMACSHA256(sessionKey).ComputeHash(buffer, offset, paddedLength); + byte[] signature = SMB2Cryptography.CalculateSignature(sessionKey, 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); } diff --git a/SMBLibrary/SMB2/SMB2Cryptography.cs b/SMBLibrary/SMB2/SMB2Cryptography.cs index cc3b8ed..fcbb815 100644 --- a/SMBLibrary/SMB2/SMB2Cryptography.cs +++ b/SMBLibrary/SMB2/SMB2Cryptography.cs @@ -12,6 +12,38 @@ namespace SMBLibrary.SMB2 { internal class SMB2Cryptography { + public static byte[] CalculateSignature(byte[] signingKey, SMB2Dialect dialect, byte[] buffer, int offset, int paddedLength) + { + if (dialect == SMB2Dialect.SMB202 || dialect == SMB2Dialect.SMB210) + { + return new HMACSHA256(signingKey).ComputeHash(buffer, offset, paddedLength); + } + else + { + return AesCmac.CalculateAesCmac(signingKey, buffer, offset, paddedLength); + } + } + + public static byte[] GenerateSigningKey(byte[] sessionKey, SMB2Dialect dialect, byte[] preauthIntegrityHashValue) + { + if (dialect == SMB2Dialect.SMB202 || dialect == SMB2Dialect.SMB210) + { + return sessionKey; + } + + if (dialect == SMB2Dialect.SMB311 && preauthIntegrityHashValue == null) + { + throw new ArgumentNullException("preauthIntegrityHashValue"); + } + + string labelString = (dialect == SMB2Dialect.SMB311) ? "SMBSigningKey" : "SMB2AESCMAC"; + byte[] label = GetNullTerminatedAnsiString(labelString); + byte[] context = (dialect == SMB2Dialect.SMB311) ? preauthIntegrityHashValue : GetNullTerminatedAnsiString("SmbSign"); + + HMACSHA256 hmac = new HMACSHA256(sessionKey); + return SP800_1008.DeriveKey(hmac, label, context, 128); + } + public static byte[] GenerateEncryptionKey(byte[] sessionKey, SMB2Dialect dialect, byte[] preauthIntegrityHashValue) { if (dialect == SMB2Dialect.SMB311 && preauthIntegrityHashValue == null) diff --git a/SMBLibrary/Server/SMBServer.SMB2.cs b/SMBLibrary/Server/SMBServer.SMB2.cs index d5f513e..02e490b 100644 --- a/SMBLibrary/Server/SMBServer.SMB2.cs +++ b/SMBLibrary/Server/SMBServer.SMB2.cs @@ -246,11 +246,25 @@ namespace SMBLibrary.Server } SessionMessagePacket packet = new SessionMessagePacket(); - packet.Trailer = SMB2Command.GetCommandChainBytes(responseChain, sessionKey); + SMB2Dialect smb2Dialect = (sessionKey != null) ? ToSMB2Dialect(state.Dialect) : SMB2Dialect.SMB2xx; + packet.Trailer = SMB2Command.GetCommandChainBytes(responseChain, sessionKey, smb2Dialect); state.SendQueue.Enqueue(packet); state.LogToServer(Severity.Verbose, "SMB2 response chain queued: Response count: {0}, First response: {1}, Packet length: {2}", responseChain.Count, responseChain[0].CommandName.ToString(), packet.Length); } + private static SMB2Dialect ToSMB2Dialect(SMBDialect smbDialect) + { + switch (smbDialect) + { + case SMBDialect.SMB202: + return SMB2Dialect.SMB202; + case SMBDialect.SMB210: + return SMB2Dialect.SMB210; + default: + throw new ArgumentException("Unsupported SMB2 Dialect: " + smbDialect.ToString()); + } + } + private static void UpdateSMB2Header(SMB2Command response, SMB2Command request, ConnectionState state) { response.Header.MessageID = request.Header.MessageID; diff --git a/Utilities/Cryptography/AesCmac.cs b/Utilities/Cryptography/AesCmac.cs new file mode 100644 index 0000000..4e3faa8 --- /dev/null +++ b/Utilities/Cryptography/AesCmac.cs @@ -0,0 +1,96 @@ +/* Based on https://stackoverflow.com/a/30123190/3419770 + */ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Utilities +{ + public static class AesCmac + { + public static byte[] CalculateAesCmac(byte[] key, byte[] buffer, int offset, int length) + { + byte[] data = ByteReader.ReadBytes(buffer, offset, length); + return CalculateAesCmac(key, data); + } + + public static byte[] CalculateAesCmac(byte[] key, byte[] data) + { + // SubKey generation + // step 1, AES-128 with key K is applied to an all-zero input block. + byte[] L = AESEncrypt(key, new byte[16], new byte[16]); + + // step 2, K1 is derived through the following operation: + byte[] FirstSubkey = Rol(L); //If the most significant bit of L is equal to 0, K1 is the left-shift of L by 1 bit. + if ((L[0] & 0x80) == 0x80) + FirstSubkey[15] ^= 0x87; // Otherwise, K1 is the exclusive-OR of const_Rb and the left-shift of L by 1 bit. + + // step 3, K2 is derived through the following operation: + byte[] SecondSubkey = Rol(FirstSubkey); // If the most significant bit of K1 is equal to 0, K2 is the left-shift of K1 by 1 bit. + if ((FirstSubkey[0] & 0x80) == 0x80) + SecondSubkey[15] ^= 0x87; // Otherwise, K2 is the exclusive-OR of const_Rb and the left-shift of K1 by 1 bit. + + // MAC computing + if (((data.Length != 0) && (data.Length % 16 == 0)) == true) + { + // If the size of the input message block is equal to a positive multiple of the block size (namely, 128 bits), + // the last block shall be exclusive-OR'ed with K1 before processing + for (int j = 0; j < FirstSubkey.Length; j++) + data[data.Length - 16 + j] ^= FirstSubkey[j]; + } + else + { + // Otherwise, the last block shall be padded with 10^i + byte[] padding = new byte[16 - data.Length % 16]; + padding[0] = 0x80; + + data = ByteUtils.Concatenate(data, padding); + + // and exclusive-OR'ed with K2 + for (int j = 0; j < SecondSubkey.Length; j++) + data[data.Length - 16 + j] ^= SecondSubkey[j]; + } + + // The result of the previous process will be the input of the last encryption. + byte[] encResult = AESEncrypt(key, new byte[16], data); + + byte[] HashValue = new byte[16]; + Array.Copy(encResult, encResult.Length - HashValue.Length, HashValue, 0, HashValue.Length); + + return HashValue; + } + + private static byte[] AESEncrypt(byte[] key, byte[] iv, byte[] data) + { + using (MemoryStream ms = new MemoryStream()) + { + RijndaelManaged aes = new RijndaelManaged(); + aes.Mode = CipherMode.CBC; + aes.Padding = PaddingMode.None; + + using (CryptoStream cs = new CryptoStream(ms, aes.CreateEncryptor(key, iv), CryptoStreamMode.Write)) + { + cs.Write(data, 0, data.Length); + cs.FlushFinalBlock(); + + return ms.ToArray(); + } + } + } + + private static byte[] Rol(byte[] b) + { + byte[] r = new byte[b.Length]; + byte carry = 0; + + for (int i = b.Length - 1; i >= 0; i--) + { + ushort u = (ushort)(b[i] << 1); + r[i] = (byte)((u & 0xff) + carry); + carry = (byte)((u & 0xff00) >> 8); + } + + return r; + } + } +} diff --git a/Utilities/Utilities.VS2005.csproj b/Utilities/Utilities.VS2005.csproj index 7e33e8c..6b0a86c 100644 --- a/Utilities/Utilities.VS2005.csproj +++ b/Utilities/Utilities.VS2005.csproj @@ -42,6 +42,7 @@ +