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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何从Windows服务启动进程到当前登录的用户会话相关的知识,希望对你有一定的参考价值。

我需要从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:windowsNotepad.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);

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

答案

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

https://github.com/murrayju/CreateProcessAsUser

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

public static bool StartProcessAsCurrentUser(string appPath, string cmdLine, string workDir, bool visible)
另一答案

A blog on MSDN describes a solution

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

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

  • 枚举进程以获取Explorer的句柄。
  • OpenProcessToken应该为您提供访问令牌。注意:运行服务的帐户必须具有相应的权限才能调用此API并获取进程令牌。
  • 获得令牌后,使用此令牌调用CreateProcessAsUser。此令牌已具有正确的会话ID。
另一答案

这样做是个坏主意。虽然可能并非完全不可能,但微软尽一切努力使其尽可能地难以实现,因为它可以实现所谓的粉碎攻击。看看Larry Osterman wrote about it back in 2005

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

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

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

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

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

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

Developing for Windows: Session 0 Isolation

另一答案

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

它实际上是纯粹的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;
}
另一答案

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

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

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

另一答案

我不知道如何在.NET中执行此操作,但一般情况下,您需要使用Win32 API CreateProcessAsUser()函数(或其任何.NET等效函数),指定所需用户的访问令牌和桌面名称。这是我在C ++服务中使用的,它工作正常。

另一答案

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

if (workDir != null)
   Directory.SetCurrentDirectory(workDir);

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

另一答案

接受的答案在我的情况下不起作用,因为我启动的应用程序需要管理员权限。我所做的是创建了一个批处理文件来启动应用程序。它包含以下内容:

C:WindowsMicrosoft.NETFrameworkv4.0.30319InstallUtil.exe  "%~dp0MySoft.exe"

然后,我将此文件的位置传递给StartProcessAsCurrentUser()方法。诀窍。

以上是关于如何从Windows服务启动进程到当前登录的用户会话的主要内容,如果未能解决你的问题,请参考以下文章

以 Windows 登录用户身份从服务启动进程

如何以与 Windows 服务不同的用户身份运行进程

如何在启动时启动 Docker 守护进程(Windows 服务)而无需登录?

如何从vb6代码确定登录到Windows服务器的工作站的IP地址

通过服务删除 Windows 登录屏幕

windows进程全解