Hyaika Blog

Penguin is all you need

技术

🖌️ 你画图它上锁:Microsoft Paint 的本地 AI 生图,藏着看不见的 GUID 水印

🖌️ 你画图它上锁:Microsoft Paint 的本地 AI 生图,藏着看不见的 GUID 水印

目录

  • 一个逆向工程师的好奇心
  • 本地生成,远程审核——你的 prompt 先去了微软的服务器
  • Watermarker.dll:一个 1.67MB 的「看不见的锁」
  • C2PA 双重验证:像素里的水印和文件里的签名
  • Photos 也在做同样的事,但失败处理不一样
  • 为什么连保存格式都被限制了

一个逆向工程师的好奇心

MS Paint 的 AI 生图流程:本地 Stable Diffusion 推理前后,prompt 经过远程审核,GUID 水印嵌入像素

xusheng 最近在逆向 Windows 应用上找到了手感。他翻过 UCPD 的动态规则,解剖过 WHESCVC 认证服务,然后他的目光落在了 Paint 上。

不是 Mr. Baby Paint 那种给三岁小孩的画图软件——是 Windows 自带的那个 Microsoft Paint,从 1985 年活到现在的老古董,微软在它身上塞了一堆 AI 功能。

他原本以为 Paint 的 AI 生图就是调一个远程 API,返回图片完事。所以他把 Binary Ninja MCP 接上 Codex,准备看看调用链。

结果他发现 Paint 的安装目录里躺着四个 .onnxe 文件——加起来 370MB 的本地 ONNX 模型。微软把 Stable Diffusion 塞进了 Windows 的 Copilot+ PC 里,用 NPU 做本地推理。

但接下来的发现才真正有意思。

本地生成,远程审核——你的 prompt 先去了微软的服务器

在 Paint 调用本地模型之前,它先做了一件事:把你的 prompt 发送到微软的 moderation 服务器。

POST https://apsaiservices-a0fqcjc6bzbhgdcd.b02.azurefd.net/v1/paint-cocreator/moderate-prompt

返回的 JSON 长这样:

{
  "revisedPrompt": "a cobalt blue circle above a tiny orange square",
  "promptGenerationId": "74d9e06b-adea-43ce-85fe-186a26e2e34a",
  "watermarkId": "83424621-03cb-40e3-9808-a9fae837156d",
  "containsHumanReference": false
}

注意那个 watermarkId。它是一个 GUID,由微软的服务器生成,和审核后的 prompt 一起返回给 Paint。

然后 Paint 才把 revisedPrompt 喂给本地 NPU 上的 Stable Diffusion,生成图片。生成完成后,它做的第一件事不是把图片显示给你——是把那个 watermarkId 以不可见水印的形式写进像素里。

「本地」不等于「离线」。你的 prompt 出去了,回来一个 GUID,这个 GUID 被嵌进了你的图片里,就算你是在自己的 NPU 上生成的。

Watermarker.dll:一个 1.67MB 的「看不见的锁」

Paint 的目录里有一个 Watermarker.dll,1.67MB。如果只是加一个可见的 Copilot logo 水印,这个 DLL 大得离谱——xusheng 的直觉告诉他,这肯定不止一个可见水印。

他反编译了 WmkWriteWatermark 函数。它的调用链是这样的:

CocreatorViewModel::GenerateImageAsync
  └─ Paint::AI::StableDiffusionHelpers::GenerateAsync(..., watermarkId, ...)
      └─ NPU-generated image result
          └─ Paint::AI::AddWatermark(bitmap, watermarkId)
              └─ Watermarker.dll!WmkWriteWatermark(...)
                  ├─ success: return watermarked bitmap
                  └─ failure: turn generation into an error

水印的 payload 是 18 字节:0x4c || GUID[16字节] || 校验和[1字节]

编码器把图像的可用的像素区域按 8 像素对齐,把 144 个 bit 一个一个地嵌入到选定的图像块中。它要求每个 bit 至少被放置三次,否则返回 -8(容量不足)。

这是 SVD 风格的图像水印——修改像素的量化值,人眼看不出来,但算法可以提取。xusheng 用 AI 写了一段代码直接调用这个函数,在 512×512 的测试图上,262,144 个像素里有 193,376 个被修改了——73.8% 的像素都被动了。

C2PA 双重验证:像素里的水印和文件里的签名

水印嵌入之后,Paint 还有第二步:把图片发送到微软的签名服务。

POST /v1/paint-cocreator/image-sign
  ├─ imageMetadata (PromptGenerationId, GenerationSeed, ...)
  └─ imageToSign.jpg

服务器返回一个 C2PA manifest,和图片捆在一起。xusheng 从一张直接用 Paint Image Creator 生成的 PNG 里提取了那个 manifest:

{
  "c2pa.soft-binding": {
    "alg": "com.microsoft.invismark.1",
    "blocks": [{
      "scope": "the entire image",
      "value": "83424621-03cb-40e3-9808-a9fae837156d"
    }]
  },
  "c2pa.actions.v2": {
    "actions": [{
      "action": "c2pa.watermarked",
      "description": "Content watermarked by Microsoft Responsible AI"
    }]
  }
}

注意那个 value——和像素级水印里的 GUID 是同一个。

C2PA 把这种绑定叫做 soft binding:manifest 里的 GUID 和像素里的 GUID 是一致的,所以即使文件级别的 manifest 被剥离了(比如截图、上传到社交平台),你仍然可以从像素里提取水印,和微软的服务器比对,证明这张图是 AI 生成的。

Photos 也在做同样的事,但失败处理不一样

xusheng 在翻文件的时候,发现 Microsoft Photos 的目录里也有一个同名 Watermarker.dll

Photos 的 Image Creator 和 Restyle Image 功能都走同样的路径:

Photos Image Creator
  └─ PerformSDTextToImageAndWatermarkAsync(..., promptGenerationId, ...)
      ├─ run the local text-to-image model
      └─ ApplyWatermark(image, promptGenerationId)
          └─ Watermarker.dll!WmkWriteWatermark(..., guid, 16, ...)

但有一个关键差异。如果水印嵌入失败,Paint 把整张图片作废,不返回给用户。Photos 只打一行日志,然后继续返回没有水印的图片。

Paint 说:「水印没打上 = 这张图不能给用户。」
Photos 说:「水印没打上?算了,用户应该不会注意到。」

为什么连保存格式都被限制了

当你用 Paint 的 AI 生图功能生成一张图片,直接保存时,它只给你一个选项:PNG。

如果你把图片应用到画布上再保存,选项扩展到 PNG、JPEG、GIF 和 Paint 自己的 .paint 格式。BMP——Paint 从 1985 年就开始用的经典格式——被故意移除了。

原因很简单:C2PA manifest 需要嵌入到文件格式的元数据中。PNG 用 caBX chunk,JPEG 用 APP11 marker,GIF 有自己的 C2PA 扩展表示。但 BMP 不支持这些——它没有标准化的元数据通道。

所以微软干脆不让用户保存成 BMP,因为那样 C2PA 签名就丢了。

这是一套设计得很完整的系统:像素级水印 + 文件级签名 + 格式限制,三层防护。但问题在于,微软在用户协议里说了「AI 生成的图片会包含 C2PA manifest」,却没有说「你的 prompt 被发送到远程服务器审核并返回了一个唯一标识,这个标识被写入了你的图片的每一个像素」。


如果你是在 Copilot+ PC 上用 Paint 的 Cocreator 功能画图,你的 NPU 在本地跑 Stable Diffusion,节能又私密——但 prompt 还是去了微软,回来一个 GUID,嵌进了你的图片里,永远擦不掉。

水印本身不是坏事。AI 生成内容的可追溯性,是欧盟 AI 法案第 50 条的要求,也是 C2PA 联盟一直在推的方向。但问题在于透明度:「本地生成」的描述会让人以为一切都在本地完成,而实际上每一步都绕不开微软的服务器。

好消息是,xusheng 也指出:既然任何人都可以直接跑本地的 Stable Diffusion 而完全不经过这些机制,这个水印系统本质上防的是「无意的用户」,而不是「有意的攻击者」。它更像一把锁——锁的是顺手,不是锁匠。

分享:

评论(0)

暂无评论,来写第一条吧~

发表评论