Windows剪贴板历史记录逆向分析与数据提取技术研究
字数 3822 2025-11-05 23:45:18
Windows剪贴板历史记录逆向分析与数据提取技术教学文档
1. 概述与背景知识
1.1 剪贴板历史功能
从 Windows 10 开始,微软引入了剪贴板历史记录功能。用户可以通过按下 Win + V 组合键调出历史记录面板。该功能默认是禁用的,需要手动在系统设置中或通过修改注册表开启。
- 注册表路径:
HKEY_CURRENT_USER\Software\Microsoft\Clipboard\EnableClipboardHistory - 特性:
- 历史记录上限为 25 条。
- 旧的条目会被新的条目覆盖,除非手动固定(Pin) 某个条目。
- 计算机关机重启后,未固定的条目会丢失。
1.2 技术目标
本文的教学目标是:通过逆向工程手段,了解Windows存储和管理剪贴板历史记录的机制,并最终实现从目标进程(如 svchost.exe)的内存中提取出完整的剪贴板历史文本数据。这在渗透测试或取证分析中(后渗透阶段)具有实际意义。
1.3 核心组件
逆向分析主要围绕以下几个系统模块展开:
CBDHSvc.dll: 负责剪贴板历史记录相关服务的核心动态链接库。它需要两个条件同时满足才能生效:- 结构64: 对应上述的
EnableClipboardHistory注册表项。 - 结构66: 对应组策略设置,默认开启。
- 结构64: 对应上述的
Windows.ApplicationModel.DataTransfer.dll: 提供了Windows运行时(WinRT)的剪贴板操作API(如Clipboard.GetContent())。ole32.dll/win32kfull.sys: 传统的Windows剪贴板API(如OleGetClipboard,GetClipboardData)最终会通过这些模块进入内核态处理。
2. 逆向分析过程
作者尝试了多种逆向路径,以下是关键的分析步骤和发现。
2.1 传统剪贴板API路径分析
首先,作者从传统的 GetClipboardData 函数开始逆向。
- 调用链:
ole32.dll!OleGetClipboard-> ... ->win32kfull.sys!xxxGetClipboardData(内核态)。 - 内核处理: 在内核函数
xxxGetClipboardData中,系统会遍历一个CLIP结构数组,通过FindClipFormat函数找到请求的剪贴板格式(如CF_UNICODETEXT,即Unicode文本)。 - 数据返回用户态: 内核函数返回一个全局内存句柄(
GlobalHandle)。回到用户态后,GetClipboardData会调用CreateLocalMemHandle来创建一个指向相同数据的新句柄,最终通过NtUserCreateLocalMemHandle将数据复制到调用进程的内存空间。
- 关键发现: 虽然通过此路径可以获取当前剪贴板中的内容,但无法直接获取历史记录列表。因为历史记录由
CBDHSvc.dll服务单独管理。
2.2 聚焦剪贴板历史服务(CBDHSvc.dll)
既然传统API行不通,目标转向了负责历史记录的 CBDHSvc.dll 服务,该服务运行在一个 svchost.exe 进程中。
-
动态分析(使用Cheat Engine):
- 步骤1: 复制一段独特的字符串到剪贴板。
- 步骤2: 使用Cheat Engine附加到承载
CBDHSvc.dll的svchost.exe进程。 - 步骤3: 在进程中搜索刚才复制的Unicode字符串。
- 步骤4: 按下
Win + V调出剪贴板历史面板,此时CE会检测到内存访问,并定位到访问该字符串的函数,例如CreateHStringFromUnicodeHGlobal。这个函数的第一个参数hMem就是包含文本数据的全局内存句柄。
-
静态分析(使用IDA Pro):
- 在
Windows.ApplicationModel.DataTransfer.dll中,可以找到与历史记录相关的函数,如:Windows::ApplicationModel::Internal::DataTransfer::ClipboardHistoryBuffer::GetHistoryItemCountWindows::ApplicationModel::Internal::DataTransfer::ClipboardHistoryBuffer::GetInMemoryItemsCount
- 关键数据结构:
GetHistoryItemCount函数的逻辑暗示,历史记录项被存储在一个指针数组中。数组的大小可以通过(EndAddress - StartAddress) / sizeof(LPVOID)计算得出。这个数组的每个元素都指向一个复杂的数据结构,描述了单个历史记录项。
- 在
-
突破:定位文本数据
由于数据结构层层嵌套,直接逆向整个结构非常困难。作者转换思路,通过CE找到的内存地址,在内存中直接观察其周边数据,发现了一个规律性的结构。使用ReClass等内存结构分析工具,可以勾勒出一个大致的轮廓。- 核心识别特征: 如果一块内存的开头是一个指向
CUnicodeTextFormat类的虚函数表(vtable) 的指针,那么其附近几乎肯定存在我们需要的Unicode文本数据。 - 定位vtable:
CUnicodeTextFormat的vtable地址位于Windows.ApplicationModel.DataTransfer.dll模块的.rdata(只读数据)段中。这是一个稳定的特征,可以用来扫描内存。
- 核心识别特征: 如果一块内存的开头是一个指向
3. 数据提取方案与实现步骤
基于以上分析,作者提出了一种可行的数据提取方案。该方案不依赖于未公开的API,而是直接扫描进程内存来定位数据。
整体步骤如下:
-
定位目标进程:
- 获取运行着
CBDHSvc.dll的svchost.exe的进程ID(PID)。可以通过遍历进程列表、检查其加载的模块是否包含CBDHSvc.dll和Windows.ApplicationModel.DataTransfer.dll来实现。
- 获取运行着
-
打开进程并获取模块信息:
- 使用
OpenProcess函数以PROCESS_QUERY_INFORMATION | PROCESS_VM_READ权限打开目标svchost.exe进程。 - 枚举进程加载的模块,找到
Windows.ApplicationModel.DataTransfer.dll的基地址(Base Address)和大小。
- 使用
-
定位特征值(vtable地址):
- 从
Windows.ApplicationModel.DataTransfer.dll的镜像中,解析出CUnicodeTextFormat类的虚函数表地址。这个地址是相对固定的,但可能随Windows版本更新而改变。因此,需要为每个主要的Windows版本维护一个偏移量,或者通过特征码扫描在.rdata段中动态定位。
- 从
-
扫描进程内存:
- 遍历目标进程的整个内存空间,查找所有具有
PAGE_READWRITE (RW)权限且已提交(Committed)的内存区域。 - 在这些内存区域中,搜索步骤3中找到的 vtable指针值。因为每个
CUnicodeTextFormat对象实例的第一个成员就是指向其vtable的指针。
- 遍历目标进程的整个内存空间,查找所有具有
-
解析数据结构并提取文本:
- 一旦找到vtable指针,就找到了一个
CUnicodeTextFormat对象的起始地址。 - 根据逆向分析出的结构偏移(例如,通过ReClass分析确定),从对象中提取出存储的Unicode文本字符串。通常,文本数据会位于对象指针之后的一个固定偏移处。
- 重复此过程,直到扫描完所有内存。找到的所有文本即对应剪贴板历史记录中的条目。
- 一旦找到vtable指针,就找到了一个
4. 总结与关键点
- 核心原理: Windows剪贴板历史记录由
CBDHSvc.dll服务管理,其数据存储在服务进程的内存中,通过复杂的C++对象结构表示。 - 技术路线: 放弃了传统的剪贴板API和完全逆向数据结构,采用了一种“特征码扫描”的实用主义方法。通过识别内存中特定的类vtable指针来定位文本数据对象。
- 关键依赖: 方案的准确性依赖于
CUnicodeTextFormat的vtable地址。这是一个潜在的版本依赖点,需要为不同的系统版本更新偏移量。 - 效果: 该方法能够有效地从内存中提取出完整的、即时的剪贴板历史文本内容,即使某些条目当前并未在
Win+V面板中显示(但仍在25条限制内)。
注意事项:
- 此技术涉及对系统进程内存的直接读取,可能被安全软件视为可疑行为。
- 本文档基于逆向工程分析,所涉及的内核结构和未文档化API可能随系统更新而变化。
- 实际代码实现时,需处理内存访问异常、不同系统版本的适配等细节。