CCob / BOF.NET

A .NET Runtime for Cobalt Strike's Beacon Object Files
681 stars 99 forks source link

BOF.NET fails to call CoInitializeSecurity which results in impersonation conflicts with other WMI tools #3

Closed Octoberfest7 closed 1 year ago

Octoberfest7 commented 1 year ago

TrustedSec recently updated their Situational Awareness BOF toolkit to support/fix remote WMI queries using tokens created with make_token. See issue: https://github.com/trustedsec/CS-Situational-Awareness-BOF/issues/94

The issue related to the "CoInitializeSecurity" call:

    hr = OLE32$CoInitializeSecurity( //Failure of this function does not necessarily mean we failed to initialize, it will fail on repeated calls, but the values from the original call are retained
        NULL,
            -1,
            NULL,
            NULL,
            RPC_C_AUTHN_LEVEL_DEFAULT,
            RPC_C_IMP_LEVEL_IMPERSONATE,
            NULL,
            EOAC_NONE,
            NULL);

Specifically the EOAC_NONE flag was changed to EOAC_DYNAMIC_CLOAKING so that subsequent WMI calls would use the thread token (see more here: https://learn.microsoft.com/en-us/windows/win32/api/objidl/ne-objidl-eole_authentication_capabilities)

From the MSDN page on CoInitializeSecurity(https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializesecurity):

The CoInitializeSecurity function initializes the security layer and sets the specified values as the security default. If a process does not call CoInitializeSecurity, COM calls it automatically the first time an interface is marshaled or unmarshaled, registering the system default security. No default security packages are registered until then.

This function is called exactly once per process, either explicitly or implicitly. It can be called by the client, server, or both.

The issue arises when BOF.NET is initialized in a process before any other COM interface, i.e. before a Situational Awareness BOF that uses WMI has been called.

Because BOF.NET fails to call CoInitializeSecurity, COM calls it automatically and without the requisite flags to allow the use of tokens created with make_token. Subsequently, all WMI related functionalities in the beacon process that require the use of an alternate token will fail and this can't be remedied.

To fix this, I have modified the ICorRuntimeHost function in bofnet_execute.cpp by adding a call to CoInitializeSecurity. An entry for CoInitializeSecurity must be made in utils.h also.

I also added a call to CoUninitialize at the bottom of the function to close the COM library once BOF.NET is done with it. This was causing a further issue with the TrustedSec BOF's which would try to intialize COM and fail because BOF.NET had already initialized it but hadn't closed it.

static ICorRuntimeHost* loadCLR(bool v4){

    BOF_LOCAL(OLE32, CoInitializeEx);
    BOF_LOCAL(OLE32, CoCreateInstance);
    BOF_LOCAL(OLE32, CLSIDFromString);
    BOF_LOCAL(OLE32, CoUninitialize); 
    BOF_LOCAL(OLE32, CoInitializeSecurity);  
    BOF_LOCAL(KERNEL32, LoadLibraryA);

    GUID                    IID_RTH, CLSID_RTH, IID_MH, CLSID_MH, CLSID_RH, IID_RH, IID_RHI;
    HRESULT                 hr;
    ICorRuntimeHost*        result = nullptr;
    ICLRMetaHost*           pMetaHost = nullptr;
    ICLRRuntimeInfo*        pRuntimeInfo = nullptr;
    ICLRRuntimeHost*        pClrRuntimeHost = nullptr;
    CLRCreateInstancePtr    pCLRCreateInstance = nullptr;
    HMODULE                 hMod = NULL;

    CLSIDFromString(L"{cb2f6722-ab3a-11d2-9c40-00c04fa30a3e}", &IID_RTH);
    CLSIDFromString(L"{cb2f6723-ab3a-11d2-9c40-00c04fa30a3e}", &CLSID_RTH);
    CLSIDFromString(L"{d332db9e-b9b3-4125-8207-a14884f53216}", &IID_MH);
    CLSIDFromString(L"{9280188D-0E8E-4867-B30C-7FA83884E8DE}", &CLSID_MH);
    CLSIDFromString(L"{bd39d1d2-ba2f-486a-89b0-b4b0cb466891}", &IID_RHI);
    CLSIDFromString(L"{90f1a06e-7712-4762-86b5-7a5eba6bdb02}", &CLSID_RH);
    CLSIDFromString(L"{90f1a06c-7712-4762-86b5-7a5eba6bdb02}", &IID_RH);

    if( (hMod = LoadLibraryA("mscoree.dll")) != NULL){
        pCLRCreateInstance = (CLRCreateInstancePtr)GetProcAddress(hMod,"CLRCreateInstance");
        if(pCLRCreateInstance == nullptr){
            log("[=]Failed to get v2 ICorRuntimeHost: 0x%x, will try .NET 2 method", hr);
            v4 = false;
        }
    }

    hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);

    hr = CoInitializeSecurity( //Failure of this function does not necessarily mean we failed to initialize, it will fail on repeated calls, but the values from the original call are retained
        NULL,
        -1,
        NULL,
        NULL,
        RPC_C_AUTHN_LEVEL_DEFAULT,
        RPC_C_IMP_LEVEL_IMPERSONATE,
        NULL,
        EOAC_DYNAMIC_CLOAKING,
        NULL);

    if(v4 && (hr = pCLRCreateInstance(CLSID_MH, IID_MH, (LPVOID*)&pMetaHost) == S_OK)){

        if((hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_RHI, (LPVOID*)&pRuntimeInfo)) == S_OK){
            if((hr = pRuntimeInfo->GetInterface(CLSID_RH, IID_RH, (LPVOID*)&pClrRuntimeHost)) == S_OK){;
                hr = pClrRuntimeHost->Start();
                hr = pRuntimeInfo->GetInterface(CLSID_RTH, IID_RTH, (LPVOID *)&result);
            }else{
                log("Failed to get CLR runtime host: 0x%x", hr);
            }
        }else{
            log("Failed to get v4 runtime info: 0x%x", hr);
        }

    }else{
        if( (hr = CoCreateInstance(CLSID_RTH, nullptr, CLSCTX_ALL, IID_RTH,(LPVOID*)&result)) == S_OK){
            hr = result->Start();
        }else{
            log("Failed to get v2 ICorRuntimeHost: 0x%x", hr);
        }
    }
    CoUninitialize();

    return result;
}
CCob commented 1 year ago

Completely missed this. I'll take a look next week!