如何在 .Net 中操纵令牌权限?

发布于 2024-10-30 04:14:06 字数 132 浏览 3 评论 0原文

我想使用 C# 来确定分配给我的进程/线程令牌的权限,并根据需要调整它们。例如,为了让我的程序重新启动计算机,它必须首先启用 SeShutdownPrivilege 权限。

如何通过托管代码安全地完成此操作?

I'd like to use C# to determine which privileges are assigned to my process/thread token, and adjust them as necessary. For example, in order for my program to restart the computer, it must first enable the SeShutdownPrivilege privilege.

How can that be done safely from managed code?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

蓝礼 2024-11-06 04:14:06

事实证明这并不简单,因为没有内置的机制。不仅需要 P/Invoke,而且您必须仔细编码,以确保不会通过启用它们然后不尽快禁用它们来“泄漏”权限(尽管如果您重新启动计算机,则这不是问题)。

有关包含说明的完整代码示例,请阅读 2005 年 3 月的 MSDN 杂志文章“操纵权限可靠、安全、高效的托管代码”,作者:Mark Novak。

以下是 P/Invoke 声明:

using System;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;

namespace PrivilegeClass
{
    [Flags]
    internal enum TokenAccessLevels 
    {
        AssignPrimary       = 0x00000001,
        Duplicate           = 0x00000002,
        Impersonate         = 0x00000004,
        Query               = 0x00000008,
        QuerySource         = 0x00000010,
        AdjustPrivileges    = 0x00000020,
        AdjustGroups        = 0x00000040,
        AdjustDefault       = 0x00000080,
        AdjustSessionId     = 0x00000100,

        Read                = 0x00020000 | Query,

        Write               = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault,

        AllAccess           = 0x000F0000       |
            AssignPrimary    |
            Duplicate        |
            Impersonate      |
            Query            |
            QuerySource      |
            AdjustPrivileges |
            AdjustGroups     |
            AdjustDefault    |
            AdjustSessionId,

        MaximumAllowed      = 0x02000000
    }

    internal enum SecurityImpersonationLevel
    {
        Anonymous = 0,
        Identification = 1,
        Impersonation = 2,
        Delegation = 3,
    }

    internal enum TokenType
    {
        Primary = 1,
        Impersonation = 2,
    }

    internal sealed class NativeMethods
    {
        internal const uint SE_PRIVILEGE_DISABLED           = 0x00000000;
        internal const uint SE_PRIVILEGE_ENABLED            = 0x00000002;

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct LUID 
        {
            internal uint LowPart;
            internal uint HighPart;
        }

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct LUID_AND_ATTRIBUTES 
        {
            internal LUID Luid;
            internal uint Attributes;
        }

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct TOKEN_PRIVILEGE 
        {
            internal uint                PrivilegeCount;
            internal LUID_AND_ATTRIBUTES Privilege;
        }

        internal const string ADVAPI32 = "advapi32.dll";
        internal const string KERNEL32 = "kernel32.dll";

        internal const int ERROR_SUCCESS = 0x0;
        internal const int ERROR_ACCESS_DENIED  = 0x5;
        internal const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
        internal const int ERROR_NO_TOKEN = 0x3f0;
        internal const int ERROR_NOT_ALL_ASSIGNED = 0x514;
        internal const int ERROR_NO_SUCH_PRIVILEGE = 0x521;
        internal const int ERROR_CANT_OPEN_ANONYMOUS = 0x543;

        [DllImport(
             KERNEL32,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern bool CloseHandle(IntPtr handle);

        [DllImport(
             ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern bool AdjustTokenPrivileges (
            [In]     SafeTokenHandle       TokenHandle,
            [In]     bool                  DisableAllPrivileges,
            [In]     ref TOKEN_PRIVILEGE   NewState,
            [In]     uint                  BufferLength,
            [In,Out] ref TOKEN_PRIVILEGE   PreviousState,
            [In,Out] ref uint              ReturnLength);

        [DllImport(
             ADVAPI32,
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool RevertToSelf();

        [DllImport(
             ADVAPI32,
             EntryPoint="LookupPrivilegeValueW",
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool LookupPrivilegeValue (
            [In]     string             lpSystemName,
            [In]     string             lpName,
            [In,Out] ref LUID           Luid);

        [DllImport(
             KERNEL32,
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern 
        IntPtr GetCurrentProcess();

        [DllImport(
             KERNEL32,
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern 
            IntPtr GetCurrentThread ();

        [DllImport(
             ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern 
        bool OpenProcessToken (
            [In]     IntPtr              ProcessToken,
            [In]     TokenAccessLevels   DesiredAccess,
            [In,Out] ref SafeTokenHandle TokenHandle);

        [DllImport
             (ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool OpenThreadToken(
            [In]     IntPtr              ThreadToken,
            [In]     TokenAccessLevels   DesiredAccess,
            [In]     bool                OpenAsSelf,
            [In,Out] ref SafeTokenHandle TokenHandle);

        [DllImport
            (ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool DuplicateTokenEx(
            [In]    SafeTokenHandle     ExistingToken,
            [In]    TokenAccessLevels   DesiredAccess,
            [In]    IntPtr              TokenAttributes,
            [In]    SecurityImpersonationLevel  ImpersonationLevel,
            [In]    TokenType           TokenType,
            [In,Out] ref SafeTokenHandle NewToken);

        [DllImport
             (ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool SetThreadToken(
            [In]    IntPtr              Thread,
            [In]    SafeTokenHandle     Token);

        static NativeMethods()
        {
        }
    }
}

This turns out to be non-trivial because there's no built-in mechanism for it. Not only is P/Invoke required, but you must code carefully to make sure that you don't "leak" privileges by enabling them and then not disabling them soon enough (though not an issue if you're restarting the computer).

For a complete code sample with description, read the MSDN magazine article from March 2005 "Manipulate Privileges in Managed Code Reliably, Securely, and Efficiently" by Mark Novak.

Here's the P/Invoke declarations:

using System;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;

namespace PrivilegeClass
{
    [Flags]
    internal enum TokenAccessLevels 
    {
        AssignPrimary       = 0x00000001,
        Duplicate           = 0x00000002,
        Impersonate         = 0x00000004,
        Query               = 0x00000008,
        QuerySource         = 0x00000010,
        AdjustPrivileges    = 0x00000020,
        AdjustGroups        = 0x00000040,
        AdjustDefault       = 0x00000080,
        AdjustSessionId     = 0x00000100,

        Read                = 0x00020000 | Query,

        Write               = 0x00020000 | AdjustPrivileges | AdjustGroups | AdjustDefault,

        AllAccess           = 0x000F0000       |
            AssignPrimary    |
            Duplicate        |
            Impersonate      |
            Query            |
            QuerySource      |
            AdjustPrivileges |
            AdjustGroups     |
            AdjustDefault    |
            AdjustSessionId,

        MaximumAllowed      = 0x02000000
    }

    internal enum SecurityImpersonationLevel
    {
        Anonymous = 0,
        Identification = 1,
        Impersonation = 2,
        Delegation = 3,
    }

    internal enum TokenType
    {
        Primary = 1,
        Impersonation = 2,
    }

    internal sealed class NativeMethods
    {
        internal const uint SE_PRIVILEGE_DISABLED           = 0x00000000;
        internal const uint SE_PRIVILEGE_ENABLED            = 0x00000002;

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct LUID 
        {
            internal uint LowPart;
            internal uint HighPart;
        }

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct LUID_AND_ATTRIBUTES 
        {
            internal LUID Luid;
            internal uint Attributes;
        }

        [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
        internal struct TOKEN_PRIVILEGE 
        {
            internal uint                PrivilegeCount;
            internal LUID_AND_ATTRIBUTES Privilege;
        }

        internal const string ADVAPI32 = "advapi32.dll";
        internal const string KERNEL32 = "kernel32.dll";

        internal const int ERROR_SUCCESS = 0x0;
        internal const int ERROR_ACCESS_DENIED  = 0x5;
        internal const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
        internal const int ERROR_NO_TOKEN = 0x3f0;
        internal const int ERROR_NOT_ALL_ASSIGNED = 0x514;
        internal const int ERROR_NO_SUCH_PRIVILEGE = 0x521;
        internal const int ERROR_CANT_OPEN_ANONYMOUS = 0x543;

        [DllImport(
             KERNEL32,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern bool CloseHandle(IntPtr handle);

        [DllImport(
             ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern bool AdjustTokenPrivileges (
            [In]     SafeTokenHandle       TokenHandle,
            [In]     bool                  DisableAllPrivileges,
            [In]     ref TOKEN_PRIVILEGE   NewState,
            [In]     uint                  BufferLength,
            [In,Out] ref TOKEN_PRIVILEGE   PreviousState,
            [In,Out] ref uint              ReturnLength);

        [DllImport(
             ADVAPI32,
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool RevertToSelf();

        [DllImport(
             ADVAPI32,
             EntryPoint="LookupPrivilegeValueW",
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool LookupPrivilegeValue (
            [In]     string             lpSystemName,
            [In]     string             lpName,
            [In,Out] ref LUID           Luid);

        [DllImport(
             KERNEL32,
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern 
        IntPtr GetCurrentProcess();

        [DllImport(
             KERNEL32,
             CharSet=CharSet.Auto,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern 
            IntPtr GetCurrentThread ();

        [DllImport(
             ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern 
        bool OpenProcessToken (
            [In]     IntPtr              ProcessToken,
            [In]     TokenAccessLevels   DesiredAccess,
            [In,Out] ref SafeTokenHandle TokenHandle);

        [DllImport
             (ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool OpenThreadToken(
            [In]     IntPtr              ThreadToken,
            [In]     TokenAccessLevels   DesiredAccess,
            [In]     bool                OpenAsSelf,
            [In,Out] ref SafeTokenHandle TokenHandle);

        [DllImport
            (ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool DuplicateTokenEx(
            [In]    SafeTokenHandle     ExistingToken,
            [In]    TokenAccessLevels   DesiredAccess,
            [In]    IntPtr              TokenAttributes,
            [In]    SecurityImpersonationLevel  ImpersonationLevel,
            [In]    TokenType           TokenType,
            [In,Out] ref SafeTokenHandle NewToken);

        [DllImport
             (ADVAPI32,
             CharSet=CharSet.Unicode,
             SetLastError=true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        internal static extern
        bool SetThreadToken(
            [In]    IntPtr              Thread,
            [In]    SafeTokenHandle     Token);

        static NativeMethods()
        {
        }
    }
}
变身佩奇 2024-11-06 04:14:06

这是我用的。它基于 Mark Novak 的文章,但减少了对不受信任的堆栈框架、CER 或重入的偏执(因为我假设您不是在编写 Internet Explorer 或 SQL Server 插件)。

Privilege.cs:

using System;
using Microsoft.Win32.SafeHandles;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Threading;
using Luid = Esatto.Win32.Processes.NativeMethods.LUID;
using Win32Exception = System.ComponentModel.Win32Exception;
using PrivilegeNotHeldException = System.Security.AccessControl.PrivilegeNotHeldException;
using static Esatto.Win32.Processes.NativeMethods;
using System.Linq;
using System.Security.Principal;

// http://msdn.microsoft.com/en-us/magazine/cc163823.aspx
namespace Esatto.Win32.Processes
{
    public static class Privilege
    {
        #region Privilege names

        public const string 
            CreateToken = "SeCreateTokenPrivilege",
            AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege",
            LockMemory = "SeLockMemoryPrivilege",
            IncreaseQuota = "SeIncreaseQuotaPrivilege",
            UnsolicitedInput = "SeUnsolicitedInputPrivilege",
            MachineAccount = "SeMachineAccountPrivilege",
            TrustedComputingBase = "SeTcbPrivilege",
            Security = "SeSecurityPrivilege",
            TakeOwnership = "SeTakeOwnershipPrivilege",
            LoadDriver = "SeLoadDriverPrivilege",
            SystemProfile = "SeSystemProfilePrivilege",
            SystemTime = "SeSystemtimePrivilege",
            ProfileSingleProcess = "SeProfileSingleProcessPrivilege",
            IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege",
            CreatePageFile = "SeCreatePagefilePrivilege",
            CreatePermanent = "SeCreatePermanentPrivilege",
            Backup = "SeBackupPrivilege",
            Restore = "SeRestorePrivilege",
            Shutdown = "SeShutdownPrivilege",
            Debug = "SeDebugPrivilege",
            Audit = "SeAuditPrivilege",
            SystemEnvironment = "SeSystemEnvironmentPrivilege",
            ChangeNotify = "SeChangeNotifyPrivilege",
            RemoteShutdown = "SeRemoteShutdownPrivilege",
            Undock = "SeUndockPrivilege",
            SyncAgent = "SeSyncAgentPrivilege",
            EnableDelegation = "SeEnableDelegationPrivilege",
            ManageVolume = "SeManageVolumePrivilege",
            Impersonate = "SeImpersonatePrivilege",
            CreateGlobal = "SeCreateGlobalPrivilege",
            TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege",
            ReserveProcessor = "SeReserveProcessorPrivilege";

        #endregion

        public static void RunWithPrivileges(Action action, params string[] privs)
        {
            if (privs == null || privs.Length == 0)
            {
                throw new ArgumentNullException(nameof(privs));
            }
            var luids = privs
                .Select(e => new LUID_AND_ATTRIBUTES { Luid = GetLuidForName(e), Attributes = SE_PRIVILEGE_ENABLED })
                .ToArray();

            RuntimeHelpers.PrepareConstrainedRegions();
            try { /* CER */ }
            finally
            {
                using (var threadToken = new ThreadTokenScope())
                using (new ThreadPrivilegeScope(threadToken, luids))
                {
                    action();
                }
            }
        }

        private static LUID_AND_ATTRIBUTES[] AdjustTokenPrivileges2(SafeTokenHandle token, LUID_AND_ATTRIBUTES[] attrs)
        {
            var sizeofAttr = Marshal.SizeOf<LUID_AND_ATTRIBUTES>();
            var pDesired = Marshal.AllocHGlobal(4 /* count */ + attrs.Length * sizeofAttr);
            try
            {
                // Fill pStruct
                {
                    Marshal.WriteInt32(pDesired, attrs.Length);
                    var pAttr = pDesired + 4;
                    for (int i = 0; i < attrs.Length; i++)
                    {
                        Marshal.StructureToPtr(attrs[i], pAttr, false);
                        pAttr += sizeofAttr;
                    }
                }

                // Call Adjust
                const int cbPrevious = 16384 /* some arbitrarily high number */;
                var pPrevious = Marshal.AllocHGlobal(cbPrevious);
                try
                {
                    if (!AdjustTokenPrivileges(token, false, pDesired, cbPrevious, pPrevious, out var retLen))
                    {
                        throw new Win32Exception();
                    }

                    // Parse result
                    {
                        var result = new LUID_AND_ATTRIBUTES[Marshal.ReadInt32(pPrevious)];
                        var pAttr = pPrevious + 4;
                        for (int i = 0; i < result.Length; i++)
                        {
                            result[i] = Marshal.PtrToStructure<LUID_AND_ATTRIBUTES>(pAttr);
                        }
                        return result;
                    }
                }
                finally { Marshal.FreeHGlobal(pPrevious); }
            }
            finally { Marshal.FreeHGlobal(pDesired); }
        }

        private static Luid GetLuidForName(string priv)
        {
            if (!LookupPrivilegeValue(null, priv, out var result))
            {
                throw new Win32Exception();
            }

            return result;
        }

        private class ThreadPrivilegeScope : IDisposable
        {
            private LUID_AND_ATTRIBUTES[] RevertTo;
            private Thread OwnerThread;
            private readonly ThreadTokenScope Token;

            public ThreadPrivilegeScope(ThreadTokenScope token, LUID_AND_ATTRIBUTES[] setTo)
            {
                this.OwnerThread = Thread.CurrentThread;
                this.Token = token ?? throw new ArgumentNullException(nameof(token));

                this.RevertTo = AdjustTokenPrivileges2(token.Handle, setTo);
            }

            public void Dispose()
            {
                if (OwnerThread != Thread.CurrentThread)
                {
                    throw new InvalidOperationException("Wrong thread");
                }
                if (RevertTo == null)
                {
                    return;
                }

                AdjustTokenPrivileges2(Token.Handle, RevertTo);
            }
        }

        private class ThreadTokenScope : IDisposable
        {
            private bool IsImpersonating;
            private readonly Thread OwnerThread;
            public readonly SafeTokenHandle Handle;

            [ThreadStatic]
            private static ThreadTokenScope Current;

            public ThreadTokenScope()
            {
                if (Current != null)
                {
                    throw new InvalidOperationException("Reentrance to ThreadTokenScope");
                }

                this.OwnerThread = Thread.CurrentThread;

                if (!OpenThreadToken(GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, out var token))
                {
                    var error = Marshal.GetLastWin32Error();
                    if (error != ERROR_NO_TOKEN)
                    {
                        throw new Win32Exception(error);
                    }

                    // No token is on the thread, copy from process
                    if (!OpenProcessToken(GetCurrentProcess(), TokenAccessLevels.Duplicate, out var processToken))
                    {
                        throw new Win32Exception();
                    }

                    if (!DuplicateTokenEx(processToken, TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges,
                        IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Impersonation, out token))
                    {
                        throw new Win32Exception();
                    }

                    if (!SetThreadToken(IntPtr.Zero, token))
                    {
                        throw new Win32Exception();
                    }
                    this.IsImpersonating = true;
                }

                this.Handle = token;
                Current = this;
            }

            public void Dispose()
            {
                if (OwnerThread != Thread.CurrentThread)
                {
                    throw new InvalidOperationException("Wrong thread");
                }
                if (Current != this)
                {
                    throw new ObjectDisposedException(nameof(ThreadTokenScope));
                }
                Current = null;

                if (IsImpersonating)
                {
                    RevertToSelf();
                }
                IsImpersonating = false;
            }
        }
    }
}

NativeMethods.cs:

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;

namespace Esatto.Win32.Processes
{
    internal static class NativeMethods
    {
        const string Advapi32 = "advapi32.dll";
        const string Kernel32 = "kernel32.dll";
        const string Wtsapi32 = "wtsapi32.dll";
        const string Userenv = "userenv.dll";

        #region constants

        public const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
        public const uint SE_PRIVILEGE_DISABLED = 0x00000000;
        public const uint SE_PRIVILEGE_ENABLED = 0x00000002;
        public const int ERROR_SUCCESS = 0x0;
        public const int ERROR_ACCESS_DENIED = 0x5;
        public const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
        public const int ERROR_NO_TOKEN = 0x3f0;
        public const int ERROR_NOT_ALL_ASSIGNED = 0x514;
        public const int ERROR_NO_SUCH_PRIVILEGE = 0x521;
        public const int ERROR_CANT_OPEN_ANONYMOUS = 0x543;
        public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
        public const uint STANDARD_RIGHTS_READ = 0x00020000;
        public const uint NORMAL_PRIORITY_CLASS = 0x0020;
        public const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
        public const uint MAX_PATH = 260;
        public const uint CREATE_NO_WINDOW = 0x08000000;
        public const uint INFINITE = 0xFFFFFFFF;

        #endregion

        #region Advapi32

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool AdjustTokenPrivileges(SafeTokenHandle TokenHandle, bool DisableAllPrivileges,
            IntPtr NewState, uint BufferLength, IntPtr PreviousState, out uint ReturnLength);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool RevertToSelf();

        [DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID Luid);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool OpenProcessToken(IntPtr ProcessToken, TokenAccessLevels DesiredAccess, out SafeTokenHandle TokenHandle);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool OpenThreadToken(IntPtr ThreadToken, TokenAccessLevels DesiredAccess, bool OpenAsSelf, out SafeTokenHandle TokenHandle);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool DuplicateTokenEx(SafeTokenHandle ExistingToken, TokenAccessLevels DesiredAccess,
            IntPtr TokenAttributes, SecurityImpersonationLevel ImpersonationLevel, TokenType TokenType, out SafeTokenHandle NewToken);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool SetThreadToken(IntPtr Thread, SafeTokenHandle Token);

        [DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateProcessAsUser(SafeTokenHandle hToken,
            StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
            IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
            EnvironmentBlockSafeHandle environment, string currentDirectory, ref STARTUPINFO startupInfo,
            out PROCESS_INFORMATION startupInformation);

        [DllImport(Advapi32, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool GetTokenInformation(IntPtr TokenHandle,
            TokenInformationClass TokenInformationClass, out int TokenInformation,
            uint TokenInformationLength, out uint ReturnLength);

        #endregion

        #region Kernel32

        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern IntPtr GetCurrentProcess();

        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern IntPtr GetCurrentThread();

        [DllImport(Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern SafeJobHandle CreateJobObject(IntPtr lpJobAttributes, string lpName);

        [DllImport(Kernel32, SetLastError = true)]
        public static extern bool SetInformationJobObject(SafeJobHandle hJob, JobObjectInfoType infoType,
            ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, int cbJobObjectInfoLength);

        [DllImport(Kernel32, SetLastError = true)]
        public static extern bool AssignProcessToJobObject(SafeJobHandle job, IntPtr process);

        #endregion

        #region Wtsapi

        [DllImport(Wtsapi32, ExactSpelling = true, SetLastError = true)]
        public static extern bool WTSQueryUserToken(int sessionid, out SafeTokenHandle handle);

        #endregion

        #region Userenv

        [DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateEnvironmentBlock(out EnvironmentBlockSafeHandle lpEnvironment, SafeTokenHandle hToken, bool bInherit);

        [DllImport(Userenv, ExactSpelling = true, SetLastError = true)]
        public extern static bool DestroyEnvironmentBlock(IntPtr hEnvironment);

        #endregion

        #region Structs

        [StructLayout(LayoutKind.Sequential)]
        public struct IO_COUNTERS
        {
            public ulong ReadOperationCount;
            public ulong WriteOperationCount;
            public ulong OtherOperationCount;
            public ulong ReadTransferCount;
            public ulong WriteTransferCount;
            public ulong OtherTransferCount;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
        {
            public long PerProcessUserTimeLimit;
            public long PerJobUserTimeLimit;
            public uint LimitFlags;
            public UIntPtr MinimumWorkingSetSize;
            public UIntPtr MaximumWorkingSetSize;
            public uint ActiveProcessLimit;
            public UIntPtr Affinity;
            public uint PriorityClass;
            public uint SchedulingClass;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public uint nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
        {
            public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
            public IO_COUNTERS IoInfo;
            public UIntPtr ProcessMemoryLimit;
            public UIntPtr JobMemoryLimit;
            public UIntPtr PeakProcessMemoryUsed;
            public UIntPtr PeakJobMemoryUsed;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct STARTUPINFO
        {
            public int cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public int dwX;
            public int dwY;
            public int dwXSize;
            public int dwYSize;
            public int dwXCountChars;
            public int dwYCountChars;
            public int dwFillAttribute;
            public int dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID
        {
            public uint LowPart;
            public uint HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID_AND_ATTRIBUTES
        {
            public LUID Luid;
            public uint Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_PRIVILEGE
        {
            public uint PrivilegeCount;
            public LUID_AND_ATTRIBUTES Privilege;
        }

        #endregion

        #region Enums

        public enum JobObjectInfoType
        {
            AssociateCompletionPortInformation = 7,
            BasicLimitInformation = 2,
            BasicUIRestrictions = 4,
            EndOfJobTimeInformation = 6,
            ExtendedLimitInformation = 9,
            SecurityLimitInformation = 5,
            GroupInformation = 11
        }

        public enum SecurityImpersonationLevel
        {
            Anonymous = 0,
            Identification = 1,
            Impersonation = 2,
            Delegation = 3,
        }

        public enum TokenType
        {
            Primary = 1,
            Impersonation = 2,
        }

        public enum TokenInformationClass
        {
            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,
            TokenIsAppContainer,
            TokenCapabilities,
            TokenAppContainerSid,
            TokenAppContainerNumber,
            TokenUserClaimAttributes,
            TokenDeviceClaimAttributes,
            TokenRestrictedUserClaimAttributes,
            TokenRestrictedDeviceClaimAttributes,
            TokenDeviceGroups,
            TokenRestrictedDeviceGroups,
            // MaxTokenInfoClass should always be the last enum
            MaxTokenInfoClass
        }

        #endregion

        #region SafeHandles

        public sealed class EnvironmentBlockSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public EnvironmentBlockSafeHandle()
                : base(true)
            {
            }

            protected override bool ReleaseHandle()
            {
                return DestroyEnvironmentBlock(handle);
            }
        }

        public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeTokenHandle()
                : base(true)
            {
            }

            override protected bool ReleaseHandle()
            {
                return CloseHandle(handle);
            }
        }

        internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeJobHandle()
                : base(true)
            {
            }

            protected override bool ReleaseHandle()
            {
                return CloseHandle(this.handle);
            }
        }

        #endregion
    }
}

在另一个用户的会话中创建进程的示例用法:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Esatto.Win32.Processes
{
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Security.Principal;
    using static NativeMethods;

#if ESATTO_WIN32
    public
#else
    internal
#endif
        static class ProcessInterop
    {
        public static void CreateProcessForSession(int sessionId, string exePath, string commandLine)
        {
            var privs = new[] { Privilege.TrustedComputingBase, Privilege.AssignPrimaryToken, Privilege.IncreaseQuota };
            Privilege.RunWithPrivileges(() => CreateProcessForSessionInternal(sessionId, exePath, commandLine), privs);
        }

        private static void CreateProcessForSessionInternal(int sessionId, string exePath, string commandLine)
        {
            SafeTokenHandle hDupToken;
            {
                SafeTokenHandle hToken;
                if (!WTSQueryUserToken(sessionId, out hToken))
                {
                    throw new Win32Exception();
                }

                using (hToken)
                {
                    if (!DuplicateTokenEx(hToken, TokenAccessLevels.AllAccess, IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Primary, out hDupToken))
                    {
                        throw new Win32Exception();
                    }
                }
            }

            using (hDupToken)
            {
                EnvironmentBlockSafeHandle env;
                if (!CreateEnvironmentBlock(out env, hDupToken, false))
                {
                    throw new Win32Exception();
                }

                using (env)
                {
                    STARTUPINFO si = new STARTUPINFO();
                    si.cb = Marshal.SizeOf(si);

                    PROCESS_INFORMATION procInfo;
                    if (!CreateProcessAsUser(hDupToken, new StringBuilder(exePath), new StringBuilder(commandLine),
                        IntPtr.Zero, IntPtr.Zero, false, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, env,
                        null, ref si, out procInfo))
                    {
                        throw new Win32Exception();
                    }

                    CloseHandle(procInfo.hProcess);
                    CloseHandle(procInfo.hThread);
                }
            }
        }

        public static int GetSessionId(this WindowsIdentity ident)
        {
            if (ident == null)
            {
                throw new ArgumentNullException();
            }

            int sessionId;
            uint unused;
            if (!GetTokenInformation(ident.Token, TokenInformationClass.TokenSessionId, out sessionId, 4, out unused))
            {
                throw new Win32Exception();
            }
            // since we are not passing a SafeHandle, we need to keep our reference on the SafeHandle
            // contained in the WindowsIdentity
            GC.KeepAlive(ident);

            return sessionId;
        }
    }
}

Here's what I use. It is based off of the Mark Novak article, but with less paranoia for untrusted stack frames, CER's, or reentrance (since I assume you are not writing internet explorer or a SQL Server Add-in).

Privilege.cs:

using System;
using Microsoft.Win32.SafeHandles;
using System.Collections.Specialized;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Threading;
using Luid = Esatto.Win32.Processes.NativeMethods.LUID;
using Win32Exception = System.ComponentModel.Win32Exception;
using PrivilegeNotHeldException = System.Security.AccessControl.PrivilegeNotHeldException;
using static Esatto.Win32.Processes.NativeMethods;
using System.Linq;
using System.Security.Principal;

// http://msdn.microsoft.com/en-us/magazine/cc163823.aspx
namespace Esatto.Win32.Processes
{
    public static class Privilege
    {
        #region Privilege names

        public const string 
            CreateToken = "SeCreateTokenPrivilege",
            AssignPrimaryToken = "SeAssignPrimaryTokenPrivilege",
            LockMemory = "SeLockMemoryPrivilege",
            IncreaseQuota = "SeIncreaseQuotaPrivilege",
            UnsolicitedInput = "SeUnsolicitedInputPrivilege",
            MachineAccount = "SeMachineAccountPrivilege",
            TrustedComputingBase = "SeTcbPrivilege",
            Security = "SeSecurityPrivilege",
            TakeOwnership = "SeTakeOwnershipPrivilege",
            LoadDriver = "SeLoadDriverPrivilege",
            SystemProfile = "SeSystemProfilePrivilege",
            SystemTime = "SeSystemtimePrivilege",
            ProfileSingleProcess = "SeProfileSingleProcessPrivilege",
            IncreaseBasePriority = "SeIncreaseBasePriorityPrivilege",
            CreatePageFile = "SeCreatePagefilePrivilege",
            CreatePermanent = "SeCreatePermanentPrivilege",
            Backup = "SeBackupPrivilege",
            Restore = "SeRestorePrivilege",
            Shutdown = "SeShutdownPrivilege",
            Debug = "SeDebugPrivilege",
            Audit = "SeAuditPrivilege",
            SystemEnvironment = "SeSystemEnvironmentPrivilege",
            ChangeNotify = "SeChangeNotifyPrivilege",
            RemoteShutdown = "SeRemoteShutdownPrivilege",
            Undock = "SeUndockPrivilege",
            SyncAgent = "SeSyncAgentPrivilege",
            EnableDelegation = "SeEnableDelegationPrivilege",
            ManageVolume = "SeManageVolumePrivilege",
            Impersonate = "SeImpersonatePrivilege",
            CreateGlobal = "SeCreateGlobalPrivilege",
            TrustedCredentialManagerAccess = "SeTrustedCredManAccessPrivilege",
            ReserveProcessor = "SeReserveProcessorPrivilege";

        #endregion

        public static void RunWithPrivileges(Action action, params string[] privs)
        {
            if (privs == null || privs.Length == 0)
            {
                throw new ArgumentNullException(nameof(privs));
            }
            var luids = privs
                .Select(e => new LUID_AND_ATTRIBUTES { Luid = GetLuidForName(e), Attributes = SE_PRIVILEGE_ENABLED })
                .ToArray();

            RuntimeHelpers.PrepareConstrainedRegions();
            try { /* CER */ }
            finally
            {
                using (var threadToken = new ThreadTokenScope())
                using (new ThreadPrivilegeScope(threadToken, luids))
                {
                    action();
                }
            }
        }

        private static LUID_AND_ATTRIBUTES[] AdjustTokenPrivileges2(SafeTokenHandle token, LUID_AND_ATTRIBUTES[] attrs)
        {
            var sizeofAttr = Marshal.SizeOf<LUID_AND_ATTRIBUTES>();
            var pDesired = Marshal.AllocHGlobal(4 /* count */ + attrs.Length * sizeofAttr);
            try
            {
                // Fill pStruct
                {
                    Marshal.WriteInt32(pDesired, attrs.Length);
                    var pAttr = pDesired + 4;
                    for (int i = 0; i < attrs.Length; i++)
                    {
                        Marshal.StructureToPtr(attrs[i], pAttr, false);
                        pAttr += sizeofAttr;
                    }
                }

                // Call Adjust
                const int cbPrevious = 16384 /* some arbitrarily high number */;
                var pPrevious = Marshal.AllocHGlobal(cbPrevious);
                try
                {
                    if (!AdjustTokenPrivileges(token, false, pDesired, cbPrevious, pPrevious, out var retLen))
                    {
                        throw new Win32Exception();
                    }

                    // Parse result
                    {
                        var result = new LUID_AND_ATTRIBUTES[Marshal.ReadInt32(pPrevious)];
                        var pAttr = pPrevious + 4;
                        for (int i = 0; i < result.Length; i++)
                        {
                            result[i] = Marshal.PtrToStructure<LUID_AND_ATTRIBUTES>(pAttr);
                        }
                        return result;
                    }
                }
                finally { Marshal.FreeHGlobal(pPrevious); }
            }
            finally { Marshal.FreeHGlobal(pDesired); }
        }

        private static Luid GetLuidForName(string priv)
        {
            if (!LookupPrivilegeValue(null, priv, out var result))
            {
                throw new Win32Exception();
            }

            return result;
        }

        private class ThreadPrivilegeScope : IDisposable
        {
            private LUID_AND_ATTRIBUTES[] RevertTo;
            private Thread OwnerThread;
            private readonly ThreadTokenScope Token;

            public ThreadPrivilegeScope(ThreadTokenScope token, LUID_AND_ATTRIBUTES[] setTo)
            {
                this.OwnerThread = Thread.CurrentThread;
                this.Token = token ?? throw new ArgumentNullException(nameof(token));

                this.RevertTo = AdjustTokenPrivileges2(token.Handle, setTo);
            }

            public void Dispose()
            {
                if (OwnerThread != Thread.CurrentThread)
                {
                    throw new InvalidOperationException("Wrong thread");
                }
                if (RevertTo == null)
                {
                    return;
                }

                AdjustTokenPrivileges2(Token.Handle, RevertTo);
            }
        }

        private class ThreadTokenScope : IDisposable
        {
            private bool IsImpersonating;
            private readonly Thread OwnerThread;
            public readonly SafeTokenHandle Handle;

            [ThreadStatic]
            private static ThreadTokenScope Current;

            public ThreadTokenScope()
            {
                if (Current != null)
                {
                    throw new InvalidOperationException("Reentrance to ThreadTokenScope");
                }

                this.OwnerThread = Thread.CurrentThread;

                if (!OpenThreadToken(GetCurrentThread(), TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges, true, out var token))
                {
                    var error = Marshal.GetLastWin32Error();
                    if (error != ERROR_NO_TOKEN)
                    {
                        throw new Win32Exception(error);
                    }

                    // No token is on the thread, copy from process
                    if (!OpenProcessToken(GetCurrentProcess(), TokenAccessLevels.Duplicate, out var processToken))
                    {
                        throw new Win32Exception();
                    }

                    if (!DuplicateTokenEx(processToken, TokenAccessLevels.Impersonate | TokenAccessLevels.Query | TokenAccessLevels.AdjustPrivileges,
                        IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Impersonation, out token))
                    {
                        throw new Win32Exception();
                    }

                    if (!SetThreadToken(IntPtr.Zero, token))
                    {
                        throw new Win32Exception();
                    }
                    this.IsImpersonating = true;
                }

                this.Handle = token;
                Current = this;
            }

            public void Dispose()
            {
                if (OwnerThread != Thread.CurrentThread)
                {
                    throw new InvalidOperationException("Wrong thread");
                }
                if (Current != this)
                {
                    throw new ObjectDisposedException(nameof(ThreadTokenScope));
                }
                Current = null;

                if (IsImpersonating)
                {
                    RevertToSelf();
                }
                IsImpersonating = false;
            }
        }
    }
}

NativeMethods.cs:

using Microsoft.Win32.SafeHandles;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;

namespace Esatto.Win32.Processes
{
    internal static class NativeMethods
    {
        const string Advapi32 = "advapi32.dll";
        const string Kernel32 = "kernel32.dll";
        const string Wtsapi32 = "wtsapi32.dll";
        const string Userenv = "userenv.dll";

        #region constants

        public const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x00002000;
        public const uint SE_PRIVILEGE_DISABLED = 0x00000000;
        public const uint SE_PRIVILEGE_ENABLED = 0x00000002;
        public const int ERROR_SUCCESS = 0x0;
        public const int ERROR_ACCESS_DENIED = 0x5;
        public const int ERROR_NOT_ENOUGH_MEMORY = 0x8;
        public const int ERROR_NO_TOKEN = 0x3f0;
        public const int ERROR_NOT_ALL_ASSIGNED = 0x514;
        public const int ERROR_NO_SUCH_PRIVILEGE = 0x521;
        public const int ERROR_CANT_OPEN_ANONYMOUS = 0x543;
        public const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
        public const uint STANDARD_RIGHTS_READ = 0x00020000;
        public const uint NORMAL_PRIORITY_CLASS = 0x0020;
        public const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
        public const uint MAX_PATH = 260;
        public const uint CREATE_NO_WINDOW = 0x08000000;
        public const uint INFINITE = 0xFFFFFFFF;

        #endregion

        #region Advapi32

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool AdjustTokenPrivileges(SafeTokenHandle TokenHandle, bool DisableAllPrivileges,
            IntPtr NewState, uint BufferLength, IntPtr PreviousState, out uint ReturnLength);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool RevertToSelf();

        [DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID Luid);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool OpenProcessToken(IntPtr ProcessToken, TokenAccessLevels DesiredAccess, out SafeTokenHandle TokenHandle);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool OpenThreadToken(IntPtr ThreadToken, TokenAccessLevels DesiredAccess, bool OpenAsSelf, out SafeTokenHandle TokenHandle);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool DuplicateTokenEx(SafeTokenHandle ExistingToken, TokenAccessLevels DesiredAccess,
            IntPtr TokenAttributes, SecurityImpersonationLevel ImpersonationLevel, TokenType TokenType, out SafeTokenHandle NewToken);

        [DllImport(Advapi32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool SetThreadToken(IntPtr Thread, SafeTokenHandle Token);

        [DllImport(Advapi32, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateProcessAsUser(SafeTokenHandle hToken,
            StringBuilder appExeName, StringBuilder commandLine, IntPtr processAttributes,
            IntPtr threadAttributes, bool inheritHandles, uint dwCreationFlags,
            EnvironmentBlockSafeHandle environment, string currentDirectory, ref STARTUPINFO startupInfo,
            out PROCESS_INFORMATION startupInformation);

        [DllImport(Advapi32, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool GetTokenInformation(IntPtr TokenHandle,
            TokenInformationClass TokenInformationClass, out int TokenInformation,
            uint TokenInformationLength, out uint ReturnLength);

        #endregion

        #region Kernel32

        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern bool CloseHandle(IntPtr handle);

        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern IntPtr GetCurrentProcess();

        [DllImport(Kernel32, ExactSpelling = true, SetLastError = true)]
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
        public static extern IntPtr GetCurrentThread();

        [DllImport(Kernel32, CharSet = CharSet.Auto, SetLastError = true)]
        public static extern SafeJobHandle CreateJobObject(IntPtr lpJobAttributes, string lpName);

        [DllImport(Kernel32, SetLastError = true)]
        public static extern bool SetInformationJobObject(SafeJobHandle hJob, JobObjectInfoType infoType,
            ref JOBOBJECT_EXTENDED_LIMIT_INFORMATION lpJobObjectInfo, int cbJobObjectInfoLength);

        [DllImport(Kernel32, SetLastError = true)]
        public static extern bool AssignProcessToJobObject(SafeJobHandle job, IntPtr process);

        #endregion

        #region Wtsapi

        [DllImport(Wtsapi32, ExactSpelling = true, SetLastError = true)]
        public static extern bool WTSQueryUserToken(int sessionid, out SafeTokenHandle handle);

        #endregion

        #region Userenv

        [DllImport(Userenv, CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateEnvironmentBlock(out EnvironmentBlockSafeHandle lpEnvironment, SafeTokenHandle hToken, bool bInherit);

        [DllImport(Userenv, ExactSpelling = true, SetLastError = true)]
        public extern static bool DestroyEnvironmentBlock(IntPtr hEnvironment);

        #endregion

        #region Structs

        [StructLayout(LayoutKind.Sequential)]
        public struct IO_COUNTERS
        {
            public ulong ReadOperationCount;
            public ulong WriteOperationCount;
            public ulong OtherOperationCount;
            public ulong ReadTransferCount;
            public ulong WriteTransferCount;
            public ulong OtherTransferCount;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_BASIC_LIMIT_INFORMATION
        {
            public long PerProcessUserTimeLimit;
            public long PerJobUserTimeLimit;
            public uint LimitFlags;
            public UIntPtr MinimumWorkingSetSize;
            public UIntPtr MaximumWorkingSetSize;
            public uint ActiveProcessLimit;
            public UIntPtr Affinity;
            public uint PriorityClass;
            public uint SchedulingClass;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES
        {
            public uint nLength;
            public IntPtr lpSecurityDescriptor;
            public int bInheritHandle;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
        {
            public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
            public IO_COUNTERS IoInfo;
            public UIntPtr ProcessMemoryLimit;
            public UIntPtr JobMemoryLimit;
            public UIntPtr PeakProcessMemoryUsed;
            public UIntPtr PeakJobMemoryUsed;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct PROCESS_INFORMATION
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public uint dwProcessId;
            public uint dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal struct STARTUPINFO
        {
            public int cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public int dwX;
            public int dwY;
            public int dwXSize;
            public int dwYSize;
            public int dwXCountChars;
            public int dwYCountChars;
            public int dwFillAttribute;
            public int dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID
        {
            public uint LowPart;
            public uint HighPart;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct LUID_AND_ATTRIBUTES
        {
            public LUID Luid;
            public uint Attributes;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct TOKEN_PRIVILEGE
        {
            public uint PrivilegeCount;
            public LUID_AND_ATTRIBUTES Privilege;
        }

        #endregion

        #region Enums

        public enum JobObjectInfoType
        {
            AssociateCompletionPortInformation = 7,
            BasicLimitInformation = 2,
            BasicUIRestrictions = 4,
            EndOfJobTimeInformation = 6,
            ExtendedLimitInformation = 9,
            SecurityLimitInformation = 5,
            GroupInformation = 11
        }

        public enum SecurityImpersonationLevel
        {
            Anonymous = 0,
            Identification = 1,
            Impersonation = 2,
            Delegation = 3,
        }

        public enum TokenType
        {
            Primary = 1,
            Impersonation = 2,
        }

        public enum TokenInformationClass
        {
            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,
            TokenIsAppContainer,
            TokenCapabilities,
            TokenAppContainerSid,
            TokenAppContainerNumber,
            TokenUserClaimAttributes,
            TokenDeviceClaimAttributes,
            TokenRestrictedUserClaimAttributes,
            TokenRestrictedDeviceClaimAttributes,
            TokenDeviceGroups,
            TokenRestrictedDeviceGroups,
            // MaxTokenInfoClass should always be the last enum
            MaxTokenInfoClass
        }

        #endregion

        #region SafeHandles

        public sealed class EnvironmentBlockSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public EnvironmentBlockSafeHandle()
                : base(true)
            {
            }

            protected override bool ReleaseHandle()
            {
                return DestroyEnvironmentBlock(handle);
            }
        }

        public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeTokenHandle()
                : base(true)
            {
            }

            override protected bool ReleaseHandle()
            {
                return CloseHandle(handle);
            }
        }

        internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            public SafeJobHandle()
                : base(true)
            {
            }

            protected override bool ReleaseHandle()
            {
                return CloseHandle(this.handle);
            }
        }

        #endregion
    }
}

Example Usage to create a process in another user's session:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Esatto.Win32.Processes
{
    using System.ComponentModel;
    using System.Runtime.InteropServices;
    using System.Security.Principal;
    using static NativeMethods;

#if ESATTO_WIN32
    public
#else
    internal
#endif
        static class ProcessInterop
    {
        public static void CreateProcessForSession(int sessionId, string exePath, string commandLine)
        {
            var privs = new[] { Privilege.TrustedComputingBase, Privilege.AssignPrimaryToken, Privilege.IncreaseQuota };
            Privilege.RunWithPrivileges(() => CreateProcessForSessionInternal(sessionId, exePath, commandLine), privs);
        }

        private static void CreateProcessForSessionInternal(int sessionId, string exePath, string commandLine)
        {
            SafeTokenHandle hDupToken;
            {
                SafeTokenHandle hToken;
                if (!WTSQueryUserToken(sessionId, out hToken))
                {
                    throw new Win32Exception();
                }

                using (hToken)
                {
                    if (!DuplicateTokenEx(hToken, TokenAccessLevels.AllAccess, IntPtr.Zero, SecurityImpersonationLevel.Impersonation, TokenType.Primary, out hDupToken))
                    {
                        throw new Win32Exception();
                    }
                }
            }

            using (hDupToken)
            {
                EnvironmentBlockSafeHandle env;
                if (!CreateEnvironmentBlock(out env, hDupToken, false))
                {
                    throw new Win32Exception();
                }

                using (env)
                {
                    STARTUPINFO si = new STARTUPINFO();
                    si.cb = Marshal.SizeOf(si);

                    PROCESS_INFORMATION procInfo;
                    if (!CreateProcessAsUser(hDupToken, new StringBuilder(exePath), new StringBuilder(commandLine),
                        IntPtr.Zero, IntPtr.Zero, false, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, env,
                        null, ref si, out procInfo))
                    {
                        throw new Win32Exception();
                    }

                    CloseHandle(procInfo.hProcess);
                    CloseHandle(procInfo.hThread);
                }
            }
        }

        public static int GetSessionId(this WindowsIdentity ident)
        {
            if (ident == null)
            {
                throw new ArgumentNullException();
            }

            int sessionId;
            uint unused;
            if (!GetTokenInformation(ident.Token, TokenInformationClass.TokenSessionId, out sessionId, 4, out unused))
            {
                throw new Win32Exception();
            }
            // since we are not passing a SafeHandle, we need to keep our reference on the SafeHandle
            // contained in the WindowsIdentity
            GC.KeepAlive(ident);

            return sessionId;
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文