예제 코드는 Visual Studio 2005로 작성 하고 Windows Vista RC2 Build 5744 에서 테스트 되었습니다.
빌드 하시려면 Windows Vista RC2 5744 용 SDK가 필요 합니다.
Windows XP가 나온지도 벌써 5년. 그 동안 말 많던 Windows Vista의 RTM출시도 이제 몇 일 안 남은 듯 합니다. 외부에 공개된 버전이 RC2(Build 5744)인데 5840이 돌아 다니는 걸 봐선 이제 몇 일 후면 RTM 이 나올 듯 하네요.
Windows Vista는 바뀐 외형 만큼이나 변경 되거나 향상 된 부분들이 상당히 많습니다. 기존의 Application 들이 제대로 호환 될 수 있도록 확인을 해 봐야 할텐데요.( 슬슬 우리 개발자들 바뻐지겠습니다.) 그 동안 공개된 Beta와 RC 버전 들을 받아 깔아 보신 분들이 많으리라 생각 됩니다. 깔아 보신 분들이 느끼신 변화 중 바뀐 Aero Glass 만큼이나 눈에 띄는 변화가 아마 UAC 가 아닐까 생각 합니다. IE 에서 ActiveX 컨트롤 설치 시 전체 화면이 어두워 지면서 실행 하겠느냐고 묻는 창이 뜨는 걸 보셨을 겁니다.(Elevation Prompt 라고 합니다.)
새로운 프로그램을 설치 하거나 시스템과 관련된 무언가를 수행 한다거나 Administrator 권한을 필요로 하는 프로그램을 실행 하려고 할 때는 언제나 뜹니다.(gpedit.msc 에서 UAC 기능을 끄실 수도 있습니다.)
UAC는 바로 User Account Control의 약자로 Microsoft에서 보안에 중점을 두고 추가한 기능으로 시스템에 중요한 자원(파일이나 레지스트리 같은)을 읽고 쓰거나 기타 사용자의 주의가 필요한 일을 실행 할때에는 꼭 Administrator 권한을 요구 하게 되는 기능입니다. 이에 대한 좀더 자세한 설명은 https://www.microsoft.com/korea/technet/resources/Technetcolumn/column_uac1.asp 의 글을 보시면 좀더 UAC에 대한 좀더 명확한 이해를 얻으 실 수 있으리라 생각 합니다. 전 이 글에서 Visual C++ 개발자의 입장에서 얘기를 해보려 합니다.
이 시점에서 머릿속에 떠오르는 질문이 있다면 다음이 아닐까 합니다.
1. 내가 Administrator 이거나 내 계정이 Administrator 구룹에 속하면 안뜨지 않느냐?
Windows 2000, XP 에서 존재 하던 Administrator 계정은 더 이상 사용할 수 없습니다. 그리고 Administrator 그룹에 속한 계정도 기본적으로는 Standard User Level 에서 실행 하게 됩니다. 즉, 꼭 필요 할 때만 Administrator 권한으로 실행 한다는 말입니다.
2. 그럼 항상 Administrator 권한으로 실행 하면 되지 않나요?
Explorer 에서 프로그램을 실행 할 때 마우스 오른쪽 클릭을 하면 Run as Administrator 항목이 있습니다. 이를 사용하시면 프로그램이 시작 될 때 사용자에게 Elevation Prompt를 보여주고 사용자의 허락하에 실행을 하게 됩니다. 이 외에 manifest 파일에 해당 프로그램이 요구 하는 권한 Level을 명시 하여 실행시 이를 물어 보도록 할 수도 있습니다. 물론 이 방법을 사용하셔도 무방 하겠으나 Microsoft가 UAC를 도입 한 이유에 반하는 일이 겠죠.
3. 그렇다면 필요 할 때만 Administrator 권한을 얻어 실행 하는 것은 어떻게 하나요?
IE에서 새 ActiveX 를 설치 하거나 Vista의 작업 관리자 등의 UI 에 방패 아이콘이 있는(Administrator 권한이 요구 되는 기능에 표시 됩니다.) 기능을 선택 하면 언제나 뜨는 Elevation Prompt를 띄워 사용자에게 Administrator 권한으로 실행 하는 것을 허용할지를 물어 사용자가 승인 하면 실행 하는 것을 볼 수 있습니다. 이와 똑같이 해주시면 되겠습니다. 네. 말로는 무지 간단합니다.
4. 그럼 실제로 그 기능은 어떻게 해야 사용할 수 있는건가요? Microsoft 에서 이를 위한 API를 따로 지원 해주겠죠?
아닙니다. 따로 지원하는 API는 없습니다. 어떤 함수 하나를 호출 하면 사용자에게 권한을 묻는 다일로그가 뜨고 이 시점 부터는 해당 프로세스가 Administrator 권한으로 실행 되거나 하는 API가 없다는 얘기 입니다. 이 대신 두가지 방법을 통해 지원합니다. 하나는 ShellExcute() API를 이용하여 별도의 프로세스를 Administrator 권한 하에 실행 하여 일을 수행 하는 방법과 COM 객체를 권한 Elevation Moniker를 이용해 생성 하여 실행 하는 법 입니다.
여기서 이해 하셔야 할 것이 한 프로세스가 이미 실행이 된 이 후에는 프로세스의 권한 레벨은 변경 될 수 없다는 것입니다. 즉, Standard User Level 로 실행 되는 프로세스는 실행 도중 Administrator 권한 레벨로 변경 되어 실행 될 수 없다는 것 입니다. 그래서 ShellExecute() 를 이용해 별도의 프로세스를 띄우는 것입니다. 그렇다면 Dll로 되어 있는 COM 객체는? COM 객체는 권한 Elevation Moniker를 이용해 별도의 Exe 서버로 실행 되어 이도 앞의 방법과 동일 한 별도의 프로세스에서 Administrator 권한으로 실행 되게 됩니다.
그럼 앞에서 설명한 내용을 예제 코드를 가지고 설명 해 보겠습니다.
이 예제는 보통의 Win32 다일로그 Application 입니다. 처음 시작 시엔 Standard User 로 실행 되기 떄문에 HKLM\SYSTEM 같은 레지스트리 키를 WRITE 권한으로 열 수 없습니다. 하단에 "권한 상승" 버튼을 누르면 Runtime 시 Administrator 권한을 획득하여 레지스트리 키를 열도록 합니다. 이 예제 에서는 앞서 설명한 두가지 방법 중에 COM 객체를 사용하는 방법을 이용하는 방법을 설명 합니다.
우선 이 예제에서 사용할 COM 객체는 ATL로 직접 만든 객체로 두가지의 메소드를 지원 합니다. 현재 권한 모드를 읽어 오는 메소드 GetElevationStatus와 시스템 레지스트리 키를 WRITE 권한으로 여는 메소드 TryOpenRegistry입니다. 이 두 메소드를 호출 하여 이에 대한 결과를 화면에 출력 하는 코드는 다음 에서 보실 수 있습니다.
void UpdateUI(HWND hDlg)
{
if(g_pUACTest){
ULONG lStatus = 0;
HRESULT hr = g_pUACTest->GetElevationStatus(&lStatus);
if(SUCCEEDED(hr)) {
HWND hWndText = ::GetDlgItem(hDlg, IDC_STATIC_ELEVATED_RESULT);
if(lStatus == 1) {
::SetWindowText(hWndText, L"권한이 상승 된 상태 입니다.");
}
else {
::SetWindowText(hWndText, L"상승 되지 않았습니다.");
}
}
......
}
}
단순히 UACTestObj COM 객체에 일을 요청 하고 반환되는 결과 값을 보고 이를 화면에 출력을 합니다.
위 코드의 g_pUACTest는 다음의 코드 처럼
HRESULT hr = ::CoCreateInstance(__uuidof(UACTestObj), NULL, CLSCTX_ALL, __uuidof(IUACTestObj), (void**)&g_pUACTest);
CoCreateInstance()로 생성된 UACTestObj COM 객체의 IUACTestObj 인터페이스 포인터 입니다. 이 예제를 실행 하면 우선 Standard User Level 에서 실행 되기 때문에 화면엔 "상승 되지 않음" 과 "HKML\SYSTEM 열기 실패" 라고 표시 됩니다. 위의 코드에서 보실 수 있듯이 이를 얻어 오는 방법은 IUACTestObj 인터페이스의 두 메소드를 호출에 얻어 옵니다. 그럼 권한 상승을 위해 사용자에게 Elevation Prompt 를 보여주고 사용자가 허락 후 Administrator 권한으로 앞서 설명한 일들이 성공 할 수 있도록 하는 코드를 보겠습니다.
우선 이 COM 객체를 실행 할 때 Windows 에게 "이 COM 객체는 Administrator 권한으로 Elevate 해 실행 해줘~" 라고 알려 줘야 합니다.
이를 위해 CoCreateInstance 대신 다음 처럼
HRESULT hr = ::CoCreateInstanceAsAdmin(hDlg, __uuidof(UACTestObj), __uuidof(IUACTestObj), (void**)&g_pUACTest);
CoCreateInstanceAsAdmin() 함수를 사용 합니다. 이 함수를 처음 봤을 때는 MS 에서 제공 하는 API라고 생각 하실 수 있으나 이는 실제 API는 아닌 MSDN 도움말에 제공 되는 함수 입니다. 이 함수를 그냥 내 프로젝트에 복사 해주시고 사용하시면 되죠. 그럼 이 CoCreateInstanceAsAdmin()가 무엇을 하느냐? 코드를 보시면 COM Elevation Moniker 라는 것을 사용해 COM 객체를 생성 하게 되어 있습니다. 이 Moniker를 사용하면 해당 COM 객체를 별도의 Surrogate Exe를 통해 실행 하여 별도의 프로세스 내에서 Adminsitrator 권한으로 실행 되게 됩니다. (이 Surrogate Exe 는 DCOM 에서 Dll COM Server를 사용가능 도록 Dll Server 를 대신 해 실행 해주는 EXE 이죠)
COM 객체를 생성 할 때
Elevation:Administrator!new:{CLSID}
라는 스트링으로 생성을 하게 되면 COM Elevation Moniker가 생성이 되고 이 Moniker는 뒤에 CLSID 의 GUID를 갖는 COM 객체를 생성해 줍니다. 이 함수가 실행 되면 사용자에게 실행 할지 묻는 Elevation Prompt를 보여주고 사용자가 허용을 하는 경우에만 Adminsitrator 권한으로 실행 합니다. 물론 이 COM 이 권한 Elevation에 이용 될 수 있도록 하기 위해선 COM 객체 등록시 추가로 해줘야 하는 것 들이 있습니다.
ATL을 이용한 COM 객체는 rgs 라는 파일에 해당 COM 객체를 등록 할 때 생성할 레지스트리 키들을 갖고 있습니다. 다음이 이번 예제의 RGS 파일 중 일부 입니다. 여기서 Elevation 다음으로 오는 5 줄을 추가 해주셔야 합니다. 여기서 Eleavation 다음에 오는 Enabled 는 이 COM 객체가 권한 상승 하여 실행 될 수 있다고 알려주는 키 입니다. 이 키를 추가한 이유는 이 키가 없다면 어떤 악성 코드가 특정 COM 객체를 생성해 권한 상승을 통해 자기가 원하는 일을 수행 할 수도 있기 때문에 그렇습니다. 그리고 LocalizedString 은 Elevation Prompt가 사용자에게 표시 될 때 어떤 Exe 나 DLL가 권한 상승을 시도 하는지 보여주기 위한 텍스트 정보 입니다. %MODULE% 는 자기 자신의 실행 모듈을 의미 하고 그 다음에 오는 숫자 101은 해당 모듈의 String Table의 101 항목을 말합니다. UACTestCOM 프로젝트의 리소스에서 String Table을 보시면 101 번의 아이디에 스트링을 보실 수 있습니다.
NoRemove CLSID
{
ForceRemove {02DD218F-86E2-4DF3-B961-1C9B04DE90CE} = s 'UACTestObj Class'
{
ProgID = s 'UACTestCOM.UACTestObj.1'
VersionIndependentProgID = s 'UACTestCOM.UACTestObj'
ForceRemove 'Programmable'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
val AppID = s '%APPID%'
'TypeLib' = s '{1CE63AAC-200E-4F65-B6B2-C9615EBDF295}'
Elevation
{
val Enabled = d 1
}
val LocalizedString = s '@%MODULE%,-101'
}
}
이 것과 남은 하나의 rgs 파일에 다음을 추가 해주셔야 합니다.
HKCR
{
NoRemove AppID
{
'%APPID%' = s 'UACTestCOM'
{
val DllSurrogate = s ''
}
'UACTestCOM.DLL'
{
val AppID = s '%APPID%'
}
}
}
이 는 해당 Dll COM 객체가 Surrogate에 의해 실행 가능함을 알려주는 키 입니다.
자 그럼 이제 예제 프로그램을 빌드 하고 실행 하면 초기엔 실해 하던 것들이 "권한 상승" 버튼을 눌러 승인 후에
실행 한 모습을 보면 성공 하는 것을 보실 수 있을 겁니다.
이와 관련된 정보들의 링크들 입니다.
http://windowssdk.msdn.microsoft.com/en-us/library/ms679687.aspx
http://blogs.msdn.com/vistacompatteam/archive/2006/09/28/CoCreateInstanceAsAdmin-or-CreateElevatedComObject-sample.aspx
http://www.microsoft.com/downloads/details.aspx?FamilyID=ba73b169-a648-49af-bc5e-a2eebb74c16b&DisplayLang=en
예제 프로젝트의 코드는 간단하니 이해 하시는데 별 어려움이 없을 거라 생각 됩니다.
비슷한 예제 설명이 channel9 에도 있으나 소스 공개가 안되있고 해당 설명이 테스트 된 Vista 버전이 달라 약간의 차이가
있어 제가 RC2에 맞게 재작성 했습니다. 또한 RTM 이 아닌 버전으로 테스트 했으므로 UAC의 세부 사항은 변경 될 수 있습니다
================================================================================================
데브피아 강좌 펌 - 신형철 (krispy)
'IT-개발,DB' 카테고리의 다른 글
[개발] DllMain에서 다음 작업들은 절대로 하지 말 것 (0) | 2011.10.05 |
---|---|
[개발/MFC] 따라해보는 키보드 후킹 (0) | 2011.09.30 |
[개발/VC] ActiveX 관리자 권한 UAC Elevation (0) | 2011.09.29 |
[개발/MFC] 윈도우즈 에러코드 GetLastError(), System Error Codes (0 - 6118) (0) | 2011.09.28 |
[개발/VC] 웹페이지 로드될 때까지 기다리기 (0) | 2011.09.27 |
댓글