Информация об изменениях

Сообщение Re: Детект прав админа у пользователя от 29.03.2024 17:06

Изменено 29.03.2024 17:10 VladD2

Re: Детект прав админа у пользователя
Здравствуйте, VladD2, Вы писали:

VD>В продукте есть функционал по обновлению стороннего софта. Для большинства обновлений требуются права администратора. В этом случае пользователю выдается стандартный виндовый диалог ввода учетки и пароля.


Если кому интересно, вот финальный, рабочий код:
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using System.Security;
using System.Net;
using System.Runtime.Versioning;
using System.Text;
using System.Windows;
using System.Diagnostics;
using System.DirectoryServices.ActiveDirectory;
using System.Windows.Interop;

namespace WpfCred;

internal class Credentials
{
    const int CREDUI_MAX_USERNAME_LENGTH = 256 + 1 + 256;
    const int CREDUI_MAX_DOMAIN_TARGET_LENGTH = 256 + 1 + 80;
    const int CREDUI_MAX_PASSWORD_LENGTH = 512 / 2;

    [Flags]
    enum CredUiWinSecure : uint
    {
        /// <summary>
        /// The caller is requesting that the credential provider return the user name and password in plain text.
        /// This value cannot be combined with SECURE_PROMPT.
        /// </summary>
        Generic = 0x1,
        /// <summary> The Save check box is displayed in the dialog box.</summary>
        Checkbox = 0x2,
        /// <summary>
        /// Only credential providers that support the authentication package specified by the pulAuthPackage parameter should be enumerated.
        /// This value cannot be combined with IN_CRED_ONLY.
        /// </summary>
        AuthpackageOnly = 0x10,
        /// <summary>
        /// Only the credentials specified by the pvInAuthBuffer parameter for the authentication package specified by the pulAuthPackage parameter should be enumerated.
        /// If this flag is set, and the pvInAuthBuffer parameter is NULL, the function fails.
        /// 
        /// This value cannot be combined with AuthpackageOnly.
        /// </summary>
        InCredOnly = 0x20,
        /// <summary>
        /// Credential providers should enumerate only administrators. This value is intended for User Account Control (UAC) purposes only.We recommend that external callers not set this flag.
        /// </summary>
        EnumerateAdmins = 0x100,
        /// <summary>
        /// Only the incoming credentials for the authentication package specified by the pulAuthPackage parameter should be enumerated.
        /// </summary>
        EnumerateCurrentUser = 0x200,
        /// <summary>
        /// The credential dialog box should be displayed on the secure desktop.This value cannot be combined with Generic
        /// Windows Vista:  This value is supported beginning with Windows Vista with SP1.
        /// </summary>
        SecurePrompt = 0x1000,
        /// <summary>
        /// The credential dialog box is invoked by the SspiPromptForCredentials function, and the client is prompted before a prior handshake.If SSPIPFC_NO_CHECKBOX is passed in the pvInAuthBuffer parameter, then the credential provider should not display the check box.
        /// Windows Vista:  This value is supported beginning with Windows Vista with SP1.
        /// </summary>
        PrePrompting = 0x2000,
        /// <summary>
        /// The credential provider will not pack the AAD authority name. This is only applied to Azure AD joined devices.
        /// Windows 10, version 1607:  This value is supported beginning with Windows 10, version 1607.
        /// </summary>
        NotPackAadAuthority = 0x40000,
        /// <summary>
        /// The credential provider should align the credential BLOB pointed to by the ppvOutAuthBuffer parameter to a 32-bit boundary, even if the provider is running on a 64-bit system.
        /// </summary>
        Pack32Wow = 0x10000000,
        /// <summary>
        /// Windows Hello credentials will be packed in a smart card auth buffer.This only applies to the face, fingerprint, and PIN credential providers.
        /// Windows 10, version 1809:  This value is supported beginning with Windows 10, version 1809.
        /// </summary>
        WindowsHello = 0x80000000
    }

    enum CRED_PACK
    {
        None = 0,
        PROTECTED_CREDENTIALS   = 0x1,
        WOW_BUFFER              = 0x2,
        GENERIC_CREDENTIALS     = 0x4,
        ID_PROVIDER_CREDENTIALS = 0x8,
    }


    [DllImport("kernel32.dll", CharSet = CharSet.Auto, EntryPoint="RtlSecureZeroMemory")]
    private static extern void SecureZeroMemory(IntPtr ptr, IntPtr cnt);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct CREDUI_INFO
    {
        public int cbSize;
        public IntPtr hwndParent;
        public string pszMessageText;
        public string pszCaptionText;
        public IntPtr hbmBanner;
    }
    // 
    [DllImport("credui.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool CredUnPackAuthenticationBuffer(CRED_PACK dwFlags,
                                                               IntPtr pAuthBuffer,
                                                               uint cbAuthBuffer,
                                                               StringBuilder pszUserName,
                                                               ref int pcchMaxUserName,
                                                               StringBuilder pszDomainName,
                                                               ref int pcchMaxDomainame,
                                                               StringBuilder pszPassword,
                                                               ref int pcchMaxPassword);
    // Decode credentials
    [DllImport("credui.dll", CharSet = CharSet.Auto)]
    private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere,
                                                                 int authError,
                                                                 ref uint authPackage,
                                                                 IntPtr InAuthBuffer,
                                                                 uint InAuthBufferSize,
                                                                 out IntPtr refOutAuthBuffer,
                                                                 out uint refOutAuthBufferSize,
                                                                 ref bool fSave,
                                                                 CredUiWinSecure flags);


    [DllImport("ole32.dll")]
    [SuppressUnmanagedCodeSecurity]
    [ResourceExposure(ResourceScope.None)]
    private static extern void CoTaskMemFree(IntPtr ptr);

    private enum Logon32
    {
        Interactive = 2,
        Network = 3,
        Batch = 4,
    }

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
        Logon32 dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private extern static bool CloseHandle(IntPtr handle);

    private sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() : base(true) { }

        [DllImport("kernel32.dll")]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle() => CloseHandle(handle);
    }

    private enum IsLoginResult
    {
        IsAdmin,
        PasswordIncorrect,
        IsNotAdmin,
    }

    private static IsLoginResult IsLoginLocalAdministrator(string userName, string domainName, string password)
    {
        var parts = userName.Split('\\');

        if (parts.Length == 2)
        {
            userName = parts[1];
            if (string.IsNullOrEmpty(domainName))
                domainName = parts[0];
        }

        SafeTokenHandle safeTokenHandle;
        try
        {
            const int LOGON32_PROVIDER_DEFAULT = 0;

            // Call LogonUser to obtain a handle to an access token.
            bool returnValue = LogonUser(userName, domainName, password,
                Logon32.Network, LOGON32_PROVIDER_DEFAULT,
                out safeTokenHandle);

            if (!returnValue)
            {
                var passwordIncorrect = 1326;
                var ret = Marshal.GetLastWin32Error();
                if (ret == passwordIncorrect)
                    return IsLoginResult.PasswordIncorrect;

                throw new System.ComponentModel.Win32Exception(ret);
            }

            using (safeTokenHandle)
            {
                // Use the token handle returned by LogonUser.
                using WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());

                var windowsPrincipal = new WindowsPrincipal(newId);
                bool isAdmin = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
                return isAdmin ? IsLoginResult.IsAdmin : IsLoginResult.IsNotAdmin;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Exception occurred. " + ex.Message);
        }

        return IsLoginResult.IsNotAdmin;
    }

    public static NetworkCredential? GetCredentials(Window parent, string caption, string message, NetworkCredential? defaultCreds = null)
    {
        var inCredBuffer = IntPtr.Zero;
        var inCredSize = 0u;
        var hWndParent = new WindowInteropHelper(parent).EnsureHandle();
        var credui = new CREDUI_INFO
        {
            hwndParent = hWndParent,
            pszCaptionText = caption,
            pszMessageText = message,
        };
        credui.cbSize = Marshal.SizeOf(credui);

        for (;;)
        {
            uint authPackage = 0;
            var save = false;
            var usernameBuf = new StringBuilder(CREDUI_MAX_USERNAME_LENGTH + 1);
            var passwordBuf = new StringBuilder(CREDUI_MAX_PASSWORD_LENGTH + 1);
            var domainBuf = new StringBuilder(CREDUI_MAX_DOMAIN_TARGET_LENGTH);
            var userNameLen = CREDUI_MAX_USERNAME_LENGTH;
            var domainLen = CREDUI_MAX_DOMAIN_TARGET_LENGTH;
            var passwordLen = CREDUI_MAX_PASSWORD_LENGTH;

            var result = CredUIPromptForWindowsCredentials(
                ref credui,
                0,
                ref authPackage,
                InAuthBuffer: inCredBuffer,
                InAuthBufferSize: inCredSize,
                out var outCredBuffer,
                out var outCredSize,
                ref save,
                CredUiWinSecure.EnumerateAdmins /*| CredUiWinSecure.Checkbox*/ | CredUiWinSecure.WindowsHello
            );

            inCredBuffer = outCredBuffer;
            inCredSize = outCredSize;

            if (result != 0)
            {
                var res = Marshal.GetLastWin32Error();
                return null;
            }

            var packAuthRes = CredUnPackAuthenticationBuffer(CRED_PACK.PROTECTED_CREDENTIALS, outCredBuffer, outCredSize,
                usernameBuf, ref userNameLen,
                domainBuf, ref domainLen,
                passwordBuf, ref passwordLen);

            if (!packAuthRes)
            {
                var res = Marshal.GetLastWin32Error();
                if (outCredBuffer != IntPtr.Zero)
                    CoTaskMemFree(outCredBuffer);
                return null;
            }

            var userName = usernameBuf.ToString();
            var domain = domainBuf.ToString();
            var password = passwordBuf.ToString();
            var isLoginResult = IsLoginLocalAdministrator(userName, domain, password);

            if (outCredBuffer != IntPtr.Zero)
                CoTaskMemFree(outCredBuffer);

            switch (isLoginResult)
            {
                case IsLoginResult.IsAdmin:
                    return new NetworkCredential
                    {
                        UserName = userName,
                        Domain = domain,
                        Password = password,
                    };
                case IsLoginResult.PasswordIncorrect:
                    MessageBox.Show(parent, "Неверный пароль.", caption, MessageBoxButton.OK, MessageBoxImage.Error);
                    continue;
                case IsLoginResult.IsNotAdmin:
                default:
                    MessageBox.Show(parent, "Пользователь не является локальным администратором.", caption, MessageBoxButton.OK, MessageBoxImage.Error);
                    continue;
            }
        }
    }
} // end Class
Re: Детект прав админа у пользователя
Здравствуйте, VladD2, Вы писали:

VD>В продукте есть функционал по обновлению стороннего софта. Для большинства обновлений требуются права администратора. В этом случае пользователю выдается стандартный виндовый диалог ввода учетки и пароля.


Если кому интересно, вот финальный, рабочий код:
using System.Runtime.InteropServices;
using System.Security.Principal;
using Microsoft.Win32.SafeHandles;
using System.Security;
using System.Net;
using System.Runtime.Versioning;
using System.Text;
using System.Windows;
using System.Diagnostics;
using System.Windows.Interop;

namespace WpfCred;

internal class Credentials
{
    const int CREDUI_MAX_USERNAME_LENGTH = 256 + 1 + 256;
    const int CREDUI_MAX_DOMAIN_TARGET_LENGTH = 256 + 1 + 80;
    const int CREDUI_MAX_PASSWORD_LENGTH = 512 / 2;

    [Flags]
    private enum CredUiWinSecure : uint
    {
        /// <summary>
        /// The caller is requesting that the credential provider return the user name and password in plain text.
        /// This value cannot be combined with SECURE_PROMPT.
        /// </summary>
        Generic = 0x1,
        /// <summary> The Save check box is displayed in the dialog box.</summary>
        Checkbox = 0x2,
        /// <summary>
        /// Only credential providers that support the authentication package specified by the pulAuthPackage parameter should be enumerated.
        /// This value cannot be combined with IN_CRED_ONLY.
        /// </summary>
        AuthpackageOnly = 0x10,
        /// <summary>
        /// Only the credentials specified by the pvInAuthBuffer parameter for the authentication package specified by the pulAuthPackage parameter should be enumerated.
        /// If this flag is set, and the pvInAuthBuffer parameter is NULL, the function fails.
        /// 
        /// This value cannot be combined with AuthpackageOnly.
        /// </summary>
        InCredOnly = 0x20,
        /// <summary>
        /// Credential providers should enumerate only administrators. This value is intended for User Account Control (UAC) purposes only.We recommend that external callers not set this flag.
        /// </summary>
        EnumerateAdmins = 0x100,
        /// <summary>
        /// Only the incoming credentials for the authentication package specified by the pulAuthPackage parameter should be enumerated.
        /// </summary>
        EnumerateCurrentUser = 0x200,
        /// <summary>
        /// The credential dialog box should be displayed on the secure desktop.This value cannot be combined with Generic
        /// Windows Vista:  This value is supported beginning with Windows Vista with SP1.
        /// </summary>
        SecurePrompt = 0x1000,
        /// <summary>
        /// The credential dialog box is invoked by the SspiPromptForCredentials function, and the client is prompted before a prior handshake.If SSPIPFC_NO_CHECKBOX is passed in the pvInAuthBuffer parameter, then the credential provider should not display the check box.
        /// Windows Vista:  This value is supported beginning with Windows Vista with SP1.
        /// </summary>
        PrePrompting = 0x2000,
        /// <summary>
        /// The credential provider will not pack the AAD authority name. This is only applied to Azure AD joined devices.
        /// Windows 10, version 1607:  This value is supported beginning with Windows 10, version 1607.
        /// </summary>
        NotPackAadAuthority = 0x40000,
        /// <summary>
        /// The credential provider should align the credential BLOB pointed to by the ppvOutAuthBuffer parameter to a 32-bit boundary, even if the provider is running on a 64-bit system.
        /// </summary>
        Pack32Wow = 0x10000000,
        /// <summary>
        /// Windows Hello credentials will be packed in a smart card auth buffer.This only applies to the face, fingerprint, and PIN credential providers.
        /// Windows 10, version 1809:  This value is supported beginning with Windows 10, version 1809.
        /// </summary>
        WindowsHello = 0x80000000
    }

    private enum CRED_PACK
    {
        None = 0,
        PROTECTED_CREDENTIALS   = 0x1,
        WOW_BUFFER              = 0x2,
        GENERIC_CREDENTIALS     = 0x4,
        ID_PROVIDER_CREDENTIALS = 0x8,
    }


    [DllImport("kernel32.dll", CharSet = CharSet.Auto, EntryPoint="RtlSecureZeroMemory")]
    private static extern void SecureZeroMemory(IntPtr ptr, IntPtr cnt);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct CREDUI_INFO
    {
        public int cbSize;
        public IntPtr hwndParent;
        public string pszMessageText;
        public string pszCaptionText;
        public IntPtr hbmBanner;
    }

    [DllImport("credui.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool CredUnPackAuthenticationBuffer(CRED_PACK dwFlags,
                                                               IntPtr pAuthBuffer,
                                                               uint cbAuthBuffer,
                                                               StringBuilder pszUserName,
                                                               ref int pcchMaxUserName,
                                                               StringBuilder pszDomainName,
                                                               ref int pcchMaxDomainame,
                                                               StringBuilder pszPassword,
                                                               ref int pcchMaxPassword);
    // Decode credentials
    [DllImport("credui.dll", CharSet = CharSet.Auto)]
    private static extern int CredUIPromptForWindowsCredentials(ref CREDUI_INFO notUsedHere,
                                                                 int authError,
                                                                 ref uint authPackage,
                                                                 IntPtr InAuthBuffer,
                                                                 uint InAuthBufferSize,
                                                                 out IntPtr refOutAuthBuffer,
                                                                 out uint refOutAuthBufferSize,
                                                                 ref bool fSave,
                                                                 CredUiWinSecure flags);


    [DllImport("ole32.dll")]
    [SuppressUnmanagedCodeSecurity]
    [ResourceExposure(ResourceScope.None)]
    private static extern void CoTaskMemFree(IntPtr ptr);

    private enum Logon32
    {
        Interactive = 2,
        Network = 3,
        Batch = 4,
    }

    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword,
        Logon32 dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private extern static bool CloseHandle(IntPtr handle);

    private sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() : base(true) { }

        [DllImport("kernel32.dll")]
        [SuppressUnmanagedCodeSecurity]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool CloseHandle(IntPtr handle);

        protected override bool ReleaseHandle() => CloseHandle(handle);
    }

    private enum IsLoginResult
    {
        IsAdmin,
        PasswordIncorrect,
        IsNotAdmin,
    }

    private static IsLoginResult IsLoginLocalAdministrator(string userName, string domainName, string password)
    {
        var parts = userName.Split('\\');

        if (parts.Length == 2)
        {
            userName = parts[1];
            if (string.IsNullOrEmpty(domainName))
                domainName = parts[0];
        }

        try
        {
            const int LOGON32_PROVIDER_DEFAULT = 0;

            // Call LogonUser to obtain a handle to an access token.
            bool returnValue = LogonUser(userName, domainName, password,
                Logon32.Network, LOGON32_PROVIDER_DEFAULT,
                out SafeTokenHandle safeTokenHandle);

            if (!returnValue)
            {
                var passwordIncorrect = 1326;
                var ret = Marshal.GetLastWin32Error();
                if (ret == passwordIncorrect)
                    return IsLoginResult.PasswordIncorrect;

                throw new System.ComponentModel.Win32Exception(ret);
            }

            using (safeTokenHandle)
            {
                // Use the token handle returned by LogonUser.
                using WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle());

                var windowsPrincipal = new WindowsPrincipal(newId);
                bool isAdmin = windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
                return isAdmin ? IsLoginResult.IsAdmin : IsLoginResult.IsNotAdmin;
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Exception occurred. " + ex.Message);
        }

        return IsLoginResult.IsNotAdmin;
    }

    public static NetworkCredential? GetCredentials(Window parent, string caption, string message, NetworkCredential? defaultCreds = null)
    {
        var inCredBuffer = IntPtr.Zero;
        var inCredSize = 0u;
        var hWndParent = new WindowInteropHelper(parent).EnsureHandle();
        var credui = new CREDUI_INFO
        {
            hwndParent = hWndParent,
            pszCaptionText = caption,
            pszMessageText = message,
        };
        credui.cbSize = Marshal.SizeOf(credui);

        for (;;)
        {
            uint authPackage = 0;
            var save = false;
            var usernameBuf = new StringBuilder(CREDUI_MAX_USERNAME_LENGTH + 1);
            var passwordBuf = new StringBuilder(CREDUI_MAX_PASSWORD_LENGTH + 1);
            var domainBuf = new StringBuilder(CREDUI_MAX_DOMAIN_TARGET_LENGTH);
            var userNameLen = CREDUI_MAX_USERNAME_LENGTH;
            var domainLen = CREDUI_MAX_DOMAIN_TARGET_LENGTH;
            var passwordLen = CREDUI_MAX_PASSWORD_LENGTH;

            var result = CredUIPromptForWindowsCredentials(
                ref credui,
                0,
                ref authPackage,
                InAuthBuffer: inCredBuffer,
                InAuthBufferSize: inCredSize,
                out var outCredBuffer,
                out var outCredSize,
                ref save,
                CredUiWinSecure.EnumerateAdmins /*| CredUiWinSecure.Checkbox*/ | CredUiWinSecure.WindowsHello
            );

            inCredBuffer = outCredBuffer;
            inCredSize = outCredSize;

            if (result != 0)
            {
                var res = Marshal.GetLastWin32Error();
                Debug.WriteLine(new System.ComponentModel.Win32Exception(res).Message);
                return null;
            }

            var packAuthRes = CredUnPackAuthenticationBuffer(CRED_PACK.PROTECTED_CREDENTIALS, outCredBuffer, outCredSize,
                usernameBuf, ref userNameLen,
                domainBuf, ref domainLen,
                passwordBuf, ref passwordLen);

            if (!packAuthRes)
            {
                var res = Marshal.GetLastWin32Error();
                Debug.WriteLine(new System.ComponentModel.Win32Exception(res).Message);
                if (outCredBuffer != IntPtr.Zero)
                    CoTaskMemFree(outCredBuffer);
                return null;
            }

            var userName = usernameBuf.ToString();
            var domain = domainBuf.ToString();
            var password = passwordBuf.ToString();
            var isLoginResult = IsLoginLocalAdministrator(userName, domain, password);

            if (outCredBuffer != IntPtr.Zero)
                CoTaskMemFree(outCredBuffer);

            switch (isLoginResult)
            {
                case IsLoginResult.IsAdmin:
                    return new NetworkCredential
                    {
                        UserName = userName,
                        Domain = domain,
                        Password = password,
                    };
                case IsLoginResult.PasswordIncorrect:
                    MessageBox.Show(parent, "Неверный пароль.", caption, MessageBoxButton.OK, MessageBoxImage.Error);
                    continue;
                case IsLoginResult.IsNotAdmin:
                default:
                    MessageBox.Show(parent, "Пользователь не является локальным администратором.", caption, MessageBoxButton.OK, MessageBoxImage.Error);
                    continue;
            }
        }
    }
} // end Class