VoidStealer: Debugging Chrome to Steal Its Secrets


Key Points
- VoidStealer is the first infostealer observed in the wild adopting a novel debugger-based Application-Bound Encryption (ABE) bypass technique that leverages hardware breakpoints to extract the
v20_master_keydirectly from browser memory. - The bypass requires neither privilege escalation nor code injection, making it a stealthier approach compared to alternative ABE bypass methods.
- In this technical blog post, we dissect the bypass technique step-by-step, from locating the target address for breakpoint placement to extracting the
v20_master_key. - To support the defense community, Gen Threat Labs provides practical detection strategies and indicators to help defenders identify and mitigate this novel bypass technique.
Introduction
When Google introduced Application-Bound Encryption (ABE) in July 2024 with Chrome 127, it didn't mark the end of infostealers – as expected, infostealers adopted quickly and came up with various methods to bypass it. Still, it undoubtedly raised the bar for accessing sensitive browser data, and, more importantly, significantly increased the visibility of such data theft attempts, as bypassing ABE now requires attackers to perform additional steps that are inherently more suspicious. Various bypass techniques have emerged since then, and since each comes with its own trade-offs, new approaches continue to appear as threat actors seek to minimize the footprint and evade detection.
In this technical blog post, we dissect a novel debugger-based ABE-bypass technique that is both technically elegant and offers a significant advantage over other existing bypass methods: a much lower detection footprint. The technique relies on attaching to the browser process as a debugger and setting breakpoints at carefully selected locations to extract the v20_master_key directly from the browser process memory. We demonstrate the technique on VoidStealer, the first emerging infostealer we have observed using this approach in the wild.
VoidStealer
VoidStealer is a MaaS infostealer that began being marketed on several darkweb forums since mid December 2025 and has been actively developed since then.

Considering the first version of VoidStealer from December 12, 2025, as the earliest date we have identified it being offered, the VoidStealer development timeline is approximately as follows (please note that exact dates may vary slightly, as we rely on announcements made by the malware developers themselves on HackForums):
| VoidStealer version | Release Date |
| v1.0 | Dec 12, 2025 |
| v1.1 | Jan 16, 2026 |
| v1.2 | Jan 19, 2026 |
| v1.3 | Feb 3, 2026 |
| v1.4 | Feb 4, 2026 |
| v1.5 | Feb 22, 2026 |
| v1.6 | Mar 3, 2026 |
| v1.7 | Mar 9, 2026 |
| v1.8 | Mar 10, 2026 |
| v1.9 | Mar 10, 2026 |
| v2.0 | Mar 13, 2026 |
v2.1 | Mar 18, 2026 |
The version 2.0, reported on Mar 13, 2026, introduced the novel ABE bypass technique.

To keep the blog post to the point, we focus mainly on the bypass technique itself rather than VoidStealer's capabilities.
ABE Bypass(es)
Similarly to other infostealers, VoidStealer does not rely on a single ABE-bypass technique but implements two distinct methods in case one fails. The first is a well-documented approach that involves injecting into the browser process and invoking IElevator::DecryptData from the browser's context via the COM interface. Since this is a well-known and thoroughly documented technique, we will not detail it here (for those interested, it is extensively documented in the ChromElevator project). Nevertheless, it is worth noting that, however reliable it is, this technique relies on process injection, which is noisy and suspicious and might easily get flagged by security tools. The second (novel) approach is stealthier in this regard – it relies solely on the WinAPI function ReadProcessMemory and debugger attachment, which are considerably less suspicious than direct browser memory injection.
To clarify how the bypass operates and what it exploits, let's take a step back and briefly recap what the idea behind Application-Bound Encryption is and how Chrome uses it to protect passwords and other sensitive data. Chrome passwords are encrypted and stored in a SQLite3 database located at %LOCALAPPDATA%\Google\Chrome\User Data\Default\Login Data (assuming standard Chrome installation on Windows) and can be accessed, for example, by running the following Python script:

The script output for a single record would look roughly as follows (note the v20 prefix in the encrypted password, indicating ABE-protected data):

The password value is encrypted using AES-GCM with the app_bound_key, which we will refer to as the v20_master_key going forward. The decryption process begins by removing the v20 prefix from the encrypted string, then extracting the following 12 bytes to use as the nonce, and finally treating the remaining data as the combined ciphertext and authentication tag. Below is a simple Python script demonstrating this decryption process:


At this point, it is crucial to highlight that the knowledge of the v20_master_key is sufficient for decrypting any ABE-protected data tied to the application (in this case, Chrome). Where does this key come from, though? Chrome stores it in an encrypted form on disk in a JSON file %LOCALAPPDATA%\Google\Chrome\User Data\Local State under the ["os_crypt"]["app_bound_encrypted_key"] field. Extracting the actual key in unencrypted form (v20_master_key) requires base64-decoding the value, removing the APPB prefix, calling CryptUnprotectData as NT AUTHORITY\SYSTEM, then calling it again as the logged-in user, and finally, specifically for Chrome, performing additional post-processing that we will not cover in detail here (for those interested, see the Chromium source code). The ABE-protected password decryption workflow is illustrated in Figure 7.

To summarize, the fundamental security mechanism behind ABE lies in that the v20_master_key, which is used to encrypt/decrypt the sensitive browser data, is protected by CryptProtectData invoked as NT AUTHORITY\SYSTEM, the highest-privileged account on Windows.
Chrome, however, runs in the context of the logged-on user, not as SYSTEM. So how does it decrypt the key? To bridge that gap, Google implemented a separate service, Google Chrome Elevation Service (elevation_service.exe), which runs with SYSTEM privileges and handles the decryption. When Chrome needs to decrypt the app_bound_encrypted_key, it invokes IElevator::Decrypt() via a COM interface. The elevation service "validates" that the caller is Chrome, and if the check passes, returns the decrypted v20_master_key (the word validates is quoted as the check is not particularly robust and opens the door to another bypass). Chrome can then use the v20_master_key to encrypt or decrypt sensitive data such as cookies and passwords.

It is obvious that if the malware managed to run as NT AUTHORITY\SYSTEM, it could simply replicate all the steps and directly decrypt the app_bound_encrypted_key (and that is also one of the ABE bypass techniques). However, such a bypass still requires SYSTEM-level privileges, limiting its applicability to only some environments. Therefore, infostealers typically use it as a complementary method when other approaches fail, rather than relying on it as their primary technique.
There is also the option of injecting into Chrome and making the key decryption request directly from within the context of the browser, a commonly used bypass technique that VoidStealer itself employs. While this approach avoids the need for SYSTEM privileges, it still requires the injection, which is noisy.
This perfectly illustrates our earlier point – ABE does not prevent data theft, but it undoubtedly forces attackers into more visible actions, thus introducing great detection/hunting opportunities for us, defenders.
What prevents infostealers from reading the v20_master_key directly from the browser's memory, though? Chrome implements several protective measures to prevent it. First, the key is retained in memory in plaintext only for the minimum duration required to complete the cryptographic operation, minimizing its exposure. Additionally, the key is encrypted using the CryptProtectMemory function with the CRYPTPROTECTMEMORY_SAME_PROCESS flag, ensuring that even if an attacker could read the encrypted form of the key from memory, they would still need to invoke the complementary decryption function, CryptUnprotectMemory, directly from the Chrome process to decrypt it. And that would require a code injection, which is noisy.
Nevertheless, despite these safeguards, there is still a critical weakness. Regardless of how small the key exposure window is, there is still a moment in which the v20_master_key is present in memory in plaintext. And that is precisely what the novel technique exploits. By attaching to the browser process as a debugger and setting a breakpoint at the precise moment when the key is present in plaintext, an attacker can extract it directly from the memory. Importantly, this can be done without any privilege escalation and, when using hardware breakpoints rather than software ones, without any writes to the browser process.
Note that the VoidStealer developers did not invent the technique. They directly adopted it from the open-source ElevationKatz project, developed by Meckazin, the same author behind CookieKatz and CredentialKatz, which were among the first tools capable of bypassing ABE since its introduction. Although there are some differences, based on our analysis, it is evident that VoidStealer's implementation is directly based on ElevationKatz, which has now been public for over half a year. Yet, VoidStealer is the first malware we are aware of employing this technique in the wild.
In the following section, we focus specifically on VoidStealer's implementation of the bypass.
Technical Walkthrough of VoidStealer's Implementation of the Novel ABE Bypass
The success of the entire technique boils down to finding the right address on which to set the breakpoint. VoidStealer begins by spawning a browser process via CreateProcessW with the CREATE_SUSPENDED and SW_HIDE flags. It then resumes the main thread and immediately attaches a debugger to the newly spawned process via DebugActiveProcess.
While it may not be immediately obvious, beginning with this step is deliberate and strategically important as browsers typically load cookies into memory during startup. And since the cookies are ABE-protected, the browser must decrypt the v20_master_key during its initialization. This makes the browser startup an ideal opportunity to intercept and steal the key.

It is unclear to us why VoidStealer launches the browser in a suspended state (an additional suspicious indicator), only to resume and attach a debugger immediately afterward, rather than directly spawning the browser under a debugger. Nevertheless, we have verified that the same bypass technique works equally well when the browser is spawned directly using CreateProcessW with the DEBUG_ONLY_THIS_PROCESS flag. We assume that this is simply a consequence of copy-pasting the code from ElevationKatz, which follows the same approach.
Once attached as a debugger, VoidStealer begins listening for debug events via WaitForDebugEvent.

Since VoidStealer targets only Chrome and Edge (like ElevationKatz), the code address the bypass tries to find resides in either chrome.dll or msedge.dll (though it could readily be extended to other Chromium-based browsers). Because VoidStealer attaches to the browser as a debugger almost immediately after launch, before all DLLs have been loaded, it cannot simply search for the target module right away. Instead, it waits for LOAD_DLL_DEBUG_EVENT (sent to the debugger whenever a new DLL is loaded into the debugged process memory) and checks each loaded module against the target DLL.

Once the target DLL is loaded, VoidStealer scans its entire .rdata section using ReadProcessMemory to find the string OSCrypt.AppBoundProvider.Decrypt.ResultCode, or more precisely, its address.

A good question is: why this particular string? The answer lies in the Chromium source code, which reveals that this string appears immediately after the call to os_crypt::DecryptAppBoundString. Thus, this is precisely the point at which the v20_master_key is present in the browser's memory in plaintext.

Furthermore, there is only one cross-reference to that string, making it a perfect target for the breakpoint.

Following the cross-reference in chrome.dll, we can see that the disassembled code corresponds to the source code and that the cross-reference comes from a LEA instruction.

The important bytes from the LEA instructions are the last four, as they specify the 32-bit displacement used to calculate the actual address, which is to be loaded into the RCX register.

The CPU essentially calculates the address as follows: RCX = (instruction_address + 7) + (signed int32) disp32. To find the LEA instruction referencing the target string, VoidStealer scans the whole .text section and searches for the byte sequences 48 8D 0D (the beginning of the LEA instruction), extracts the following 4 bytes (the 32-bit displacement), and calculates the referenced address. It then compares each calculated address against the OSCrypt.AppBoundProvider.Decrypt.ResultCode string address to identify the matching LEA instruction referencing it, which becomes the target address for the breakpoint.

If the search is successful, VoidStealer sets a hardware breakpoint at this address across all browser threads by resolving NtGetNextThread from ntdll.dll and iterates through each thread, suspends it, writes the breakpoint address into DR0, enables it via DR7, and resumes the execution.


To ensure complete coverage, VoidStealer’s debug loop also handles CREATE_THREAD_DEBUG_EVENT events to set up the hardware breakpoint for any newly created thread.

At this point, it might be worth explaining why hardware breakpoints are chosen over software ones. Setting a software breakpoint requires patching the binary by overwriting the target address with, for example, an INT3 (0xCC) instruction, necessitating a memory write into the browser’s memory. And that is precisely what attackers aim to avoid. Hardware breakpoints, on the other hand, can be configured entirely through SetThreadContext without ever modifying the browser’s memory, making the bypass stealthier.
Once the breakpoints are in place, all that is left is to wait for them to trigger. Since cookies are protected by ABE and the browser needs to load them at startup, it must request the v20_master_key decryption shortly after launching to decrypt them from disk. Therefore, there is a high chance that the breakpoint will be triggered shortly after the browser starts.


If the breakpoint is placed on the LEA RCX, OSCrypt.AppBoundProvider.Decrypt.ResultCode address, then in current builds, the R14 register (for Edge) or R15 register (for Chrome) holds a pointer to the v20_master_key. From there extracting the key straight from the memory is only two ReadProcessMemory calls away.
To make it easier to grasp the big picture of the entire bypass, we have illustrated the complete workflow in Figure 23.

Detection Strategies for Defenders
We should first emphasize that different malware authors will likely adapt the bypass technique in various ways – for example, they may not target the v20_master_key usage immediately after startup (in which case they could leverage an already-running browser instance). For detection purposes, it is crucial to identify what is unlikely to change across all possible variations. Regardless of implementation details, the bypass fundamentally relies on setting hardware breakpoints at strategic locations, which requires either attaching a debugger or launching the browser under a debugger, along with browser memory reads.
These invariants provide strong detection opportunities, especially in sandboxed environments, as legitimate applications do not debug browsers. What about legitimate debuggers, though? Even those require users to explicitly specify which application to debug. Therefore, if an application starts debugging a browser autonomously, it should be a clear red flag.
But beyond that, there are other detection opportunities. While reading browser memory may seem less suspicious than writing to it, as this bypass demonstrates, it actually can be. Therefore, monitoring reads from browser memory can provide valuable detection signals, as benign applications rarely have legitimate reasons to access it.
Lastly, the technique requires a running browser instance, which malware will typically try to hide from the user. Whether by launching it via CreateProcess with the SW_HIDE flag, running it in headless mode, creating a new desktop and running it from there, or positioning the browser window off-screen. These evasion tactics can serve as additional indicators of such malicious activity.
Summary
VoidStealer is the first infostealer observed in the wild employing a debugger-based ABE bypass technique (directly adapted from the publicly available ElevationKatz project), highlighting the continuously evolving landscape of Application-Bound Encryption bypasses. Unlike traditional bypass methods, it requires neither privilege escalation nor code injection, making it a stealthier alternative.
Given these advantages, combined with the availability of a public PoC (ElevationKatz), it is only to be expected that more infostealers will adopt the bypass technique in the future.
IoC
f783fde5cf7930e4b3054393efadd3675b505cbef8e9d7ae58aa35b435adeea4 (VoidStealer v2.0)