CreateProcessAsUser results in process exit and error 0xc0000142 when the process starts

111 Views Asked by At

I am creating a simple console application to learn the usage of CreateProcessAsUser().

I have followed what little code examples I can find online. I have also completed a similar example application that uses CreateProcess() (following more or less this example), and that worked fine.

I basically modified that example to use LogonUser(), then pass that token to CreateProcessAsUser(). I also used CreateEnvironmentBlock() to get the environment of the token. Although I'm not sure that's necessary, examples I saw included that step. I printed out the results of CreateEnvironmentBlock(), and they are as I expect, including showing that the current user is the one I'm trying to logon as.

My problem: CreateProcessAsUser() executes without error, but the child process whoami.exe immediately creates a popup saying:

The application was unable to start correctly (0xc0000142). Click OK to close the application.

image

I am launching my application from an administrator command prompt, and through Local Security Policy I have granted administrators and my specific account the User Rights SeAssignPrimaryTokenPrivilege and SeIncreaseQuotaPrivilege, which I read are required to call CreateProcessAsUser(). I am a little uncertain if there is more I need to do to ensure my calling process has these privileges. Do I need to programmatically enable these permissions too?

The logon account username below is a normal account. It has the permission to access and execute whoami.exe and to logon interactively. I do not believe it requires other permissions (?).

Here is my code snippet:

int main()
{
    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);

    ZeroMemory(&pi, sizeof(pi));

    WCHAR cmd_line[] = L"C:\\Windows\\System32\\whoami.exe";

    HANDLE token = INVALID_HANDLE_VALUE;
    LPVOID proc_env = NULL;
    char username[MAX_PATH] = "username";

    BOOL success = LogonUserA(username, "machine_domain", "password",
        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token);
    if (!success)
    {
        std::cout << "LogonUser() GLE " << GetLastError() << std::endl;
    }
    if (token == NULL || token == INVALID_HANDLE_VALUE)
    {
        std::cout << "token is bad" << std::endl;
    }

    // Use token to get environment.
    success = CreateEnvironmentBlock(&proc_env, token, FALSE);
    if (!success)
    {
        std::cout << "CreateEnvironmentBlock() GLE " << GetLastError() << std::endl;
        CloseHandle(token);
        return 1;
    }
    if (proc_env == NULL)
    {
        std::cout << "procenv is null" << std::endl;
    }

    success = CreateProcessAsUser(
        token,
        NULL,   // app name
        cmd_line,    // includes app name.
        NULL,   // proc security attrs
        NULL,   // thread security attrs
        FALSE,   // inherit handles
        CREATE_UNICODE_ENVIRONMENT, // creation flags
        proc_env,    // env
        L"C:\\",   // current dir
        &si,    // startupinfo
        &pi);
    if (!success)
    {
        std::cout << "CreateProcessAsUser() GLE " << GetLastError() << std::endl;
        return 1;
    }

    // Wait until child process exits.
    WaitForSingleObject(pi.hProcess, INFINITE);

    // Close process and thread handles. 
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}

I have tried many other options in CreationFlags (eg CREATE_UNICODE_ENVIRONMENT), and also I have tried setting STARTUPINFO's lpDesktop to "winsta0\\default", as suggested in some posts. Everything I tried had the same result, but maybe I'm missing the correct option.

Edit 1

I made slight edits to ensure STARTUPINFO's std handles were configured with pipes correctly, which included changing lbInherit to TRUE as required when STARTF_USESTDHANDLES is specified. The below code works for me if the target user is Administrator. The pipe outputs the whoami results for the target user as expected! (I tried NULL L"" and L"winsta0\\default" as lpDesktop and all worked.) However I am getting the same failure as before when the target user is not admin. I found these articles suggesting the launched process needs permission to access the desktop winsta0\default. Is it possible to grant this permission to a non-admin account? I also see the suggestion to try CreateProcessWithLogon() which I will try.

int main()
{

    WCHAR cmd_line[] = L"C:\\Windows\\System32\\whoami.exe";

    HANDLE token = INVALID_HANDLE_VALUE;
    LPVOID proc_env = NULL;
    char username[MAX_PATH] = "target_user";

    BOOL success = LogonUserA(username, "domain_local", "password",
        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &token);
    if (!success)
    {
        std::cout << "LogonUser() GLE " << GetLastError() << std::endl;
    }
    if (token == NULL || token == INVALID_HANDLE_VALUE)
    {
        std::cout << "token is bad" << std::endl;
    }

    // Use token to get environment.
    success = CreateEnvironmentBlock(&proc_env, token, FALSE);
    if (!success)
    {
        std::cout << "CreateEnvironmentBlock() GLE " << GetLastError() << std::endl;
        CloseHandle(token);
        return 1;
    }
    if (proc_env == NULL)
    {
        std::cout << "procenv is null" << std::endl;
    }

    HANDLE child_write_pipe = NULL;  // child's STDOUT
    HANDLE parent_read_pipe = NULL;

    SECURITY_ATTRIBUTES sa = { 0 };
    sa.bInheritHandle = TRUE;
    sa.lpSecurityDescriptor = NULL;
    sa.nLength = sizeof(sa);
    // Create pipe to communicate with child.
    success = CreatePipe(&parent_read_pipe, &child_write_pipe, &sa, 0);
    if (!success)
    {
        std::cout << "CreatePipe fail" << std::endl;
        return 1;
    }

    success = SetHandleInformation(parent_read_pipe, HANDLE_FLAG_INHERIT, 0);
    if (!success)
    {
        std::cout << "SetHandleInformation fail" << std::endl;
        return 1;
    }

    STARTUPINFO si;
    PROCESS_INFORMATION pi;

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
    si.hStdOutput = child_write_pipe;
    si.hStdError = child_write_pipe;
    si.hStdInput = NULL;
    //// Prevent cmd window pop up.
    si.wShowWindow = SW_HIDE;
    WCHAR lpd[] = L"";
    si.lpDesktop = lpd;

    ZeroMemory(&pi, sizeof(pi));

    // Token must have TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY.
    success = CreateProcessAsUser(
        token,
        NULL,   // app name
        cmd_line,    // includes app name.
        NULL,   // proc security attrs
        NULL,   // thread security attrs
        TRUE,   // inherit handles
        CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // creation flags
        proc_env,    // env
        L"C:\\",   // current dir
        &si,    // startupinfo
        &pi);
    if (!success)
    {
        std::cout << "CreateProcessAsUser() GLE " << GetLastError() << std::endl;
        return 1;
    }

    // Read the results from child (POLL / BLOCK).
    char* result_buf = NULL;
    _result_read(pi.hProcess, parent_read_pipe, &result_buf);
    if (result_buf)
    {
        std::cout << "result: " << result_buf << std::endl;
        // Copy into result object.
        free(result_buf);
    }

    // get exit code.
    DWORD ec = 0;
    success = GetExitCodeProcess(pi.hProcess, &ec);
    std::cout << "exit code: " << ec << std::endl;

    // Close process and thread handles. 
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    return 0;
}
0

There are 0 best solutions below