From 28de11ee97bbc9416f9f29350c6a2d572b5ee6b7 Mon Sep 17 00:00:00 2001 From: Ryan Newington Date: Sat, 11 Nov 2023 16:09:12 +1100 Subject: [PATCH 1/6] Adds initial support for reading parameters passed to consent UI --- .../ConsentUI/ConsentUICommandLineArgs.cs | 11 + .../ConsentUI/ConsentUIData.cs | 337 ++++++++++++++++++ .../ConsentUI/ConsentUIDataActiveX.cs | 19 + .../ConsentUI/ConsentUIDataCom.cs | 53 +++ .../ConsentUI/ConsentUIDataCredCollect.cs | 19 + .../ConsentUI/ConsentUIDataExe.cs | 59 +++ .../ConsentUI/ConsentUIDataMsi.cs | 71 ++++ .../ConsentUI/ConsentUIDataMsix.cs | 46 +++ .../ConsentUI/ElevationType.cs | 10 + .../Samples/dump-consent-com-x64.dat | Bin 0 -> 272 bytes .../Samples/dump-consent-com-x86.dat | Bin 0 -> 236 bytes .../Samples/dump-consent-exe-x64.dat | Bin 0 -> 490 bytes .../Samples/dump-consent-exe-x86.dat | Bin 0 -> 446 bytes .../Samples/dump-consent-msi-x64.dat | Bin 0 -> 398 bytes .../Samples/dump-consent-msi-x86.dat | Bin 0 -> 298 bytes .../Samples/dump-consent-msix-x64.dat | Bin 0 -> 732 bytes .../Samples/dump-consent-msix-x86.dat | Bin 0 -> 692 bytes .../Samples/dump-consent-winsec-x86.dat | Bin 0 -> 384 bytes .../CredentialProviderBase.cs | 10 + .../Enums/ConsentUIType.cs | 12 + .../Interop/SafeHGlobalHandle.cs | 64 ++++ .../Interop/Structs/ConsentUIStructureCom.cs | 27 ++ .../Interop/Structs/ConsentUIStructureExe.cs | 29 ++ .../Structs/ConsentUIStructureHeader.cs | 33 ++ .../Interop/Structs/ConsentUIStructureMsi.cs | 38 ++ .../Interop/Structs/ConsentUIStructureMsix.cs | 28 ++ .../Lithnet.CredentialProvider.csproj | 7 +- .../NativeMethods.json | 3 + .../NativeMethods.txt | 7 + .../InternalLogger.cs | 64 +++- .../TestCredentialProviderNet472x64.cs | 4 +- .../TestCredentialProviderTile.cs | 2 +- .../TestCredentialProviderNet472x86.cs | 6 +- .../TestCredentialProviderNet60x64.cs | 6 +- .../TestCredentialProviderNet60x86.cs | 6 +- 35 files changed, 952 insertions(+), 19 deletions(-) create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x64.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x86.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x64.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x86.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x64.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x86.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x64.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x86.dat create mode 100644 src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-winsec-x86.dat create mode 100644 src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs create mode 100644 src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs create mode 100644 src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs create mode 100644 src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs create mode 100644 src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs create mode 100644 src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs create mode 100644 src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs create mode 100644 src/Lithnet.CredentialProvider/NativeMethods.json create mode 100644 src/Lithnet.CredentialProvider/NativeMethods.txt diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs new file mode 100644 index 0000000..dfee9ee --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUICommandLineArgs.cs @@ -0,0 +1,11 @@ +namespace Lithnet.CredentialProvider +{ + internal class ConsentUICommandLineArgs + { + public uint AppInfoProcessId { get; set; } + + public int Size { get; set; } + + public long Address { get; set; } + } +} \ No newline at end of file diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs new file mode 100644 index 0000000..6c56ae8 --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs @@ -0,0 +1,337 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Security.Principal; +using Lithnet.CredentialProvider.Interop; +using Microsoft.Win32.SafeHandles; +using Windows.Win32.Foundation; +using Windows.Win32.System.Threading; +using NativeMethods = Windows.Win32.PInvoke; + +namespace Lithnet.CredentialProvider +{ + /// + /// ConsentUIData is an abstract base class that represents all the different types of data structures that can be passed to the ConsentUI process for a UAC elevation prompt. + /// The static members of the class can be used to retrieve the data structure passed to the ConsentUI process, or to determine if the current process is the ConsentUI process. + /// The caller will be provided with one of the concrete implementations of this class, depending on the type of data structure that was passed to the ConsentUI process. + /// Use the property to determine the type of data structure and cast it to one of the concrete implementations. + /// + public abstract class ConsentUIData + { + private static bool? isConsentUI; + private static ConsentUICommandLineArgs commandLineArgs; + private protected ConsentUIStructureHeader header; + private readonly byte[] rawData; + + /// + /// Gets a value indicating the type of ConsentUI data structure + /// + public ConsentUIType Type => this.header.Type; + + /// + /// Gets a value indicating the consent prompt type + /// + public int PromptType => this.header.PromptType; + + /// + /// Gets a handle to the Window that was responsible for invoking the ConsentUI prompt + /// + public IntPtr HWnd => this.header.hWnd; + + public ElevationType ElevationType => this.header.elevationType; + + /// + /// Gets the ID of the session where the ConsentUI prompt was originally invoked + /// + public int SessionId => this.header.sessionId; + + private protected ConsentUIData(IntPtr pData, int expectedSize) + { + this.rawData = GetRawBytes(pData, expectedSize); + this.header = Marshal.PtrToStructure(pData); + + if (this.header.Size != expectedSize) + { + throw new InvalidDataException($"The size of the data structure {this.header.Size} does not match the expected size {expectedSize}"); + } + } + + /// + /// Gets the Windows Identity from the original caller requesting elevation + /// + /// A WindowsIdentity object that represents the user requesting elevation + /// Thrown when the user's token could not be obtained from the session information + public WindowsIdentity GetWindowsIdentity() + { + var duplicatedToken = DuplicateHandleInternal(this.header.hToken); + return new WindowsIdentity(duplicatedToken.DangerousGetHandle()); + } + + /// + /// Gets a raw byte array representing the ConsentUI data structure + /// + /// A byte array + public byte[] GetRawData() + { + return this.rawData; + } + + /// + /// Gets a string value that is packed at the end of the data structure if the offset is valid + /// + /// A pointer to the start of the data structure + /// The position from the start of the data structure where the string starts + /// A string containing all characters from the given offset up to the first null character found + private protected string GetStringValueIfValid(IntPtr pData, int offset) + { + if (offset > 0) + { + this.ThrowOnInvalidOffset(offset); + return Marshal.PtrToStringUni(IntPtr.Add(pData, offset)); + } + + return null; + } + + /// + /// Throws an exception if the given offset is greater than the size of the data structure + /// + /// The value of the offset + /// Thrown when the value of the pointer is greater than the expected data size + private protected void ThrowOnInvalidOffset(int value) + { + if (value >= this.header.Size) + { + throw new InvalidDataException($"Offset value {value} is greater than the expected data size {this.header.Size}"); + } + } + + /// + /// Creates a ConsentUIData object from a previously-obtained raw byte representation + /// + /// The raw bytes of a supported ConsentUI data structure + /// A ConsentUIData object + public static ConsentUIData GetConsentUIData(byte[] consentUIDataStructure) + { + SafeHGlobalHandle pData = SafeHGlobalHandle.AllocHGlobal(consentUIDataStructure.Length); + Marshal.Copy(consentUIDataStructure, 0, pData.ToIntPtr(), consentUIDataStructure.Length); + return CreateInstance(pData.ToIntPtr(), consentUIDataStructure.Length); + } + + /// + /// Gets the data structure passed to the Consent UI process + /// + /// A ConsentUIData object + public static ConsentUIData GetConsentUIData() + { + var pData = GetConsentUIData(out int structSize); + return ConsentUIData.CreateInstance(pData.ToIntPtr(), structSize); + } + + /// + /// Gets a value indicating whether the current process is consent.exe, indicating that the provider is running inside an elevated UAC prompt + /// + /// + public static bool IsConsentUIParent() + { + if (isConsentUI == null) + { + var consentPath = Environment.ExpandEnvironmentVariables("%systemroot%\\system32\\consent.exe"); + var process = Process.GetCurrentProcess(); + var callingProcess = process.MainModule?.FileName; + isConsentUI = string.Equals(callingProcess, consentPath, StringComparison.OrdinalIgnoreCase); + } + + return isConsentUI.Value; + } + + /// + /// Gets the raw bytes of the ConsentUI data structure + /// + /// A byte array + public static byte[] GetConsentUIDataRawBytes() + { + var pData = GetConsentUIData(out int structSize); + return GetRawBytes(pData.ToIntPtr(), structSize); + } + + /// + /// Parses the command line of the consent.exe process to retrieve the data structure passed to it + /// + /// Returns the size of the data structure as reported in the command line arguments + /// A pointer to the newly created copy of the data structure + /// Thrown when either consent.exe is not the parent process + /// Throw when the arguments passed to consent.exe are invalid + private static SafeHGlobalHandle GetConsentUIData(out int size) + { + if (!IsConsentUIParent()) + { + throw new InvalidOperationException("The consent UI data can only be retrieved when consent.exe is the parent process"); + } + + commandLineArgs ??= GetConsentUICommandLineArgs(); + + size = commandLineArgs.Size; + return ReadMemoryFromProcess(commandLineArgs.AppInfoProcessId, commandLineArgs.Address, commandLineArgs.Size); + } + + + /// + /// Extracts the command line arguments passed to the consent.exe process + /// + /// A ConsentUICommandLineArgs object containing the arguments parsed from the command line + /// Thrown when the arguments passed to consent.exe cannot be parsed or are of the incorrect number + private static ConsentUICommandLineArgs GetConsentUICommandLineArgs() + { + var args = Environment.GetCommandLineArgs(); + + if (args.Length != 4) + { + throw new ArgumentException($"Unable to parse command line of consent.exe. The number of elements was incorrect\r\n{string.Join("\r\n", args)}"); + } + + if (!uint.TryParse(args[1], out var appInfoPid)) + { + throw new ArgumentException($"Unable to parse command line of consent.exe. The expected first element was not an integer\r\n{string.Join("\r\n", args)}"); + } + + if (!int.TryParse(args[2], out var size)) + { + throw new ArgumentException($"Unable to parse command line of consent.exe. The expected second element was not an integer\r\n{string.Join("\r\n", args)}"); + } + + if (!long.TryParse(args[3], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var address)) + { + throw new ArgumentException($"Unable to parse command line of consent.exe. The expected third element was not an integer\r\n{string.Join("\r\n", args)}"); + } + + return new ConsentUICommandLineArgs + { + Address = address, + AppInfoProcessId = appInfoPid, + Size = size, + }; + } + + /// + /// Copies the memory from a raw pointer into a managed byte array + /// + /// The pointer where the data copy must start + /// The number of bytes to copy + /// A copy of the raw memory returned as a managed byte array + private static byte[] GetRawBytes(IntPtr pData, int size) + { + byte[] dataForExport = new byte[size]; + Marshal.Copy(pData, dataForExport, 0, size); + return dataForExport; + } + + /// + /// Reads the memory from a specified process + /// + /// The ID of the process + /// The memory address to read + /// The size of the data at the specified memory address + /// A handle to a copy of the process memory + /// Thrown when the process could not be opened or the memory address could not be read + /// Thrown when the size of the copied structure did not equal the expected size as passed to the method + private static SafeHGlobalHandle ReadMemoryFromProcess(uint processId, long address, int size) + { + SafeHGlobalHandle pData = SafeHGlobalHandle.AllocHGlobal(size); + var pAddress = new IntPtr(address); + + SafeFileHandle hProcess = OpenProcessHandle(processId, PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ); + + unsafe + { + nuint numberOfBytesRead = 0; + + if (!NativeMethods.ReadProcessMemory(hProcess, pAddress.ToPointer(), pData.ToIntPtr().ToPointer(), (nuint)size, &numberOfBytesRead)) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error, $"Unable to read memory from process {processId}"); + } + + if (numberOfBytesRead != (nuint)size) + { + throw new InvalidDataException($"Bytes read from memory {numberOfBytesRead} was not the expected structure size {size}"); + } + } + + return pData; + } + + /// + /// Opens a native handle to a process + /// + /// The ID of the process + /// The requested access rights + /// A safe handle to the process + /// Thrown when the process handle could not be obtained + private static SafeFileHandle OpenProcessHandle(uint processId, PROCESS_ACCESS_RIGHTS rights) + { + var hProcess = NativeMethods.OpenProcess_SafeHandle(rights, false, processId); + if (hProcess.IsInvalid) + { + int error = Marshal.GetLastWin32Error(); + throw new Win32Exception(error, $"Unable to open process {processId}"); + } + + return hProcess; + } + + /// + /// Creates an instance of the appropriate subclass of ConsentUIData by reading the type from the data structure + /// + /// A pointer to the data structure + /// The expected size of the data structure + /// A ConsentUIData object + /// Thrown when the size of the expected data structure does not match the size reported in the structure itself + private static ConsentUIData CreateInstance(IntPtr pData, int expectedSize) + { + var sizeReportedInStructure = Marshal.ReadInt32(pData, 0); + + if (sizeReportedInStructure != expectedSize) + { + throw new InvalidDataException($"The expected size {expectedSize} did not match the size reported by the structure {sizeReportedInStructure}"); + } + + var type = (ConsentUIType)Marshal.ReadInt32(pData, 4); + + return type switch + { + ConsentUIType.Exe => new ConsentUIDataExe(pData, sizeReportedInStructure), + ConsentUIType.Msi => new ConsentUIDataMsi(pData, sizeReportedInStructure), + ConsentUIType.Com => new ConsentUIDataCom(pData, sizeReportedInStructure), + ConsentUIType.Msix => new ConsentUIDataMsix(pData, sizeReportedInStructure), + ConsentUIType.ActiveX => new ConsentUIDataActiveX(pData, sizeReportedInStructure), + ConsentUIType.CredCollect => new ConsentUIDataCredCollect(pData, sizeReportedInStructure), + _ => throw new InvalidDataException("The ConsentUI data structure was for an unknown type"), + }; + } + + /// + /// Duplicates a handle passed in from the AppInfo service + /// + /// The handle to duplicate + /// A duplicated reference to the handle + /// Thrown when the handle could not be duplicated + protected private static SafeHandle DuplicateHandleInternal(IntPtr handle) + { + commandLineArgs ??= GetConsentUICommandLineArgs(); + + var processHandle = OpenProcessHandle(commandLineArgs.AppInfoProcessId, PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE); + SafeFileHandle t = new(handle, false); + + if (!NativeMethods.DuplicateHandle(processHandle, t, Process.GetCurrentProcess().SafeHandle, out var duplicatedToken, 0, false, DUPLICATE_HANDLE_OPTIONS.DUPLICATE_SAME_ACCESS)) + { + throw new Win32Exception(Marshal.GetLastWin32Error(), "Unable to duplicate the handle"); + } + + return duplicatedToken; + } + } +} \ No newline at end of file diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs new file mode 100644 index 0000000..c19dc3a --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataActiveX.cs @@ -0,0 +1,19 @@ +using System; + +namespace Lithnet.CredentialProvider +{ + /// + /// Represents the data structure passed to consent UI when a user is trying to install an ActiveX control + /// + /// This data structure is currently unknown and only the common header values are present + public class ConsentUIDataActiveX : ConsentUIData + { + internal ConsentUIDataActiveX(IntPtr pData, int expectedSize) : base(pData, expectedSize) + { + if (this.header.Type != ConsentUIType.ActiveX) + { + throw new InvalidOperationException("The data structure is not of type ActiveX"); + } + } + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs new file mode 100644 index 0000000..c596f0e --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCom.cs @@ -0,0 +1,53 @@ +using System; +using System.Runtime.InteropServices; +using Lithnet.CredentialProvider.Interop; + +namespace Lithnet.CredentialProvider +{ + /// + /// Represents the data structure passed to consent UI when a user is trying to elevate a COM component. This is typically seen when a user presses a 'shield' icon in something like the file security dialog to elevate permissions. + /// + public class ConsentUIDataCom : ConsentUIData + { + /// + /// The path to the COM component that is requesting elevation + /// + public string ComComponentPath { get; } + + /// + /// The resource path to the image to display in consent UI + /// + public string ImageResourcePath { get; } + + /// + /// The path to the process that is hosting the COM component + /// + public string ProcessPath { get; } + + /// + /// A user friendly description of the type of operation that will be performed by the elevation + /// + public string OperationType { get; } + + /// + /// The CLSID of the COM component + /// + public Guid ClsId { get; } + + internal ConsentUIDataCom(IntPtr pData, int expectedSize) : base(pData, expectedSize) + { + if (this.header.Type != ConsentUIType.Com) + { + throw new InvalidOperationException("The data structure is not of type COM"); + } + + var s = Marshal.PtrToStructure(pData); + + this.ComComponentPath = this.GetStringValueIfValid(pData, (int)s.oComComponentPath); + this.ImageResourcePath = this.GetStringValueIfValid(pData, (int)s.oImageResourcePath); + this.ProcessPath = this.GetStringValueIfValid(pData, (int)s.oProcessPath); + this.OperationType = this.GetStringValueIfValid(pData, (int)s.oOperationType); + this.ClsId = s.Clsid; + } + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs new file mode 100644 index 0000000..a8554b6 --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataCredCollect.cs @@ -0,0 +1,19 @@ +using System; + +namespace Lithnet.CredentialProvider +{ + /// + /// Represents the data structure passed to consent UI in a yet unknown scenario + /// + /// This data structure is currently unknown and only the common header values are present + public class ConsentUIDataCredCollect : ConsentUIData + { + internal ConsentUIDataCredCollect(IntPtr pData, int expectedSize) : base(pData, expectedSize) + { + if (this.header.Type != ConsentUIType.CredCollect) + { + throw new InvalidOperationException("The data structure is not of type CredCollect"); + } + } + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs new file mode 100644 index 0000000..4d74baa --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs @@ -0,0 +1,59 @@ +using System; +using System.Runtime.InteropServices; +using Lithnet.CredentialProvider.Interop; + +namespace Lithnet.CredentialProvider +{ + /// + /// Represents the data structure passed to consent UI when a user is trying to elevate an executable + /// + public class ConsentUIDataExe : ConsentUIData + { + private IntPtr hFile; + + /// + /// The path to the process that the user has requested to be launched as an administrator + /// + public string ExecutablePath { get; } + + /// + /// A currently unknown value. In most cases it seems to be the same as + /// + public string Unknown1 { get; } + + /// + /// The full command line, including arguments that will be used to launch the executable + /// + public string CommandLine { get; } + + /// + /// A currently unknown parameter + /// + public string Unknown2 { get; } + + /// + /// Gets a handle to the executable that the user has requested to be launched as an administrator + /// + /// A handle to the executable + public SafeHandle GetExecutableHandle() + { + return DuplicateHandleInternal(this.hFile); + } + + internal ConsentUIDataExe(IntPtr pData, int expectedSize) : base(pData, expectedSize) + { + if (this.header.Type != ConsentUIType.Exe) + { + throw new InvalidOperationException("The data structure is not of type EXE"); + } + + var s = Marshal.PtrToStructure(pData); + + this.hFile = s.hFile; + this.ExecutablePath = this.GetStringValueIfValid(pData, (int)s.oExecutablePath1); + this.Unknown1 = this.GetStringValueIfValid(pData, (int)s.oExecutablePath2); + this.CommandLine = this.GetStringValueIfValid(pData, (int)s.oCommandLine); + this.Unknown2 = this.GetStringValueIfValid(pData, (int)s.oUnknown0); + } + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs new file mode 100644 index 0000000..930412b --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs @@ -0,0 +1,71 @@ +using System; +using System.Runtime.InteropServices; +using Lithnet.CredentialProvider.Interop; + +namespace Lithnet.CredentialProvider +{ + /// + /// Represents the data structure passed to consent UI when a user is trying to elevate an MSI installer + /// + public class ConsentUIDataMsi : ConsentUIData + { + /// + /// The name of the product being installed + /// + public string ProductName { get; } + + /// + /// The version of the product being installed + /// + public string Version { get; } + + /// + /// The locale of the product being installed + /// + public string Locale { get; } + + /// + /// The publisher of the product being installed + /// + public string Publisher { get; } + + /// + /// The path to the MSI installer + /// + public string ExecutionPath { get; } + + /// + /// The path to the original MSI file launched by the user + /// + public string OriginalMsi { get; } + + /// + /// A currently unknown parameter + /// + public string Unknown1 { get; } + + /// + /// A currently unknown parameter + /// + public string Unknown2 { get; } + + internal ConsentUIDataMsi(IntPtr pData, int expectedSize) : base(pData, expectedSize) + { + if (this.header.Type != ConsentUIType.Msi) + { + throw new InvalidOperationException("The data structure is not of type MSI"); + } + + var s = Marshal.PtrToStructure(pData); + + this.ProductName = this.GetStringValueIfValid(pData, (int)s.oProductName); + this.Version = this.GetStringValueIfValid(pData, (int)s.oVersion); + this.Locale = this.GetStringValueIfValid(pData, (int)s.oLocale); + this.Publisher = this.GetStringValueIfValid(pData, (int)s.oPublisher); + this.ExecutionPath = this.GetStringValueIfValid(pData, (int)s.oExecutionPath); + this.OriginalMsi = this.GetStringValueIfValid(pData, (int)s.oOriginalMsi); + this.Unknown1 = this.GetStringValueIfValid(pData, (int)s.oUnknown1); + this.Unknown2 = this.GetStringValueIfValid(pData, (int)s.oUnknown2); + } + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs new file mode 100644 index 0000000..cbced84 --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs @@ -0,0 +1,46 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + /// + /// Represents the data structure passed to consent UI when a user is trying to elevate an MSIX package + /// + public class ConsentUIDataMsix : ConsentUIData + { + /// + /// The path to the package being installed + /// + public string ExecutablePath { get; } + + /// + /// The name of the package being installed + /// + public string PackageName { get; } + + /// + /// The full command line, including any arguments used to launch the installer + /// + public string CommandLine { get; } + + /// + /// A currently unknown parameter + /// + public string OtherName { get; } + + internal ConsentUIDataMsix(IntPtr pData, int expectedSize) : base(pData, expectedSize) + { + if (this.header.Type != ConsentUIType.Msix) + { + throw new InvalidOperationException("The data structure is not of type MSIX"); + } + + var s = Marshal.PtrToStructure(pData); + + this.ExecutablePath = this.GetStringValueIfValid(pData, (int)s.oExecutablePath); + this.PackageName = this.GetStringValueIfValid(pData, (int)s.oPackageName); + this.CommandLine = this.GetStringValueIfValid(pData, (int)s.oCommandLine); + this.OtherName = this.GetStringValueIfValid(pData, (int)s.oOtherName); + } + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs b/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs new file mode 100644 index 0000000..39770de --- /dev/null +++ b/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs @@ -0,0 +1,10 @@ +namespace Lithnet.CredentialProvider +{ + public enum ElevationType + { + Unknown1 = 0, + Unknown2 = 1, + Consent = 2, + Credentials = 3 + } +} \ No newline at end of file diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-com-x64.dat new file mode 100644 index 0000000000000000000000000000000000000000..289236b56be59c76251b0c6995875fcbff9154dc GIT binary patch literal 272 zcmWe&WMBXy24)}zfkVukVA=sHzyjofn0r7BAaDXI-hPvbq2g_2)qEho04&PD&9nrV@INcaB8FCm>859`&fqGLJiWm|ZN`PYd40%8u&J0!z zF%02AejY;#P^_Gx7|1VXs088?pz2%(V+JFjNI8lMJ+N#(*sOG*Dm|dy93TV%>?XQ0 XQ~+(y0oqUmc7q;+D?&m0A;-YL0%S7*@ex%91}C88=AVX41t0R+e1KwEK)efx_W*He z=mo3rjsTBI<$^n+Z})M!F=R62Fr+dlF!(bR0O=xzM1~TeSUy7@P_;9I6+;X|IFO&m zkOCAdXD9~piy10`xCE#=m%*692q;pHqCyWWn-4ZC9jHnVs5b`)K>)jnt_&4G+jD?6 Q6oK8K$Kc8k0mKXp0N&~>xc~qF literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x64.dat new file mode 100644 index 0000000000000000000000000000000000000000..17c483ff34ebfcac806f69c01fe182c5529677e9 GIT binary patch literal 490 zcmaFG$N&b+5Q;&A5hCCM6<`C3gP40*APRzj)Rvn}3_l*PubK~Jw?NeuK%^O-KCO$8H#`^ zGk~gcfG~!kjKPpWkHG-QDuBxu!{zmW>MDSkfngx*R2l$Vl^7Hl92tTb+<~EFh{QGm G2R#7EEKm3V literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-exe-x86.dat new file mode 100644 index 0000000000000000000000000000000000000000..0ef9b15ff37778d8b345137a1472c9a045e44bca GIT binary patch literal 446 zcmdnT$N&b+Kr)M!jp2nf0|Oh7%>=|pR2dk8fb^!HhD--O^svgPGcdRS`8$AE2dHlk z5ZeG1J2O}@#4v<2WHRJ2q%h<&lrt0q`N0g8K)Qq>l_8hGn864`MF3c3Dnk)aWd=}H z4iLsLlrb1G=rI@oSp{(UVz|5>P+bKO4~(5k1K?971_cI3U@W);L&*?{Z3GMh1_l7O CIZmMf literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x64.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x64.dat new file mode 100644 index 0000000000000000000000000000000000000000..4087acf7dbc0b1f3c8c9dbd958adf0a0b71fa180 GIT binary patch literal 398 zcmYk1F-`(O7==G}@g5{LT0mCTxY5D_ZMLH^8nVqAjoAbTvJg}bVYmTDAoiACLTd{S zKuN)yah7$GnfJf>-+TYedjkR>)V6CJ9y;7K9dID9y-&}3(tmsh+^tvh<)6fz3txYE z{PFnfaqIC?{{O4;RhZCbK*5+6U1B;!oTxtHl`~FNJ@TObRC|QsSYfr!sw3w?u1Nk^ z{gfwZOL_a`%ovKzbbrMK8MjI@${g{+L_8InC_k5OsA;9{?nzu$%($5+5i9m literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msi-x86.dat new file mode 100644 index 0000000000000000000000000000000000000000..9c63366f4fa75a775f597d21357a8f2ecab5525a GIT binary patch literal 298 zcmYL@u?_)I5Jis_@f#Ex30XF5DADK?L?YX4vLc(TB?yU9?K2cwy+Ywz{D4a5&KA6v zJ8$ORnLDqV0|&?p_d=0t84Ajcdsd(){q|`%kN4SV#zG>TgtKrFUV9Y--!06Q>V(q-$1U+;s&k%+2hXf1A-? g_K(fURfzbnXN!CxetrKx-|b)YtS#RwF8&UD0z~_GZ~y=R literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-msix-x86.dat new file mode 100644 index 0000000000000000000000000000000000000000..b1e69c2e880fce0525fdeada36306bb1cbb4ce5b GIT binary patch literal 692 zcmds#y-Pw-7{-4pO0%0yCO8EbKcFNU3L$7nf&?1^@vDny8Qs9^|K$`74gEyqu3^8nQq}OUBzF?1`rNC_`R+{i>$lKMT1t{rwcIw3dX^95gxM zj%%Ko5xVwxpvVPdri_TJb@U$@TAv%A^W;qOSLO%neUY_$UI@(HaLKKDJ!=!!aJ@UE zEoY`?m#VxP6|Jt<&^s0@R$`+gu`AB)K4C#$*3h``Oma!zNX__HkI_aykP~ZA+S#*} Wd?EgP|J(2OKQs?E?-l&~9ry+&HFu2w literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-winsec-x86.dat b/src/Lithnet.CredentialProvider/ConsentUI/Samples/dump-consent-winsec-x86.dat new file mode 100644 index 0000000000000000000000000000000000000000..311afd6fc1222df83e9605acdba8667c3dfa233a GIT binary patch literal 384 zcmZo*WMBXy24*1EU{zxHpuoVu0%S7*@ex%91}C88wx5Pf6F!~c@&StF0r4>))&h#_ zAO7Sj_I8R-%jD~NKdo4$!x=Jx@+l1Y4CM^P3`%V5l41XO{p(*r1%$dCh6lfmG~kPhVK0oi&$yK;b#fx&@BChIWh eG8i%#05LAN2b1J>4`A4Ue5wcZK?M*qFaQ8igGoUE literal 0 HcmV?d00001 diff --git a/src/Lithnet.CredentialProvider/CredentialProviderBase.cs b/src/Lithnet.CredentialProvider/CredentialProviderBase.cs index 86cda32..605c31b 100644 --- a/src/Lithnet.CredentialProvider/CredentialProviderBase.cs +++ b/src/Lithnet.CredentialProvider/CredentialProviderBase.cs @@ -51,6 +51,16 @@ namespace Lithnet.CredentialProvider /// public IReadOnlyList Tiles { get; private set; } + /// + /// Gets a value that indicates if this credential provider is loaded by Logon UI + /// + public bool IsLogonUI => this.UsageScenario == UsageScenario.Logon; + + /// + /// Gets a value that indicates if the credential provider is loaded by Consent UI (eg UAC prompt) + /// + public bool IsConsentUI => this.UsageScenario == UsageScenario.CredUI && ConsentUIData.IsConsentUIParent(); + /// /// Provides access to the serialized input data provided by CredUI /// diff --git a/src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs b/src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs new file mode 100644 index 0000000..91446e7 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Enums/ConsentUIType.cs @@ -0,0 +1,12 @@ +namespace Lithnet.CredentialProvider +{ + public enum ConsentUIType + { + Exe = 0, + Com = 1, + Msi = 2, + ActiveX = 3, + CredCollect = 4, + Msix = 5 + } +} diff --git a/src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs b/src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs new file mode 100644 index 0000000..7650635 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Interop/SafeHGlobalHandle.cs @@ -0,0 +1,64 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + internal sealed class SafeHGlobalHandle : IDisposable + { + /// + /// Unmanaged pointer wrapped by this object + /// + IntPtr pointer; + + SafeHGlobalHandle() + { + this.pointer = IntPtr.Zero; + } + + SafeHGlobalHandle(IntPtr handle) + { + this.pointer = handle; + } + + ~SafeHGlobalHandle() + { + this.Dispose(); + } + + public static SafeHGlobalHandle InvalidHandle => new SafeHGlobalHandle(IntPtr.Zero); + + /// + /// Operator to obtain the unmanaged pointer wrapped by the object. Note + /// that the returned pointer is only valid for the lifetime of this + /// object. + /// + /// Unmanaged pointer wrapped by the object + public IntPtr ToIntPtr() + { + return this.pointer; + } + + public void Dispose() + { + if (this.pointer != IntPtr.Zero) + { + Marshal.FreeHGlobal(this.pointer); + this.pointer = IntPtr.Zero; + } + + GC.SuppressFinalize(this); + } + + public static SafeHGlobalHandle AllocHGlobal(int cb) + { + if (cb < 0) + { + throw new ArgumentOutOfRangeException(nameof(cb), "The value of this argument must be non-negative"); + } + + SafeHGlobalHandle result = new SafeHGlobalHandle(); + result.pointer = Marshal.AllocHGlobal(cb); + return result; + } + } +} \ No newline at end of file diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs new file mode 100644 index 0000000..24a35a3 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureCom.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct ConsentUIStructureCom + { + public ConsentUIStructureHeader Header; + + // 64 + + public IntPtr oOperationType; // 8 + public IntPtr oComComponentPath; // 8 + + // 64 + 16 == 80 + + public IntPtr oImageResourcePath; // 8 + public IntPtr oProcessPath; // 8 + + // 80 + 16 == 96 + + public Guid Clsid; // 16 + + // 96 + 16 == 112 + } +} diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs new file mode 100644 index 0000000..ed69888 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureExe.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct ConsentUIStructureExe + { + public ConsentUIStructureHeader Header; + + // 64 + + public IntPtr hFile; // 8 + public IntPtr oExecutablePath1; // 8 + + // 64 + 16 = 80 + + public IntPtr oExecutablePath2; // 8 + public IntPtr oCommandLine; // 8 + + // 80 + 16 == 96 + + public IntPtr oUnknown0; // 8 + public int ProcessId; // 4 + // Padding (x64) // 4 + + // 96 + 12 (+ 4) = 112 + } +} diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs new file mode 100644 index 0000000..e84af60 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct ConsentUIStructureHeader + { + public int Size; // 4 + public ConsentUIType Type; // 4 + public int PromptType; // 4 + // padding on x64 - 4 + + // 16 + + public IntPtr hWnd; // 8 + public IntPtr hToken; // 8 + + // 32 + + public ElevationType elevationType; // 4 + public int sessionId; // 4 + public IntPtr hMutex; // 8 + + // 48+ + + public int unknownFlags1; // 4 + public int unknownFlags2; // 4 + public IntPtr pReturnAddress; // 8 + + // 64 + } +} diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs new file mode 100644 index 0000000..85b7ab3 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs @@ -0,0 +1,38 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct ConsentUIStructureMsi + { + public ConsentUIStructureHeader Header; + + // 64 + + public IntPtr hUnknown1; // 8 + public IntPtr oProductName; // 8 + + // 64 + 16 == 80 + + public IntPtr oVersion; // 8 + public IntPtr oLocale; // 8 + + // 80 + 16 == 96 + + public IntPtr oPublisher; // 8 + public IntPtr oExecutionPath; // 8 + + // 96 + 16 == 112 + + public IntPtr oOriginalMsi; // 8 + public IntPtr hUnknown2; // 8 + + // 96 + 16 == 128 + + public IntPtr oUnknown1; // 8 + public IntPtr oUnknown2; // 8 + + // 128 + 16 == 144 + } +} diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs new file mode 100644 index 0000000..456624a --- /dev/null +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsix.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; + +namespace Lithnet.CredentialProvider.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct ConsentUIStructureMsix + { + public ConsentUIStructureHeader Header; + + // 64 + + public IntPtr oExecutablePath; // 8 + public IntPtr oCommandLine; // 8 + + // 64 + 16 == 80 + + public IntPtr oPackageName; // 8 + public IntPtr oOtherName; // 8 + + // 80 + 16 == 96 + + public int ProcessId; // 4 + // Padding (x64) // 4 + + // 96 + 4 + 4 = 104 + } +} diff --git a/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj b/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj index b6161f4..34d41dc 100644 --- a/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj +++ b/src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj @@ -17,8 +17,8 @@ Lithnet Copyright 2023 Lithnet Pty Ltd Lithnet Windows Credential Provider - 1.0.0 - alpha1 + 1.1.0 + beta1 Lithnet true true @@ -43,6 +43,9 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all + diff --git a/src/Lithnet.CredentialProvider/NativeMethods.json b/src/Lithnet.CredentialProvider/NativeMethods.json new file mode 100644 index 0000000..6d04a57 --- /dev/null +++ b/src/Lithnet.CredentialProvider/NativeMethods.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json" +} \ No newline at end of file diff --git a/src/Lithnet.CredentialProvider/NativeMethods.txt b/src/Lithnet.CredentialProvider/NativeMethods.txt new file mode 100644 index 0000000..9b8730e --- /dev/null +++ b/src/Lithnet.CredentialProvider/NativeMethods.txt @@ -0,0 +1,7 @@ +ReadProcessMemory +OpenProcess +WTSQueryUserToken +GetTokenInformation +CloseHandle +DuplicateHandle +GetFileSizeEx \ No newline at end of file diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs index b54b624..df61b4d 100644 --- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs +++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/InternalLogger.cs @@ -1,14 +1,22 @@ -using Microsoft.Extensions.Logging; +using System; +using Microsoft.Extensions.Logging; using NLog; using NLog.Extensions.Logging; namespace Lithnet.CredentialProvider.Samples { - internal static class InternalLogger + internal class InternalLoggerFactory : ICredentialProviderLoggerFactory { - internal static ILoggerFactory LoggerFactory { get; } + internal static ICredentialProviderLoggerFactory Instance { get; } - static InternalLogger() + private ILoggerFactory loggerFactory; + + public InternalLoggerFactory(ILoggerFactory factory) + { + this.loggerFactory = factory; + } + + static InternalLoggerFactory() { /* This sample uses NLog to capture trace events from the provider, but you can use any @@ -30,7 +38,53 @@ namespace Lithnet.CredentialProvider.Samples config.AddRule(NLog.LogLevel.Trace, NLog.LogLevel.Fatal, logconsole); LogManager.Configuration = config; - InternalLogger.LoggerFactory = new NLogLoggerFactory(new NLogLoggerProvider(new NLogProviderOptions() { ReplaceLoggerFactory = true }, LogManager.LogFactory)); + var loggerFactory = new NLogLoggerFactory(new NLogLoggerProvider(new NLogProviderOptions() { ReplaceLoggerFactory = true }, LogManager.LogFactory)); + InternalLoggerFactory.Instance = new InternalLoggerFactory(loggerFactory); + } + + public ICredentialProviderLogger CreateLogger(Type type) + { + return new CredentialProviderLogger(loggerFactory.CreateLogger(type)); + } + + public ICredentialProviderLogger CreateLogger() + { + return new CredentialProviderLogger(loggerFactory.CreateLogger()); + } + } + + public class CredentialProviderLogger : ICredentialProviderLogger + { + private readonly Microsoft.Extensions.Logging.ILogger logger; + + public CredentialProviderLogger(Microsoft.Extensions.Logging.ILogger logger) + { + this.logger = logger; + } + + public void LogError(Exception ex, string message) + { + this.logger.LogError(ex, message); + } + + public void LogError(string message) + { + this.logger.LogError(message); + } + + public void LogTrace(string message) + { + this.logger.LogTrace(message); + } + + public void LogInformation(string message) + { + this.logger.LogInformation(message); + } + + public void LogWarning(string message) + { + this.logger.LogWarning(message); } } } diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs index 8486d7f..8346cfb 100644 --- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs +++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderNet472x64.cs @@ -14,9 +14,9 @@ namespace Lithnet.CredentialProvider.Samples [Guid("4eb911fa-ca18-40ea-86df-19aff5d1da58")] public class TestCredentialProviderNet472x64 : CredentialProviderBase { - protected override ILoggerFactory GetLoggerFactory() + protected override ICredentialProviderLoggerFactory GetLoggerFactory() { - return InternalLogger.LoggerFactory; + return InternalLoggerFactory.Instance; } public override IEnumerable GetControls(UsageScenario cpus) diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs index 0720de8..adb26e8 100644 --- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs +++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x64/TestCredentialProviderTile.cs @@ -15,7 +15,7 @@ namespace Lithnet.CredentialProvider.Samples private SmallLabelControl CheckboxStateControl; private SmallLabelControl ComboboxStateControl; - private ILogger logger = InternalLogger.LoggerFactory.CreateLogger(); + private ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger(); public TestCredentialProviderTile(CredentialProviderBase credentialProvider) : base(credentialProvider) { diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs b/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs index c4ce35c..9bf6ad8 100644 --- a/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs +++ b/src/samples/Lithnet.CredentialProvider.Sample.net472.x86/TestCredentialProviderNet472x86.cs @@ -13,11 +13,11 @@ namespace Lithnet.CredentialProvider.Samples [Guid("c9055c88-03f9-4a12-8e33-1ee75826a4a6")] public class TestCredentialProviderNet472x86 : CredentialProviderBase { - private static readonly ILogger logger = InternalLogger.LoggerFactory.CreateLogger(); + private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger(); - protected override ILoggerFactory GetLoggerFactory() + protected override ICredentialProviderLoggerFactory GetLoggerFactory() { - return InternalLogger.LoggerFactory; + return InternalLoggerFactory.Instance; } public override IEnumerable GetControls(UsageScenario cpus) diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs index 774b7b5..1ef2d92 100644 --- a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs +++ b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x64/TestCredentialProviderNet60x64.cs @@ -13,11 +13,11 @@ namespace Lithnet.CredentialProvider.Samples [Guid("4cd12d80-9259-4f38-94dc-1828080ad9ff")] public class TestCredentialProviderNet60x64 : CredentialProviderBase { - private static readonly ILogger logger = InternalLogger.LoggerFactory.CreateLogger(); + private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger(); - protected override ILoggerFactory GetLoggerFactory() + protected override ICredentialProviderLoggerFactory GetLoggerFactory() { - return InternalLogger.LoggerFactory; + return InternalLoggerFactory.Instance; } public override IEnumerable GetControls(UsageScenario cpus) diff --git a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs index 035cec6..8b4d847 100644 --- a/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs +++ b/src/samples/Lithnet.CredentialProvider.Sample.net6.0.x86/TestCredentialProviderNet60x86.cs @@ -13,11 +13,11 @@ namespace Lithnet.CredentialProvider.Samples [Guid("90592593-f4d3-4f62-aa83-9cf1f7b590e0")] public class TestCredentialProviderNet60x86 : CredentialProviderBase { - private static readonly ILogger logger = InternalLogger.LoggerFactory.CreateLogger(); + private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger(); - protected override ILoggerFactory GetLoggerFactory() + protected override ICredentialProviderLoggerFactory GetLoggerFactory() { - return InternalLogger.LoggerFactory; + return InternalLoggerFactory.Instance; } public override IEnumerable GetControls(UsageScenario cpus) From 6dd9a185a9d0ebe9a774673ce090356757b8c56b Mon Sep 17 00:00:00 2001 From: Ryan Newington Date: Sat, 11 Nov 2023 16:09:36 +1100 Subject: [PATCH 2/6] incrememnt minor verison --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3ec47f2..cc6bb37 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -9,7 +9,7 @@ variables: - name: build.version.major value: 1 - name: build.version.minor - value: 0 + value: 1 - name: build.version.revision value: $[counter(format('{0}.{1}', variables['build.version.major'], variables['build.version.minor']), 0)] - name: build.version.suffix From 2499c4acbd96006cdf3b1b44ba9cd216be1c034a Mon Sep 17 00:00:00 2001 From: James Spencer Date: Sun, 17 Dec 2023 15:19:44 +1100 Subject: [PATCH 3/6] Tidy and document structures, document more fields from Consent/AppInfo --- .../ConsentUI/ConsentUIData.cs | 17 ++++- .../ConsentUI/ConsentUIDataExe.cs | 3 + .../ConsentUI/ConsentUIDataMsi.cs | 22 +++--- .../Enums/ConsentUIElevationType.cs | 24 +++++++ .../Enums/ConsentUIFlags.cs | 71 +++++++++++++++++++ .../Enums/ConsentUIMsiAction.cs | 11 +++ .../ConsentUIPromptType.cs} | 8 +-- .../Structs/ConsentUIStructureHeader.cs | 10 +-- .../Interop/Structs/ConsentUIStructureMsi.cs | 2 +- 9 files changed, 141 insertions(+), 27 deletions(-) create mode 100644 src/Lithnet.CredentialProvider/Enums/ConsentUIElevationType.cs create mode 100644 src/Lithnet.CredentialProvider/Enums/ConsentUIFlags.cs create mode 100644 src/Lithnet.CredentialProvider/Enums/ConsentUIMsiAction.cs rename src/Lithnet.CredentialProvider/{ConsentUI/ElevationType.cs => Enums/ConsentUIPromptType.cs} (53%) diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs index 6c56ae8..de92d28 100644 --- a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIData.cs @@ -34,14 +34,25 @@ namespace Lithnet.CredentialProvider /// /// Gets a value indicating the consent prompt type /// - public int PromptType => this.header.PromptType; + public ConsentUIPromptType PromptType => this.header.PromptType; /// /// Gets a handle to the Window that was responsible for invoking the ConsentUI prompt /// - public IntPtr HWnd => this.header.hWnd; + public IntPtr HWnd => this.header.hWindow; - public ElevationType ElevationType => this.header.elevationType; + /// + /// Gets the method that ConsentUI has been told to fetch approval. + /// In the case where a Credential Provider is initialised, this should always be `Credentials`. + /// + public ConsentUIElevationType ElevationType => this.header.ElevationType; + + /// + /// A series of flags that AppInfo passes to ConsentUI to signifiy actions that need to + /// take place on the UI side. + /// This includes specifics around the UI that should be presented & signature verification settings. + /// + public ConsentUIFlags Flags => this.header.Flags; /// /// Gets the ID of the session where the ConsentUI prompt was originally invoked diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs index 4d74baa..7a90c44 100644 --- a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataExe.cs @@ -9,6 +9,9 @@ namespace Lithnet.CredentialProvider /// public class ConsentUIDataExe : ConsentUIData { + /// + /// A file handle pointing to the EXE in question + /// private IntPtr hFile; /// diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs index 930412b..436999d 100644 --- a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsi.cs @@ -9,6 +9,11 @@ namespace Lithnet.CredentialProvider /// public class ConsentUIDataMsi : ConsentUIData { + /// + /// The action being performed, such as Install, Uninstall or Repair + /// + public ConsentUIMsiAction Action { get; set; } + /// /// The name of the product being installed /// @@ -39,16 +44,6 @@ namespace Lithnet.CredentialProvider /// public string OriginalMsi { get; } - /// - /// A currently unknown parameter - /// - public string Unknown1 { get; } - - /// - /// A currently unknown parameter - /// - public string Unknown2 { get; } - internal ConsentUIDataMsi(IntPtr pData, int expectedSize) : base(pData, expectedSize) { if (this.header.Type != ConsentUIType.Msi) @@ -58,14 +53,13 @@ namespace Lithnet.CredentialProvider var s = Marshal.PtrToStructure(pData); + this.Action = s.MsiAction; this.ProductName = this.GetStringValueIfValid(pData, (int)s.oProductName); this.Version = this.GetStringValueIfValid(pData, (int)s.oVersion); this.Locale = this.GetStringValueIfValid(pData, (int)s.oLocale); this.Publisher = this.GetStringValueIfValid(pData, (int)s.oPublisher); - this.ExecutionPath = this.GetStringValueIfValid(pData, (int)s.oExecutionPath); - this.OriginalMsi = this.GetStringValueIfValid(pData, (int)s.oOriginalMsi); - this.Unknown1 = this.GetStringValueIfValid(pData, (int)s.oUnknown1); - this.Unknown2 = this.GetStringValueIfValid(pData, (int)s.oUnknown2); + this.ExecutionPath = this.GetStringValueIfValid(pData, (int)s.oExecutionPath); + this.OriginalMsi = this.GetStringValueIfValid(pData, (int)s.oOriginalMsi); } } } diff --git a/src/Lithnet.CredentialProvider/Enums/ConsentUIElevationType.cs b/src/Lithnet.CredentialProvider/Enums/ConsentUIElevationType.cs new file mode 100644 index 0000000..b01808b --- /dev/null +++ b/src/Lithnet.CredentialProvider/Enums/ConsentUIElevationType.cs @@ -0,0 +1,24 @@ +namespace Lithnet.CredentialProvider +{ + public enum ConsentUIElevationType + { + Unknown = 0, + + /// + /// Automatic Admin Mode. + /// This seems to be an instance where UAC creates a local, secondary + /// account called '%username%_admin' which is used to elevate a process. + /// + AutomaticAdmin = 1, + + /// + /// Prompt the user for consent (i.e. Yes or No) + /// + Consent = 2, + + /// + /// Prompt the user for credentials + /// + Credentials = 3 + } +} \ No newline at end of file diff --git a/src/Lithnet.CredentialProvider/Enums/ConsentUIFlags.cs b/src/Lithnet.CredentialProvider/Enums/ConsentUIFlags.cs new file mode 100644 index 0000000..08bda47 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Enums/ConsentUIFlags.cs @@ -0,0 +1,71 @@ +using System; + +namespace Lithnet.CredentialProvider +{ + [Flags] + public enum ConsentUIFlags + { + SkipSignatureVerification = 0x01, + + /// + /// Indicates to ConsentUI that it needs to switch to the Secure Desktop + /// + SecureDesktop = 0x02, + + Unknown1 = 0x04, + Unknown2 = 0x08, + Unknown3 = 0x10, + + /// + /// This flag seems to cause ConsentUI to + /// skip all signature verification related code. + /// + SkipVerification = 0x20, + + /// + /// Indicates that the executable file is contained within a Windows directory. + /// As all the executables in System32, etc. are unsigned, ConsentUI + /// uses this to toggle catalog verification, if required. + /// + InWindowsDirectory = 0x40, + + /// + /// Seems to indicates to ConsentUI that automatic elevation should occur, + /// and that the executable is in a safe Windows location + /// + AutoElevationWindows = 0x80, + + /// + /// Like `AutoElevationWindows`, this seems indicates to ConsentUI that + /// automatic elevation should occur, but that further verification + /// inside ConsentUI should occur. + /// + AutoElevationOther = 0x100, + + Unknown4 = 0x200, + + /// + /// ConsentUI uses this flag to determine if it should pass + /// SIF_BASE_VERIFICATION | SIF_AUTHENTICODE_SIGNED to WTGetSignatureInfo + /// + PerformBaseVerification = 0x400, + + /// + /// Indicates that the publisher is untrusted - this is what seems to trigger + /// an AMSI scan (i.e., SmartScreen) + /// + UntrustedPublisher = 0x800, + + /// + /// This flag seems to cause ConsentUI to skip all elevation-related code and exit. + /// + BlockElevation = 0x1000, + + /// + /// Corresponds to `ConsentUIElevationType.AutomaticAdmin` + /// This seems to be an instance where UAC creates a local, secondary + /// account called '%username%_admin' which is used to elevate a process. + /// + AutomaticAdminMode = 0x2000 + } +} diff --git a/src/Lithnet.CredentialProvider/Enums/ConsentUIMsiAction.cs b/src/Lithnet.CredentialProvider/Enums/ConsentUIMsiAction.cs new file mode 100644 index 0000000..24e7ad1 --- /dev/null +++ b/src/Lithnet.CredentialProvider/Enums/ConsentUIMsiAction.cs @@ -0,0 +1,11 @@ +using System; + +namespace Lithnet.CredentialProvider +{ + public enum ConsentUIMsiAction : uint + { + Install = 0, + Uninstall = 1, + Update = 2 + } +} diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs b/src/Lithnet.CredentialProvider/Enums/ConsentUIPromptType.cs similarity index 53% rename from src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs rename to src/Lithnet.CredentialProvider/Enums/ConsentUIPromptType.cs index 39770de..eee927a 100644 --- a/src/Lithnet.CredentialProvider/ConsentUI/ElevationType.cs +++ b/src/Lithnet.CredentialProvider/Enums/ConsentUIPromptType.cs @@ -1,10 +1,10 @@ namespace Lithnet.CredentialProvider { - public enum ElevationType + public enum ConsentUIPromptType { - Unknown1 = 0, - Unknown2 = 1, + Unknown = 0, + AutomaticAdmin = 1, Consent = 2, Credentials = 3 } -} \ No newline at end of file +} diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs index e84af60..f016be1 100644 --- a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureHeader.cs @@ -8,24 +8,24 @@ namespace Lithnet.CredentialProvider.Interop { public int Size; // 4 public ConsentUIType Type; // 4 - public int PromptType; // 4 + public ConsentUIPromptType PromptType; // 4 // padding on x64 - 4 // 16 - public IntPtr hWnd; // 8 + public IntPtr hWindow; // 8 public IntPtr hToken; // 8 // 32 - public ElevationType elevationType; // 4 + public ConsentUIElevationType ElevationType; // 4 public int sessionId; // 4 public IntPtr hMutex; // 8 // 48+ - public int unknownFlags1; // 4 - public int unknownFlags2; // 4 + public ConsentUIFlags Flags; // 4 + public int unknown0; // 4 public IntPtr pReturnAddress; // 8 // 64 diff --git a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs index 85b7ab3..b7a04bc 100644 --- a/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs +++ b/src/Lithnet.CredentialProvider/Interop/Structs/ConsentUIStructureMsi.cs @@ -10,7 +10,7 @@ namespace Lithnet.CredentialProvider.Interop // 64 - public IntPtr hUnknown1; // 8 + public ConsentUIMsiAction MsiAction; // 8 public IntPtr oProductName; // 8 // 64 + 16 == 80 From 6f9fecb137d1ab1fa8135c0ab928e055ddceb9fa Mon Sep 17 00:00:00 2001 From: Ryan Newington Date: Sat, 6 Jan 2024 21:01:04 +1100 Subject: [PATCH 4/6] Fixes namespace for ConsentUIDataMsix --- src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs index cbced84..d93e4ee 100644 --- a/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs +++ b/src/Lithnet.CredentialProvider/ConsentUI/ConsentUIDataMsix.cs @@ -1,7 +1,8 @@ using System; using System.Runtime.InteropServices; +using Lithnet.CredentialProvider.Interop; -namespace Lithnet.CredentialProvider.Interop +namespace Lithnet.CredentialProvider { /// /// Represents the data structure passed to consent UI when a user is trying to elevate an MSIX package From 65506d9945a3b75526a422fbf305b41a7e855712 Mon Sep 17 00:00:00 2001 From: Ryan Newington Date: Sun, 7 Jan 2024 14:32:56 +1100 Subject: [PATCH 5/6] Adds OnLoad and OnUnload methods to the credential provider and tile --- .../CredentialProviderBase.ICredentialProvider.cs | 4 ++++ .../CredentialProviderBase.cs | 10 ++++++++++ .../CredentialTile.ICredentialProviderCredential.cs | 4 ++++ src/Lithnet.CredentialProvider/CredentialTile.cs | 11 +++++++++++ 4 files changed, 29 insertions(+) diff --git a/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs b/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs index 7d1e643..9aff688 100644 --- a/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs +++ b/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs @@ -60,6 +60,8 @@ namespace Lithnet.CredentialProvider Marshal.AddRef(intPtr); } + this.OnLoad(); + return HRESULT.S_OK; } catch (Exception ex) @@ -83,6 +85,8 @@ namespace Lithnet.CredentialProvider this.credentialProviderEventsAdviseContext = IntPtr.Zero; } + this.OnUnload(); + return HRESULT.S_OK; } catch (Exception ex) diff --git a/src/Lithnet.CredentialProvider/CredentialProviderBase.cs b/src/Lithnet.CredentialProvider/CredentialProviderBase.cs index 605c31b..3568fb1 100644 --- a/src/Lithnet.CredentialProvider/CredentialProviderBase.cs +++ b/src/Lithnet.CredentialProvider/CredentialProviderBase.cs @@ -174,6 +174,16 @@ namespace Lithnet.CredentialProvider /// The inbound serialized credential public virtual void OnSetSerialization(CredentialSerialization inboundSerialization) { } + /// + /// This method is first called when the credential provider is initialized. Override this method to perform any initialization tasks + /// + public virtual void OnLoad() { } + + /// + /// The method is called when the credential provider is being unloaded. Override this method to perform any cleanup tasks + /// + public virtual void OnUnload() { } + private void BuildControls() { if (this.Controls == null) diff --git a/src/Lithnet.CredentialProvider/CredentialTile.ICredentialProviderCredential.cs b/src/Lithnet.CredentialProvider/CredentialTile.ICredentialProviderCredential.cs index bf4a163..81b0362 100644 --- a/src/Lithnet.CredentialProvider/CredentialTile.ICredentialProviderCredential.cs +++ b/src/Lithnet.CredentialProvider/CredentialTile.ICredentialProviderCredential.cs @@ -22,6 +22,8 @@ namespace Lithnet.CredentialProvider Marshal.AddRef(intPtr); } + this.OnLoad(); + return HRESULT.S_OK; } catch (Exception ex) @@ -45,6 +47,8 @@ namespace Lithnet.CredentialProvider this.events = null; } + this.OnUnload(); + return HRESULT.S_OK; } catch (Exception ex) diff --git a/src/Lithnet.CredentialProvider/CredentialTile.cs b/src/Lithnet.CredentialProvider/CredentialTile.cs index 16facd2..9afcac7 100644 --- a/src/Lithnet.CredentialProvider/CredentialTile.cs +++ b/src/Lithnet.CredentialProvider/CredentialTile.cs @@ -135,6 +135,17 @@ namespace Lithnet.CredentialProvider this.events2.EndFieldUpdates(); } + + /// + /// This method is called when the credential provider tile is initialized. Override this method to perform any initialization tasks + /// + public virtual void OnLoad() { } + + /// + /// The method is called when the credential provider tile is being unloaded. Override this method to perform any cleanup tasks + /// + public virtual void OnUnload() { } + /// /// Called after the tile has been initialized. Override this method to perform post-initialization actions. /// From 0687d70bbab3f65c7929d1b4f72674aa5d2a5dd5 Mon Sep 17 00:00:00 2001 From: Ryan Newington Date: Fri, 23 Feb 2024 07:10:47 +1100 Subject: [PATCH 6/6] Fixes an issue where a default tile was incorrectly set --- .../CredentialProviderBase.ICredentialProvider.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs b/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs index 9aff688..52f27cf 100644 --- a/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs +++ b/src/Lithnet.CredentialProvider/CredentialProviderBase.ICredentialProvider.cs @@ -7,6 +7,8 @@ namespace Lithnet.CredentialProvider { public abstract partial class CredentialProviderBase : ICredentialProvider { + private const uint CREDENTIAL_PROVIDER_NO_DEFALT = 0xFFFFFFFF; + int ICredentialProvider.SetUsageScenario(UsageScenario cpus, CredUIWinFlags dwFlags) { try @@ -151,7 +153,7 @@ namespace Lithnet.CredentialProvider int ICredentialProvider.GetCredentialCount(out uint pdwCount, out uint pdwDefault, out int pbAutoLogonWithDefault) { pdwCount = 0; - pdwDefault = 0; + pdwDefault = CREDENTIAL_PROVIDER_NO_DEFALT; pbAutoLogonWithDefault = 0; try @@ -170,18 +172,18 @@ namespace Lithnet.CredentialProvider var autoLogonTile = this.Tiles.FirstOrDefault(t => t.IsAutoLogon); var defaultTile = this.Tiles.FirstOrDefault(t => t.IsDefault); - int defaultIndex = 0; + uint defaultIndex = CREDENTIAL_PROVIDER_NO_DEFALT; if (autoLogonTile != null) { - defaultIndex = this.tiles.IndexOf(autoLogonTile); + defaultIndex = (uint)this.tiles.IndexOf(autoLogonTile); } else if (defaultTile != null) { - defaultIndex = this.tiles.IndexOf(defaultTile); + defaultIndex = (uint)this.tiles.IndexOf(defaultTile); } pdwCount = (uint)this.Tiles.Count; - pdwDefault = (uint)defaultIndex; + pdwDefault = defaultIndex; pbAutoLogonWithDefault = autoLogonTile == null ? 0 : 1; this.logger.LogTrace($"GetCredentialCount returning pdwCount: {pdwCount}, pdwDefault: {pdwDefault}, pbAutoLogonWithDefault: {pbAutoLogonWithDefault}");