public static byte[] Decrypt(byte[] bytes, Dictionary<ulong, Smb2CryptoInfo> cryptoInfoTable, Smb2Role role) { Transform_Header transformHeader = Smb2Utility.UnmarshalStructure<Transform_Header>(bytes); if (transformHeader.SessionId == 0 || !cryptoInfoTable.ContainsKey(transformHeader.SessionId)) throw new InvalidOperationException("Invalid SessionId in TRANSFORM_HEADER."); Smb2CryptoInfo cryptoInfo = cryptoInfoTable[transformHeader.SessionId]; using (var bcrypt = new BCryptAlgorithm("AES")) { int nonceLength = 0; BCryptCipherMode mode = BCryptCipherMode.NotAvailable; GetCryptoParams(cryptoInfo, CryptoOperationType.Decrypt, out mode, out nonceLength); bcrypt.Mode = mode; bcrypt.Key = role == Smb2Role.Server ? cryptoInfo.ServerInKey : cryptoInfo.ServerOutKey; return bcrypt.Decrypt(bytes.Skip(52).ToArray(), transformHeader.Nonce.ToByteArray().Take(nonceLength).ToArray(), bytes.Skip(20).Take(32).ToArray(), transformHeader.Signature); } }
/// <summary> /// Encrypt a byte array. /// Many data in NRPC struct is required to be encrypted. /// </summary> /// <param name="isAesNegotiated"> /// Is AES negotiated during secure channel setup. /// If AES is not negotiated, use RC4 to encrypt. /// </param> /// <param name="sessionKey"> /// Session key. /// </param> /// <param name="buffer"> /// Buffer to encrypt. /// </param> /// <returns> /// Encrypted buffer. /// </returns> /// <exception cref="ArgumentNullException"> /// Thrown when sessionKey or buffer is null. /// </exception> /// <exception cref="ArgumentException"> /// Thrown when session key length is incorrect. /// </exception> public static byte[] EncryptBuffer( bool isAesNegotiated, byte[] sessionKey, byte[] buffer) { if (sessionKey == null) { throw new ArgumentNullException("sessionKey"); } if (buffer == null) { throw new ArgumentNullException("buffer"); } if (sessionKey.Length != NETLOGON_SESSION_KEY_LENGTH) { throw new ArgumentException("Session key length is incorrect.", "sessionKey"); } if (isAesNegotiated) { using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = sessionKey; aes.IV = new byte[aes.BlockSize]; return aes.Encrypt(buffer); } } else { using (RC4 rc4 = RC4.Create()) { rc4.Key = sessionKey; return rc4.CreateEncryptor().TransformFinalBlock(buffer, 0, buffer.Length); } } }
/// <summary> /// Compute the Netlogon Credential. /// </summary> /// <param name="algorithm"> /// Algorithm to compute the Netlogon authenticator. /// </param> /// <param name="input"> /// A byte array that contains the input. /// </param> /// <param name="sessionKey"> /// Session Key. /// </param> /// <returns>Netlogon Credential</returns> /// <exception cref="ArgumentNullException"> /// Thrown when input or sessionKey parameter /// passed to the method is null. /// </exception> /// <exception cref="ArgumentException"> /// Thrown when computation algorithm is not supported. /// Thrown when session key length is incorrect. /// </exception> public static byte[] ComputeNetlogonCredential( NrpcComputeNetlogonCredentialAlgorithm algorithm, byte[] input, byte[] sessionKey) { if (input == null) { throw new ArgumentNullException("input"); } if (sessionKey == null) { throw new ArgumentNullException("sessionKey"); } if (sessionKey.Length != NETLOGON_SESSION_KEY_LENGTH) { throw new ArgumentException("Session key length is incorrect.", "sessionKey"); } byte[] credential; switch (algorithm) { case NrpcComputeNetlogonCredentialAlgorithm.AES128: //ComputeNetlogonCredential(Input, Sk, Output) //SET IV = 0 //CALL AesEncrypt(Input, Sk, IV, Output) using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = sessionKey; aes.IV = new byte[aes.BlockSize]; //AES128's IV is 128 bits. credential = aes.Encrypt(input); } break; case NrpcComputeNetlogonCredentialAlgorithm.DESECB: //ComputeNetlogonCredential(Input, Sk, Output) //SET k1 to bytes(0, 6, Sk) //CALL InitLMKey(k1, k3) //SET k2 to bytes(7, 13, Sk) //CALL InitLMKey(k2, k4) //CALL DES_ECB(Input, k3, &output1) //CALL DES_ECB(output1, k4, &output2) //SET Output to output2 byte[] k1 = ArrayUtility.SubArray(sessionKey, 0, 7); byte[] k2 = ArrayUtility.SubArray(sessionKey, 7, 7); using (DES des = DES.Create()) { des.Mode = CipherMode.ECB; des.Padding = PaddingMode.None; des.Key = InitLMKey(k1); byte[] output1 = des.CreateEncryptor().TransformFinalBlock(input, 0, input.Length); des.Key = InitLMKey(k2); byte[] output2 = des.CreateEncryptor().TransformFinalBlock(output1, 0, output1.Length); credential = output2; } break; default: throw new ArgumentException( "Specified netlogon credential computation algorithm is not valid.", "algorithm"); } return credential; }
/// <summary> /// validate netlogon signature token when AES is negotiated /// </summary> /// <param name="sequenceNumber">sequence number</param> /// <param name="sessionKey">session key</param> /// <param name="requestConfidentiality">confidentiality is required or not</param> /// <param name="isClientSideOutbound">Is client side outbound message.</param> /// <param name="securityBuffers"> /// Security buffer, contains plain-text if requestConfidentiality is false; /// or cipher-text if requestConfidentiality is true; /// and signature. /// </param> /// <returns>true if validate success; otherwise, false.</returns> private static bool ValidateNetlogonSignatureTokenWhenAesIsNegotiated( ref ulong sequenceNumber, byte[] sessionKey, bool requestConfidentiality, bool isClientSideOutbound, SecurityBuffer[] securityBuffers) { byte[] plainText; byte[] cipherText; byte[] token; //If AES is negotiated, a server receives an NL_AUTH_SHA2_SIGNATURE structure token = SspiUtility.ConcatenateSecurityBuffers(securityBuffers, SecurityBufferType.Token); NL_AUTH_SHA2_SIGNATURE nlAuthSha2Sign = TypeMarshal.ToStruct<NL_AUTH_SHA2_SIGNATURE>(token); //The SignatureAlgorithm bytes MUST be verified to ensure: //If AES is negotiated, the first byte is set to 0x13. //The second byte is set to 0x00 if (nlAuthSha2Sign.SignatureAlgorithm != NL_AUTH_SHA2_SIGNATURE_SignatureAlgorithm_Values.HMACSHA256) { return false; } if (requestConfidentiality) { //If the Confidentiality option is requested from the application, //then the SealAlgorithm MUST be verified to ensure that //if AES is negotiated, the first byte is set to 0x1A; //The second byte is set to 0x00. if (nlAuthSha2Sign.SealAlgorithm != NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.AES128) { return false; } } else { //If the Confidentiality option is not requested, //then the SealAlgorithm MUST be verified to contain all 0xff bytes. if (nlAuthSha2Sign.SealAlgorithm != NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.NotEncrypted) { return false; } } //The Pad MUST be verified to contain all 0xff bytes. if (nlAuthSha2Sign.Pad != NL_AUTH_SHA2_SIGNATURE_Pad_Values.V1) { return false; } //The Flags data MAY be<86> disregarded. //The SequenceNumber MUST be decrypted. //If AES is negotiated, then the server MUST use the AES128 algorithm //and a key of SessionKey and an initialization vector constructed //by concatenating the checksum with itself (thus getting 16 bytes of data). using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = sessionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.Checksum, nlAuthSha2Sign.Checksum); nlAuthSha2Sign.SequenceNumber = aes.Decrypt(nlAuthSha2Sign.SequenceNumber); } //A local copy of SequenceNumber MUST be computed using the following algorithm. byte[] copySequenceNumber = ComputeCopySequenceNumber(sequenceNumber, isClientSideOutbound); //The SequenceNumber MUST be compared to CopySeqNumber. if (!ArrayUtility.CompareArrays( copySequenceNumber, nlAuthSha2Sign.SequenceNumber)) { return false; } //ServerSequenceNumber MUST be incremented. sequenceNumber += 1; if (requestConfidentiality) { //If the Confidentiality option is requested, //the Confounder and the data MUST be decrypted. byte[] aesEncryptionKey = ComputeEncryptionKeyWhenAesIsNegotiated(sessionKey); using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = aesEncryptionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.SequenceNumber, nlAuthSha2Sign.SequenceNumber); nlAuthSha2Sign.Confounder = aes.Decrypt(nlAuthSha2Sign.Confounder); cipherText = SspiUtility.ConcatenateReadWriteSecurityBuffers( securityBuffers, SecurityBufferType.Data); plainText = aes.Decrypt(cipherText); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, plainText); } //Compute signature plainText = ConcatenateSecurityBuffersForChecksum(securityBuffers); byte[] checksum = ComputeSignatureWhenAesIsNegotiated( nlAuthSha2Sign, sessionKey, requestConfidentiality, plainText); //The first 8 bytes of the computed signature MUST be compared to the checksum. if (checksum != null && checksum.Length >= NL_AUTH_SIGNATURE_CHECKSUM_LENGTH && nlAuthSha2Sign.Checksum != null && nlAuthSha2Sign.Checksum.Length >= NL_AUTH_SIGNATURE_CHECKSUM_LENGTH) { for (int i = 0; i < NL_AUTH_SIGNATURE_CHECKSUM_LENGTH; i++) { if (checksum[i] != nlAuthSha2Sign.Checksum[i]) { return false; } } } return true; }
/// <summary> /// initial netlogon signature token when AES is negotiated /// </summary> /// <param name="sequenceNumber">sequence number</param> /// <param name="sessionKey">session key</param> /// <param name="requestConfidentiality">confidentiality is required or not</param> /// <param name="isClientSideOutbound">Is client side outbound message.</param> /// <param name="securityBuffers"> /// Security buffers, contains input plain-text; output cipher-text and signature. /// </param> private static void InitialNetlogonSignatureTokenWhenAesIsNegotiated( ref ulong sequenceNumber, byte[] sessionKey, bool requestConfidentiality, bool isClientSideOutbound, SecurityBuffer[] securityBuffers) { byte[] plainText; byte[] cipherText; byte[] token; NL_AUTH_SHA2_SIGNATURE nlAuthSha2Sign = new NL_AUTH_SHA2_SIGNATURE(); //The SignatureAlgorithm first byte MUST be set to 0x13, //and the second byte MUST be set to 0x00. nlAuthSha2Sign.SignatureAlgorithm = NL_AUTH_SHA2_SIGNATURE_SignatureAlgorithm_Values.HMACSHA256; if (requestConfidentiality) { //If the Confidentiality option (section 3.3.1) is //requested from the application, then the SealAlgorithm //first byte MUST be set to 0x1A, the second byte MUST //be set to 0x00, and the Confounder MUST be filled with //cryptographically random data. nlAuthSha2Sign.SealAlgorithm = NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.AES128; nlAuthSha2Sign.Confounder = GenerateNonce(NL_AUTH_SIGNATURE_CONFOUNDER_LENGTH); } else { //If the Confidentiality option (section 3.3.1) is not //requested, then the SealAlgorithm MUST be filled with //two bytes of 0xff, and the Confounder is not included in the token. nlAuthSha2Sign.SealAlgorithm = NL_AUTH_SHA2_SIGNATURE_SealAlgorithm_Values.NotEncrypted; nlAuthSha2Sign.Confounder = null; } //The Pad MUST be filled with 0xff bytes. nlAuthSha2Sign.Pad = NL_AUTH_SHA2_SIGNATURE_Pad_Values.V1; //The Flags MUST be filled with 0x00 bytes. nlAuthSha2Sign.Flags = Flags_Values.V1; //The SequenceNumber MUST be computed using the following algorithm. nlAuthSha2Sign.SequenceNumber = ComputeCopySequenceNumber(sequenceNumber, isClientSideOutbound); //The ClientSequenceNumber MUST be incremented by 1. sequenceNumber += 1; //Compute signature plainText = ConcatenateSecurityBuffersForChecksum(securityBuffers); nlAuthSha2Sign.Checksum = ComputeSignatureWhenAesIsNegotiated( nlAuthSha2Sign, sessionKey, requestConfidentiality, plainText); //If the Confidentiality option is requested, the data and the Confounder //field MUST be encrypted. If AES is negotiated then the server MUST use //the AES-128 algorithm using the SessionKey with an initialization //vector constructed by concatenating the sequence number with //itself twice (thus getting 16 bytes of data) if (requestConfidentiality) { byte[] aesEncryptionKey = ComputeEncryptionKeyWhenAesIsNegotiated(sessionKey); using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = aesEncryptionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.SequenceNumber, nlAuthSha2Sign.SequenceNumber); nlAuthSha2Sign.Confounder = aes.Encrypt(nlAuthSha2Sign.Confounder); plainText = SspiUtility.ConcatenateReadWriteSecurityBuffers( securityBuffers, SecurityBufferType.Data); cipherText = aes.Encrypt(plainText); } SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Data, cipherText); } else { cipherText = null; } //The SequenceNumber MUST be encrypted. //If AES is negotiated, then the server MUST use the AES-128 //algorithm using the SessionKey with an initialization vector //constructed by concatenating the first 8 bytes of the //checksum with itself twice (thus getting 16 bytes of data) using (BCryptAlgorithm aes = new BCryptAlgorithm("AES")) { aes.Mode = BCryptCipherMode.CFB; aes.Key = sessionKey; aes.IV = ArrayUtility.ConcatenateArrays( nlAuthSha2Sign.Checksum, // Checksum is only 8 bytes nlAuthSha2Sign.Checksum); nlAuthSha2Sign.SequenceNumber = aes.Encrypt(nlAuthSha2Sign.SequenceNumber); } nlAuthSha2Sign.Dummy = GenerateNonce(NL_AUTH_SHA2_SIGNATURE_DUMMY_LENGTH); // 24 == size of dummy token = TypeMarshal.ToBytes(nlAuthSha2Sign); SspiUtility.UpdateSecurityBuffers(securityBuffers, SecurityBufferType.Token, token); }
private static byte[] Encrypt(ulong sessionId, Smb2CryptoInfo cryptoInfo, Smb2Role role, Smb2Packet originalPacket) { Packet_Header header; if (originalPacket is Smb2SinglePacket) { header = (originalPacket as Smb2SinglePacket).Header; } else { header = (originalPacket as Smb2CompoundPacket).Packets[0].Header; } // Encrypt all messages after session setup if global encryption enabled. // Encrypt all messages after tree connect if global encryption disabled but share encryption enabled. if (header.Command != Smb2Command.NEGOTIATE && header.Command != Smb2Command.SESSION_SETUP && (cryptoInfo.EnableSessionEncryption || (cryptoInfo.EnableTreeEncryption.Contains(header.TreeId) && header.Command != Smb2Command.TREE_CONNECT ) ) ) { using (var bcrypt = new BCryptAlgorithm("AES")) { byte[] originalBinary = originalPacket.ToBytes(); Transform_Header transformHeader = new Transform_Header { ProtocolId = Smb2Consts.ProtocolIdInTransformHeader, OriginalMessageSize = (uint)originalBinary.Length, SessionId = sessionId, Signature = new byte[16] }; if (cryptoInfo.Dialect == DialectRevision.Smb311) { transformHeader.Flags = TransformHeaderFlags.Encrypted; } else { transformHeader.EncryptionAlgorithm = EncryptionAlgorithm.ENCRYPTION_AES128_CCM; } byte[] tag; int nonceLength = 0; BCryptCipherMode mode = BCryptCipherMode.NotAvailable; GetCryptoParams(cryptoInfo, CryptoOperationType.Encrypt, out mode, out nonceLength); bcrypt.Mode = mode; bcrypt.Key = role == Smb2Role.Server ? cryptoInfo.ServerOutKey : cryptoInfo.ServerInKey; // The reserved field (5 bytes for CCM, 4 bytes for GCM) must be set to zero. byte[] nonce = new byte[16]; Buffer.BlockCopy(Guid.NewGuid().ToByteArray(), 0, nonce, 0, nonceLength); transformHeader.Nonce = new Guid(nonce); byte[] output = bcrypt.Encrypt( originalBinary, transformHeader.Nonce.ToByteArray().Take(nonceLength).ToArray(), // Use the fields including and after Nonce field as auth data Smb2Utility.MarshalStructure(transformHeader).Skip(20).ToArray(), // Signature is 16 bytes in length 16, out tag); transformHeader.Signature = tag; return Smb2Utility.MarshalStructure(transformHeader).Concat(output).ToArray(); } } // Return null if the message is not required to be encrypted. return null; }