微软Paint在本地AI图片的像素里埋了隐形水印

admin 2026-08-27 05:25:24 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 逆向分析发现微软Paint和Photos本地AI生图并非完全离线,提示词需发送至微软审核并获取唯一GUID。该GUID通过InvisMark算法隐写入图片像素,同时绑定C2PA元数据实现双重溯源。Paint强制要求水印嵌入,可见水印开关对此隐形水印无效。此隐蔽机制涉及用户隐私且缺乏透明度披露。 综合评分: 91 文章分类: 逆向分析,AI安全,安全意识


微软Paint在本地 AI 图片的像素里埋了隐形水印

原创

黑鸟 黑鸟

黑鸟

2026年8月25日 23:27 广东

在小说阅读器读本章

去阅读

在公众号小说中沉浸阅读

如果你用过Windows自带的Paint画图工具,可能知道它现在能AI生图了。输入一段文字描述,几秒就能出一张图,看起来全是在你自己电脑上跑的。但一位逆向工程师拆开Paint的文件后发现,事情没那么简单。你的提示词会先发给微软服务器审核,服务器返回一个唯一的GUID(全局唯一标识符),然后这个GUID被悄悄写进图片的像素里,肉眼完全看不见。就算你把图片截图、裁剪、转格式,这串”身份证号”大概率还在。

这不是阴谋论,是从DLL文件里一行行分析出来的实锤。下面我们把整个技术链条拆开讲清楚。

Paint里居然塞了本地AI模型

这项研究的起点很单纯。作者之前挖过一些Windows里不太受人关注的功能,最近把目光投向了Paint的AI生图。一开始他以为Paint只是调用云端API来生成图片,毕竟大多数AI绘图工具都是这么干的。但用Binary Ninja分析之后,他发现微软居然把AI模型直接装在了系统里。

Paint应用位于C:\Program Files\WindowsApps\下面的Microsoft.Paint目录,里面躺着四个.onnxe文件,这是加密后的ONNX模型格式。最大的一个叫mager.onnxe,足足302.4MB,另外三个分别是seg.onnxe(23.1MB)、inseg_enc.onnx(28.0MB)和inseg_dec.onnx(16.5MB)。

.onnxe的加密方式之前就被人研究过,本质是XOR加密。seg.onnxe用的密钥是字符串”Microsoft_2023″,而另外三个文件用了新密钥。在segapi.dll里有一个小型密钥注册表,记录了两个版本对应的密钥,新版本的密钥是一个4096字节的字母数字字符串。解密之后用onnx.checker.check_model()验证,四个模型全部能正常加载。mager.onnx有15284个计算节点,是真正的生图主力,其他三个负责图像分割和蒙版相关的功能。

这说明在Copilot+PC上,Stable Diffusion的推理确实是本地跑的,图片本身不需要上传到微软服务器。但”本地生成”不等于”全程离线”,后面会看到为什么。

一个1.67MB的DLL引起了怀疑

在翻文件的时候,研究者注意到了Watermarker.dll。看名字就知道是加水印用的,大小1.67MB。

Paint里确实有一个可见水印的开关,在设置里可以选”从不””始终”或”每次询问”。开启后,生成的图片右下角会多一个小小的Copilot标志,这很正常。

但问题来了,贴一个logo需要1.67MB的DLL吗?这体积太反常了。可见水印本质上就是把一张SVG图标合成到图片角落,几十KB的代码都绰绰有余。研究者的逆向直觉告诉他,这个DLL里还藏着别的东西。恰好当时Claude发布了文本水印功能的公告,更让他觉得有必要查一查。

隐形水印:写进像素里的16字节

分析Watermarker.dll的导出函数,可见水印由AddPerceptibleWatermark负责,逻辑很直白:根据设置判断要不要加,要加就把Copilot的SVG图标合成到位图上。

但还有另一个函数叫WmkWriteWatermark,签名是这样的:它接收输出像素缓冲区、payload数据、payload长度、宽高、步长、输入像素和像素格式。追踪调用链发现,这个函数在本地Stable Diffusion生成图片之后被调用。更关键的是,如果WmkWriteWatermark执行失败,Paint会把整个生图任务标记为失败,而不是返回一张没有水印的图片。这说明微软把水印当成了必选项,不是可选项。

payload长度小于16返回错误码-6,大于16返回-5,太短和太长还用了两个不同的错误码。然后函数直接忽略传入的长度参数,用硬编码的循环把前16个字节拷贝进去。

16字节,这是一个非常经典的长度。在Windows的世界里,16字节几乎等于GUID。但光凭长度不能下定论,很多东西都可以是16字节。

研究者继续往上追溯调用者,在PaintAIManager.dll里找到了包装函数Paint::AI::AddWatermark,它的第二个参数类型是winrt::guid const&。实锤了,payload就是一个GUID。

不过WmkWriteWatermark并不直接嵌入这16字节。它的包装函数会构造一条18字节也就是144位的消息:第一个字节固定是0x4c,中间16字节是GUID,最后一个字节是GUID所有字节求和后对256取模的校验和。

核心编码器会把图片的可用尺寸向下取整到8的倍数,然后为144位中的每一位维护一个计数器,要求每一位至少被成功放置3次。嵌入循环在选中的图像块上做小幅量化修改,涉及3乘5的矩阵运算和矩阵分解,用到了24.0、0.25、0.5、0.2这些常数。从算法特征来看,这是一种内容自适应的块域SVD风格水印,和Google的SynthID属于同一类技术路线。

研究者做了个直接测试:构造一张512乘512的BGRA纯色图,调用WmkWriteWatermark之后,262144个像素里有193376个发生了变化,占比超过73%。改动量很小,肉眼完全看不出来,但覆盖面极广,这就是为什么截图、压缩之后水印往往还能存活。

GUID从哪来?远程提示词审核服务器

既然水印的内容是GUID,那这个GUID是谁生成的?答案是微软的远程服务器。

在本地模型运行之前,AIServices.dll会把提示词和风格信息发到一个Azure Front Door地址,路径是/v1/paint-cocreator/moderate-prompt。请求是JSON格式,至少包含prompt、style和lastPromptGenerationId三个字段。

服务器返回的JSON里有四个关键字段:revisedPrompt(审核后可能修改过的提示词)、promptGenerationId、watermarkId,以及containsHumanReference。

研究者复用了Paint自己的认证会话,发了一条”一个钴蓝色圆圈在一个小橙色方块上方”的提示词,服务器返回了HTTP 200,其中watermarkId的值是83424621-03cb-40e3-9808-a9fae837156d。换成”一个戴蓝帽子微笑的人的肖像”,返回了另一对GUID,而且containsHumanReference变成了true。这个字段是服务器端对提示词是否涉及人物的分类,Paint会解析并存储它,但没有证据表明它会影响水印步骤本身。

ParseModerateResponse会把两个ID字符串都解析成GUID,如果是全零值就拒绝。服务器返回的watermarkId最终就是被嵌进图片像素里的那个值。整个链条很清晰:提示词发给微软,微软审核后发回一个watermarkId,本地生图完成后把这个ID写进像素。

还有一个细节值得注意。每次请求会带上上一次的promptGenerationId作为lastPromptGenerationId,这意味着连续的生图请求可以被服务器显式地串联起来。即使你换了提示词,微软也知道这是同一次会话里的连续操作。

C2PA元数据:第二层追踪

光改像素还不够,Paint还会给保存的文件附加C2PA Content Credentials(内容凭证)。负责这件事的是ProvenanceHelper.dll,底层用的是provenancesdk.dll。

本地Stable Diffusion出图之后,流程分成两条线并行:一条是前面说的AddWatermark把watermarkId写进像素,另一条是SignIngredientOnlineAsync把图片和元数据发给微软的/v1/paint-cocreator/image-sign接口做在线签名。签名请求里带的是promptGenerationId,而图片像素里已经藏了watermarkId。这两个值都是审核阶段由服务器分配的,所以服务器能把签名请求和像素里已有的水印对应起来。

研究者从Paint里直接保存了一张生成的PNG,检查它的二进制结构。在IHDR块之后紧接一个18979字节的caBX块,里面就是经过签名的C2PA清单。最关键的部分是c2pa.soft-binding字段,算法标识为com.microsoft.invismark.1,作用范围是整张图片,value的值正好是83424621-03cb-40e3-9808-a9fae837156d,和像素里的watermarkId完全一致。

也就是说,C2PA清单里写着”这张图用了微软InvisMark算法做了软绑定,水印值是这个GUID”,而且这条声明是微软用密码学签名的。C2PA把这种机制叫做soft binding(软绑定),意思是把一个值嵌入内容本身,这样就算文件层面的C2PA清单被删掉,仍然可以通过检测像素水印来匹配到对应的来源记录。

所以这里其实有两层追踪:文件层的C2PA元数据和像素层的隐形水印,两者用同一个GUID关联,构成了一套完整的来源溯源系统。

为什么本地生成也要自己加水印

理解了这些之后,Watermarker.dll存在的意义就清楚了。Paint其实有两条完全不同的生图路径。

第一条是Image Creator,用的是Azure OpenAI ImageGen,生成、水印、来源打包全在微软云端完成,Paint拿到的就是一张已经带好隐形水印和C2PA清单的成品图。

第二条是Cocreator,在支持的Copilot+PC上由NPU本地生成图片,但安全审核仍然走Azure在线服务。这就产生了一个问题:云端生成器可以在返回图片之前就加好水印,但本地生成器做不到,因为图片是在你电脑上生成的,微软碰不到。所以Paint必须自己动手,在本地生成的像素里写入服务器下发的watermarkId。这也解释了为什么水印失败会被当作整个生图的失败,微软不允许出现一张没有水印的本地生成图。

还有一个细节能看出微软把来源溯源设计到了骨子里。从Image Creator面板直接保存生成结果时,保存格式只有PNG一种。

把AI结果应用到Paint画布之后,可用格式也仅限于PNG、JPEG、GIF和Paint自己的.paint格式。经典的BMP格式居然缺席了。原因和C2PA有关:PNG用caBX块存清单,JPEG用APP11标记段,GIF有自己的应用扩展表示,.paint是微软自己的格式想怎么存就怎么存。而C2PA规范明确指出BMP这种经典格式无法嵌入任意清单数据,必须用外部清单文件。如果允许直接导出BMP,文件层的C2PA元数据就丢了。

Photos应用也在做同样的事

研究者在磁盘上找Watermarker.dll的时候,意外发现微软Photos应用里也有一个同名DLL。Photos的Image Creator和Restyle Image功能背后同样有本地Stable Diffusion操作,两条路径最终都走到同一个水印包装函数ApplyWatermark。

C:\Program Files\WindowsApps\Microsoft.Windows.Photos_2026.11060.2004.0_x64__8wekyb3d8bbwe\Watermarker.dll

不过Photos和Paint有一个细微但重要的区别:失败处理方式不同。

如果水印编码器返回错误,Photos的代码会记录一条日志说”ApplyWatermark遇到错误,水印将不会被应用”,然后继续返回生成的图片。而Paint前面说过,水印失败等于生图失败,图片根本不会返回给用户。换句话说,Photos对水印的态度相对宽松,Paint则是强制执行。

微软说了什么,又没说什么

微软在Image Creator的支持页面上确实披露了一些相关信息。关于内容过滤,页面说”我们应用内容过滤以防止生成图片”。关于生成的图片,页面说”将包含C2PA清单,帮助用户识别这是AI生成的图片”。页面还解释了Image Creator使用Azure在线服务,微软会收集用户和设备标识符以及提示词用于防滥用和监控。

这些都是有意义的披露。但页面没有解释的是:C2PA清单里包含一个对应像素隐形水印的GUID,也没有解释Paint的本地生成路径从远程提示词审核那里获取水印GUID。把这个功能叫做”Content Credentials”(内容凭证)在字面上没错,但它没有让普通Windows用户意识到,自己的提示词被关联了一个唯一标识符,而且这个标识符被写进了图片的每一个角落。

这可能和欧盟AI法案第50条有关。该条的透明度规则在2026年8月2日生效,要求AI生成内容携带可检测的机器可读标记,但并没有要求携带提示词特定的GUID。微软披露了C2PA元数据的存在,但研究者没有找到任何披露说明服务器下发的水印GUID、它与提示词审核的关联,以及它存在于像素之中。这些细节显然涉及隐私和知情权。

当然,理论上可以修改Paint或Photos来绕过提示词审核和水印。但这并没有带来什么新能力,因为任何人都可以直接运行Stable Diffusion,这两个机制本来就不存在。

黑鸟总结一下整个发现。

Paint和Photos在本地AI生图时,提示词会先发给微软审核,服务器返回一个唯一的watermarkId,这个GUID通过InvisMark算法嵌入图片像素,同时也记录在C2PA元数据的soft binding声明里。文件层和像素层两层追踪用同一个ID关联,构成了完整的来源溯源体系。”本地生成”并不意味着离线,提示词审核和来源签名始终需要联网。可见水印的开关只控制右下角那个Copilot图标,和这层隐形水印完全无关。

这是已知的第一份详细记录和分析Paint及Photos隐形水印行为的研究。可见水印不新鲜,Google的SynthID和Bing的隐藏水印也早已存在,但把服务器下发的提示词关联GUID写进本地生成图片的像素,并且和C2PA做密码学绑定,这个完整链条此前没有被公开拆解过。

参考来源:xusheng.dev《Microsoft Paint and Photos Embed Server-Issued GUIDs as Invisible Watermarks in Locally-Generated Images》

More👇


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:黑鸟 黑鸟 黑鸟《微软Paint在本地 AI 图片的像素里埋了隐形水印》

评论:0   参与:  0