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: 负责剪贴板历史记录相关服务的核心动态链接库。它需要两个条件同时满足才能生效:
    1. 结构64: 对应上述的 EnableClipboardHistory 注册表项。
    2. 结构66: 对应组策略设置,默认开启。
  • Windows.ApplicationModel.DataTransfer.dll: 提供了Windows运行时(WinRT)的剪贴板操作API(如 Clipboard.GetContent())。
  • ole32.dll / win32kfull.sys: 传统的Windows剪贴板API(如 OleGetClipboard, GetClipboardData)最终会通过这些模块进入内核态处理。

2. 逆向分析过程

作者尝试了多种逆向路径,以下是关键的分析步骤和发现。

2.1 传统剪贴板API路径分析
首先,作者从传统的 GetClipboardData 函数开始逆向。

  1. 调用链ole32.dll!OleGetClipboard -> ... -> win32kfull.sys!xxxGetClipboardData(内核态)。
  2. 内核处理: 在内核函数 xxxGetClipboardData 中,系统会遍历一个 CLIP 结构数组,通过 FindClipFormat 函数找到请求的剪贴板格式(如 CF_UNICODETEXT,即Unicode文本)。
  3. 数据返回用户态: 内核函数返回一个全局内存句柄(GlobalHandle)。回到用户态后,GetClipboardData 会调用 CreateLocalMemHandle 来创建一个指向相同数据的新句柄,最终通过 NtUserCreateLocalMemHandle 将数据复制到调用进程的内存空间。
  • 关键发现: 虽然通过此路径可以获取当前剪贴板中的内容,但无法直接获取历史记录列表。因为历史记录由 CBDHSvc.dll 服务单独管理。

2.2 聚焦剪贴板历史服务(CBDHSvc.dll)
既然传统API行不通,目标转向了负责历史记录的 CBDHSvc.dll 服务,该服务运行在一个 svchost.exe 进程中。

  1. 动态分析(使用Cheat Engine)

    • 步骤1: 复制一段独特的字符串到剪贴板。
    • 步骤2: 使用Cheat Engine附加到承载 CBDHSvc.dllsvchost.exe 进程。
    • 步骤3: 在进程中搜索刚才复制的Unicode字符串。
    • 步骤4: 按下 Win + V 调出剪贴板历史面板,此时CE会检测到内存访问,并定位到访问该字符串的函数,例如 CreateHStringFromUnicodeHGlobal。这个函数的第一个参数 hMem 就是包含文本数据的全局内存句柄。
  2. 静态分析(使用IDA Pro)

    • Windows.ApplicationModel.DataTransfer.dll 中,可以找到与历史记录相关的函数,如:
      • Windows::ApplicationModel::Internal::DataTransfer::ClipboardHistoryBuffer::GetHistoryItemCount
      • Windows::ApplicationModel::Internal::DataTransfer::ClipboardHistoryBuffer::GetInMemoryItemsCount
    • 关键数据结构GetHistoryItemCount 函数的逻辑暗示,历史记录项被存储在一个指针数组中。数组的大小可以通过 (EndAddress - StartAddress) / sizeof(LPVOID) 计算得出。这个数组的每个元素都指向一个复杂的数据结构,描述了单个历史记录项。
  3. 突破:定位文本数据
    由于数据结构层层嵌套,直接逆向整个结构非常困难。作者转换思路,通过CE找到的内存地址,在内存中直接观察其周边数据,发现了一个规律性的结构。使用ReClass等内存结构分析工具,可以勾勒出一个大致的轮廓。

    • 核心识别特征: 如果一块内存的开头是一个指向 CUnicodeTextFormat 类的虚函数表(vtable) 的指针,那么其附近几乎肯定存在我们需要的Unicode文本数据。
    • 定位vtableCUnicodeTextFormat 的vtable地址位于 Windows.ApplicationModel.DataTransfer.dll 模块的 .rdata(只读数据)段中。这是一个稳定的特征,可以用来扫描内存。

3. 数据提取方案与实现步骤

基于以上分析,作者提出了一种可行的数据提取方案。该方案不依赖于未公开的API,而是直接扫描进程内存来定位数据。

整体步骤如下:

  1. 定位目标进程

    • 获取运行着 CBDHSvc.dllsvchost.exe 的进程ID(PID)。可以通过遍历进程列表、检查其加载的模块是否包含 CBDHSvc.dllWindows.ApplicationModel.DataTransfer.dll 来实现。
  2. 打开进程并获取模块信息

    • 使用 OpenProcess 函数以 PROCESS_QUERY_INFORMATION | PROCESS_VM_READ 权限打开目标 svchost.exe 进程。
    • 枚举进程加载的模块,找到 Windows.ApplicationModel.DataTransfer.dll 的基地址(Base Address)和大小。
  3. 定位特征值(vtable地址)

    • Windows.ApplicationModel.DataTransfer.dll 的镜像中,解析出 CUnicodeTextFormat 类的虚函数表地址。这个地址是相对固定的,但可能随Windows版本更新而改变。因此,需要为每个主要的Windows版本维护一个偏移量,或者通过特征码扫描在 .rdata 段中动态定位。
  4. 扫描进程内存

    • 遍历目标进程的整个内存空间,查找所有具有PAGE_READWRITE (RW)权限且已提交(Committed)的内存区域。
    • 在这些内存区域中,搜索步骤3中找到的 vtable指针值。因为每个 CUnicodeTextFormat 对象实例的第一个成员就是指向其vtable的指针。
  5. 解析数据结构并提取文本

    • 一旦找到vtable指针,就找到了一个 CUnicodeTextFormat 对象的起始地址。
    • 根据逆向分析出的结构偏移(例如,通过ReClass分析确定),从对象中提取出存储的Unicode文本字符串。通常,文本数据会位于对象指针之后的一个固定偏移处。
    • 重复此过程,直到扫描完所有内存。找到的所有文本即对应剪贴板历史记录中的条目。

4. 总结与关键点

  • 核心原理: Windows剪贴板历史记录由 CBDHSvc.dll 服务管理,其数据存储在服务进程的内存中,通过复杂的C++对象结构表示。
  • 技术路线: 放弃了传统的剪贴板API和完全逆向数据结构,采用了一种“特征码扫描”的实用主义方法。通过识别内存中特定的类vtable指针来定位文本数据对象。
  • 关键依赖: 方案的准确性依赖于 CUnicodeTextFormat 的vtable地址。这是一个潜在的版本依赖点,需要为不同的系统版本更新偏移量。
  • 效果: 该方法能够有效地从内存中提取出完整的、即时的剪贴板历史文本内容,即使某些条目当前并未在 Win+V 面板中显示(但仍在25条限制内)。

注意事项

  • 此技术涉及对系统进程内存的直接读取,可能被安全软件视为可疑行为。
  • 本文档基于逆向工程分析,所涉及的内核结构和未文档化API可能随系统更新而变化。
  • 实际代码实现时,需处理内存访问异常、不同系统版本的适配等细节。

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 : 对应组策略设置,默认开启。 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::GetHistoryItemCount Windows::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文本字符串。通常,文本数据会位于对象指针之后的一个固定偏移处。 重复此过程,直到扫描完所有内存。找到的所有文本即对应剪贴板历史记录中的条目。 4. 总结与关键点 核心原理 : Windows剪贴板历史记录由 CBDHSvc.dll 服务管理,其数据存储在服务进程的内存中,通过复杂的C++对象结构表示。 技术路线 : 放弃了传统的剪贴板API和完全逆向数据结构,采用了一种“特征码扫描”的实用主义方法。通过识别内存中特定的类vtable指针来定位文本数据对象。 关键依赖 : 方案的准确性依赖于 CUnicodeTextFormat 的vtable地址。这是一个潜在的 版本依赖 点,需要为不同的系统版本更新偏移量。 效果 : 该方法能够有效地从内存中提取出完整的、即时的剪贴板历史文本内容,即使某些条目当前并未在 Win+V 面板中显示(但仍在25条限制内)。 注意事项 : 此技术涉及对系统进程内存的直接读取,可能被安全软件视为可疑行为。 本文档基于逆向工程分析,所涉及的内核结构和未文档化API可能随系统更新而变化。 实际代码实现时,需处理内存访问异常、不同系统版本的适配等细节。