Microsoft MVP성태의 닷넷 이야기
Windows: 209. Windows NT Service에서 UI를 다루는 방법 [링크 복사], [링크+제목 복사],
조회: 16226
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 4개 있습니다.)
Windows: 82. 윈도우 8 - "Interactive Services Detection" 서비스 시작하는 방법
; https://www.sysnet.pe.kr/2/0/1583

Windows: 83. 윈도우 8 - UI가 있는 프로그램을 Local SYSTEM 권한의 세션 0 데스크톱에서 실행하는 방법
; https://www.sysnet.pe.kr/2/0/1584

개발 환경 구성: 265. TrustedInstaller 권한으로 프로그램 실행시키는 방법
; https://www.sysnet.pe.kr/2/0/2915

Windows: 209. Windows NT Service에서 UI를 다루는 방법
; https://www.sysnet.pe.kr/2/0/13120




Windows NT Service에서 UI를 다루는 방법

Windows의 Service는 처음 윈도우 프로그래밍을 배우는 분들에게는 다소 어려운 주제입니다. 또한 윈도우 버전마다 서비스를 다루는 방식이 달라지는 바람에 예전에, 가령 XP 시절에 Service를 많이 다뤄보셨다고 해도 근래에 다시 접하면 어려울 수 있습니다.

일단, 마이크로소프트가 제시한 원칙은 간단합니다.

NT Service 내에 작성한 코드에서는 UI 이외의 코드를 작성하고,
UI가 필요하다면 그 부분만 일반적인 데스크톱 프로그램에서 담당한다.

바로 저 원칙을 잘 따르는 프로그램이 "Microsoft SQL Server"입니다. SQL Server는 RDB 서비스를 sqlserver.exe 프로세스를 통해 제공하는데요, 그 프로세스 내에 있는 코드는 UI를 전혀 제공하지 않습니다.

물론, 데이터베이스를 만드는 등의 작업들은 쉽게 UI로 하고 싶을 텐데요, 그래서 마이크로소프트는 NT Service로 구동 중인 sqlsevrer.exe에서 제공하는 기능을 이용한 SQL Server Management Studio (SSMS) 도구를 별도로 제공합니다.

즉, UI는 SSMS가, 핵심 DB 코드는 NT Service가 담당하는 것입니다.




그런데, 만약... SQL Server처럼 별도의 UI 프로그램을 제공하지 않고 굳이 NT Service가 UI를 접근하고 싶다면 어떻게 해야 할까요? 이에 대한 방법을 아래의 글에서 설명하고 있습니다.

[.NET] Windows Service 에서 UI 사용하기
; https://blog.naver.com/vactorman/222847763183

그런데, 설명이 좀 아쉽군요. ^^

우선, "Windows Service"는 커널 모드로 실행되는 프로세스가 아닙니다. 그것 역시 User Mode이고, 오직 device driver로만 작성한 프로그램만이 커널 모드의 권한으로 실행이 됩니다. 따라서, 당연히 NT 서비스는 프로세스 별로 가상화된 User 메모리를 접근하고, 절대 커널 메모리에는 접근할 수 없습니다.

그렇다면, 왜? NT 서비스에서는 UI 생성을 하지 못하는 걸까요? 사실 NT 서비스도 UI를 생성할 수 있습니다. 단지, 그렇게 생성된 UI를 우리가 사용하는 화면에서는 보이지 않는 것뿐입니다. 왜냐하면, 서로 간에 세션 자체가 달라 UI가 활성화되는 Desktop이 분리돼 있기 때문입니다.

아래의 작업 관리자 화면을 보면,

nt_service_is_1.png

위에서 DtsApo4Service.exe는 0번 세션에서 활성 중인 NT 서비스입니다. Windows Vista 이후로 모든 NT 서비스는 이렇게 0번 세션에서 활성화됩니다. 반면, 우리가 사용하는 explorer.exe는 1번 세션에 묶여 있습니다. 일반적으로는, 물리 PC에 직접 로그인한 사용자가 1번 세션입니다.

앞서 설명한 대로, NT 서비스가 활성화되는 0번 세션을 우리는 볼 수 없습니다. 하지만 과거에는 공식적으로 이것이 가능하도록 "Interactive Services Detection" 서비스를 제공했으므로 저도 아래의 글에서,

윈도우 8 - UI가 있는 프로그램을 Local SYSTEM 권한의 세션 0 데스크톱에서 실행하는 방법
; https://www.sysnet.pe.kr/2/0/1584

psexec.exe를 활용해 계산기 프로세스(calc.exe)를 세션 0에 띄운 후 그 UI를 볼 수 있었습니다. 하지만, Windows 10 Build 1803과 Windows Server 2016/2019부터는 아예 "Interactive Services Detection"까지 없애 버렸습니다. (없어지긴 했지만, 그 프로그램이 사용하던 API가 없어지진 않았을 것이므로 별도로 구현하면 방법이 있을 것입니다. ^^)

세션이 나누어진 것을 낯설게 느낄 수도 있는데, 흔한 예로 RDP 접속을 하면 새로운 세션이 만들어지는 것이므로 여러분은 알게 모르게 자신만의 세션을 사용했던 것입니다. 일례로, 물리 컴퓨터 앞에 앉아 로그인하고 있는 사용자가 1번 세션을 사용하고, 그 컴퓨터에 RDP 접속을 한 사용자는 또 다른 세션을 사용하게 되는데, 그 사용자들 간에 UI를 간섭할 수 없다는 것을 여러분들은 바로 수긍할 수 있을 것입니다.

참고로, 세션과 데스크톱, 윈도우 스테이션에 관해서는 다음의 글에서 잘 설명하고 있습니다.

Sessions, Desktops and Windows Stations
; https://techcommunity.microsoft.com/t5/ask-the-performance-team/sessions-desktops-and-windows-stations/ba-p/372473

위의 글에 실린 아래의 그림이 너무 잘 표현해주고 있어서 더 이상의 설명은 생략합니다. ^^

nt_service_is_2.png




위의 사실을 인지하고 "[.NET] Windows Service 에서 UI 사용하기" 글에 실린 소스 코드를 보면 이제 이해가 될 것입니다.

public static bool StartProcessAndBypassUAC(string applicationName, out PROCESS_INFORMATION procInfo)
{
    uint winlogonPid = 0;
    IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
    procInfo = new PROCESS_INFORMATION();

    // Active Console Session을 찾는 거니까, 결국 사용자가 로그인한 세션의 아이디이고,
    uint dwSessionId = WTSGetActiveConsoleSessionId();

    // 그리고 그 세션에 속한 winlogon.exe 프로세스의 id를 가져온 다음,
    Process[] processes = Process.GetProcessesByName("winlogon");
    foreach (Process p in processes)
    {
        if ((uint)p.SessionId == dwSessionId)
        {
            winlogonPid = (uint)p.Id;
        }
    }

    // obtain a handle to the winlogon process
    hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);

    // obtain a handle to the access token of the winlogon process
    if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
    {
        CloseHandle(hProcess);
        return false;
    }

    // ... 그 프로세스를 실행한 사용자의 Access Token을 가져오고...
    // 세션 내의 첫 번째 Window Station에 활성화되도록 설정한 프로세스를 CreateProcessAsUser Win32 API를 이용해 실행
    // ...

    return result; // return the result
}

그리고 저 코드가 항상 실행되는 것은 아니라는 것도 알 수 있습니다. WTSGetActiveConsoleSessionId 함수는,

WTSGetActiveConsoleSessionId function (winbase.h)
; https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-wtsgetactiveconsolesessionid

using System.Runtime.InteropServices;

namespace ConsoleApp1;

internal class Program
{
    [DllImport("kernel32.dll")]
    internal static extern int WTSGetActiveConsoleSessionId();

    static void Main(string[] args)
    {
        Console.WriteLine(WTSGetActiveConsoleSessionId()); // 출력 결과: 1 (RDP 환경에서도!)
    }
}

물리적으로 콘솔에 로그인한 사용자가 있는 경우에 한해 세션 ID를 가져오므로, Windows Server 제품군처럼 RDP로 접속해 작업하는 식이라면 저 값은 -1을 반환할 수 있습니다. 직접 테스트해봐야 하는데... 아마 그럴 것입니다. ^^




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 9/15/2023]

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

비밀번호

댓글 작성자
 



2023-09-08 05시49분
게으른 지식에 깨우침 주셔서 감사합니다.
잘못된 내용을 당당하게 제가 포스팅을 해뒀군요 =ㅅ=;;
가르침 감사드립니다.
SeongHwan Lee
2023-09-11 11시17분
@SeongHwan Lee 님, 쓰신 글("https://blog.naver.com/vactorman/223206122756") 보고 RDP 환경에서 WTSGetActiveConsoleSessionId를 테스트했더니 저도 1이 나왔습니다. 작업 관리자를 보면, 사용자가 "콘솔"에서 로그인하지 않아도 세션 1의 프로세스가 5개(csrss.exe, dwm.exe, fontdrvhost.exe, LogonUI.exe, winlogon.exe)가 기본적으로 떠 있는데 그래서 1이 반환되는 듯합니다.

그리고, "Administrator"라고 단독으로 있는 것은 Built-in 계정입니다. 근래에는 보안 강화로 기본적으로 disabled 상태로 돼 있습니다. ("Computer Management" MMC에서 "System Tools" / "Local Users and Groups" / "Users"에서 보이는 "Administrator"가 그것입니다.

중간에 추가한 "...\Administrator"는 혹시 Active Directory 계정인가요? 아마도 Built-in 관리자 계정을 쓰지 않았을 것이므로 어찌되었든 다른 계정일 것입니다.

마지막으로, "특권(Privileges)"은 사실 보안상 기본적으로 필요한 만큼만 활성화되는 것이 보통입니다. 반면 NT 서비스를 "Local SYSTEM" 등의 계정으로 활성화시키면 해당 계정은 보다 많은 특권을 활성화시키므로 CreateProcessAsUser 실행에 문제가 없습니다.

-----------------------------

참고로, 계정의 "특권"이 어떤 것들이 활성화되었는지 런타임에 확인하려면 "로컬 보안 정책" MMC보다는, "Process Explorer"를 이용("https://www.sysnet.pe.kr/2/0/1806#privileges")하는 것이 더 편할 것입니다. ^^
정성태
2023-09-12 09시27분
넵. 맞슴다.
사내 장비들은 AD 로 관리 중이고 로컬 PC 로그온도 AD 계정으로 하고 있습니다. 로그온한 계정이 당연히 built-in admin 이었을 거라고 착각하는 바람에 문제 확인하는 데에 애를 좀 먹었네요.
privilege 확인하는 건 좋은 정보 하나 더 배웠습니다. 감사합니다.
SeongHwan Lee

... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...
NoWriterDateCnt.TitleFile(s)
1303정성태6/26/201227388개발 환경 구성: 152. sysnet DB를 SQL Azure 데이터베이스로 마이그레이션
1302정성태6/25/201229366개발 환경 구성: 151. Azure 웹 사이트에 사용자 도메인 네임 연결하는 방법
1301정성태6/20/201225760오류 유형: 156. KB2667402 윈도우 업데이트 실패 및 마이크로소프트 Answers 웹 사이트 대응
1300정성태6/20/201231747.NET Framework: 329. C# - Rabin-Miller 소수 생성방법을 이용하여 RSACryptoServiceProvider의 개인키를 직접 채워보자 [1]파일 다운로드2
1299정성태6/18/201232866제니퍼 .NET: 21. 제니퍼 닷넷 - Ninject DI 프레임워크의 성능 분석 [2]파일 다운로드2
1298정성태6/14/201234398VS.NET IDE: 72. Visual Studio에서 pfx 파일로 서명한 경우, 암호는 어디에 저장될까? [2]
1297정성태6/12/201231040VC++: 63. 다른 프로세스에 환경 변수 설정하는 방법파일 다운로드1
1296정성태6/5/201227667.NET Framework: 328. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 - 두 번째 이야기 [4]파일 다운로드1
1295정성태6/5/201225072.NET Framework: 327. RSAParameters와 System.Numerics.BigInteger 이야기파일 다운로드1
1294정성태5/27/201248515.NET Framework: 326. 유니코드와 한글 - 유니코드와 닷넷을 이용한 한글 처리 [7]파일 다운로드2
1293정성태5/24/201229770.NET Framework: 325. System.Drawing.Bitmap 데이터를 Parallel.For로 처리하는 방법 [2]파일 다운로드1
1292정성태5/24/201223747.NET Framework: 324. First-chance exception에 대해 조건에 따라 디버거가 멈추게 할 수는 없을까? [1]파일 다운로드1
1291정성태5/23/201230263VC++: 62. 배열 초기화를 위한 기계어 코드 확인 [2]
1290정성태5/18/201235074.NET Framework: 323. 관리자 권한이 필요한 작업을 COM+에 대행 [7]파일 다운로드1
1289정성태5/17/201239231.NET Framework: 322. regsvcs.exe로 어셈블리 등록 시 시스템 변경 사항 [5]파일 다운로드2
1288정성태5/17/201226456.NET Framework: 321. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (3) - Type Library파일 다운로드1
1287정성태5/17/201229285.NET Framework: 320. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (2) - .NET 4.0 + .NET 2.0 [2]
1286정성태5/17/201238205.NET Framework: 319. regasm.exe로 어셈블리 등록 시 시스템 변경 사항 (1) - .NET 2.0 + x86/x64/AnyCPU [5]
1285정성태5/16/201233257.NET Framework: 318. gacutil.exe로 어셈블리 등록 시 시스템 변경 사항파일 다운로드1
1284정성태5/15/201225687오류 유형: 155. Windows Phone 연결 상태에서 DRIVER POWER STATE FAILURE 블루 스크린 뜨는 현상
1283정성태5/12/201233300.NET Framework: 317. C# 관점에서의 Observer 패턴 구현 [1]파일 다운로드1
1282정성태5/12/201226095Phone: 6. Windows Phone 7 Silverlight에서 Google Map 사용하는 방법 [3]파일 다운로드1
1281정성태5/9/201233177.NET Framework: 316. WPF/Silverlight의 그래픽 단위와 Anti-aliasing 처리를 이해하자 [1]파일 다운로드1
1280정성태5/9/201226149오류 유형: 154. Could not load type 'System.ServiceModel.Activation.HttpModule' from assembly 'System.ServiceModel, ...'.
1279정성태5/9/201224912.NET Framework: 315. 해당 DLL이 Managed인지 / Unmanaged인지 확인하는 방법 [1]파일 다운로드1
1278정성태5/8/201226139오류 유형: 153. Visual Studio 디버깅 - Unable to break execution. This process is not currently executing the type of code that you selected to debug.
... 136  137  138  139  140  141  142  143  144  145  146  147  148  149  [150]  ...