천객만래 [千客萬來] (It has an interminable succession of visitors)




윈도우즈 7에서 네이트온 원격제어를 이용하려할 때 상대방의 원격제어를 수락해도 아무런 반응이 없다.
즉, 안되는 것이다.

윈도우즈 7에서 왜 안되는 것일까?
그것은 UAC라는 사용자계정컨트롤 기능이 윈도우즈 7에서 작동하기 때문이다.
UAC는 사용자 컴퓨터에서 관리자 수준 권한이 필요한 변경 내용이 적용되기 전에 이를 사용자에게 알려준다.
기본 UAC 설정에서 프로그램이 컴퓨터 변경을 시도할 때 이를 사용자에게 알리지만 
UAC가 알리는 빈도를 변경할 수가 있는데 이것을 활용해야한다.


사용자 계정 컨트롤의 활성화를 통해서 이를 해결할 수 있다고 한다.


제어판으로 가서 컴퓨터의 상태 검토를 선택하고 관리센터에서 사용자 계정 컨트롤 설정 변경을 선택해보자.

컴퓨터 변경 내용에 대한 알림 조건을 선택하십시오라고 나온다.
알리지 않음으로 게이지에서 맨 아랬쪽을 선택한다.
확인 버튼을 클릭하고 윈도우즈 7 하단에 알림메시지가 나타나면 클릭해서 재부팅을 한다.

알리지 않음 으로 하면 완전 해결된다고 한다.

원격제어 사용후 UAC 상태를 다시 돌려놓는게 좋다.





Posted by SB패밀리

[개발/VC++] IE 보호 모드에서 비보호 모드 프로세스 실행




IE 보호 모드에서 비보호 모드 프로세스 실행


UAC 환경(Windows Vista, Windows 7)의 IE7, IE8 보호모드에서 동작하는 ActiveX에서 실행 파일을 실행할 경우(브로커 프로세스) 비보호 모드로 열린다는 보안 경고창이 나온다.
이 문제를 해결하려면 아래와 같이 자동 권한 상승 정책을 레지스트리에 등록해주어야 한다. {347DAD60-9849-4921-89B6-16FA1B7936CC}는 임의의 GUID값이다.

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\Low Rights\ElevationPolicy\{347DAD60-9849-4921-89B6-16FA1B7936CC}]
"AppName"="실행 파일명"
"AppPath"="실행 파일 위치(디렉토리)"
"Policy"=dword:00000003

또 다른 문제는 이 자동 권한 상승 정책을 IE 프로세스가 아닌 외부에서 추가하는 경우 ieuser.exe(IE7), iexplore.exe(IE8)에서 권한 상승 정책을 캐싱하고 있기 때문에 바로 적용이 안 된다는 것이다. ieuser.exe나 iexplore.exe 프로세스를 종료해야만 적용할 수 있다. 하지만 설치 과정에서 프로세스를 종료하는 것은 부담스러운 일이다.

이 문제를 해결하기 위해서 2007년 6월에 배포된 IE 보안 업데이트에 아래와 같은 새로운 API가 추가되었다.

HRESULT IERefreshElevationPolicy(VOID);

자동 권한 상승 정책을 레지스트리를 등록하고 ieframe.dll에 있는 IERefreshElevationPolicy 함수를 호출해주면 추가된 자동 권한 상승 정책이 바로 적용된다.
주의할 점은 IERefreshElevationPolicy를 호출할 때 반드시 IE 프로세스가 실행 중인 계정에서 호출해야 한다는 것이다. 별도의 관리자 계정이나 시스템 계정(Windows Installer의 경우 서비스에서 설치를 수행해 시스템 계정으로 설치가 되는 것으로 보인다)에서 호출할 경우 보안 경고창이 뜰 수 있다.

아래는 공식 블로그에 있는 예제 코드이다.

HRESULT RefreshPolicies()
{
    HRESULT hr = E_NOTIMPL;

    HMODULE hDll = LoadLibrary(L"ieframe.dll");
    if (NULL != hDll)
    {
        typedef HRESULT (*PFNIEREFRESHELEVATIONPOLICY)();
        PFNIEREFRESHELEVATIONPOLICY pfnIERefreshElePol = (PFNIEREFRESHELEVATIONPOLICY) GetProcAddress(hDll, "IERefreshElevationPolicy");
        if (pfnIERefreshElePol)
        {
            hr = pfnIERefreshElePol();
        } else {
             DWORD error = GetLastError(); 
             hr = HRESULT_FROM_WIN32(error);
         }
        FreeLibrary(hDll);
    } else {
       DWORD error = GetLastError(); 
       hr = HRESULT_FROM_WIN32(error);
    }
    return hr;
}

출처 : 인터넷



Posted by SB패밀리

[개발/보안] Windows에서 프로그램을 실행할 때 "다음 계정으로 실행" 해결 방법


Microsoft Windows 2000 및 Microsoft Windows XP에서 현재 로그온한 사용자 이외의 사용자로 프로그램을 실행할 수 있습니다. 이렇게 하려면 Windows 2000에서는 RunAs 서비스를 실행하고 있어야 하고 Windows XP에서는 Secondary Logon 서비스를 실행하고 있어야 합니다. RunAs 서비스와 Secondary Logon 서비스는 이름이 다른 동일 서비스입니다. 이 문서에서는 Windows 2000 기반 컴퓨터나 Windows XP 기반 컴퓨터에서 다음 계정으로 실행 명령을 설정하고 사용하는 방법을 단계별로 설명합니다.

  1. 컴퓨터에 관리자로 로그온하거나 관리 권한이 있는 사용자로 로그온합니다.
  2. 내 컴퓨터 아이콘을 마우스 오른쪽 단추로 누른 다음 관리를 누릅니다.
  3. 컴퓨터 관리에서 서비스 및 응용 프로그램 노드를 확장한 다음 서비스를 누릅니다.
  4. 사용하는 운영 체제에 따라 다음 단계 중 하나를 수행합니다.
    • Windows 2000: 세부 정보 창에서 RunAs 서비스를 마우스 오른쪽 단추로 누른 다음 등록 정보를 누릅니다.
    • Windows XP: 세부 정보 창에서 Secondary Logon 서비스를 마우스 오른쪽 단추로 누른 다음 속성을 누릅니다.
  5. 등록 정보(Windows 2000) 또는 속성(Windows XP) 대화 상자에서 시작 유형을 자동으로 설정한 다음 시작을 누릅니다.
  6. 서비스가 시작되면 확인을 눌러 속성 대화 상자를 닫은 다음 컴퓨터 관리를 닫습니다.

다음 계정으로 실행 명령을 실행하는 데 필요한 서비스가 컴퓨터에서 실행됩니다.

 

윈도우즈 XP 에서 프로그램을 실행하려고 할때나 인터넷 익스플로러를 실행했을 때
"다음 계정으로 실행"이라는 창이 뜨는 경우가 있다.
인터넷 익스플로러의 경우 정확히 왜 이 창이 뜨는지 알 수가 없다. 세세하게 추적하는 수밖에...







이런 경우 개발자는 어떻게 해결해야할까? 내가 만든 프로그램이 실행을 하니 이런 창이 떡하니 화면에 나타난다면
난감하다.

ShellExecute() 함수를 사용해서 외부 프로그램을 실행할 때 이런 경우가 생긴다.
이런 경우 XP와 Vista 이상의 경우와 구분해서 ShellExecute()를 실행해줘야 한다.
Vista 이상에서는 관리자 권한으로 해서 실행해주고 XP에서는 그냥 일반적으로 실행해주면 된다.

Posted by SB패밀리

Vista, Windows 7 UAC (User Access Control) 를 델파이에서 설정하기


델파이나 그 외 개발툴로 작성되는 윈도우즈 어플리케이션에는

UAC라는 사용자 권한 제어를 적용해야한다.






델파이에서 실행에 요구되는 Level


일반 유저나 adminstrator 권한으로 실행한다면 실행 level을 설정해야 한다.

XPMan Unit에 대한 모든 참조를 제거해야한다.

어플리케이션에 manifest로 리소스를 추가한다.


program TestAsInvoker;


{$R 'ExecutionLevelAsInVokerManifest.res' 'ExecutionLevelAsInvokeManifest.rc'}


uses

Forms,

MainForm in 'MainForm.pas' {Form1};


{$R *.res}


begin 

Application.Initialize;

Application.CreateForm(TForm1, Form1);

Application.Run;

End;


델파이에서 실행에 요구되는 Level를 지원하기 위해서 우리는 manifest 리소스 파일을

직접 만들어서 포함시킬 것이다.


rc 파일은 컴파일되어 res 파일로 생성된다. Manifests는 리소스 타입 24 이며 파일명을 포함시킨다.


Manifest 파일


<?xml version="1.0" encoding="UTF-8" standalone="yes"?>


<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">

    <dependency>

<dependencyAssembly>

<assemblyIdentity

type="win32"

name="Microsoft.Windows.Common-Controls"

version="6.0.0.0"

publickKeyToken="6595b64144ccf1df"

language="*"

processorArchitecture="X86"

/>

    </dependency>

</dependencyAssembly>

    

    <description>elevate execution level</description>


    <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">

        <security>

            <requestedPrivileges>

                <requestedExecutionLevel level="requireAdministrator" />

            </requestedPrivileges>

        </security>

    </trustInfo>

</assembly>



trust정보는 아래의 값으로 할 수 있다.

- level = "asInvoker"

: 프로세스 생성과 동일한 token으로 실행하려는 프로세스를 시작한다.

- level = "highestAvailable"

: 사용자가 관리 권한을 갖는다면 권한상승을 허락하기 위해 administrators 를 요청하지만 일반유저 권한으로 시작한다.

- level = "requireAdministrator"

: administrators 권한 상승을 요청한다. 일반 유저는 login dialog 를 받게 된다. 결국, 관리자 권한으로 시작하게 된다.


Windows XP 경고


Manifest 포맷이 잘못되면 Windows XP 에서는 blue screen을 유발한다. MS 사이트에서 KB921337 을 봐라.



SHGetFolderPath


일반유저에게는 여러 시스템 폴더 경로가 읽기 모드가 된다. 

확실한 경로를 사용하는 것이 중요하다.

SHGetFolderPath 함수를 사용해서 아래의 위치들을 구할 수 있다.


CSIDL_PERSONAL { My Documents }

CSIDL_APPDATA { Application Data, new for NT4 }

CSIDL_LOCAL_APPDATA { non roaming, user\Local Settings\Application Data }

CSIDL_COMMON_APPDATA { All Users\Application Data }

CSIDL_MYPICTURES { My Pictures, new for Win2K }

CSIDL_COMMON_DOCUMENTS { All Users\Documents }

...



implementation


{$R *.dfm}


uses 

SHFolder;


Function GetFolder(csidl: Integer; ForceFolder: Boolean = False): string;

var

i: Integer;

begin

SetLength(Result, MAX_PATH);

if ForceFolder then

SHGetFolderPath(0, csidl or CSIDL_FLAG_CREATE, 0, 0 PChar(Result))

else

SHGetFolderPath(0, csidl, 0, 0, PChar(Result));

i := pos(#0, Result);

if  i > 0 Then

SetLength(Result, Pred(i));

End;


function GetLocalAppDataFolder(ForceFodler: Boolean = False) : String;

begin

Result := GetFolder(CSIDL_LOCAL_APPDATA, ForceFolder);

end;




RunAsAdmin


ShelllExecute 를 사용해서 administrator 권한으로 프로세스를 실행할 수 있다.

Application이 유저가 어떤 작업을 하려할 때 수락 다이얼로그를 노출해서 바탕화면 보안을 막는 것을 최소화한다면  Application.Handle은 권한 상승을 지연한다.

Application.Handle을 사용하자. (or MainForm.Handle)


핸들을 사용하지 않는다면 직접 전면 권한상승을 줄것이다.


// Vista Utilties

procdure RunAsAdmin (hWnd: HWND; aFile: String; aParameters: String);

Var

sei: TShellExecuteInfoA;

begin

FillChar(sei, SizeOf(sei), 0);

sei.cbSize := sizeof (sei);

sei.wnd := hWnd;

sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;

sei.lpVerb := "runas";

sei.lpFile := PChar(aFile);

sei.lpParameters := PChar(aParameters);

sei.nShow := SW_SHOWNORMAL;

if not ShellExecuteEx(@sei) then

RaiseLastOSError;

end;



Using COM class for Admin tasks


admin 작업을 수행하기 위해 다른 어플리케이션을 실행하는 대신에 일반 유저로 실행하는 프로세스로부터

권한상승된 실행하는 COM 어브젝트를 호출하는 것이 가능하다.


COM 서버는 EXE 이어야 하며 EXE COM 어브젝트를 설치하기 위하여 requireAdministrator 를 필요로 한다.

등록을 위해서 아래 두가지를 해야한다.


1. LocalizedString값을 추가한다.

2. Elevation키의 값으로 Enabled=1을 추가한다.



Creating the elevated COM object


admin token의 프로세스에서 권한상승된 CoClass를 생성하기 위해 사용자 프로세스로부터 Moniker를 사용하자.


function NewCoGetObject(pazName: PWideChar; pBindOptions: PBindOpts3; const iid: TIID; out ppv): HRESULT; stdcall; external 'ole32.dll' name 'CoGetObject';


function CoCreateInstanceAdmin(WndHandle: HWND; clsid: TCLSID; iid: TIID; out ppv): HRESULT;

var

Bo: TBindOpts3;

Moniker: PWideChar;

Begin

Moniker := PWIdeChar(WideString('Elevation:Administrator!new:' + GuidToString(clsid)));

FillChar(Bo, SizeOf(Bo), #0);

Bo.hWnd := WndHandle;

Bo.cbStruct := SizeOf(Bo);

Bo.dwClassContext := CLSCTX_LOCAL_SERVER;

Result := NewCoGetObject(Moniker, @Bo, iid, ppv);

End;


Adding the Shield to your buttons- SetElevationRequiredState


admin 권한으로 클릭하는 표시로 버튼, 링크 메뉴 아이템의 Shield icon을 얻는 단순한 방법.

쉽게 수락 다이얼로그를 나타낸다.



const

BCM_FIRST = $1600; // Button control message

BCM_SETSHIELD = BCM_FIRST + $000C;


procedure SetElevationRequireState(aControl: TWinControl; Required: Boolean);

var

lRequired: Integer;

begin

lRequired := Integer(Required);

SendMessage(aControl.Handle, BCM_SETSHIELD, 0, lRequired);

end;



Posted by SB패밀리
[작업] Internet Explorer 주소창 검색 기술 개발

얼마전 인터넷 익스플로러어 주소검색창에서 키워드를 입력하면 검색엔진을 이용하여 사용자에게 유익한 검색결과를 보여주는 프로그램을 제작했다.

여기서, 단지 주소창 검색은 쉬워보였었다. 연초에 만들어 보았더니 쉽게 되어서 별거 아니구나 했었는데 이번에 개발하는데 무지어려웠다.

이미 공개된 소스들로는 로직만 사용될 정도였다. 제대로 구현되지 않아서는 세상에 공개할 수 없는 작품이었다.

윈도우즈 7이 나오면서 무지 어려워진것이다.
그리고 IE 버전별로 이렇게 동작과 구성이 다를줄 몰랐다. 결국 win xp와 win 7기반에서 IE버전별로 모조리 분석을 했다.

여러차례의 시행착오를 거쳤다. 그리고 결국 만들어 냈다. 그런데, win 7 ultimate 버전이 말썽이었다. BHO로 동작하면서 win 7의 UAC에 막혀서 고전을 했다.

하지만 이것도 결국 이겨냈다.
그래서 IE 6~9 모든 버전과 win xp, win 7 모두에서 주소창 검색이 동작하는 주소창 검색 솔루션을 만들었다.

추석연휴까지 반납했었는데...
된다고 믿었고 나 하나가 아닌 함께하는 사람들을 떠올리면서 이루어낸 것이라 생각했다.

추석에 못 찾아뵌 가족들은 이후 찾아뵈었다.
이 성과물이 나와 함께하는 분들에게 좋은 결과를 가져다 줄 것이라 믿는다.

많이 도와준 분들도 약간 도와준 분들도 나에게 모두 고마운 분들이다. 고마운 마음으로 계속해 나갈 것이다.
Posted by SB패밀리

예제 코드는 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) 

Posted by SB패밀리
TAG UAC, Vista, 개발
[개발/VC] ActiveX 관리자 권한 UAC Elevation

관리자 권한: UAC(User Access Control)

아시다시피, Windows Vista부터 UAC(User Access Control)라는 개념이 도입 되었다.

UAC는 한마디로, "시스템에 중대한 영향을 끼치는 작업을 하려면 적절한 권한을 가지고 있을 것"이라고 할 수있다. 보통때는 "일반 사용자 권한"으로 사용하다가, 새로운 어플리케이션의 설치나 레지스트리 편집처럼 시스템에 중대한 변경을 가하는 작업을 하기 위해서는 "관리자 권한"이 있어야 한다.

여기서 한단계 더 나아가, 인터넷 익스플로러는 "보호모드(Low IL)"에서 동작하게 되었고, 이 위에서 동작하는 ActiveX도 덩달아 많은 제약을 받게 되었다. 이러한 변화는 최근 대부분의 바이러스가 ActiveX형태로 전파되는 점을 감안하면 반드시 필요한 조치이긴 하다.

아무튼, 이제 사용자 PC에 있는 파일을 읽고 쓰거나 하는 저수준 작업은 원칙적으로 ActiveX에서 허용되지 않는다. 이러한 작업을 하고 싶다면, ActiveX가 관리자 권한을 획득해야 한다.

 

프로그램 흐름

최종 결과물 Javascript를 통해 전체 프로그램의 흐름을 파악해 보자.

여러가지 방법이 있지만, 나는 Javascript에서 프로그램의 전체 흐름을 제어하고, ActiveX는 필요한 기능만을 제공하는 방법을 선호한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function runInUAC() {
 
    var helloCtrl = null;
 
    try {
 
        //
        // 1. ActiveX 컨트롤의 인스턴스를 생성.
        //
        helloCtrl = new ActiveXObject("GreenmaruX.HelloCtrl");
        if (helloCtrl == null) {
            // HelloCtrl이 시스템에 설치되지 않은 경우의 처리.
            alert("GreenmaruX HelloCtrl was not installed.");
        }
 
        //
        // 2. UAC가 필요한 OS인지를 판단.
        //
        if (helloCtrl.IsNeedElevation) {
 
            //
            // 3. 관리자 권한이 적용된 개체를 구함.
            //
            helloCtrl = helloCtrl.GetElevationObject();
        }
 
        //
        // 4. 관리자 권한이 필요한 저수준 기능을 사용.
        //
        helloCtrl.SomeFunction();
    }
    catch (ex) {
        var em = "ERROR:" + ex.message + "(" + ex.number + ")";
        alert(em);
    }
}

대충 감을 잡으셨는가? 그러면 하나씩 구현해 보도록 하자. 다만, 관리자 권한을 획득하는 과정까지가 중요하기 때문에 SomeFunction은 실제로 구현하지 않겠다.

 

UAC 필요여부 판단하기

아직도 Windows XP는 많이 사용되고 있기 때문에, 무조건 UAC를 진행할 필요는 없다. Windows OS를 판별해서 UAC가 필요한 운영체제인지를 판단하도록 하자. OS버전을 알아내기 위해서 COSVersion이라는 클래스를 사용했다. 이 클래스에 대한 설명은 다음 포스트를 참조하시기 바란다.

2010/12/06 - [소프트웨어 개발] - Windows Version 알아내기

1
2
3
4
5
6
7
HRESULT FinalConstruct()
{
    // Vista이상의 운영체제는 UAC elevation이 필요합니다.
    Greenmaru::COSVersion osv;
    _isNeedElevation = osv.IsWindowsVistaOrLater();
    return S_OK;
}

_isNeedElevation이라는 BOOL형 멤버변수를 추가하고, FinalConstruct에서 UAC의 필요 여부에 따라 값을 설정하도록 했다. 참고로, FinalConstruct는 이름처럼 개체 생성이 완료되는 시점에서 호출되는 함수다.

그리고 이 값을 개체 외부로 알리기 위한 IsNeedElevation COM Property를 추가해 준다. 이 값은 개체 외부에서 설정할 필요는 없으므로(해서도 안되므로) Get함수만 구현하도록 하겠다.

1
2
3
4
5
STDMETHODIMP CHelloCtrl::get_IsNeedElevation(VARIANT_BOOL* pVal)   
{   
    *pVal = m_isNeedElevation;   
    return S_OK;   

 

UAC Elevation

UAC 권한상승(Elevation)의 원리는 사실 간단하다. ActiveX객체가 자신의 새로운 인스턴스를 권한상승을 거쳐 만들도록하면 된다.

GetElevationObject라는 COM Method를 IHelloCtrl에 추가하자. 반환형은 VARIANT*형으로 지정한다. 이 함수는 권한 상승된 HelloCtrl의 새로운 인스턴스를 반환해 준다.

HelloCtrl.cpp에서 strsafe.h를 Include해 준 다음, 함수를 다음과 같이 구현하록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
STDMETHODIMP CHelloCtrl::GetElevationObject(VARIANT* lpvObject)
{
    HRESULT hr;
    BIND_OPTS3 bo;
    WCHAR wszCLSID[64];
    WCHAR wszMonikerName[512];
    IHelloCtrl * objElevator = NULL;
 
    if ( ! m_isNeedElevation )
    {
        lpvObject->vt = VT_DISPATCH;
        lpvObject->pdispVal = this;
        return S_OK;
    }
 
    // 권한 상승을 시도합니다.
    StringFromGUID2( CLSID_HelloCtrl, wszCLSID, sizeof(wszCLSID)/sizeof(wszCLSID[0]) );
    hr = StringCchPrintfW( wszMonikerName,
            sizeof(wszMonikerName)/sizeof(wszMonikerName[0]),
            L"Elevation:Administrator!new:%s", wszCLSID );
    if ( FAILED(hr) )
    {
        lpvObject->vt = VT_NULL;
        return S_FALSE;
    }
 
    memset( &bo, NULL, sizeof(bo) );
    bo.cbStruct = sizeof(bo);
    bo.hwnd = NULL;
    bo.dwClassContext = CLSCTX_LOCAL_SERVER;
 
    hr =  CoGetObject( wszMonikerName, &bo, IID_IHelloCtrl, (void**)&objElevator );
    if ( FAILED(hr) )
    {
        lpvObject->vt = VT_NULL;
        return S_FALSE;
    }
 
    lpvObject->vt = VT_DISPATCH;
    lpvObject->pdispVal = objElevator;
    lpvObject->pdispVal->AddRef();
 
    return S_OK;
}

천천히 살펴보면 눈치챌 수 있겠지만, 핵심은 CoGetObject를 통해 COM개체 자신의 새로운 인스턴스를 만들어 내는 것이다. 다만 일반적인 CoCreateObject등의 방식으로는 Elevation처리를 거칠 수 없기 때문에 조금 더 복잡해 졌다.

 

그 다음, 문자열 리소스를 하나 추가한다. 나는 IDS_ELEVATION이라고 이름지었는데, 이 문자열은 Elevation과정에서 사용자에게 컨트롤의 이름으로 표시된다.

그리고, DLL이름.rgs파일(Greenmaru.rgs)을 다음과 같이 만든다.

아시다시피, rgs파일의 내용은 COM개체를 시스템에 등록할 때(regsvr32.exe) 레지스트리에 기록된다. 다른 컨트롤을 만들때는 대충 어디를 어떻게 바꿔야 할지를 짐작할 수 있을 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HKCR
{
    NoRemove AppID
    {
        '%APPID%' = s 'GreenmaruX'
        {
            val DllSurrogate = s ''
        }
 
        'GreenmaruX.DLL'
        {
            val AppID = s '%APPID%'
        }
    }
}

 

다음으로, 컨트롤이름.rgs파일(HelloCtrl.rgs)의 내용은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38