【问题标题】:In Windows: How do you programatically launch a process in administrator mode under another user context?在 Windows 中:如何以编程方式在另一个用户上下文中以管理员模式启动进程?
【发布时间】:2014-03-10 02:31:36
【问题描述】:

场景

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

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

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

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

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

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

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

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

我的尝试

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

为什么不批量处理

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

为什么不使用 PowerShell

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

为什么不用 C#

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

为什么不用外部工具?

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

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

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

【问题讨论】:

  • 正在启动的进程是您自己编写的吗?你知道 Manifest 吗?
  • @paddy 这个过程是由我自己写的东西启动的。我不熟悉清单。
  • 听起来您应该在加入域的 PC 上使用 domain services to install SW,可能是通过组策略。
  • 安装程序是否需要与用户交互或对用户可见?
  • @Fozi 如果您可以使用服务为这个问题提供一个优雅的解决方案,我很乐意看到它:)。

标签: windows batch-file


【解决方案1】:

好的,所以CreateProcessWithLogonW 函数过滤了用户令牌,LogonUser 也是如此。这似乎让我们陷入困境,因为我们没有正确的权限来纠正问题(见脚注)但事实证明,如果您使用 @987654327,LogonUser 确实过滤令牌@ 而不是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 日编辑:更正了 Windows Station 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 帮助我澄清这一点。

【讨论】:

  • 我尝试编译并运行此示例程序,但 cmd.exe 似乎以非管理员模式启动。是否需要将标志或开关传递给 CreateProcessWithLogonW 以使其以管理员模式启动 cmd?
  • 我必须道歉;你说的很对,这里也不行。我发现这非常令人惊讶。安全日志显示该帐户以管理员权限登录,我只能猜测在更改登录 ID 的同时正在过滤令牌。
  • 好的,我已经发布了一些新代码,实际上可以工作(至少在我的机器上)。
  • 您必须更改窗口站和桌面权限才能允许新进程显示 GUI。我添加了新的示例代码来启动 GUI 进程。
  • 难以置信。这真是令人印象深刻。我一直在寻找的一切以及更多!
猜你喜欢
  • 1970-01-01
  • 2023-04-09
  • 2012-11-30
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 2022-01-01
  • 1970-01-01
相关资源
最近更新 更多