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

비밀번호

댓글 작성자
 




... 76  77  78  79  80  81  82  83  84  [85]  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11806정성태1/29/201919454디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/201921486.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [4]파일 다운로드1
11804정성태1/24/201919385Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/201918297디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/201920038.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/201921399개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/201820432오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/201822130.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/201820998개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/201817014개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
11796정성태12/19/201817219개발 환경 구성: 424. vcpkg - "File does not have expected hash" 오류를 무시하는 방법
11795정성태12/19/201820540Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회 [1]
11794정성태12/16/201816627오류 유형: 508. Get-AzureWebsite : Request to a downlevel service failed.
11793정성태12/16/201819212개발 환경 구성: 423. NuGet 패키지 제작 - Native와 Managed DLL을 분리하는 방법 [1]
11792정성태12/11/201818990Graphics: 34. .NET으로 구현하는 OpenGL (11) - Per-Pixel Lighting파일 다운로드1
11791정성태12/11/201819030VS.NET IDE: 130. C/C++ 프로젝트의 시작 프로그램으로 .NET Core EXE를 지정하는 경우 닷넷 디버깅이 안 되는 문제 [1]
11790정성태12/11/201817526오류 유형: 507. Could not save daemon configuration to C:\ProgramData\Docker\config\daemon.json: Access to the path 'C:\ProgramData\Docker\config' is denied.
11789정성태12/10/201831119Windows: 153. C# - USB 장치의 연결 및 해제 알림을 위한 WM_DEVICECHANGE 메시지 처리 [2]파일 다운로드2
11788정성태12/4/201817380오류 유형: 506. SqlClient - Value was either too large or too small for an Int32.Couldn't store <2151292191> in ... Column
11787정성태11/29/201821529Graphics: 33. .NET으로 구현하는 OpenGL (9), (10) - OBJ File Format, Loading 3D Models파일 다운로드1
11786정성태11/29/201818453오류 유형: 505. OpenGL.NET 예제 실행 시 "Managed Debugging Assistant 'CallbackOnCollectedDelegate'" 예외 발생
11785정성태11/21/201820832디버깅 기술: 120. windbg 분석 사례 - ODP.NET 사용 시 Finalizer에서 System.AccessViolationException 예외 발생으로 인한 비정상 종료
11784정성태11/18/201820065Graphics: 32. .NET으로 구현하는 OpenGL (7), (8) - Matrices and Uniform Variables, Model, View & Projection Matrices파일 다운로드1
11783정성태11/18/201818201오류 유형: 504. 윈도우 환경에서 docker가 설치된 컴퓨터 간의 ping IP 주소 풀이 오류
11782정성태11/18/201817371Windows: 152. 윈도우 10에서 사라진 "Adapters and Bindings" 네트워크 우선순위 조정 기능 - 두 번째 이야기
11781정성태11/17/201820712개발 환경 구성: 422. SFML.NET 라이브러리 설정 방법 [1]파일 다운로드1
... 76  77  78  79  80  81  82  83  84  [85]  86  87  88  89  90  ...