Adds initial support for reading parameters passed to consent UI

This commit is contained in:
Ryan Newington
2023-11-11 16:09:12 +11:00
parent d5b3b24870
commit 28de11ee97
35 changed files with 952 additions and 19 deletions
@@ -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; }
}
}
@@ -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
{
/// <summary>
/// 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 <see cref="ConsentUIData.ConsentUIType"/> property to determine the type of data structure and cast it to one of the concrete implementations.
/// </summary>
public abstract class ConsentUIData
{
private static bool? isConsentUI;
private static ConsentUICommandLineArgs commandLineArgs;
private protected ConsentUIStructureHeader header;
private readonly byte[] rawData;
/// <summary>
/// Gets a value indicating the type of ConsentUI data structure
/// </summary>
public ConsentUIType Type => this.header.Type;
/// <summary>
/// Gets a value indicating the consent prompt type
/// </summary>
public int PromptType => this.header.PromptType;
/// <summary>
/// Gets a handle to the Window that was responsible for invoking the ConsentUI prompt
/// </summary>
public IntPtr HWnd => this.header.hWnd;
public ElevationType ElevationType => this.header.elevationType;
/// <summary>
/// Gets the ID of the session where the ConsentUI prompt was originally invoked
/// </summary>
public int SessionId => this.header.sessionId;
private protected ConsentUIData(IntPtr pData, int expectedSize)
{
this.rawData = GetRawBytes(pData, expectedSize);
this.header = Marshal.PtrToStructure<ConsentUIStructureHeader>(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}");
}
}
/// <summary>
/// Gets the Windows Identity from the original caller requesting elevation
/// </summary>
/// <returns>A WindowsIdentity object that represents the user requesting elevation</returns>
/// <exception cref="Win32Exception">Thrown when the user's token could not be obtained from the session information</exception>
public WindowsIdentity GetWindowsIdentity()
{
var duplicatedToken = DuplicateHandleInternal(this.header.hToken);
return new WindowsIdentity(duplicatedToken.DangerousGetHandle());
}
/// <summary>
/// Gets a raw byte array representing the ConsentUI data structure
/// </summary>
/// <returns>A byte array</returns>
public byte[] GetRawData()
{
return this.rawData;
}
/// <summary>
/// Gets a string value that is packed at the end of the data structure if the offset is valid
/// </summary>
/// <param name="pData">A pointer to the start of the data structure</param>
/// <param name="offset">The position from the start of the data structure where the string starts</param>
/// <returns>A string containing all characters from the given offset up to the first null character found</returns>
private protected string GetStringValueIfValid(IntPtr pData, int offset)
{
if (offset > 0)
{
this.ThrowOnInvalidOffset(offset);
return Marshal.PtrToStringUni(IntPtr.Add(pData, offset));
}
return null;
}
/// <summary>
/// Throws an exception if the given offset is greater than the size of the data structure
/// </summary>
/// <param name="value">The value of the offset</param>
/// <exception cref="InvalidDataException">Thrown when the value of the pointer is greater than the expected data size</exception>
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}");
}
}
/// <summary>
/// Creates a ConsentUIData object from a previously-obtained raw byte representation
/// </summary>
/// <param name="consentUIDataStructure">The raw bytes of a supported ConsentUI data structure</param>
/// <returns>A ConsentUIData object</returns>
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);
}
/// <summary>
/// Gets the data structure passed to the Consent UI process
/// </summary>
/// <returns>A ConsentUIData object</returns>
public static ConsentUIData GetConsentUIData()
{
var pData = GetConsentUIData(out int structSize);
return ConsentUIData.CreateInstance(pData.ToIntPtr(), structSize);
}
/// <summary>
/// Gets a value indicating whether the current process is consent.exe, indicating that the provider is running inside an elevated UAC prompt
/// </summary>
/// <returns></returns>
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;
}
/// <summary>
/// Gets the raw bytes of the ConsentUI data structure
/// </summary>
/// <returns>A byte array</returns>
public static byte[] GetConsentUIDataRawBytes()
{
var pData = GetConsentUIData(out int structSize);
return GetRawBytes(pData.ToIntPtr(), structSize);
}
/// <summary>
/// Parses the command line of the consent.exe process to retrieve the data structure passed to it
/// </summary>
/// <param name="size">Returns the size of the data structure as reported in the command line arguments</param>
/// <returns>A pointer to the newly created copy of the data structure</returns>
/// <exception cref="InvalidOperationException">Thrown when either consent.exe is not the parent process</exception>
/// <exception cref="ArgumentException">Throw when the arguments passed to consent.exe are invalid</exception>
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);
}
/// <summary>
/// Extracts the command line arguments passed to the consent.exe process
/// </summary>
/// <returns>A ConsentUICommandLineArgs object containing the arguments parsed from the command line</returns>
/// <exception cref="ArgumentException">Thrown when the arguments passed to consent.exe cannot be parsed or are of the incorrect number</exception>
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,
};
}
/// <summary>
/// Copies the memory from a raw pointer into a managed byte array
/// </summary>
/// <param name="pData">The pointer where the data copy must start</param>
/// <param name="size">The number of bytes to copy</param>
/// <returns>A copy of the raw memory returned as a managed byte array</returns>
private static byte[] GetRawBytes(IntPtr pData, int size)
{
byte[] dataForExport = new byte[size];
Marshal.Copy(pData, dataForExport, 0, size);
return dataForExport;
}
/// <summary>
/// Reads the memory from a specified process
/// </summary>
/// <param name="processId">The ID of the process</param>
/// <param name="address">The memory address to read</param>
/// <param name="size">The size of the data at the specified memory address</param>
/// <returns>A handle to a copy of the process memory</returns>
/// <exception cref="Win32Exception">Thrown when the process could not be opened or the memory address could not be read</exception>
/// <exception cref="InvalidDataException">Thrown when the size of the copied structure did not equal the expected size as passed to the method</exception>
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;
}
/// <summary>
/// Opens a native handle to a process
/// </summary>
/// <param name="processId">The ID of the process</param>
/// <param name="rights">The requested access rights</param>
/// <returns>A safe handle to the process</returns>
/// <exception cref="Win32Exception">Thrown when the process handle could not be obtained</exception>
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;
}
/// <summary>
/// Creates an instance of the appropriate subclass of ConsentUIData by reading the type from the data structure
/// </summary>
/// <param name="pData">A pointer to the data structure</param>
/// <param name="expectedSize">The expected size of the data structure</param>
/// <returns>A ConsentUIData object</returns>
/// <exception cref="InvalidDataException">Thrown when the size of the expected data structure does not match the size reported in the structure itself</exception>
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"),
};
}
/// <summary>
/// Duplicates a handle passed in from the AppInfo service
/// </summary>
/// <param name="handle">The handle to duplicate</param>
/// <returns>A duplicated reference to the handle</returns>
/// <exception cref="Win32Exception">Thrown when the handle could not be duplicated</exception>
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;
}
}
}
@@ -0,0 +1,19 @@
using System;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents the data structure passed to consent UI when a user is trying to install an ActiveX control
/// </summary>
/// <remarks>This data structure is currently unknown and only the common header values are present</remarks>
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");
}
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// 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.
/// </summary>
public class ConsentUIDataCom : ConsentUIData
{
/// <summary>
/// The path to the COM component that is requesting elevation
/// </summary>
public string ComComponentPath { get; }
/// <summary>
/// The resource path to the image to display in consent UI
/// </summary>
public string ImageResourcePath { get; }
/// <summary>
/// The path to the process that is hosting the COM component
/// </summary>
public string ProcessPath { get; }
/// <summary>
/// A user friendly description of the type of operation that will be performed by the elevation
/// </summary>
public string OperationType { get; }
/// <summary>
/// The CLSID of the COM component
/// </summary>
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<ConsentUIStructureCom>(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;
}
}
}
@@ -0,0 +1,19 @@
using System;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents the data structure passed to consent UI in a yet unknown scenario
/// </summary>
/// <remarks>This data structure is currently unknown and only the common header values are present</remarks>
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");
}
}
}
}
@@ -0,0 +1,59 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents the data structure passed to consent UI when a user is trying to elevate an executable
/// </summary>
public class ConsentUIDataExe : ConsentUIData
{
private IntPtr hFile;
/// <summary>
/// The path to the process that the user has requested to be launched as an administrator
/// </summary>
public string ExecutablePath { get; }
/// <summary>
/// A currently unknown value. In most cases it seems to be the same as <see cref="ExecutablePath"/>
/// </summary>
public string Unknown1 { get; }
/// <summary>
/// The full command line, including arguments that will be used to launch the executable
/// </summary>
public string CommandLine { get; }
/// <summary>
/// A currently unknown parameter
/// </summary>
public string Unknown2 { get; }
/// <summary>
/// Gets a handle to the executable that the user has requested to be launched as an administrator
/// </summary>
/// <returns>A handle to the executable</returns>
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<ConsentUIStructureExe>(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);
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Runtime.InteropServices;
using Lithnet.CredentialProvider.Interop;
namespace Lithnet.CredentialProvider
{
/// <summary>
/// Represents the data structure passed to consent UI when a user is trying to elevate an MSI installer
/// </summary>
public class ConsentUIDataMsi : ConsentUIData
{
/// <summary>
/// The name of the product being installed
/// </summary>
public string ProductName { get; }
/// <summary>
/// The version of the product being installed
/// </summary>
public string Version { get; }
/// <summary>
/// The locale of the product being installed
/// </summary>
public string Locale { get; }
/// <summary>
/// The publisher of the product being installed
/// </summary>
public string Publisher { get; }
/// <summary>
/// The path to the MSI installer
/// </summary>
public string ExecutionPath { get; }
/// <summary>
/// The path to the original MSI file launched by the user
/// </summary>
public string OriginalMsi { get; }
/// <summary>
/// A currently unknown parameter
/// </summary>
public string Unknown1 { get; }
/// <summary>
/// A currently unknown parameter
/// </summary>
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<ConsentUIStructureMsi>(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);
}
}
}
@@ -0,0 +1,46 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
/// <summary>
/// Represents the data structure passed to consent UI when a user is trying to elevate an MSIX package
/// </summary>
public class ConsentUIDataMsix : ConsentUIData
{
/// <summary>
/// The path to the package being installed
/// </summary>
public string ExecutablePath { get; }
/// <summary>
/// The name of the package being installed
/// </summary>
public string PackageName { get; }
/// <summary>
/// The full command line, including any arguments used to launch the installer
/// </summary>
public string CommandLine { get; }
/// <summary>
/// A currently unknown parameter
/// </summary>
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<ConsentUIStructureMsix>(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);
}
}
}
@@ -0,0 +1,10 @@
namespace Lithnet.CredentialProvider
{
public enum ElevationType
{
Unknown1 = 0,
Unknown2 = 1,
Consent = 2,
Credentials = 3
}
}
@@ -51,6 +51,16 @@ namespace Lithnet.CredentialProvider
/// </summary>
public IReadOnlyList<CredentialTile> Tiles { get; private set; }
/// <summary>
/// Gets a value that indicates if this credential provider is loaded by Logon UI
/// </summary>
public bool IsLogonUI => this.UsageScenario == UsageScenario.Logon;
/// <summary>
/// Gets a value that indicates if the credential provider is loaded by Consent UI (eg UAC prompt)
/// </summary>
public bool IsConsentUI => this.UsageScenario == UsageScenario.CredUI && ConsentUIData.IsConsentUIParent();
/// <summary>
/// Provides access to the serialized input data provided by CredUI
/// </summary>
@@ -0,0 +1,12 @@
namespace Lithnet.CredentialProvider
{
public enum ConsentUIType
{
Exe = 0,
Com = 1,
Msi = 2,
ActiveX = 3,
CredCollect = 4,
Msix = 5
}
}
@@ -0,0 +1,64 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
internal sealed class SafeHGlobalHandle : IDisposable
{
/// <summary>
/// Unmanaged pointer wrapped by this object
/// </summary>
IntPtr pointer;
SafeHGlobalHandle()
{
this.pointer = IntPtr.Zero;
}
SafeHGlobalHandle(IntPtr handle)
{
this.pointer = handle;
}
~SafeHGlobalHandle()
{
this.Dispose();
}
public static SafeHGlobalHandle InvalidHandle => new SafeHGlobalHandle(IntPtr.Zero);
/// <summary>
/// Operator to obtain the unmanaged pointer wrapped by the object. Note
/// that the returned pointer is only valid for the lifetime of this
/// object.
/// </summary>
/// <returns>Unmanaged pointer wrapped by the object</returns>
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;
}
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
}
@@ -17,8 +17,8 @@
<Company>Lithnet</Company>
<Copyright>Copyright 2023 Lithnet Pty Ltd</Copyright>
<ProductName>Lithnet Windows Credential Provider</ProductName>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>alpha1</VersionSuffix>
<VersionPrefix>1.1.0</VersionPrefix>
<VersionSuffix>beta1</VersionSuffix>
<Authors>Lithnet</Authors>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<AutoIncrementPackageRevision>true</AutoIncrementPackageRevision>
@@ -43,6 +43,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Drawing.Common" Version="6.0.0" Condition="$(TargetFrameworkIdentifier) != '.NETFramework'" />
</ItemGroup>
</Project>
@@ -0,0 +1,3 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json"
}
@@ -0,0 +1,7 @@
ReadProcessMemory
OpenProcess
WTSQueryUserToken
GetTokenInformation
CloseHandle
DuplicateHandle
GetFileSizeEx
@@ -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<T>()
{
return new CredentialProviderLogger(loggerFactory.CreateLogger<T>());
}
}
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);
}
}
}
@@ -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<ControlBase> GetControls(UsageScenario cpus)
@@ -15,7 +15,7 @@ namespace Lithnet.CredentialProvider.Samples
private SmallLabelControl CheckboxStateControl;
private SmallLabelControl ComboboxStateControl;
private ILogger logger = InternalLogger.LoggerFactory.CreateLogger<TestCredentialProviderTile>();
private ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderTile>();
public TestCredentialProviderTile(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
@@ -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<TestCredentialProviderNet472x86>();
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderNet472x86>();
protected override ILoggerFactory GetLoggerFactory()
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
return InternalLogger.LoggerFactory;
return InternalLoggerFactory.Instance;
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
@@ -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<TestCredentialProviderNet60x64>();
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderNet60x64>();
protected override ILoggerFactory GetLoggerFactory()
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
return InternalLogger.LoggerFactory;
return InternalLoggerFactory.Instance;
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
@@ -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<TestCredentialProviderNet60x86>();
private static readonly ICredentialProviderLogger logger = InternalLoggerFactory.Instance.CreateLogger<TestCredentialProviderNet60x86>();
protected override ILoggerFactory GetLoggerFactory()
protected override ICredentialProviderLoggerFactory GetLoggerFactory()
{
return InternalLogger.LoggerFactory;
return InternalLoggerFactory.Instance;
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)