본문으로 바로가기
비스타의 세션 0 고립화와 응용 프로그램 호환성

최근 한 고객이 자기들이 만든 윈도우 서비스를 비스타에 적용하는데 문제를 겪었다. 서비스는 시작과 함께 시스템 트레이에 아이콘을 보여주고, 이 아이콘은 사용자와 상호작용하는 데 사용된다. 서비스가 윈도우즈 XP, 윈도우즈 2003에서는 잘 동작했지만, 비스타에서는 잘 동작하지 않았다.

아래 포스팅이 비스타에서 변화된 점, 왜 바뀌어야 했는지, 이 변화가 응용 프로그램에 어떤 영향을 미칠지를 설명한다. 마지막 섹션에서는 개발자들이 서비스를 사용자와 대화할 수 있도록 만들고 싶을 때, 응용 프로그램을 비스타에서 호환되게 하기 위해 어떻게 해야 하는지를 설명하겠다.

Backgrounder

윈도우즈 XP, 윈도우즈 서버 2003과 그 이전 버전의 윈도우즈 운영체제에선, 모든 서비스는 콘롤에 처음 로그인한 사용자의 세션에서 실행됐다. 이 세션이 세션 0이다. 서비스와 사용자 응용 프로그램을 세션 0에서 한꺼번에 돌리는 것은 보안상의 위협을 안고 있다. 왜냐하면, 서비스는 높은 권한(예를 들어 Local System)으로 실행될 수 있으며, 자기의 권한을 높일 방법을 찾는 악의적인 프로그램의 표적이 되기 때문이다.

마이크로소프트 윈도우즈 비스타 운영체제는 서비스를 세션 0에 고립시키고, 세션 0을 비대화형(non-interactive)으로 만들어서 이 보안 위협을 줄였다. 윈도우즈 비스타에서는 시스템 프로세스와 서비스만이 세션 0에서 실행될 수 있다. 로그온한 첫번째 사용자는 세션 1에, 이후에 로그인한 사용자는 1 이후의 번호의 세션에 로그인 하게 된다. 이로서 서비스는 절대 사용자 응용프로그램과 같은 세션에서 돌 수 없게 되었으며, 따라서 응용 프로그램 코드에서부터의 공격에서 보호받게 되었다.

세션 0 고립화와 다양한 응용 프로그램 호환성 이슈에 대해 더 자세한 설명을 하기 전에, 어떻게 응용 프로그램에 어떤 세션에서 돌고 있는지를 알아내는 방법을 잠시 설명하자. (이 방법은 비스타 이전의 모든 윈도우즈에도 적용된다.)

1. 작업 관리자를 연다. (윈도우키+r 을 누르고 "taskmgr"을 친다.)
2. 프로세스 탭을 선택한다.
3. 보기 메뉴 -> 열 선택
4. 대화상자에서 "세션 ID" 체크박스를 선택하고 확인을 누른다.
5. 여러 응용 프로그램의 세션 ID를 주목하자. XP 등 에서는 서비스와 응용 프로그램이 같은 세션에서 돌고 있음에 주목하자.

윈도우즈 비스타에서는 사용자 응용 프로그램(비주얼 스튜디오, 익스플로러)은 세션 0에서 돌지 않는다. 반면 모든 서비스는 세션 0에서 돈다. (모든 서비스를 보여주지 않는다면, 대화상자 아래부분의 "모든 사용자의 프로세스 보기"를 체크한다.)

세션 0 고립화와 UI

세션 0 고립화는 사용자에게 UI를 보여줘야 하는 서비스에 문제를 야기했다. 서비스가 데스크탑과 다른 세션에서 돌기 때문에, UI가 사용자에게 보이지 않고, 따라서 응용 프로그램은 멈춰버린 것처럼 보인다. 윈도우즈 비스타는 이 문제를 대비해 한시적인 대응책을 갖고 있다. 다음 섹션에선 윈도우즈 비스타가 옛날 버전의 서비스의 사용자와의 상호작용에 대응하기 위해 세션 0으로 변경하라고 알려주는 대화창을 설명할 것이다.

말했지만, 이 대응책은 한시적일 뿐이다. UI를 보여주기 위해 권장되는 방법은 CreateProcessAsUser 함수를 이용하여 사용자 세션에서 프로세스를 실행하는 것이다.

세션 0에서 실행되는 서비스가 사용자와 상호작용하려는 것을 시스템이 감지하면, 시스템은 아래와 같은 대화상자를 띄운다. 개발자는 세션 0의 데스크탑으로 변경하여 대화상자와 상호작용할 수 있다. 아니라면, 서비스는 사용자 입력을 기다릴 것이면, 멈춰버린 것처럼 보일 것이다.

사용자 세션에서 UI 띄우기

위에서 말했듯이, UI를 띄울 때에는 CreateProcessAsUser 함수로 사용자 세션에서 프로세스를 생성하길 권장한다. MSDN을 찾아보면, CreateProcessAsUser는 다음과 같은 신택스를 갖는다.

BOOL CreateProcessAsUser( HANDLE hToken, LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );

첫번째 인자는 토큰의 HANDLE이며, 사용자를 알려주는 프라이머리 토큰이다. 여기서 나올 질문은 "사용자의 토큰을 어떻게 알아내지?"이다. LogonUser라는 다른 함수로 사용자의 토큰을 가져올 수 있다. 그러나, LogonUser 는 암호화되지 않은 사용자의 아이디와 암호를 인자로 넘겨야 쓸 수 있다. 이건 확실히 좋은 방법은 아니다.

프라이머리 토큰을 얻을 때 다음 방법을 쓰길 제안한다.

  1. PROCESS_ALL_ACCESS로 OpenProcess를 호출하고, 사용자 프로세스 하나를 연다. 호출이 성공하면, 그 프로세스의 핸들을 반환한다.
  2. 위에서 얻은 핸들로, desiredAccess를 TOKEN_ASSIGN_PRIMARY와 TOKEN_DUPLICATE로 하여 OpenProcessToken을 호출한다. 호출이 성공하면, 프로세스 토큰의 핸들을 반환한다.
  3. 2에서 얻은 토큰 핸들을 사용하여, accessRights를 TOKEN_ASSIGN_PRIMARY와 TOKEN_ALL_ACCESS로 하여 DuplicateTokenEx를 호출한다. 토큰 타입은 TokenPrimary로 설정한다. 호출이 성공하면, 토큰의 핸들이 얻어지고, 이 토큰은 프라이머리 토큰으로 사용할 수 있다.
  4. CreateProcessAsUser 함수로 사용자 세션에서 응용 프로그램을 생성한다.

각 동작에 대해 설명을 조금 더 덧붙여 본다.

  1. 어떤 사용자가 시작한 모든 프로세스는 그 사용자의 토큰을 가지고 있다. 이는 특정 프로세스에서 프로세스를 실행한 사용자를 찾을 수 있기 위함이다.
  2. OpenProcessToken 은 프라이머리 토큰을 요구하는 함수에선 쓰일 수 없는 임퍼스네이티드 토큰(impersonated token, 자격변경 토큰)을 반환한다.
  3. 위에서 설명한 2단계에서, 임퍼스네이티드 토큰을 프라이머리 토큰으로 바꿔야 한다. 이를 위해 DuplicateTokenEx 함수를 사용한다.
  4. 임퍼스네이티드 토큰을 프라이머리 토큰으로 일단 변경한 후에는 이 토큰을 프라이머리 토큰을 필요로하는 함수를 사용할 수 있다.
  5. 토큰을 사용한 후에는, CloseHandle로 토큰을 닫아줘야 한다.

비스타에서의 세션 0과 UI 통신에 대한 가장 어려운 문제에 대해 답이 됐으리라 본다. ...

좋은정보가 되셨다면 아래 한번 클릭해주세요^^



댓글을 달아 주세요