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

MFC Active 생성





1. Workspace 생성

    MFC ActiveX control wizard를 선택.

2. Property 생성(메뉴 View > ClassWizard)

    1) ClassWizard의 Automation tab에서 선택
    2) Class name에서 Control class 선택(...Ctrl)
    3) (Add Property...) 버튼 클릭
    3) External name: ActiveX를 사용하는 프로그램에서 사용할 property name
    4) Type: property type
    5) Variable name: ActiveX 내부에서 사용할 property 변수명
    6) Notification function: property 값이 변경되면 실행되는 함수. 별로 사용하지 않는다.
    7) Implementation:
        - Member variable: 외부에서 ActiveX.propertyName = value 형태로 사용하는 방법
        - Get/Set methods: GetPropertyName(), SetPropertyName() 형태로 사용하는 방법

    ☞ 이렇게 하면,
        Conrol class에 해당 member 변수가 생성되는데, 변수명은
        Variable name에 설정된 이름으로 생성된다.

3. Method 생성

    1) ClassWizard의 Automation tab에서 선택
    2) Class name에서 Control class 선택(...Ctrl)
    3) (Add Method...) 버튼 클릭
    4) External name: ActiveX를 사용하는 프로그램에서 사용할 method 이름
    5) Internal name: ActiveX 내부에서 사용할 때 사용할 이름
    6) Return type: method를 호출했을 때, 결과로 넘겨줄 값의 type
    7) Parameter list: method에 넘겨줄 parameter와 그 type
    
    ☞ 이렇게 하면,
        Conrol class에 해당 method가 member함수로 생성되는데,
        함수 이름은 "Internal name"으로 생성된다.
        이 함수에 필요한 logic을 코딩하면 되며,
        ActiveX를 사용하는 프로그램에서 "External name"으로 설정된
        함수를 call하면, ActiveX에서는 "Internal name"으로 설정된
        함수가 실행된다.

4. Event 생성

    1) ClassWizard의 ActiveX Events tab 선택
    2) Class name: Control class 선택(...Ctrl)
    3) (Add Event...) 버튼 클릭
    4) External name: ActiveX를 사용하는 program에서 사용할 event name
    5) Internal name: ActiveX 내부에서 사용할 event name
    6) Parameter list: event도 하나의 함수다. 이 함수에 넘겨줄 parameter와
        그 type을 정의한다.

    ☞ 이렇게 하면,
        Conrol class의 Header file에 "Internal name"으로 설정된 void type의 함수가
        "AFX_EVENT(CSamAxCtrl)" event map으로 생성된다.
        ActiveX의 원하는 곳에서 이 함수를 call하면,
        ActiveX를 사용하는 프로그램에서 해당 event가 발생하게 된다.
    
5. 필요한 code 작성

    4번까지 하면, 기본적으로 ActiveX control이 작성된 것이다.
    여기에 필요한 logic을 작성한 다음 compile하면 ocx 파일이 생성된다.
    
6. CLSID 알아내기

    만들어진 ActiveX control을 웹에서 사용하기 위해서는 기본적으로 CLSID를 알아야 한다.
    이것은 VC++의 FileView에서 ".odl" 파일을 살펴보면 알 수 있다.
    ".odl" 파일의 맨 하단을 보면 다음과 같은 주석을 볼 수 있다.
    
         //  Class information for CExActiveX2Ctrl
    
    그리고, 그 밑에 아래와 같은 코드를 볼 수 있다.
    
        [ uuid(4AA8A54C-8F74-4F9F-8D3C-BD1079A73AD8),
          helpstring("ExActiveX2 Control"), control ]
    
    위 코드에서 "uuid"가 바로 CLSID 이다.

참고 : http://cafe.naver.com/swnara/145

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패밀리
[개발/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
39
40
41
42
43
44
45
HKCR
{
    GreenmaruX.HelloCtrl.1 = s 'Greenmaru Example ActiveX Control'
    {
        CLSID = s '{4C80B2EE-6D19-4F77-9946-DF7AD17EEE67}'
    }
    GreenmaruX.HelloCtrl = s 'Greenmaru Example ActiveX Control'
    {
        CLSID = s '{4C80B2EE-6D19-4F77-9946-DF7AD17EEE67}'
        CurVer = s 'GreenmaruX.HelloCtrl.1'