使用以 SYSTEM 身份运行的 vb.net 应用程序,如何为每个登录用户启动一个分离的进程?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14188988/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using a vb.net application running as SYSTEM, how do I start a detached process for each logged on user?
提问by
After weeks of research on this topic I've finally decided to start a thread of my own in hope there is someone out there with experience that can help. I've scoured the internet trying to understand the various coding examples out there, but I've come up short trying to put a working solution together. Let me start with some background--
在对这个主题进行了数周的研究后,我终于决定开始我自己的一个线程,希望有经验的人可以提供帮助。我已经在互联网上搜索,试图了解那里的各种编码示例,但我试图将一个可行的解决方案放在一起。让我从一些背景开始——
Background:
I have a vb.net application that is getting delivered to Windows Servers and PCs in my organization using CA IT Client Manager (ITCM). Much like Microsoft SCCM, CA ITCM has an agent service running as SYSTEM on each PC. Hence, when my application gets delivered and executed on the target PC, it's running in the "NT Authority\SYSTEM" context.
背景:
我有一个 vb.net 应用程序,它使用 CA IT Client Manager (ITCM) 传送到我组织中的 Windows 服务器和 PC。与 Microsoft SCCM 非常相似,CA ITCM 具有在每台 PC 上作为 SYSTEM 运行的代理服务。因此,当我的应用程序在目标 PC 上交付和执行时,它运行在“NT Authority\SYSTEM”上下文中。
The Problem:
During the initial phase of my application, there's a process running in the context of each logged in user that I need to stop. At the end of my applications execution, I have a requirement of restarting this process for each logged on user to prevent them from having to log off and back on again. The process I'm stopping is actually a system tray process that the user can interact with on their desktop.
问题:
在我的应用程序的初始阶段,有一个进程在我需要停止的每个登录用户的上下文中运行。在我的应用程序执行结束时,我需要为每个登录的用户重新启动此进程,以防止他们必须注销并重新登录。我要停止的进程实际上是一个系统托盘进程,用户可以在其桌面上与之交互。
Chasing a VB.NET Solution:
Researching endlessly on the internet, it seems there is no native .NET solution for this problem without having the password for each logged on user or prompting the user to enter some credentials. Since this is not an option for me, I need to find a way to start a process without having to know or require the logged on users credentials.
寻找 VB.NET 解决方案:
在 Internet 上无休止地研究,如果没有每个登录用户的密码或提示用户输入一些凭据,似乎没有针对此问题的本机 .NET 解决方案。由于这不是我的选择,我需要找到一种方法来启动进程,而不必知道或不需要登录用户的凭据。
Researching this avenue led me to the CreateProcessAsUserWindows API function. From what I understand, I can do something along these lines -- (see below)
研究这条途径使我找到了CreateProcessAsUserWindows API 函数。据我所知,我可以按照这些方式做一些事情——(见下文)
Notes:
This is my first time using unmanaged code calls in VB.NET to Windows APIs. There's a lot of ambiguity around the constants, enumerations and function declarations as I pieced the code together from various postings. Please do let me know if you notice any errors in any of these declarations. I have many questions about when a datatype needs to be "marshaled" as a different type. Please read carefully!!
注意:
这是我第一次在 VB.NET 中使用非托管代码调用 Windows API。当我从各种帖子中将代码拼凑在一起时,常量、枚举和函数声明存在很多歧义。如果您发现这些声明中的任何错误,请告诉我。我有很多关于何时需要将数据类型“编组”为不同类型的问题。请仔细阅读!!
Since there were countless examples from similar postings, I tried to follow the MSDN example from the CreateProcessAsUser page:
由于类似帖子中有无数示例,我尝试遵循 CreateProcessAsUser 页面中的 MSDN 示例:
MSDN Link:
http://msdn.microsoft.com/en-us/library/windows/desktop/ms682429(v=vs.85).aspx
MSDN 链接:http: //msdn.microsoft.com/en-us/library/windows/desktop/ms682429(v=
vs.85).aspx
Example Link:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379608(v=vs.85).aspx
示例链接:http:
//msdn.microsoft.com/en-us/library/windows/desktop/aa379608(v=vs.85).aspx
In addition to reviewing the validity of each Windows API call, please review the overall order of operations and let me know if I'm over-complicating this or even possibly missing something. The only code I have not implemented from the Microsoft example is allowing each SID full access to the interactive windows station, followed by allowing the SID full access to the interactive desktop. Maybe I'm wrong, but I figure each user should already have access to their interactive desktop already!
除了检查每个 Windows API 调用的有效性之外,请检查操作的整体顺序,如果我过于复杂甚至可能遗漏了什么,请告诉我。我没有从 Microsoft 示例中实现的唯一代码是允许每个 SID 完全访问交互式 windows 站,然后允许 SID 完全访问交互式桌面。也许我错了,但我认为每个用户都应该已经可以访问他们的交互式桌面了!
Public Class WindowsAPI
Private Const SE_CREATE_TOKEN_NAME As String = "SeCreateTokenPrivilege"
Private Const SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege"
Private Const SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege"
Private Const SE_INCREASE_QUOTA_NAME = "SeIncreaseQuotaPrivilege"
Private Const SE_UNSOLICITED_INPUT_NAME = "SeUnsolicitedInputPrivilege"
Private Const SE_MACHINE_ACCOUNT_NAME = "SeMachineAccountPrivilege"
Private Const SE_TCB_NAME = "SeTcbPrivilege"
Private Const SE_SECURITY_NAME = "SeSecurityPrivilege"
Private Const SE_TAKE_OWNERSHIP_NAME = "SeTakeOwnershipPrivilege"
Private Const SE_LOAD_DRIVER_NAME = "SeLoadDriverPrivilege"
Private Const SE_SYSTEM_PROFILE_NAME = "SeSystemProfilePrivilege"
Private Const SE_SYSTEMTIME_NAME = "SeSystemtimePrivilege"
Private Const SE_PROF_SINGLE_PROCESS_NAME = "SeProfileSingleProcessPrivilege"
Private Const SE_INC_BASE_PRIORITY_NAME = "SeIncreaseBasePriorityPrivilege"
Private Const SE_CREATE_PAGEFILE_NAME = "SeCreatePagefilePrivilege"
Private Const SE_CREATE_PERMANENT_NAME = "SeCreatePermanentPrivilege"
Private Const SE_BACKUP_NAME = "SeBackupPrivilege"
Private Const SE_RESTORE_NAME = "SeRestorePrivilege"
Private Const SE_SHUTDOWN_NAME = "SeShutdownPrivilege"
Private Const SE_DEBUG_NAME = "SeDebugPrivilege"
Private Const SE_AUDIT_NAME = "SeAuditPrivilege"
Private Const SE_SYSTEM_ENVIRONMENT_NAME = "SeSystemEnvironmentPrivilege"
Private Const SE_CHANGE_NOTIFY_NAME = "SeChangeNotifyPrivilege"
Private Const SE_REMOTE_SHUTDOWN_NAME = "SeRemoteShutdownPrivilege"
Private Const SE_UNDOCK_NAME = "SeUndockPrivilege"
Private Const SE_SYNC_AGENT_NAME = "SeSyncAgentPrivilege"
Private Const SE_ENABLE_DELEGATION_NAME = "SeEnableDelegationPrivilege"
Private Const SE_MANAGE_VOLUME_NAME = "SeManageVolumePrivilege"
Private Const SE_IMPERSONATE_NAME = "SeImpersonatePrivilege"
Private Const SE_CREATE_GLOBAL_NAME = "SeCreateGlobalPrivilege"
Private Const SE_PRIVILEGE_ENABLED As Integer = &H2
Private Enum WindowShowStyle As UInteger
Hide = 0
ShowNormal = 1
ShowMinimized = 2
ShowMaximized = 3
Maximize = 3
ShowNormalNoActivate = 4
Show = 5
Minimize = 6
ShowMinNoActivate = 7
ShowNoActivate = 8
Restore = 9
ShowDefault = 10
ForceMinimized = 11
End Enum
Private Enum STARTF As Integer
STARTF_USESHOWWINDOW = &H1
STARTF_USESIZE = &H2
STARTF_USEPOSITION = &H4
STARTF_USECOUNTCHARS = &H8
STARTF_USEFILLATTRIBUTE = &H10
STARTF_RUNFULLSCREEN = &H20
STARTF_FORCEONFEEDBACK = &H40
STARTF_FORCEOFFFEEDBACK = &H80
STARTF_USESTDHANDLES = &H100
STARTF_USEHOTKEY = &H200
End Enum
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 ACCESS_MASK
DELETE = &H10000
READ_CONTROL = &H20000
WRITE_DAC = &H40000
WRITE_OWNER = &H80000
SYNCHRONIZE = &H100000
STANDARD_RIGHTS_REQUIRED = &HF0000
STANDARD_RIGHTS_READ = &H20000
STANDARD_RIGHTS_WRITE = &H20000
STANDARD_RIGHTS_EXECUTE = &H20000
STANDARD_RIGHTS_ALL = &H1F0000
SPECIFIC_RIGHTS_ALL = &HFFFF
ACCESS_SYSTEM_SECURITY = &H1000000
MAXIMUM_ALLOWED = &H2000000
GENERIC_READ = &H80000000
GENERIC_WRITE = &H40000000
GENERIC_EXECUTE = &H20000000
GENERIC_ALL = &H10000000
DESKTOP_READOBJECTS = &H1
DESKTOP_CREATEWINDOW = &H2
DESKTOP_CREATEMENU = &H4
DESKTOP_HOOKCONTROL = &H8
DESKTOP_JOURNALRECORD = &H10
DESKTOP_JOURNALPLAYBACK = &H20
DESKTOP_ENUMERATE = &H40
DESKTOP_WRITEOBJECTS = &H80
DESKTOP_SWITCHDESKTOP = &H100
WINSTA_ENUMDESKTOPS = &H1
WINSTA_READATTRIBUTES = &H2
WINSTA_ACCESSCLIPBOARD = &H4
WINSTA_CREATEDESKTOP = &H8
WINSTA_WRITEATTRIBUTES = &H10
WINSTA_ACCESSGLOBALATOMS = &H20
WINSTA_EXITWINDOWS = &H40
WINSTA_ENUMERATE = &H100
WINSTA_READSCREEN = &H200
WINSTA_ALL_ACCESS = &H37F
End Enum
<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 Enum SECURITY_IMPERSONATION_LEVEL
SecurityAnonymous = 0
SecurityIdentification = 1
SecurityImpersonation = 2
SecurityDelegation = 3
End Enum
Private Enum TOKEN_TYPE
TokenPrimary = 1
TokenImpersonation = 2
End Enum
Structure LUID
Public LowPart As UInt32
Public HighPart As Integer
End Structure
Structure TOKEN_PRIVILEGES
Public PrivilegeCount As Integer
Public TheLuid As LUID
Public Attributes As Integer
End Structure
Enum TOKEN_INFORMATION_CLASS
TokenUser = 1
TokenGroups
TokenPrivileges
TokenOwner
TokenPrimaryGroup
TokenDefaultDacl
TokenSource
TokenType
TokenImpersonationLevel
TokenStatistics
TokenRestrictedSids
TokenSessionId
TokenGroupsAndPrivileges
TokenSessionReference
TokenSandBoxInert
TokenAuditPolicy
TokenOrigin
TokenElevationType
TokenLinkedToken
TokenElevation
TokenHasRestrictions
TokenAccessInformation
TokenVirtualizationAllowed
TokenVirtualizationEnabled
TokenIntegrityLevel
TokenUIAccess
TokenMandatoryPolicy
TokenLogonSid
MaxTokenInfoClass
End Enum
<StructLayoutAttribute(LayoutKind.Sequential)>
Public Structure SECURITY_DESCRIPTOR
Public revision As Byte
Public size As Byte
Public control As Short
Public owner As IntPtr
Public group As IntPtr
Public sacl As IntPtr
Public dacl As IntPtr
End Structure
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function AdjustTokenPrivileges(ByVal TokenHandle As IntPtr,
ByVal DisableAllPrivileges As Boolean,
ByRef NewState As TOKEN_PRIVILEGES,
ByVal BufferLengthInBytes As UInt32,
ByRef PreviousState As TOKEN_PRIVILEGES,
ByRef ReturnLengthInBytes As UInt32) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function CreateProcessAsUser(ByVal hToken As IntPtr,
ByVal lpApplicationName As String,
ByVal lpCommandLine As String,
ByRef lpProcessAttributes As SECURITY_ATTRIBUTES,
ByRef lpThreadAttributes As SECURITY_ATTRIBUTES,
ByVal bInheritHandles As Boolean,
ByVal dwCreationFlags As UInteger,
ByVal lpEnvironment As IntPtr,
ByVal lpCurrentDirectory As String,
ByRef lpStartupInfo As STARTUPINFO,
ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function DuplicateTokenEx(ByVal hExistingToken As IntPtr,
ByVal dwDesiredAccess As UInteger,
ByRef lpTokenAttributes As SECURITY_ATTRIBUTES,
ByVal ImpersonationLevel As SECURITY_IMPERSONATION_LEVEL,
ByVal TokenType As TOKEN_TYPE,
ByRef phNewToken As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function ImpersonateLoggedOnUser(ByVal hToken As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function OpenProcessToken(ByVal ProcessHandle As IntPtr,
ByVal DesiredAccess As Integer,
ByRef TokenHandle As IntPtr) As Boolean
End Function
<DllImport("advapi32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function RevertToSelf() As Boolean
End Function
<DllImport("kernel32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function CloseHandle(ByVal hObject As IntPtr) As Boolean
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function GetProcessWindowStation() As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function OpenDesktop(ByVal lpszDesktop As String,
ByVal dwFlags As Integer,
ByVal fInderit As Boolean,
ByVal dwDesiredAccess As Integer) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function OpenWindowStation(ByVal lpszWinSta As String,
ByVal fInherit As Boolean,
ByVal dwDesiredAccess As ACCESS_MASK) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)>
Private Shared Function SetProcessWindowStation(ByVal hWinSta As IntPtr) As Boolean
End Function
Public Shared Function LaunchProcess(ByVal CmdLine As String) As Boolean
' Declare and initialize variables
Dim ExplorerProcesses As Process()
Dim UserTokenHandle As IntPtr
Dim PrimaryTokenHandle As IntPtr
Dim CurrentWinStationHandle As IntPtr
Dim InteractiveWinStationHandle As IntPtr
Dim InteractiveDesktopHandle As IntPtr
Dim StartupInfo As STARTUPINFO
Dim ProcessInfo As PROCESS_INFORMATION
' Get all explorer.exe IDs
ExplorerProcesses = Process.GetProcessesByName("explorer")
' Verify explorers were found
If ExplorerProcesses.Length = 0 Then
' Return
Return True
End If
' Iterate each explorer.exe process
For Each ExplorerProcess As Process In ExplorerProcesses
' Get the user token handle address (Query access level)
If OpenProcessToken(ExplorerProcess.Handle, TokenAccessLevels.MaximumAllowed, UserTokenHandle) = False Then
' Do some error handling
' Iterate the next process
Continue For
End If
' Get a primary token
If DuplicateTokenEx(UserTokenHandle,
TokenAccessLevels.MaximumAllowed,
Nothing,
SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
TOKEN_TYPE.TokenPrimary,
PrimaryTokenHandle) = False Then
' Do some error handling
' Iterate the next process
Continue For
End If
' Save a handle to the current window station
CurrentWinStationHandle = GetProcessWindowStation()
' Check for valid handle to the windows station
If CurrentWinStationHandle = IntPtr.Zero Then
' Do some error handling
' Iterate the next process
Continue For
End If
' Get a handle to the interactive window station
InteractiveWinStationHandle = OpenWindowStation("winsta0", False, ACCESS_MASK.READ_CONTROL Or ACCESS_MASK.WRITE_DAC)
' Check for a valid handle
If InteractiveWinStationHandle = Nothing Then
' Do some error handling
' Iterate the next user
Continue For
End If
' To get the correct default desktop, set the caller's window station to the interactive window station
If SetProcessWindowStation(InteractiveWinStationHandle) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Get handle to interactive desktop
InteractiveDesktopHandle = OpenDesktop("default",
0,
False,
ACCESS_MASK.READ_CONTROL Or
ACCESS_MASK.WRITE_DAC Or
ACCESS_MASK.DESKTOP_WRITEOBJECTS Or
ACCESS_MASK.DESKTOP_READOBJECTS)
' Restore the caller's window station
If SetProcessWindowStation(CurrentWinStationHandle) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Check for a valid handle
If InteractiveDesktopHandle = IntPtr.Zero Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Initialize process and startup info
ProcessInfo = New PROCESS_INFORMATION
StartupInfo = New STARTUPINFO
StartupInfo.cb = Marshal.SizeOf(StartupInfo)
StartupInfo.lpDesktop = "winsta0\default"
' Impersonate client to ensure access to executable file
If ImpersonateLoggedOnUser(PrimaryTokenHandle) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' Launch the process in the client's logon session
If CreateProcessAsUser(PrimaryTokenHandle,
Nothing,
CmdLine,
Nothing,
Nothing,
False,
CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT Or
CreateProcessFlags.NORMAL_PRIORITY_CLASS Or,
Nothing,
Nothing,
StartupInfo,
ProcessInfo) = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
' End impersonation of client
If RevertToSelf() = False Then
' Do some error handling
' Iterate the next user
Continue For
End If
Next
' Check for open handle
If Not PrimaryTokenHandle = IntPtr.Zero Then
' Close the handle
CloseHandle(PrimaryTokenHandle)
End If
' Return
Return True
End Function
End Class
My Result:
Currently, I'm testing calls to the LaunchProcess function from my Administrator account (running my solution from Visual Studio 2010 on my local machine) and by delivering the application through ITCM (running from the SYSTEM account on my local machine). In both cases I get the specified process to start in the users session, but with the following error:
我的结果:
目前,我正在测试从我的管理员帐户(在我的本地机器上从 Visual Studio 2010 运行我的解决方案)并通过 ITCM(从我的本地机器上的 SYSTEM 帐户运行)交付应用程序对 LaunchProcess 函数的调用。在这两种情况下,我都会在用户会话中启动指定的进程,但出现以下错误:
"The application was unable to start correctly (0xc0000142). Click OK to close the application."
“应用程序无法正确启动 (0xc0000142)。单击确定关闭应用程序。”
I'm hoping someone can review my code and point me in the right direction. Otherwise, please advise on how I can possibly debug what the heck is going wrong here.
我希望有人可以我的代码并指出我正确的方向。否则,请告诉我如何调试这里出了什么问题。
Many thanks for all your input in advance.
非常感谢您提前提供的所有意见。
采纳答案by
The Solution:
解决方案:
Before posting the full code solution, I wanted to share how I found my answer. After revisiting the MSDN article on the CreateProcessAsUser API function, I realized I needed to verify if my process actually held the required privileges mentioned by the article:
在发布完整的代码解决方案之前,我想分享一下我是如何找到答案的。在重新访问有关 CreateProcessAsUser API 函数的 MSDN 文章后,我意识到我需要验证我的进程是否确实拥有文章中提到的所需权限:
SE_INCREASE_QUOTA_NAME
SE_INCREASE_QUOTA_NAME
SE_ASSIGNPRIMARYTOKEN_NAME
SE_ASSIGNPRIMARYTOKEN_NAME
Also, not mentioned in the article, but perhaps critical to some of the other related Windows API calls for looking up and adjusting token privileges to enable the above privileges is:
此外,文章中未提及,但可能对其他一些相关的 Windows API 调用(用于查找和调整令牌权限以启用上述权限)至关重要:
SE_TCB_NAME
SE_TCB_NAME
Recall my application is getting delivered using CA's IT Client Manager (ITCM) software to target Windows Servers and PCs. The core CA ITCM agent service logs on as "Local System", but its plugins that perform all the dirty work startup and run as the SYSTEM account. Apparently there is a HUGE difference between the SYSTEM account and the "Local System" account.
回想一下,我的应用程序正在使用 CA 的 IT 客户端管理器 (ITCM) 软件交付,以针对 Windows 服务器和 PC。核心 CA ITCM 代理服务作为“本地系统”登录,但它的插件执行所有脏工作启动并作为系统帐户运行。显然,SYSTEM 帐户和“本地系统”帐户之间存在巨大差异。
Using SysInternal's Process Explorer tool, I was able to inspect my application and find that it did not hold all of the required privileges. It's been a while since I ran the test, so I forget which privilege was actually not held.
使用 SysInternal 的 Process Explorer 工具,我能够检查我的应用程序并发现它没有拥有所有必需的权限。自从我运行测试以来已经有一段时间了,所以我忘记了实际上没有持有哪个特权。
The initial mistake I made was writing unmanaged Windows API function calls to try to enable the missing privilege, but unfortunately it doesn't work that way. Either your process has the privilege or it doesn't, period. If it holds the privilege, then your only responsibility it to make sure the privilege is enabled.
我最初犯的错误是编写非托管的 Windows API 函数调用来尝试启用缺少的权限,但不幸的是它不能那样工作。您的进程要么有特权,要么没有,期间。如果它拥有特权,那么您唯一的责任是确保启用该特权。
To overcome this, I had to use a different approach. In order to obtain the privileges I required, my application needed to be installed and executed as a service, so it could be running as the "Local System" account. However, to redesign the entire application as an installable service just to satisfy this one simple requirement didn't make sense.
为了克服这个问题,我不得不使用不同的方法。为了获得我需要的权限,我的应用程序需要作为服务安装和执行,因此它可以作为“本地系统”帐户运行。但是,将整个应用程序重新设计为可安装服务只是为了满足这一简单的要求是没有意义的。
Instead, I created a second VB.NET project, which is the code I'm posting. The second project is a simple Windows Service that takes startup parameters. The first startup parameter is the application you would like launched for each logged on user. Any remaining startup parameters are passed as startup switches to the application you specify :-)
相反,我创建了第二个 VB.NET 项目,这是我发布的代码。第二个项目是一个简单的 Windows 服务,它接受启动参数。第一个启动参数是您希望为每个登录用户启动的应用程序。任何剩余的启动参数在启动切换到您指定的应用程序时传递:-)
Armed with a service that now held the proper privileges and can dynamically receive startup parameters for specifying exactly what you want launched for each logged in user, I embedded the resultant executable in my first project.
有了现在拥有适当权限的服务,并且可以动态接收启动参数以准确指定您要为每个登录用户启动的内容,我将生成的可执行文件嵌入到我的第一个项目中。
My first project, running as the SYSTEM account, HAS the rights/permissions/privileges to install a new system services. It also has the rights to start that system services, passing the parameters I needed to start the tray service for each logged in user in the system. Problem SOLVED!
我的第一个项目以 SYSTEM 帐户运行,具有安装新系统服务的权限/权限/特权。它还有权启动该系统服务,为系统中的每个登录用户传递启动托盘服务所需的参数。问题解决了!
Here's the code for my Windows Service--
这是我的 Windows 服务的代码--
LaunchService.vb:
启动服务.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:
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
Implementation:
执行:
As mentioned above, I've taken the service executable from my second project, added it in Visual Studio as an "existing item" to my first project. I then changed the "build action" to embed the service executable into my first applications executable.
如上所述,我从我的第二个项目中获取了服务可执行文件,将它作为“现有项目”添加到我的第一个项目中。然后我更改了“构建操作”以将服务可执行文件嵌入到我的第一个应用程序可执行文件中。
When the first application executes on the target, it runs code to extract the embedded executable to the target machine. You can Google for that code, it's rather simplistic.
当第一个应用程序在目标上执行时,它会运行代码以将嵌入的可执行文件提取到目标机器上。你可以用谷歌搜索那个代码,它相当简单。
After the embedded service executable is extracted to a path on the local system, here's the four "sc" commands I run--
将嵌入式服务可执行文件解压缩到本地系统上的路径后,这是我运行的四个“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>
Note: In the first sc command remember to put spaces after the equal signs.
注意:在第一个 sc 命令中,请记住在等号后放置空格。
Using sc commands is far simpler than using installutil.exe or creating a setup project to package the service in an MSI and install it. Just make sure you WAIT for each sc command to return before proceeding to the next one.
使用 sc 命令远比使用 installutil.exe 或创建安装项目来将服务打包到 MSI 中并安装它简单得多。在继续下一个命令之前,请确保您等待每个 sc 命令返回。
Final Note:
最后说明:
To all those that provided positive insight and feedback, thank you very much for all your help. It's for good people like yourselves that I gladly post my code. For all those who told me this couldn't be done or tried to assert it's somehow malicious to poke into users desktops and to run something, I politely encourage you to start thinking out of the box. I wouldn't want you working on my team!
对于所有提供积极见解和反馈的人,非常感谢您的帮助。我很乐意发布我的代码,这是为了像你们这样的好人。对于那些告诉我这是不可能完成的或试图断言刺入用户桌面并运行某些东西是恶意的所有那些人,我礼貌地鼓励您开始跳出框框思考。我不希望你在我的团队工作!
It's a slippery slope to walk through life with the mentality that just because something could have malicious or dangerous implications in the wrong hands that it shouldn't be accomplished at all. Your stinking thinking is contrary to all that's beautiful in the universe.
带着这样的心态走过人生是一个滑坡,仅仅因为某些事情可能会在坏人手中产生恶意或危险的影响,因此根本不应该完成。你那恶毒的想法与宇宙中所有美好的事物背道而驰。

