Added SMB 3.0 message signing logic

This commit is contained in:
TalAloni 2020-11-20 14:38:20 +02:00
parent ca29e4e759
commit e3bbf1708a
7 changed files with 180 additions and 12 deletions

View file

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Cryptography; using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using SMBLibrary.SMB2;
using Utilities; using Utilities;
namespace SMBLibrary.Tests namespace SMBLibrary.Tests
@ -16,7 +17,7 @@ namespace SMBLibrary.Tests
public class SMB2SigningTests public class SMB2SigningTests
{ {
[TestMethod] [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 }; 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]); 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); 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 }; 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)); Assert.IsTrue(ByteUtils.AreByteArraysEqual(signature, expected));
} }
[TestMethod] [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 }; 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]); 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); 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 }; 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)); 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() public void TestAll()
{ {
Test1(); TestSMB202SignatureCalculation();
Test2(); TestSMB210SignatureCalculation();
TestSMB300SignatureCalculation();
} }
} }
} }

View file

@ -523,7 +523,7 @@ namespace SMBLibrary.Client
{ {
request.Header.Signature = new byte[16]; // Request could be reused request.Header.Signature = new byte[16]; // Request could be reused
byte[] buffer = request.GetBytes(); 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. // [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); request.Header.Signature = ByteReader.ReadBytes(signature, 0, 16);
} }

View file

@ -124,13 +124,16 @@ namespace SMBLibrary.SMB2
public static byte[] GetCommandChainBytes(List<SMB2Command> commands) public static byte[] GetCommandChainBytes(List<SMB2Command> commands)
{ {
return GetCommandChainBytes(commands, null); return GetCommandChainBytes(commands, null, SMB2Dialect.SMB2xx);
} }
/// <param name="sessionKey"> /// <param name="sessionKey">
/// 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.
/// </param> /// </param>
public static byte[] GetCommandChainBytes(List<SMB2Command> commands, byte[] sessionKey) /// <param name="dialect">
/// Used for signature calculation when applicable.
/// </param>
public static byte[] GetCommandChainBytes(List<SMB2Command> commands, byte[] sessionKey, SMB2Dialect dialect)
{ {
int totalLength = 0; int totalLength = 0;
for (int index = 0; index < commands.Count; index++) for (int index = 0; index < commands.Count; index++)
@ -167,7 +170,7 @@ namespace SMBLibrary.SMB2
if (command.Header.IsSigned && sessionKey != null) if (command.Header.IsSigned && sessionKey != null)
{ {
// [MS-SMB2] Any padding at the end of the message MUST be used in the hash computation. // [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. // [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, 16);
} }

View file

@ -12,6 +12,38 @@ namespace SMBLibrary.SMB2
{ {
internal class SMB2Cryptography 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) public static byte[] GenerateEncryptionKey(byte[] sessionKey, SMB2Dialect dialect, byte[] preauthIntegrityHashValue)
{ {
if (dialect == SMB2Dialect.SMB311 && preauthIntegrityHashValue == null) if (dialect == SMB2Dialect.SMB311 && preauthIntegrityHashValue == null)

View file

@ -246,11 +246,25 @@ namespace SMBLibrary.Server
} }
SessionMessagePacket packet = new SessionMessagePacket(); 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.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); 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) private static void UpdateSMB2Header(SMB2Command response, SMB2Command request, ConnectionState state)
{ {
response.Header.MessageID = request.Header.MessageID; response.Header.MessageID = request.Header.MessageID;

View file

@ -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;
}
}
}

View file

@ -42,6 +42,7 @@
<Compile Include="Conversion\BigEndianConverter.cs" /> <Compile Include="Conversion\BigEndianConverter.cs" />
<Compile Include="Conversion\Conversion.SimpleTypes.cs" /> <Compile Include="Conversion\Conversion.SimpleTypes.cs" />
<Compile Include="Conversion\LittleEndianConverter.cs" /> <Compile Include="Conversion\LittleEndianConverter.cs" />
<Compile Include="Cryptography\AesCmac.cs" />
<Compile Include="Cryptography\CRC32.cs" /> <Compile Include="Cryptography\CRC32.cs" />
<Compile Include="Generics\BlockingQueue.cs" /> <Compile Include="Generics\BlockingQueue.cs" />
<Compile Include="Generics\KeyValuePairList.cs" /> <Compile Include="Generics\KeyValuePairList.cs" />