Handle Ksec CNG provider resolution requests

Add a new `KsecCngProviders` implementation for `\Device\KsecDD` IOCTL `0x390400` request `0x00020000`, including request parsing, algorithm/interface matching, and construction of a pointer-size-correct self-relative `CRYPT_PROVIDER_REFS` response for the Microsoft Primitive Provider. Update `KsecDevice` to treat `0x390400` as a multiplexed CNG request channel, dispatch provider-resolution requests to the new handler, and keep existing generic output behavior for other request kinds.

This fixed bugs inside bcrypt/bcryptprimitives.
This commit is contained in:
AdvDebug
2026-08-01 13:54:13 +03:00
parent e498ac7a0b
commit 8c15645471
2 changed files with 204 additions and 2 deletions
@@ -0,0 +1,199 @@
using System;
using System.Buffers.Binary;
using System.Text;
namespace Brovan.Core.Emulation.OS.Windows
{
internal static class KsecCngProviders
{
internal const uint RequestMagic = 0x1A2B3C4D;
internal const uint RequestResolveProviders = 0x00020000;
private const string PrimitiveProvider = "Microsoft Primitive Provider";
private const string PrimitiveImage = "bcryptprimitives.dll";
private const uint ImageFlagsUserMode = 1;
private const int NullReference = -1;
private const uint CryptUserMode = 1;
private const uint CryptModeMask = 3;
private const int InterfaceOffset = 0x10;
private const int FunctionOffset = 0x18;
private const int ProviderOffset = 0x20;
private const int ModeOffset = 0x28;
private const int HeaderSize = 0x30;
private static readonly (string Name, uint Interface)[] Algorithms =
{
("3DES", 1), ("3DES_112", 1), ("AES", 1), ("DES", 1), ("DESX", 1), ("RC2", 1), ("RC4", 1),
("AES-CMAC", 2), ("AES-GMAC", 2), ("MD2", 2), ("MD4", 2), ("MD5", 2),
("SHA1", 2), ("SHA256", 2), ("SHA384", 2), ("SHA512", 2),
("RSA", 3),
("DH", 4), ("ECDH", 4), ("ECDH_P256", 4), ("ECDH_P384", 4), ("ECDH_P521", 4),
("DSA", 5), ("ECDSA", 5), ("ECDSA_P256", 5), ("ECDSA_P384", 5), ("ECDSA_P521", 5), ("RSA_SIGN", 5),
("RNG", 6),
("CAPI_KDF", 7), ("PBKDF2", 7), ("SP800_108_CTR_HMAC", 7), ("SP800_56A_CONCAT", 7),
("TLS1_1_KDF", 7), ("TLS1_2_KDF", 7), ("HKDF", 7),
};
internal static bool IsResolveRequest(ReadOnlySpan<byte> Input)
{
return Input.Length >= 8
&& BinaryPrimitives.ReadUInt32LittleEndian(Input) == RequestMagic
&& BinaryPrimitives.ReadUInt32LittleEndian(Input.Slice(4)) == RequestResolveProviders;
}
internal static NTSTATUS Resolve(BinaryEmulator Instance, ref DeviceData Data)
{
ReadOnlySpan<byte> Input = Data.InputBuffer.AsSpan(0, (int)Math.Min(Data.InputLength, (uint)Data.InputBuffer.Length));
if (Input.Length < HeaderSize)
return NTSTATUS.STATUS_INVALID_PARAMETER;
uint RequestedInterface = BinaryPrimitives.ReadUInt32LittleEndian(Input.Slice(InterfaceOffset));
uint Mode = BinaryPrimitives.ReadUInt32LittleEndian(Input.Slice(ModeOffset));
string Function = ReadRequestString(Input, FunctionOffset);
string Provider = ReadRequestString(Input, ProviderOffset);
if ((Mode & CryptModeMask & CryptUserMode) == 0)
return NTSTATUS.STATUS_NOT_FOUND;
if (Provider != null && !Provider.Equals(PrimitiveProvider, StringComparison.OrdinalIgnoreCase))
return NTSTATUS.STATUS_NOT_FOUND;
int MatchCount = 0;
foreach ((string Name, uint Interface) Algorithm in Algorithms)
{
if (Matches(Algorithm, Function, RequestedInterface))
MatchCount++;
}
if (MatchCount == 0)
return NTSTATUS.STATUS_NOT_FOUND;
return WriteProviderRefs(Instance, ref Data, Function, RequestedInterface, MatchCount);
}
private static bool Matches((string Name, uint Interface) Algorithm, string Function, uint RequestedInterface)
{
if (Function != null && !Algorithm.Name.Equals(Function, StringComparison.OrdinalIgnoreCase))
return false;
return RequestedInterface == 0 || Algorithm.Interface == RequestedInterface;
}
private static string ReadRequestString(ReadOnlySpan<byte> Input, int OffsetField)
{
long Offset = BinaryPrimitives.ReadInt64LittleEndian(Input.Slice(OffsetField));
if (Offset < 0 || Offset + 2 > Input.Length)
return null;
int End = (int)Offset;
while (End + 1 < Input.Length && (Input[End] != 0 || Input[End + 1] != 0))
End += 2;
return Encoding.Unicode.GetString(Input.Slice((int)Offset, End - (int)Offset));
}
private static NTSTATUS WriteProviderRefs(BinaryEmulator Instance, ref DeviceData Data, string Function, uint RequestedInterface, int MatchCount)
{
int Pointer = Instance.WinHelper.PointerSize;
int RefsSize = 2 * Pointer;
int RefSize = 7 * Pointer;
int ImageRefSize = 2 * Pointer;
int Cursor = RefsSize;
int PointerArray = Cursor;
Cursor += MatchCount * Pointer;
int FirstRef = Cursor;
Cursor += MatchCount * RefSize;
int[] ImageRefs = new int[MatchCount];
int[] ImageStrings = new int[MatchCount];
int[] ProviderStrings = new int[MatchCount];
int[] FunctionStrings = new int[MatchCount];
int Index = 0;
foreach ((string Name, uint Interface) Algorithm in Algorithms)
{
if (!Matches(Algorithm, Function, RequestedInterface))
continue;
ImageRefs[Index] = Cursor;
Cursor += ImageRefSize;
ImageStrings[Index] = Cursor;
Cursor = AlignUp(Cursor + Encoding.Unicode.GetByteCount(PrimitiveImage) + 2, Pointer);
ProviderStrings[Index] = Cursor;
Cursor = AlignUp(Cursor + Encoding.Unicode.GetByteCount(PrimitiveProvider) + 2, Pointer);
FunctionStrings[Index] = Cursor;
Cursor = AlignUp(Cursor + Encoding.Unicode.GetByteCount(Algorithm.Name) + 2, Pointer);
Index++;
}
uint Required = (uint)Cursor;
if (Data.OutputBuffer == null || Data.OutputLength < Required)
{
if (Data.OutputBuffer != null && Data.OutputLength >= 4)
{
BinaryPrimitives.WriteUInt32LittleEndian(Data.OutputBuffer.AsSpan(0, 4), Required);
Data.Information = 4;
}
return NTSTATUS.STATUS_BUFFER_OVERFLOW;
}
Span<byte> Output = Data.OutputBuffer.AsSpan(0, (int)Required);
Output.Clear();
BinaryPrimitives.WriteUInt32LittleEndian(Output, (uint)MatchCount);
WriteReference(Output, Pointer, Pointer, PointerArray);
Index = 0;
foreach ((string Name, uint Interface) Algorithm in Algorithms)
{
if (!Matches(Algorithm, Function, RequestedInterface))
continue;
int Entry = FirstRef + Index * RefSize;
WriteReference(Output, PointerArray + Index * Pointer, Pointer, Entry);
BinaryPrimitives.WriteUInt32LittleEndian(Output.Slice(Entry), Algorithm.Interface);
WriteReference(Output, Entry + Pointer, Pointer, FunctionStrings[Index]);
WriteReference(Output, Entry + 2 * Pointer, Pointer, ProviderStrings[Index]);
WriteReference(Output, Entry + 4 * Pointer, Pointer, NullReference);
WriteReference(Output, Entry + 5 * Pointer, Pointer, ImageRefs[Index]);
WriteReference(Output, Entry + 6 * Pointer, Pointer, NullReference);
WriteReference(Output, ImageRefs[Index], Pointer, ImageStrings[Index]);
BinaryPrimitives.WriteUInt32LittleEndian(Output.Slice(ImageRefs[Index] + Pointer), ImageFlagsUserMode);
WriteString(Output, ImageStrings[Index], PrimitiveImage);
WriteString(Output, ProviderStrings[Index], PrimitiveProvider);
WriteString(Output, FunctionStrings[Index], Algorithm.Name);
Index++;
}
Data.Information = Required;
return NTSTATUS.STATUS_SUCCESS;
}
private static void WriteReference(Span<byte> Buffer, int Offset, int PointerSize, int Target)
{
ulong Value = Target == NullReference ? ulong.MaxValue : (ulong)Target;
if (PointerSize == 8)
BinaryPrimitives.WriteUInt64LittleEndian(Buffer.Slice(Offset, 8), Value);
else
BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(Offset, 4), (uint)Value);
}
private static void WriteString(Span<byte> Buffer, int Offset, string Value)
{
Encoding.Unicode.GetBytes(Value, Buffer.Slice(Offset));
}
private static int AlignUp(int Value, int Alignment)
{
int Mask = Alignment - 1;
return (Value + Mask) & ~Mask;
}
}
}
@@ -15,7 +15,7 @@ namespace Brovan.Core.Emulation.OS.Windows
private const uint IOCTL_KSEC_DECRYPT_MEMORY_CROSS_PROCESS = 0x39001A;
private const uint IOCTL_KSEC_ENCRYPT_MEMORY_SAME_LOGON = 0x39001E;
private const uint IOCTL_KSEC_DECRYPT_MEMORY_SAME_LOGON = 0x390022;
private const uint IOCTL_KSEC_CLIENT_HANDSHAKE = 0x390400;
private const uint IOCTL_KSEC_CNG_REQUEST = 0x390400;
public NTSTATUS Create(BinaryEmulator Instance, string DevicePath, byte[] EaBuffer, out string InternalPath, out WinDeviceDelegate Handler)
{
@@ -59,7 +59,10 @@ namespace Brovan.Core.Emulation.OS.Windows
return NTSTATUS.STATUS_SUCCESS;
}
case IOCTL_KSEC_CLIENT_HANDSHAKE:
case IOCTL_KSEC_CNG_REQUEST:
if (Data.InputBuffer != null && KsecCngProviders.IsResolveRequest(Data.InputBuffer.AsSpan(0, (int)Math.Min(Data.InputLength, (uint)Data.InputBuffer.Length))))
return KsecCngProviders.Resolve(Instance, ref Data);
if (Data.OutputBuffer != null && Data.OutputLength > 0)
{
uint OutN = Math.Min(Data.OutputLength, (uint)Data.OutputBuffer.Length);