Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
930 views
in Technique[技术] by (71.8m points)

batch file - In Windows: How do you programatically launch a process in administrator mode under another user context?

Scenario

I have a remote computer that I want to run installers (arbitrary executables) on programatically. These installers require two things:

  • They must run in Administrator mode.
  • They must run under a specific user context (Specifically, a local user who is a member of the Administrators group).

This has proven to be very challenging.

It appears as though there are a few external tools that exist that do this, but I am looking for a solution that comes with Windows.

What a valid solution to this problem would look like

From an elevated context (e.g. an elevated batch file or executable program) a valid solution should be able to programatically launch a process in Administrator mode under another user context. Assume that the other user's id and password are available, and that the other user is a member of the Administrators group. Additional restrictions:

  • A valid solution cannot rely on an external tool. Since newer versions of Windows come with .NET and PowerShell by default, these are valid tools to use.
  • A valid solution cannot require user interactions. This means that if a UAC window pops up, or if any user confirmation is required, the solution is invalid.

Please test your solution before posting it to make sure it works! If you are going to provide a link to another solution, please verify that the linked solution works before posting. Many people who claim to have working solutions to this problem in fact do not.

What I have tried

I have tried using Batch Scripts, PowerShell, and C#. As far as I can tell, none of these technologies will accomplish the task. They all suffer from the same fundamental problem - running a task as another user and in Administrator mode are mutually exclusive processes. Let me be more specific:

Why Not Batch

The command that one would use to run under a different user context is Runas, which does not launch the process elevated. There are several external tools that claim to get around this, but as stated earlier these are not permitted.

Why Not PowerShell

The command to start a new process, Start-Process, can elevate a new process and run it as a different user, but not at the same time. I have an open question here referring to this issue. Unfortunately no one has provided a solution, which leads me to believe that it is impossible.

Why Not C#

This also appears to be impossible, as the Process class does not appear to support launching a process in Administrator mode and under a different user's credentials.

Why not an external tool?

This forces me to rely on someone else's code to do the right thing, and I would rather code it up myself than do that. In fact I have a solution that is one step better than relying on someone else, but is rather hackish:

  • Create a task using the Task Scheduler to launch the executable in administrator mode on the specified account at some time in the very distant future.
  • Force the Task to run immediately.
  • Wait to see if the task has finished. Answered here.

Thanks in advance to anyone who tries to help! It is greatly appreciated and I hope that if nothing else, other people are able to find this for the Task Scheduler work around.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

OK, so it turns out that CreateProcessWithLogonW function filters the user token, and so does LogonUser. This would seem to leave us stuck, since we don't have the right privileges to correct the problem (see footnote) but it turns out that LogonUser does not filter the token if you use LOGON32_LOGON_BATCH rather than LOGON32_LOGON_INTERACTIVE.

Here's some code that actually works. We use the CreateProcessAsTokenW function to launch the process, because this particular variant requires only SE_IMPERSONATE_NAME privilege, which is granted to administrator accounts by default.

This sample program launches a subprocess which creates a directory in c:windowssystem32, which would not be possible if the subprocess was not elevated.

#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
", GetLastError());
        return 1;
    }

    if (!CreateProcessWithTokenW(usertoken, LOGON_WITH_PROFILE, L"c:\windows\system32\cmd.exe", command, 0, NULL, NULL, &sinfo, &pinfo)) 
    {
        printf("CreateProcess: %u
", GetLastError());
        return 1;
    }

    return 0;
}

However, if the target process is a GUI process (including a process with a visible console) it won't display properly. Apparently CreateProcessWithTokenW only assigns the minimum desktop and window station permissions necessary for a process to run, which is not enough to actually display a GUI.

Even if you don't actually need to see the output, there's a risk that the broken GUI will cause functional problems with the program.

So, unless the target process runs in the background, we should probably assign permissions appropriately. In general, it is best to create a new window station and a new desktop, to isolate the target process; in this case, though, the target process is going to be running as admin anyway, so there's no point - we can make life easier by just changing the permissions on the existing window station and desktop.

Edit 24 November 2014: corrected access rights in window station ACE so they will work for non-administrative users. Note that doing this may allow the non-admin user in question to compromise processes in the target 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
", GetLastError());
        return 1;
    }

    if (!GetTokenInformation(usertoken, TokenUser, buffer_token_user, sizeof(buffer_token_user), &dw)) 
    {
        printf("GetTokenInformation(TokenUser): %u
", GetLastError());
        return 1;
    }

    window_station = GetProcessWindowStation();
    if (window_station == NULL)
    {
        printf("GetProcessWindowStation: %u
", 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
", GetLastError());
        return 1;
    }

    existing_sd = malloc(size);
    if (existing_sd == NULL)
    {
        printf("malloc failed
");
        return 1;
    }

    if (!GetUserObjectSecurity(window_station, &sec_info_dacl, existing_sd, size, &dw))
    {
        printf("GetUserObjectSecurity(window_station) call 2: %u
", GetLastError());
        return 1;
    }

    if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted))
    {
        printf("GetSecurityDescriptorDacl(window_station): %u
", GetLastError());
        return 1;
    }

    if (!dacl_present)
    {
        printf("no DACL present on window station
");
        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
", dw);
        return 1;
    }

    if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION))
    {
        printf("InitializeSecurityDescriptor(window_station): %u
", GetLastError());
        return 1;
    }

    if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE))
    {
        printf("SetSecurityDescriptorDacl(window_station): %u
", GetLastError());
        return 1;
    }

    if (!SetUserObjectSecurity(window_station, &sec_info_dacl, &new_sd))
    {
        printf("SetUserObjectSecurity(window_station): %u
", GetLastError());
        return 1;
    }

    free(existing_sd);
    LocalFree(new_dacl);

    desktop = GetThreadDesktop(GetCurrentThreadId());
    if (desktop == NULL)
    {
        printf("GetThreadDesktop: %u
", GetLastError());
        return 1;
    }

    if (!GetUserObjectSecurity(desktop, &sec_info_dacl, &dw, sizeof(dw), &size) && GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    {
        printf("GetUserObjectSecurity(desktop) call 1: %u
", GetLastError());
        return 1;
    }

    existing_sd = malloc(size);
    if (existing_sd == NULL)
    {
        printf("malloc failed
");
        return 1;
    }

    if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, size, &dw))
    {
        printf("GetUserObjectSecurity(desktop) call 2: %u
", GetLastError());
        return 1;
    }

    if (!GetUserObjectSecurity(desktop, &sec_info_dacl, existing_sd, 4096, &dw))
    {
        printf("GetUserObjectSecurity: %u
", GetLastError());
        return 1;
    }

    if (!GetSecurityDescriptorDacl(existing_sd, &dacl_present, &existing_dacl, &dacl_defaulted))
    {
        printf("GetSecurityDescriptorDacl: %u
", GetLastError());
        return 1;
    }

    if (!dacl_present)
    {
        printf("no DACL present
");
        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
", dw);
        return 1;
    }

    if (!InitializeSecurityDescriptor(&new_sd, SECURITY_DESCRIPTOR_REVISION))
    {
        printf("InitializeSecurityDescriptor: %u
", GetLastError());
        return 1;
    }

    if (!SetSecurityDescriptorDacl(&new_sd, TRUE, new_dacl, FALSE))
    {
        printf("SetSecurityDescriptorDacl: %u
", GetLastError());
        return 1;
    }

    if (!SetUserObjectSecurity(desktop, &sec_info_dacl, &new_sd))
    {
        printf("SetUserObjectSecurity(window_station): %u
", 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
", GetLastError());
        return 1;
    }

    return 0;
}

Note the use of LOGON_WITH_PROFILE. This is not necessary to display a GUI, and it slows down launching the process considerably, so remove it if you don't need it - but if you are an administrator, the most likely reason that you are launching a process as a different administrator is that you need something in that administrator's user profile. (Another scenario might be that you need to use a specific domain account in order to access resources on another machine.)


Footnote:

Specifically, you need SeTcbPrivilege in order to use GetTokenInformation and TokenLinkedToken to obtain a usable handle to the elevated token that LogonUser generates. Unfortunately, this privilege is usually only available if you are running as local system.

If you do not have SeTcbPrivilege you can still obtain a copy of the linked token, but in this case it is an impersonation token at SecurityIdentification level so is of no use when creating a new process. Thanks to RbMm for helping me clarify this.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...