Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)
(시리즈 글이 5개 있습니다.)
.NET Framework: 1066. Wslhub.Sdk 사용으로 알아보는 CoInitializeSecurity 사용 제약
; https://www.sysnet.pe.kr/2/0/12665

.NET Framework: 1067. 별도 DLL에 포함된 타입을 STAThread Main 메서드에서 사용하는 경우 CoInitializeSecurity 자동 호출
; https://www.sysnet.pe.kr/2/0/12666

.NET Framework: 1068. COM+ 서버 응용 프로그램을 이용해 CoInitializeSecurity 제약 해결
; https://www.sysnet.pe.kr/2/0/12667

.NET Framework: 1071. DLL Surrogate를 이용한 Out-of-process COM 개체에서의 CoInitializeSecurity 문제
; https://www.sysnet.pe.kr/2/0/12670

COM 개체 관련: 23. CoInitializeSecurity의 전역 설정을 재정의하는 CoSetProxyBlanket 함수 사용법
; https://www.sysnet.pe.kr/2/0/12679




CoInitializeSecurity의 전역 설정을 재정의하는 CoSetProxyBlanket 함수 사용법

지난 글에서,

역공학을 통한 lxssmanager.dll의 ILxssSession 사용법 분석
; https://www.sysnet.pe.kr/2/0/12677

드디어 COM 개체를 직접 사용해 WSL을 제어하는 방법을 (QueryState 함수에 한해) 살펴봤는데요. 혹시나, 나중에 Type Library 및 문서가 공개돼 모든 인터페이스의 함수들을 사용할 수 있게 된다면 CoInitializeSecurity 문제를 가진 Win32 API 래퍼 함수에 더 이상 얽매이지 않을 수 있습니다.

왜냐하면, 프로세스 수준에서 단 한 번만 호출 가능한 CoInitializeSecurity의 보안 설정을 인터페이스 단위로 재정의할 수 있는 CoSetProxyBlanket 함수를 사용하면 되기 때문입니다.

CoSetProxyBlanket function (combaseapi.h)
; https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cosetproxyblanket

IClientSecurity::SetBlanket method (objidl.h)
; https://learn.microsoft.com/en-us/windows/win32/api/objidl/nf-objidl-iclientsecurity-setblanket

이에 관해서는 다음의 문서가 설명하고 있는데요,

Client-Side Requirements for Impersonation
; https://learn.microsoft.com/en-us/windows/win32/cossdk/client-side-requirements-for-impersonation

The client can indicate the impersonation level programmatically, using either CoSetProxyBlanket—equivalent to IClientSecurity::SetBlanket, which can be called as often as necessary—or CoInitializeSecurity, which can be called once per process.


한번 테스트를 해볼까요? ^^

우선, 다음과 같이 CoInitializeSecurity를 호출해 두면 QueryState 함수가 E_ACCESSDENIED를 반환하며 실패하게 될 것입니다.

#include <Windows.h>
#include <stdio.h>
#include "lxssmanager.h"

HRESULT OleMain();
BOOL WslIsDistributionRegistered(ILxssSession* pSession, const wchar_t* distroName);

int main()
{
    HRESULT hr = CoInitializeEx(nullptr, 0);
    if (hr != S_OK)
    {
        return 1;
    }

    hr = OleMain();

    CoUninitialize();

    return (hr == S_OK) ? 0 : hr;
}

HRESULT OleMain()
{
    HRESULT hr = S_FALSE;

    hr = CoInitializeSecurity(nullptr,
        -1,
        nullptr,
        nullptr,
        RPC_C_AUTHN_LEVEL_DEFAULT,
        RPC_C_IMP_LEVEL_IDENTIFY,
        nullptr,
        EOAC_STATIC_CLOAKING,
        nullptr);

    if (hr != S_OK)
    {
        return hr;
    }

    ILxssSession* pSession = nullptr;

    do
    {
        hr = CoCreateInstance(CLSID_LxssManager, nullptr, CLSCTX_LOCAL_SERVER, 
            __uuidof(ILxssSession), (PVOID*)&pSession);
        if (hr != S_OK)
        {
            break;
        }

        printf("IsRegistered: %d\n", WslIsDistributionRegistered(pSession, L"Ubuntu20.04"));

    } while (false);

    if (pSession != nullptr)
    {
        pSession->Release();
    }

    return hr;
}

BOOL WslIsDistributionRegistered(ILxssSession* pSession, const wchar_t* distroName)
{
    IID iid;
    HRESULT hr = pSession->QueryState(distroName, 0, &iid);

    // hr == E_ACCESSDENIED 반환

    if (hr != S_OK)
    {
        return FALSE;
    }

    return TRUE;
}

자, 그럼 전역 보안 설정을 override하는 CoSetProxyBlanket을 위의 예제 코드에서 CoCreateInstance 호출 후에 사용하면,

do
{
    hr = CoCreateInstance(CLSID_LxssManager, nullptr, CLSCTX_LOCAL_SERVER, 
        __uuidof(ILxssSession), (PVOID*)&pSession);
    if (hr != S_OK)
    {
        break;
    }

    hr = CoSetProxyBlanket(pSession, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE,
        nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_STATIC_CLOAKING);
    if (hr != S_OK)
    {
        break;
    }

    printf("IsRegistered: %d\n", WslIsDistributionRegistered(pSession, L"Ubuntu20.04"));

} while (false);

이제 정상적으로 (CoInitializeSecurity 함수의 호출 여부에 상관없이) WslIsDistributionRegistered/QueryState 함수가 동작하는 것을 확인할 수 있습니다.




그럼 코드를 좀 다듬어서,

using System;
using System.Runtime.InteropServices;
using WslSdk.Interop;
using static WslSdk.Interop.NativeMethods;

namespace WslSdk
{
    public class LxSession : IDisposable
    {
        public static Guid CLSID_LxssManager = new Guid("4f476546-b412-4579-b64c-123df331e3d6");
        public static Guid IID_ILxssSession = new Guid("536A6BCF-FE04-41D9-B978-DCACA9A9B5B9");
        public static Guid IID_ILxInstance = new Guid("8f9e8123-58d4-484a-ac25-7ef7d5f7448f");

        ILxssSession pSession;

        public LxSession()
        {
            IntPtr pUnknown = IntPtr.Zero;

            try
            {
                int hr = NativeMethods.CoCreateInstance(CLSID_LxssManager, IntPtr.Zero, NativeMethods.CLSCTX.LOCAL_SERVER, IID_ILxssSession,
                    ref pUnknown);
                if (hr != 0)
                {
                    return;
                }

                hr = NativeMethods.CoSetProxyBlanket(pUnknown, RpcAuthN.WINNT, RpcAuthZ.NONE,
                            IntPtr.Zero, RpcAuthnLevel.Call, RpcImpLevel.Impersonate, IntPtr.Zero, EoAuthnCap.StaticCloaking);
                if (hr != 0)
                {
                    return;
                }

                pSession = Marshal.GetTypedObjectForIUnknown(pUnknown, typeof(ILxssSession)) as ILxssSession;
            }
            finally
            {
                Marshal.Release(pUnknown);
            }
        }

        public bool WslIsDistributionRegistered(string distroName)
        {
            if (pSession == null)
            {
                return false;
            }

            Guid instanceId = Guid.Empty;
            pSession.QueryState(distroName, 0, ref instanceId);

            return instanceId != Guid.Empty;
        }

        public void Dispose()
        {
            if (pSession != null)
            {
                Marshal.ReleaseComObject(pSession);
                pSession = null;
            }
        }
    }
}

해보는 김에, WebBrowser를 얹은 Windows Forms 예제에도,

using System;
using System.Windows.Forms;
using WslSdk;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            this.webBrowser1.Navigate("https://forum.dotnetdev.kr");

            string distroName = Wsl.GetDefaultDistro().DistroName;
            using (LxSession lxSession = new LxSession())
            {
                MessageBox.Show(lxSession.WslIsDistributionRegistered(distroName).ToString());
            }
        }
    }
}

적용해 보면 QueryState를 직접 호출한 WslIsDistributionRegistered 버전으로는 잘 동작합니다.

결국, 전역적으로 설정되는 CoInitializeSecurity와 wslapi.dll의 Win32 API 호출 간의 문제를 극복하기 위해 별도의 프로세스로 호스팅하는 노력들이,

COM+ 서버 응용 프로그램을 이용해 CoInitializeSecurity 제약 해결
; https://www.sysnet.pe.kr/2/0/12667

애당초 마이크로소프트 측에서 ILxssSession의 Type Library를 공개했다면 없었을 문제였던 것입니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




또 다른 예제 코드로,

C++ (Cpp) CoSetProxyBlanket Examples
; https://cpp.hotexamples.com/examples/-/-/CoSetProxyBlanket/cpp-cosetproxyblanket-function-examples.html

WMI의 IWbemServices 인터페이스 개체에 대해 CoSetProxyBlanket을 호출한 사례를 볼 수 있습니다.

참고로 아래의 문서를 보면,

Win32_TSExpandEnvironmentVariables class
; https://learn.microsoft.com/en-us/windows/win32/termserv/win32-tsexpandenvironmentvariables

To connect to the \root\CIMV2\TerminalServices namespace, the authentication level must include packet privacy. For C/C++ calls, this is an authentication level of RPC_C_AUTHN_LEVEL_PKT_PRIVACY, which can be set by using the CoSetProxyBlanket COM function.


CoSetProxyBlanket이 요구하는 RPC_C_AUTHN_LEVEL_??? 설정이 상황마다 다르다는 것을 알 수 있습니다. 그리고, 일부 옵션의 값을 적절치 않게 설정하면,

hr = CoSetProxyBlanket(pSession, RPC_C_AUTHN_NONE, RPC_C_AUTHZ_NONE,
    nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, EOAC_STATIC_CLOAKING);
    // hr  0x800706d4 : The authentication level is unknown.

친절하게 그에 대한 옵션을 알려줘서 그나마 쉽게 문제점을 발견할 수 있습니다. ^^




휴~~~ 이제야 CoInitializeSecurity로부터 시작한 기나긴 여정이 끝났군요. 바로 이 글을 쓰기 위해 그동안 다음의 모든 주제가 쓰인 것입니다. ^^;

C# - CoCreateInstance 관련 Inteop 오류 정리
; https://www.sysnet.pe.kr/2/0/12678

Wslhub.Sdk 사용으로 알아보는 CoInitializeSecurity 사용 제약
; https://www.sysnet.pe.kr/2/0/12665

별도 DLL에 포함된 타입을 STAThread Main 메서드에서 사용하는 경우 CoInitializeSecurity 자동 호출
; https://www.sysnet.pe.kr/2/0/12666

COM+ 서버 응용 프로그램을 이용해 CoInitializeSecurity 제약 해결
; https://www.sysnet.pe.kr/2/0/12667

ionescu007/lxss github repo에 공개된 lxssmanager.dll의 CLSID_LxssUserSession/IID_ILxssSession 사용법
; https://www.sysnet.pe.kr/2/0/12676

역공학을 통한 lxssmanager.dll의 ILxssSession 사용법 분석
; https://www.sysnet.pe.kr/2/0/12677

C# - DLL Surrogate를 이용한 Out-of-process COM 개체 제작
; https://www.sysnet.pe.kr/2/0/12668

DLL Surrogate를 이용한 Out-of-process COM 개체에서의 CoInitializeSecurity 문제
; https://www.sysnet.pe.kr/2/0/12670

CoInitializeSecurity의 전역 설정을 재정의하는 CoSetProxyBlanket 함수 사용법
; https://www.sysnet.pe.kr/2/0/12679




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 2/4/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 166  167  168  169  170  171  172  173  174  175  176  177  178  179  [180]  ...
NoWriterDateCnt.TitleFile(s)
484정성태3/17/200719479오류 유형: 31. SQL Compact Edition 설치 후 오류
483정성태3/17/200740881오류 유형: 30. x64 환경: .NET + COM 프로젝트 실행 시 오류 - 80040154 [2]
482정성태3/17/200730351Team Foundation Server: 17. 팀 프로젝트 접속 및 사용
481정성태3/17/200724279Team Foundation Server: 16. 팀 프로젝트 읽기 전용 사용자 등록
480정성태3/14/200722389.NET Framework: 86. GC(Garbage Collector)의 변화
479정성태3/14/200726394개발 환경 구성: 25. D820 - ReadyBoost 구동
478정성태3/14/200725729개발 환경 구성: 24. D820 고주파음 문제
477정성태3/14/200735024개발 환경 구성: 23. 비스타 x64 버전에서 서명되지 않은 드라이버 사용 [4]
476정성태3/9/200730411개발 환경 구성: 22. D820 노트북 - 설치 및 BitLocker 구성 [1]
475정성태3/6/200724827.NET Framework: 85. 공용 프로퍼티 자동 생성
474정성태3/5/200723012.NET Framework: 84. Lambda 표현식 응용 사례 [1]
473정성태3/4/200730130디버깅 기술: 14. TFS 오류 추적(TF53010, TF14105)
472정성태3/3/200729233디버깅 기술: 13. 예외 발생 시 Minidump 생성 - WinDBG [3]파일 다운로드1
471정성태3/1/200718423디버깅 기술: 12. Managed Method에 Break Point 걸기
469정성태2/28/200729971디버깅 기술: 11. (Managed) Main Method에 Break Point 걸기 [3]파일 다운로드1
470정성태3/1/200721342    답변글 디버깅 기술: 11.1. (Managed) Main Method에 Break Point 걸기 - 내용 보강
468정성태2/25/200731195COM 개체 관련: 20. 탭 브라우저의 윈도우 핸들 구하기 [3]
466정성태2/22/200722905Windows: 23. 롱혼 서버 코어 버전 [2]
465정성태2/21/200721881오류 유형: 29. TFS 관련 스케줄 작업 실패
464정성태2/25/200723056오류 유형: 28. TF10217, TF53010, TF14105 오류
463정성태2/21/200716093Team Foundation Server: 15. 포탈 사이트의 보고서 주소를 도메인 명으로 적용
462정성태2/13/200743376.NET Framework: 83. 라이브러리에 다국어 리소스 추가 방법 [4]파일 다운로드1
461정성태2/13/200721089오류 유형: 27. DLinq 예제 오류 : error: 26 - Error Locating Server/Instance Specified
460정성태2/13/200721302.NET Framework: 82. Orcas 1월 CTP에서 Linq 소스 컴파일 방법
459정성태2/17/200725321오류 유형: 26. "Automatic Updates" 서비스 CPU 100% 점유 현상 - 두 번째 이야기 [3]
458정성태2/12/200721777.NET Framework: 81. LINQ 개발 환경 설정 [1]
... 166  167  168  169  170  171  172  173  174  175  176  177  178  179  [180]  ...