Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

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" 특권에 원하는 계정 또는 그룹을 설정하는 방법을 보여주고 있습니다.

enable_large_page_1.png

경우에 따라서는 이런 특권 설정을 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으로 반환한 구조체 메모리를 해제해서는 안 됩니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 12/26/2022]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... [31]  32  33  34  35  36  37  38  39  40  41  42  43  44  45  ...
NoWriterDateCnt.TitleFile(s)
12862정성태12/3/20215981오류 유형: 768. Golang - 빌드 시 "cmd/go: unsupported GOOS/GOARCH pair linux /amd64" 오류
12861정성태12/3/20218198개발 환경 구성: 609. 파이썬 - "Windows embeddable package"로 개발 환경 구성하는 방법
12860정성태12/1/20216306오류 유형: 767. SQL Server - 127.0.0.1로 접속하는 경우 "Access is denied"가 발생한다면?
12859정성태12/1/202112479개발 환경 구성: 608. Hyper-V 가상 머신에 Console 모드로 로그인하는 방법
12858정성태11/30/20219736개발 환경 구성: 607. 로컬의 USB 장치를 원격 머신에 제공하는 방법 - usbip-win
12857정성태11/24/20217149개발 환경 구성: 606. WSL Ubuntu 20.04에서 파이썬을 위한 uwsgi 설치 방법
12856정성태11/23/20218984.NET Framework: 1121. C# - 동일한 IP:Port로 바인딩 가능한 서버 소켓 [2]
12855정성태11/13/20216278개발 환경 구성: 605. Azure App Service - Kudu SSH 환경에서 FTP를 이용한 파일 전송
12854정성태11/13/20217917개발 환경 구성: 604. Azure - 윈도우 VM에서 FTP 여는 방법
12853정성태11/10/20216244오류 유형: 766. Azure App Service - JBoss 호스팅 생성 시 "This region has quota of 0 PremiumV3 instances for your subscription. Try selecting different region or SKU."
12851정성태11/1/20217649스크립트: 34. 파이썬 - MySQLdb 기본 예제 코드
12850정성태10/27/20218813오류 유형: 765. 우분투에서 pip install mysqlclient 실행 시 "OSError: mysql_config not found" 오류
12849정성태10/17/20217914스크립트: 33. JavaScript와 C#의 시간 변환 [1]
12848정성태10/17/20218885스크립트: 32. 파이썬 - sqlite3 기본 예제 코드 [1]
12847정성태10/14/20218732스크립트: 31. 파이썬 gunicorn - WORKER TIMEOUT 오류 발생
12846정성태10/7/20218499스크립트: 30. 파이썬 __debug__ 플래그 변수에 따른 코드 실행 제어
12845정성태10/6/20218345.NET Framework: 1120. C# - BufferBlock<T> 사용 예제 [5]파일 다운로드1
12844정성태10/3/20216324오류 유형: 764. MSI 설치 시 "... is accessible and not read-only." 오류 메시지
12843정성태10/3/20216774스크립트: 29. 파이썬 - fork 시 기존 클라이언트 소켓 및 스레드의 동작파일 다운로드1
12842정성태10/1/202125209오류 유형: 763. 파이썬 오류 - AttributeError: type object '...' has no attribute '...'
12841정성태10/1/20218615스크립트: 28. 모든 파이썬 프로세스에 올라오는 특별한 파일 - sitecustomize.py
12840정성태9/30/20218690.NET Framework: 1119. Entity Framework의 Join 사용 시 다중 칼럼에 대한 OR 조건 쿼리파일 다운로드1
12839정성태9/15/20219741.NET Framework: 1118. C# 11 - 제네릭 타입의 특성 적용파일 다운로드1
12838정성태9/13/20219361.NET Framework: 1117. C# - Task에 전달한 Action, Func 유형에 따라 달라지는 async/await 비동기 처리 [2]파일 다운로드1
12837정성태9/11/20218293VC++: 151. Golang - fmt.Errorf, errors.Is, errors.As 설명
12836정성태9/10/20217895Linux: 45. 리눅스 - 실행 중인 다른 프로그램의 출력을 확인하는 방법
... [31]  32  33  34  35  36  37  38  39  40  41  42  43  44  45  ...