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

时间:2014-02-12 00:58:09

标签: windows batch-file

方案

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

  • 他们必须以管理员模式运行。
  • 它们必须在特定用户上下文下运行(具体而言,是Administrators组成员的本地用户)。

事实证明这非常具有挑战性。

好像有一些外部工具可以做到这一点,但我正在寻找Windows附带的解决方案。

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

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

  • 有效的解决方案不能依赖外部工具。由于Windows的新版本默认使用.NET和PowerShell,因此这些是有效的工具。
  • 有效的解决方案不能要求用户互动。这意味着如果弹出UAC窗口,或者需要任何用户确认,则解决方案无效。

请在发布前测试您的解决方案以确保其有效!如果您要提供指向其他解决方案的链接,请在发布之前验证链接的解决方案是否有效。许多声称对这个问题有解决方法的人实际上没有。

我尝试了什么

我尝试过使用Batch Scripts,PowerShell和C#。据我所知,这些技术都不能完成任务。它们都遭受同样的基本问题 - 以另一个用户身份运行任务并且在管理员模式下是相互排斥的进程。让我更具体一点:

为什么不批量

用于在不同用户上下文下运行的命令是Runas,它不会启动提升的进程。有几种外部工具可以解决这个问题,但如前所述,这些工具是不允许的。

为什么不是PowerShell

启动新进程Start-Process的命令可以提升新进程并以不同用户身份运行,但不能同时运行。我有一个悬而未决的问题here指的是这个问题。不幸的是,没有人提供解决方案,这让我相信这是不可能的。

为什么不是C#

这似乎也是不可能的,因为Process类似乎不支持在管理员模式下以及在不同用户的凭据下启动进程。

为什么不使用外部工具?

这迫使我依赖别人的代码来做正确的事情,我宁愿自己编码而不是那样做。事实上,我有一个solution比依赖别人好一步,但 相当hackish

  • 使用任务计划程序创建任务,以便在非常遥远的将来某个时间以指定帐户的管理员模式启动可执行文件。
  • 强制任务立即运行。
  • 等待任务完成。 Answered here

提前感谢任何试图提供帮助的人!非常感谢,我希望如果没有别的,其他人能够找到任务计划程序的工作。

1 个答案:

答案 0 :(得分:7)

好的,事实证明CreateProcessWithLogonW函数会过滤用户令牌,LogonUser也是如此。这似乎让我们陷入困境,因为我们没有正确的权限来纠正问题(请参阅脚注),但事实证明LogonUser 过滤令牌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导致程序出现功能问题。

因此,除非目标进程在后台运行,否则我们应该适当地分配权限。通常,最好创建一个新的窗口站和一个新的桌面,以隔离目标进程;但是,在这种情况下,目标进程将以管理员身份运行,所以没有意义 - 只需更改现有窗口站和桌面的权限,我们就可以让生活更轻松。

编辑2014年11月24日:更正了窗口站ACE中的访问权限,以便它们适用于非管理用户。请注意,这样做可能会使相关的非管理员用户破坏目标会话中的进程。

#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才能使用GetTokenInformationTokenLinkedToken来获取LogonUser生成的提升令牌的可用句柄。不幸的是,此权限通常仅在您作为本地系统运行时才可用。

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