如何使用 CredUIPromptForWindowsCredentials 验证凭据
How to validate credentials with CredUIPromptForWindowsCredentials
我不知道为什么我无法使用 CredUnPackAuthenticationBufferW 解压 CredUIPromptForWindowsCredentials 中使用的身份验证缓冲区,我总是得到 ERROR_INSUFFICIENT_BUFFER 错误。
我会感谢你的帮助。
std::wstring caption = L"Caption";
std::wstring msg= L"Msg";
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;
ULONG authPackage = 0;
LPVOID outCredBuffer = nullptr;
ULONG outCredSize = 0;
BOOL save = false;
LPWSTR pszUserName = nullptr;
DWORD pcchlMaxUserName = 0;
LPWSTR pszDomainName = nullptr;
DWORD pcchMaxDomainName = 0;
LPWSTR pszPassword = nullptr;
DWORD pcchMaxPassword = 0;
DWORD result = CredUIPromptForWindowsCredentialsW(&credui,
0,
&authPackage,
nullptr,
0,
&outCredBuffer,
&outCredSize,
&save,
CREDUIWIN_ENUMERATE_ADMINS);
std::cout <<CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS
,outCredBuffer
,outCredSize
,pszUserName
,&pcchlMaxUserName
,pszDomainName
,&pcchMaxDomainName
,pszPassword
,&pcchMaxPassword) << std::endl;
std::cout << GetLastError() << std::endl; // out put 122 == ERROR_INSUFFICIENT_BUFFER
这是典型的 winapi 模式 - api 必须 return 内存缓冲区中的一些信息。而是自己分配缓冲区——它要求调用者分配缓冲区。
所以调用者必须自己分配缓冲区并将它的指针和大小传递给 api。
api 检查缓冲区大小 - 如果它足够大以填充信息来缓冲,否则 return ERROR_INSUFFICIENT_BUFFER
(假设没有其他错误)或有时 ERROR_MORE_DATA
。具体错误返回 ERROR_INSUFFICIENT_BUFFER
或 ERROR_MORE_DATA
通常直接记录为 api 调用。这 2 个错误之间的不同之处在于:ERROR_INSUFFICIENT_BUFFER
- 表示根本没有任何信息填充到缓冲区,而 ERROR_MORE_DATA
表示某些数据已 returned,但不完整。
和 api return 给用户,通过一些输出参数,在这种情况下需要缓冲区大小。通常这是通过相同的 inout 参数 - 指向 DWORD 的指针来完成的。在输入中指定用户分配缓冲区的大小,在输出中 - 指定所需的缓冲区大小或 returned 数据
的大小
经常需要哪个缓冲区大小 - 开始时未知。所以我们首先需要或调用 api 大小为 0 的缓冲区,或者分配一些据称足够的缓冲区大小。如果缓冲区不足 - 重新分配或扩展它并再次调用 api。对于某些 api(如 CredUnPackAuthenticationBufferW
),所需的输出缓冲区不会随时间变化(如果输入参数未更改),但通常的输出缓冲区大小可能会在调用之间发生变化 - 即使第二次调用缓冲区大小 [=第一次调用 36=]ed 可能会因缓冲区大小错误而失败(因为 returned 数据可能会在两次调用之间增长)。在这种情况下需要在 do/while(error == ERROR_INSUFFICIENT_BUFFER/ERROR_MORE_DATA)
循环中调用 api。但即使输出缓冲区不随时间变化,我们也可以更好地做到这一点,这是在内部使用单个 api 调用的循环,而不是 2 个 api 调用。
具体的案例代码看起来像
ULONG cred()
{
CREDUI_INFO ci = { sizeof(ci) };
BOOL bSave = FALSE;
PVOID pvOutAuthBuffer;
ULONG ulOutAuthBufferSize;
ULONG ulAuthPackage = 0;
ULONG dwError = CredUIPromptForWindowsCredentials(
&ci, NOERROR, &ulAuthPackage, 0, 0,
&pvOutAuthBuffer, &ulOutAuthBufferSize,
&bSave, CREDUIWIN_ENUMERATE_ADMINS );
if (dwError == NOERROR)
{
ULONG cchUserName = 0;
ULONG cchPassword = 0;
ULONG cchDomain = 0;
static volatile UCHAR guz = 0;
PWSTR stack = (PWSTR)alloca(guz);
PWSTR szUserName = 0, szPassword = 0, szDomainName = 0;
ULONG cchNeed, cchAllocated = 0;
do
{
if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
{
szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
cchAllocated = (ULONG)(stack - szUserName);
szPassword = szUserName + cchUserName;
szDomainName = szPassword + cchPassword;
}
dwError = CredUnPackAuthenticationBuffer(
CRED_PACK_PROTECTED_CREDENTIALS,
pvOutAuthBuffer, ulOutAuthBufferSize,
szUserName, &cchUserName,
szDomainName, &cchDomain,
szPassword, &cchPassword)
? NOERROR : GetLastError();
if (dwError == NOERROR)
{
DbgPrint("%S@%S %S\n", szDomainName, szUserName, szPassword);
break;
}
} while (dwError == ERROR_INSUFFICIENT_BUFFER);
CoTaskMemFree(pvOutAuthBuffer);
}
return dwError;
}
@RbMm - 你是对的!我使用 LogonUser 对其进行了测试,它运行良好。谢谢。
对于现成的解决方案,我得到了这个:
bool Authenticate_ADMIN_User(std::wstring caption, std::wstring msg, int maxReAsks = 0)
{
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;
ULONG authPackage = 0,
outCredSize = 0;
LPVOID outCredBuffer = nullptr;
BOOL save = false;
DWORD err = 0;
int tries = 0;
bool reAsk = false;
do
{
tries++;
if(CredUIPromptForWindowsCredentialsW(&credui,
err,
&authPackage,
nullptr,
0,
&outCredBuffer,
&outCredSize,
&save,
CREDUIWIN_ENUMERATE_ADMINS)
!= ERROR_SUCCESS)
return false;
ULONG cchUserName = 0;
ULONG cchPassword = 0;
ULONG cchDomain = 0;
ULONG cchNeed, cchAllocated = 0;
static volatile UCHAR guz = 0;
PWSTR stack = (PWSTR)alloca(guz);
PWSTR szUserName = nullptr, szPassword = nullptr, szDomainName = nullptr;
BOOL ret;
do{
if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
{
szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
cchAllocated = (ULONG)(stack - szUserName);
szPassword = szUserName + cchUserName;
szDomainName = szPassword + cchPassword;
}
ret = CredUnPackAuthenticationBuffer(
CRED_PACK_PROTECTED_CREDENTIALS , outCredBuffer, outCredSize, szUserName, &cchUserName,
szDomainName, &cchDomain, szPassword,
&cchPassword);
}while(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
SecureZeroMemory(outCredBuffer, outCredSize);
CoTaskMemFree(outCredBuffer);
HANDLE handle = nullptr;
if (LogonUser(szUserName,
szDomainName,
szPassword,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&handle))
{
CloseHandle(handle);
return true;
}
else
{
err = ERROR_LOGON_FAILURE;
reAsk = true;
}
}while(reAsk && tries < maxReAsks);
return false;
}
我不知道为什么我无法使用 CredUnPackAuthenticationBufferW 解压 CredUIPromptForWindowsCredentials 中使用的身份验证缓冲区,我总是得到 ERROR_INSUFFICIENT_BUFFER 错误。 我会感谢你的帮助。
std::wstring caption = L"Caption";
std::wstring msg= L"Msg";
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;
ULONG authPackage = 0;
LPVOID outCredBuffer = nullptr;
ULONG outCredSize = 0;
BOOL save = false;
LPWSTR pszUserName = nullptr;
DWORD pcchlMaxUserName = 0;
LPWSTR pszDomainName = nullptr;
DWORD pcchMaxDomainName = 0;
LPWSTR pszPassword = nullptr;
DWORD pcchMaxPassword = 0;
DWORD result = CredUIPromptForWindowsCredentialsW(&credui,
0,
&authPackage,
nullptr,
0,
&outCredBuffer,
&outCredSize,
&save,
CREDUIWIN_ENUMERATE_ADMINS);
std::cout <<CredUnPackAuthenticationBufferW(CRED_PACK_PROTECTED_CREDENTIALS
,outCredBuffer
,outCredSize
,pszUserName
,&pcchlMaxUserName
,pszDomainName
,&pcchMaxDomainName
,pszPassword
,&pcchMaxPassword) << std::endl;
std::cout << GetLastError() << std::endl; // out put 122 == ERROR_INSUFFICIENT_BUFFER
这是典型的 winapi 模式 - api 必须 return 内存缓冲区中的一些信息。而是自己分配缓冲区——它要求调用者分配缓冲区。
所以调用者必须自己分配缓冲区并将它的指针和大小传递给 api。
api 检查缓冲区大小 - 如果它足够大以填充信息来缓冲,否则 return ERROR_INSUFFICIENT_BUFFER
(假设没有其他错误)或有时 ERROR_MORE_DATA
。具体错误返回 ERROR_INSUFFICIENT_BUFFER
或 ERROR_MORE_DATA
通常直接记录为 api 调用。这 2 个错误之间的不同之处在于:ERROR_INSUFFICIENT_BUFFER
- 表示根本没有任何信息填充到缓冲区,而 ERROR_MORE_DATA
表示某些数据已 returned,但不完整。
和 api return 给用户,通过一些输出参数,在这种情况下需要缓冲区大小。通常这是通过相同的 inout 参数 - 指向 DWORD 的指针来完成的。在输入中指定用户分配缓冲区的大小,在输出中 - 指定所需的缓冲区大小或 returned 数据
经常需要哪个缓冲区大小 - 开始时未知。所以我们首先需要或调用 api 大小为 0 的缓冲区,或者分配一些据称足够的缓冲区大小。如果缓冲区不足 - 重新分配或扩展它并再次调用 api。对于某些 api(如 CredUnPackAuthenticationBufferW
),所需的输出缓冲区不会随时间变化(如果输入参数未更改),但通常的输出缓冲区大小可能会在调用之间发生变化 - 即使第二次调用缓冲区大小 [=第一次调用 36=]ed 可能会因缓冲区大小错误而失败(因为 returned 数据可能会在两次调用之间增长)。在这种情况下需要在 do/while(error == ERROR_INSUFFICIENT_BUFFER/ERROR_MORE_DATA)
循环中调用 api。但即使输出缓冲区不随时间变化,我们也可以更好地做到这一点,这是在内部使用单个 api 调用的循环,而不是 2 个 api 调用。
具体的案例代码看起来像
ULONG cred()
{
CREDUI_INFO ci = { sizeof(ci) };
BOOL bSave = FALSE;
PVOID pvOutAuthBuffer;
ULONG ulOutAuthBufferSize;
ULONG ulAuthPackage = 0;
ULONG dwError = CredUIPromptForWindowsCredentials(
&ci, NOERROR, &ulAuthPackage, 0, 0,
&pvOutAuthBuffer, &ulOutAuthBufferSize,
&bSave, CREDUIWIN_ENUMERATE_ADMINS );
if (dwError == NOERROR)
{
ULONG cchUserName = 0;
ULONG cchPassword = 0;
ULONG cchDomain = 0;
static volatile UCHAR guz = 0;
PWSTR stack = (PWSTR)alloca(guz);
PWSTR szUserName = 0, szPassword = 0, szDomainName = 0;
ULONG cchNeed, cchAllocated = 0;
do
{
if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
{
szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
cchAllocated = (ULONG)(stack - szUserName);
szPassword = szUserName + cchUserName;
szDomainName = szPassword + cchPassword;
}
dwError = CredUnPackAuthenticationBuffer(
CRED_PACK_PROTECTED_CREDENTIALS,
pvOutAuthBuffer, ulOutAuthBufferSize,
szUserName, &cchUserName,
szDomainName, &cchDomain,
szPassword, &cchPassword)
? NOERROR : GetLastError();
if (dwError == NOERROR)
{
DbgPrint("%S@%S %S\n", szDomainName, szUserName, szPassword);
break;
}
} while (dwError == ERROR_INSUFFICIENT_BUFFER);
CoTaskMemFree(pvOutAuthBuffer);
}
return dwError;
}
@RbMm - 你是对的!我使用 LogonUser 对其进行了测试,它运行良好。谢谢。 对于现成的解决方案,我得到了这个:
bool Authenticate_ADMIN_User(std::wstring caption, std::wstring msg, int maxReAsks = 0)
{
CREDUI_INFOW credui = {};
credui.cbSize = sizeof(credui);
credui.hwndParent = nullptr;
credui.pszMessageText = msg.c_str();
credui.pszCaptionText = caption.c_str();
credui.hbmBanner = nullptr;
ULONG authPackage = 0,
outCredSize = 0;
LPVOID outCredBuffer = nullptr;
BOOL save = false;
DWORD err = 0;
int tries = 0;
bool reAsk = false;
do
{
tries++;
if(CredUIPromptForWindowsCredentialsW(&credui,
err,
&authPackage,
nullptr,
0,
&outCredBuffer,
&outCredSize,
&save,
CREDUIWIN_ENUMERATE_ADMINS)
!= ERROR_SUCCESS)
return false;
ULONG cchUserName = 0;
ULONG cchPassword = 0;
ULONG cchDomain = 0;
ULONG cchNeed, cchAllocated = 0;
static volatile UCHAR guz = 0;
PWSTR stack = (PWSTR)alloca(guz);
PWSTR szUserName = nullptr, szPassword = nullptr, szDomainName = nullptr;
BOOL ret;
do{
if (cchAllocated < (cchNeed = cchUserName + cchPassword + cchDomain))
{
szUserName = (PWSTR)alloca((cchNeed - cchAllocated) * sizeof(WCHAR));
cchAllocated = (ULONG)(stack - szUserName);
szPassword = szUserName + cchUserName;
szDomainName = szPassword + cchPassword;
}
ret = CredUnPackAuthenticationBuffer(
CRED_PACK_PROTECTED_CREDENTIALS , outCredBuffer, outCredSize, szUserName, &cchUserName,
szDomainName, &cchDomain, szPassword,
&cchPassword);
}while(!ret && GetLastError() == ERROR_INSUFFICIENT_BUFFER);
SecureZeroMemory(outCredBuffer, outCredSize);
CoTaskMemFree(outCredBuffer);
HANDLE handle = nullptr;
if (LogonUser(szUserName,
szDomainName,
szPassword,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
&handle))
{
CloseHandle(handle);
return true;
}
else
{
err = ERROR_LOGON_FAILURE;
reAsk = true;
}
}while(reAsk && tries < maxReAsks);
return false;
}