Development Windows Hardening - ShadobaDev/Platemaker-qt GitHub Wiki

Windows security hardening

What the application does about DLL search-path injection, what it defers, and what it does not address.

Platemaker is an unsigned desktop app that ships a large bundled DLL closure (Qt, platemaker.dll, the whole libvips graph, the toolchain's runtime), all copied next to the executable (cross-platform notes). Third-party global hooks do inject themselves into the process (seen: LG OnScreen Control's ScreenSplitterHook64X.dll, RGB software) โ€” benign, but a reminder that the process has to take its own precautions. This page records the security posture and, importantly, what is deliberately not done and why.

What we do โ€” restrict the DLL search path

app/main.cpp calls SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32) as the first statement in main() โ€” before QApplication, before any post-main dynamic DLL load. This drops the current working directory and PATH from the default LoadLibrary search, closing the classic DLL search-order hijack / planting vector (drop a malicious zlib1/libpng on PATH or beside a data file and have our graph pick it up).

  • Why it's safe here: the entire dependency closure is co-located in the application directory, which LOAD_LIBRARY_SEARCH_APPLICATION_DIR still covers, and the exe's static imports are resolved before main() runs โ€” so startup linkage is unaffected. Only post-main dynamic loads are narrowed (Qt plugins, loaded by absolute path with their deps in the app dir; libvips operation DLLs at render time). Verified by launching + running a full render under both the Qt Creator run environment (which injects the Qt bin via PATH) and the installed build.
  • Robustness: the API is resolved dynamically via GetProcAddress, so it degrades to a no-op on any pre-Windows-8 host instead of failing to load, and sidesteps MinGW header/_WIN32_WINNT quirks. It logs a one-line startup notice (DLL hardening: โ€ฆ) so the code path is verifiable in DebugView / the Qt Creator Application Output.
  • The one consequence: SVG icons. Qt's SVG plugins (qsvgicon, qsvg) import Qt6Svg.dll, which nothing in the executable referenced, so it would load lazily โ€” after the search path no longer includes PATH, where a build-directory run finds the Qt bin. Every SVG then rendered blank. main() constructs a throwaway QSvgRenderer, which makes Qt6Svg.dll a load-time import resolved before the hardening runs. Deployed builds carry it in the app directory anyway.

What we defer โ€” blocking extension-point injection

The natural companion is SetProcessMitigationPolicy(ProcessExtensionPointDisablePolicy, โ€ฆ), which would block exactly the hook injection we observed, plus AppInit_DLLs and legacy IME DLLs. We deliberately do not ship it. It also disables legacy IMM32 IMEs and some accessibility / assistive tools. Broken IME would hurt non-Latin text entry for Korean / Japanese / Chinese authors โ€” a core webcomic audience โ€” and we have no way to validate that without a CJK IME test rig (modern TSF IMEs are usually unaffected; legacy ones are not). The benign, low real-world risk of hook injection (the attacker already needs hook-registration privileges on the machine โ€” not a remote vector) does not justify that regression. It stays an idea, not a plan: it costs more than it buys, and it is not on the TODO list.

What this does not address

Neither mitigation affects SmartScreen / AV reputation โ€” for an unsigned app that is per-file-hash and resets each release. Code signing is the real integrity/reputation fix; the search-path restriction is defence-in-depth layered under it, not a substitute.


See also: Releasing ยท Building