C# - gpedit.msc의 "User Rights Assignment" 특권을 코드로 설정/해제하는 방법
특권에 대한 제어는 gpedit.msc를 이용해 "Local Computer Policy" / "Computer Configuration" / "Windows Settings" / "Security Settigns" / "Local Policies" / "User Rights Assignment" 패널에서 할 수 있습니다. 가령, 아래의 화면은 "Lock pages in memory" 특권에 원하는 계정 또는 그룹을 설정하는 방법을 보여주고 있습니다.
경우에 따라서는 이런 특권 설정을 GUI를 통해 하는 것이 번거로울 수 있는데요, 당연히 코드를 통해 제어하는 방법도 제공하고 있습니다. ^^
Assigning Privileges to an Account
; https://learn.microsoft.com/en-us/windows/win32/secbp/assigning-privileges-to-an-account
위의 설명에 간략하게 나오지만 LsaAddAccountRights API를 통해 가능합니다. (제거는
LsaRemoveAccountRights)
LsaAddAccountRights function
; https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-lsaaddaccountrights
구체적인 사용법은 아래의 Q&A에서 찾을 수 있는데요,
Enable large pages in Windows programmatically
; https://stackoverflow.com/questions/42354504/enable-large-pages-in-windows-programmatically
위의 글에서는 C++로 제공하고 있으니 ^^ 이 글에서는 C#으로 포팅해 다음과 같이 작성할 수 있습니다.
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Principal;
using System.Text;
[assembly: SupportedOSPlatform("windows")]
internal class Program
{
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
static unsafe extern uint LsaOpenPolicy(LSA_UNICODE_STRING systemName, LSA_OBJECT_ATTRIBUTES* objectAttributes, POLICY_ACCESS desiredAccess, out IntPtr PolicyHandle);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
static unsafe extern uint LsaAddAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, LSA_UNICODE_STRING* UserRights, uint CountOfRights);
[DllImport("advapi32.dll", SetLastError = true, PreserveSig = true)]
static unsafe extern uint LsaRemoveAccountRights(IntPtr PolicyHandle, IntPtr AccountSid, bool allRights, LSA_UNICODE_STRING* UserRights, uint CountOfRights);
[DllImport("advapi32.dll")]
private static extern long LsaClose(IntPtr ObjectHandle);
public const string SE_LOCK_MEMORY_NAME = "SeLockMemoryPrivilege";
public const int STATUS_SUCCESS = 0;
static void Main(string[] args)
{
IntPtr hToken = WindowsIdentity.GetCurrent().Token;
using (Win32UserToken tokenUser = new Win32UserToken(hToken))
{
Console.WriteLine($"SID Found: {tokenUser.Sid}");
// 특권에 사용자 추가
if (ChangePrivileges(tokenUser, SE_LOCK_MEMORY_NAME, true) == true)
{
Console.WriteLine($"{SE_LOCK_MEMORY_NAME}: added");
}
// 특권에 사용자 제거
// if (ChangePrivileges(tokenUser, SE_LOCK_MEMORY_NAME, false) == true)
// {
// Console.WriteLine($"{SE_LOCK_MEMORY_NAME}: removed");
// }
}
}
private static unsafe bool ChangePrivileges(TOKEN_USER tokenUser, string privilegeName, bool addRights)
{
LSA_OBJECT_ATTRIBUTES attr = new LSA_OBJECT_ATTRIBUTES();
IntPtr policyHandle;
LSA_UNICODE_STRING serverName = new LSA_UNICODE_STRING();
uint result = LsaOpenPolicy(serverName, &attr, POLICY_ACCESS.POLICY_CREATE_ACCOUNT | POLICY_ACCESS.POLICY_LOOKUP_NAMES, out policyHandle);
if (result != 0)
{
#if DEBUG
Console.WriteLine($"LsaOpenPolicy failed: {Marshal.GetLastWin32Error()}");
#endif
return false;
}
LSA_UNICODE_STRING userRightsLSAString = new LSA_UNICODE_STRING(privilegeName);
try
{
if (addRights)
{
result = LsaAddAccountRights(policyHandle, tokenUser.User.Sid, &userRightsLSAString, 1);
#if DEBUG
if (result != STATUS_SUCCESS)
{
Console.WriteLine($"ChangePrivileges failed: {result:x}, {Marshal.GetLastWin32Error()}");
}
#endif
}
else
{
result = LsaRemoveAccountRights(policyHandle, tokenUser.User.Sid, false, &userRightsLSAString, 1);
}
}
finally
{
userRightsLSAString.Dispose();
LsaClose(policyHandle);
}
#if DEBUG
if (result != STATUS_SUCCESS)
{
Console.WriteLine($"ChangePrivileges failed: {result:x}, {Marshal.GetLastWin32Error()}");
}
#endif
return result == STATUS_SUCCESS;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct LSA_UNICODE_STRING
{
public UInt16 Length;
public UInt16 MaximumLength;
public IntPtr Buffer;
public LSA_UNICODE_STRING(string text)
{
Buffer = Marshal.StringToHGlobalUni(text);
Length = (UInt16)(text.Length * UnicodeEncoding.CharSize);
MaximumLength = (UInt16)(Length + UnicodeEncoding.CharSize);
}
public override string ToString()
{
return Marshal.PtrToStringUni(Buffer, Length / UnicodeEncoding.CharSize);
}
public void Dispose()
{
if (Buffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(Buffer);
}
Buffer = IntPtr.Zero;
Length = 0;
MaximumLength = 0;
}
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct LSA_OBJECT_ATTRIBUTES
{
public uint Length;
public IntPtr RootDirectory;
public LSA_UNICODE_STRING* ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
[Flags]
internal enum POLICY_ACCESS
{
POLICY_CREATE_ACCOUNT = 0x00000010,
POLICY_LOOKUP_NAMES = 0x00000800,
}
위의 코드는 특권을 변경하는 중요한 보안 이슈인만큼 반드시 "관리자 권한"으로 실행해야 합니다. 또한, "
Assigning Privileges to an Account" 문서에서도 언급하고 있지만,
Assigning a privilege to an account does not affect existing user tokens. A user must log off and then log back on to get an access token with the newly assigned privilege.
반드시 로그온/로그오프를 해야만 적용됩니다. (가령 위의 코드에서 예를 든 "Lock pages in memory" 특권의 경우, 기본적으로는 시스템에 어떠한 사용자 계정도 등록돼 있지 않습니다.)
게다가 변경 사항은 현재 실행 중인 gpedit.msc 화면에는 반영되지 않으므로 위의 코드 실행 후 사용자가 특권을 가지고 있는지 확인하려면 실행 중인 gpedit.msc를 종료하고 다시 실행해야만 합니다. 테스트하기가 여간 귀찮은 작업이 아닐 수 없습니다. ^^;
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
만약 LsaAddAccountRights가 c000000d를 반환한다면? 제 경우에 GetTokenInformation으로 반환받은 2번째 인자의 Sid가,
LsaAddAccountRights(policyHandle, tokenUser.User.Sid, &userRightsLSAString, 1);
메모리 해제되었기 때문에 발생한 문제였습니다.
지난번에 설명한 것처럼, TokenUser 내부에 SID 정보가 함께 포함돼 있기 때문에 LsaAddAccountRights API를 호출하기 전까지는 GetTokenInformation으로 반환한 구조체 메모리를 해제해서는 안 됩니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]