场景
我有一台远程计算机,我想以编程方式在其上运行安装程序(任意可执行文件)。这些安装程序需要两件事:
- 它们必须以管理员模式运行。
- 它们必须在特定用户环境下运行(具体来说,是管理员组成员的本地用户)。
事实证明这非常具有挑战性。
似乎有一些外部工具可以执行此操作,但我正在寻找 Windows 附带的解决方案。
这个问题的有效解决方案是什么样的
从提升的上下文(例如,提升的批处理文件或可执行程序),有效的解决方案应该能够以编程方式在另一个用户上下文下以管理员模式启动进程。假设另一个用户的 id 和密码可用,并且另一个用户是 Administrators 组的成员。附加限制:
- 有效的解决方案不能依赖外部工具。由于较新版本的 Windows 默认带有 .NET 和 PowerShell,因此这些都是可用的有效工具。
- 有效的解决方案不需要用户交互。这意味着如果弹出 UAC 窗口,或者需要任何用户确认,则解决方案无效。
请在发布之前测试您的解决方案以确保其有效!如果您要提供指向另一个解决方案的链接,请在发布前验证链接的解决方案是否有效。许多声称对这个问题有有效解决方案的人实际上并没有。
我尝试过的
我尝试过使用批处理脚本、PowerShell 和 C#。据我所知,这些技术都无法完成这项任务。它们都面临着相同的基本问题——以另一个用户和管理员模式运行任务是互斥的进程。让我更具体一点:
为什么不批量
用于在不同用户上下文下运行的命令是 Runas,它不会启动提升的进程。有几种外部工具声称可以解决这个问题,但如前所述,这些工具是不允许的。
为什么不是 PowerShell
启动新进程的命令 Start-Process 可以提升新进程并以不同的用户身份运行它,但不能同时运行。我有一个悬而未决的问题 here提到这个问题。不幸的是,没有人提供解决方案,这让我相信这是不可能的。
为什么不是 C#
这似乎也是不可能的,因为 Process 类似乎不支持在管理员模式下和在不同用户的凭据下启动进程。
为什么不用外部工具?
这迫使我依赖别人的代码来做正确的事情,我宁愿自己编写代码也不愿这样做。事实上我有一个 solution这比依赖别人好一步,但是相当老套:
- 使用任务计划程序创建一个任务,以便在遥远的将来某个时间在指定帐户上以管理员模式启动可执行文件。
- 强制任务立即运行。
- 等着看任务是否完成。 Answered here .
提前感谢任何试图提供帮助的人!非常感谢,我希望如果不出意外,其他人能够找到此任务计划程序解决方法。
最佳答案
好的,结果是 CreateProcessWithLogonW
函数过滤用户 token ,LogonUser
也是如此.这似乎让我们陷入困境,因为我们没有纠正问题的正确权限(请参阅脚注),但事实证明 LogonUser
确实不过滤 token 如果您使用 LOGON32_LOGON_BATCH
而不是 LOGON32_LOGON_INTERACTIVE
。
这是一些实际有效的代码。我们使用 CreateProcessAsTokenW
函数来启动进程,因为这个特定的变体只需要 SE_IMPERSONATE_NAME
权限,默认情况下授予管理员帐户。
此示例程序启动了一个子进程,该子进程在 c:\windows\system32
中创建了一个目录,如果子进程未被提升,这将是不可能的。
#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 也有可能导致程序出现功能问题。
因此,除非目标进程在后台运行,否则我们应该适本地分配权限。一般来说,最好新建一个window station和一个新的桌面,隔离目标进程;不过,在这种情况下,目标进程无论如何都将以管理员身份运行,所以没有意义 - 我们可以通过更改现有窗口站和桌面上的权限来简化工作。
2014 年 11 月 24 日编辑:更正了窗口站 ACE 中的访问权限,以便它们适用于非管理用户。请注意,这样做可能会允许相关的非管理员用户破坏目标 session 中的进程。
#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 所必需的,并且它会大大减慢启动进程的速度,因此如果您不需要它,请将其删除 - 但如果您是管理员,最有可能的原因是您以不同的管理员身份启动进程是您需要该管理员的用户配置文件中的内容。 (另一种情况可能是您需要使用特定的域帐户才能访问另一台计算机上的资源。)
脚注:
具体来说,您需要 SeTcbPrivilege
才能使用 GetTokenInformation
和 TokenLinkedToken
获取 LogonUser 提升 token 的可用句柄
生成。不幸的是,此权限通常仅在您作为本地系统运行时才可用。
如果您没有 SeTcbPrivilege
,您仍然可以获得链接 token 的副本,但在这种情况下,它是 SecurityIdentification
级别的模拟 token ,因此不在创建新进程时使用。感谢 RbMm 帮助我澄清这一点。
关于windows - 在 Windows 中 : How do you programatically launch a process in administrator mode under another user context?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21716527/