3

I am using IShellDispatch2.ShellExecute to run a process under standard user from my elevated process, as described in Raymond Chen's article. Unlike ShellExecuteEx, this method does not return any information about the process.

I need to know when the launched process finishes, and I may need its exit code. Is there a way to obtain a handle of this process (any other way than taking snapshots)?

lpVoid
  • 31
  • 2
  • Why can't you use ShellExecuteEx ? – battlmonstr Mar 07 '19 at 18:30
  • @battlmonstr - for run process under another, unelevated token – RbMm Mar 07 '19 at 18:30
  • If you do know the user details, maybe calling "runas" command is possible to run as demoted. Also this seems relevant: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/20a2d22b-6de6-449a-82f8-6b17e6ccd5be/how-to-createprocess-not-as-administrator – battlmonstr Mar 07 '19 at 18:47
  • 1
    if you have debug privilege you can take system process token, impersonate with it, then take your session token with `WTSQueryUserToken` or found it by logonsid from process enum and use `CreateProcessAsUserW` finally – RbMm Mar 07 '19 at 21:24
  • [How do I start a program as the desktop user from an elevated app?](https://blogs.msdn.microsoft.com/aaron_margosis/2009/06/06/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app/) – zett42 Mar 08 '19 at 10:47
  • @zett42 thanks, that approach might be useful – lpVoid Mar 08 '19 at 10:58
  • What do you know about the nature of this other program? I ask because arbitrary programs can do arbitrary things - such as re-launching themselves or farming our the real work to other processes and then terminating their own process far earlier than when someone might say "this program has done its work". Are you sure you won't be interacting wish such a program? – Damien_The_Unbeliever Mar 08 '19 at 11:55
  • @Damien_The_Unbeliever basically, I know whether the program behaves correctly under the limited account, or it needs access to HKLM etc. (in this case I can just use CreateProcess) – lpVoid Mar 12 '19 at 21:59

2 Answers2

0

You can't because the shell does not expose a ShellExecuteEx method, and even if it did, the returned process handle would not be valid in your process.

The least hacky solution I can think of is to create a little helper application that acts as a middleman between the shell and the real application you want to start. This middleman application can call ShellExecuteEx and send a message back to your real application when the child process has exited.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • possible of course create all without helper process, but code will be bit large – RbMm Mar 07 '19 at 21:25
  • @RbMm That really depends, searching the list of processes for a specific process name/path started after a certain timestamp can still give false-positives. A middleman process (could even be implemented in your main .EXE) is guaranteed not to give false-positives. – Anders Mar 07 '19 at 21:40
  • no, i have solution which not depends from process name/path/time and free from false positive – RbMm Mar 07 '19 at 21:43
  • we need use `CreateProcessAsUserW`. but here 2 problem - get user token and privileges (for call `CreateProcessAsUserW` and possible get user token, depend from way). we can get required privileges if we have initially debug privilege: enumerate processes, open it tokens, look are token containing required privileges, duplicate and impersonate. hor get user token we can use or `WTSQueryUserToken` (from the same terminal session) or again searh from processes with the same LogonId sid in token – RbMm Mar 07 '19 at 21:53
0

you can use CreateProcessAsUserW for start process with not elevated user token. but here exisr several problems - you need SE_INCREASE_QUOTA_NAME and SE_ASSIGNPRIMARYTOKEN_NAME privilege for call this api, if hToken is a restricted version of the caller's primary token.

second how get user token ? you can use WTSQueryUserToken for this, but for call this api you need have SE_TCB_NAME privilege

so you need have/get 3 privilege SE_ASSIGNPRIMARYTOKEN_NAME , SE_TCB_NAME, and SE_INCREASE_QUOTA_NAME. in general LocalSystem processes have it. we can open some of this process, if we have SE_DEBUG_PRIVILEGE.

so in general we need do next:

  • get self SessionId (need for call WTSQueryUserToken)
  • enable SE_DEBUG_PRIVILEGE in process or thread token (elevated token usually have this privilege)
  • search processes with token, which have required for as privilege
  • impersonate with this token
  • call WTSQueryUserToken
  • call CreateProcessAsUserW

----- code: ---------------

inline ULONG BOOL_TO_ERROR(BOOL f)
{
    return f ? NOERROR : GetLastError();
}

// use in _alloca(guz) because _alloca(0) work incorrect
// return 0 pointer instead allocates a zero-length item
volatile UCHAR guz;

ULONG takePrivileges(HANDLE hToken, ::PTOKEN_PRIVILEGES ptp, ULONG cb, BOOL& bContinue)
{
    if (ULONG PrivilegeCount = ptp->PrivilegeCount)
    {
        int n = 3;
        BOOL fAdjust = FALSE;

        ::PLUID_AND_ATTRIBUTES Privileges = ptp->Privileges;

        do 
        {
            switch (Privileges->Luid.LowPart)
            {
            case SE_ASSIGNPRIMARYTOKEN_PRIVILEGE:
            case SE_INCREASE_QUOTA_PRIVILEGE:
            case SE_TCB_PRIVILEGE:

                if (!(Privileges->Attributes & SE_PRIVILEGE_ENABLED))
                {
                    Privileges->Attributes |= SE_PRIVILEGE_ENABLED;
                    fAdjust = TRUE;
                }

                if (!--n)
                {
                    bContinue = FALSE;

                    ULONG dwError = BOOL_TO_ERROR(DuplicateTokenEx(hToken, 
                        TOKEN_ADJUST_PRIVILEGES|TOKEN_IMPERSONATE, 
                        0, ::SecurityImpersonation, ::TokenImpersonation, 
                        &hToken));

                    if (dwError == NOERROR)
                    {
                        if (fAdjust)
                        {
                            AdjustTokenPrivileges(hToken, FALSE, ptp, cb, NULL, NULL);
                            dwError = GetLastError();
                        }

                        if (dwError == NOERROR)
                        {
                            dwError = BOOL_TO_ERROR(SetThreadToken(0, hToken));
                        }

                        CloseHandle(hToken);
                    }

                    return dwError;
                }
            }
        } while (Privileges++, --PrivilegeCount);
    }

    return ERROR_NOT_FOUND;
}

ULONG GetPrivileges()
{
    ULONG dwError;

    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

    if (hSnapshot != INVALID_HANDLE_VALUE)
    {
        dwError = ERROR_NOT_FOUND;

        PROCESSENTRY32W pe = { sizeof(pe) };

        if (Process32FirstW(hSnapshot, &pe))
        {
            ULONG cb = 0, rcb = 0x100;

            PVOID stack = alloca(guz);

            union {
                PVOID buf;
                ::PTOKEN_PRIVILEGES ptp;
            };

            BOOL bContinue = TRUE;

            do 
            {
                if (HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe.th32ProcessID))
                {
                    HANDLE hToken;

                    if (OpenProcessToken(hProcess, TOKEN_QUERY|TOKEN_DUPLICATE, &hToken))
                    {
                        do 
                        {
                            if (cb < rcb)
                            {
                                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
                            }

                            if (GetTokenInformation(hToken, ::TokenPrivileges, buf, cb, &rcb))
                            {
                                dwError = takePrivileges(hToken, ptp, rcb, bContinue);
                                break;
                            }

                        } while (GetLastError() == ERROR_INSUFFICIENT_BUFFER);

                        CloseHandle(hToken);
                    }

                    CloseHandle(hProcess);
                }
            } while (bContinue && Process32NextW(hSnapshot, &pe));
        }

        CloseHandle(hSnapshot);
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}

ULONG RunNotElevated(PCWSTR lpApplicationName, PWSTR lpCommandLine, PCWSTR lpCurrentDirectory)
{
    HANDLE hToken, hDupToken = 0;

    ULONG SessionId;

    ULONG dwError = BOOL_TO_ERROR(ProcessIdToSessionId(GetCurrentProcessId(), &SessionId));

    if (NOERROR == dwError && 
        (NOERROR == (dwError = BOOL_TO_ERROR(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY|TOKEN_DUPLICATE, &hToken)))))
    {
        dwError = BOOL_TO_ERROR(DuplicateTokenEx(hToken, 
            TOKEN_IMPERSONATE|TOKEN_ADJUST_PRIVILEGES, 0,
            ::SecurityImpersonation, ::TokenImpersonation, &hDupToken));

        CloseHandle(hToken);

        if (dwError == NOERROR)
        {
            // get SE_DEBUG_PRIVILEGE

            static ::TOKEN_PRIVILEGES tp = { 1, { { {SE_DEBUG_PRIVILEGE }, SE_PRIVILEGE_ENABLED } } };

            AdjustTokenPrivileges(hDupToken, FALSE, &tp, 0, 0, 0);

            if ((dwError = GetLastError()) == NOERROR)
            {
                dwError = BOOL_TO_ERROR(SetThreadToken(0, hDupToken));
            }

            CloseHandle(hDupToken);

            if (dwError == NOERROR)
            {
                if (NOERROR == (dwError = GetPrivileges()))
                {
                    STARTUPINFOW si = { sizeof(si) };
                    PROCESS_INFORMATION pi;

                    PVOID lpEnvironment;

                    if (WTSQueryUserToken(SessionId, &hToken))
                    {
                        dwError = BOOL_TO_ERROR(CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE));

                        if (dwError == NOERROR)
                        {
                            dwError = BOOL_TO_ERROR(CreateProcessAsUserW(
                                hToken, lpApplicationName, lpCommandLine, 
                                NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT,
                                lpEnvironment, lpCurrentDirectory, &si, &pi));

                            DestroyEnvironmentBlock(lpEnvironment);
                        }
                        CloseHandle(hToken);

                        if (dwError == NOERROR)
                        {
                            CloseHandle(pi.hThread);
                            CloseHandle(pi.hProcess);
                        }
                    }
                }

                SetThreadToken(0, 0);
            }
        }
    }

    return dwError;
}

void test_r()
{
    WCHAR cmd[MAX_PATH];
    if (GetEnvironmentVariable(L"comspec", cmd, RTL_NUMBER_OF(cmd)))
    {
        RunNotElevated1(cmd, L"cmd /k whoami /all",0);
    }
}
RbMm
  • 31,280
  • 3
  • 35
  • 56