πŸ” ARCHIVE PASSWORD: github.com

SignGuiPatcher β€” Windows Watermark Remover

SignGuiPatcher

**Pure MASM x64 Β· Zero CRT Β· ~25 KB Β· Vista through Windows 11 Build 28000** *Deploys a COM-hijacked proxy DLL (`ExpIorerFrame.dll`) that patches shell32.dll's Import Address Table at runtime* *intercepting all rendering paths used by `CDesktopWatermark::s_DesktopBuildPaint`* *to suppress every desktop watermark string without touching any system file β€” built in pure MASM x64, fully reversible*

πŸ“š Table of Contents


The Problem

Windows displays desktop watermarks in the bottom-right corner of the screen in several scenarios:

  • Test Signing Mode β€” bcdedit /set testsigning on (unsigned drivers)
  • Evaluation builds β€” trial/demo editions of Windows
  • Insider / pre-release builds β€” build number + branch strings
  • Activation strings β€” "Activate Windows" reminders on unlicensed installs

All watermarks are rendered by shell32!CDesktopWatermark::s_DesktopBuildPaint, a single function called during desktop composition. On older builds (Vista–Win10) it uses GDI ExtTextOutW; on modern builds (Win11 26H1 / Build 28000+) it routes through UxTheme!DrawTextWithGlow (ordinal 126) for the glow effect. The tool intercepts every known rendering path.


Debugging Session

Step 1 β€” Locating the watermark renderer in shell32.dll

Symbols were loaded and the desktop watermark class was identified:

x shell32!CDesktopWatermark::*

Key methods:

shell32!CDesktopWatermark::s_DesktopBuildPaint
shell32!CDesktopWatermark::s_IsTestSigningEnabled
shell32!CDesktopWatermark::s_GetTestModeString
shell32!CDesktopWatermark::s_GetProductBuildString

s_DesktopBuildPaint is the root painter β€” it assembles all strings and calls the render API. Breaking on it confirmed it fires on every desktop repaint containing a watermark.

Step 2 β€” Disassembling s_DesktopBuildPaint on Build 28000

The function opens with a call to BrandingLoadStringForEdition (from winbrand.dll):

call    qword ptr [shell32+0x753C48]   ; BrandingLoadStringForEdition (delay IAT slot)
test    eax, eax
je      +0x9ED                          ; if result == 0 β†’ skip edition string

Initial hypothesis: returning 0 from BrandingLoadStringForEdition would cause an early exit at je +0x9ED and bypass all rendering.

This was wrong. Tracing the jump target revealed it does NOT skip rendering β€” it only zeroes the edition string buffer and falls through to s_GetProductBuildString at +0x102, which assembles the build number + branch string and proceeds to DrawTextWithGlow.

"Windows 11 Pro" disappeared from the watermark, but "Test Mode" and "Build 28000…" remained.

Step 3 β€” Finding DrawTextWithGlow as the actual renderer

WinDbg was used to inspect the shell32 delay-load import table for UxTheme:

dq SHELL32+753AB0 L1         ; IAT slot β€” holds current function pointer
ln poi(SHELL32+753AB0)       ; resolve symbol

Result: UxTheme!DrawTextWithGlow β€” ordinal 126.

Every watermark string (Test Mode, build number, activation text) is rendered through this single call. ExtTextOutW and DrawTextW are also present as fallback paths in older code, but on Build 28000 DrawTextWithGlow is the only active renderer.

Step 4 β€” Identifying the import mechanism (delay-load by ordinal)

DrawTextWithGlow is delay-loaded from UxTheme β€” its IAT slot holds a thunk stub until the function is first called. A standard value-scan would fail at DllMain time because UxTheme may not yet be resolved. The import name table (INT) must be scanned directly.

Scanning the INT revealed the entry for DrawTextWithGlow has bit 63 set β€” it is an ordinal-only import (no name string), ordinal 126 in bits 15:0:

INT entry: 0x800000000000007E   β†’   bit63=1 (ordinal), ordinal = 0x7E = 126

BrandingLoadStringForEdition from winbrand.dll is similarly delay-loaded, but imported by name (bit 63 clear); the INT entry points to IMAGE_IMPORT_BY_NAME with the ASCII name at RVA + 2.

Step 5 β€” Verifying shell32 resource IDs on Build 28000

An earlier diagnosis suggested shell32 resource IDs 33088–33123 (activation watermark strings) had been removed from Build 28000. PowerShell verification disproved this:

Add-Type -TypeDefinition '...'
[Win32]::LoadString([Win32]::LoadLibraryEx("shell32.dll", 0, 2), 33088)
# β†’ "Test Mode"

Resources are present. The LoadStringW hook that blocks IDs 62000/62001 (evaluation strings) and learns IDs 33088–33123 (live watermark patterns) is still necessary for full coverage on older builds where the GDI/USER32 paths are active.

Step 6 β€” Defense-in-depth: five hooks

Each hook targets a different rendering path that has been active across different Windows versions:

Hook Source DLL Import type Builds
LoadStringW api-ms-win-core-libraryloader regular IAT Vista+
ExtTextOutW gdi32 regular IAT Vista–Win10
DrawTextW user32 regular IAT some Win10
BrandingLoadStringForEdition winbrand delay IAT by name Win8+
DrawTextWithGlow UxTheme ord 126 delay IAT by ordinal Win11 Build 28000

On Build 28000, DrawTextWithGlow alone is sufficient. On older builds the first three hooks carry the load. All five are installed together for full compatibility.


How It Works

flowchart TD PATCHER["SignGuiPatcher.exe\nTrustedInstaller impersonation"] --> WRITE["Write ExpIorerFrame.dll\nto System32"] WRITE --> REG["Patch CLSID registry\ncapital-I trick"] REG --> RESTART["Restart Explorer"] RESTART --> LOAD["Explorer loads ExpIorerFrame.dll\nvia COM at startup"] LOAD --> FWD["Forward all exports\nto real ExplorerFrame.dll"] LOAD --> HOOKS["DllMain calls PatchShell32Imports"] HOOKS --> H1["Hook LoadStringW\nblock IDs 62000 / 62001"] HOOKS --> H2["Hook ExtTextOutW\ncheck branding patterns"] HOOKS --> H3["Hook DrawTextW\ncheck branding patterns"] HOOKS --> H4["Hook BrandingLoadStringForEdition\nreturn 0, zero buffer"] HOOKS --> H5["Hook DrawTextWithGlow\nreturn S_OK immediately"] H1 & H2 & H3 & H4 & H5 --> CLEAR["Desktop watermark suppressed"]

COM CLSID Hijack β€” the capital-I trick

Windows Explorer loads ExplorerFrame.dll via COM:

HKCR\CLSID\{ab0b37ec-56f6-4a0e-a8fd-7a8bf7c2da96}\InProcServer32
  (Default) = %SystemRoot%\system32\ExplorerFrame.dll

The patcher changes one character of the registry value to:

(Default) = %SystemRoot%\system32\ExpIorerFrame.dll
                                          ^
                                  capital I (U+0049) β€” visually identical to lowercase l (U+006C)

Windows loads ExpIorerFrame.dll (our proxy) instead of the real ExplorerFrame.dll. The filename is visually indistinguishable in Explorer and most file listing tools.

Proxy DLL β€” ExplorerFrame (ExpIorerFrame.dll)

The proxy DLL forwards all ExplorerFrame.dll exports via a .def file with ExplorerFrame.dll as the forwarding target, so Explorer's functionality is completely preserved. At DllMain attach, PatchShell32Imports is called to install all five IAT hooks.

IAT patching

Regular imports (LoadStringW, ExtTextOutW, DrawTextW):

  1. Walk IMAGE_NT_HEADERS64 β†’ DataDirectory[1] (import directory) to find the DLL descriptor
  2. Scan FirstThunk for the current function address
  3. VirtualProtect(PAGE_EXECUTE_READWRITE) β†’ overwrite slot β†’ restore old protection

Delay imports (BrandingLoadStringForEdition, DrawTextWithGlow):

  1. Walk DataDirectory[13] (delay-load directory) to find the ImgDelayDescr for the DLL
  2. Walk the INT (Import Name Table at rvaINT):
    • By name: bit 63 = 0 β†’ lower 32 bits = RVA of IMAGE_IMPORT_BY_NAME, name at +2
    • By ordinal: bit 63 = 1 β†’ bits 15:0 = ordinal number
  3. Patch the corresponding IAT slot at the same index in rvaIAT

Scanning the INT works regardless of whether the DLL is loaded β€” the name/ordinal data is always present in the PE image, unlike the IAT slots which hold thunk stubs until first call.

Hook implementations

InterceptedLoadStringW β€” blocks resource IDs 62000 (0xF230) and 62001 (0xF231) (evaluation/watermark string table entries). All other IDs are forwarded. As a side effect, when shell32 loads resource IDs 33088–33123 (live activation strings), the hook copies each string into g_brandingPatterns[] so the text-level hooks have accurate patterns.

InterceptedExtTextOutW / InterceptedDrawTextW β€” call ContainsBrandingWatermark() on the text argument. If any loaded pattern matches (substring search via WideStrFind), the call is suppressed (returns TRUE / 0 without drawing). Otherwise tail-called to the original function preserving all stack arguments.

InterceptedBrandingLoadStringForEdition β€” zeroes the output buffer and returns 0. This removes the edition string from the watermark on builds that display it.

InterceptedDrawTextWithGlow β€” returns S_OK (0) immediately. One-instruction leaf function. Suppresses all glow-rendered watermark text on Win11 Build 28000+.

TrustedInstaller deployment

Writing to System32 and modifying HKCR\CLSID requires TrustedInstaller privileges. The patcher:

  1. Opens the TrustedInstaller service and retrieves its process token
  2. Duplicates the token as an impersonation token
  3. Calls ImpersonateLoggedOnUser to assume TI identity
  4. Extracts ExpIorerFrame.dll from the embedded CAB resource (via FDI)
  5. Writes the DLL to %SystemRoot%\system32\ExpIorerFrame.dll
  6. Updates the registry value
  7. Calls RevertToSelf to drop TI impersonation
  8. Restarts Explorer (TerminateProcess β†’ WaitForMultipleObjects(500ms) β†’ ShellExecuteExW)

Restore reverses steps 4–6: registry value is reset to the original, DLL is deleted (MoveFileExW(MOVEFILE_DELAY_UNTIL_REBOOT) if locked), Explorer is restarted.


Usage

GUI mode

Run SignGuiPatcher.exe as Administrator (UAC manifest embedded β€” elevation prompt appears automatically).

  • APPLY PATCH β€” deploys DLL, patches registry, restarts Explorer
  • RESTORE β€” reverts registry, removes DLL, restarts Explorer
  • Status indicator shows current state in real time (green / red / orange during transition)

CLI mode

SignGuiPatcher.exe [switch]

  (no switch)   GUI mode
  -apply        Apply watermark patch
  -restore      Remove watermark patch
  -status       Query patch status
  /? -h -help   This help

Example β€” scripted deploy:

Start-Process SignGuiPatcher.exe -ArgumentList '-apply' -Verb RunAs -Wait

Status exit codes: 0 = success, non-zero = failure (check console output).


Requirements

OS Windows Vista through Windows 11 (tested on Build 10.0.28000)
Arch x64 only
Privileges Administrator (UAC prompt) + TrustedInstaller (obtained internally)

Building from Source

Requires Visual Studio 2022 / Build Tools v17+ with MASM (ML64) component.

Full build (DLL + EXE, packaged)

.\build.ps1

This runs both sub-project builds and produces WaterMarkRemover\bin\SignGuiPatcher.exe with ExpIorerFrame.dll embedded as a CAB resource.

Sub-project builds

.\ExplorerFrame\build.ps1     # compiles ExpIorerFrame.dll
.\WaterMarkRemover\build.ps1  # compiles SignGuiPatcher.exe (embeds DLL from above)

Project layout

SignGuiPatcher/
β”œβ”€β”€ ExplorerFrame/              Proxy DLL (ExpIorerFrame.dll)
β”‚   β”œβ”€β”€ x64/
β”‚   β”‚   β”œβ”€β”€ consts.inc          Win32 constants, IAT/delay-IAT offsets
β”‚   β”‚   β”œβ”€β”€ globals.inc         Exported data (branding pattern table)
β”‚   β”‚   β”œβ”€β”€ main.asm            DllMain β€” calls PatchShell32Imports on attach
β”‚   β”‚   β”œβ”€β”€ patch.asm           IAT walker, delay-IAT walker, PatchShell32Imports
β”‚   β”‚   β”œβ”€β”€ intercept.asm       Five interceptor functions + ContainsBrandingWatermark
β”‚   β”‚   β”œβ”€β”€ patterns.asm        g_brandingPatterns table + static seed strings
β”‚   β”‚   β”œβ”€β”€ strutil.asm         wcslen_p, wcscpy_p, WideStrFind
β”‚   β”‚   └── forward.asm         Export forwarder stubs β†’ ExplorerFrame.dll
β”‚   β”œβ”€β”€ ef.def                  Module definition: all exports forwarded
β”‚   β”œβ”€β”€ ef.manifest             DLL manifest (no CRT, no activation context)
β”‚   β”œβ”€β”€ ef.rc                   Version resource
β”‚   └── build.ps1               ML64 + LINK, /NODEFAULTLIB, produces ExpIorerFrame.dll
β”‚
β”œβ”€β”€ WaterMarkRemover/           Patcher EXE (SignGuiPatcher.exe)
β”‚   β”œβ”€β”€ x64/
β”‚   β”‚   β”œβ”€β”€ consts.inc          Constants (TI token, FDI, registry, process flags)
β”‚   β”‚   β”œβ”€β”€ globals.inc         Global state
β”‚   β”‚   β”œβ”€β”€ main.asm            Entry point, GUI/CLI dispatch
β”‚   β”‚   β”œβ”€β”€ patch.asm           TI impersonation, DLL deploy, IAT-free registry patch,
β”‚   β”‚   β”‚                       KillExplorer, StartExplorer
β”‚   β”‚   β”œβ”€β”€ cli.asm             CLI argument parser and dispatch
β”‚   β”‚   β”œβ”€β”€ window.asm          Win32 GUI (WNDCLASSEX, dialog, buttons, dark mode, Mica)
β”‚   β”‚   β”œβ”€β”€ token.asm           GetTIToken β€” TrustedInstaller token acquisition
β”‚   β”‚   └── strutil.asm         wcscpy_p, wcscat_p, wcscmp_ci
β”‚   β”œβ”€β”€ sp.manifest             UAC requireAdministrator, SupportedOS
β”‚   β”œβ”€β”€ sp.rc                   Icon + CAB resource (RCDATA 102 = ExpIorerFrame.dll as CAB)
β”‚   └── build.ps1               ML64 + LINK + embed DLL into RCDATA resource
β”‚
β”œβ”€β”€ IcoBuilder/
β”‚   └── SignGuiPatcher.ico      Application icon
β”‚
β”œβ”€β”€ build.ps1                   Master build: ExplorerFrame β†’ IcoBuilder β†’ WaterMarkRemover
└── images/                     Screenshots for README

Technical highlights

Feature Detail
Pure MASM x64 Zero CRT, zero C++ runtime, no MSVCRT import
~25 KB binary DLL embedded as CAB resource
Delay-import INT scan Patches slots before the target DLL is loaded β€” name or ordinal directly from DataDirectory[13]
Five-hook defense-in-depth Covers every rendering path from Vista to Build 28000+
ContainsBrandingWatermark Pattern table updated live from LoadStringW interception; handles %xxx%middle%suffix segment format
TrustedInstaller impersonation No kernel driver, no service install, no DKOM
Fully reversible Registry single-value patch, DLL removed on restore
UAC manifest embedded Elevation prompt at launch, no external manifest file
GUI: dark mode + Mica DwmSetWindowAttribute(DWMWA_USE_IMMERSIVE_DARK_MODE) + DWMWA_SYSTEMBACKDROP_TYPE = DWMSBT_MAINWINDOW
x64 ABI compliant RSP ≑ 0 mod 16 at every CALL, shadow space, non-volatile register save/restore, proper leaf functions

Compatibility

Windows version LoadStringW ExtTextOutW DrawTextW BrandingLS DrawTextWithGlow
Vista / 7 βœ“ βœ“ β€” β€” β€”
8 / 8.1 βœ“ βœ“ β€” βœ“ β€”
10 (early) βœ“ βœ“ βœ“ βœ“ β€”
10 (22H2) βœ“ βœ“ βœ“ βœ“ β€”
11 (22H2–25H2) βœ“ β€” β€” βœ“ βœ“
11 Build 28000 βœ“ β€” β€” βœ“ βœ“

Hooks that are not active on a given build are installed anyway (IAT slot not found β†’ no-op).


Disclaimer

This tool is intended for removing watermarks on systems you own or administer β€” evaluation lab machines, insider preview installs, test-signing development environments. It is not a license bypass or activation crack; it suppresses visual text only.

All changes are fully reversible via the Restore function. The author is not responsible for any unintended consequences.


MIT License β€” GitHub

Author: Marek WesoΕ‚owski (WESMAR)
Contact: [email protected]
Website: kvc.pl