Microsoft MVP성태의 닷넷 이야기
VC++: 144. 역공학을 통한 lxssmanager.dll의 ILxssSession 사용법 분석 [링크 복사], [링크+제목 복사]
조회: 9429
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 9개 있습니다.)

역공학을 통한 lxssmanager.dll의 ILxssSession 사용법 분석

이전 글에서,

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

ILxssSession, ILxInstnace를 소개했는데요, 아쉽게도 동작은 하지 않았지만 어쨌든 4개의 함수 signature를 공개했다는 것이 중요합니다. 그럼, 혹시 우리도 알 수 있지 않을까요? ^^

일단, CoCreateInstance로 반환한 ILxssSession 인스턴스를,

hr = CoCreateInstance(CLSID_LxssManager, nullptr, CLSCTX_LOCAL_SERVER, 
            __uuidof(ILxssSession), (PVOID*)&pSession);

// pSession == 00000267BD58AB98

해당 주솟값을 참조해서 들어가면 vtable 위치를 알 수 있는데요,

[00000267BD58AB98] == 00007ff9ae572180 

vtable의 항목에 대해 windbg에서 symbol을 확인할 수 있습니다. 아래는 vtable에 대해 정리한 것입니다.

C# - PDB 파일로부터 심벌(Symbol) 및 타입(Type) 정보 열거
; https://www.sysnet.pe.kr/2/0/12114

00007ff9`ae572180 00007ff9ae571490 LxssManagerProxyStub!IUnknown_QueryInterface_Proxy: // @0 IUnknown::QueryInterface
00007ff9`ae572188 00007ff9ae571320 LxssManagerProxyStub!IUnknown_AddRef_Proxy: // @8 IUnknown::AddRef
00007ff9`ae572190 00007ff9ae5713d0 LxssManagerProxyStub!IUnknown_Release_Proxy: // @10 IUnknown::Release
00007ff9`ae572198 00007ff9ae571340 LxssManagerProxyStub!ObjectStublessClient3: // @18 GetCurrentInstance
00007ff9`ae5721a0 00007ff9ae5714c0 LxssManagerProxyStub!ObjectStublessClient4: // @20 StartDefaultInstance
00007ff9`ae5721a8 00007ff9ae571390 LxssManagerProxyStub!ObjectStublessClient5: // @28 SetState
00007ff9`ae5721b0 00007ff9ae5713e0 LxssManagerProxyStub!ObjectStublessClient6: // @30 QueryState
00007ff9`ae5721b8 00007ff9ae571370 LxssManagerProxyStub!ObjectStublessClient7: // @38 InitializeFileSystem
00007ff9`ae5721c0 00007ff9ae5713f0 LxssManagerProxyStub!ObjectStublessClient8: // @40 Destroy
00007ff9`ae5721c8 00007ff9ae571400 LxssManagerProxyStub!ObjectStublessClient9:
00007ff9`ae5721d0 00007ff9ae571430 LxssManagerProxyStub!ObjectStublessClient10:
00007ff9`ae5721d8 00007ff9ae571310 LxssManagerProxyStub!ObjectStublessClient11:
00007ff9`ae5721e0 00007ff9ae5712f0 LxssManagerProxyStub!ObjectStublessClient12:
00007ff9`ae5721e8 00007ff9ae571380 LxssManagerProxyStub!ObjectStublessClient13:
00007ff9`ae5721f0 00007ff9ae571300 LxssManagerProxyStub!ObjectStublessClient14:
00007ff9`ae5721f8 00007ff9ae571360 LxssManagerProxyStub!ObjectStublessClient15:
00007ff9`ae572200 00007ff9ae571470 LxssManagerProxyStub!ObjectStublessClient16:
00007ff9`ae572208 00007ff9ae571420 LxssManagerProxyStub!ObjectStublessClient17:
00007ff9`ae572210 00007ff9ae571350 LxssManagerProxyStub!ObjectStublessClient18:
00007ff9`ae572218 00007ff9ae5713b0 LxssManagerProxyStub!ObjectStublessClient19:

아쉽게도 ObjectStublessClient3부터 시작해 ObjectStublessClient19까지의 함수는 추적해 들어가는 경우 얻을 수 있는 정보가 없습니다. ^^; 솔직히 없다기보다는 INPROC COM 개체였다면 쉽게 분석을 들어갈 수 있었겠지만 DCOM 서버라서 RPC 호출이 엮어져서 분석이 매우 어렵습니다.

그럼 접근법을 달리해서 wslapi.dll에 있는 함수 호출을 분석해 들어가면 될 듯합니다. 이를 위해 적당한 API 하나를 골라야 하는데, 함수 인자가 1개인 WslIsDistributionRegistered가 후보로 좋을 듯합니다. ^^

WslIsDistributionRegistered function (wslapi.h)
; https://learn.microsoft.com/en-us/windows/win32/api/wslapi/nf-wslapi-wslisdistributionregistered

사용은 이렇게 하고,

typedef BOOL(__stdcall* pIsDistroRegistered)(PCWSTR distroName);

HMODULE dll = LoadLibraryExW(L"wslapi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
pIsDistroRegistered IsDistroRegistered = (pIsDistroRegistered)GetProcAddress(dll, "WslIsDistributionRegistered");

BOOL result = IsDistroRegistered(L"Ubuntu20.04");
printf("IsDistroRegistered: %d\n", result);

windbg로 IsDistroRegistered 호출부터 BP를 걸어 들어가면 됩니다. 한번 따라가볼까요? ^^




아래는 windbg에서 본 wslapi!WslIsDistributionRegistered 코드입니다.

struct _LX_DIST_INFO
{
    wchar_t *pName;
    int *pValue;
}

wslapi!WslIsDistributionRegistered:
00007ff9`b9ff23a0 48895c2418      mov     qword ptr [rsp+18h],rbx
00007ff9`b9ff23a5 48894c2408      mov     qword ptr [rsp+8],rcx ss:00000094`eacffae0={ConsoleApplication1!`string' (00007ff7`54dead00)}
                                     // rsp + 8 == 00000094`eacffae0
                                     // [rsp + 8] == 00007ff754dead58 == L"Ubuntu20.04"
00007ff9`b9ff23aa 57              push    rdi
00007ff9`b9ff23ab 4883ec30        sub     rsp,30h
                                     // int parent_sp_2; // rsp+48h == 00000094`eacffae8
                                     // void *ptr_2; // rsp+28h == 00000094`eacffac8
00007ff9`b9ff23af 33ff            xor     edi,edi
00007ff9`b9ff23b1 897c2448        mov     dword ptr [rsp+48h],edi
                                     // parent_sp_2 = 0; // [rsp + 48] == (00007ff9)00000000
00007ff9`b9ff23b5 8d4f10          lea     ecx,[rdi+10h] // rcx == 0x10
00007ff9`b9ff23b8 e8db360000      call    wslapi!operator new (00007ff9`b9ff5a98) // 0x10바이트만큼 공간 확보
00007ff9`b9ff23bd 4885c0          test    rax,rax // rax == 00000267`bd5a5820
                                     // 00000267`bd5a5820 38 01 00 00 00 00 00  8......
                                     // 00000267`bd5a5827 00 01 00 00 00 10 16  .......
00007ff9`b9ff23c0 7413            je      wslapi!WslIsDistributionRegistered+0x35 (00007ff9`b9ff23d5)
00007ff9`b9ff23c2 488d4c2440      lea     rcx,[rsp+40h] // L"Ubuntu20.04"
00007ff9`b9ff23c7 488908          mov     qword ptr [rax],rcx
00007ff9`b9ff23ca 488d4c2448      lea     rcx,[rsp+48h]
00007ff9`b9ff23cf 48894808        mov     qword ptr [rax+8],rcx

                                     // _LX_DIST_INFO pInfo = new();
                                     // info->pName = [1st_arg];
                                     // info->pValue = &parent_sp_2;

00007ff9`b9ff23d3 eb03            jmp     wslapi!WslIsDistributionRegistered+0x38 (00007ff9`b9ff23d8)
00007ff9`b9ff23d5 488bc7          (mov     rax,rdi)
00007ff9`b9ff23d8 4885c0          test    rax,rax
00007ff9`b9ff23db 747b            je      wslapi!WslIsDistributionRegistered+0xb8 (00007ff9`b9ff2458)
00007ff9`b9ff23dd 48897c2428      mov     qword ptr [rsp+28h],rdi
                                     // ptr_2 = nullptr; // rsp+28h == 00000094`eacffac8

00007ff9`b9ff23e2 4c8d0577050000  lea     r8,[wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl> (00007ff9`b9ff2960)]
00007ff9`b9ff23e9 4c8bc8          mov     r9,rax
00007ff9`b9ff23ec 897c2420        mov     dword ptr [rsp+20h],edi // DWORD dwCreationFlags = 0
00007ff9`b9ff23f0 33d2            xor     edx,edx
00007ff9`b9ff23f2 33c9            xor     ecx,ecx
00007ff9`b9ff23f4 48ff15054f0000  call    qword ptr [wslapi!_imp_CreateThread (00007ff9`b9ff7300)]
                                    // CreateThread(0, 0, [wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>, pInfo, dwCreationFlags, ptr_2);
00007ff9`b9ff23fb 0f1f440000      nop     dword ptr [rax+rax]
00007ff9`b9ff2400 488bd8          mov     rbx,rax
                                    // rax == 1bc
                                    // !handle 1bc ==> Type Thread
00007ff9`b9ff2403 4885c0          test    rax,rax
00007ff9`b9ff2406 7439            je      wslapi!WslIsDistributionRegistered+0xa1 (00007ff9`b9ff2441)
00007ff9`b9ff2408 83caff          or      edx,0FFFFFFFFh
00007ff9`b9ff240b 488bc8          mov     rcx,rax
00007ff9`b9ff240e 48ff15534f0000  call    qword ptr [wslapi!_imp_WaitForSingleObject (00007ff9`b9ff7368)]
                                   // WaitForSingleObject(rax, 4294967295);

00007ff9`b9ff2415 0f1f440000      nop     dword ptr [rax+rax]
00007ff9`b9ff241a 8b7c2448        mov     edi,dword ptr [rsp+48h]
00007ff9`b9ff241e 4883fbff        cmp     rbx,0FFFFFFFFFFFFFFFFh
00007ff9`b9ff2422 740f            je      wslapi!WslIsDistributionRegistered+0x93 (00007ff9`b9ff2433)
00007ff9`b9ff2424 488bcb          mov     rcx,rbx
00007ff9`b9ff2427 48ff15f24d0000  call    qword ptr [wslapi!_imp_CloseHandle (00007ff9`b9ff7220)]
00007ff9`b9ff242e 0f1f440000      nop     dword ptr [rax+rax]
00007ff9`b9ff2433 488b5c2450      mov     rbx,qword ptr [rsp+50h]
00007ff9`b9ff2438 8bc7            mov     eax,edi
00007ff9`b9ff243a 4883c430        add     rsp,30h
00007ff9`b9ff243e 5f              pop     rdi
00007ff9`b9ff243f c3              ret

코드를 보면, 다른 건 별로 의미가 없고 WslIsDistributionRegistered의 본래 기능은 CreateThread에 전달된 lambda_invoker_cdecl 함수에서 실제 동작을 하는 걸로 보입니다. lambda_invoker_cdecl은 다음의 코드로 이뤄집니다.

wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>:
    // DWORD WINAPI ThreadProc(_In_ LPVOID lpParameter);
00007ff9`b9ff2960 48895c2408      mov     qword ptr [rsp+8],rbx
00007ff9`b9ff2965 57              push    rdi
00007ff9`b9ff2966 4883ec40        sub     rsp,40h
                                       // _LX_CERATE_INFO *pCerateInfo;
                                       // __int128* pField1; // [rsp + 28h]
00007ff9`b9ff296a 488b059f760000  mov     rax,qword ptr [wslapi!_security_cookie (00007ff9`b9ffa010)]
00007ff9`b9ff2971 4833c4          xor     rax,rsp
00007ff9`b9ff2974 4889442438      mov     qword ptr [rsp+38h],rax

00007ff9`b9ff2979 488bf9          mov     rdi,rcx // rdi = lpParameter
00007ff9`b9ff297c 488d542420      lea     rdx,[rsp+20h] // rdx = 94eb3ff900
00007ff9`b9ff2981 488b09          mov     rcx,qword ptr [rcx] // rcx == 94eacffae0, [94eacffae0] == 7ff754dead58 == L"Ubuntu20.04"
00007ff9`b9ff2984 33db            xor     ebx,ebx
00007ff9`b9ff2986 48895c2420      mov     qword ptr [rsp+20h],rbx
                                       // pCerateInfo = nullptr;

00007ff9`b9ff298b 488b09          mov     rcx,qword ptr [rcx] // rcx = [rcx] == 7ff754dead58
00007ff9`b9ff298e e889220000      call    wslapi!WslSession::s_Create (00007ff9`b9ff4c1c)
                                         // WslSession::s_Create(L"Ubuntu20.04", ptr_1);
                                         // pCreateInfo에 pSession을 담아 반환
00007ff9`b9ff2993 85c0            test    eax,eax
00007ff9`b9ff2995 7919            jns     wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x50 (00007ff9`b9ff29b0) // 00007ff9`b9ff29b0로 점프
...[생략]...
00007ff9`b9ff29b0 488b4c2420      mov     rcx,qword ptr [rsp+20h] // rcx =  pCerateInfo;
00007ff9`b9ff29b5 488d542428      lea     rdx,[rsp+28h] // rdx = &pField1;
00007ff9`b9ff29ba 0f57c0          xorps   xmm0,xmm0 
00007ff9`b9ff29bd 0f11442428      movups  xmmword ptr [rsp+28h],xmm0 // pField1 = nullptr;
00007ff9`b9ff29c2 e829250000      call    wslapi!WslSession::_GetDistroId (00007ff9`b9ff4ef0)
                                        // WslSession::_GetDistroId(pCreateInfo, pField1);
00007ff9`b9ff29c7 85c0            test    eax,eax
00007ff9`b9ff29c9 7821            js      wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x8c (00007ff9`b9ff29ec)
00007ff9`b9ff29cb 488b442428      mov     rax,qword ptr [rsp+28h]
00007ff9`b9ff29d0 483b05514e0000  cmp     rax,qword ptr [wslapi!GUID_NULL (00007ff9`b9ff7828)]
00007ff9`b9ff29d7 750e            jne     wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x87 (00007ff9`b9ff29e7)
00007ff9`b9ff29d9 488b442430      mov     rax,qword ptr [rsp+30h]
00007ff9`b9ff29de 483b054b4e0000  cmp     rax,qword ptr [wslapi!GUID_NULL+0x8 (00007ff9`b9ff7830)]
00007ff9`b9ff29e5 7405            je      wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x8c (00007ff9`b9ff29ec)
00007ff9`b9ff29e7 bb01000000      mov     ebx,1
00007ff9`b9ff29ec 488b4708        mov     rax,qword ptr [rdi+8]
00007ff9`b9ff29f0 8918            mov     dword ptr [rax],ebx
00007ff9`b9ff29f2 488d4c2420      lea     rcx,[rsp+20h]
00007ff9`b9ff29f7 e8c4ecffff      call    wslapi!wistd::unique_ptr<WslSession,wistd::default_delete<WslSession> >::~unique_ptr<WslSession,wistd::default_delete<WslSession> > (00007ff9`b9ff16c0)
00007ff9`b9ff29fc 488bcf          mov     rcx,rdi
00007ff9`b9ff29ff e888300000      call    wslapi!operator delete (00007ff9`b9ff5a8c)
00007ff9`b9ff2a04 33c0            xor     eax,eax
00007ff9`b9ff2a06 488b4c2438      mov     rcx,qword ptr [rsp+38h]
00007ff9`b9ff2a0b 4833cc          xor     rcx,rsp
00007ff9`b9ff2a0e e8cd340000      call    wslapi!_security_check_cookie (00007ff9`b9ff5ee0)
00007ff9`b9ff2a13 488b5c2450      mov     rbx,qword ptr [rsp+50h]
00007ff9`b9ff2a18 4883c440        add     rsp,40h
...[생략]...

위의 호출에서 s_Create 함수를 먼저 들어가 보면,

struct _LX_CREATE_UNK16
{
    wchar_t *pDistName;
    ILxssSession *pSession;
}

struct _LX_CERATE_INFO
{
    _LX_CREATE_UNK16 f1;
    __int64 flag2;
}

wslapi!WslSession::s_Create: // WslSession::s_Create(L"Ubuntu20.04", ptr_1);
00007ff9`b9ff4c1c 48895c2408      mov     qword ptr [rsp+8],rbx ss:00000094`eb3ff8e0=0000000000000000
00007ff9`b9ff4c21 4889742418      mov     qword ptr [rsp+18h],rsi
00007ff9`b9ff4c26 57              push    rdi
00007ff9`b9ff4c27 4883ec20        sub     rsp,20h
00007ff9`b9ff4c2b 488bfa          mov     rdi,rdx  // rdi == ptr_1 == 94eb3ff900
00007ff9`b9ff4c2e 488bf1          mov     rsi,rcx  // rsi == "Ubuntu20.04"
00007ff9`b9ff4c31 488d15182a0000  lea     rdx,[wslapi!std::nothrow (00007ff9`b9ff7650)]
00007ff9`b9ff4c38 b918000000      mov     ecx,18h
00007ff9`b9ff4c3d e89a180000      call    wslapi!operator new (00007ff9`b9ff64dc)
                                          // _LX_CERATE_INFO pCreateInfo = new [0x18] // 24bytes [267bd5a5660]
00007ff9`b9ff4c42 4885c0          test    rax,rax
00007ff9`b9ff4c45 7410            je      wslapi!WslSession::s_Create+0x3b (00007ff9`b9ff4c57)
00007ff9`b9ff4c47 0f57c0          xorps   xmm0,xmm0
00007ff9`b9ff4c4a 33c9            xor     ecx,ecx
00007ff9`b9ff4c4c 0f1100          movups  xmmword ptr [rax],xmm0
                                          // pCreateInfo->f1 = 0;
00007ff9`b9ff4c4f 48894810        mov     qword ptr [rax+10h],rcx
                                          // pCreateInfo->flag2 = 0;
00007ff9`b9ff4c53 c6401001        mov     byte ptr [rax+10h],1
                                          // pCreateInfo->flag2 = 1;
00007ff9`b9ff4c57 488b1f          mov     rbx,qword ptr [rdi] // rbx == 부모가 준 ptr_1 스택 영역 (아마도 구조체)
                                                              // rbx = 0
00007ff9`b9ff4c5a 488364243800    and     qword ptr [rsp+38h],0
00007ff9`b9ff4c60 488907          mov     qword ptr [rdi],rax // ptr_1 = pCreateInfo;
00007ff9`b9ff4c63 4885db          test    rbx,rbx
00007ff9`b9ff4c66 7464            je      wslapi!WslSession::s_Create+0xb0 (00007ff9`b9ff4ccc) // rbx != null이므로 00007ff9`b9ff4ccc로 점프
...[생략]...
00007ff9`b9ff4ccc 488d4c2438      lea     rcx,[rsp+38h] // rcx = 94eb3ff8e8
00007ff9`b9ff4cd1 e8eac9ffff      call    wslapi!wistd::unique_ptr<WslSession,wistd::default_delete<WslSession> >::~unique_ptr<WslSession,wistd::default_delete<WslSession> > (00007ff9`b9ff16c0)
00007ff9`b9ff4cd6 488b0f          mov     rcx,qword ptr [rdi]  // rcx = pCreateInfo
00007ff9`b9ff4cd9 4885c9          test    rcx,rcx
00007ff9`b9ff4cdc 7520            jne     wslapi!WslSession::s_Create+0xe2 (00007ff9`b9ff4cfe) // rcx != null이므로 00007ff9`b9ff4cfe로 점프
...[생략]...
00007ff9`b9ff4cfe 488bd6          mov     rdx,rsi
00007ff9`b9ff4d01 e826000000      call    wslapi!WslSession::_Initialize (00007ff9`b9ff4d2c)
                                          // wslapi!WslSession::_Initialize(pCreateInfo, "Ubuntu20.04");
                                          // pCreateInfo에 pSession을 담아 반환
00007ff9`b9ff4d06 8bd8            mov     ebx,eax
00007ff9`b9ff4d08 85c0            test    eax,eax
00007ff9`b9ff4d0a 7907            jns     wslapi!WslSession::s_Create+0xf7 (00007ff9`b9ff4d13)
...[생략]...
00007ff9`b9ff4d13 33c0            xor     eax,eax // 반환값 0
00007ff9`b9ff4d15 488b5c2430      mov     rbx,qword ptr [rsp+30h]
00007ff9`b9ff4d1a 488b742440      mov     rsi,qword ptr [rsp+40h]
00007ff9`b9ff4d1f 4883c420        add     rsp,20h
00007ff9`b9ff4d23 5f              pop     rdi
00007ff9`b9ff4d24 c3              ret

실제 작업은 "WslSession::_Initialize" 함수에서 이뤄지는 것을 알 수 있습니다.

wslapi!WslSession::_Initialize:
00007ff9`b9ff4d2c 48895c2410      mov     qword ptr [rsp+10h],rbx ss:00000094`eb3ff8b8=0000000000000000
00007ff9`b9ff4d31 48896c2418      mov     qword ptr [rsp+18h],rbp
00007ff9`b9ff4d36 4889742420      mov     qword ptr [rsp+20h],rsi
00007ff9`b9ff4d3b 57              push    rdi
00007ff9`b9ff4d3c 4883ec60        sub     rsp,60h
00007ff9`b9ff4d40 488d7910        lea     rdi,[rcx+10h]
00007ff9`b9ff4d44 488bea          mov     rbp,rdx
00007ff9`b9ff4d47 488bf1          mov     rsi,rcx
00007ff9`b9ff4d4a c60700          mov     byte ptr [rdi],0
00007ff9`b9ff4d4d 33d2            xor     edx,edx
00007ff9`b9ff4d4f 33c9            xor     ecx,ecx
00007ff9`b9ff4d51 48ff1538240000  call    qword ptr [wslapi!_imp_CoInitializeEx (00007ff9`b9ff7190)]
                                          // CoInitializeEx(nullptr, 0);
00007ff9`b9ff4d58 0f1f440000      nop     dword ptr [rax+rax]
00007ff9`b9ff4d5d 85c0            test    eax,eax
00007ff9`b9ff4d5f 0f8874010000    js      wslapi!WslSession::_Initialize+0x1ad (00007ff9`b9ff4ed9)
00007ff9`b9ff4d65 488364244000    and     qword ptr [rsp+40h],0
00007ff9`b9ff4d6b 4533c9          xor     r9d,r9d
00007ff9`b9ff4d6e c744243820000000 mov     dword ptr [rsp+38h],20h
00007ff9`b9ff4d76 4533c0          xor     r8d,r8d
00007ff9`b9ff4d79 488364243000    and     qword ptr [rsp+30h],0
00007ff9`b9ff4d7f 83caff          or      edx,0FFFFFFFFh
00007ff9`b9ff4d82 c744242803000000 mov     dword ptr [rsp+28h],3
00007ff9`b9ff4d8a 33c9            xor     ecx,ecx
00007ff9`b9ff4d8c 8364242000      and     dword ptr [rsp+20h],0
00007ff9`b9ff4d91 48ff1500240000  call    qword ptr [wslapi!_imp_CoInitializeSecurity (00007ff9`b9ff7198)]
                                          // CoInitializeSecurity(0, -1, 0, 0, RpcAuthnLevel.Default /* 0 */,
                                                        RpcImpLevel.Impersonate /* 3 */, 0, EoAuthnCap.StaticCloaking /* 0x20 */, 0);
00007ff9`b9ff4d98 0f1f440000      nop     dword ptr [rax+rax]
00007ff9`b9ff4d9d ba00000080      mov     edx,80000000h
00007ff9`b9ff4da2 8d0c10          lea     ecx,[rax+rdx]
00007ff9`b9ff4da5 85ca            test    edx,ecx
00007ff9`b9ff4da7 750b            jne     wslapi!WslSession::_Initialize+0x88 (00007ff9`b9ff4db4)
00007ff9`b9ff4da9 3d19010180      cmp     eax,80010119h
00007ff9`b9ff4dae 0f850e010000    jne     wslapi!WslSession::_Initialize+0x196 (00007ff9`b9ff4ec2) // RCP_E_TOO_LATE
00007ff9`b9ff4db4 488d5e08        lea     rbx,[rsi+8] // rbx == &pCreateInfo->f1.pSession;
00007ff9`b9ff4db8 488b0b          mov     rcx,qword ptr [rbx]  // rcx = pCreateInfo->f1.pSession; // == 0
00007ff9`b9ff4dbb 48832300        and     qword ptr [rbx],0  // pCreateInfo->f1.pSession = 0;
00007ff9`b9ff4dbf 4885c9          test    rcx,rcx
00007ff9`b9ff4dc2 740d            je      wslapi!WslSession::_Initialize+0xa5 (00007ff9`b9ff4dd1) // 00007ff9`b9ff4dd1 점프
...[생략]...
00007ff9`b9ff4dd1 33d2            xor     edx,edx
00007ff9`b9ff4dd3 48895c2420      mov     qword ptr [rsp+20h],rbx
00007ff9`b9ff4dd8 4c8d0db12c0000  lea     r9,[wslapi!GUID_536a6bcf_fe04_41d9_b978_dcaca9a9b5b9 (00007ff9`b9ff7a90)]
00007ff9`b9ff4ddf 488d0d9a2c0000  lea     rcx,[wslapi!`string'+0x4 (00007ff9`b9ff7a80)]
00007ff9`b9ff4de6 448d4204        lea     r8d,[rdx+4]
00007ff9`b9ff4dea 48ff15af230000  call    qword ptr [wslapi!_imp_CoCreateInstance (00007ff9`b9ff71a0)]
                                        // CoCreateInstance(CLSID_LxssManager, nullptr, CLSCTX_LOCAL_SERVER, __uuidof(ILxssSession), &pCreateInfo->f1.pSession);
00007ff9`b9ff4df1 0f1f440000      nop     dword ptr [rax+rax]
00007ff9`b9ff4df6 8bd8            mov     ebx,eax
00007ff9`b9ff4df8 b89e010780      mov     eax,8007019Eh // The Windows Subsystem for Linux has not been enabled. 
00007ff9`b9ff4dfd 81fb54010480    cmp     ebx,80040154h // Class not registered - Retrieving the COM class factory for component with CLSID
00007ff9`b9ff4e03 0f44d8          cmove   ebx,eax
00007ff9`b9ff4e06 85db            test    ebx,ebx
00007ff9`b9ff4e08 7907            jns     wslapi!WslSession::_Initialize+0xe5 (00007ff9`b9ff4e11) [br=1] // 00007ff9`b9ff4e11로 점프
...[생략]...
00007ff9`b9ff4e11 4983c8ff        or      r8,0FFFFFFFFFFFFFFFFh // r8 = ffffffffffffffff
00007ff9`b9ff4e15 488d4c2470      lea     rcx,[rsp+70h]  // 00000094`eb3ff8b0 (이 위치는 WslSession::_initialize 반환 주소 값을 담고 있는 그다음 부모의 rsp 마지막 기준 주소)
00007ff9`b9ff4e1a 488bd5          mov     rdx,rbp  // rdx == "Ubuntu20.08"
00007ff9`b9ff4e1d e8c20a0000      call    wslapi!wil::make_unique_string_nothrow<wil::unique_any_t<wil::details::unique_storage<wil::details::resource_policy<unsigned short * __ptr64,void (__cdecl*)(void * __ptr64),&CoTaskMemFree,wistd::integral_constant<unsigned __int64,0>,unsigned short * __ptr64,unsigned short * __ptr64,0,std::nullptr_t> > > > (00007ff9`b9ff58e4)
                                            // [rsp+70h] 00000094`eb3ff8b0 == 00000267bd5a57e0 == "Ubuntu20.04"
00007ff9`b9ff4e22 488d542470      lea     rdx,[rsp+70h] // rdx == "Ubuntu20.04"
                                                        // rdx == 94eb3ff8b0, [rdx] == 00000267bd5a57e0 == "Ubuntu20.04"
00007ff9`b9ff4e27 488bce          mov     rcx,rsi  // rcx == pCreateInfo
00007ff9`b9ff4e2a e8b5050000      call    wslapi!wil::unique_any_t<wil::details::unique_storage<wil::details::resource_policy<unsigned short * __ptr64,void (__cdecl*)(void * __ptr64),&CoTaskMemFree,wistd::integral_constant<unsigned __int64,0>,unsigned short * __ptr64,unsigned short * __ptr64,0,std::nullptr_t> > >::operator= (00007ff9`b9ff53e4)
                                         // 호출 후,
                                         // rax == 267bd5a5660, pCreateInfo
                                         // [rsp+70h] = 0
00007ff9`b9ff4e2f 488b4c2470      mov     rcx,qword ptr [rsp+70h] // rcx = 0
00007ff9`b9ff4e34 4885c9          test    rcx,rcx
00007ff9`b9ff4e37 740c            je      wslapi!WslSession::_Initialize+0x119 (00007ff9`b9ff4e45) // 00007ff9`b9ff4e45로 점프
...[생략]...
00007ff9`b9ff4e45 48833e00        cmp     qword ptr [rsi],0
00007ff9`b9ff4e49 752c            jne     wslapi!WslSession::_Initialize+0x14b (00007ff9`b9ff4e77) // 00007ff9`b9ff4e77로 점프
...[생략]...
00007ff9`b9ff4e77 488d442450      lea     rax,[rsp+50h] // rax == 00000094`eb3ff890 fffffffffffffffe
00007ff9`b9ff4e7c 483bf8          cmp     rdi,rax // rdi == 267bd5a5670, pCreateInfo->flag2
00007ff9`b9ff4e7f 741a            je      wslapi!WslSession::_Initialize+0x16f (00007ff9`b9ff4e9b)
00007ff9`b9ff4e81 8a07            mov     al,byte ptr [rdi]  // pCreateInfo->flag2 == 0 (1로 초기화했는데 언제 0으로???)
00007ff9`b9ff4e83 c60700          mov     byte ptr [rdi],0  // 어쨌든 pCreateInfo->flag2 = 0으로 초기화
00007ff9`b9ff4e86 84c0            test    al,al
00007ff9`b9ff4e88 740c            je      wslapi!WslSession::_Initialize+0x16a (00007ff9`b9ff4e96) // 00007ff9`b9ff4e96으로 점프
...[생략]...
00007ff9`b9ff4e96 c60701          mov     byte ptr [rdi],1  // 어쨌든 pCreateInfo->flag2 = 1로 초기화
00007ff9`b9ff4e99 eb0c            jmp     wslapi!WslSession::_Initialize+0x17b (00007ff9`b9ff4ea7) // 00007ff9`b9ff4ea7로 점프
...[생략]...
00007ff9`b9ff4ea7 33db            xor     ebx,ebx
00007ff9`b9ff4ea9 4c8d5c2460      lea     r11,[rsp+60h]
00007ff9`b9ff4eae 8bc3            mov     eax,ebx // 리턴 값 0
00007ff9`b9ff4eb0 498b5b18        mov     rbx,qword ptr [r11+18h]
00007ff9`b9ff4eb4 498b6b20        mov     rbp,qword ptr [r11+20h]
00007ff9`b9ff4eb8 498b7328        mov     rsi,qword ptr [r11+28h]
00007ff9`b9ff4ebc 498be3          mov     rsp,r11
00007ff9`b9ff4ebf 5f              pop     rdi
00007ff9`b9ff4ec0 c3              ret

익숙한 함수 호출들을 볼 수 있는데요, 결국 하는 것은 ILxssSession 인스턴스를 구하는 것입니다. 위의 함수를 호출한 lambda_invoker_cdecl 측에서는 저렇게 ILxssSession 인스턴스를 구한 후 WslSession::_GetDistroId 함수를 호출하게 됩니다. 그리고 마침내 이 함수에서 ILxssSession 인터페이스의 (QueryState로 보이는) 7번째 함수의,

MIDL_INTERFACE("536A6BCF-FE04-41D9-B978-DCACA9A9B5B9") ILxssSession : IUnknown
{
    STDMETHOD(GetCurrentInstance)(ILxInstnace** pInstanceOut);
    STDMETHOD(StartDefaultInstance)(_In_ const IID& InstanceIid, _Out_ PVOID* InstanceOut);
    STDMETHOD(SetState)(/* unknown */);
    STDMETHOD(QueryState)(/* unknown */);
    STDMETHOD(InitializeFileSystem)(/* unknown */);
    STDMETHOD(Destroy)(/* unknown */);
};

사용 코드가 나옵니다.

wslapi!WslSession::_GetDistroId:
00007ff9`b9ff4ef0 4053            push    rbx
00007ff9`b9ff4ef2 4883ec30        sub     rsp,30h
00007ff9`b9ff4ef6 4c8b5108        mov     r10,qword ptr [rcx+8] // r10 == pCreateInfo->f1.pSession;
00007ff9`b9ff4efa 4c8bca          mov     r9,rdx // r9 == pField1 // 94eb3ff908
00007ff9`b9ff4efd 488b11          mov     rdx,qword ptr [rcx] // rdx == 267bd5a57e0 == "Ubuntu20.04"
00007ff9`b9ff4f00 4533c0          xor     r8d,r8d // r8 = 0
00007ff9`b9ff4f03 498bca          mov     rcx,r10 // rcx = 267bd58ab98, [267bd58ab98] == pSession;
00007ff9`b9ff4f06 498b02          mov     rax,qword ptr [r10] // rax = pSesion.vptr
00007ff9`b9ff4f09 488b4030        mov     rax,qword ptr [rax+30h] // rax = pSession의 QueryState
00007ff9`b9ff4f0d ff15ad250000    call    qword ptr [wslapi!_guard_dispatch_icall_fptr (00007ff9`b9ff74c0)]
                                       // pSession->QueryState("Ubuntu20.04", 0, &pField1);
00007ff9`b9ff4f13 8bd8            mov     ebx,eax // 80070005
00007ff9`b9ff4f15 85c0            test    eax,eax
00007ff9`b9ff4f17 791d            jns     wslapi!WslSession::_GetDistroId+0x46 (00007ff9`b9ff4f36)
00007ff9`b9ff4f19 488b4c2438      mov     rcx,qword ptr [rsp+38h]
00007ff9`b9ff4f1e 4c8d05bb2a0000  lea     r8,[wslapi!`string' (00007ff9`b9ff79e0)]
00007ff9`b9ff4f25 448bc8          mov     r9d,eax
00007ff9`b9ff4f28 ba67000000      mov     edx,67h
00007ff9`b9ff4f2d e836cbffff      call    wslapi!wil::details::in1diag3::Return_Hr (00007ff9`b9ff1a68)
00007ff9`b9ff4f32 8bc3            mov     eax,ebx
00007ff9`b9ff4f34 eb02            jmp     wslapi!WslSession::_GetDistroId+0x48 (00007ff9`b9ff4f38)
00007ff9`b9ff4f36 33c0            xor     eax,eax
00007ff9`b9ff4f38 4883c430        add     rsp,30h
00007ff9`b9ff4f3c 5b              pop     rbx
00007ff9`b9ff4f3d c3              ret

여기까지 보면 QueryState의 signature를 이렇게 정의할 수 있는데요,

STDMETHOD(QueryState)(const wchar_t* distroName, int unknownField, __int64 *pField);

마지막 인자에 대한 힌트를 lambda_invoker_cdecl의 WslSession::_GetDistroId에서 QueryState를 호출한 이후 다행히 그것을 다루는 코드에서 찾아볼 수 있습니다.

00007ff9`b9ff29c2 e829250000      call    wslapi!WslSession::_GetDistroId (00007ff9`b9ff4ef0)
                                        // WslSession::_GetDistroId(pCreateInfo, pField1);
00007ff9`b9ff29c7 85c0            test    eax,eax
00007ff9`b9ff29c9 7821            js      wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x8c (00007ff9`b9ff29ec)
00007ff9`b9ff29cb 488b442428      mov     rax,qword ptr [rsp+28h]
00007ff9`b9ff29d0 483b05514e0000  cmp     rax,qword ptr [wslapi!GUID_NULL (00007ff9`b9ff7828)]
00007ff9`b9ff29d7 750e            jne     wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x87 (00007ff9`b9ff29e7)
00007ff9`b9ff29d9 488b442430      mov     rax,qword ptr [rsp+30h]
00007ff9`b9ff29de 483b054b4e0000  cmp     rax,qword ptr [wslapi!GUID_NULL+0x8 (00007ff9`b9ff7830)]
00007ff9`b9ff29e5 7405            je      wslapi!<lambda_d62d766b87cfe9fe43d92b02b0dda941>::<lambda_invoker_cdecl>+0x8c (00007ff9`b9ff29ec)
00007ff9`b9ff29e7 bb01000000      mov     ebx,1

GUID_NULL이 나오죠. ^^ 따라서 이렇게 마무리할 수 있습니다.

STDMETHOD(QueryState)(const wchar_t* distroName, int unknownField, _In_ _Out_ IID* pGuid);

아울러, 이 값이 GUID라는 것에서 이전에 사용한 StartDefaultInstance의 사용법이 잘못되었음을 인지하게 됩니다.

hr = pSession->StartDefaultInstance(__uuidof(ILxInstance), (PVOID *)&pInstance);
printf("StartDefaultInstance hr == %d(0x%x)\n", hr, hr);

__uuidof(ILxInstance)가 아마도 QueryState로부터 받아온 GUID 값으로 보입니다.

IID instanceId;
hr = pSession->QueryState(L"Ubuntu20.04", 0, &instanceId);
hr = pSession->StartDefaultInstance(instanceId, (PVOID *)&pInstance);

(그래도 crash되는 것은 마찬가지입니다. 아마도 다른 인자가 있을 듯합니다.)




마지막으로, 이것을 이용해 다음과 같이 WslIsDistributionRegistered 함수를 QueryState로 다시 작성할 수 있습니다.

#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_IMPERSONATE,
        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;
        }

        // -2147024891 == 0x80070005 E_ACCESSDENIED (Access is denied.)
        // -2147024894 == 0x80070002 (The system cannot find the file specified.)
        // -2147024809 == 0x80070057 E_INVALIDARG (The parameter is incorrect.)
        // -2147220734 == 0x80040302

        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);

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

    /*
    OLECHAR* guidString;
    StringFromCLSID(iid, &guidString);

    printf("%ls\n", guidString);
    CoTaskMemFree(guidString);
    */

    return TRUE;
}

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




그나저나, 이런 어려움은 언젠가 마이크로소프트 측에서 (c:\windows\system32\lxss\)lxssmanager.dll에 대한 Type Library를 배포하면 자연스럽게 해결될 것입니다. 그런데 이게 그다지 희망적이지가 않습니다.

왜냐하면, lxssmanager.dll에서 제공하는 COM 개체의 호스팅 서비스를 찾아보면 마이크로소프트가 이 부분을 얼마나 비공개로 두고 싶은지 느낄 수 있기 때문입니다. 심심한데 이것도 기록을 남겨 볼까요? ^^

우선 lxssmanager.dll의 export 함수에서,

ServiceMain
SvchostPushServiceGlobals
DllCanUnloadNow
DllGetClassObject
InprocRegister
InprocUnregister

DllCanUnloadNow
DllGetClassObject

svchost.exe로 호스팅되는 서비스 프로세스가 아닐까 예상을 하게 되는데요, 실제로 레지스트리에는 다음과 같은 서비스 정보가 있습니다.

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LxssManager]
DependOnService: RPCSS staterepository
Description: @%systemroot%\\system32\\lxss\\LxssManager.dll,-101
DisplayName: @%systemroot%\\system32\\lxss\\LxssManager.dll,-100
ErrorControl=0x00000001
Group: LxssManagerGroup
ImagePath: %systemroot%\system32\svchost.exe -k netsvcs -p
LaunchProtected: 0x00000002
ObjectName: LocalSystem
RequiredPrivileges: SeChangeNotifyPrivilege SeDebugPrivilege SeImpersonatePrivilege SeAssignPrimaryTokenPrivilege SeLoadDriverPrivilege SeTcbPrivilege
ServiceSidType: 0x00000001
Start: 0x00000003
Type: 0x00000020

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LxssManager\parameters]
ServiceDll: %SystemRoot%\system32\lxss\LxssManager.dll
ServiceDllUnloadOnStop: 0x00000001

"C:\Windows\System32\lxss\en-US\LxssManager.dll.mui" 리소스 파일의 Strings 테이블에 따라,

100: LXSSMANAGER
101: The LXSS Manager service supports running native ELF binaries. The service provides the infrastructure necessary for ELF binaries to run on Windows. If the service is stopped or disabled, those binaries will no longer run.
102: LxssManagerUser

DisplayName은 LXSSMANAGER이므로 서비스 관리자에서 해당 이름과 "Path to executable"을 보면 다음과 같이 잘 나와 있습니다.

Path to executable: C:\WINDOWS\system32\svchost.exe -k netsvcs -p
Status: Running

오호~~~ 그럼 분명히 실행 중이기 때문에 process explorer를 통해 lxssmanager.dll을 로딩한 프로세스를 검색할 수 있을 것입니다. 그런데 애석하게도 검색이 안 됩니다. 할 수 없이, 또는, 작업 관리자를 이용해 프로세스 실행/중지를 반복하며 어떤 svchost.exe인지 애써 찾아보면 (근래의 Process Explorer로 DLL 정보는 볼 수 있는)Process Explorer에서조차 DLL 로딩 정보를 볼 수 없는 특별한 (PPL) 프로세스가 나옵니다.

lxssmanager_service_1.png

찾긴 찾았지만, 관리자 권한의 windbg로 attach시켜도 권한이 없다는 오류 메시지(Could not attach to process 9840, Win32 error 0n5 Access is denied)와 함께 더 이상 할 수 있는 것이 아무것도 없습니다. ^^;

이 정도로 보안에 신경 쓴다는 것은, Type Library를 공개하지 않을 충분한 이유가 있는 걸로 보입니다. ^^;

(2023-10-20 업데이트) 발표 자료가 하나 있군요. ^^

The linux kernel hidden inside windows 10
; https://www.slideshare.net/mark-smith/the-linux-kernel-hidden-inside-windows-10

위의 자료를 보면, GetConfiguration, GetId, QueryState, SetState, CreateLxProcess, RegisterAdssBusServer, ConnectAdssBusServer, GetState, StartSelf, StopSelf, GetSuspendState 등의 함수가 구현되었다고 합니다. 아쉽게도 인자 정보가 없군요. ^^ (참고로, Adss는 "Android Sub System"의 약어라고 합니다.)


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




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/14/2023]

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

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  10  [11]  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13346정성태5/10/20233775오류 유형: 858. RDP 원격 환경과 로컬 PC 간의 Ctrl+C, Ctrl+V 복사가 안 되는 문제
13345정성태5/9/20235038.NET Framework: 2117. C# - (OpenAI 기반의) Microsoft Semantic Kernel을 이용한 자연어 처리 [1]파일 다운로드1
13344정성태5/9/20236317.NET Framework: 2116. C# - OpenAI API 사용 - 지원 모델 목록 [1]파일 다운로드1
13343정성태5/9/20234200디버깅 기술: 192. Windbg - Hyper-V VM으로 이더넷 원격 디버깅 연결하는 방법
13342정성태5/8/20234121.NET Framework: 2115. System.Text.Json의 역직렬화 시 필드/속성 주의
13341정성태5/8/20233907닷넷: 2114. C# 12 - 모든 형식의 별칭(Using aliases for any type)
13340정성태5/8/20233925오류 유형: 857. Microsoft.Data.SqlClient.SqlException - 0x80131904
13339정성태5/6/20234616닷넷: 2113. C# 12 - 기본 생성자(Primary Constructors)
13338정성태5/6/20234102닷넷: 2112. C# 12 - 기본 람다 매개 변수파일 다운로드1
13337정성태5/5/20234625Linux: 59. dockerfile - docker exec로 container에 접속 시 자동으로 실행되는 코드 적용
13336정성태5/4/20234380.NET Framework: 2111. C# - 바이너리 출력 디렉터리와 연관된 csproj 설정
13335정성태4/30/20234514.NET Framework: 2110. C# - FFmpeg.AutoGen 라이브러리를 이용한 기본 프로젝트 구성 - Windows Forms파일 다운로드1
13334정성태4/29/20234165Windows: 250. Win32 C/C++ - Modal 메시지 루프 내에서 SetWindowsHookEx를 이용한 Thread 메시지 처리 방법
13333정성태4/28/20233626Windows: 249. Win32 C/C++ - 대화창 템플릿을 런타임에 코딩해서 사용파일 다운로드1
13332정성태4/27/20233721Windows: 248. Win32 C/C++ - 대화창을 위한 메시지 루프 사용자 정의파일 다운로드1
13331정성태4/27/20233744오류 유형: 856. dockerfile - 구 버전의 .NET Core 이미지 사용 시 apt update 오류
13330정성태4/26/20233414Windows: 247. Win32 C/C++ - CS_GLOBALCLASS 설명
13329정성태4/24/20233622Windows: 246. Win32 C/C++ - 직접 띄운 대화창 템플릿을 위한 Modal 메시지 루프 생성파일 다운로드1
13328정성태4/19/20233258VS.NET IDE: 184. Visual Studio - Fine Code Coverage에서 동작하지 않는 Fake/Shim 테스트
13327정성태4/19/20233682VS.NET IDE: 183. C# - .NET Core/5+ 환경에서 Fakes를 이용한 단위 테스트 방법
13326정성태4/18/20235052.NET Framework: 2109. C# - 닷넷 응용 프로그램에서 SQLite 사용 (System.Data.SQLite) [1]파일 다운로드1
13325정성태4/18/20234402스크립트: 48. 파이썬 - PostgreSQL의 with 문을 사용한 경우 연결 개체 누수
13324정성태4/17/20234237.NET Framework: 2108. C# - Octave의 "save -binary ..."로 생성한 바이너리 파일 분석파일 다운로드1
13323정성태4/16/20234134개발 환경 구성: 677. Octave에서 Excel read/write를 위한 io 패키지 설치
13322정성태4/15/20234934VS.NET IDE: 182. Visual Studio - 32비트로만 빌드된 ActiveX와 작업해야 한다면?
13321정성태4/14/20233734개발 환경 구성: 676. WSL/Linux Octave - Python 스크립트 연동
1  2  3  4  5  6  7  8  9  10  [11]  12  13  14  15  ...