【Windows/C#】アクセストークンの特権を変更する

# はじめに
とあるやりたい事を実現するのに、プロセスに関連付けられているアクセストークンの特権を変更しないといけないことが分かり、参考になるソースコードを探してみたら、どれも古いMicrosoftのサンプルをベースにしたものばかりで、しっくり来るものがなかったので、より今どきのC#っぽい綺麗な(？)ものを自作してみた。

【参考】

https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Win7Samples/winbase/bootconfigurationdata/bcdsamplelib/Utils.cs

# ソース
```csharp
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;

/// <summary>
/// 特権を変更するための機能を提供します。
/// </summary>
public static class Privilege
{
	/// <summary>
	/// 現在のプロセスに関連付けられているアクセストークンの特権を変更します。
	/// </summary>
	/// <param name="privilegeName">変更する特権名</param>
	/// <param name="enable">特権を有効にする場合は、true。それ以外は、false。</param>
	public static void Adjust(string privilegeName, bool enable)
	{
		if (!NativeMethods.LookupPrivilegeValue(null, privilegeName, out var priLuid))
			throw new ArgumentException("指定された特権名は存在しません。", nameof(privilegeName));

		var curProc = Process.GetCurrentProcess();
		if (curProc.Handle == IntPtr.Zero)
			throw new InvalidOperationException("プロセスハンドルが取得できませんでした。");

		if (!NativeMethods.OpenProcessToken(curProc.Handle,
			NativeMethods.TOKEN_ADJUST_PRIVILEGES | NativeMethods.TOKEN_QUERY,
			out var hToken))
			throw new InvalidOperationException("トークンハンドルが取得できませんでした。", new Win32Exception());

		using (hToken)
		{
			var tokenPrivileges = new NativeMethods.TOKEN_PRIVILEGES()
			{
				PrivilegeCount = 1,
				Privileges = new int[3]
				{
					priLuid.LowPart,
					priLuid.HighPart,
					enable ? NativeMethods.SE_PRIVILEGE_ENABLED : 0
				}
			};

			NativeMethods.AdjustTokenPrivileges(
				hToken, false, tokenPrivileges,
				Marshal.SizeOf(tokenPrivileges), IntPtr.Zero, out _);

			int err = Marshal.GetLastWin32Error();
			if (err != 0)
				throw new Win32Exception(err);
		}
	}

	#region NT Defined Privileges
	public const string SeCreateTokenPrivilege = "SeCreateTokenPrivilege";
	public const string SeAssignPrimaryTokenPrivilege = "SeAssignPrimaryTokenPrivilege";
	public const string SeLockMemoryPrivilege = "SeLockMemoryPrivilege";
	public const string SeIncreaseQuotaPrivilege = "SeIncreaseQuotaPrivilege";
	public const string SeUnsolicitedInputPrivilege = "SeUnsolicitedInputPrivilege";
	public const string SeMachineAccountPrivilege = "SeMachineAccountPrivilege";
	public const string SeTcbPrivilege = "SeTcbPrivilege";
	public const string SeSecurityPrivilege = "SeSecurityPrivilege";
	public const string SeTakeOwnershipPrivilege = "SeTakeOwnershipPrivilege";
	public const string SeLoadDriverPrivilege = "SeLoadDriverPrivilege";
	public const string SeSystemProfilePrivilege = "SeSystemProfilePrivilege";
	public const string SeSystemtimePrivilege = "SeSystemtimePrivilege";
	public const string SeProfileSingleProcessPrivilege = "SeProfileSingleProcessPrivilege";
	public const string SeIncreaseBasePriorityPrivilege = "SeIncreaseBasePriorityPrivilege";
	public const string SeCreatePagefilePrivilege = "SeCreatePagefilePrivilege";
	public const string SeCreatePermanentPrivilege = "SeCreatePermanentPrivilege";
	public const string SeBackupPrivilege = "SeBackupPrivilege";
	public const string SeRestorePrivilege = "SeRestorePrivilege";
	public const string SeShutdownPrivilege = "SeShutdownPrivilege";
	public const string SeDebugPrivilege = "SeDebugPrivilege";
	public const string SeAuditPrivilege = "SeAuditPrivilege";
	public const string SeSystemEnvironmentPrivilege = "SeSystemEnvironmentPrivilege";
	public const string SeChangeNotifyPrivilege = "SeChangeNotifyPrivilege";
	public const string SeRemoteShutdownPrivilege = "SeRemoteShutdownPrivilege";
	public const string SeUndockPrivilege = "SeUndockPrivilege";
	public const string SeSyncAgentPrivilege = "SeSyncAgentPrivilege";
	public const string SeEnableDelegationPrivilege = "SeEnableDelegationPrivilege";
	public const string SeManageVolumePrivilege = "SeManageVolumePrivilege";
	public const string SeImpersonatePrivilege = "SeImpersonatePrivilege";
	public const string SeCreateGlobalPrivilege = "SeCreateGlobalPrivilege";
	public const string SeTrustedCredManAccessPrivilege = "SeTrustedCredManAccessPrivilege";
	public const string SeRelabelPrivilege = "SeRelabelPrivilege";
	public const string SeIncreaseWorkingSetPrivilege = "SeIncreaseWorkingSetPrivilege";
	public const string SeTimeZonePrivilege = "SeTimeZonePrivilege";
	public const string SeCreateSymbolicLinkPrivilege = "SeCreateSymbolicLinkPrivilege";
	#endregion

	private static class NativeMethods
	{
		internal const int SE_PRIVILEGE_ENABLED = 0x2;
		internal const int TOKEN_QUERY = 0x8;
		internal const int TOKEN_ADJUST_PRIVILEGES = 0x20;

		[StructLayout(LayoutKind.Sequential)]
		internal struct TOKEN_PRIVILEGES
		{
			public int PrivilegeCount;
			[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
			public int[] Privileges;
		}

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

		[DllImport("Kernel32.dll", SetLastError = true)]
		internal static extern bool CloseHandle(IntPtr hObject);
		[DllImport("Advapi32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
		internal static extern bool OpenProcessToken(IntPtr hProcess, uint desiredAccess, out SafeProcessTokenHandle hToken);
		[DllImport("Advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
		internal static extern bool LookupPrivilegeValue(
			[MarshalAs(UnmanagedType.LPWStr), In] string? lpSystemName,
			[MarshalAs(UnmanagedType.LPWStr), In] string lpName,
			out LUID Luid);
		[DllImport("Advapi32.dll", SetLastError = true), DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
		internal static extern bool AdjustTokenPrivileges(SafeProcessTokenHandle TokenHandle,
			bool DisableAllPrivileges, in TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, out int ReturnLength);
	}

	private class SafeProcessTokenHandle :
		Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid
	{
		public SafeProcessTokenHandle()
			: this(IntPtr.Zero)
		{
		}

		internal SafeProcessTokenHandle(IntPtr handle)
			: base(true)
		{
			this.SetHandle(handle);
		}

		protected override bool ReleaseHandle() => NativeMethods.CloseHandle(this.handle);
	}
}
```

# 使い方
```csharp
Privilege.Adjust(Privilege.SeTakeOwnershipPrivilege, true);
Privilege.Adjust(Privilege.SeRestorePrivilege, true);
Privilege.Adjust(Privilege.SeBackupPrivilege, true);
```
かんたん。
有効にする権限によっては、管理者権限が必要。

# おわりに
`IDisposable`なクラスで、`Dispose`時に変更した特権を元に戻すようにしておいて、`using`句で特権の変更範囲を最小限に留めるような仕組みも面白そう。
ただ実際は、頻繁に使うものでもないし、必要なところに↑のクラスの必要な部分だけ埋め込んでしまう方が実用的かも・・・