mirror of
https://github.com/TalAloni/SMBLibrary.git
synced 2025-05-29 16:59:50 +02:00
Added SMB 3.0 message signing logic
This commit is contained in:
parent
ca29e4e759
commit
e3bbf1708a
7 changed files with 180 additions and 12 deletions
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
96
Utilities/Cryptography/AesCmac.cs
Normal file
96
Utilities/Cryptography/AesCmac.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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" />
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue