【问题标题】:How to launch non-elevated administrator process from elevated administrator context on Windows 10?如何在 Windows 10 上从提升的管理员上下文启动非提升的管理员进程?
【发布时间】:2016-10-23 05:12:14
【问题描述】:

有没有一种简单的方法可以从提升的管理员进程创建正常的管理员进程(未提升)? 我正在使用 Windows 10 专业版。情况是我正在尝试制作某种部署工具。该工具将在提升的管理员上下文中运行,以便将文件写入“程序文件”(并访问其他特权资源)。但是其中一个步骤是调用外部程序。该程序在使用提升的管理员权限创建时似乎存在奇怪的问题。我们必须在非提升的管理员上下文中启动它。 我在 MSDN 博客中尝试了这种方法,https://blogs.msdn.microsoft.com/winsdk/2010/05/31/dealing-with-administrator-and-standard-users-context 它根本不起作用。

【问题讨论】:

  • 请分享一些代码和步骤,说明您是如何尝试以及如何失败的。
  • 如果我能找到他们我会的。但实际上我从我提供的链接中复制了代码。我了解该示例试图显示关于提升管理员和非提升管理员的两个链接令牌。通过得到提升的,你可以找到链接的部分。使用链接部分,您可以获得 CreateProcessWithToken 所需的参数。在 Windows 10 上,结论是不正确的。可以获取链接的令牌,但那个似乎是一种缩小版本或令牌的视图。它只是不能在 CreateProcessWithToken 中使用。
  • 有一个名为 ProcessHacker 的开源项目,据我了解,它是来自 sysinternals 的 ProcessExplorer 的克隆。我已经从 ProcessHacker 移植了代码。他们的方法是获取提升的管理员令牌并调整其安全描述符。它似乎适用于像记事本这样的简单程序,但无法像我们的例子那样启动复杂的程序。我倾向于相信他们制造的令牌是近似值,但仍然不是与非提升管理员相关的真实令牌。

标签: c++ windows winapi


【解决方案1】:

Raymond Chen 在他的 MSDN 上的“Old New Thing”博客中回答了这个确切的问题:

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

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

...

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

Raymond 使用IShellFolderViewDualIShellDispatch2 来实现1

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

// FindDesktopFolderView incorporated by reference

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

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

scratch http://www.msn.com/
在用户的默认 Web 浏览器中打开未提升的网页。

scratch cmd.exe "" C:\Users "" 3
在 C:\Users 打开一个未提升的命令提示符,最大化。

scratch C:\Path\To\Image.bmp "" "" edit
在未提升的图像编辑器中编辑位图

1FindDesktopFolderView()的实现在Raymond博客的另一篇文章中:

Manipulating the positions of desktop icons:

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

【讨论】:

  • 谢谢!我确实尝试过 shellexecute 自己的方法,但它对我不起作用。但是与您发布的代码相比,我的 shellexecute 代码相当简单。也许差异确实是这里的关键。我会尝试并报告。
  • @H.Tao:您尝试使用实际的ShellExecute() 函数,而Raymond 正在使用IShellDispatch2.ShellExecute() 接口方法来委托给未提升的用户运行的Explorer 副本。
  • 我做了一个快速测试。到目前为止它有效。谢谢!顺便说一句:胡安的解决方案。 S 也有效。但我觉得这里呈现的方式更优雅。
  • 因为我刚刚发现了这段代码的用途,所以我在my answer 中通过 C++ 异常添加了一些错误处理。谢谢!
  • 没有理由使用 com.我认为这种方式过于复杂了。你可以简单地使用 ShellExecuteW(nullptr, L"open", L"explorer.exe", "somepathtoopen", nullptr, NULL);
【解决方案2】:

这是我的 Raymond Chen's code 版本,它通过 C++ 异常添加了错误处理。

首先,我们声明一些帮助函数,用于从HRESULT 抛出std::system_error 异常,将 GUID 转换为字符串和用于 COM 初始化的 RAII 包装器。

#include <windows.h>
#include <shldisp.h>
#include <shlobj.h>
#include <exdisp.h>
#include <atlbase.h>
#include <stdlib.h>
#include <iostream>
#include <system_error>

template< typename T >
void ThrowIfFailed( HRESULT hr, T&& msg )
{
    if( FAILED( hr ) )
        throw std::system_error{ hr, std::system_category(), std::forward<T>( msg ) };
}

template< typename ResultT = std::string >
ResultT to_string( REFIID riid )
{
    LPOLESTR pstr = nullptr;
    if( SUCCEEDED( ::StringFromCLSID( riid, &pstr ) ) ) 
    {
        // Use iterator arguments to cast from wchar_t to char if element type of ResultT is char.
        ResultT result{ pstr, pstr + wcslen( pstr ) };
        ::CoTaskMemFree( pstr ); pstr = nullptr;
        return result;
    }
    return {};
}

struct ComInit
{
    ComInit() { ThrowIfFailed( ::CoInitialize( nullptr ), "Could not initialize COM" ); }
    ~ComInit() { ::CoUninitialize(); }
    ComInit( ComInit const& ) = delete;
    ComInit& operator=( ComInit const& ) = delete;
};

接下来是执行实际工作的函数。这与Remy Lebeau's answer 中的代码基本相同,但增加了错误处理(以及我个人的格式化风格)。

void FindDesktopFolderView( REFIID riid, void **ppv )
{
    CComPtr<IShellWindows> spShellWindows;
    ThrowIfFailed( 
        spShellWindows.CoCreateInstance( CLSID_ShellWindows ),
        "Could not create instance of IShellWindows" );

    CComVariant vtLoc{ CSIDL_DESKTOP };
    CComVariant vtEmpty;
    long lhwnd = 0;
    CComPtr<IDispatch> spdisp;
    ThrowIfFailed( 
        spShellWindows->FindWindowSW(
            &vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp ),
        "Could not find desktop shell window" );

    CComQIPtr<IServiceProvider> spProv{ spdisp };
    if( ! spProv )
        ThrowIfFailed( E_NOINTERFACE, "Could not query interface IServiceProvider" );

    CComPtr<IShellBrowser> spBrowser;
    ThrowIfFailed( 
        spProv->QueryService( SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser) ),
        "Could not query service IShellBrowser" );

    CComPtr<IShellView> spView;
    ThrowIfFailed( 
        spBrowser->QueryActiveShellView( &spView ),
        "Could not query active IShellView" );

    ThrowIfFailed( 
        spView->QueryInterface( riid, ppv ),
        "Could not query interface " + to_string( riid ) + " from IShellView" );
}

void GetDesktopAutomationObject( REFIID riid, void **ppv )
{
    CComPtr<IShellView> spsv;
    FindDesktopFolderView( IID_PPV_ARGS(&spsv) );

    CComPtr<IDispatch> spdispView;
    ThrowIfFailed( 
        spsv->GetItemObject( SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView) ),
        "Could not get item object SVGIO_BACKGROUND from IShellView" );
    ThrowIfFailed( 
        spdispView->QueryInterface( riid, ppv ),
        "Could not query interface " + to_string( riid ) + " from ShellFolderView" );
}

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;
    ThrowIfFailed( 
        spFolderView->get_Application( &spdispShell ),
        "Could not get application object from IShellFolderViewDual" );

    CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
    if( !spdispShell2 )
        ThrowIfFailed( E_NOINTERFACE, "Could not query interface IShellDispatch2" );

    ThrowIfFailed( 
        spdispShell2->ShellExecute(
            CComBSTR{ pszFile },
            CComVariant{ pszParameters ? pszParameters : L"" },
            CComVariant{ pszDirectory ? pszDirectory : L"" },
            CComVariant{ pszOperation ? pszOperation : L"" },
            CComVariant{ nShowCmd } ),
        "ShellExecute failed" );
}

用法示例:

int main()
{
    try
    {
        ComInit init;
        ShellExecuteFromExplorer( L"http://www.stackoverflow.com" );
    }
    catch( std::system_error& e )
    {
        std::cout << "ERROR: " << e.what() << "\n"
            << "Error code: " << e.code() << std::endl;
    }
}

补充说明:

使用此方法时,您可能会注意到已启动应用程序的窗口并不总是出现在前台,尤其是在它已经在运行的情况下。我的解决方法是在调用ShellExecuteFromExplorer() 之前调用AllowSetForegroundWindow( ASFW_ANY ) 以使进程能够将自身置于前台(我们无法指定进程ID,因为我们不知道将要预先启动的进程)。

【讨论】:

    【解决方案3】:

    这是 Raymond Chen 的方法,有错误处理,没有可悲的异常。

    #include <atlbase.h>
    #include <Shlobj.h>
    
    HRESULT FindDesktopFolderView(REFIID riid, void **ppv)
    {
    HRESULT hr;
    CComPtr<IShellWindows> spShellWindows;
    hr = spShellWindows.CoCreateInstance(CLSID_ShellWindows);
    if(FAILED(hr))
        return hr;
    
    CComVariant vtLoc { 0 };    // 0 = CSIDL_DESKTOP
    CComVariant vtEmpty;
    long lhwnd = 0;
    CComPtr<IDispatch> spdisp;
    hr = spShellWindows->FindWindowSW(&vtLoc, &vtEmpty, SWC_DESKTOP, &lhwnd, SWFO_NEEDDISPATCH, &spdisp);
    if(FAILED(hr))
        return hr;
    
    CComQIPtr<IServiceProvider> spProv{ spdisp };
    if(!spProv)
        return E_NOINTERFACE;
    
    CComPtr<IShellBrowser> spBrowser;
    hr = spProv->QueryService(SID_STopLevelBrowser, IID_PPV_ARGS(&spBrowser));
    if(FAILED(hr))
        return hr;
    
    CComPtr<IShellView> spView;
    hr = spBrowser->QueryActiveShellView(&spView);
    if(FAILED(hr))
        return hr;
    
    return spView->QueryInterface(riid, ppv);
    }
    
    
    HRESULT GetDesktopAutomationObject(REFIID riid, void **ppv)
    {
    HRESULT hr;
    CComPtr<IShellView> spsv;
    hr = FindDesktopFolderView(IID_PPV_ARGS(&spsv));
    if(FAILED(hr))
        return hr;
    if(!spsv)
        return E_NOINTERFACE;
    
    CComPtr<IDispatch> spdispView;
    hr = spsv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&spdispView));
    if(FAILED(hr))
        return hr;
    
    return spdispView->QueryInterface(riid, ppv);
    }
    
    
    HRESULT ShellExecuteFromExplorer(PCWSTR pszFile, PCWSTR pszParameters, PCWSTR pszDirectory, PCWSTR pszOperation, int nShowCmd)
    {
    HRESULT hr;
    CComPtr<IShellFolderViewDual> spFolderView;
    hr = GetDesktopAutomationObject(IID_PPV_ARGS(&spFolderView));
    if(FAILED(hr))
        return hr;
    
    CComPtr<IDispatch> spdispShell;
    hr = spFolderView->get_Application(&spdispShell);
    if(FAILED(hr))
        return hr;
    
    CComQIPtr<IShellDispatch2> spdispShell2{ spdispShell };
    if(!spdispShell2)
        return E_NOINTERFACE;
    
    // without this, the launched app is not moved to the foreground
    AllowSetForegroundWindow(ASFW_ANY);
    
    return spdispShell2->ShellExecute(
            CComBSTR{ pszFile },
            CComVariant{ pszParameters ? pszParameters : L"" },
            CComVariant{ pszDirectory ? pszDirectory : L"" },
            CComVariant{ pszOperation ? pszOperation : L"" },
            CComVariant{ nShowCmd } );
    }
    

    【讨论】:

      【解决方案4】:

      解决方案有点复杂。也许您不能通过获取一种访问令牌并将其传递给createprocesswithtoken来直接从提升的管理员变为非提升的管理员,但是您可以在另一个方向上更进一步。您可以从提升的管理员转到具有更高权限的系统帐户。从系统帐户权限,您应该能够在非提升的管理员上下文中启动进程。使用关键字“impersonate”进行搜索可以为您提供很多示例。 那么如何从提升的管理员到系统呢?您只能在提升的管理员上下文中编写系统服务并创建/启动该服务。

      【讨论】:

      • 我选择这个复杂的答案作为“为我工作”的原因是:我可以获得为以下等待函数返回的进程 ID。在 shellexecute 方法中,我没有找到一种简单的方法来识别正是由我的代码启动的进程 ID。
      • 你能提供更多关于这个冒名顶替的细节吗?这样做需要用户密码吗?
      猜你喜欢
      • 2015-07-20
      • 1970-01-01
      • 2014-07-27
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-15
      • 2015-06-16
      相关资源
      最近更新 更多