在LocalSystem Windows服务中使用CreateProcessAsUser以其他用户身份运行可执行文件

人工智能 2026-07-09

我正在尝试通过 services.mscLocalSystem 账户下启动一个 .exe。我想通过这个可执行文件以另一位用户的身份运行一个应用程序。

我研究了一阵子,发现事情并不像看起来那么简单。我把问题和解决方法都来自微软文档本身:

CreateProcessWithLogonW函数 (winbase.h)

你不能从运行在 "LocalSystem" 账户下的进程调用 CreateProcessWithLogonW,因为该函数在调用者令牌中使用了登录SID,而 "LocalSystem" 账户的令牌不包含这个SID。作为替代,请使用 CreateProcessAsUserLogonUser 函数。

于是我想出了这个变通办法:

  1. 登录用户并创建主令牌:LogonUserDuplicateTokenEx
  2. 加载用户配置文件:LoadUserProfile
  3. 创建用户环境块:CreateEnvironmentBlock
  4. 以目标用户启动进程:CreateProcessAsUser

说实话,这个做法是AI给出的,但我深入研究后发现它确实有道理,于是我尝试去实现。

下面是我尝试的内容:

在LocalSystem账户下运行的主EXE:

Imports System
Imports System.Runtime.InteropServices
Imports System.Text

Public Class Form1
    ' =========================
    ' CONSTANTS
    ' =========================
    Const LOGON32_LOGON_INTERACTIVE As Integer = 2
    Const LOGON32_PROVIDER_DEFAULT As Integer = 0

    Const TOKEN_ASSIGN_PRIMARY As Integer = &H1
    Const TOKEN_DUPLICATE As Integer = &H2
    Const TOKEN_QUERY As Integer = &H8
    Const TOKEN_ALL_ACCESS As Integer = &HF01FF

    Const CREATE_NEW_CONSOLE As Integer = &H10
    Const CREATE_UNICODE_ENVIRONMENT As Integer = &H400

    Const SE_PRIVILEGE_ENABLED As Integer = &H2
    Const TOKEN_ADJUST_PRIVILEGES As Integer = &H20
    Const SE_BACKUP_NAME As String = "SeBackupPrivilege"
    Const SE_RESTORE_NAME As String = "SeRestorePrivilege"
    Const INFINITE As Integer = -1

    ' =========================
    ' STRUCTURES
    ' =========================
<StructLayout(LayoutKind.Sequential)>
    Public Structure STARTUPINFO
        Public cb As Integer
        Public lpReserved As String
        Public lpDesktop As String
        Public lpTitle As String
        Public dwX As Integer
        Public dwY As Integer
        Public dwXSize As Integer
        Public dwYSize As Integer
        Public dwXCountChars As Integer
        Public dwYCountChars As Integer
        Public dwFillAttribute As Integer
        Public dwFlags As Integer
        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

    <StructLayout(LayoutKind.Sequential)>
    Public Structure PROCESS_INFORMATION
        Public hProcess As IntPtr
        Public hThread As IntPtr
        Public dwProcessId As Integer
        Public dwThreadId As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure SECURITY_ATTRIBUTES
        Public nLength As Integer
        Public lpSecurityDescriptor As IntPtr
<MarshalAs(UnmanagedType.Bool)>
        Public bInheritHandle As Boolean
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure LUID
        Public LowPart As UInteger
        Public HighPart As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure LUID_AND_ATTRIBUTES
        Public Luid As LUID
        Public Attributes As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure TOKEN_PRIVILEGES
        Public PrivilegeCount As Integer
        Public Privileges As LUID_AND_ATTRIBUTES
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Auto)>
    Public Structure PROFILEINFO
        Public dwSize As Integer
        Public dwFlags As Integer
        Public lpUserName As String
        Public lpProfilePath As String
        Public lpDefaultPath As String
        Public lpServerName As String
        Public lpPolicyPath As String
        Public hProfile As IntPtr
    End Structure

    ' =========================
    ' API DECLARATIONS
    ' =========================

    <DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Public Shared Function LogonUser(
        lpszUsername As String,
        lpszDomain As String,
        lpszPassword As String,
        dwLogonType As Integer,
        dwLogonProvider As Integer,
        ByRef phToken As IntPtr) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)>
    Public Shared Function DuplicateTokenEx(
        hExistingToken As IntPtr,
        dwDesiredAccess As Integer,
        lpTokenAttributes As IntPtr,
        ImpersonationLevel As Integer,
        TokenType As Integer,
        ByRef phNewToken As IntPtr) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Public Shared Function CreateProcessAsUser(
        hToken As IntPtr,
        lpApplicationName As String,
        lpCommandLine As String,
        ByRef lpProcessAttributes As SECURITY_ATTRIBUTES,
        ByRef lpThreadAttributes As SECURITY_ATTRIBUTES,
        bInheritHandles As Boolean,
        dwCreationFlags As Integer,
        lpEnvironment As IntPtr,
        lpCurrentDirectory As String,
        ByRef lpStartupInfo As STARTUPINFO,
        ByRef lpProcessInformation As PROCESS_INFORMATION) As Boolean
    End Function

    <DllImport("userenv.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Public Shared Function LoadUserProfile(
        hToken As IntPtr,
        ByRef lpProfileInfo As PROFILEINFO) As Boolean
    End Function

    <DllImport("userenv.dll", SetLastError:=True)>
    Public Shared Function UnloadUserProfile(
        hToken As IntPtr,
        hProfile As IntPtr) As Boolean
    End Function

    <DllImport("userenv.dll", SetLastError:=True)>
    Public Shared Function CreateEnvironmentBlock(
        ByRef lpEnvironment As IntPtr,
        hToken As IntPtr,
        bInherit As Boolean) As Boolean
    End Function

    <DllImport("userenv.dll", SetLastError:=True)>
    Public Shared Function DestroyEnvironmentBlock(
        lpEnvironment As IntPtr) As Boolean
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function CloseHandle(hObject As IntPtr) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Public Shared Function LookupPrivilegeValue(
        lpSystemName As String,
        lpName As String,
        ByRef lpLuid As LUID) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)>
    Public Shared Function AdjustTokenPrivileges(
        TokenHandle As IntPtr,
        DisableAllPrivileges As Boolean,
        ByRef NewState As TOKEN_PRIVILEGES,
        BufferLength As Integer,
        ByRef PreviousState As TOKEN_PRIVILEGES,
        ByRef ReturnLength As Integer) As Boolean
    End Function

    <DllImport("advapi32.dll", SetLastError:=True)>
    Public Shared Function OpenProcessToken(
        ProcessHandle As IntPtr,
        DesiredAccess As Integer,
        ByRef TokenHandle As IntPtr) As Boolean
    End Function

    <DllImport("kernel32.dll")>
    Public Shared Function GetCurrentProcess() As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Public Shared Function WaitForSingleObject(
        hHandle As IntPtr,
        dwMilliseconds As Integer) As Integer
    End Function

    Private Function NormalizeLocalUncPath(path As String) As String
        If String.IsNullOrWhiteSpace(path) Then
            Return path
        End If

        If Not path.StartsWith("\\") Then
            Return path
        End If

        Dim parts() As String = path.TrimStart("\"c).Split("\"c)
        If parts.Length < 3 Then
            Return path
        End If

        Dim share As String = parts(1)

        If share.Length = 1 AndAlso Char.IsLetter(share(0)) Then
            Dim rest As String = String.Join("\", parts, 2, parts.Length - 2)
            Dim localCandidate As String = share & ":\\" & rest
            If System.IO.File.Exists(localCandidate) Then
                Return localCandidate
            End If
        End If

        If share.Length = 2 AndAlso Char.IsLetter(share(0)) AndAlso share(1) = "$"c Then
            Dim rest As String = String.Join("\", parts, 2, parts.Length - 2)
            Dim localCandidate As String = share(0) & ":\\" & rest
            If System.IO.File.Exists(localCandidate) Then
                Return localCandidate
            End If
        End If

        Return path
    End Function

    Private Sub EnablePrivilege(privilegeName As String)
        Dim hToken As IntPtr = IntPtr.Zero
        If Not OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES Or TOKEN_QUERY, hToken) Then
            Throw New Exception("OpenProcessToken failed: " & Marshal.GetLastWin32Error())
        End If

        Dim tp As New TOKEN_PRIVILEGES()
        tp.PrivilegeCount = 1
        tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED

        If Not LookupPrivilegeValue(Nothing, privilegeName, tp.Privileges.Luid) Then
            CloseHandle(hToken)
            Throw New Exception("LookupPrivilegeValue failed: " & Marshal.GetLastWin32Error())
        End If

        Dim prev As New TOKEN_PRIVILEGES()
        Dim retLen As Integer = 0
        If Not AdjustTokenPrivileges(hToken, False, tp, Marshal.SizeOf(tp), prev, retLen) Then
            CloseHandle(hToken)
            Throw New Exception("AdjustTokenPrivileges failed: " & Marshal.GetLastWin32Error())
        End If

        CloseHandle(hToken)
    End Sub

    ' =========================
    ' MAIN FUNCTION
    ' =========================
    Public Sub RunProcessAsUser(username As String, domain As String, password As String, appPath As String)

        Dim userToken As IntPtr = IntPtr.Zero
        Dim primaryToken As IntPtr = IntPtr.Zero
        Dim envBlock As IntPtr = IntPtr.Zero

        ' 1. Logon user -> get token
        If Not LogonUser(username, domain, password,
                        LOGON32_LOGON_INTERACTIVE,
                        LOGON32_PROVIDER_DEFAULT,
                        userToken) Then

            Throw New Exception("LogonUser failed: " & Marshal.GetLastWin32Error())
        End If

        ' Convert to primary token
        If Not DuplicateTokenEx(userToken,
                                TOKEN_ALL_ACCESS,
                                IntPtr.Zero,
                                2,
                                1,
                                primaryToken) Then

            Throw New Exception("DuplicateTokenEx failed: " & Marshal.GetLastWin32Error())
        End If

        EnablePrivilege(SE_BACKUP_NAME)
        EnablePrivilege(SE_RESTORE_NAME)

        ' 2. Load user profile 
        Dim profileInfo As New PROFILEINFO()
        profileInfo.dwSize = Marshal.SizeOf(profileInfo)
        profileInfo.lpUserName = username

        If Not LoadUserProfile(userToken, profileInfo) Then
            Throw New Exception("LoadUserProfile failed: " & Marshal.GetLastWin32Error())
        End If

        ' 3. Create environment block for the user
        If Not CreateEnvironmentBlock(envBlock, primaryToken, False) Then
            Throw New Exception("CreateEnvironmentBlock failed: " & Marshal.GetLastWin32Error())
        End If

        Dim si As New STARTUPINFO()
        si.cb = Marshal.SizeOf(si)
        si.lpDesktop = "winsta0\default" 

        Dim pi As New PROCESS_INFORMATION()

        Dim sa As New SECURITY_ATTRIBUTES()
        sa.nLength = Marshal.SizeOf(sa)

        Dim resolvedAppPath As String = NormalizeLocalUncPath(appPath)

        If Not System.IO.File.Exists(resolvedAppPath) Then
            Throw New Exception("Target EXE not found: " & resolvedAppPath)
        End If

        ' 4. Create process as user
        Dim workingDir As String = System.IO.Path.GetDirectoryName(resolvedAppPath)

        Dim commandLine As String = """" & resolvedAppPath & """"

        Dim result As Boolean = CreateProcessAsUser(
            primaryToken,
            Nothing,
            commandLine,
            sa,
            sa,
            False,
            CREATE_NEW_CONSOLE Or CREATE_UNICODE_ENVIRONMENT,
            envBlock,
            workingDir,
            si,
            pi)

        If Not result Then
            Dim err As Integer = Marshal.GetLastWin32Error()
            DestroyEnvironmentBlock(envBlock)
            UnloadUserProfile(userToken, profileInfo.hProfile)
            CloseHandle(primaryToken)
            CloseHandle(userToken)
            Throw New Exception("CreateProcessAsUser failed: " & err)
        End If

        WaitForSingleObject(pi.hProcess, INFINITE)

        DestroyEnvironmentBlock(envBlock)
        CloseHandle(pi.hProcess)
        CloseHandle(pi.hThread)
        UnloadUserProfile(userToken, profileInfo.hProfile)
        CloseHandle(primaryToken)
        CloseHandle(userToken)

    End Sub
    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        Call RunProcessAsUser("test123", "pc187", "test123", "\\pc187\D\RandD\RunProcessAsUser\CallExe1\CallExe1\bin\Debug\CallExe1.exe")
        MsgBox("program ran")
        Application.Exit()
    End Sub


    Private Sub Form1_Shown(sender As Object, e As System.EventArgs) Handles Me.Shown
        Me.Hide()
    End Sub
End Class

从上述EXE调用的那个EXE应该以不同的用户身份运行:

Imports System.IO

Module Program
    Sub Main()
        File.AppendAllText(My.Application.Info.DirectoryPath & "\log.txt", Now & " - exe called" & vbCrLf)
    End Sub
End Module

当我手动点击该EXE时,它能够工作并向日志文件追加新的一行。 但通过第一个EXE启动时,它就不起作用。

也许可以使用其他应用程序或工具来实现,但我想看看是否能够通过上述方法或类似的方法来实现。我主要想知道这个方法是否真的可行,或者我到底做错了什么。如果这种方法不可行,我也想知道原因。

我花了好几个小时与人工智能较劲,试图找出这到底为什么不起作用,但似乎它再也帮不上忙了,所以现在来寻求人类的帮助。

我查看了类似的帖子,但不幸地没有太大帮助。我不知道这些解决方案是不是太旧,或者是我实现它们的方式不对。

如何在Windows服务中使用C#将非交互式进程以另一用户身份运行?

从Windows服务以凭据启动进程

CreateProcessAsUser未正确设置用户

解决方案

在Windows服务下以 LocalSystem 账户运行一个进程并试图以不同的用户身份启动另一个应用程序,确实并不简单。

原因在于服务运行在 Session 0,出于安全原因与交互式用户会话相隔离。因此,单纯地从服务调用 CreateProcess 并不会在目标用户的桌面会话中启动应用程序。

根据微软文档,通常正确的做法是:

  1. 使用诸如以下API获取目标用户的令牌:
  2. LogonUser()
  3. WTSQueryUserToken()
  4. 使用以下方式复制令牌:
  5. DuplicateTokenEx()
  6. 使用以下方式启动进程:
  7. CreateProcessAsUser()

你可能还需要:

  • 调整诸如 SE_ASSIGNPRIMARYTOKEN_NAMESE_INCREASE_QUOTA_NAME 之类的权限,
  • 设置正确的桌面(winsta0\default),
  • 确保进程在活动用户会话中启动。

示例流程:

LogonUser(...);
DuplicateTokenEx(...);
CreateEnvironmentBlock(...);
CreateProcessAsUser(...);

微软还解释说,由于在较新版本的Windows中引入了Session 0隔离,服务不能再直接与用户桌面交互。

所以,是的,你的理解是正确的 —— 从服务启动一个应用程序以另一用户身份运行,需要使用Windows安全令牌和正确的进程创建API,而不是普通的 CreateProcess() 调用。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章