MS Paint and Photos inivisibly watermark even locally generated output with GUID

Aug 24, 2026 10:28 PM - 2 hours ago 1

Reverse engineering reveals really Paint and Photos embed a server-issued GUID into the pixels of locally generated AI images.

TL;DR

  • Microsoft Paint supports some section and unreality image generation
  • Paint and Photos besides vessel section AI models
  • The 2 apps nonstop the punctual to a distant server for moderation
  • The server returns a GUID on pinch the moderated prompt
  • The GUID is embedded into the locally generated image arsenic an invisible watermark
  • A abstracted visible-watermark mounting does not power this invisible watermark
  • On Copilot+ PCs, image procreation is section but punctual moderation remains remote
  • Microsoft discloses that Paint adds C2PA metadata to AI-generated images
  • AI-generated image saves constricted to C2PA-preserving formats: PNG, JPEG, GIF, and .paint

Paint sends the personification punctual to Microsoft’s moderation server, receives a moderated punctual and watermark GUID, generates the image locally, and embeds the GUID into the last image pixels

A funny look astatine Microsoft Paint

This investigation started pinch my curiosity astir Paint. I precocious had immoderate occurrence looking into less-explored Windows features for illustration UCPD, WHESCVC, and I person agelong known that Microsoft added a bunch of AI features into the Paint app. I do not cognize if anyone really uses Paint + AI to make images, but I wanted to spot really precisely the image procreation works.

Before I started, I expected that it simply called a distant API to do the image generation. However, aft I group up Binary Ninja MCP pinch Codex and started the analysis, I soon realized that Microsoft really shipped section models successful Windows arsenic portion of Copilot.

The Paint App is sitting successful the pursuing way (yes, they are each Windows Apps now):

C:\Program Files\WindowsApps\Microsoft.Paint_11.2605.71.0_x64__8wekyb3d8bbwe\PaintApp\

And location are 4 evident exemplary files pinch the .onnxe extension:

seg.onnxe 23.1 MB inseg_enc.onnxe 28.0 MB inseg_dec.onnxe 16.5 MB mager.onnxe 302.4 MB

The format of seg.onnxe was previously known, i.e., erstwhile it is XORed pinch the drawstring Microsoft_2023, it becomes a normal ONNX file. However, the format of the different 3 .onnxe files initially looked different.

It turned retired that Microsoft had not changed the algorithm, only the key. segapi.dll contains a mini cardinal registry:

ps_enc_key.1.0.80-main -> "Microsoft_2023" ps_enc_key.1.0.81-main -> a 4,096-byte alphanumeric string

After decryption, onnx.checker.check_model() useful connected each of them:

Model Graph
seg.onnx 1,094 nodes, input input_image, output output
inseg_enc.onnx 1,014 nodes, output image_embeddings
inseg_dec.onnx 1,133 nodes, inputs for embeddings, points and masks; output masks
mager.onnx 15,284 nodes, image/mask inputs; output output

A visible watermark

While stepping done these files, I recovered a Watermarker.dll:

The properties of Watermarker.dll included pinch Microsoft Paint

This is not ace astonishing to me, because while I interacted pinch the Paint app, I already discovered that it has a mounting to embed a visible watermark to the image that it produces:

Paint offers Never, Always, and Ask each clip choices for its visible AI watermark

The visible watermark is conscionable a mini Copilot logo astatine the bottommost correct of the image, which is wholly normal.

Then, retired of nowhere, I decided to inquire AI to analyse the DLL and spot if it could besides beryllium embedding an invisible watermark. This is portion of my intuition arsenic a reverse engineer, because the record is 1.67 MB successful size, which is unusually ample for specified trivial functionality (arguably, the visible watermark does not moreover require a abstracted DLL). Apparently, the caller Claude Code text-watermark announcement besides played a domiciled successful prompting maine to deliberation astir this possibility.

An invisible watermark

To statesman with, the visible watermark is added by AddPerceptibleWatermark:

CPBDoc::Save(...) | `-- perceptible-watermark prevention helper(bitmap, WatermarkSetting) | +-- WatermarkSetting::Never | `-- return the original bitmap | +-- WatermarkSetting::AskEveryTime | `-- show the Yes / No confirmation popup | +-- No: return the original bitmap | `-- Yes: continue | `-- Always aliases confirmed Yes +-- Paint::AI::GetPerceptibleWatermarkSvg() `-- Paint::AI::AddPerceptibleWatermark(bitmap, SVG stream) `-- composite the visible Copilot logo

Then location is besides a different WmkWriteWatermark function:

Watermarker.dll!WmkWriteWatermark( output_pixels, payload, payload_length, width, height, stride, input_pixels, pixel_format);

Tracing the telephone tree, we tin spot WmkWriteWatermark is called aft a section Stable Diffusion image generation. And if WmkWriteWatermark fails, Paint converts the full procreation into an correction alternatively than returning the image without it:

CocreatorViewModel::GenerateImageAsync(...) | `-- Paint::AI::StableDiffusionHelpers::GenerateAsync(..., watermarkId, ...) | `-- Microsoft.ImageCreation.ImageGenerator | `-- NPU-generated image result | +-- output safety/moderation checks | +-- Paint::AI::AddWatermark(bitmap, watermarkId) | | | `-- Watermarker.dll!WmkWriteWatermark(...) | | | +-- success: return the watermarked bitmap | `-- failure: move procreation into an error | `-- conception successful StableDiffusionResult

Then it is earthy to inquire what the incoming payload really is. It quickly becomes evident that it must beryllium 16 bytes:

if (payload_length < 16) return -6; if (payload_length > 16) return -5;

It is funny to maine that the codification is utilizing 2 different correction codes erstwhile the payload is excessively short aliases excessively long. The usability past ignores the magnitude parameter and uses a hard-coded loop bound erstwhile it copies the payload:

for (size_t one = 0; one < 16; i++) message.push_back(payload[i]);

We do not yet cognize what the 16-byte payload is, but arsenic we will spot later, it is simply a GUID! WmkWriteWatermark does not embed the GUID directly. Its wrapper constructs the pursuing 18-byte (144-bit) message:

0x4c || GUID[0..15] || (sum of the 16 GUID bytes modulo 256)

The halfway encoder rounds the usable image dimensions down to multiples of 8 and keeps 144 counters, 1 for each bit. It requires each spot to beryllium placed astatine slightest 3 times.

The encoder itself tin beryllium summarized as:

WmkWriteWatermark(output, guid, 16, width, height, stride, input, format) | +-- validate pointers, format, stride, and payload length +-- require width >= 192 and tallness >= 192 +-- conception payload | `-- 0x4c || GUID || byte-sum checksum +-- grow 18 bytes into 144 individual bits +-- information usable dimensions down to 8-pixel boundaries +-- scan/select suitable image blocks +-- quantize selected block/matrix values according to each bit +-- require astatine slightest 3 successful placements per bit | | | `-- insufficient capacity -> return -8 `-- reconstruct RGB pixels into the output buffer

The embedding loop performs mini quantized changes complete selected image blocks. It contains 3-by-5 matrix operations and a matrix-decomposition routine, and it uses constants including 24.0, 0.25, 0.5, and 0.2. This looks for illustration a content-adaptive block-domain, SVD-style watermark.

I americium not an master successful image watermarking, but 1 point should beryllium clear – this is an invisible watermark! AI moreover wrote immoderate codification to telephone this usability straight and tested it pinch a synthetic 512-by-512 BGRA image – 193,376 of the 262,144 pixels changed aft adding the watermark.

That led to the adjacent question. Where does the input of the watermark travel from?

a GUID from distant punctual moderation

At the WmkWriteWatermark boundary, the payload is only a pointer and a length. Knowing that it must beryllium 16 bytes was a clue, but galore things tin beryllium 16 bytes. I truthful started stepping backward done its callers. The contiguous wrapper successful PaintAIManager.dll has this symbolized signature:

Paint::AI::AddWatermark( Gdiplus::Bitmap& image, winrt::guid const& watermarkId);

winrt::guid, yikes! Now we cognize that the 16-byte watermark payload is so a GUID.

Further search the source, we find that the GUID really comes from a web request. Before Paint runs the section image model, AIServices.dll sends the punctual and style to:

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

The petition is JSON and contains astatine slightest these fields:

{ "prompt": "...", "style": "...", "lastPromptGenerationId": "..." }

The consequence parser expects:

{ "revisedPrompt": "...", "promptGenerationId": "...", "watermarkId": "...", "containsHumanReference": false }

Static study is nice, but astatine this constituent I wanted to spot a existent consequence from the server. I reused Paint’s ain authenticated convention and sent the following prompt done the moderation endpoint:

a cobalt bluish circle supra a mini orangish square

The server returned HTTP 200:

{ "revisedPrompt": "a cobalt bluish circle supra a mini orangish square", "promptGenerationId": "74d9e06b-adea-43ce-85fe-186a26e2e34a", "watermarkId": "83424621-03cb-40e3-9808-a9fae837156d", "containsHumanReference": false }

I besides tried the punctual a image of a smiling personification wearing a bluish hat. This clip the consequence contained a different brace of GUIDs and containsHumanReference was true. The section is truthful a server-side classification of whether the punctual refers to a human. Paint parses and stores it alongside the IDs, though I recovered nary grounds that it controls the watermarking measurement itself.

ParseModerateResponse parses some ID strings arsenic GUIDs and rejects zero values pinch InvalidPromptGenerationId aliases InvalidWatermarkId. The server’s watermarkId is what becomes portion of the generated image:

PaintUI.dll `-- IPromptModerationService `-- PaintAIManager.dll `-- AIServices.dll!ModerateAsync(...) | +-- build JSON | +-- prompt | +-- style | `-- lastPromptGenerationId | +-- HTTPS POST /v1/paint-cocreator/moderate-prompt | `-- AIServices.dll!ParseModerateResponse(response) +-- revisedPrompt +-- promptGenerationId -> parse arsenic GUID +-- watermarkId -> parse arsenic GUID `-- containsHumanReference | `-- PaintUI stores WatermarkId `-- StableDiffusionHelpers::GenerateAsync(..., watermarkId, ...) `-- section Stable Diffusion result `-- Paint::AI::AddWatermark(bitmap, winrt::guid const&) `-- WmkWriteWatermark(..., guid, 16, ...) `-- modified RGB pixels

In different words, “generated locally” does not mean that the complete cognition is local. Microsoft receives and moderates the prompt, past issues the unsocial GUID that Paint embeds into the locally generated image. Paint besides sends the erstwhile promptGenerationId arsenic lastPromptGenerationId pinch its adjacent moderation request, allowing successive requests to beryllium linked explicitly.

There is different portion to this story. Paint does much than change the pixels. It besides attaches C2PA Content Credentials to the saved file. The codification responsible for this lives successful ProvenanceHelper.dll, backed by provenancesdk.dll.

For the section Stable Diffusion path, the travel looks for illustration this:

local Stable Diffusion result | +-- Paint::AI::AddWatermark(bitmap, watermarkId) | `-- Watermarker.dll!WmkWriteWatermark(..., watermarkId, 16, ...) | `-- AIServices.dll!SignIngredientOnlineAsync(..., promptGenerationId, image, ...) | +-- POST /v1/paint-cocreator/image-sign | +-- imageMetadata | | +-- PromptGenerationId | | +-- GenerationSeed | | +-- CreativityLevel | | +-- AIFVersion | | `-- moderation scores | `-- imageToSign.jpg | `-- ParseProvenanceResponse(...) `-- server-supplied C2PA manifest `-- ProvenanceHelper::InsertManifestIngredient(...) `-- AuthoringFinalizeOutputToBufferAsync(...) `-- last image pinch C2PA metadata

Notice that the signing petition sends PromptGenerationId, while the image already contains the separately returned watermarkId. The server assigned some values during moderation, truthful it tin subordinate the signing petition pinch the watermark already coming successful the submitted pixels.

I past saved a existent image straight from Paint’s Image Creator and inspected its PNG chunks. Immediately aft IHDR was an 18,979-byte caBX chunk containing a signed C2PA manifest. The absorbing portion was this:

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

Decoded into thing much readable, the manifest says:

  • Generator: Microsoft Responsible AI Provenance
  • AI system: Azure OpenAI ImageGen
  • Action: c2pa.watermarked
  • Algorithm: com.microsoft.invismark.1
  • Watermark value: 83424621-03cb-40e3-9808-a9fae837156d
  • Description: Content watermarked by Microsoft Responsible AI

The server’s watermarkId, the identifier embedded into the pixels, and the C2PA c2pa.soft-binding.value are the aforesaid per-generation value.

That narration is important. C2PA calls this a soft binding: a worth derived from, aliases embedded into, the contented truthful that the contented tin still beryllium matched pinch its provenance grounds aft the file-level manifest has been removed. For a watermark soft binding, the worth is the watermark’s contented identifier. Microsoft cryptographically signed this assertion.

Why does Paint watermark locally?

At this point, the beingness of Watermarker.dll started to make much sense. Paint really has 2 alternatively different procreation paths.

The Image Creator characteristic I tested supra uses Azure OpenAI ImageGen. Generation, watermarking, and provenance packaging tin each hap successful Microsoft’s cloud, and Paint tin simply person a vanished image that already contains some the invisible watermark and C2PA manifest:

Image Creator `-- Microsoft cloud +-- contented filtering +-- Azure OpenAI ImageGen +-- invisible watermark +-- C2PA manifest `-- completed image returned to Paint

Cocreator is different. On a supported Copilot+ PC, Microsoft says that the NPU generates the image locally, while Azure online services still execute the information checks. The characteristic truthful requires some a Microsoft relationship and an net relationship moreover though the existent Stable Diffusion conclusion runs connected the device:

Cocreator connected a Copilot+ PC | +-- punctual -> Microsoft moderation service | +-- revisedPrompt | +-- promptGenerationId | `-- watermarkId | +-- revisedPrompt + sketch -> section NPU generation | +-- Watermarker.dll -> embed watermarkId locally | `-- online provenance signing -> last C2PA manifest

This is astir apt the logic Paint needs a section watermark implementation astatine all. A unreality generator tin watermark its output earlier returning it. A section generator cannot trust connected that, truthful Paint has to change the locally generated pixels itself. It besides explains why Paint treats a nonaccomplishment from WmkWriteWatermark arsenic a nonaccomplishment of the full procreation alternatively of softly returning an unmarked image.

There is different amazingly visible motion that Microsoft designed the prevention way astir provenance. When I prevention a generated consequence straight from the Image Creator pane, Paint offers precisely 1 format: PNG.

Paint only offers PNG erstwhile redeeming an AI-generated consequence directly

After an AI consequence is applied to the Paint canvas, the disposable formats are still restricted to PNG, JPEG, GIF, and Paint’s ain .paint format. BMP—the classical Paint format—is conspicuously absent.

This lines up pinch the formats supported by C2PA. PNG stores its manifest successful a caBX chunk, JPEG uses 1 aliases much APP11 marker segments, and GIF has its ain C2PA application-extension representation. The .paint format is controlled by Microsoft and tin sphere immoderate provenance authorities Paint requires. By contrast, the C2PA specification explicitly calls retired BMP arsenic a classical format that cannot embed arbitrary manifest information without utilizing an outer manifest. If Paint allowed the image to beryllium exported straight arsenic BMP, the file-level C2PA manifest would truthful disappear.

The divided besides raises an absorbing information mobility astir the unreality path. If the underlying distant image-generation endpoint tin beryllium made to return the generated image earlier watermarking and provenance packaging—or has an soul action that suppresses those stages—it mightiness beryllium imaginable to get a cloud-generated image pinch neither awesome attached.

How to categorize specified a way would dangle wholly connected Microsoft’s creation goal. It could beryllium intended behaviour if the underlying work is allowed to return earthy generations and Paint is simply responsible for applying the provenance layers. It could beryllium a merchandise bug if Microsoft overlooked the anticipation of personification calling the API straight and bypassing Paint’s watermarking step. Or it could beryllium a information vulnerability if Microsoft treats watermarking arsenic a mandatory abuse-prevention aliases provenance power and the endpoint tin beryllium made to bypass it. Without knowing the intended spot boundary, each 3 possibilities stay open.

Photos app does the aforesaid thing

While I was trying to find the Watermarker.dll connected disk, I happened to announcement that Microsoft Photos contains a DLL pinch the aforesaid name:

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

There are besides section Stable Diffusion operations down Photos’ Image Creator and Restyle Image features. Both lead to the aforesaid watermark wrapper:

Photos Image Creator `-- PerformSDTextToImageAndWatermarkAsync(..., promptGenerationId, ...) +-- tally the section text-to-image model `-- ApplyWatermark(image, promptGenerationId) +-- parse promptGenerationId arsenic a GUID +-- ConvertGUIDtoContiguousByteArray() +-- person RGBA to ARGB +-- Watermarker.dll!WmkWriteWatermark(..., guid, 16, ...) `-- person ARGB backmost to RGBA

Restyle Image takes the parallel path:

Photos Restyle Image `-- PerformSDSketchToImageAndWatermarkAsync(..., promptGenerationId, ...) `-- ApplyWatermark(image, promptGenerationId) `-- Watermarker.dll!WmkWriteWatermark(..., guid, 16, ...)

A subtle quality betwixt Photos and Paint is nonaccomplishment behavior. If the watermark encoder returns an error, its codification logs:

ApplyWatermark encountered error: ... - watermark will not beryllium applied.

It past appears to proceed returning the generated image. Paint alternatively treats a watermarking nonaccomplishment arsenic a procreation nonaccomplishment and the image is not returned to the user.

What Microsoft discloses

After doing this analysis, I recovered that Microsoft does disclose immoderate adjacent parts of the strategy connected its Image Creator support page. On contented filtering, it says:

“we use contented filtering to forestall the procreation of images”

The aforesaid page says that generated images:

“will incorporate C2PA manifest helping users place that it is an AI generated image.”

It besides explains that Image Creator uses Azure online services and says Microsoft collects personification and instrumentality identifiers together pinch prompts for maltreatment prevention and monitoring. That is simply a meaningful disclosure of distant filtering and C2PA metadata.

What the page does not explicate is that the C2PA manifest contains a GUID identifying the invisible pixel watermark, aliases that Paint’s section procreation way receives its watermark GUID from distant punctual moderation. Calling the characteristic “Content Credentials” is accurate, but it does not make this prompt-associated identifier evident to a Windows user.

Conclusion

To the champion of my knowledge, this is the first investigation to archive and analyse the invisible-watermarking behaviour of Paint and Photos. Visible watermarks connected AI-generated images are not new—Microsoft documents them for Microsoft 365 and Bing Image Creator—nor are invisible pixel watermarks specified arsenic Google’s SynthID and Bing’s hidden watermark.

Microsoft does disclose that Paint uses distant contented filtering and adds C2PA Content Credentials. The caller grounds shows that this metadata is not simply an unrelated file-level AI label: its signed c2pa.soft-binding assertion names Microsoft InvisMark and records the identifier carried by the invisible pixel watermark. The file-level manifest and pixel-level watermark are 2 layers of the aforesaid provenance system.

The section and unreality paths besides explicate the different section of labor. Cloud Image Creator tin return an already watermarked and signed image, while Cocreator must embed the server-issued identifier aft section NPU inference. In some cases, “local” does not mean offline: the punctual still goes to Microsoft for moderation, and the completed section consequence goes done online provenance signing.

This mightiness beryllium related to Article 50 of the EU AI Act, whose transparency rules took effect connected August 2, 2026 and require AI-generated contented to transportation a detectable, machine-readable mark—but not a prompt-specific GUID. Microsoft discloses the beingness of C2PA metadata, but I could not find a disclosure explaining the server-issued watermark GUID, its relation pinch punctual moderation, aliases its beingness successful the pixels. Those specifications transportation evident privateness and right-to-know implications.

It besides appears imaginable to modify Paint aliases Photos to bypass some punctual moderation and watermarking. But that does not supply a caller capability: anyone tin already tally Stable Diffusion straight without either mechanism.

More