模仿iOS做的Windows剪切板审批工具,原理是什么?

https://github.com/LeonspaceX/Clipboard-access-control

项目源代码已上传至github喵,求star qwq。

前言

想必各位如果用过iOS,一定见过这样的弹窗吧:

这就是iOS 16之后引入的Paste from Other Apps机制,会在APP主动获取剪切板内容时弹出审批窗口,算是iOS独树一帜的设计了喵。正好晓夜也在前几天刷到一条关于Windows剪切板能获取多少隐私的视频,作者使用Flutter编写了一个剪切板读取工具来测试进程到底能获取多少信息,结果发现在Windows上,剪切板基本上是裸奔的....

所以,为什么Windows不能有类似iOS的机制呢?抱着试一试的心态,晓夜花2天时间vibe出了一个软件——ClipboardAccessCtrl喵!

不过,在讲CAC(ClipboardAccessCtrl)的具体细节前,晓夜想先讲讲为什么windows的剪切板api这么“不安全”喵

历史包袱

剪切板的雏形,是由Pentti Kanerva提出的"删除缓冲区",可以临时存放被删除的文本,方便后续恢复喵。到了1973年,clipboard这个词语正式被Pentti Kanerva提出,他将这些操作命名为剪切、复制、粘贴,这些名称也一直沿用至今。1983年,Apple Lisa成为了第一台将"临时储存空间"命名为剪切板的商业系统。很快,Windows也跟进了,Windows 3.0中已经出现了ClipBoard程序。

Source:互联网

这时,GetClipboardData的api已经出现,不过在那个时代,没有人会考虑什么权限隔离和审批弹窗,毕竟操作系统才刚刚兴起,开发者能有什么"坏心思"呢。

随着计算机软件的蓬勃发展,坏心思也出现了喵。2006年,ZeuS木马通过MITB(中间人攻击的变体,Man in the Browser)和HTML注入窃取网上银行和社交软件的密码,由于寄生在浏览器内部甚至可以绕过HTTPS,直接造成了超过1亿美元的损失,人们开始重视这些隐私,催生了MFA/2FA等保护措施。可是剪切板似乎成为了一个隐私盲区,虽然早期的木马也会顺手窃取剪切板内容,但是直到2017年CryptoShuffler的爆发,人们才意识到这个“任何程序都可以读取的缓冲区”,并不是想象中的那么安全喵。

Source:kaspersky.com

CryptoShuffler通过篡改剪切板中的比特币钱包地址,让本应该到达对方钱包里的虚拟货币到达了攻击者的钱包中,后来甚至出现了“撞”出头尾相同的钱包地址来进一步降低受害者怀疑的手法喵。

不仅是攻击者,商业软件也嗅到了这股气息,既然获取定位需要用户审批,读取其他应用的文件需要root,为什么不试试对剪切板做手脚呢?于是,"分享口令"便出现了,复制一段话,打开目标APP便自动跳转,时至今日依旧存在喵,虽不属于攻击但也足以用恶心用户来描述。

Apple意识到了这个问题,在WWDC 2020 Session "What's new in privacy"中首次提到了剪切板访问通知,2年后又扩展成了现在的审批弹窗,随后谷歌在安卓12中也加入了类似机制qwq。那么为什么Windows没有跟进呢喵

Source:维基百科 条目Windows_API

Windows的剪切板API至今未变,可能还要归功于Windows的设计哲学——宁可忍受开发者写的烂代码,也绝对不能让程序跑不起来(photoshop安装程序不能在wine里跑起来也得归功于这个设计哲学喵,具体可以看看这篇文章如果为了安全改掉这30多年的设计(技术债),让旧程序出现疯狂弹窗审批的情况,那很明显不是微软的作风喵,于是——虽然微软的Project Reunion团队公开讨论过要不要重新设计剪切板API,并表示当前的API极其陈旧,但是这件事还是不了了之了。

接下来,我们来看看CAC的具体实现吧喵~

整体架构

项目分为三个模块,Controller(后文也被称作主程序)、HookHost和NativeDLL,第一个是主要逻辑,负责弹出审批窗口、管理黑白名单和显示日志;第二个负责注入DLL到目标进程;第三个也是最核心的部分,inline hook DLL喵。第二、三个都有两个版本,x86和x64,这样才能覆盖到所有场景喵(因为x64 DLL不能混入x86进程,反之同理)。至于这些具体是什么...我们很快就知道啦

怎么读取剪切板内容

既然要制造拦截剪切板用的“盾”,那就必须先知道“矛”是怎么工作的。

剪切板读取API位于user32.dll和ole32.dll中,可以直接读取具体内容的只有两个user32!GetClipboardDataole32!OleGetClipboard 前者是通用接口,后者是富文本接口,虽然UWP应用也有一个WinRT剪切板API,但是实测最终也会请求到这两个api喵,所以拦截这两个就足够啦!

当然,测试的时候我们可以直接用那个B站视频作者写好的矛喵

那...该怎么拦截呢?

“矛”的架构很简单,但是要想制造出一个稳定的“盾”可不容易喵。思路很简单,只需要通过inline hook那两个函数就可以了喵。简单说说inline hook是什么:它是最简单粗暴的一种hook方式了,直接修改目标函数内存中的机器码,让原函数跳转到自己的hook函数。如果希望调用原函数呢,比如需要审批通过?也很粗暴,只需要把原函数的机器码原样复制一份到一块空闲内存中,再跳转到这个空闲内存中的函数就可以了喵。这就是Trampoline(蹦床)。

在hook之后,我们把某个软件要请求剪切板的信息传给主程序,等待主程序返回后再决定返回Null/False(模拟剪切板中没有任何内容的结果)还是执行原函数,就可以了喵!

虽然我们初版选择了自己手写这些hook代码,但是发现这样维护困难,于是便使用了经典hook库——MinHook。MinHook帮我们处理了Trampoline,就不需要担心复制指令时因为反汇编没做好而不慎从中间截断啦!

初版CAC截图

如果要把这个dll注入到一个进程中,CreateRemoteThread+LoadLibrary就是最简单的手法,可是全局注入呢喵?Windows为我们提供了一个简单的方法——SetWindowsHookEx 。当你用这个方法注册一个全局钩子(比如WH_GETMESSAGEWH_CBT这类),并指定一个DLL作为钩子处理函数所在的模块时,Windows会自动把这个DLL注入到所有GUI进程里——这是系统帮你做的,我们就不需要手写CreateRemoteThread那一套了qwq。

怎样才能与主程序通信?

拦截到了是一回事,但是由于inline hook内不能做太多事(不然会导致崩溃),弹窗和审批还是得到Controller里做。DLL该怎么和Controller通信呢?很简单,Windows也帮我们预留了方法,那就是命名管道喵,格式形如\\.\pipe\YourPipeName。它叫"管道"是因为概念上确实很像——一端写入数据,另一端读取数据,数据按顺序流动,跟物理管道运水有不少相似点。而叫"命名"则是因为它有一个全局唯一的名字,不同进程只要知道这个名字,就能连接到同一根管道上,不需要像普通(匿名)管道那样必须有父子进程关系才能共享。

所以,我们只需要定义一套格式,就可以让DLL和Controller进行通信啦

CAC中的命名管道通信格式

卸载DLL

安装容易卸载难,卸载DLL也是CAC项目里研究时间最长的一个模块了喵。

最初,我们直接使用了UnhookWindowsHookEx ,也就是微软给出的官方解法,但是这样有一个致命问题——何时卸载完全由Windows决定,点击“停止注入”后,DLL还不会被释放,被大量程序占用喵。

微软确实给出了后续解法——用WM_NULL广播唤醒。简单来说,Hook是在程序有消息循环时才处理的,WM_NULL就是微软为“没有实际东西,但是必须要跑一遍消息循环”的场景设计的喵。但是实测问题并没有得到缓解。

后来,我们又换了新的策略——既然你不肯释放,就强迫你释放喵~❤️我们使用FreeLibraryAndExitThread远程卸载DLL,结果是....几乎所有软件都闪退了,原本20多个后台进程一瞬间只剩下6个。为什么呢?主要有两个缺陷,第一个是我们不能用主控进程里的 GetModuleHandle 得到的 HMODULE,那个值只在主控进程地址空间有效,导致我们用一个无效的值调用函数让目标进程闪退;第二个是容易和正在被hook的进程产生竞争,最终也确实造成了闪退喵。

那干脆自己注入,不依赖微软的史山了?也不行。用了WMI 监听新进程、手动注入的方法之后,覆盖面显著降低,很多进程都无法拦截了喵。

最后...我们终于找到了一种比较妥帖的混合方案喵:

这个方法参考了WindHawk作者的博客,在此表示感谢喵!

这个方案解决了上文提到的竞争问题——计数器确保了调用归零后才执行卸载;也解决了HMODULE问题——DLL主动执行卸载;最后,也解决了UnhookWindowsHookEx不生效的问题(通过WM_NULL事件)喵!可以说是最完美的一个方案啦~

判断剪切板是否未变

如果同一个程序请求同一份剪切板内容,还需要审批,会不会显得有些麻烦呢?但是如果一个保护隐私的程序还要去读取剪切板,未免有点自相矛盾了喵...不过,Windows也给出了一个较好的解法——GetClipboardSequenceNumber 。这个函数会返回一个编号,这个编号就是检测剪切板是否变更的准确标识,每当剪切板中出现新的内容时数字就+1。所以,只需要维护一个字典就可以实现这个效果了喵!

(process path, api name)

成果展示

审批界面:

审批通过:

审批不通过:

成功拦截喵!~

结语

以上就是ClipboardAccessCtrl项目的整体细节啦!希望能对你有所帮助哦!

相比实体上的盗窃,隐私盗窃往往显得更加隐蔽——因为隐私是一个无实体的东西,被拿走后,那些秘密看似还保留在自己心里,但实际上已经不再是隐私了...为什么剪切板这么久都变成了隐私盲区?可能恰恰是因为它和"复制粘贴"这个动作太日常、太理所当然了,剪切板大概也还要裸奔很多年。直到下一个CryptoShuffler广泛爆发,或许人们才能意识到这是多么重要,为剪切板打上补丁。可是,这个补丁又能持续多久呢?

由于项目出自AI之手,难免有不少漏洞和问题,期待你的issue、PR或者fork哦!我们下次再见啦~

ヾ(≧▽≦*)o


模仿iOS做的Windows剪切板审批工具,原理是什么?
https://leonxie.cn/archives/how-CAC-works
作者
LeonXie
发布于
2026年06月28日
更新于
2026年06月28日
许可协议