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)
1203정성태12/21/201125588제니퍼 .NET: 18. MEF가 적용된 ASP.NET 웹 사이트를 제니퍼 닷넷으로 모니터링 해본 결과! [6]
1202정성태12/21/201126087오류 유형: 144. The database '...' cannot be opened because it is version 661.
1201정성태12/14/201141134디버깅 기술: 47. .NET Reflector를 이용한 "소스 코드가 없는" 어셈블리 디버깅 [4]
1200정성태12/11/201126970디버깅 기술: 46. Windbg 확장 DLL 만들기 (2) - Debugger Extension API 사용파일 다운로드1
1199정성태12/11/201128374VC++: 55. JNI DLL 컴파일 시 x86과 x64의 Export된 함수의 이름이 왜 다를까요? [2]파일 다운로드1
1198정성태12/9/201132195디버깅 기술: 45. Windbg 확장 DLL 만들기 (1) - 스레드를 강제 종료시키는 명령어 [2]파일 다운로드1
1197정성태12/9/201129968.NET Framework: 282. Shader 강좌와 함께 배워보는 XNA Framework (2) - RenderMonkey의 Shader/Model 파일 연동파일 다운로드2
1196정성태12/9/201133160.NET Framework: 281. Shader 강좌와 함께 배워보는 XNA Framework (1) - 기초 프로그램 구조 [3]파일 다운로드2
1195정성태12/8/201147802오류 유형: 143. DXSDK_Jun10.exe 설치 시 "Error Code: S1023" 오류 해결하는 방법 [4]
1194정성태12/8/201135585개발 환경 구성: 137. Visual C++ 런타임 구성요소에 대한 디버그 버전 설치하는 방법
1193정성태12/8/201122642오류 유형: 142. Windows Phone SDK 7.1 설치 시 Expression Blend 제거를 요구하는 경우
1192정성태12/8/201125675개발 환경 구성: 136. Windows 7 SP1의 IIS에서 사용자 프로파일을 로드하는 방법
1191정성태12/6/201126826.NET Framework: 280. MVC3에서 JavaScriptSerializer 재정의하는 방법파일 다운로드1
1190정성태12/6/201129995오류 유형: 141. Visual C++ 컴파일 오류 - error C2275: 'xxxxx' : illegal use of this type as an expression [1]
1189정성태12/6/201137061VS.NET IDE: 70. Visual Studio에서 프로젝트 로드가 안된다면?
1188정성태12/3/201126162개발 환경 구성: 135. 마이크로소프트 TFS 호스팅 서비스 - Preview [3]
1187정성태12/2/201130820개발 환경 구성: 134. Robocopy 오류 및 종료 코드
1186정성태12/1/201132683.NET Framework: 279. WPF - 그리기 성능 및 Blurring 문제파일 다운로드1
1185정성태11/29/201123404.NET Framework: 278. WPF - Content의 Changed 이벤트에 해당하는게 뭔가요?파일 다운로드1
1184정성태11/29/201126208.NET Framework: 277. F#과 WPF가 어울리지 못하는 근본적인 이유 [2]
1183정성태11/26/201121715오류 유형: 140. Visual Studio 2010 - Floating된 에디트 윈도우가 사라지지 않는 경우 [2]
1182정성태11/25/201157436.NET Framework: 276. 중복 없는 숫자를 랜덤으로 배열하는 방법 [5]파일 다운로드1
1181정성태11/24/201127922디버깅 기술: 44. windbg의 mscordacwks DLL 로드 문제
1180정성태11/23/201137707.NET Framework: 275. 레지스트리 등록 및 Interop DLL 없이 COM 개체 사용하는 방법 [2]파일 다운로드1
1179정성태11/22/201128289.NET Framework: 274. ReaderWriterLockSlim은 언제 쓰는 걸까요? [4]파일 다운로드1
1178정성태11/19/201124807.NET Framework: 273. 설치된 .NET 버전에 민감한 코드를 포함하는 경우, 다중으로 어셈블리를 만들어야 할까요?파일 다운로드1
... 151  152  153  [154]  155  156  157  158  159  160  161  162  163  164  165  ...