如何解密从安全桌面获得的身份验证缓冲区

本文关键字:桌面 缓冲区 身份验证 安全 何解密 解密 | 更新日期: 2023-09-27 18:20:52

我试图请求用户在安全桌面上输入凭据,但我找不到解密生成的身份验证缓冲区的方法:

凭证通过:获得

CREDUI_INFO cui = new CREDUI_INFO()
{
    cbSize = (uint)Marshal.SizeOf(typeof(CREDUI_INFO)),
    pszMessageText = "MyMessage",
    pszCaptionText = "MyCaption",
};
bool save = false;
uint authPackage = 0U;
IntPtr authBuffer;
uint authBufferSize;
uint error = CredUIPromptForWindowsCredentials(
                ref cui,
                0U,
                ref authPackage,
                IntPtr.Zero,
                0U,
                out authBuffer,
                out authBufferSize,
                ref save,
                CREDUIWIN_SECURE_PROMPT);

凭证使用:解包

uint userBufferSize = CREDUI_MAX_USERNAME_LENGTH;
uint domainBufferSize = CRED_MAX_STRING_LENGTH;
uint passwordBufferSize = CREDUI_MAX_PASSWORD_LENGTH;
StringBuilder userBuffer = new StringBuilder((int)userBufferSize);
StringBuilder domainBuffer = new String((int)domainBufferSize);
IntPtr passwordBuffer = Marshal.AllocCoTaskMem((int)passwordBufferSize);
bool success = CredUnPackAuthenticationBuffer(
                CRED_PACK_PROTECTED_CREDENTIALS,
                authBuffer, authBufferSize
                userBuffer, ref userBufferSize,
                domainBuffer, ref domainBufferSize,
                passwordBuffer, ref passwordBufferSize);

如果像上面的例子一样在CredUIPromptForWindowsCredentials中指定CREDUIWIN_SECURE_PROMPT,那么CredUnPackAuthenticationBuffer将使用ERROR_NOT_CAPABLE失败。如果我使用CREDUIWIN_GENERIC而不是安全桌面,它会成功。

如何从安全桌面上的此类加密凭据中获取名称、域和密码或者,如果不可能的话,我怎么能普遍使用它们

如何解密从安全桌面获得的身份验证缓冲区

凭据提供程序使用CredProtectWCredProtectEx加密密码。具有CRED_PACK_PROTECTED_CREDENTIALSCredUnPackAuthenticationBufferW尝试通过使用CredUnprotectWCredUnprotectEx来解密认证缓冲器中的凭证(密码)。错误-error_NOT_CAPABLE完全由CredUnprotectW返回。

用于加密凭据的安全上下文与用于解密凭据的安全环境不同

这是因为,当CREDUIWIN_SECURE_PROMPT用于收集用户凭据时,系统在上下文中作为系统进程运行LogonUI.exe。并在此过程中对密码进行加密。当您在另一个安全上下文-用户进程中运行时。

如果您在令牌中没有启用任何管理员组,则无法对此执行任何操作——您可以通过LsaLogonUser将此凭据传递给系统,但不能解密密码。如果您有";admin";,您可以使用系统令牌进行模拟,然后在线程进行模拟时调用CredUnPackAuthenticationBuffer(CRED_PACK_PROTECTED_CREDENTIALS,..)

或者,您可以在不使用CRED_PACK_PROTECTED_CREDENTIALS的情况下调用CredUnPackAuthenticationBuffer(0,..),然后在模拟系统令牌时通过调用CredUnprotectW来分离未受保护的密码。或者,如果您不能模拟系统令牌,那么您可以获得用户名的最小值。但它无法访问密码。如果您使用返回的密码调用CredIsProtectedW,您将获得CredTrustedProtection

模拟系统令牌的代码:

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}
NTSTATUS ImpersonateSystemToken(PSYSTEM_PROCESS_INFORMATION pspi)
{
    NTSTATUS status;
    ULONG NextEntryOffset = 0;
    do
    {
        (ULONG_PTR&)pspi += NextEntryOffset;
        HANDLE hProcess, hToken;
        CLIENT_ID ClientId = { pspi->UniqueProcessId };
        if (ClientId.UniqueProcess)
        {
            const SECURITY_QUALITY_OF_SERVICE sqos = {
                sizeof (sqos), SecurityImpersonation, SECURITY_DYNAMIC_TRACKING, FALSE
            };
            const OBJECT_ATTRIBUTES oa_sqos = { sizeof(oa_sqos), 0, 0, 0, 0, const_cast<SECURITY_QUALITY_OF_SERVICE*>(&sqos) };
            if (0 <= NtOpenProcess(&hProcess, PROCESS_QUERY_LIMITED_INFORMATION,
                const_cast<POBJECT_ATTRIBUTES>(&oa_sqos), &ClientId))
            {
                status = NtOpenProcessToken(hProcess, TOKEN_DUPLICATE|TOKEN_QUERY, &hToken);
                NtClose(hProcess);
                if (0 <= status)
                {
                    TOKEN_STATISTICS ts;
                    if (0 <= (status = NtQueryInformationToken(hToken, TokenStatistics, &ts, sizeof(ts), &ts.DynamicCharged)))
                    {
                        status = STATUS_NOT_FOUND;
                        static const LUID SystemLuid = SYSTEM_LUID;
                        if (ts.AuthenticationId.LowPart == SystemLuid.LowPart &&
                            ts.AuthenticationId.HighPart == SystemLuid.HighPart)
                        {
                            status = NtDuplicateToken(hToken, TOKEN_IMPERSONATE|TOKEN_ADJUST_PRIVILEGES,
                                const_cast<POBJECT_ATTRIBUTES>(&oa_sqos), FALSE, TokenImpersonation, &hProcess);
                        }
                    }
                    NtClose(hToken);
                    if (0 <= status)
                    {
                        const TOKEN_PRIVILEGES tp_TCB = { 1, { { { SE_TCB_PRIVILEGE }, SE_PRIVILEGE_ENABLED } } };
                        if (STATUS_SUCCESS == NtAdjustPrivilegesToken(hProcess, FALSE,
                            const_cast<TOKEN_PRIVILEGES*>(&tp_TCB), sizeof(tp_TCB), 0, 0))
                        {
                            status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, &hProcess, sizeof(hProcess));
                        }
                        else
                        {
                            status = -1;
                        }
                        NtClose(hProcess);
                        if (-1 != status)
                        {
                            return status;
                        }
                    }
                }
            }
        }
    } while (NextEntryOffset = pspi->NextEntryOffset);
    return STATUS_UNSUCCESSFUL;
}
NTSTATUS ImpersonateSystemToken()
{
    BOOLEAN WasEnabled;
    RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &WasEnabled);
    NTSTATUS status;
    ULONG cb = 0x40000;
    do
    {
        status = STATUS_INSUFFICIENT_RESOURCES;
        if (PBYTE buf = new BYTE[cb += 0x1000])
        {
            if (0 <= (status = NtQuerySystemInformation(SystemProcessInformation, buf, cb, &cb)))
            {
                status = ImpersonateSystemToken((PSYSTEM_PROCESS_INFORMATION)buf);
                if (status == STATUS_INFO_LENGTH_MISMATCH)
                {
                    status = STATUS_UNSUCCESSFUL;
                }
            }
            delete [] buf;
        }
    } while(status == STATUS_INFO_LENGTH_MISMATCH);
    return status;
}

您可以将其用作:

    if (0 <= ImpersonateSystemToken())
    {
        if (CredUnPackAuthenticationBuffer(CRED_PACK_PROTECTED_CREDENTIALS, pvOutAuthBuffer, ulOutAuthBufferSize,
            szUserName, &cchUserName, szDomainName, &cchDomainName, szPassword, &cchPassword))
        {
            //...
        }
        else
        {
            GetLastError();
        }
        SetThreadToken(0, 0);
    }

或者通过这种方式:

    if (CredUnPackAuthenticationBuffer(0, pvOutAuthBuffer, ulOutAuthBufferSize,
        szUserName, &cchUserName, szDomainName, &cchDomainName, szPassword, &cchPassword))
    {
        HRESULT hr = DecryptPassword(szPassword, cchPassword);
        // ...
    }
    else
    {
        GetLastError();
    }

HRESULT DecryptPassword(PWSTR szPassword, ULONG cchPassword)
{
    CRED_PROTECTION_TYPE ProtectionType;
    HRESULT hr = HRESULT_FROM_WIN32(BOOL_TO_ERROR(CredIsProtectedW(szPassword, &ProtectionType)));
    if (S_OK == hr)
    {
        ULONG cch = 0;
        PWSTR pszCredentials = 0;
        switch (ProtectionType)
        {
        case CredUnprotected:
            pszCredentials = szPassword, cch = cchPassword;
            break;
        case CredTrustedProtection:
        case CredForSystemProtection:
            if (0 > (hr = ImpersonateSystemToken()))
            {
                hr |= FACILITY_NT_BIT;
                break;
            }
            [[fallthrough]];
        case CredUserProtection:
            while (ERROR_INSUFFICIENT_BUFFER == (hr = BOOL_TO_ERROR(CredUnprotectW(
                FALSE, szPassword, cchPassword, pszCredentials, &cch))))
            {
                if (pszCredentials)
                {
                    break;
                }
                pszCredentials = (PWSTR)alloca(cch * sizeof(WCHAR));
            }
            if (ProtectionType != CredUserProtection) SetThreadToken(0, 0);
            hr = HRESULT_FROM_WIN32(hr);
            break;
        default:
            hr = HRESULT_FROM_NT(STATUS_NOT_IMPLEMENTED);
        }
        if (0 <= hr)
        {
            DbgPrint("pass= '"%.*S'"'n", cch, pszCredentials);
        }
    }
    return hr;
}