
Signal Messenger is widely recognized for protecting private communications, but secure messaging depends on controls beyond encryption. This Signal Messenger security case study examines how IOActive tested Signal Desktop’s Windows screen-capture protection and what the results reveal about evaluating application controls against realistic attacker capabilities.
Signal Messenger Security: At a glance
| Problem |
| Signal Desktop’s Windows content-protection feature prevents the application window from appearing in screenshots and screen recordings. However, the control relies on the assumption that only Signal’s own process will change the window’s display-affinity setting. |
| Solution |
| IOActive traced the feature from Signal’s open-source code through Electron and the Windows graphics stack, then tested whether the protection could be removed from outside the application. The team also analyzed the Windows ownership check that enforced the control. |
| Results |
| Direct calls from another process failed, including calls from an administrator process. IOActive successfully removed the protection by executing the relevant API call within Signal’s process using CreateRemoteThread, demonstrating that local code execution can undermine this type of defense-in-depth control. |
“Security controls should be tested against the conditions attackers can create, not only the conditions they were designed to handle.”
The security control under review
The investigation began when IOActive Senior Security Consultant Taha Draidia noticed that Signal Desktop was missing from a screen-sharing session. Signal Desktop is built with Electron, and its publicly available source code exposed the relevant contentProtection setting. A search of the code identified the call to window.setContentProtection(contentProtection), which enables or disables the feature.
According to the Electron BrowserWindow documentation, enabling content protection on Windows maps to the Windows API SetWindowDisplayAffinity, using the WDA_EXCLUDEFROMCAPTURE flag. On supported Windows versions, this excludes the application window from screen captures. On older versions, the behavior falls back to displaying a black rectangle.
The control is implemented below the application interface, in the Windows desktop and graphics stack. That makes it more difficult to defeat through ordinary application settings, but it does not make the control independent of the endpoint’s security state.
How IOActive tested the protection
The research team first identified Signal’s top-level window and obtained its window handle. The Windows APIs EnumWindows, GetWindowTextW, and GetWindowThreadProcessId can be combined to locate a window by its title and associated process.
The relevant function accepts a window handle and an affinity value:
WDA_NONE, which removes the capture restrictionWDA_MONITOR, which limits content to a monitorWDA_EXCLUDEFROMCAPTURE, which excludes the window from capture
IOActive evaluated three ways to remove the restriction.
| Research phase | Method tested | Result | Security lesson |
| 1 | Call SetWindowDisplayAffinity(hwnd, WDA_NONE) from another process | Rejected with ERROR_ACCESS_DENIED | A valid window handle does not automatically grant permission to change every property associated with that window. |
| 2 | Repeat the call from an elevated administrator process | Rejected with the same error | The control is based on process ownership, not simply administrator privilege. |
| 3 | Execute the API call within Signal’s process using CreateRemoteThread | Successful | Legitimate APIs can be combined with process injection to satisfy an ownership check that blocks cross-process calls. |
Sources for the table: IOActive’s original research, Microsoft’s SetWindowDisplayAffinity documentation, and Microsoft’s CreateRemoteThread documentation.
Why the first attempts failed
The first attempt was straightforward: identify Signal’s window and call SetWindowDisplayAffinity from a separate process. Windows rejected the request with ERROR_ACCESS_DENIED.
IOActive repeated the test from a process running with administrator privileges. The result did not change. Elevation was not enough because the check was not primarily evaluating whether the caller was an administrator. It was evaluating whether the caller belonged to the process that owned the target window.
The test exposed an important assumption behind the control. A security control may correctly enforce its documented API contract while still depending on a broader assumption about the endpoint. In this case, the control expected the API call to originate from Signal’s own process. It did not establish a stronger boundary against an attacker who could execute code in that process.
The successful bypass
The third approach used CreateRemoteThread to execute the display-affinity call inside Signal’s process context. The proof of concept is available in our public GitHub repository.
Because the call originated within Signal’s process, the process-ownership check was satisfied. The protection was then removed without requiring the API call to come from an administrator process.
This result does not mean Signal’s end-to-end encryption was broken. The research evaluated a Windows desktop presentation control, not Signal Messenger’s cryptographic design. The finding demonstrates that screen-capture protection is a defense-in-depth measure whose effectiveness depends on the integrity of the local endpoint.
Confirming the assumption through kernel analysis
IOActive did not stop after demonstrating the behavior. The team reverse-engineered the Windows implementation to understand why the first two approaches failed.
The user-mode call chain leads from user32!SetWindowDisplayAffinity to the NtUserSetWindowDisplayAffinity system call. The ownership enforcement occurs deeper in the Windows graphics subsystem. The kernel compares the process associated with the target window against the process associated with the calling thread. If those values differ, Windows returns ERROR_ACCESS_DENIED.
A remote thread changes that condition by causing the API call to execute inside the target process. The bypass does not defeat the ownership comparison directly. It satisfies the comparison by moving execution into the process that owns the window.
IOActive also examined research into the Desktop Window Manager, including the DWMShield proof of concept, which reaches the internal graphics routine responsible for applying the capture-exclusion state. Together, these paths illustrate that a control enforced in the graphics stack can still be affected by code execution elsewhere in the operating system.
What this case study means for product security
This Signal Messenger security investigation offers several lessons for software and product teams:
First, application security controls should be evaluated against the actions available to a local attacker, not only ordinary user behavior. If an attacker can execute code within a trusted process, controls that rely on process identity may no longer provide the intended protection.
Second, API-level correctness is not the same as system-level resilience. SetWindowDisplayAffinity correctly rejects unauthorized cross-process calls. The weakness lies in combining that control with another legitimate capability: process injection.
Third, security testing should follow a control through its complete implementation path. IOActive combined source-code review, API analysis, Windows internals research, reverse engineering, and offensive testing. That combination exposed an assumption that would have been difficult to identify through surface-level functional testing alone.
Finally, screen-capture protection should be treated as one layer in a larger endpoint security strategy. It can reduce casual or routine capture, but it cannot replace endpoint protection, application hardening, monitoring, and defenses against local code execution.
Signal Messenger Security: Major takeaways
IOActive’s research into Signal Desktop shows why product security requires more than verifying that a security setting works under normal conditions. The screen-capture control performed as designed against ordinary capture attempts and rejected direct calls from standard and elevated external processes. However, deeper testing showed that code executing within Signal’s own process could remove the restriction.
The case study positions Signal Messenger security within a broader engineering principle: security controls must be tested against the conditions an attacker can create. IOActive’s work across application security, reverse engineering, Windows internals, and offensive testing shows how research-led testing can reveal the difference between a control that works as documented and a control that remains effective under realistic attack conditions. It also demonstrates how IOActive goes above and beyond to find the root cause of important cybersecurity threats, with a research-led workflow that makes them one-of-a-kind in the industry.
Need to evaluate your own application security controls against realistic attacker capabilities? Contact IOActive to discuss Signal Messenger security, product security research, and advanced offensive testing.
