Home > OS >  CreateProcess with lower privileges than the caller
CreateProcess with lower privileges than the caller

Time:04-04

We have an application that is run with admin privileges, where (apart from other operations that actually require admin privileges) the user can send emails.

Our email system works like this: admin-run application precompiles the email fields and launches (via CreateProcess) our email application that calls the actual email send. If the email is complete and ready it will send it directly, otherwise it will show the Outlook email form to let the user fill the missing fields and send.

Our email application uses TJclEmail to handle email sending and showing Outlook email form. My problem is this: the email application won't show the Outlook email form if Outlook isn't run as administrator, I guess because it's called from the admin-run application so it inherits privileges. Since Outlook is hardly ever run as administrator I'd like to find a way to call CreateProcess with normal user privileges, insted of inheriting admin privileges from its caller.

Is there a way to do so?

CodePudding user response:

Per How can I launch an unelevated process from my elevated process and vice versa?:

Going from an unelevated process to an elevated process is easy. You can run a process with elevation by passing the runas verb to Shell­Execute or Shell­Execute­Ex.

Going the other way is trickier. For one thing, it’s really hard to munge your token to remove the elevation nature properly. And for another thing, even if you could do it, it’s not the right thing to do, because the unelevated user may be different from the elevated user.

...

The solution here is to go back to Explorer and ask Explorer to launch the program for you. Since Explorer is running as the original unelevated user, the program (in this case, the Web browser) will run as Bob. This is also important in the case that the handler for the file you want to open runs as an in-process extension rather than as a separate process, for in that case, the attempt to unelevate would be pointless since no new process was created in the first place. (And if the handler for the file tries to communicate with an existing unelevated copy of itself, things may fail because of UIPI.)

And then the article goes on to show an example that gets the desktop's IShellFolderViewDual interface, and from that an IShellDispatch2 interface, and then calls IShellDispatch2::ShellExecute() to execute the new process as the logged-in user (which is basically the same example provided on MSDN: Execute In Explorer Sample):

#define STRICT
#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>

void FindDesktopFolderView(REFIID riid, void **ppv)
{
 CComPtr<IShellWindows> spShellWindows;
 spShellWindows.CoCreateInstance(CLSID_ShellWindows);

 CComVariant vtLoc(CSIDL_DESKTOP);
 CComVariant vtEmpty;
 long lhwnd;
 CComPtr<IDispatch> spdisp;
 spShellWindows->FindWindowSW(
     &vtLoc, &vtEmpty,
     SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);

 CComPtr<IShellBrowser> spBrowser;
 CComQIPtr<IServiceProvider>(spdisp)->
     QueryService(SID_STopLevelBrowser,
                  IID_PPV_ARGS(&spBrowser));

 CComPtr<IShellView> spView;
 spBrowser->QueryActiveShellView(&spView);

 spView->QueryInterface(riid, ppv);
}

void GetDesktopAutomationObject(REFIID riid, void **ppv)
{
 CComPtr<IShellView> spsv;
 FindDesktopFolderView(IID_PPV_ARGS(&spsv));
 CComPtr<IDispatch> spdispView;
 spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
 spdispView->QueryInterface(riid, ppv);
}

void ShellExecuteFromExplorer(
    PCWSTR pszFile,
    PCWSTR pszParameters = nullptr,
    PCWSTR pszDirectory  = nullptr,
    PCWSTR pszOperation  = nullptr,
    int nShowCmd         = SW_SHOWNORMAL)
{
 CComPtr<IShellFolderViewDual> spFolderView;
 GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
 CComPtr<IDispatch> spdispShell;
 spFolderView->get_Application(&spdispShell);
 CComQIPtr<IShellDispatch2>(spdispShell)
    ->ShellExecute(CComBSTR(pszFile),
                   CComVariant(pszParameters ? pszParameters : L""),
                   CComVariant(pszDirectory ? pszDirectory : L""),
                   CComVariant(pszOperation ? pszOperation : L""),
                   CComVariant(nShowCmd));
}

int __cdecl wmain(int argc, wchar_t **argv)
{
 if (argc < 2) return 0;
 CCoInitialize init;
 ShellExecuteFromExplorer(
    argv[1],
    argc >= 3 ? argv[2] : L"",
    argc >= 4 ? argv[3] : L"",
    argc >= 5 ? argv[4] : L"",
    argc >= 6 ? _wtoi(argv[5]) : SW_SHOWNORMAL);
 return 0;
}

And per How can I launch an unelevated process from my elevated process, redux:

There’s another way which is a bit more direct, but it assumes that the thing you want to do can be done with a direct Create­Process call. In other words, if you need the system to look up the user’s file associations or default browser, then this technique is not for you.

The idea is to take advantage of PROCESS_CREATE_PROCESS access and the accompanying PROC_THREAD_ATTRIBUTE_PARENT_PROCESS process thread attribute

...

Basically, this lets you tell the Create­Process function, "Hey, like, um, pretend that other guy over there is creating the process."

And here is the example from that article:

int main(int, char**)
{
  HWND hwnd = GetShellWindow();

  DWORD pid;
  GetWindowThreadProcessId(hwnd, &pid);

  HANDLE process =
    OpenProcess(PROCESS_CREATE_PROCESS, FALSE, pid);

  SIZE_T size;
  InitializeProcThreadAttributeList(nullptr, 1, 0, &size);
  auto p = (PPROC_THREAD_ATTRIBUTE_LIST)new char[size];

  InitializeProcThreadAttributeList(p, 1, 0, &size);
  UpdateProcThreadAttribute(p, 0,
    PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
    &process, sizeof(process),
    nullptr, nullptr);

  wchar_t cmd[] = L"C:\\Windows\\System32\\cmd.exe";
  STARTUPINFOEX siex = {};
  siex.lpAttributeList = p;
  siex.StartupInfo.cb = sizeof(siex);
  PROCESS_INFORMATION pi;

  CreateProcessW(cmd, cmd, nullptr, nullptr, FALSE,
    CREATE_NEW_CONSOLE | EXTENDED_STARTUPINFO_PRESENT,
    nullptr, nullptr, &siex.StartupInfo, &pi);

  CloseHandle(pi.hProcess);
  CloseHandle(pi.hThread);
  delete[] (char*)p;
  CloseHandle(process);
  return 0;
}

This program runs a copy of cmd.exe using the shell process (usually explorer.exe) as its parent, which means that if the shell process is unelevated, then so too will the cmd.exe process. Of course, if the user is an administrator and has disabled UAC, then Explorer will still be elevated, and so too will be the cmd.exe. But in that case, the user wants everything to run elevated, so you’re just following the user's preferences.

  • Related