解决方案:
在发布完整的代码解决方案之前,我想分享一下我是如何找到答案的。在重新访问有关 CreateProcessAsUser API 函数的 MSDN 文章后,我意识到我需要验证我的进程是否确实拥有该文章提到的所需权限:
SE_INCREASE_QUOTA_NAME
SE_ASSIGNPRIMARYTOKEN_NAME
另外,本文中没有提及,但对于查找和调整令牌权限以启用上述权限的其他一些相关 Windows API 调用可能至关重要的是:
SE_TCB_NAME
回想一下,我的应用程序是使用 CA 的 IT 客户端管理器 (ITCM) 软件以 Windows 服务器和 PC 为目标交付的。核心 CA ITCM 代理服务以“本地系统”身份登录,但其插件执行所有脏工作启动并以系统帐户运行。显然,系统帐户和“本地系统”帐户之间存在巨大差异。
使用 SysInternal 的 Process Explorer 工具,我能够检查我的应用程序并发现它没有拥有所有必需的权限。自从我运行测试以来已经有一段时间了,所以我忘记了实际上没有持有哪个特权。
我犯的最初错误是编写非托管 Windows API 函数调用来尝试启用缺失的权限,但不幸的是它并不能以这种方式工作。您的进程要么有特权,要么没有,就这样。如果它拥有该特权,那么您唯一的责任就是确保启用该特权。
为了克服这个问题,我不得不使用不同的方法。为了获得我所需的权限,我的应用程序需要作为服务安装和执行,因此它可以作为“本地系统”帐户运行。然而,仅仅为了满足这一简单的需求而将整个应用程序重新设计为可安装服务是没有意义的。
相反,我创建了第二个 VB.NET 项目,这就是我要发布的代码。第二个项目是一个简单的 Windows 服务,它接受启动参数。第一个启动参数是您希望为每个登录用户启动的应用程序。任何剩余的启动参数都会作为启动开关传递到您指定的应用程序:-)
有了一项服务,该服务现在拥有适当的权限,并且可以动态接收启动参数,以准确指定您想要为每个登录用户启动的内容,我将生成的可执行文件嵌入到我的第一个项目中。
我的第一个项目以 SYSTEM 帐户运行,拥有安装新系统服务的权利/许可/特权。它还有权启动该系统服务,传递我为系统中每个登录用户启动托盘服务所需的参数。问题解决了!
这是我的 Windows 服务的代码--
启动服务.vb:
'****************************** Class Header *******************************\
' Project Name: LaunchService
' Class Name: LaunchService
' File Name: LaunchService.vb
' Author: fonbr01
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
' IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
' OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
' ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
' OTHER DEALINGS IN THE SOFTWARE.
'***************************************************************************/
Public Class LaunchService
Protected Overrides Sub OnStart(ByVal args() As String)
' Local Variables
Dim AppName As String
' Get the application name
AppName = args(0)
args(0) = " "
' Check for additional arguments
If args.Length > 1 Then
' Shift the arguments
For i As Integer = 1 To args.Length - 1
' Swap the args
args(i - 1) = args(i)
Next
' Remove the last argument
args(args.Length - 1) = ""
End If
' Launch the App for all users
WindowsAPI.LaunchProcess(AppName, args)
End Sub
Protected Overrides Sub OnStop()
End Sub
End Class
WindowsAPI.vb:
'****************************** Class Header *******************************\
' Project Name: LaunchService
' Class Name: WindowsAPI
' File Name: WindowsAPI.vb
' Author: fonbr01
'
' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
' IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
' OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
' ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
' OTHER DEALINGS IN THE SOFTWARE.
'***************************************************************************/
' Imports
Imports Microsoft.Win32.SafeHandles
Imports System.ComponentModel
Imports System.Runtime.InteropServices
Imports System.Security.Principal
Imports System.Diagnostics
' Windows API Class
Public Class WindowsAPI
' *************************
' * Windows API Functions
' *************************
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function AdjustTokenPrivileges(
<[In]()> ByVal TokenHandle As SafeTokenHandle,
<[In](), MarshalAs(UnmanagedType.Bool)> ByVal DisableAllPrivileges As Boolean,
<[In]()> ByRef NewState As TOKEN_PRIVILEGES,
<[In]()> ByVal BufferLengthInBytes As UInt32,
<Out()> ByRef PreviousState As TOKEN_PRIVILEGES,
<Out()> ByRef ReturnLengthInBytes As UInt32) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function CreateProcessAsUser(
<[In]()> ByVal hToken As SafeTokenHandle,
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpApplicationName As String,
<[In](), Out(), MarshalAs(UnmanagedType.LPWStr)> ByVal lpCommandLine As String,
<[In]()> ByRef lpProcessAttributes As SECURITY_ATTRIBUTES,
<[In]()> ByRef lpThreadAttributes As SECURITY_ATTRIBUTES,
<[In](), MarshalAs(UnmanagedType.Bool)> ByVal bInheritHandles As Boolean,
<[In]()> ByVal dwCreationFlags As UInteger,
<[In]()> ByVal lpEnvironment As IntPtr,
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpCurrentDirectory As String,
<[In]()> ByRef lpStartupInfo As STARTUPINFO,
<Out()> ByRef lpProcessInformation As PROCESS_INFORMATION) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function DuplicateToken(
<[In]()> ByVal ExistingTokenHandle As SafeTokenHandle,
<[In]()> ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
<Out()> ByRef DuplicateTokenHandle As SafeTokenHandle) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function DuplicateTokenEx(
<[In]()> ByVal hExistingToken As IntPtr,
<[In]()> ByVal dwDesiredAccess As UInteger,
<[In]()> ByRef lpTokenAttributes As SECURITY_ATTRIBUTES,
<[In]()> ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
<[In]()> ByVal TokenType As TOKEN_TYPE,
<Out()> ByRef phNewToken As SafeTokenHandle) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function LookupPrivilegeValue(
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpSystemName As String,
<[In](), MarshalAs(UnmanagedType.LPWStr)> ByVal lpName As String,
<Out()> ByRef lpLuid As LUID) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Private Shared Function OpenProcessToken(
<[In]()> ByVal hProcess As IntPtr,
<[In]()> ByVal desiredAccess As UInt32,
<Out()> ByRef hToken As SafeTokenHandle) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function
' *************************
' * Structures
' *************************
<StructLayout(LayoutKind.Sequential)>
Private Structure PROCESS_INFORMATION
Public hProcess As IntPtr
Public hThread As IntPtr
Public dwProcessId As System.UInt32
Public dwThreadId As System.UInt32
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure SECURITY_ATTRIBUTES
Public nLength As System.UInt32
Public lpSecurityDescriptor As IntPtr
Public bInheritHandle As Boolean
End Structure
<StructLayout(LayoutKind.Sequential)>
Private Structure STARTUPINFO
Public cb As System.UInt32
Public lpReserved As String
Public lpDesktop As String
Public lpTitle As String
Public dwX As System.UInt32
Public dwY As System.UInt32
Public dwXSize As System.UInt32
Public dwYSize As System.UInt32
Public dwXCountChars As System.UInt32
Public dwYCountChars As System.UInt32
Public dwFillAttribute As System.UInt32
Public dwFlags As System.UInt32
Public wShowWindow As Short
Public cbReserved2 As Short
Public lpReserved2 As IntPtr
Public hStdInput As IntPtr
Public hStdOutput As IntPtr
Public hStdError As IntPtr
End Structure
Private Structure LUID
Public LowPart As UInt32
Public HighPart As Integer
End Structure
Private Structure LUID_AND_ATTRIBUTES
Public Luid As LUID
Public Attributes As Integer
End Structure
Private Structure TOKEN_PRIVILEGES
Public PrivilegeCount As UInt32
<MarshalAs(UnmanagedType.ByValArray)> Public Privileges() As LUID_AND_ATTRIBUTES
End Structure
' ******************************
' * Enumerations
' ******************************
Private Enum CreateProcessFlags
DEBUG_PROCESS = &H1
DEBUG_ONLY_THIS_PROCESS = &H2
CREATE_SUSPENDED = &H4
DETACHED_PROCESS = &H8
CREATE_NEW_CONSOLE = &H10
NORMAL_PRIORITY_CLASS = &H20
IDLE_PRIORITY_CLASS = &H40
HIGH_PRIORITY_CLASS = &H80
REALTIME_PRIORITY_CLASS = &H100
CREATE_NEW_PROCESS_GROUP = &H200
CREATE_UNICODE_ENVIRONMENT = &H400
CREATE_SEPARATE_WOW_VDM = &H800
CREATE_SHARED_WOW_VDM = &H1000
CREATE_FORCEDOS = &H2000
BELOW_NORMAL_PRIORITY_CLASS = &H4000
ABOVE_NORMAL_PRIORITY_CLASS = &H8000
INHERIT_PARENT_AFFINITY = &H10000
INHERIT_CALLER_PRIORITY = &H20000
CREATE_PROTECTED_PROCESS = &H40000
EXTENDED_STARTUPINFO_PRESENT = &H80000
PROCESS_MODE_BACKGROUND_BEGIN = &H100000
PROCESS_MODE_BACKGROUND_END = &H200000
CREATE_BREAKAWAY_FROM_JOB = &H1000000
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = &H2000000
CREATE_DEFAULT_ERROR_MODE = &H4000000
CREATE_NO_WINDOW = &H8000000
PROFILE_USER = &H10000000
PROFILE_KERNEL = &H20000000
PROFILE_SERVER = &H40000000
CREATE_IGNORE_SYSTEM_DEFAULT = &H80000000
End Enum
Private Enum SECURITY_IMPERSONATION_LEVEL
SecurityAnonymous = 0
SecurityIdentification
SecurityImpersonation
SecurityDelegation
End Enum
Private Enum TOKEN_TYPE
TokenPrimary = 1
TokenImpersonation = 2
End Enum
' ******************************
' * Constants
' ******************************
Private Const SE_ASSIGNPRIMARYTOKEN_NAME As String = "SeAssignPrimaryTokenPrivilege"
Private Const SE_INCREASE_QUOTA_NAME As String = "SeIncreaseQuotaPrivilege"
Private Const SE_TCB_NAME As String = "SeTcbPrivilege"
Private Const SE_PRIVILEGE_ENABLED As UInt32 = &H2
' ******************************
' * Safe Token Handle Class
' ******************************
Private Class SafeTokenHandle
Inherits SafeHandleZeroOrMinusOneIsInvalid
Private Sub New()
MyBase.New(True)
End Sub
Friend Sub New(ByVal handle As IntPtr)
MyBase.New(True)
MyBase.SetHandle(handle)
End Sub
<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Friend Shared Function CloseHandle(ByVal handle As IntPtr) As Boolean
End Function
Protected Overrides Function ReleaseHandle() As Boolean
Return SafeTokenHandle.CloseHandle(MyBase.handle)
End Function
End Class
' ******************************
' * Increase Privileges Function
' ******************************
Public Shared Function IncreasePrivileges() As Boolean
' Local variables
Dim hToken As SafeTokenHandle = Nothing
Dim luid As LUID
Dim NewState As TOKEN_PRIVILEGES
NewState.PrivilegeCount = 1
ReDim NewState.Privileges(0)
' Get current process token
If OpenProcessToken(Diagnostics.Process.GetCurrentProcess.Handle, TokenAccessLevels.MaximumAllowed, hToken) = False Then
' Write debug
WriteEvent("Error: Windows API OpenProcessToken function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Lookup SeIncreaseQuotaPrivilege
If Not LookupPrivilegeValue(Nothing, SE_INCREASE_QUOTA_NAME, luid) Then
' Write debug
WriteEvent("Error: Windows API LookupPrivilegeValue function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Enable SeIncreaseQuotaPrivilege
NewState.Privileges(0).Luid = luid
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
' Adjust the token privileges
If Not AdjustTokenPrivileges(hToken, False, NewState, Marshal.SizeOf(NewState), Nothing, Nothing) Then
' Write debug
WriteEvent("Error: Windows API AdjustTokenPrivileges function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Lookup SeAssignPrimaryTokenPrivilege
If Not LookupPrivilegeValue(Nothing, SE_ASSIGNPRIMARYTOKEN_NAME, luid) Then
' Write debug
WriteEvent("Error: Windows API LookupPrivilegeValue function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Enable SeAssignPrimaryTokenPrivilege
NewState.Privileges(0).Luid = luid
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
' Adjust the token privileges
If Not AdjustTokenPrivileges(hToken, False, NewState, Marshal.SizeOf(NewState), Nothing, Nothing) Then
' Write debug
WriteEvent("Error: Windows API AdjustTokenPrivileges function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Lookup SeTcbPrivilege
If Not LookupPrivilegeValue(Nothing, SE_TCB_NAME, luid) Then
' Write debug
WriteEvent("Error: Windows API LookupPrivilegeValue function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Enable SeTcbPrivilege
NewState.Privileges(0).Luid = luid
NewState.Privileges(0).Attributes = SE_PRIVILEGE_ENABLED
' Adjust the token privileges
If Not AdjustTokenPrivileges(hToken, False, NewState, Marshal.SizeOf(NewState), Nothing, Nothing) Then
' Write debug
WriteEvent("Error: Windows API AdjustTokenPrivileges function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Return
Return False
End If
' Return
Return True
End Function
' ******************************
' * Launch Process Sub
' ******************************
Public Shared Sub LaunchProcess(ByVal CmdLine As String, ByVal args As String())
' Local variables
Dim Arguments As String = ""
Dim ExplorerProcesses As Process()
Dim hToken As SafeTokenHandle = Nothing
Dim principle As WindowsIdentity
Dim phNewToken As SafeTokenHandle = Nothing
Dim si As STARTUPINFO
Dim pi As PROCESS_INFORMATION
' Process arguments
For Each arg As String In args
' Build argument string
Arguments += " " + arg
Next
' Increase Privileges
If IncreasePrivileges() = False Then
' Write debug
WriteEvent("Warning: Failed to increase current process privileges.", EventLogEntryType.Warning)
End If
' Get all explorer.exe IDs
ExplorerProcesses = Process.GetProcessesByName("explorer")
' Verify explorers were found
If ExplorerProcesses.Length = 0 Then
' Write debug
WriteEvent("Warning: No explorer.exe processes found.", EventLogEntryType.Warning)
' Return
Exit Sub
End If
' Iterate each explorer.exe process
For Each hProcess As Process In ExplorerProcesses
' Get the user token handle
If OpenProcessToken(hProcess.Handle, TokenAccessLevels.MaximumAllowed, hToken) = False Then
' Write debug
WriteEvent("Error: Windows API OpenProcessToken function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Iterate the next process
Continue For
End If
' Get the windows identity
principle = New WindowsIdentity(hToken.DangerousGetHandle)
' Get a primary token
If Not DuplicateTokenEx(hToken.DangerousGetHandle,
TokenAccessLevels.MaximumAllowed,
Nothing,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
phNewToken) Then
' Write debug
WriteEvent("Error: Windows API DuplicateTokenEx function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
' Iterate the next process
Continue For
End If
' Initialize process and startup info
pi = New PROCESS_INFORMATION
si = New STARTUPINFO
si.cb = Marshal.SizeOf(si)
si.lpDesktop = Nothing
' Launch the process in the client's logon session
If Not CreateProcessAsUser(phNewToken,
Nothing,
CmdLine + Arguments,
Nothing,
Nothing,
False,
CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT,
Nothing,
Nothing,
si,
pi) Then
' Write debug
WriteEvent("Error: Windows API CreateProcessAsUser function returns an error." + Environment.NewLine +
"Windows API error code: " + Marshal.GetLastWin32Error.ToString, EventLogEntryType.Error)
Else
' Write debug
WriteEvent("Created new user process: " + Environment.NewLine +
"User: " + principle.Name + Environment.NewLine +
"Process: " + CmdLine + Arguments + Environment.NewLine +
"PID: " + pi.dwProcessId.ToString, EventLogEntryType.Information)
End If
' Free resources
hToken.Close()
hToken = Nothing
phNewToken.Close()
phNewToken = Nothing
principle = Nothing
pi = Nothing
si = Nothing
Next
End Sub
' ******************************
' * Write Event Log Sub
' ******************************
Public Shared Sub WriteEvent(EventMessage As String, EntryType As EventLogEntryType)
' Check if event source exists
If Not EventLog.SourceExists("WinOffline Launch Service") Then
' Create the event source
EventLog.CreateEventSource("WinOffline Launch Service", "System")
End If
' Write the message
EventLog.WriteEntry("WinOffline Launch Service", EventMessage, EntryType)
End Sub
End Class
执行:
如上所述,我从第二个项目中获取了服务可执行文件,将其作为“现有项目”添加到 Visual Studio 中到我的第一个项目中。然后,我更改了“构建操作”,将服务可执行文件嵌入到我的第一个应用程序可执行文件中。
当第一个应用程序在目标上执行时,它会运行代码以将嵌入式可执行文件提取到目标计算机。你可以谷歌一下这个代码,它相当简单。
将嵌入式服务可执行文件提取到本地系统上的路径后,这是我运行的四个“sc”命令 -
sc create <ServiceName> binpath= <Full Path to Service Executable> start= demand
sc start <ServiceName> <Full Path to App to Launch for all Users> <Parameters>
sc stop <ServiceName>
sc delete <ServiceName>
注意:在第一个 sc 命令中,请记住在等号后面添加空格。
使用 sc 命令比使用 installutil.exe 或创建安装项目将服务打包在 MSI 中并安装要简单得多。只需确保等待每个 sc 命令返回,然后再继续下一个命令即可。
最后注意事项:
对于所有提供积极见解和反馈的人,非常感谢您的帮助。对于像你们这样的好人,我很乐意发布我的代码。对于所有告诉我这不可能完成或试图断言刺入用户桌面并运行某些内容是恶意的人,我礼貌地鼓励您开始跳出框框思考。我不想让你在我的团队里工作!
带着这样的心态走过人生,如果某件事在错误的人手中可能会产生恶意或危险的影响,那么它根本不应该完成,这是一个滑坡。你的臭想法与宇宙中一切美好的事物背道而驰。