Expanding on ChunkyIngress - Clippy Goes Rogue (GoClipC2)

GoClipC2: A covert Windows clipboard-based C2 channel for VDI/RDP environments. Bypasses network monitoring with encrypted Base64 messaging.

Expanding on ChunkyIngress - Clippy Goes Rogue (GoClipC2)

I wrote a tool over a year ago when playing around with a VDI-based lab environment. One issue I found was that the clipboard was enabled, but you couldn't copy and paste binaries or scripts into the environment.

What you could do, however, was paste it into text from your browser, which got me thinking, how do I convert what I want into the environment into text? Base64 to the rescue. My initial revision was to punt it across and hope I'd copied it correctly, but knowing human error and the fun that unfolds, I decided to automate it into chunks based on the size of the clipboard buffer.

Unpacking ChunkyIngress

I started writing this blog post over a year ago when I wrote ChunkyIngress. It works simply by taking an input file as a variable, it chunks it up into a select size of chunks and copies said chunks to your clipboard:

if ($mode -eq 'encode') {
        $fileBytes = [IO.File]::ReadAllBytes($inputPath)
        $base64String = [Convert]::ToBase64String($fileBytes)
        $chunkSize = 68KB  # Modify chunk size as needed
        $chunkCount = [math]::Ceiling($base64String.Length / $chunkSize)
        
        Write-Output "Prepare for ${chunkCount} blocks of data"
        Write-Output "Once you have pasted each into your environment, hit return to copy the next block"

        for ($i = 0; $i -lt $chunkCount; $i++) {
            $startIndex = $i * $chunkSize
            $chunk = $base64String.Substring($startIndex, [math]::Min($chunkSize, $base64String.Length - $startIndex))
            Set-Clipboard -Value $chunk
            Write-Output "Chunk $i copied to clipboard. Press Enter to copy the next chunk or Ctrl+C to exit."
            Read-Host
        }

        Write-Output "Total chunks created: $chunkCount"

In the example and default code, it will split it into 68KB chunks, it will do the maths to calculate how many chunks it needs, and then it'll copy the chunks to your clipboard to allow you to paste in the other side to be decoded back to your file using something like:

$BuildMahFile = [Convert]::FromBase64String("")
[IO.File]::WriteAllBytes('ChunkyIngress.7z', $BuildMahFile)

Which is great for infil/exfil when you have PowerShell available, but equally what if we try to use the same clipboard as a command and control channel, there is existing tooling out there that does this already in PowerShell(https://github.com/inguardians/Invoke-Clipboard) but I like creating things and equally saw an opportunity to learn something new and play about with Golang.

Looking at the Clipboard at an API Level

Before diving into how I did the thing, it is important to understand a little bit more about the clipboard, sepcifically on Windows as this was my use case.

At a foundational level the primary APIs in use are listed below and the different information each can obtain is listed.

Opening/Closing:

  • OpenClipboard(hwnd) - Opens clipboard for reading/writing
  • CloseClipboard() - Closes clipboard access
  • EmptyClipboard() - Clears clipboard contents

Data Operations:

  • SetClipboardData(format, handle) - Places data on clipboard
  • GetClipboardData(format) - Retrieves data from clipboard
  • IsClipboardFormatAvailable(format) - Checks if format exists
  • EnumClipboardFormats(format) - Enumerates available formats

Common Formats:

  • CF_TEXT - ANSI text
  • CF_UNICODETEXT - Unicode text
  • CF_BITMAP - Bitmap images
  • CF_DIB - Device-independent bitmap

A Quick OS History Lesson

Not that it is entirely relevant to the blog post but when down a rabbithole, different information gets uncovered unintentionally, so figured I'd share with the class. Here's a quick history lesson on Windows and the differences in the clipboard over the years. Going back to the differences on different windows versions, here's a short breakdown of the flavours of Windows where differences occur.

If you are reading this somewhere other than blog.zsec.uk and the content appears to be a word-for-word copy, be aware that the original article was written by Andy Gill and is hosted there.

Windows XP and Earlier

For those reading that remember the wonders of XP and earlier, you are unlikely to encounter it in an environment but the chances of seeing it are not zero. As earlier operating systems lack modern technologies there are distinct limitations on what can be done.

  • Limited format support: Primarily basic formats like CF_TEXT, CF_BITMAP, CF_DIB - Basically you can't copy unicode very well, it does do it but some things fail sometimes.
  • No clipboard history: Unlike more modern versions of Windows, there's no clipboard history which can be good and bad, you copy something once and it clears the clipboard, done and dusted.

Windows Vista/7

  • Enhanced format support: Introduction of more complex data formats, to build on XP and the ability to copy different text and unicode formats betterer.
  • UAC integration: Clipboard access affected by User Account Control, limitations were introduced to prevent copy paste between privilege levels without UAC intervention, something that helped raise the bar for controls around priv esc.
  • Application isolation: This is interesting in that our C2 channel that I'll dive into later on uses applications written in Go to facilitate clipboard access and coms, W7 was the first time MS introduced application isolation for clipboard and the ability to restrict operations between apps. Something we will encounter as the OSes age and as we're more likely to see Windows 10 and onwards in modern environments, thus something to consider.

Windows 8/8.1

Not a lot of difference in Windows 8, it did bring the new applications called metro apps which introduced additional changes around clipboard but thankfully said metro apps died with Windows 10 introduction so not something we're likely to see much of nowadays.

What it did bring though was stricter controls around copying between different app types and restrictions on what data app developers could prevent being copied out of their apps, for example indroducing controls to prevent copy paste from GUI text boxes with ctrl+c etc.

Windows 10

Windows 10 probably one of the bigger breakthroughs in clipboard stuff, it was the first to bring clipboard history which was classed as a 'major enhancement', it added the ability to retain up to 25 recent clipboard items. In addition to history, it also added the ability to sync clipboard data across devices via a MS account, similar to what Apple do with their unified device(not sure the naming convention) whereby you can copy on a tablet and paste on a laptop as an example if they share the same account.

Also added more metadata which is great for defenders as it also added the ability to integrate timelining to when clipboard activities occurred and logged in a timeline. Something that the blue team will likely be aware of is Windows Timeline (Win + Tab) which shows a visual history of app usage and document activity including browser sessions, Office docs, and some clipboard-assisted workflows.

Windows 11

Building on the major improvements of Windows 10, W11 brought equally its own handful of 'enhancements' by extending the clipboard history, adding better detection of formats and adding the ability to search your clipboard history, all great features for users but equally great features for attackers. It also added more granular controls around privacy of said new features and the ablity to copy and paste emojis too.

Thanks for sticking around through the history lesson, what we've learnt is that the API calls related to Windows 10/11 are gives full control over lots of variables but if you are up against XP/Vista/7/8 then there are restrictions that exist and understanding this and acknowledging it is important.

C2 Exploration

Off the back of ChunkyIngress' success and the ability to make it work, I got thinking about other avenues to leverage the clipboard for a C2 channel, Invoke-Clipboard exists and is based in PowerShell much like ChunkyIngress but I wanted something a bit more extensive and the ability to ingress a binary on disk and have it use 'encrypted' blobs of data in a nice manner with various functions to allow easy copy between systems and add additional functions.

Enter GoClipC2, a fairly extenive proof of concept I put together that serves as a client and a server setup, drop the server on whatever you want to act as your controller and the client binary onto the VDI/RDP hosts you want to establish C2 into.

If you don't have the ability to copy paste binaries in, you can use ChunkyIngress to ingress the client binary then hand off to client server ClipC2. Because it uses clipboard and these are technically local operations, the setup does not have any outbound port connectivity thus raises better opsec, the only considerations are the memory artifacts on the clipboard and the binary on disk in the form of the client.

Building the Tools

I love writing tools and putting together plans for how to expand and build things, I started out with a simple PoC whereby sending commands was do-able but because AD8K and the ability to build out PoCs in Go is fun and relatively straightforward, I started adding features and functions.

The project began as a basic clipboard-based command execution proof-of-concept in the form of ChunkyIngress, but I quickly realised that PowerShell is great, but Go is far more versatile. The project has stepped through the motions of the various versions:

v0.0.1: Core Communication

I started out intending to monitor the clipboard using a 500ms polling loop for clipboard changes to act as C2.

Quickly realising that 500ms gets VERY noisy, so flipped it up to a few seconds, also added encryption for communications as defender was getting sad with large B64 blobs passing between an RDP session and host, so I added AES-GCM wrapper for all traffic to ensure it wasn't easily decrypted using a pre-shared key.

As these things go, lots of data started flowing and I had more and more ideas of things to add so I built out a JSON-based message structure with unique IDs to allow the server to differentiate different clients and coms. Finally because the clipboard is always active but different things can poll to it, I added a heartbeat functionality.

Andy Gill is the original writer of this piece, first published at https://blog.zsec.uk. Should you encounter identical content elsewhere, it's likely been copied without permission.

v0.0.2: Actual C2 Actions

Getting core coms working, probably took me the longest with the help of a mate of mine who is a Go dev as a full time job(granted not adversarial at all, so he did have questions as to why I was wanting todo specific actions!) as I had a working PoC but things kept breaking. Stackoverflow didn't have all the answers.

So the published version currently supports the following C2 actions:

  • VDI/RDP Environment Detection - Multi-vector detection using processes, it's not the smartest in the world but it works as a PoC.
  • File Upload/Download - Chunked transfer architecture with progress tracking and error recovery
  • Command Queuing System - I wanted to test out running multiple commands at once and show them executing on the endpoint so I setup queuing.
  • Background Persistence Control - This is more to run the process in the background rather than traditional persistence, it can be toggled on or off using the persist CLIENTNAME on or off which will show or hide the window from the current session. Certain functions don't work when it's in persist mode though.
  • Sleep/wake functionality - Mimicking the sleep functions of legit beacon operation with configurable dormancy to allow it to be not so noisy (it currently works about 60% of the time, every time).
  • Process List Enumeration - This runs tasklist under the hood but with some modifications to hide the window execution:
cmd := exec.Command("tasklist", "/fo", "table", "/v")
    cmd.SysProcAttr = &syscall.SysProcAttr{
        HideWindow: true,  
    }
  • Heartbeat Customisation - Adaptive heartbeat system with health monitoring

How It Works

So as previously mentioned, the C2 channel operates over the Windows Clipboard (I have been playing about with other OSes and may update tooling). The ASCII diagram below details how coms flow:

At the server's core there are several message types that it sends to the client binary in order inact specific commands or operations on the client side:

// Message types
const (
	MSG_HEARTBEAT     = "HB"
	MSG_COMMAND       = "CMD"
	MSG_RESPONSE      = "RESP"
	MSG_DATA          = "DATA"
	MSG_ERROR         = "ERR"
	MSG_SHELL         = "SHELL"
	MSG_SLEEP         = "SLEEP"
	MSG_WAKE          = "WAKE"
	MSG_SET_HEARTBEAT = "SET_HB"
	MSG_STATUS        = "STATUS"
	MSG_QUEUE         = "QUEUE"
	MSG_QUEUE_STATUS  = "QUEUE_STATUS"
	MSG_DOWNLOAD      = "DOWNLOAD"
	MSG_UPLOAD        = "UPLOAD"
	MSG_FILE_CHUNK    = "FILE_CHUNK"
	MSG_FILE_COMPLETE = "FILE_COMPLETE"
	MSG_FILE_ERROR    = "FILE_ERROR"
	MSG_PERSIST       = "PERSIST"
	MSG_ENV_INFO      = "ENV_INFO"
	MSG_SCREENSHOT    = "SCREENSHOT"
	MSG_KEYLOG        = "KEYLOG"
	MSG_PROC_LIST     = "PROC_LIST"
)

The C2 messaging system is built around communication patterns that handle everything from basic command execution to chunked file transfers. Core function message types like MSG_HEARTBEAT maintain the connection, with MSG_COMMAND and MSG_RESPONSE handling command execution flows on the endpoint. File operations use a chunked approach with MSG_FILE_CHUNK and MSG_FILE_COMPLETE messages to ensure reliable transfers over the clipboard medium.

Client state management is handled through messages such as MSG_SLEEP and MSG_WAKE for operational security, whilst MSG_QUEUE enables batch command processing. The system also supports advanced reconnaissance through MSG_ENV_INFO andMSG_PROC_LIST.

Example Output

All messages are sent as encrypted Base64 blobs prefixed with SYUPD to blend in with legitimate system clipboard activity. This encoding ensures that casual clipboard monitoring or automated security tools are unlikely to detect the C2 traffic (unless specifically hunting for it!), as the messages appear as benign system update data rather than structured command and control communications. Here's an example block:

SYSUPD:cwOXba5E9tldKP8xwdLy7LatBk6IMTs26u/i3JUTK3PanCvGivcvw80CJSCh6jkTymGg+qzzRxsB3E+W9Xh7uJFOSDuIk+AVRmOwt5e+NC2T5HRdz48CjeFDs4r+1RIFNXtu0X1+UsZDhxcEXBr/R2piJIai4x9jfixOavp+W/UD6es4bLJgxXbvwh6DNjTHuaEbbCj2jtjBva+Q09UXENTOwa1ftJKBxt3CpQzb

Running the server in my lab I have the server running on WS01 and the client on DC01:

Executing the server binary starts the server and giving the client an ID it'll connect in as shown:

Sending commands with send blog-demo whoami and send blog-demo dir get the output on the server side:

Other operations that are nice to have like envinfo blog-demo can be run to pull environmental information from the host:

Detection Opportunities

As I always try to include detection opportunities for my tools and research here's some Sysmon, Yara and Elastic queries:

Sysmon Event IDs for GoClipC2 Detection

Event ID 1 (Process Creation):

  • Detects initial GoClipC2 client execution
  • Captures hidden window executions (HideWindow flag)
  • Identifies rapid spawning of cmd.exe/powershell.exe child processes
  • Monitors processes executing from temp directories

Event ID 7 (Image/DLL Load):

  • Detects user32.dll loading for clipboard API access
  • Identifies kernel32.dll loading by non-GUI processes
  • Captures clipboard-related library loads by suspicious executables

Event ID 10 (Process Access):

  • Identifies GoClipC2 accessing other processes for reconnaissance
  • Detects environment detection attempts
  • Monitors cross-process clipboard service interactions
  • Captures potential privilege escalation activities

Event ID 11 (File Create):

  • Monitors file transfers via clipboard chunking
  • Detects temporary files created during operations
  • Tracks downloaded files with "downloaded_" prefix

In addition to sysmon here are some sigma rules (I'm not a detection engineer so appologies if they're crap!): https://github.com/ZephrFish/GoClipC2/tree/main/detection

Future Plans

I have a semi-functioning port of COFFLoader that works for inprocess exection that I might release as an addon to GoClipC2. Also may look to see what else can be done from an anti-forensics perspective to improve the operaitonal security of execution using more native Go functions rather than child processes.