在Windows中:如何以编程方式在另一个用户环境下以pipe理员模式启动进程?

脚本

我有一个远程计算机,我想以编程方式运行安装程序(任意可执行文件)。 这些安装程序需要两件事情:

  • 它们必须以pipe理员模式运行。
  • 它们必须在特定的用户上下文(特别是属于Administrators组的成员的本地用户)下运行。

这已被certificate是非常具有挑战性的。

看起来似乎存在一些外部工具,但是我正在寻找Windows附带的解决scheme。

这个问题的有效解决scheme是什么样的

从提升的上下文(例如,升级的batch file或可执行程序)中,有效的解决scheme应该能够在另一用户上下文中以编程方式在pipe理员模式下启动进程。 假定其他用户的ID和密码可用,而另一个用户是Administrators组的成员。 其他限制:

  • 有效的解决scheme不能依靠外部工具。 由于默认情况下Windows的更新版本与.NET和PowerShell一起提供,因此这些是有效的工具。
  • 有效的解决scheme不需要用户交互。 这意味着如果UAC窗口popup,或者需要任何用户确认,解决scheme是无效的。

请在发布之前testing您的解决scheme,以确保其正常工作! 如果您要提供另一个解决scheme的链接,请在发布前确认链接的解决scheme是否正常工作。 许多声称对这个问题有解决scheme的人其实并没有这样做。

我曾经尝试过

我曾尝试使用批处理脚本,PowerShell和C#。 据我所知,这些技术都不能完成任务。 他们都遭受同样的根本性问题 – 作为另一个用户来运行一个任务,在pipe理员模式下是相互排斥的过程。 让我更具体一点:

为什么不批

用于在不同的用户环境下运行的命令是Runas,它不会启动升级过程。 有几个外部工具声称解决这个问题,但正如前面所述,这是不允许的。

为什么不是PowerShell

启动新进程Start-Process的命令可以提升一个新进程,并以不同的用户身份运行,但不能同时运行。 这里提到这个问题我有一个公开的问题。 不幸的是,没有人提供了一个解决scheme,这使我相信这是不可能的。

为什么不是C#

这似乎也是不可能的,因为Process类似乎不支持以pipe理员模式和不同的用户凭证启动进程。

为什么不是一个外部工具?

这迫使我依靠别人的代码来做正确的事情,而我宁愿自己编写代码。 事实上,我有一个比依靠别人更好的解决scheme ,但是相当危险

  • 使用“任务计划程序”创build一个任务,以便在指定的帐户上以pipe理员模式启动可执行文件。
  • 强制任务立即运行。
  • 等待看看任务是否完成。 在这里回答 。

预先感谢任何人试图帮助! 这是非常感激,我希望如果没有别的,其他人能够find这个任务计划周围的工作。

好吧,事实certificate, CreateProcessWithLogonW函数过滤用户令牌, LogonUser也是如此。 这似乎让我们陷入困境,因为我们没有正确的权限来纠正问题(见脚注),但事实certificate,如果使用LOGON32_LOGON_BATCH而不是LOGON32_LOGON_INTERACTIVELogonUser不会过滤令牌。

这里有一些实际工作的代码。 我们使用CreateProcessAsTokenW函数来启动进程,因为这个特定的变体只需要SE_IMPERSONATE_NAME权限,默认授予pipe理员帐户。

这个示例程序将启动一个在c:\windows\system32创build一个目录的subprocess,如果subprocess没有被提升的话,这是不可能的。

 #define _WIN32_WINNT 0x0501 #include <Windows.h> #include <Sddl.h> #include <conio.h> #include <stdio.h> wchar_t command[] = L"c:\\windows\\system32\\cmd.exe /c md c:\\windows\\system32\\proof-that-i-am-an-admin"; int main(int argc, char **argv) { HANDLE usertoken; STARTUPINFO sinfo; PROCESS_INFORMATION pinfo; ZeroMemory(&sinfo, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken)) { printf("LogonUser: %u\n", GetLastError()); return 1; } if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\\windows\\system32\\cmd.exe", command, 0, NULL, NULL, &sinfo, &pinfo)) { printf("CreateProcess: %u\n", GetLastError()); return 1; } return 0; } 

但是,如果目标进程是一个GUI进程(包括一个具有可见控制台的进程),它将不能正确显示。 显然, CreateProcessWithTokenW只分配进程运行所需的最小桌面和窗口站点权限,这不足以实际显示GUI。

即使您实际上不需要查看输出,也可能会导致GUI被破坏,导致程序出现function问题。

所以,除非目标进程在后台运行,否则我们应该适当地分配权限。 一般来说,最好创build一个新的窗口站和一个新的桌面,以隔离目标进程; 在这种情况下,目标进程将以pipe理员身份运行,所以没有意义 – 只需更改现有窗口站点和桌面上的权限即可使生活更轻松。

编辑2014年11月24日:更正窗口站ACE中的访问权限,以便它们可以用于非pipe理用户。 请注意,这样做可能会导致有问题的非pipe理员用户在目标会话中损害进程。

 #define _WIN32_WINNT 0x0501 #include <Windows.h> #include <AccCtrl.h> #include <Aclapi.h> #include <stdio.h> wchar_t command[] = L"c:\\windows\\system32\\notepad.exe"; int main(int argc, char **argv) { HANDLE usertoken; STARTUPINFO sinfo; PROCESS_INFORMATION pinfo; HDESK desktop; EXPLICIT_ACCESS explicit_access; BYTE buffer_token_user[SECURITY_MAX_SID_SIZE]; PTOKEN_USER token_user = (PTOKEN_USER)buffer_token_user; PSECURITY_DESCRIPTOR existing_sd; SECURITY_DESCRIPTOR new_sd; PACL existing_dacl, new_dacl; BOOL dacl_present, dacl_defaulted; SECURITY_INFORMATION sec_info_dacl = DACL_SECURITY_INFORMATION; DWORD dw, size; HWINSTA window_station; if (!LogonUser(L"username", L"domain", L"password", LOGON32_LOGON_BATCH, LOGON32_PROVIDER_DEFAULT, &usertoken)) { printf("LogonUser: %u\n", GetLastError()); return 1; } if (!GetTokenInformation(usertoken, TokenUser, buffer_token_user, sizeof(buffer_token_user), &dw)) { printf("GetTokenInformation(TokenUser): %u\n", GetLastError()); return 1; } window_station = GetProcessWindowStation(); if (window_station == NULL) { printf("GetProcessWindowStation: %u\n", GetLastError()); return 1; } if (!GetUserObjectSecurity(window_station, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { printf("GetUserObjectSecurity(window_station) call 1: %u\n", GetLastError()); return 1; } existing_sd = malloc(size); if (existing_sd == NULL) { printf("malloc failed\n"); return 1; } if (!GetUserObjectSecurity(window_station, &sec_info_dacl, existing_sd, size, &dw)) { printf("GetUserObjectSecurity(window_station) call 2: %u\n", GetLastError()); return 1; } if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted)) { printf("GetSecurityDescriptorDacl(window_station): %u\n", GetLastError()); return 1; } if (!dacl_present) { printf("no DACL present on window station\n"); return 1; } explicit_access.grfAccessMode = SET_ACCESS; explicit_access.grfAccessPermissions = WINSTA_ALL_ACCESS | READ_CONTROL; explicit_access.grfInheritance = NO_INHERITANCE; explicit_access.Trustee.pMultipleTrustee = NULL; explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER; explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid; dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl); if (dw != ERROR_SUCCESS) { printf("SetEntriesInAcl(window_station): %u\n", dw); return 1; } if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION)) { printf("InitializeSecurityDescriptor(window_station): %u\n", GetLastError()); return 1; } if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE)) { printf("SetSecurityDescriptorDacl(window_station): %u\n", GetLastError()); return 1; } if (!SetUserObjectSecurity(window_station, &sec_info_dacl, &new_sd)) { printf("SetUserObjectSecurity(window_station): %u\n", GetLastError()); return 1; } free(existing_sd); LocalFree(new_dacl); desktop = GetThreadDesktop(GetCurrentThreadId()); if (desktop == NULL) { printf("GetThreadDesktop: %u\n", GetLastError()); return 1; } if (!GetUserObjectSecurity(desktop, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER) { printf("GetUserObjectSecurity(desktop) call 1: %u\n", GetLastError()); return 1; } existing_sd = malloc(size); if (existing_sd == NULL) { printf("malloc failed\n"); return 1; } if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, size, &dw)) { printf("GetUserObjectSecurity(desktop) call 2: %u\n", GetLastError()); return 1; } if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, 4096, &dw)) { printf("GetUserObjectSecurity: %u\n", GetLastError()); return 1; } if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted)) { printf("GetSecurityDescriptorDacl: %u\n", GetLastError()); return 1; } if (!dacl_present) { printf("no DACL present\n"); return 1; } explicit_access.grfAccessMode = SET_ACCESS; explicit_access.grfAccessPermissions = GENERIC_ALL; explicit_access.grfInheritance = NO_INHERITANCE; explicit_access.Trustee.pMultipleTrustee = NULL; explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER; explicit_access.Trustee.ptstrName = (LPTSTR)token_user->User.Sid; dw = SetEntriesInAcl(1, &explicit_access, existing_dacl, &new_dacl); if (dw != ERROR_SUCCESS) { printf("SetEntriesInAcl: %u\n", dw); return 1; } if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION)) { printf("InitializeSecurityDescriptor: %u\n", GetLastError()); return 1; } if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE)) { printf("SetSecurityDescriptorDacl: %u\n", GetLastError()); return 1; } if (!SetUserObjectSecurity(desktop, &sec_info_dacl, &new_sd)) { printf("SetUserObjectSecurity(window_station): %u\n", GetLastError()); return 1; } free(existing_sd); LocalFree(new_dacl); ZeroMemory(&sinfo, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\\windows\\system32\\notepad.exe", command, 0, NULL, NULL, &sinfo, &pinfo)) { printf("CreateProcess: %u\n", GetLastError()); return 1; } return 0; } 

请注意使用LOGON_WITH_PROFILE。 这不是显示GUI的必要条件,而且会大大减慢启动进程的速度,所以如果不需要它,就可以将其删除 – 但如果您是pipe理员,最有可能的原因是您以不同的pipe理员身份启动进程是你需要在pipe理员的用户configuration文件中的东西。 (另一种情况可能是您需要使用特定的域帐户才能访问另一台计算机上的资源。)


脚注:

具体而言,您需要SeTcbPrivilege才能使用GetTokenInformationTokenLinkedToken获取LogonUser生成的提升标记的可用句柄。 不幸的是,这个特权通常只有在你作为本地系统运行时才可用。

如果您没有SeTcbPrivilege您仍然可以获取链接令牌的副本,但在这种情况下,它是SecurityIdentification级别的模拟令牌,因此在创build新stream程时没有用处。 感谢RbMm帮助我澄清这一点。