Merge pull request #12 from lithnet/v2-consent-header

Adds support for reading 'v2' consent ui headers
This commit is contained in:
Ryan Newington
2024-09-08 02:48:50 +02:00
committed by GitHub
39 changed files with 550 additions and 54 deletions
@@ -23,8 +23,12 @@ namespace Lithnet.CredentialProvider
{
private static bool? isConsentUI;
private static ConsentUICommandLineArgs commandLineArgs;
private protected ConsentUIStructureHeader header;
private protected ConsentUIStructureHeaderBase header;
private readonly byte[] rawData;
private static readonly Version v2HeaderOsVersion = new Version(10, 0, 26100, 0);
internal static int v2HeaderSize = Marshal.SizeOf<ConsentUIStructureHeaderV2>();
internal static int v1HeaderSize = Marshal.SizeOf<ConsentUIStructureHeaderV1>();
internal static int HeaderSize { get; set; } = Environment.OSVersion.Version >= v2HeaderOsVersion ? v2HeaderSize : v1HeaderSize;
/// <summary>
/// Gets a value indicating the type of ConsentUI data structure
@@ -32,7 +36,8 @@ namespace Lithnet.CredentialProvider
public ConsentUIType Type => this.header.Type;
/// <summary>
/// Gets a value indicating the consent prompt type
/// Gets a value indicating how UAC has been told to fetch approval.
/// In the case where a Credential Provider is initialised, this should always be `Credentials`.
/// </summary>
public ConsentUIPromptType PromptType => this.header.PromptType;
@@ -42,13 +47,13 @@ namespace Lithnet.CredentialProvider
public IntPtr HWnd => this.header.hWindow;
/// <summary>
/// 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`.
/// Gets the reason why `consent.exe` was started in the first place. In other words,
/// the type of action that led to an elevation request.
/// </summary>
public ConsentUIElevationType ElevationType => this.header.ElevationType;
public ConsentUIElevationReason ElevationReason => this.header.ElevationReason;
/// <summary>
/// A series of flags that AppInfo passes to ConsentUI to signifiy actions that need to
/// A series of flags that AppInfo passes to ConsentUI to signify actions that need to
/// take place on the UI side.
/// This includes specifics around the UI that should be presented & signature verification settings.
/// </summary>
@@ -62,12 +67,18 @@ namespace Lithnet.CredentialProvider
private protected ConsentUIData(IntPtr pData, int expectedSize)
{
this.rawData = GetRawBytes(pData, expectedSize);
this.header = Marshal.PtrToStructure<ConsentUIStructureHeader>(pData);
this.header = Marshal.PtrToStructure<ConsentUIStructureHeaderBase>(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}");
}
if (HeaderSize > this.header.Size)
{
throw new InvalidDataException($"The size of the data structure {this.header.Size} is less than the expected header size {HeaderSize}");
}
}
/// <summary>
@@ -330,7 +341,7 @@ namespace Lithnet.CredentialProvider
/// <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)
private protected static SafeHandle DuplicateHandleInternal(IntPtr handle)
{
commandLineArgs ??= GetConsentUICommandLineArgs();
@@ -41,7 +41,7 @@ namespace Lithnet.CredentialProvider
throw new InvalidOperationException("The data structure is not of type COM");
}
var s = Marshal.PtrToStructure<ConsentUIStructureCom>(pData);
var s = Marshal.PtrToStructure<ConsentUIStructureCom>(pData + HeaderSize);
this.ComComponentPath = this.GetStringValueIfValid(pData, (int)s.oComComponentPath);
this.ImageResourcePath = this.GetStringValueIfValid(pData, (int)s.oImageResourcePath);
@@ -50,7 +50,7 @@ namespace Lithnet.CredentialProvider
throw new InvalidOperationException("The data structure is not of type EXE");
}
var s = Marshal.PtrToStructure<ConsentUIStructureExe>(pData);
var s = Marshal.PtrToStructure<ConsentUIStructureExe>(pData + HeaderSize);
this.hFile = s.hFile;
this.ExecutablePath = this.GetStringValueIfValid(pData, (int)s.oExecutablePath1);
@@ -51,7 +51,7 @@ namespace Lithnet.CredentialProvider
throw new InvalidOperationException("The data structure is not of type MSI");
}
var s = Marshal.PtrToStructure<ConsentUIStructureMsi>(pData);
var s = Marshal.PtrToStructure<ConsentUIStructureMsi>(pData + HeaderSize);
this.Action = s.MsiAction;
this.ProductName = this.GetStringValueIfValid(pData, (int)s.oProductName);
@@ -36,7 +36,7 @@ namespace Lithnet.CredentialProvider
throw new InvalidOperationException("The data structure is not of type MSIX");
}
var s = Marshal.PtrToStructure<ConsentUIStructureMsix>(pData);
var s = Marshal.PtrToStructure<ConsentUIStructureMsix>(pData + HeaderSize);
this.ExecutablePath = this.GetStringValueIfValid(pData, (int)s.oExecutablePath);
this.PackageName = this.GetStringValueIfValid(pData, (int)s.oPackageName);
@@ -0,0 +1,89 @@
namespace Lithnet.CredentialProvider
{
public enum ConsentUIElevationReason
{
/// <summary>
/// Application Compatibility
/// e.g. "Run this program as an Administrator" explicitly configured
///
/// > "The AppCompat database stores information in the application
/// > compatibility fix entries for an application."
///
/// `ELEVATION_REASON.ELEVATION_REASON_APPCOMPAT_EXPLICIT (ole32.dll)`
/// </summary>
AppCompatExplicit = 0,
/// <summary>
/// Application Compatibility
/// e.g. "Run this program as an Administrator" set via Windows heuristics
///
/// > "The AppCompat database stores information in the application
/// > compatibility fix entries for an application."
///
/// `ELEVATION_REASON.ELEVATION_REASON_APPCOMPAT_HEURISTIC (ole32.dll)`
/// </summary>
AppCompatHeuristic = 1,
/// <summary>
/// Application manifest
/// > "The Fusion database stores information from application
/// > manifests that describe the applications. The manifest schema
/// > is updated to add a new requested execution level field."
/// See also: https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#trustinfo
///
/// `ELEVATION_REASON.ELEVATION_REASON_FUSION (ole32.dll)`
/// </summary>
Fusion = 2,
/// <summary>
/// Automatically detected Windows Installer package (e.g. an EXE installer)
/// > "Installer detection detects setup files, which helps prevent installations
/// > from being run without the user's knowledge and consent."
///
/// `ELEVATION_REASON.ELEVATION_REASON_INSTALLER (ole32.dll)`
/// </summary>
Installer = 3,
/// <summary>
/// COM elevation action
///
/// `ELEVATION_REASON.ELEVATION_REASON_CLSID (ole32.dll)`
/// </summary>
Clsid = 4,
/// <summary>
/// Windows Installer package installation (MSI)
///
/// `ELEVATION_REASON.ELEVATION_REASON_MSI (ole32.dll)`
/// </summary>
Msi = 5,
/// <summary>
/// "Run as Administrator..." (e.g. manual UAC)
///
/// `ELEVATION_REASON.ELEVATION_REASON_REQUEST (ole32.dll)`
/// </summary>
Request = 6,
/// <summary>
/// ActiveX Installer Service (AXIS)
///
/// `ELEVATION_REASON.ELEVATION_REASON_AXIS (ole32.dll)`
/// </summary>
Axis = 7,
/// <summary>
/// Packaged Applications (MSIX / APPX)
///
/// `ELEVATION_REASON.ELEVATION_REASON_PACKAGED_APP (ole32.dll)`
/// </summary>
Msix = 8,
/// <summary>
/// Unknown
///
/// `ELEVATION_REASON.ELEVATION_REASON_NUM_REASONS (ole32.dll)`
/// </summary>
NumReasons = 9,
}
}
@@ -1,24 +0,0 @@
namespace Lithnet.CredentialProvider
{
public enum ConsentUIElevationType
{
Unknown = 0,
/// <summary>
/// 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.
/// </summary>
AutomaticAdmin = 1,
/// <summary>
/// Prompt the user for consent (i.e. Yes or No)
/// </summary>
Consent = 2,
/// <summary>
/// Prompt the user for credentials
/// </summary>
Credentials = 3
}
}
@@ -62,7 +62,7 @@ namespace Lithnet.CredentialProvider
BlockElevation = 0x1000,
/// <summary>
/// Corresponds to `ConsentUIElevationType.AutomaticAdmin`
/// Corresponds to `ConsentUIPromptType.AutomaticAdmin`
/// This seems to be an instance where UAC creates a local, secondary
/// account called '%username%_admin' which is used to elevate a process.
/// </summary>
@@ -10,6 +10,8 @@ namespace Lithnet.CredentialProvider.Interop
/// </summary>
IntPtr pointer;
int size;
SafeHGlobalHandle()
{
this.pointer = IntPtr.Zero;
@@ -25,6 +27,8 @@ namespace Lithnet.CredentialProvider.Interop
this.Dispose();
}
public int Size => size;
public static SafeHGlobalHandle InvalidHandle => new SafeHGlobalHandle(IntPtr.Zero);
/// <summary>
@@ -58,6 +62,7 @@ namespace Lithnet.CredentialProvider.Interop
SafeHGlobalHandle result = new SafeHGlobalHandle();
result.pointer = Marshal.AllocHGlobal(cb);
result.size = cb;
return result;
}
}
@@ -6,8 +6,6 @@ namespace Lithnet.CredentialProvider.Interop
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureCom
{
public ConsentUIStructureHeader Header;
// 64
public IntPtr oOperationType; // 8
@@ -6,8 +6,6 @@ namespace Lithnet.CredentialProvider.Interop
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureExe
{
public ConsentUIStructureHeader Header;
// 64
public IntPtr hFile; // 8
@@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureHeader
internal struct ConsentUIStructureHeaderBase
{
public int Size; // 4
public ConsentUIType Type; // 4
@@ -18,7 +18,7 @@ namespace Lithnet.CredentialProvider.Interop
// 32
public ConsentUIElevationType ElevationType; // 4
public ConsentUIElevationReason ElevationReason; // 4
public int sessionId; // 4
public IntPtr hMutex; // 8
@@ -26,8 +26,5 @@ namespace Lithnet.CredentialProvider.Interop
public ConsentUIFlags Flags; // 4
public int unknown0; // 4
public IntPtr pReturnAddress; // 8
// 64
}
}
@@ -0,0 +1,15 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureHeaderV1
{
public ConsentUIStructureHeaderBase BaseHeader;
public IntPtr pReturnAddress; // 8
// 64
}
}
@@ -0,0 +1,16 @@
using System;
using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureHeaderV2
{
public ConsentUIStructureHeaderBase BaseHeader;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x81)]
public byte[] unknown1;
public IntPtr pReturnAddress; // 8
}
}
@@ -6,8 +6,6 @@ namespace Lithnet.CredentialProvider.Interop
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureMsi
{
public ConsentUIStructureHeader Header;
// 64
public ConsentUIMsiAction MsiAction; // 8
@@ -6,8 +6,6 @@ namespace Lithnet.CredentialProvider.Interop
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct ConsentUIStructureMsix
{
public ConsentUIStructureHeader Header;
// 64
public IntPtr oExecutablePath; // 8
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0-windows;net7.0-windows;net461</TargetFrameworks>
<TargetFrameworks>net6.0-windows;net7.0-windows;net8.0-windows;net461</TargetFrameworks>
<RegisterForComInterop>false</RegisterForComInterop>
<OutputType>Library</OutputType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -38,6 +38,15 @@
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Lithnet.CredentialProvider.UnitTests.x64</_Parameter1>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>Lithnet.CredentialProvider.UnitTests.x86</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
@@ -1,3 +0,0 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json"
}