MS Paint and Photos inivisibly watermark even locally generated output with GUID
MS Paint & Photos: The Secret GUID Watermark
Reverse engineering reveals how Microsoft Paint and Photos embed a server-issued GUID into the pixels of locally generated AI images.
📌 Executive Summary
- Hybrid Generation: Paint supports both cloud-based and local image generation.
- Remote Moderation: Even local models send prompts to a remote server for safety checks.
- The GUID Loop: The server returns a moderated prompt and a GUID, which is then embedded as an invisible watermark.
- Independent Settings: The "visible watermark" toggle does not disable this invisible tracking.
- Copilot+ PCs: Generation is local (NPU), but moderation remains remote.
- Metadata: C2PA metadata is added; saves are restricted to C2PA-compatible formats (PNG, JPEG, GIF,
.paint).
🔍 A Deep Dive into Microsoft Paint
This investigation began with a general curiosity about under-documented Windows features (such as UCPD and WHESCVC). While AI features in Paint are well-known, the underlying mechanism was a mystery.
I initially assumed the app simply called a remote API for all image generation. However, using Binary Ninja MCP with Codex, it was discovered that Windows actually ships local AI models as part of the Copilot integration.
Local Model Analysis
The Paint application is located at:
C:\Program Files\WindowsApps\Microsoft.Paint_11.2605.71.0_x64__8wekyb3d8bbwe\PaintApp\
Within this directory, four .onnxe model files were identified:
| Model File | Size | Purpose/Output |
|---|---|---|
seg.onnxe | 23.1 MB | output (Segmentation) |
inseg_enc.onnxe | 28.0 MB | image_embeddings |
inseg_dec.onnxe | 16.5 MB | masks (via embeddings/points) |
mager.onnxe | 302.4 MB | Final output image/mask |
The Encryption Layer:
The .onnxe format is a modified ONNX file. The original seg.onnxe was decrypted using a simple XOR operation:
For the other models, the key was updated. Inside segapi.dll, two keys were found:
ps_enc_key.1.0.80-main:"Microsoft_2023"ps_enc_key.1.0.81-main: A unique 4,096-byte alphanumeric string.
🎨 The Watermarking Mechanism
During the analysis, a file named Watermarker.dll was discovered. While Paint has a user-facing setting for a visible watermark (the Copilot logo), the size of this DLL (1.67 MB) suggested more complex functionality.
1. The Visible Watermark
The visible logo is handled by AddPerceptibleWatermark. The logic follows this path:
CPBDoc::Saveperceptible-watermark save helperCheckWatermarkSettingIf "Yes/Always", callPaint::AI::AddPerceptibleWatermarkComposite SVG logo.
2. The Invisible Watermark
A separate function, WmkWriteWatermark, handles the invisible data. This is triggered after the local Stable Diffusion process completes.
Crucially, if the invisible watermarking fails, the app will not simply provide the image without a watermark; it will treat the entire generation as a failure.
🛠️ Technical Payload Details
The WmkWriteWatermark function requires a specific payload length. If the payload is not exactly 16 bytes, it returns an error (using different error codes for "too short" vs "too long").
The 16-byte payload is a GUID. The wrapper then expands this into a 144-bit (18-byte) message:
// Payload Structure
[0x4c] + [16-byte GUID] + [Checksum (Sum of GUID bytes mod 256)]
The Encoding Process: The encoder rounds the image dimensions down to the nearest multiple of eight. It then utilizes 144 separate counters (one for each bit of the message) to embed the data into the pixels.
The result is a locally generated image that carries a server-issued identity, invisibly etched into the image data.