利用Windows剪贴板构建隐蔽C2通道:GoClipC2技术解析

本文详细介绍了基于Windows剪贴板的隐蔽C2工具GoClipC2,涵盖Base64编码、AES-GCM加密、API调用等技术细节,适用于VDI/RDP环境下的数据渗透与命令控制。

Clippy Goes Rogue (GoClipC2)

关于Andy Gill/ZephrFish

  • 预注册我的课程
  • 我的书籍
  • LTR101文章
  • 摄影博客
  • 预注册我的课程

特色内容

扩展ChunkyIngress - Clippy Goes Rogue (GoClipC2)

GoClipC2:一种基于Windows剪贴板的隐蔽C2通道,适用于VDI/RDP环境。通过加密的Base64消息传递绕过网络监控。

Andy Gill
2025年6月15日
• 10分钟阅读

一年多前,我在玩VDI实验室环境时编写了一个工具。我发现的一个问题是剪贴板已启用,但无法将二进制文件或脚本复制粘贴到环境中。然而,你可以将其从浏览器粘贴为文本,这让我思考如何将所需内容转换为文本?Base64来救援。我的初始版本是将其分段发送,并希望我复制正确,但考虑到人为错误和随之而来的乐趣,我决定根据剪贴板缓冲区的大小自动分块。

解包ChunkyIngress

我一年多前在编写ChunkyIngress时开始写这篇博客文章。它通过将输入文件作为变量,将其分块为选定大小的块,并将所述块复制到剪贴板:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
if ($mode -eq 'encode') {
    $fileBytes = [IO.File]::ReadAllBytes($inputPath)
    $base64String = [Convert]::ToBase64String($fileBytes)
    $chunkSize = 68KB  # 根据需要修改块大小
    $chunkCount = [math]::Ceiling($base64String.Length / $chunkSize)
    
    Write-Output "准备${chunkCount}个数据块"
    Write-Output "将每个块粘贴到环境后,按回车键复制下一个块"

    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 "块$i已复制到剪贴板。按回车键复制下一个块或Ctrl+C退出。"
        Read-Host
    }

    Write-Output "总块数创建:$chunkCount"
}

在示例和默认代码中,它将文件分成68KB的块,计算所需块数,然后将块复制到剪贴板,允许你在另一侧粘贴并使用类似以下代码解码回文件:

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

当你有PowerShell可用时,这对于渗透/渗出非常有用,但同样,如果我们尝试使用相同的剪贴板作为命令和控制通道呢?已有现有的PowerShell工具(https://github.com/inguardians/Invoke-Clipboard),但我喜欢创造东西,并同样看到了学习新东西和玩转Golang的机会。

在API级别查看剪贴板

在深入探讨如何实现之前,了解剪贴板的更多信息非常重要,特别是在Windows上,因为这是我的用例。

在基础级别,使用的主要API如下所列,每个API可以获取的不同信息也列出。

打开/关闭:

  • OpenClipboard(hwnd) - 打开剪贴板进行读/写
  • CloseClipboard() - 关闭剪贴板访问
  • EmptyClipboard() - 清除剪贴板内容

数据操作:

  • SetClipboardData(format, handle) - 将数据放置在剪贴板上
  • GetClipboardData(format) - 从剪贴板检索数据
  • IsClipboardFormatAvailable(format) - 检查格式是否存在
  • EnumClipboardFormats(format) - 枚举可用格式

常见格式:

  • CF_TEXT - ANSI文本
  • CF_UNICODETEXT - Unicode文本
  • CF_BITMAP - 位图图像
  • CF_DIB - 设备无关位图

快速操作系统历史课

虽然与博客文章不完全相关,但在深入兔子洞时,无意中发现了不同的信息,所以我想与大家分享。以下是Windows及其剪贴板多年来差异的快速历史课。

如果你在blog.zsec.uk以外的其他地方阅读此内容,并且内容似乎是逐字复制,请注意原始文章由Andy Gill撰写并托管在那里。

Windows XP及更早版本 对于记得XP及更早版本奇迹的读者,你在环境中不太可能遇到它,但看到它的机会不是零。由于早期操作系统缺乏现代技术,可以做的事情有明确的限制。

  • 有限的格式支持:主要是基本格式,如CF_TEXT、CF_BITMAP、CF_DIB - 基本上你不能很好地复制unicode,它确实可以,但有时某些东西会失败。
  • 无剪贴板历史记录:与更现代的Windows版本不同,没有剪贴板历史记录,这有好有坏,你复制一次,它清除剪贴板,完成。

Windows Vista/7

  • 增强的格式支持:引入更复杂的数据格式,以构建在XP之上,并更好地复制不同的文本和unicode格式。
  • UAC集成:剪贴板访问受用户帐户控制影响,引入了限制,以防止在没有UAC干预的情况下在权限级别之间复制粘贴,这有助于提高围绕权限提升的控制。
  • 应用程序隔离:这很有趣,因为我们稍后将深入探讨的C2通道使用用Go编写的应用程序来促进剪贴板访问和通信,W7是MS首次引入应用程序隔离用于剪贴板以及限制应用程序之间操作的能力。随着操作系统老化,我们更可能在现代环境中看到Windows 10及更高版本,因此需要考虑这一点。

Windows 8/8.1 Windows 8没有太多差异,它确实带来了称为metro应用程序的新应用程序,这引入了围绕剪贴板的额外更改,但幸运的是,所述metro应用程序随着Windows 10的引入而死亡,因此我们现在不太可能看到太多。但它确实带来了在不同应用程序类型之间复制的更严格控制,以及应用程序开发人员可以防止从其应用程序中复制哪些数据的限制,例如引入控制以防止从GUI文本框使用ctrl+c等复制粘贴。

Windows 10 Windows 10可能是剪贴板东西的较大突破之一,它是第一个带来剪贴板历史的,被归类为“重大增强”,它增加了保留最多25个最近剪贴板项目的能力。除了历史记录,它还增加了通过MS帐户跨设备同步剪贴板数据的能力,类似于Apple与其统一设备(不确定命名约定)所做的,例如,如果它们共享相同的帐户,你可以在平板电脑上复制并在笔记本电脑上粘贴。 还添加了更多元数据,这对防御者来说很棒,因为它还增加了将时间线集成到剪贴板活动发生并记录在时间线中的能力。蓝队可能意识到的是Windows时间线(Win + Tab),它显示应用程序使用和文档活动的视觉历史记录,包括浏览器会话、Office文档和一些剪贴板辅助的工作流程。

Windows 11 在Windows 10的重大改进基础上,W11通过扩展剪贴板历史记录、添加更好的格式检测以及添加搜索剪贴板历史记录的能力,带来了自己的一把“增强功能”,所有这些对用户来说都是很棒的功能,但对攻击者来说同样很棒。它还围绕所述新功能的隐私和复制粘贴表情符号的能力添加了更细粒度的控制。

感谢你坚持听完历史课,我们学到的是,与Windows 10/11相关的API调用可以完全控制许多变量,但如果你面对XP/Vista/7/8,则存在限制,理解并承认这一点很重要。

C2探索

在ChunkyIngress成功并使其工作之后,我开始思考利用剪贴板进行C2通道的其他途径,Invoke-Clipboard存在并且基于PowerShell,很像ChunkyIngress,但我想要更广泛的东西,以及将磁盘上的二进制文件渗透并以良好的方式使用“加密”数据块的能力,具有各种功能,允许在系统之间轻松复制并添加额外功能。

进入GoClipC2,我组装的一个相当广泛的Proof of Concept,它充当客户端和服务器设置,将服务器放在任何你想充当控制器的东西上,并将客户端二进制文件放在你想要建立C2的VDI/RDP主机上。如果你没有复制粘贴二进制文件的能力,你可以使用ChunkyIngress渗透客户端二进制文件,然后交给客户端服务器ClipC2。因为它使用剪贴板,并且这些在技术上是本地操作,所以设置没有任何出站端口连接,从而提高了操作安全性,唯一的考虑是剪贴板上的内存工件和磁盘上的客户端二进制文件。

构建工具

我喜欢编写工具并制定如何扩展和构建事物的计划,我开始了一个简单的PoC,其中发送命令是可行的,但因为AD8K并且在Go中构建PoC很有趣且相对简单,我开始添加功能和函数。

该项目开始时是ChunkyIngress形式的基本基于剪贴板的命令执行Proof of Concept,但我很快意识到PowerShell很棒,但Go更通用。该项目已经经历了各个版本的步骤:

v0.0.1:核心通信 我开始打算使用500毫秒轮询循环监控剪贴板更改以充当C2。 很快意识到500毫秒非常嘈杂,所以将其翻转到几秒钟,还为通信添加了加密,因为防御者对RDP会话和主机之间传递的大B64块感到悲伤,所以我为所有流量添加了AES-GCM包装器,以确保它不容易使用预共享密钥解密。 随着这些事情的发展,大量数据开始流动,我有越来越多的想法要添加,所以我构建了一个基于JSON的消息结构,具有唯一ID,允许服务器区分不同的客户端和通信。最后,因为剪贴板始终处于活动状态,但不同的东西可以轮询它,我添加了心跳功能。Andy Gill是这篇文章的原始作者,首次发布于https://blog.zsec.uk。如果你在其他地方遇到相同的内容,很可能是在未经许可的情况下复制的。

v0.0.2:实际C2操作 让核心通信工作,可能花了我最长的时间,在我一位全职Go开发的朋友的帮助下(当然完全不是对抗性的,所以他确实有问题为什么我想做特定的动作!),因为我有工作的PoC,但事情不断破裂。Stackoverflow没有所有答案。所以发布的版本目前支持以下C2操作:

  • VDI/RDP环境检测 - 使用进程的多向量检测,它不是世界上最聪明的,但作为PoC工作。
  • 文件上传/下载 - 具有进度跟踪和错误恢复的分块传输架构
  • 命令队列系统 - 我想测试一次运行多个命令并在端点上显示它们执行,所以我设置了队列。
  • 后台持久性控制 - 这更多是为了在后台运行进程而不是传统持久性,可以使用persist CLIENTNAME on或off切换,这将显示或隐藏当前会话的窗口。不过,某些功能在持久模式下不起作用。
  • 睡眠/唤醒功能 - 模拟合法信标操作的睡眠功能,具有可配置的休眠,以使其不那么嘈杂(目前大约60%的时间工作,每次)。
  • 进程列表枚举 - 这在底层运行tasklist,但进行了一些修改以隐藏窗口执行:
1
2
3
4
cmd := exec.Command("tasklist", "/fo", "table", "/v")
cmd.SysProcAttr = &syscall.SysProcAttr{
    HideWindow: true,  
}
  • 心跳定制 - 具有健康监控的自适应心跳系统

工作原理

如前所述,C2通道通过Windows剪贴板操作(我一直在玩其他操作系统,并可能更新工具)。下面的ASCII图详细说明了通信流程:

在服务器的核心,有几种消息类型,它发送到客户端二进制文件,以在客户端执行特定命令或操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 消息类型
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"
)

C2消息系统围绕通信模式构建,处理从基本命令执行到分块文件传输的所有内容。核心功能消息类型如MSG_HEARTBEAT维护连接,MSG_COMMAND和MSG_RESPONSE处理端点上的命令执行流。文件操作使用分块方法,带有MSG_FILE_CHUNK和MSG_FILE_COMPLETE消息,以确保在剪贴板介质上的可靠传输。

客户端状态管理通过消息如MSG_SLEEP和MSG_WAKE用于操作安全,而MSG_QUEUE启用批处理命令处理。系统还通过MSG_ENV_INFO和MSG_PROC_LIST支持高级侦察。

示例输出 所有消息都作为加密的Base64块发送,前缀为SYUPD,以与合法系统剪贴板活动混合。这种编码确保临时剪贴板监控或自动化安全工具不太可能检测到C2流量(除非专门狩猎它!),因为消息显示为良性系统更新数据而不是结构化的命令和控制通信。以下是一个示例块:

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

在我的实验室中运行服务器,服务器在WS01上运行,客户端在DC01上:

执行服务器二进制文件启动服务器,并给客户端一个ID,它将连接如下:

使用send blog-demo whoami和send blog-demo dir发送命令在服务器端获取输出:

其他操作,如envinfo blog-demo,可以运行以从主机提取环境信息:

检测机会

正如我总是尝试为我的工具和研究包括检测机会,这里有一些Sysmon、Yara和Elastic查询:

Sysmon事件ID用于GoClipC2检测

  • 事件ID 1(进程创建):
    • 检测初始GoClipC2客户端执行
    • 捕获隐藏窗口执行(HideWindow标志)
    • 识别cmd.exe/powershell.exe子进程的快速生成
    • 监控从临时目录执行的进程
  • 事件ID 7(图像/DLL加载):
    • 检测user32.dll加载用于剪贴板API访问
    • 识别非GUI进程的kernel32.dll加载
    • 捕获可疑可执行文件的剪贴板相关库加载
  • 事件ID 10(进程访问):
    • 识别GoClipC2访问其他进程进行侦察
    • 检测环境检测尝试
    • 监控跨进程剪贴板服务交互
    • 捕获潜在的权限提升活动
  • 事件ID 11(文件创建):
    • 监控通过剪贴板分块的文件传输
    • 检测操作期间创建的临时文件
    • 跟踪带有“downloaded_”前缀的下载文件

除了sysmon,这里还有一些sigma规则(我不是检测工程师,所以如果它们很烂,抱歉!):https://github.com/ZephrFish/GoClipC2/tree/main/detection

未来计划

我有一个半功能的COFFLoader端口,适用于进程内执行,我可能作为GoClipC2的附加组件发布。还可能从反取证角度看看还能做些什么,以使用更多原生Go函数而不是子进程来提高执行的操作安全性。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计