【问题标题】:How to Launch an Exe at User Level from a Higher Level如何从更高级别启动用户级别的 Exe
【发布时间】:2011-01-04 07:37:04
【问题描述】:

我想要一个始终在用户级别运行的进程。当它由以管理员级别运行的安装程序(自定义,而不是 msi)启动时,或者当用户登录时。环顾四周,我不确定这是否可能。

【问题讨论】:

    标签: c++ winapi uac


    【解决方案1】:

    最简单的方法是拥有 2 个进程。一个是普通用户,它启动提升/管理进程。然后admin进程就可以使用IPC请求普通用户进程做事了。

    如果你没有正常的用户进程,那么Raymond Chen文档:

    从未提升的流程转到提升的流程很容易。您可以通过passing the runas verb 将进程提升到 ShellExecute 或 ShellExecuteEx。

    走另一条路比较棘手。一方面,要正确删除高程特性确实很难使用您的令牌。还有一件事,即使你能做到,也不是正确的做法,因为未提升的用户可能与提升的用户不同。

    这里的解决方案是返回 Explorer 并要求 Explorer 为您启动程序。由于 Explorer 以原始未提升用户身份运行,因此程序(在本例中为 Web 浏览器)将以 Bob 身份运行。这在您要打开的文件的处理程序作为进程内扩展而不是单独进程运行的情况下也很重要,因为在这种情况下,取消提升的尝试将毫无意义,因为没有创建新进程第一名。 (如果文件的处理程序尝试与自身的现有未提升副本进行通信,则可能会因为 UIPI 而失败。)

    好吧,我知道小程序不应该有动力,但我还是忍不住。唠叨够了。让我们编写代码。 (请记住,小程序很少或根本不进行错误检查,因为这是它们滚动的方式。)

    #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);
    }
    

    GetDesktopAutomationObject 函数定位桌面文件夹视图,然后请求视图的调度对象。然后我们以调用者请求的形式返回该调度对象。这个dispatch对象是一个ShellFolderView,它的C++接口是IShellFolderViewDual,所以大多数callres都会要求这个接口,但是如果你是一个受虐狂,你可以跳过双重接口直接与IDispatch对话。

    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));
    }
    

    ShellExecuteFromExplorer 函数首先获取桌面文件夹自动化对象。我们使用桌面并不是因为它特别有意义,而是因为我们知道它总会在那里。

    与桌面文件夹视图一样,我们对 ShellFolderView 对象本身并不感兴趣。这对我们来说很有趣,因为该对象驻留在托管桌面视图的进程中(这是主资源管理器进程)。从 ShellFolderView,我们请求 Application 属性,以便我们可以访问主 Shell.Application 对象,该对象具有 IShellDispatch 接口(及其扩展 IShellDispatch2 到 IShellDispatch6)作为其 C++ 接口。而我们真正想要的是 IShellDispatch2::ShellExecute 方法。

    我们使用适当的参数调用 IShellDispatch2::ShellExecute。请注意,IShellDispatch2::ShellExecute 的参数顺序与 ShellExecute 的参数顺序不同!

    好的,让我们把它放在一个小程序中。

    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;
    }
    

    程序接受一个强制性的命令行参数,它是要执行的东西,无论是程序、文档还是 URL。可选参数是正在执行的事物的参数,要使用的当前目录,要执行的操作以及应该如何打开窗口。

    打开提升的命令提示符,然后以各种方式运行此程序。

    • 从头开始http://www.msn.com/
      在用户的默认 Web 浏览器中打开未提升的 Web 页面。
    • 从头开始 cmd.exe ""C:\Users ""3
      在 C:\Users 处打开一个未提升的命令提示符,最大化。
    • 从头开始 C:\Path\To\Image.bmp "" "" 编辑
      在未提升的图像编辑器中编辑位图。

    【讨论】:

    • 非常有帮助。谢谢。它工作得很好。不幸的是,您必须小心不要过早调用 FindWindowSW。如果在桌面启动之前调用,它将返回带有 NULL 指针的 S_FALSE。当在 Windows 10 机器上设置新用户时,我们经常看到这种情况。我们的代码在完成之前触发。
    【解决方案2】:

    每个人都在寻找另一种方式。无论如何,这个code project 应该会有所帮助。

    【讨论】:

      【解决方案3】:

      假设您知道要以哪个用户身份运行并拥有他们的密码,CreateProcessWithLogonW Function 就会这样做。

      【讨论】:

        【解决方案4】:

        有很多 hacky 方法可以做到这一点(使用任务调度程序,注入 explorer.exe 等)

        获得正确用户的唯一方法(在 UAC 提升之前启动程序的用户(这可能与 shell/“登录”/“会话所有者”不是同一用户))是让安装程序运行两个自身的实例,一个未提升的“外部”实例,它通常只是通过使用带有 runas 动词的 ShellExecute[Ex] 启动自身来启动另一个实例。当需要启动中/低级别进程时,通过某种形式的 IPC 提升的实例告诉外部实例启动新进程。

        实施起来很麻烦,我建议不要在安装程序的末尾有一个运行复选框。

        【讨论】:

        • 这很好,但不是“唯一”(正确)的方式。您可以在不注入的情况下调用 IShellDispatch2::ShellExecute。
        • @Alexander:IShellDispatch2 将以与 Explorer.exe 相同的用户身份执行,这可能不是“正确”用户,并且在带有 cmd.exe shell 的服务器核心上根本无法工作......跨度>
        【解决方案5】:

        与比尔所说的类似,您也可以使用 CreateProcessAsUser() API 来做到这一点。

        1. 首先使用 LogonUser() 并为该进程需要运行的用户获取访问令牌。在这里,如果用户属于管理员组(如果您将 LOGON_FLAG 作为 LOGON32_LOGON_INTERACTIVE 传递,您将获得一个拆分令牌)。因此,如果您需要提升的管理员令牌,请将标志传递为 LOGON32_LOGON_BATCH。
        2. 使用上面获得的令牌,您可以调用 CreateProcessAsUser() 传递命令行和参数。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-02-25
          • 1970-01-01
          • 1970-01-01
          • 2015-05-22
          • 2017-11-24
          相关资源
          最近更新 更多