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




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

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


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


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

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

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

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





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'
    }
 
    NoRemove CLSID
    {
        ForceRemove {4C80B2EE-6D19-4F77-9946-DF7AD17EEE67}
            = s 'Greenmaru Example ActiveX Control'