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

비밀번호

댓글 작성자
 




... 151  152  153  154  [155]  156  157  158  159  160  161  162  163  164  165  ...
NoWriterDateCnt.TitleFile(s)
1177정성태11/18/201129959.NET Framework: 272. 소켓 연결 시간 제한 - 두 번째 이야기 [1]파일 다운로드1
1176정성태11/17/201129173.NET Framework: 271. C#에서 확인해 보는 관리 힙의 인스턴스 구조 [3]파일 다운로드1
1175정성태11/16/201127168.NET Framework: 270. .NET 참조 개체 인스턴스의 Object Header를 확인하는 방법 [1]파일 다운로드1
1174정성태11/15/201126578.NET Framework: 269. 일반 참조형의 기본 메모리 소비는 얼마나 될까요? [4]
1173정성태11/14/201122776.NET Framework: 268. .NET Array는 왜 12bytes의 기본 메모리를 점유할까? [1]
1172정성태11/13/201119740.NET Framework: 267. windbg - GC Heap에서 .NET 타입에 대한 배열을 찾는 방법
1171정성태11/12/201136443.NET Framework: 266. StringBuilder에서의 OutOfMemoryException 오류 원인 분석 [4]파일 다운로드1
1170정성태11/10/201125665.NET Framework: 265. Named 동기화 개체 생성 시 System.UnauthorizedAccessException 예외 발생하는 경우
1169정성태11/10/201129448.NET Framework: 264. 다중 LAN 카드 환경에서 Dns.GetHostAddresses(local)가 반환해 주는 IP의 우선순위는 어떻게 될까요? [4]
1168정성태11/6/201125322오류 유형: 139. TlbImp : error TI0000 : A single valid machine type compatible with the input type library must be specified
1167정성태11/5/201137129개발 환경 구성: 133. Registry 등록 과정 없이 COM 개체 사용 - 두 번째 이야기 [5]파일 다운로드4
1166정성태11/5/201123176.NET Framework: 263. byte[] pData = new byte[100000]로 인한 성능 차이? [1]파일 다운로드1
1165정성태11/3/201128081개발 환경 구성: 132. "Visual Studio Command Prompt (2010)" 명령행에서 2.0 버전의 MSBuild를 구동하는 방법 [2]파일 다운로드1
1164정성태11/1/201126245.NET Framework: 262. .NET 스레드 콜 스택 덤프 (4) - .NET 4.0을 지원하지 않는 MSE 응용 프로그램 원인 분석
1163정성태10/31/201125742.NET Framework: 261. .NET 스레드 콜 스택 덤프 (3) - MSE 소스 코드 개선파일 다운로드1
1162정성태10/30/201125869.NET Framework: 260. .NET 스레드 콜 스택 덤프 (2) - Managed Stack Explorer 소스 코드를 이용한 스택 덤프 구하는 방법파일 다운로드1
1161정성태10/29/201122672.NET Framework: 259. Type.GetMethod - System.Reflection.AmbiguousMatchException파일 다운로드1
1159정성태10/28/201126111.NET Framework: 258. Roslyn 맛보기 - SyntaxTree 조작 [2]
1158정성태10/24/201125442.NET Framework: 257. Roslyn 맛보기 - Roslyn Symbol / Binding API파일 다운로드1
1157정성태10/23/201129878.NET Framework: 256. Roslyn 맛보기 - Syntax Analysis (Roslyn Syntax API) [2]
1156정성태10/23/201128337.NET Framework: 255. Roslyn 맛보기 - Roslyn Services APIs를 이용한 Code Issue 및 Code Action 기능 소개 [1]
1155정성태10/22/201126405.NET Framework: 254. Roslyn 맛보기 - C# Interactive (2)
1154정성태10/22/201133152.NET Framework: 253. Roslyn 맛보기 - C# Interactive (1)
1153정성태10/21/201142028.NET Framework: 252. Roslyn 맛보기 - C# 소스 코드를 스크립트처럼 다루는 방법 [7]파일 다운로드1
1152정성태10/20/201123702.NET Framework: 251. string.GetHashCode는 hash 값을 cache 할까?
1151정성태10/18/201122629Java: 13. 자바도 64비트에서 (2GB) OutOfMemoryException 예외가 발생할까?
... 151  152  153  154  [155]  156  157  158  159  160  161  162  163  164  165  ...