Getting Clipboard History in C++

Earlier today, or yesterday, I can't remember because my sense of time is so distorted, I saw a post from @inversecos about Windows clipboard forensics. I thought it was really interesting (original post: https://x.com/inversecos/status/1946180118067740882)

In summary, some nerd made a post about malware "pulling" data from the clipboard. Some malware (such as Redline or Lumma Stealer) will read whatever data is currently housed in the Windows clipboard (CTRL + C thingy).

What I didn't know was that Windows 10+ has a feature where clipboard history is saved (you have to enable it though). She shared her article about it (https://www.inversecos.com/2022/05/how-to-perform-clipboard-forensics.html).

She commented creative Red Teamers could (probably) abuse this. She is correct. That is cool and badass and I want to abuse it.

When I looked more into it the only APIs for the Clipboard history was mostly WinRT and C# junk. I saw some broken and janky code discussions and/or posts about it on AutoIT forums. Windows software engineer Raymond Chen also wrote an article about it (https://devblogs.microsoft.com/oldnewthing/20230302-00/?p=107889).

But, I don't want to do anything in AutoIT. I also don't want to use WinRT like Raymond Chen (it's yucky and you have to do a bunch of weird stuff). I also don't like coding in C#. So what do we do?

Let's write it in C++ (but in a C style syntax) and use the WINAPI (we're not cowards). This stuff was poorly documented, or not at all, and required me reading the Windows SDK headers and screaming at my computer for about 120 minutes. The code can basically be copy-pasted into Visual Studio.

For reasons I don't understand (and didn't bother to look into) the Clipboard manager only allows me to pull the last 25 entries. You figure it out (unless that's how it's supposed to work).

You could also setup events to monitor clipboard changes to monitor what's being copied in-real-time if you care enough.

Okay, I'm going to go now.

Love you -smelly

#include <windows.h>
#include <roapi.h>
#include <stdio.h>

#include <windows.applicationmodel.datatransfer.h>
#include <windows.foundation.h>

using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::ApplicationModel::DataTransfer;

#pragma comment(lib, "runtimeobject.lib")

MIDL_INTERFACE("4da6d9df-4e7e-50ff-93d5-dcb5dfd1c6de")
IAsyncOperation_HSTRING : public IInspectable
{
public:
    virtual HRESULT STDMETHODCALLTYPE put_Completed(IInspectable * handler) = 0;
    virtual HRESULT STDMETHODCALLTYPE get_Completed(IInspectable** handler) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetResults(HSTRING* result) = 0;
};

SIZE_T StringLengthW(_In_ LPCWSTR String)
{
    LPCWSTR String2;

    for (String2 = String; *String2; ++String2);

    return (String2 - String);
}

HRESULT RoWalkClipboardManagerObject(__FIVectorView_1_Windows__CApplicationModel__CDataTransfer__CClipboardHistoryItem* Items)
{
    HRESULT Result = S_OK;
    IClipboardHistoryItem* Object = NULL;
    UINT32 Count = 0;
    IDataPackageView* Data = NULL;
    __FIAsyncOperation_1_HSTRING* TextObject = NULL;
    HSTRING Text = NULL;
    HSTRING UniqueId = NULL;

    Result = Items->get_Size(&Count);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    for (UINT32 i = 0; i < Count; i++)
    {
        Result = Items->GetAt(i, &Object);
        if (SUCCEEDED(Result))
        {
            Result = Object->get_Id(&UniqueId);
            if (SUCCEEDED(Result))
            {
                Result = Object->get_Content(&Data);
                if (SUCCEEDED(Result))
                {
                    Result = Data->GetTextAsync(&TextObject);
                    if (SUCCEEDED(Result))
                    {
                        Result = TextObject->GetResults(&Text);
                        if (SUCCEEDED(Result))
                            printf("[+] Count: %d Item ID: %ws\r\nText Buffer: %ws\r\n", i, WindowsGetStringRawBuffer(UniqueId, NULL), WindowsGetStringRawBuffer(Text, NULL));
                    }
                }
            }
        }

        if(Text)
            WindowsDeleteString(Text);

        if (TextObject)
            TextObject->Release();

        if (Data)
            Data->Release();

        if (Object)
            Object->Release();

        if (UniqueId)
            WindowsDeleteString(UniqueId);
    }

    Object = NULL;
    UniqueId = NULL;
    Data = NULL;
    TextObject = NULL;

EXIT_ROUTINE:

    if (Text)
        WindowsDeleteString(Text);

    if (TextObject)
        TextObject->Release();

    if (Data)
        Data->Release();

    if (Object)
        Object->Release();

    if (UniqueId)
        WindowsDeleteString(UniqueId);

    return Result;
}

INT main(VOID) 
{
    IClipboardStatics2* Statics = NULL;
    IInspectable* ClipboardFactory = NULL;
    HRESULT Result = S_OK;
    HSTRING ClipboardStringObject = NULL;
    __FIAsyncOperation_1_Windows__CApplicationModel__CDataTransfer__CClipboardHistoryItemsResult* AsyncOp = NULL;
    __FIVectorView_1_Windows__CApplicationModel__CDataTransfer__CClipboardHistoryItem* Items = NULL;
    IClipboardHistoryItemsResult* HistoryResults = NULL;

    Result = RoInitialize(RO_INIT_SINGLETHREADED);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    Result = WindowsCreateString(L"Windows.ApplicationModel.DataTransfer.Clipboard", (UINT32)StringLengthW(L"Windows.ApplicationModel.DataTransfer.Clipboard"), &ClipboardStringObject);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

#pragma warning(push)
#pragma warning(disable: 6387)
    Result = RoGetActivationFactory(ClipboardStringObject, __uuidof(IClipboardStatics2), (PVOID*)&ClipboardFactory);
#pragma warning(pop)
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    Result = ClipboardFactory->QueryInterface(&Statics);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    Result = Statics->GetHistoryItemsAsync(&AsyncOp);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    Result = AsyncOp->GetResults(&HistoryResults);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    Result = HistoryResults->get_Items(&Items);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

    Result = RoWalkClipboardManagerObject(Items);
    if (!SUCCEEDED(Result))
        goto EXIT_ROUTINE;

EXIT_ROUTINE:

    if (ClipboardFactory)
        WindowsDeleteString(ClipboardStringObject);

    if (ClipboardFactory)
        ClipboardFactory->Release();

    if (Statics)
        Statics->Release();

    if (AsyncOp)
        AsyncOp->Release();

    if (HistoryResults)
        HistoryResults->Release();

    if (Items)
        Items->Release();

    return Result;
}

Last updated