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
정성태

... 121  122  123  124  125  126  127  128  129  [130]  131  132  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1806정성태11/10/201425135.NET Framework: 477. SeCreateGlobalPrivilege 특권과 WCF NamedPipe
1805정성태11/5/201421993.NET Framework: 476. Visual Studio에서 Mono용 Profiler 개발 [3]파일 다운로드1
1804정성태11/5/201428248.NET Framework: 475. ETW(Event Tracing for Windows)를 C#에서 사용하는 방법 [9]파일 다운로드1
1803정성태11/4/201420343오류 유형: 261. Windows Server Backup 오류 - Error in backup of E:\$Extend\$RmMetadata\$TxfLog
1802정성태11/4/201422263오류 유형: 260. 이벤트 로그 - Windows Error Reporting / AEAPPINVW8
1801정성태11/4/201427609오류 유형: 259. 이벤트 로그 - Windows Error Reporting / IPX Assertion / KorIME.exe [1]
1800정성태11/4/201418262오류 유형: 258. 이벤트 로그 - Starting a SMART disk polling operation in Automatic mode.
1799정성태11/4/201423070오류 유형: 257. 이벤트 로그 - The WMI Performance Adapter service entered the stopped state.
1798정성태11/4/201431861오류 유형: 256. 이벤트 로그 - The WinHTTP Web Proxy Auto-Discovery Service service entered the stopped state. [1]
1797정성태11/4/201417576오류 유형: 255. 이벤트 로그 - The Adobe Flash Player Update Service service entered the stopped state.
1796정성태10/30/201424544개발 환경 구성: 249. Visual Studio 2013에서 Mono 컴파일하는 방법
1795정성태10/29/201427075개발 환경 구성: 248. Lync 2013 서버 설치 방법
1794정성태10/29/201422501개발 환경 구성: 247. "Microsoft Office 365 Enterprise E3" 서비스에 대한 간략 소개
1793정성태10/27/201423158.NET Framework: 474. C# - chromiumembedded 사용 - 두 번째 이야기 [2]파일 다운로드1
1792정성태10/27/201423286.NET Framework: 473. WebClient 객체에 쿠키(Cookie)를 사용하는 방법
1791정성태10/22/201423007VC++: 83. G++ - 템플릿 클래스의 iterator 코드 사용에서 발생하는 컴파일 오류 [5]
1790정성태10/22/201418536오류 유형: 254. NETLOGON Service is paused on [... AD Server...]
1789정성태10/22/201421211오류 유형: 253. 이벤트 로그 - The client-side extension could not remove user policy settings for '...'
1788정성태10/22/201423237VC++: 82. COM 프로그래밍에서 HRESULT 타입의 S_FALSE는 실패일까요? 성공일까요? [2]
1787정성태10/22/201431405오류 유형: 252. COM 개체 등록시 0x8002801C 오류가 발생한다면?
1786정성태10/22/201432730디버깅 기술: 65. 프로세스 비정상 종료 시 "Debug Diagnostic Tool"를 이용해 덤프를 남기는 방법 [3]파일 다운로드1
1785정성태10/22/201421942오류 유형: 251. 이벤트 로그 - Load control template file /_controltemplates/TaxonomyPicker.ascx failed [1]
1784정성태10/22/201430027.NET Framework: 472. C/C++과 C# 사이의 메모리 할당/해제 방법파일 다운로드1
1783정성태10/21/201423491VC++: 81. 프로그래밍에서 borrowing의 개념
1782정성태10/21/201420219오류 유형: 250. 이벤트 로그 - Application Server job failed for service instance Microsoft.Office.Server.Search.Administration.SearchServiceInstance
1781정성태10/21/201420668디버깅 기술: 64. new/delete의 짝이 맞는 경우에도 메모리 누수가 발생한다면?
... 121  122  123  124  125  126  127  128  129  [130]  131  132  133  134  135  ...