场景

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

  • 它们必须在管理员模式下运行。
  • 它们必须在特定的用户上下文(尤其是Administrators组成员的本地用户)下运行。

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

    似乎有一些外部工具可以执行此操作,但是我正在寻找Windows附带的解决方案。

    这个问题的有效解决方案看起来像

    从提升的上下文(例如,提升的批处理文件或可执行程序),有效的解决方案应该能够在另一个用户上下文下以管理员模式以编程方式启动进程。假定另一个用户的ID和密码可用,并且另一个用户是Administrators组的成员。其他限制:
  • 有效的解决方案不能依赖外部工具。由于Windows的较新版本默认情况下随.NET和PowerShell一起提供,因此这些都是有效的工具。
  • 有效的解决方案不能要求用户交互。这意味着,如果弹出一个UAC窗口,或者需要任何用户确认,则该解决方案无效。

  • 请在发布解决方案之前对其进行测试,以确保其有效!如果要提供到另一个解决方案的链接,请在发布之前验证链接的解决方案是否有效。实际上,许多声称拥有解决此问题的方法的人没有。

    我尝试过的

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

    为什么不批处理

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

    为什么不使用PowerShell

    启动新进程的命令Start-Process可以提升新进程并以其他用户身份运行它,但不能同时运行。我有一个关于这个问题的开放问题here。不幸的是,没有人提供解决方案,这使我相信这是不可能的。

    为什么不使用C#

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

    为什么不使用外部工具?

    这迫使我依靠别人的代码来做正确的事情,我宁愿自己编写代码,也不愿这样做。实际上,我的solution比依赖其他人好了一步,但是而不是朴实的:
  • 在不久的将来的某个时候,使用任务计划程序创建任务以在管理员模式下以指定帐户启动可执行文件。
  • 强制任务立即运行。
  • 等待查看任务是否完成。 Answered here

  • 在此先感谢任何尝试提供帮助的人!非常感谢,我希望如果没有其他事情,其他人能够在Task Scheduler中找到解决方法。

    最佳答案

    OK,事实证明 CreateProcessWithLogonW 函数过滤了用户 token , LogonUser 也是如此。这似乎使我们陷入困境,因为我们没有纠正问题的正确权限(请参阅脚注),但事实证明,如果您使用LogonUser而不是LOGON32_LOGON_BATCHLOGON32_LOGON_INTERACTIVE不会过滤 token 。

    这是一些实际有效的代码。我们使用 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和新的桌面,以隔离目标进程。但是,在这种情况下,目标进程仍将以admin身份运行,所以没有意义-我们只需更改现有window station和桌面上的权限就可以使生活更轻松。

    编辑2014年11月24日:更正了Window station 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才能使用GetTokenInformationTokenLinkedToken获得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/

    10-11 22:52
    查看更多