CreateProcess 具有比调用者更低的权限

CreateProcess with lower privileges than the caller

我们有一个 运行 具有管理员权限的应用程序,其中(除了实际需要管理员权限的其他操作)用户可以发送电子邮件。

我们的电子邮件系统是这样工作的:admin-运行 应用程序预编译电子邮件字段并启动(通过 CreateProcess)调用实际电子邮件发送的我们的电子邮件应用程序。如果电子邮件已完成并准备就绪,它将直接发送,否则将显示 Outlook 电子邮件表单,让用户填写缺失的字段并发送。

我们的电子邮件应用程序使用 TJclEmail 来处理电子邮件发送和显示 Outlook 电子邮件表单。我的问题是:如果 Outlook 不是 运行 管理员,电子邮件应用程序将不会显示 Outlook 电子邮件表单,我猜是因为它是从 admin-运行 应用程序调用的,所以它继承了权限。由于 Outlook 几乎从未 运行 作为管理员,我想找到一种方法来调用 CreateProcess 具有普通用户权限,而不是从其调用者那里继承管理员权限。

有办法吗?

根据 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.)

然后文章继续展示一个例子,获取桌面的IShellFolderViewDual接口,从中获取IShellDispatch2接口,然后调用IShellDispatch2::ShellExecute()执行新进程作为 logged-in 用户(基本上与 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;
}

根据 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."

这里是那篇文章的例子:

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.