如何从Windows服务启动进程到当前登录的用户会话

Shr*_*ike 44 .net windows-services

我需要从Windows服务启动一个程序.该程序是用户UI应用程序.此外,该应用程序应在特定用户帐户下启动.

问题是Window Services在会话#0中运行,但登录的用户会话是1,2等.

所以问题是:如何从窗口服务启动一个进程,使其在当前登录的用户会话中运行?

我强调的问题不在于如何在特定帐户下启动流程(很明显 - Process.Start(new ProcessStartInfo(".."){UserName = ..,Password = ..})).即使我安装我的Windows以在当前用户帐户下运行,该服务仍将在会话#0中运行.设置"允许服务与桌面交互"没有帮助.

我的Windows服务是基于.net的.

更新:首先,.NET在这里无关,它实际上是纯Win32的东西.这就是我正在做的事情.以下代码在我的Windows服务中(C#使用win32函数通过P/Inkove,我跳过导入签名,它们都在这里 - http://www.pinvoke.net/default.aspx/advapi32/CreateProcessWithLogonW.html):

    var startupInfo = new StartupInfo()
        {
            lpDesktop = "WinSta0\\Default",
            cb = Marshal.SizeOf(typeof(StartupInfo)),
        };
    var processInfo = new ProcessInformation();
    string command = @"c:\windows\Notepad.exe";
    string user = "Administrator";
    string password = "password";
    string currentDirectory = System.IO.Directory.GetCurrentDirectory();
    try
    {
        bool bRes = CreateProcessWithLogonW(user, null, password, 0,
            command, command, 0,
            Convert.ToUInt32(0),
            currentDirectory, ref startupInfo, out processInfo);
        if (!bRes)
        {
            throw new Win32Exception(Marshal.GetLastWin32Error());
        }
    }
    catch (Exception ex)
    {
        writeToEventLog(ex);
        return;
    }
    WaitForSingleObject(processInfo.hProcess, Convert.ToUInt32(0xFFFFFFF));
    UInt32 exitCode = Convert.ToUInt32(123456);
    GetExitCodeProcess(processInfo.hProcess, ref exitCode);
    writeToEventLog("Notepad has been started by WatchdogService. Exitcode: " + exitCode);

    CloseHandle(processInfo.hProcess);
    CloseHandle(processInfo.hThread);
Run Code Online (Sandbox Code Playgroud)

代码转到"Notepad已由WatchdogService启动.Exitcode:"+ exitCode.Exitcode是3221225794.并且没有任何新的记事本开始.我哪里错了?

mur*_*yju 37

Shrike的答案的问题在于它不适用于通过RDP连接的用户.
这是我的解决方案,它在创建流程之前正确确定当前用户的会话.它已经过测试,适用于XP和7.

https://github.com/murrayju/CreateProcessAsUser

您需要的一切都包含在一个静态方法的.NET类中:

public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)
Run Code Online (Sandbox Code Playgroud)

  • 真棒!我正在努力解决这个问题,你的代码很棒! (3认同)
  • 优秀.正是我需要和正在寻找的东西.工作一种享受. (2认同)
  • 好东西!万分感谢! (2认同)

Shr*_*ike 28

MSDN上的博客描述了一种解决方案

关于从Vista/7上的Windows服务启动交互式会话中的新流程,这是非常有用的帖子.

对于非LocalSystem服务,基本思想是:

  • 枚举进程以获取Explorer的句柄.

  • OpenProcessToken应该为您提供访问令牌.注意:运行服务的帐户必须具有相应的权限才能调用此API并获取进程令牌.

  • 获得令牌后,使用此令牌调用CreateProcessAsUser.此令牌已具有正确的会话ID.


Dir*_*mar 8

这样做是个坏主意.虽然可能并非完全不可能,但微软尽一切努力使其尽可能地难以实现,因为它可以实现所谓的粉碎攻击.看看Larry Osterman 在2005年写到的内容:

这个坏主意的主要原因是交互式服务能够实现一类被称为"粉碎"攻击的威胁(因为它们"粉碎了窗户",我相信).

如果您搜索"碎片攻击",您可以看到这些安全威胁如何工作的一些细节.Microsoft还发布了知识库文章327618,该文章扩展了有关交互式服务的文档,Michael Howard撰写了一篇关于MSDN Library的交互式服务的文章.最初,粉碎攻击发生在具有后台窗口消息泵(已经修复了很久)的Windows组件之后,但它们也被用于攻击弹出UI的第三方服务.

这是一个坏主意的第二个原因是SERVICE_INTERACTIVE_PROCESS标志根本无法正常工作.服务UI在系统会话中弹出(通常是会话0).另一方面,如果用户正在另一个会话中运行,则用户永远不会看到UI.用户在另一个会话中连接有两种主要方案 - 终端服务和快速用户切换.TS并不常见,但在有多人使用单台计算机的家庭场景中,通常会启用FUS(例如,我们在厨房的计算机上几乎所有人都登录了4人).

交互式服务不好的第三个原因是交互式服务不能保证与Windows Vista一起使用:)作为进入Windows Vista的安全加固过程的一部分,交互式用户登录到系统会话以外的会话 - 第一个交互式用户在会话1中运行,而不是在会话0中运行.这样可以完全消除膝盖上的碎片攻击 - 用户应用程序无法与服务中运行的高权限窗口进行交互.

建议的解决方法是在用户的系统托盘中使用应用程序.

如果您可以放心地忽略上述问题和警告,您可以按照此处给出的说明操作:

针对Windows开发:会话0隔离

  • 我不希望我的服务是交互式的(显示任何UI),我只希望它能够启动另一个进程(在用户的会话中).这有点不同,不是吗? (2认同)

Tob*_*s81 6

这是我实现它的方式.它将尝试以当前登录用户(来自服务)启动进程.这是基于几个来源汇总到有效的东西.

它实际上是纯粹的WIN32/C++,所以可能不会对原始问题100%有帮助.但我希望它可以节省其他人一些时间寻找类似的东西.

它需要Windows XP/2003(不适用于Windows 2000).您必须链接到Wtsapi32.lib

#define WINVER 0x0501
#define _WIN32_WINNT 0x0501
#include <Windows.h>
#include <WtsApi32.h>

bool StartInteractiveProcess(LPTSTR cmd, LPCTSTR cmdDir) {
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.lpDesktop = TEXT("winsta0\\default");  // Use the default desktop for GUIs
    PROCESS_INFORMATION pi;
    ZeroMemory(&pi, sizeof(pi));
    HANDLE token;
    DWORD sessionId = ::WTSGetActiveConsoleSessionId();
    if (sessionId==0xffffffff)  // Noone is logged-in
        return false;
    // This only works if the current user is the system-account (we are probably a Windows-Service)
    HANDLE dummy;
    if (::WTSQueryUserToken(sessionId, &dummy)) {
        if (!::DuplicateTokenEx(dummy, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &token)) {
            ::CloseHandle(dummy);
            return false;
        }
        ::CloseHandle(dummy);
        // Create process for user with desktop
        if (!::CreateProcessAsUser(token, NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi)) {  // The "new console" is necessary. Otherwise the process can hang our main process
            ::CloseHandle(token);
            return false;
        }
        ::CloseHandle(token);
    }
    // Create process for current user
    else if (!::CreateProcess(NULL, cmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, cmdDir, &si, &pi))  // The "new console" is necessary. Otherwise the process can hang our main process
        return false;
    // The following commented lines can be used to wait for the process to exit and terminate it
    //::WaitForSingleObject(pi.hProcess, INFINITE);
    //::TerminateProcess(pi.hProcess, 0);
    ::CloseHandle(pi.hProcess);
    ::CloseHandle(pi.hThread);
    return true;
}
Run Code Online (Sandbox Code Playgroud)


Ale*_*che 6

在 W10 上将@murrayju代码实施到 Windows 服务中。从 Program Files 运行可执行文件总是在 VS 中抛出错误 -2。我相信这是由于服务的启动路径设置为 System32 所致。指定workDir并没有解决问题,直到我在StartProcessAsCurrentUser中的CreateProcessAsUser之前添加以下行:

if (workDir != null)
   Directory.SetCurrentDirectory(workDir);
Run Code Online (Sandbox Code Playgroud)

PS:这应该是评论而不是答案,但我还没有所需的声誉。我花了一段时间来调试,希望它可以节省别人的时间。


Ton*_*Nam 5

我在这里找到了解决方案:

http://www.codeproject.com/Articles/35773/Subverting-Vista-UAC-in-Both-32-and-64-bit-Archite

我认为这是一个很好的链接.