Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)
(시리즈 글이 4개 있습니다.)
Windows: 158. 컴퓨터와 사용자의 SID(security identifier) 확인 방법
; https://www.sysnet.pe.kr/2/0/11819

.NET Framework: 2084. C# - GetTokenInformation으로 사용자 SID(Security identifiers) 구하는 방법
; https://www.sysnet.pe.kr/2/0/13206

.NET Framework: 2085. C# - gpedit.msc의 "User Rights Assignment" 특권을 코드로 설정/해제하는 방법
; https://www.sysnet.pe.kr/2/0/13207

.NET Framework: 2130. C# - Win32 API를 이용한 윈도우 계정 정보 (예: 마지막 로그온 시간)
; https://www.sysnet.pe.kr/2/0/13382




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(STATUS_INVALID_PARAMETER)를 반환한다면? 제 경우에 GetTokenInformation으로 반환받은 2번째 인자의 Sid가,

LsaAddAccountRights(policyHandle, tokenUser.User.Sid, &userRightsLSAString, 1);

메모리 해제되었기 때문에 발생한 문제였습니다. 지난번에 설명한 것처럼, TokenUser 내부에 SID 정보가 함께 포함돼 있기 때문에 LsaAddAccountRights API를 호출하기 전까지는 GetTokenInformation으로 반환한 구조체 메모리를 해제해서는 안 됩니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 3/26/2025]

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

비밀번호

댓글 작성자
 



2024-12-04 07시48분
특권을 조회하는 whoami 명령어

c:\temp> whoami
testpc\testusr

c:\temp> whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name Description State
============================= ==================================== ========
SeShutdownPrivilege Shut down the system Disabled
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeUndockPrivilege Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set Disabled
SeTimeZonePrivilege Change the time zone Disabled
정성태

... 151  152  153  154  155  156  157  158  159  160  161  162  [163]  164  165  ...
NoWriterDateCnt.TitleFile(s)
972정성태1/7/201124204개발 환경 구성: 95. SQL Server 2008 R2 이하 버전 정보 확인
971정성태1/5/201133774.NET Framework: 199. .NET 코드 - Named Pipe 닷넷 서버와 VC++ 클라이언트 제작 [2]파일 다운로드1
970정성태1/4/201134307.NET Framework: 198. 윈도우 응용 프로그램에 Facebook 로그인 연동 [1]파일 다운로드1
969정성태12/31/201040397VC++: 45. Winsock 2 Layered Service Provider - Visual Studio 2010용 프로젝트 [1]파일 다운로드1
968정성태12/30/201026669개발 환경 구성: 94. 개발자가 선택할 수 있는 윈도우에서의 네트워크 프로그래밍 기술 [2]
967정성태12/27/201028427.NET Framework: 197. .NET 코드 - 단일 Process 실행파일 다운로드1
966정성태12/26/201026380.NET Framework: 196. .NET 코드 - 창 흔드는 효과파일 다운로드1
965정성태12/25/201027892개발 환경 구성: 93. MSBuild를 이용한 닷넷 응용프로그램의 다중 어셈블리 출력 빌드파일 다운로드1
964정성태12/21/2010143055개발 환경 구성: 92. 윈도우 서버 환경에서, 최대 생성 가능한 소켓(socket) 연결 수는 얼마일까? [14]
963정성태12/13/201027925개발 환경 구성: 91. MSBuild를 이용한 닷넷 응용프로그램의 플랫폼(x86/x64)별 빌드 [2]파일 다운로드1
962정성태12/10/201022766오류 유형: 110. GAC 등록 - Failure adding assembly to the cache: Invalid file or assembly name.
961정성태12/10/201099813개발 환경 구성: 90. 닷넷에서 접근해보는 PostgreSQL DB [5]
960정성태12/8/201045143.NET Framework: 195. .NET에서 코어(Core) 관련 CPU 정보 알아내는 방법파일 다운로드1
959정성태12/8/201031960.NET Framework: 194. Facebook 연동 - API Error Description: Invalid OAuth 2.0 Access Token
958정성태12/7/201028969개발 환경 구성: 89. 배치(batch) 파일에서 또 다른 배치 파일을 동기 방식으로 실행 및 반환값 얻기 [2]
957정성태12/6/201031722디버깅 기술: 31. Windbg - Visual Studio 디버그 상태에서 종료해 버리는 응용 프로그램 [3]
953정성태11/28/201036939.NET Framework: 193. 페이스북(Facebook) 계정으로 로그인하는 C# 웹 사이트 제작 [5]
952정성태11/25/201025362.NET Framework: 192. GC의 부하는 상대적인 것! [4]
950정성태11/18/201076731.NET Framework: 191. ClickOnce - 관리자 권한 상승하는 방법 [17]파일 다운로드2
954정성태11/29/201048721    답변글 .NET Framework: 191.1. [답변] 클릭원스 - 요청한 작업을 수행하려면 권한 상승이 필요합니다. (Exception from HRESULT: 0x800702E4) [2]
949정성태11/16/201027255오류 유형: 109. System.ServiceModel.Security.SecurityNegotiationException
948정성태11/16/201036097.NET Framework: 190. 트위터 계정으로 로그인하는 C# 웹 사이트 제작 [7]파일 다운로드1
947정성태11/14/201041704.NET Framework: 189. Mono Cecil로 만들어 보는 .NET Decompiler [1]파일 다운로드1
946정성태11/11/201041561.NET Framework: 188. .NET 64비트 응용 프로그램에서 왜 (2GB) OutOfMemoryException 예외가 발생할까? [1]파일 다운로드1
945정성태11/11/201025067VC++: 44. C++/CLI 컴파일 오류 - error C4368: mixed types are not supported
944정성태11/11/201031594VC++: 43. C++/CLI 컴파일 오류 - error C2872: 'IServiceProvider' : ambiguous symbol could be ...
... 151  152  153  154  155  156  157  158  159  160  161  162  [163]  164  165  ...