奇怪的窗户冒充行为

我的Windows应用程序可能需要它的某些部分的pipe理权限。 对于这些情况,我想要求用户inputpipe理员凭据,并使用以下代码模拟pipe理员:

BOOL impersonate(LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR lpszPassword) { BOOL ret = LogonUser(lpszUsername, lpszDomain, lpszPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken); if (ret != TRUE) return FALSE; OutputDebugString (L"step 1"); ret = ImpersonateLoggedOnUser(hToken); if (ret != TRUE) return FALSE; OutputDebugString(L"step 2"); return IsUserAdmin() } 

其中函数IsUserAdmin() 已从MSDN取得 ,如下所示:

 BOOL IsUserAdmin(VOID) { BOOL b; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID AdministratorsGroup; b = AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup); if (b) { if (!CheckTokenMembership(NULL, AdministratorsGroup, &b)) { b = FALSE; } FreeSid(AdministratorsGroup); } return(b); } 

情况1:
从pipe理员帐户运行应用程序。

  1. 调用IsUserAdmin()=>返回TRUE (好)

  2. 调用impersonate(“非pipe理用户”,“域”,“密码”)=>返回FALSE (好!)

情景2:
从pipe理员帐户运行应用程序,使用pipe理员帐户的runas.exe

  1. 调用IsUserAdmin()=>返回FALSE (好)

  2. 调用模拟(“pipe理员”,“域”,“密码”)=>返回FALSE (不好!

情景3:
直接从非pipe理员帐户运行应用程序。

  1. 调用IsUserAdmin()=>返回FALSE (好)

  2. 调用模拟(“pipe理员”,“域”,“密码”)=>返回FALSE (不好!

所有scheme都打印step 1 step 2step 2

据我所知,鉴于合法凭证,上述内容应该保证模仿。 我在这里错过了什么?

Solutions Collecting From Web of "奇怪的窗户冒充行为"

在执行需要管理权限的任务之前,您不应该可编程地检查管理权限。 只要无条件地尝试任务,让API告诉你任务是否由于权限不足而失败,如果是这样,那么你可以决定是否只是在错误的任务中失败,或者提示用户输入凭据并再次尝试任务。

如果你试图和UAC一起玩,你应该做的是将你的管理代码作为一个单独的进程或者COM对象来实现,然后你可以在需要的时候以提升的状态运行这个进程/ COM而不必提升你的主进程。 让操作系统在操作系统需要时提示用户输入管理员凭据(并决定该提示的外观),不要自己手动执行。

雷米的答案是现货,你试图做的不是你的方案的正确的解决方案。 但是,根据文档,您的代码应该按照预期工作。 似乎有两个原因,为什么没有:

  1. 看起来,与文档相反,您无法在不保留SeImpersonatePrivilege情况下冒充通过LogonUser获取的令牌,其级别高于SecurityIdentification 。 [在Windows 7 SP1 x64上测试]

  2. CheckTokenMembership函数不能正常工作,如果您为标记传递NULL ,并且线程的模拟级别为SecurityIdentification 。 [同上。]

您可以通过使用OpenThreadToken显式提取模拟令牌而不是传递NULL作为令牌来轻松解决问题2,如下所示。 或者,您可以使用DuplicateToken主标记,并将复制的标记传递给CheckTokenMembership 。 如果你只想检查令牌的内容,那将是一个更有效的解决方案。

我没有意识到问题1的任何解决方法,除了在新的上下文中启动一个子进程来代表您的工作。 当然,正如雷米指出的那样,这就是你应该做的。

以下是我在测试中使用的一些代码,供参考:

 #include <Windows.h> #include <stdio.h> #include <conio.h> void fail(wchar_t * err) { DWORD dw = GetLastError(); printf("%ws: %u\n", err, dw); ExitProcess(1); } void impersonate(LPTSTR lpszUsername, LPTSTR lpszDomain, LPTSTR lpszPassword) { HANDLE hToken, hImpToken2; BOOL b; SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY; PSID AdministratorsGroup; DWORD dwLen; SECURITY_IMPERSONATION_LEVEL imp_level; if (!LogonUser(lpszUsername, lpszDomain, lpszPassword, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken)) fail(L"LogonUser"); if (!ImpersonateLoggedOnUser(hToken)) fail(L"ImpersonateLoggedOnUser"); if (!OpenThreadToken(GetCurrentThread(), MAXIMUM_ALLOWED, TRUE, &hImpToken2)) fail(L"OpenThreadToken"); if (!GetTokenInformation(hImpToken2, TokenImpersonationLevel, &imp_level, sizeof(imp_level), &dwLen)) fail(L"GetTokenInformation"); printf("Impersonation level: %u\n", imp_level); if (!AllocateAndInitializeSid( &NtAuthority, 2, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &AdministratorsGroup)) fail(L"AllocateAndInitializeSid"); if (!CheckTokenMembership(hImpToken2, AdministratorsGroup, &b)) fail(L"CheckTokenMembership"); if (!b) fail(L"membership"); } int main(int argc, char ** argv) { wchar_t password[1024]; wchar_t * ptr; for (ptr = password;; ptr++) { *ptr = _getwch(); if (*ptr == 13) break; } *ptr = '\0'; impersonate(L"Administrator", NULL, password); return 0; }