private void TryVerifySignature(Smb2SinglePacket singlePacket, ulong sessionId, byte[] messageBytes) { // [MS-SMB2] 3.2.5.1.3 //If the MessageId is 0xFFFFFFFFFFFFFFFF, no verification is necessary. if (singlePacket.Header.MessageId == 0xFFFFFFFFFFFFFFFF) { return; } Smb2CryptoInfo cryptoInfo = null; //[MS-SMB2] 3.2.5.1.3 //If the SMB2 header of the response has SMB2_FLAGS_SIGNED set in the Flags field, the client MUST verify the signature if (singlePacket.Header.Flags.HasFlag(Packet_Header_Flags_Values.FLAGS_SIGNED)) { //This is for the session binding success situation. //After the session setup success, the cryptoInfoTable will be updated with new signingkey //The new signingkey is not in the cryptoInfoTable now. Cannot verify the signature before the table is updated. if (cryptoInfoTable.TryGetValue(sessionId, out cryptoInfo)) { if (!VerifySignature(singlePacket, cryptoInfo, messageBytes)) { //If signature verification fails, the client MUST discard the received message and do no further processing for it. //The client MAY also choose to disconnect the connection. //Throw exception here. throw new InvalidOperationException("Incorrect signed packet: " + singlePacket.ToString()); } } } }
/// <summary> /// Verify the signature of a Smb2CompoundPacket /// </summary> /// <param name="packet">The compound packet to be verified</param> private void TryVerifySignature(Smb2CompoundPacket packet, byte[] messageBytes) { try { ulong firstSessionId = packet.Packets[0].Header.SessionId; uint offset = 0; for (int i = 0; i < packet.Packets.Count; i++) { Smb2SinglePacket singlePacket = packet.Packets[i]; // NextCommand is the offset, in bytes, from the beginning of this SMB2 header to the start of the subsequent 8-byte aligned SMB2 header. uint packetLen = singlePacket.Header.NextCommand != 0 ? singlePacket.Header.NextCommand : (uint)(messageBytes.Length - offset); byte[] packetBytes = new byte[packetLen]; Array.Copy(messageBytes, offset, packetBytes, 0, packetLen); offset += packetLen; // For Related operations, the sessinId is in the first packet of the compound packet. ulong sessionId = singlePacket.Header.Flags.HasFlag(Packet_Header_Flags_Values.FLAGS_RELATED_OPERATIONS) ? firstSessionId : singlePacket.Header.SessionId; TryVerifySignature(singlePacket, sessionId, packetBytes); } } catch (Exception ex) { throw new Exception("Error happened during signature verification of compound packet: " + packet.ToString() + ". Exception message:" + ex.Message); } }
/// <summary> /// Verify the signature of a Smb2SinglePacket /// </summary> /// <param name="packet">The packet to be verified</param> /// <param name="cryptoInfo">The cryptoInfo of smb2client</param> /// <returns>True when signature verification succeeds and false when fails</returns> private bool VerifySignature(Smb2SinglePacket packet, Smb2CryptoInfo cryptoInfo) { if (cryptoInfo.DisableVerifySignature) { // Skip the verification. return(true); } try { if (IsErrorPacket(packet.Header)) { packet = packet.Error; } //save the 16-byte signature from the Signature field in the SMB2 Header byte[] originalSignature = packet.Header.Signature; //zero out the 16-byte signature field in the SMB2 Header of the incoming message. packet.Header.Signature = new byte[Smb2Consts.SignatureSize]; byte[] bytesToCompute = packet.ToBytes(); //Compute the message with signing key byte[] computedSignature = new byte[] { }; if (Smb2Utility.IsSmb3xFamily(cryptoInfo.Dialect)) { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect belongs to the SMB 3.x dialect family, //the receiver MUST compute a 16-byte hash by using AES-128-CMAC over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The AES-128-CMAC is specified in [RFC4493]. //TD has mentioned to use Session.SigningKey for SESSION_SETUP Response and Channel.SigningKey for other responses //In the current SDK, the SigningKey is the Channel.SigningKey computedSignature = AesCmac128.ComputeHash(cryptoInfo.SigningKey, bytesToCompute); } else { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect is "2.002" or "2.100", the receiver MUST compute a 32-byte hash by using HMAC-SHA256 over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The HMAC-SHA256 hash is specified in [FIPS180-2] and [RFC2104]. HMACSHA256 hmacSha = new HMACSHA256(cryptoInfo.SigningKey); computedSignature = hmacSha.ComputeHash(bytesToCompute); } packet.Header.Signature = originalSignature; //[MS-SMB2] 3.1.5.1 //If the first 16 bytes (the high-order portion) of the computed signature from step 3 or step 4 matches the saved signature from step 1, the message is signed correctly // compare the first 16 bytes of the originalSignature and computedSignature return(originalSignature.SequenceEqual(computedSignature.Take(16))); } catch (Exception ex) { throw new Exception("Error happened during signature verification of packet: " + packet.ToString() + ". Exception message: " + ex.Message); } }
/// <summary> /// Try to verify the signature of a singlePacket which is a Session Setup Response. /// </summary> public void TryVerifySessionSetupResponseSignature(Smb2SinglePacket singlePacket, ulong sessionId, byte[] messageBytes) { if (singlePacket.Header.Command != Smb2Command.SESSION_SETUP) { return; } TryVerifySignature(singlePacket, sessionId, messageBytes); }
/// <summary> /// Check if the packet needs to be encrypted but actually not encrypted. /// </summary> private void CheckIfNeedEncrypt(Smb2SinglePacket packet, ulong sessionId = 0) { if (!CheckEncrypt) { return; } var realSessionId = sessionId == 0 ? packet.Header.SessionId : sessionId; var cryptoInfo = cryptoInfoTable.ContainsKey(realSessionId) ? cryptoInfoTable[realSessionId] : null; if (cryptoInfo != null) { if (cryptoInfo.EnableSessionEncryption || (cryptoInfo.EnableTreeEncryption.Contains(packet.Header.TreeId) && packet.Header.Command != Smb2Command.TREE_CONNECT)) { throw new Exception($"The packet should be encrypted: \"{packet.ToString()}\""); } } }
/// <summary> /// Check if the compound packet needs to be encrypted but actually not encrypted. /// </summary> private void CheckIfNeedEncrypt(Smb2CompoundPacket packet) { if (!CheckEncrypt) { return; } ulong firstSessionId = packet.Packets[0].Header.SessionId; for (int i = 0; i < packet.Packets.Count; i++) { Smb2SinglePacket singlePacket = packet.Packets[i]; // For Related operations, the sessionId is in the first packet of the compound packet. ulong sessionId = singlePacket.Header.Flags.HasFlag(Packet_Header_Flags_Values.FLAGS_RELATED_OPERATIONS) ? firstSessionId : singlePacket.Header.SessionId; CheckIfNeedEncrypt(singlePacket, sessionId); } }
/// <summary> /// Update the context from a SessioinSetup request and response messages. /// The caller should ensure the messages are input in the right order. /// </summary> /// <param name="packet">The packet used to update this context. It must be the one of the following types: /// Smb2SessionSetupRequestPacket, Smb2SessionSetupResponsePacket</param> public void UpdateSessionState(ulong sessionId, Smb2SinglePacket packet) { //Check the packet type if (!(packet is Smb2SessionSetupRequestPacket) && !(packet is Smb2SessionSetupResponsePacket)) { throw new InvalidCastException("packet must be one of the types: Smb2SessionSetupRequestPacket, Smb2SessionSetupResponsePacket."); } if (completedSessionSet.Contains(sessionId)) { return; // Don't update session hash value for reauthetication } //Refer to [MS-SMB2] 3.2.5.3 Receiving an SMB2 SESSION_SETUP Response if (packet is Smb2SessionSetupRequestPacket) { if (!sessionPreauthIntegrityHashValueTable.ContainsKey(sessionId)) { sessionPreauthIntegrityHashValueTable.Add(sessionId, connectionPreauthIntegrityHashValue); } byte[] messageBytes = packet.ToBytes(); byte[] hashData = sessionPreauthIntegrityHashValueTable[sessionId].Concat(messageBytes).ToArray(); sessionPreauthIntegrityHashValueTable[sessionId] = CreateHashAlgorithm().ComputeHash(hashData); } else if (packet is Smb2SessionSetupResponsePacket) { if (packet.Header.Status == Smb2Status.STATUS_MORE_PROCESSING_REQUIRED) { byte[] messageBytes = packet.ToBytes(); byte[] hashData = sessionPreauthIntegrityHashValueTable[sessionId].Concat(messageBytes).ToArray(); sessionPreauthIntegrityHashValueTable[sessionId] = CreateHashAlgorithm().ComputeHash(hashData); } else if (packet.Header.Status == Smb2Status.STATUS_SUCCESS) { completedSessionSet.Add(sessionId); } } }
/// <summary> /// Verify the signature of a Smb2CompoundPacket /// </summary> /// <param name="packet">The compound packet to be verified</param> /// <returns>True when signature verification succeeds and false when fails</returns> private bool TryVerifySignature(Smb2CompoundPacket packet) { try { Packet_Header header = TypeMarshal.ToStruct <Packet_Header>(packet.ToBytes()); if ((header.Flags & Packet_Header_Flags_Values.FLAGS_RELATED_OPERATIONS) == Packet_Header_Flags_Values.FLAGS_RELATED_OPERATIONS) { //The realSessionId is the sessionId in the first pacekt of the compoundpacket for related packets. ulong realSessionId = packet.Packets[0].Header.SessionId; for (int i = 0; i < packet.Packets.Count; i++) { Smb2SinglePacket singlePacket = packet.Packets[i]; if (!TryVerifySignature(singlePacket, realSessionId)) { break; } } } else { for (int i = 0; i < packet.Packets.Count; i++) { Smb2SinglePacket singlePacket = packet.Packets[i]; if (!TryVerifySignature(singlePacket, singlePacket.Header.SessionId)) { break; } } } return(true); } catch (Exception ex) { throw new Exception("Error happened during signature verification of compound packet: " + packet.ToString() + ". Exception message:" + ex.Message); } }
/// <summary> /// Check if the packet needs to be encrypted but actually not encrypted. /// </summary> private void CheckIfNeedEncrypt(Smb2SinglePacket packet, ulong sessionId = 0) { if (!CheckEncrypt) { return; } var realSessionId = sessionId == 0 ? packet.Header.SessionId : sessionId; var cryptoInfo = cryptoInfoTable.ContainsKey(realSessionId) ? cryptoInfoTable[realSessionId] : null; if (cryptoInfo != null) { if (cryptoInfo.EnableSessionEncryption) { // MS-SMB2 3.3.4.1.4 Encrypting the Message // If Connection.Dialect belongs to the SMB 3.x dialect family and Connection.ClientCapabilities includes the SMB2_GLOBAL_CAP_ENCRYPTION bit, the server MUST encrypt the message before sending, if IsEncryptionSupported is TRUE and any of the following conditions are satisfied: // If the message being sent is any response to a client request for which Request.IsEncrypted is TRUE. // If Session.EncryptData is TRUE and the response being sent is not SMB2_NEGOTIATE or SMB2 SESSION_SETUP. // If Session.EncryptData is FALSE, the response being sent is not SMB2_NEGOTIATE or SMB2 SESSION_SETUP or SMB2 TREE_CONNECT, and Share.EncryptData for the share associated with the TreeId in the SMB2 header of the response is TRUE. // The server MUST encrypt the message as specified in section 3.1.4.3, before sending it to the client. // above description means SMB2_NEGOTIATE or SMB2 SESSION_SETUP never encrypts the message. if (packet.Header.Command == Smb2Command.SESSION_SETUP && (packet is Smb2SessionSetupResponsePacket)) { return; } } if (cryptoInfo.EnableSessionEncryption) { throw new Exception($"The packet should be encrypted for session: \"{packet.ToString()}\""); } if ((cryptoInfo.EnableTreeEncryption.Contains(packet.Header.TreeId) && packet.Header.Command != Smb2Command.TREE_CONNECT)) { throw new Exception($"The packet should be encrypted for tree: \"{packet.ToString()}\""); } } }
/// <summary> /// Update the context from a Negotiate Request or Response messages. /// The caller should ensure the messages are input in the right order. /// </summary> /// <param name="packet">The packet used to update this context. It must be the one of the following types: /// Smb2NegotiateRequestPacket, Smb2NegotiateResponsePacket</param> public void UpdateConnectionState(Smb2SinglePacket packet) { //Check the packet type if (!(packet is Smb2NegotiateRequestPacket) && !(packet is Smb2NegotiateResponsePacket)) { throw new InvalidCastException("packet must be one of the types: Smb2NegotiateRequestPacket, Smb2NegotiateResponsePacket."); } //Refer to [MS-SMB2] 3.2.5.2 Receiving an SMB2 NEGOTIATE Response if (packet is Smb2NegotiateRequestPacket) { connectionPreauthIntegrityHashValue = ZeroHashValue; byte[] messageBytes = packet.ToBytes(); byte[] hashData = connectionPreauthIntegrityHashValue.Concat(messageBytes).ToArray(); connectionPreauthIntegrityHashValue = CreateHashAlgorithm().ComputeHash(hashData); } else if (packet is Smb2NegotiateResponsePacket) { byte[] messageBytes = packet.ToBytes(); byte[] hashData = connectionPreauthIntegrityHashValue.Concat(messageBytes).ToArray(); connectionPreauthIntegrityHashValue = CreateHashAlgorithm().ComputeHash(hashData); } }
/// <summary> /// Test if the response packet is an interim response packet /// </summary> /// <param name="singlePacket">The single response packet</param> /// <returns>True if it is an interim packet, otherwise false.</returns> private bool IsInterimResponsePacket(Smb2SinglePacket singlePacket) { return Smb2Utility.IsInterimResponsePacket(singlePacket); }
/// <summary> /// modify the header of packet because it is a async packet /// </summary> /// <param name="packet">The packet</param> /// <param name="endpoint">The endpoint of client</param> /// <param name="asyncId">The asyncId</param> private void ModifyAsyncHeader(Smb2SinglePacket packet, Smb2Endpoint endpoint, ulong asyncId) { packet.Header.Flags |= Packet_Header_Flags_Values.FLAGS_ASYNC_COMMAND; packet.Header.ProcessId = (uint)asyncId; packet.Header.TreeId = (uint)(asyncId >> 32); //for finnal async response, if Interim Response has been send, it does not grand //any credits because credits has been granded in Interim Response if (context.connectionList[endpoint.EndpointId].asyncCommandList.ContainsKey(asyncId)) { packet.Header.CreditRequest_47_Response = 0; } else { //grand credits as normal } }
/// <summary> /// Set packet header field /// </summary> /// <param name="packet">The packet</param> /// <param name="endpoint">The client endpoint</param> /// <param name="messageId">The messageId of request packet</param> private void SetHeader(Smb2SinglePacket packet, Smb2Endpoint endpoint, ulong messageId) { SetHeader(packet, 0, endpoint, messageId); }
private Smb2Packet DecodeSingleResponsePacket( byte[] messageBytes, bool ignoreCompoundFlag, ulong realSessionId, uint realTreeId, out int consumedLength, out int expectedLength ) { Packet_Header smb2Header; bool isLeaseBreakPacket = false; int offset = 0; smb2Header = TypeMarshal.ToStruct <Packet_Header>(messageBytes, ref offset); if (smb2Header.Command == Smb2Command.OPLOCK_BREAK) { ushort structureSize = TypeMarshal.ToStruct <ushort>(messageBytes, ref offset); if (structureSize == (ushort)OplockLeaseBreakStructureSize.LeaseBreakNotification || structureSize == (ushort)OplockLeaseBreakStructureSize.LeaseBreakResponse || structureSize == 9) // Add this condition temporally to handle LeaseBreakResponse is error response (i.e. structureSize == 9), but this will still hide the condition when OplockBreakResponse is error response { isLeaseBreakPacket = true; } } Smb2SinglePacket packet = null; ushort structSize = BitConverter.ToUInt16(messageBytes, Smb2Consts.Smb2HeaderLen); switch (smb2Header.Command) { case Smb2Command.CANCEL: packet = new Smb2CancelResponsePacket(); break; case Smb2Command.CHANGE_NOTIFY: packet = new Smb2ChangeNotifyResponsePacket(); break; case Smb2Command.CLOSE: packet = new Smb2CloseResponsePacket(); break; case Smb2Command.CREATE: packet = new Smb2CreateResponsePacket(); break; case Smb2Command.ECHO: packet = new Smb2EchoResponsePacket(); break; case Smb2Command.FLUSH: packet = new Smb2FlushResponsePacket(); break; case Smb2Command.IOCTL: packet = new Smb2IOCtlResponsePacket(); break; case Smb2Command.LOCK: packet = new Smb2LockResponsePacket(); break; case Smb2Command.LOGOFF: packet = new Smb2LogOffResponsePacket(); break; case Smb2Command.NEGOTIATE: packet = new Smb2NegotiateResponsePacket(); break; case Smb2Command.OPLOCK_BREAK: if (smb2Header.MessageId == ulong.MaxValue) { if (!isLeaseBreakPacket) { packet = new Smb2OpLockBreakNotificationPacket(); } else { packet = new Smb2LeaseBreakNotificationPacket(); } } else { if (!isLeaseBreakPacket) { packet = new Smb2OpLockBreakResponsePacket(); } else { packet = new Smb2LeaseBreakResponsePacket(); } } break; case Smb2Command.QUERY_DIRECTORY: packet = new Smb2QueryDirectoryResponePacket(); break; case Smb2Command.QUERY_INFO: packet = new Smb2QueryInfoResponsePacket(); break; case Smb2Command.READ: packet = new Smb2ReadResponsePacket(); break; case Smb2Command.SESSION_SETUP: packet = new Smb2SessionSetupResponsePacket(); ((Smb2SessionSetupResponsePacket)packet).MessageBytes = messageBytes; break; case Smb2Command.SET_INFO: packet = new Smb2SetInfoResponsePacket(); break; case Smb2Command.TREE_CONNECT: packet = new Smb2TreeConnectResponsePacket(); break; case Smb2Command.TREE_DISCONNECT: packet = new Smb2TreeDisconnectResponsePacket(); break; case Smb2Command.WRITE: packet = new Smb2WriteResponsePacket(); break; default: throw new InvalidOperationException("Received an unknown packet! the type of the packet is " + smb2Header.Command.ToString()); } if (IsErrorPacket(smb2Header)) { var error = new Smb2ErrorResponsePacket(); error.FromBytes(messageBytes, out consumedLength, out expectedLength); packet.Header = error.Header; packet.Error = error; } else { packet.FromBytes(messageBytes, out consumedLength, out expectedLength); } //if ignoreCompoundFlag is false, means the process of decoding this packet //is not part of the process of decoding a compound packet. We will update //context here. if (!ignoreCompoundFlag) { } return(packet); }
/// <summary> /// Decode the message except length field which may exist if transport is tcp /// </summary> /// <param name="messageBytes">The received packet</param> /// <param name="role">The role of this decoder, client or server</param> /// <param name="ignoreCompoundFlag">indicate whether decode the packet as a single packet or a compound packet /// when compound flag is set</param> /// <param name="realSessionId">The real sessionId for this packet</param> /// <param name="realTreeId">The real treeId for this packet</param> /// <param name="consumedLength">[OUT]The consumed length of the message</param> /// <param name="expectedLength">[OUT]The expected length</param> /// <returns>A Smb2Packet</returns> public Smb2Packet DecodeCompletePacket( byte[] messageBytes, Smb2Role role, bool ignoreCompoundFlag, ulong realSessionId, uint realTreeId, out int consumedLength, out int expectedLength ) { //protocol version is of 4 bytes len byte[] protocolVersion = new byte[sizeof(uint)]; Array.Copy(messageBytes, 0, protocolVersion, 0, protocolVersion.Length); SmbVersion version = DecodeVersion(protocolVersion); if (version == SmbVersion.Version1) { // SMB Negotiate packet return(DecodeSmbPacket(messageBytes, role, out consumedLength, out expectedLength)); } else if (version == SmbVersion.Version2Encrypted) { // SMB2 encrypted packet return(DecodeEncryptedSmb2Packet( messageBytes, role, ignoreCompoundFlag, realSessionId, realTreeId, out consumedLength, out expectedLength )); } else { // SMB2 packet not encrypted Smb2Packet decodedPacket = DecodeSmb2Packet( messageBytes, role, ignoreCompoundFlag, realSessionId, realTreeId, out consumedLength, out expectedLength ); //For single packet signature verification if (decodedPacket is Smb2SinglePacket) { //verify signature of a single packet Smb2SinglePacket singlePacket = decodedPacket as Smb2SinglePacket; TryVerifySignatureExceptSessionSetupResponse(singlePacket, singlePacket.Header.SessionId, messageBytes); } else if (decodedPacket is Smb2CompoundPacket)//For Compound packet signature verification { //verify signature of the compound packet TryVerifySignature(decodedPacket as Smb2CompoundPacket, messageBytes); } return(decodedPacket); } }
/// <summary> /// Verify the signature of a Smb2SinglePacket /// </summary> /// <param name="packet">The packet to be verified</param> /// <param name="cryptoInfo">The cryptoInfo of smb2client</param> /// <returns>True when signature verification succeeds and false when fails</returns> private bool VerifySignature(Smb2SinglePacket packet, Smb2CryptoInfo cryptoInfo, byte[] messageBytes) { if (cryptoInfo.DisableVerifySignature) { // Skip the verification. return true; } try { if (IsErrorPacket(packet.Header)) { packet = packet.Error; } byte[] bytesToCompute = messageBytes; // Zero out the 16-byte signature field in the SMB2 Header of the incoming message. Array.Clear(bytesToCompute, System.Runtime.InteropServices.Marshal.SizeOf(packet.Header) - Smb2Consts.SignatureSize, Smb2Consts.SignatureSize); //Compute the message with signing key byte[] computedSignature = null; if (Smb2Utility.IsSmb3xFamily(cryptoInfo.Dialect)) { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect belongs to the SMB 3.x dialect family, //the receiver MUST compute a 16-byte hash by using AES-128-CMAC over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The AES-128-CMAC is specified in [RFC4493]. //TD has mentioned to use Session.SigningKey for SESSION_SETUP Response and Channel.SigningKey for other responses //In the current SDK, the SigningKey is the Channel.SigningKey computedSignature = AesCmac128.ComputeHash(cryptoInfo.SigningKey, bytesToCompute); } else { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect is "2.002" or "2.100", the receiver MUST compute a 32-byte hash by using HMAC-SHA256 over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The HMAC-SHA256 hash is specified in [FIPS180-2] and [RFC2104]. HMACSHA256 hmacSha = new HMACSHA256(cryptoInfo.SigningKey); computedSignature = hmacSha.ComputeHash(bytesToCompute); } //[MS-SMB2] 3.1.5.1 //If the first 16 bytes (the high-order portion) of the computed signature from step 3 or step 4 matches the saved signature from step 1, the message is signed correctly // compare the first 16 bytes of the originalSignature and computedSignature return packet.Header.Signature.SequenceEqual(computedSignature.Take(Smb2Consts.SignatureSize)); } catch (Exception ex) { throw new Exception("Error happened during signature verification of packet: " + packet.ToString() + ". Exception message: " + ex.Message); } }
private void SetHeader(Smb2SinglePacket packet, uint status, Smb2Endpoint endpoint, ulong messageId) { packet.Endpoint = endpoint; Smb2SinglePacket singleRequestPacket = context.FindRequestPacket(endpoint.EndpointId, messageId) as Smb2SinglePacket; bool isRequestSigned = false; ushort clientRequestCredits = 0; if (singleRequestPacket == null) { packet.Header.MessageId = 0; packet.Header.Command = Smb2Command.NEGOTIATE; } else { packet.Header.MessageId = singleRequestPacket.Header.MessageId; packet.Header.SessionId = singleRequestPacket.Header.SessionId; packet.Header.TreeId = singleRequestPacket.Header.TreeId; packet.Header.ProcessId = singleRequestPacket.Header.ProcessId; packet.Header.Command = singleRequestPacket.Header.Command; if (((singleRequestPacket).Header.Flags & Packet_Header_Flags_Values.FLAGS_SIGNED) == Packet_Header_Flags_Values.FLAGS_SIGNED) { isRequestSigned = true; } clientRequestCredits = singleRequestPacket.Header.CreditRequest_47_Response; } packet.Header.CreditRequest_47_Response = Smb2Utility.CaculateResponseCredits(clientRequestCredits, context.connectionList[endpoint.EndpointId].commandSequenceWindow.Count); packet.Header.ProtocolId = Smb2Consts.Smb2ProtocolId; packet.Header.Signature = new byte[Smb2Consts.SignatureSize]; packet.Header.StructureSize = Packet_Header_StructureSize_Values.V1; packet.Header.Status = status; packet.Header.Flags |= Packet_Header_Flags_Values.FLAGS_SERVER_TO_REDIR; if (packet.Header.SessionId != 0 && (isRequestSigned || context.ShouldPacketBeSigned(singleRequestPacket.GetSessionId()))) { packet.Header.Flags |= Packet_Header_Flags_Values.FLAGS_SIGNED; (packet as Smb2SinglePacket).SessionKey = context.globalSessionTable[singleRequestPacket.GetSessionId()].sessionKey; } }
/// <summary> /// Verify the signature of a Smb2SinglePacket /// </summary> /// <param name="packet">The packet to be verified</param> /// <param name="cryptoInfo">The cryptoInfo of smb2client</param> /// <returns>True when signature verification succeeds and false when fails</returns> private bool VerifySignature(Smb2SinglePacket packet, Smb2CryptoInfo cryptoInfo, byte[] messageBytes) { if (cryptoInfo.DisableVerifySignature) { // Skip the verification. return(true); } try { if (IsErrorPacket(packet.Header)) { packet = packet.Error; } byte[] bytesToCompute = messageBytes; // Zero out the 16-byte signature field in the SMB2 Header of the incoming message. Array.Clear(bytesToCompute, System.Runtime.InteropServices.Marshal.SizeOf(packet.Header) - Smb2Consts.SignatureSize, Smb2Consts.SignatureSize); //Compute the message with signing key byte[] computedSignature = null; if (Smb2Utility.IsSmb3xFamily(cryptoInfo.Dialect)) { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect belongs to the SMB 3.x dialect family, //the receiver MUST compute a 16-byte hash by using AES-128-CMAC over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The AES-128-CMAC is specified in [RFC4493]. //TD has mentioned to use Session.SigningKey for SESSION_SETUP Response and Channel.SigningKey for other responses //In the current SDK, the SigningKey is the Channel.SigningKey if (cryptoInfo.SigningId == SigningAlgorithm.AES_GMAC) { var nonce = Smb2Utility.ComputeNonce(packet, this.decodeRole); var(_ciphertext, tag) = AesGmac.ComputeHash(cryptoInfo.SigningKey, nonce, bytesToCompute); computedSignature = tag; } else { computedSignature = AesCmac128.ComputeHash(cryptoInfo.SigningKey, bytesToCompute); } } else { //[MS-SMB2] 3.1.5.1 //If Session.Connection.Dialect is "2.002" or "2.100", the receiver MUST compute a 32-byte hash by using HMAC-SHA256 over the entire message, //beginning with the SMB2 Header from step 2, and using the key provided. //The HMAC-SHA256 hash is specified in [FIPS180-2] and [RFC2104]. HMACSHA256 hmacSha = new HMACSHA256(cryptoInfo.SigningKey); computedSignature = hmacSha.ComputeHash(bytesToCompute); } //[MS-SMB2] 3.1.5.1 //If the first 16 bytes (the high-order portion) of the computed signature from step 3 or step 4 matches the saved signature from step 1, the message is signed correctly // compare the first 16 bytes of the originalSignature and computedSignature return(packet.Header.Signature.SequenceEqual(computedSignature.Take(Smb2Consts.SignatureSize))); } catch (Exception ex) { throw new Exception("Error happened during signature verification of packet: " + packet.ToString() + ". Exception message: " + ex.Message); } }
/// <summary> /// Build a Smb2Packet from a byte array /// </summary> /// <param name="data">The byte array</param> /// <param name="consumedLen">The consumed data length</param> /// <param name="expectedLen">The expected data length</param> /// <returns>The Smb2Packet</returns> internal override void FromBytes(byte[] data, out int consumedLen, out int expectedLen) { this.Packets = new List <Smb2SinglePacket>(); Packet_Header header = TypeMarshal.ToStruct <Packet_Header>(data); int innerConsumedLen = 0; int innerExpectedLen = 0; consumedLen = 0; byte[] temp = null; Smb2SinglePacket singlePacket = null; while (header.NextCommand != 0) { temp = new byte[header.NextCommand]; Array.Copy(data, consumedLen, temp, 0, temp.Length); singlePacket = decoder.DecodeSinglePacket( temp, decoder.DecodeRole, GetRealSessionId(header), GetRealTreeId(header), out innerConsumedLen, out innerExpectedLen ) as Smb2SinglePacket; singlePacket.OuterCompoundPacket = this; singlePacket.IsInCompoundPacket = true; singlePacket.IsLast = false; Packets.Add(singlePacket); //If a packet is in compound packet, there may be some padding at the end, //here we do not rely on the innerConsumedLen but header.NextCommand consumedLen += temp.Length; header = TypeMarshal.ToStruct <Packet_Header>(ArrayUtility.SubArray(data, consumedLen)); } temp = new byte[data.Length - consumedLen]; Array.Copy(data, consumedLen, temp, 0, temp.Length); singlePacket = decoder.DecodeSinglePacket( temp, decoder.DecodeRole, GetRealSessionId(header), GetRealTreeId(header), out innerConsumedLen, out innerExpectedLen ) as Smb2SinglePacket; singlePacket.OuterCompoundPacket = this; singlePacket.IsInCompoundPacket = true; singlePacket.IsLast = true; Packets.Add(singlePacket); consumedLen += innerConsumedLen; expectedLen = 0; }
/// <summary> /// Try to verify the signature of a singlePacket /// </summary> /// <param name="singlePacket">The singlepacket to verified</param> /// <param name="sessionId">The sessionId to retrieve the cryptoinfo of the session.</param> private void TryVerifySignature(Smb2SinglePacket singlePacket, ulong sessionId, byte[] messageBytes) { // [MS-SMB2] 3.2.5.1.3 //If the MessageId is 0xFFFFFFFFFFFFFFFF, no verification is necessary. if (singlePacket.Header.MessageId == 0xFFFFFFFFFFFFFFFF) { return; } Smb2CryptoInfo cryptoInfo = null; //[MS-SMB2] 3.2.5.1.3 //If the SMB2 header of the response has SMB2_FLAGS_SIGNED set in the Flags field, the client MUST verify the signature if (singlePacket.Header.Flags.HasFlag(Packet_Header_Flags_Values.FLAGS_SIGNED)) { //This is for the session binding success situation. //After the session setup success, the cryptoInfoTable will be updated with new signingkey //The new signingkey is not in the cryptoInfoTable now. Cannot verify the signature before the table is updated. if (singlePacket.Header.Command == Smb2Command.SESSION_SETUP && singlePacket.Header.Status == Smb2Status.STATUS_SUCCESS) { //skip return; } if (cryptoInfoTable.TryGetValue(sessionId, out cryptoInfo)) { if (!VerifySignature(singlePacket, cryptoInfo, messageBytes)) { //If signature verification fails, the client MUST discard the received message and do no further processing for it. //The client MAY also choose to disconnect the connection. //Throw exception here. throw new InvalidOperationException("Incorrect signed packet: " + singlePacket.ToString()); } } } return; }
/// <summary> /// Grand credit to client /// </summary> /// <param name="packet">The response packet</param> /// <param name="connectionId">Used to find the connection</param> private void GrandCredit(Smb2SinglePacket packet, int connectionId) { connectionList[connectionId].GrandCredit(packet.Header.CreditRequest_47_Response); }