はじめに
とあるやりたい事を実現するのに、プロセスに関連付けられているアクセストークンの特権を変更しないといけないことが分かり、参考になるソースコードを探してみたら、どれも古いMicrosoftのサンプルをベースにしたものばかりで、しっくり来るものがなかったので、より今どきのC#っぽい綺麗な(?)ものを自作してみた。
【参考】
ソース
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);
}
}
使い方
Privilege.Adjust(Privilege.SeTakeOwnershipPrivilege, true);
Privilege.Adjust(Privilege.SeRestorePrivilege, true);
Privilege.Adjust(Privilege.SeBackupPrivilege, true);
かんたん。
有効にする権限によっては、管理者権限が必要。
おわりに
IDisposable
なクラスで、Dispose
時に変更した特権を元に戻すようにしておいて、using
句で特権の変更範囲を最小限に留めるような仕組みも面白そう。
ただ実際は、頻繁に使うものでもないし、必要なところに↑のクラスの必要な部分だけ埋め込んでしまう方が実用的かも・・・