xusheng 在八月底发了一篇《Microsoft Paint and Photos Embed Server-Issued GUIDs as Invisible Watermarks in Locally-Generated Images》,讲他逆向微软 Paint 和 Photos 的过程。结论一句话:这两个应用在本地生成的 AI 图片里,像素层面嵌了一个服务端下发的 GUID。
我关注这篇不是因为"微软又干了什么",而是它示范了一次很干净的逆向流程:从一个"文件尺寸不合理"的怀疑出发,定位到算法,再推出它到底在传什么信息。
起点:一个不该那么大的文件
作者的直觉来自文件大小。他发现相关模块有 1.67MB,对一个"在图片上加个可见水印"的功能来说,这个体积不合理——可见水印不过是往角上叠一行字。
这个判断是逆向工程里最常见也最有效的入口:如果一个东西比它该有的大小大得多,多出来的部分一定在做别的事。他顺着这个线索往下挖,发现里面包含 3×5 的矩阵运算和一个矩阵分解例程,以及若干常数:24.0、0.25、0.5、0.2。
这些数字透露了什么
水印算法分两大类。一类是空间域的直接嵌入,比如最低有效位(LSB)替换——把信息编码进像素值的最低位,人眼看不出,实现也简单。另一类是变换域的方法,先把图像块做某种线性变换,在变换系数里嵌入信息,再变换回来。
3×5 矩阵加矩阵分解,显然不是 LSB 那一类。它更像是把一个图像块映射到某个基底上,在系数上做修改。为什么需要矩阵分解?一种常见做法是把水印信息分散到多个系数上,用类似扩频的方式让每个系数的改动都很小——这样抗压缩、抗缩放的能力更强,也更难被简单的像素差值检测出来。
几个常数的量级也能说明问题:0.25、0.5 像是权重或缩放因子,0.2 可能是嵌入强度,24.0 可能是某个归一化系数或块尺寸相关量。这些是典型的水印嵌入参数,不是图像处理里随便出现的数。
需要说明的是,具体系数含义是我从数值推断的,作者原文给的是"包含 3×5 矩阵运算与矩阵分解",不是完整逆向出的公式。但这已经足够判断它属于"变换域嵌入 + 抗性设计",而不是随手埋个标记。
一个服务端 GUID 意味着什么
比算法更值得注意的是嵌进去的东西:一个服务端下发的 GUID。
这个设计有两层含义。第一层是本地生成并不等于本地闭环——应用把 prompt 发给远端服务器,服务器返回一个 GUID,这个 GUID 被写进最终图片的像素里。第二层是这个 GUID 是唯一且服务端签发的,也就是说,它可以用来把一张图片对应回一次具体的生成会话。
作者在标题里用了 "locally-generated" 这个词,是在点这个矛盾:图片是在你本地渲染出来的,模型也在本地跑,但生成过程依然跟服务端有一次信息交换,并且这次交换的结果被隐蔽地固化进了你的文件。
至于这个 GUID 的目的是什么,公开信息只到"它存在、它被嵌入"这一步。可追踪来源是合理推测,但具体用来做什么、是否会上报,作者没有下结论,我也不替他下。
怎么验证这类隐形水印
如果你想确认自己机器上的某个应用有没有干类似的事,方法不复杂:
- 用同一个 prompt 生成两张图,在像素层面做 diff。如果差异是均匀散布在整张图上的小幅噪声,而不是集中在局部,那就是全局嵌入的特征。
from PIL import Image, ImageChops
a = Image.open("gen1.png"); b = Image.open("gen2.png")
diff = ImageChops.difference(a.convert("RGB"), b.convert("RGB"))
print(diff.getextrema()) # 每通道的差异范围,水印通常是很小的非零值
diff.save("diff.png") # 观察差异是散布还是集中
- 做一次 FFT,看频谱里有没有规律性的峰值。变换域水印往往会在特定频段留下痕迹。
- 检查文件大小异常。像作者那样,一个"功能很简单"的模块如果体积远超预期,先怀疑它带了额外逻辑。
- 跨账号/跨设备生成同一张图,比对像素差异。如果差异随账号变化,说明嵌入的信息跟会话绑定。
这几步不需要反汇编,用脚本就能做,成本很低。
⚠️ 网络安全免责声明
本文内容仅供技术研究与安全学习之用。文中描述的漏洞分析、攻击链拆解和代码示例均基于公开披露的安全研究,旨在帮助开发者理解攻击原理、提升安全意识和防御能力。
请勿将本文所述技术用于任何未经授权的安全测试、渗透攻击或非法用途。任何因滥用本文信息而造成的法律后果,由使用者自行承担,与本文作者无关。
如发现系统安全漏洞,请通过合法渠道向相关厂商或平台报告,共同维护网络安全生态。
我的做法
这类"我的文件里被塞了什么"的问题,我现在会养成两个习惯:
- 生成内容之后,本地算一遍哈希并留档。如果同一输入两次生成的像素不完全一致,先搞清差异从哪来。
- 对会调用云端的"本地功能"保持警觉。用了本地模型不代表数据不出门,prompt 的上行和标识的下行是两回事。
作者那句"这是作为逆向工程师的直觉"很实在。逆向很多时候不是从反汇编开始的,是从"这个数不对"开始的。
浙公网安备 33010602011771号