从服务启动用户会话中的进程

Posted

技术标签:

【中文标题】从服务启动用户会话中的进程【英文标题】:Launching a process in user’s session from a service 【发布时间】:2010-06-27 16:40:25 【问题描述】:

在 Windows Vista/7/2008/2008R2 中,是否可以?具体来说,本地会话将是最有用的。

我一直在阅读的所有内容似乎都在说这是不可能的,但我想在完全放弃之前我会在这里问一下。

我正在使用 VB.NET 进行编码,但会接受任何建议。

【问题讨论】:

本地交互用户是我关注的重点。我主要对从服务中锁定工作站感兴趣,但也需要执行其他程序,具体取决于某些条件。 【参考方案1】:

真的有可能。您遇到的主要问题是 Windows 应被视为终端服务器,而用户会话应被视为远程会话。您的服务应该能够启动在远程会话中运行的属于用户的进程。

顺便说一下,如果你编写了一个在 Windows XP 下运行的服务,但它没有添加到域中,并且激活了快速用户切换,那么在第二个(第三个和等等)登录的用户桌面。

我希望你有一个用户令牌,例如你收到的关于模拟的用户令牌,或者你有一个dwSessionId 的会话。如果您没有它,您可以尝试使用一些 WTS 功能(远程桌面服务 API http://msdn.microsoft.com/en-us/library/aa383464.aspx,例如 WTSEnumerateProcessesWTSGetActiveConsoleSessionId)或 LSA-API 来找出相应的用户会话(LsaEnumerateLogonSessions 请参阅http://msdn.microsoft.com/en-us/library/aa378275.aspx 和 LsaGetLogonSessionData 见http://msdn.microsoft.com/en-us/library/aa378290.aspx)或ProcessIdToSessionId(见http://msdn.microsoft.com/en-us/library/aa382990.aspx)。

如果您知道用户令牌hClient,则可以使用GetTokenInformation 函数和参数TokenSessionId(请参阅http://msdn.microsoft.com/en-us/library/aa446671.aspx)来接收用户会话的会话ID dwSessionId

BOOL bSuccess;
HANDLE hProcessToken = NULL, hNewProcessToken = NULL;
DWORD dwSessionId, cbReturnLength;

bSuccess = GetTokenInformation (hClient, TokenSessionId, &dwSessionId,
                                sizeof(DWORD), &cbReturnLength);
bSuccess = OpenProcessToken (GetCurrentProcess(), MAXIMUM_ALLOWED, &hProcessToken);
bSuccess = DuplicateTokenEx (hProcessToken, MAXIMUM_ALLOWED, NULL,
                             SecurityImpersonation,
                             TokenPrimary, &hNewProcessToken);
EnablePrivilege (SE_TCB_NAME);
bSuccess = SetTokenInformation (hNewProcessToken, TokenSessionId, &dwSessionId,
                                sizeof(DWORD));
bSuccess = CreateProcessAsUser (hNewProcessToken, NULL, szCommandToExecute, ...);

这段代码只是一个模式。 EnablePrivilege 是一个简单的函数,使用AdjustTokenPrivileges 来启用SE_TCB_NAME 权限(参见http://msdn.microsoft.com/en-us/library/aa446619.aspx 作为模板)。重要的是,您从中启动的进程具有 TCB 权限,但如果您的服务在本地系统下运行,则您有足够的权限。顺便说一句,以下代码片段不仅适用于本地系统帐户,而且该帐户必须具有SE_TCB_NAME 权限才能切换当前终端服务器会话。

再说一句。在上面的代码中,我们使用与当前进程相同的帐户启动新进程(例如本地系统)。您更改代码以使用另一个帐户,例如用户令牌hClient。拥有primary token 很重要。如果您有一个模拟令牌,您可以将其转换为与上述代码完全相同的主令牌。

CreateProcessAsUser 中使用的STARTUPINFO 结构中,您应该使用lpDesktop = WinSta0\Default"。

根据您的要求,可能还需要使用CreateEnvironmentBlock 来创建您将传递给新进程的新环境块。

我建议您也阅读How to ensure process window launched by Process.Start(ProcessStartInfo) has focus of all Forms?,其中我描述了如何强制该进程在用户桌面的前台启动。

【讨论】:

我刚刚解决了执行此操作的服务时遇到的问题,并在这里找到了您的答案...非常有帮助。就我而言,当我从生成的进程运行文件打开对话框时,我遇到了一个恼人的“拒绝访问”错误(否则对话框工作正常)。调用CreateEnvironmentBlock 并将结果传递给CreateProcessAsUser 似乎已经修复了它。我真的需要设置我的STARTUPINFO 结构的lpDesktop 值吗?我只是将其保留为 NULL。此外,它无需设置 SE_TCB_NAME 权限即可工作。 @paddy:抱歉,如果您有一些常见情况,所有步骤都是必需的。如果服务在某个用户帐户下运行而不是作为系统运行,则您确实必须设置TokenSessionId 以使交互式用户能够看到已启动进程的窗口。为了能够将SetTokenInformationTokenSessionId 一起使用,服务帐户确实需要具有SE_TCB_NAME 权限并且必须启用它。如果屏幕保护程序在进程启动时工作,则应将lpDesktop 设置为指定屏幕保护程序的桌面或默认用户桌面。【参考方案2】:

如果有帮助,我也遇到过类似的问题,但想要一个纯粹的 powershell 解决方案。

我从其他网站拼凑了一些内容并想出了这个:

function Invoke-CommandInSession 

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [ScriptBlock] $expression
    )

    $commandLine = “powershell“

    ## Convert the command into an encoded command for PowerShell
    $commandBytes = [System.Text.Encoding]::Unicode.GetBytes($expression)
    $encodedCommand = [Convert]::ToBase64String($commandBytes)
    $args = “-Output XML -EncodedCommand $encodedCommand”


    $action = New-ScheduledTaskAction -Execute $commandLine -Argument $args
    $setting = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter ([Timespan]::Zero)
    $trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date) + ([Timespan]::FromSeconds(5)))
    $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $setting
    Register-ScheduledTask "Invoke-CommandInSession - $([Guid]::NewGuid())" -InputObject $task

是的,它有点奇怪,但它可以工作 - 最重要的是你可以从 ps remoting 中调用它

【讨论】:

【参考方案3】:

这是可以做到的,但服务直接与用户会话交互并不被认为是好的做法,因为它会造成严重的安全漏洞。

http://support.microsoft.com/kb/327618

更好的方法是创建 2 个程序,一个后端服务和一个前端客户端 UI 程序。服务后端始终运行并使用 WCF(例如)公开其操作。客户端程序可以在用户会话启动时运行。

【讨论】:

我不相信您链接的文章内容对于 2008(r2) 仍然有效。会话空隔离等... 这不是一回事。 Brad 询问是否使用用户凭据创建 /new/ 进程,而不是在用户会话中作为服务进程执行交互式操作。 在用户会话中启动进程仍在与用户桌面交互。让我们不要分裂头发它仍然不是一个好习惯。我同意链接不是最好的:) 是的,我的程序实际上就是这样设计的。有一个客户端应用程序使用远程处理通过命名管道与服务进行通信。出于安全原因,如果有人停止客户端进程,我需要找到一种锁定工作站的方法。我完全理解与用户会话的交互通常很差......这略有不同。谢谢你的回复!【参考方案4】:

是的,使用CreateProcessAsUser 你可以做到这一点。 MSDN 有示例文章,但有一些注意事项。

【讨论】:

请不要发布带有版本号的 MSDN 链接,除非有理由始终使用该版本。 谢谢,我现在正在处理 .NET 的 API 声明。【参考方案5】:

Subverting Vista UAC in Both 32 and 64 bit Architectures

提供的代码针对 Vista,但也适用于 Win7 和 Win10

【讨论】:

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

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

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

从用户会话进程打开在服务中创建的 JobObject

CreateProcessAsUser 进程以 -1073741502 退出

Windows之会话ID

从进程组会话终端的概念深入理解守护进程