Decrypting a DPAPI-NG pinned clipboard item on a Microsoft account
Relizane is Down! is a 500-point forensics challenge I wrote for L3akCTF 2026. Its first question asks for the last text a user copied, and the answer is a pinned clipboard item sealed with DPAPI-NG on an account that signs in with a Microsoft account. The NT hash is a dead end; the key comes out of the attacker's own staging archive.
Relizane is Down! ran at L3akCTF 2026, from 31 July to 2 August, as a 500-point forensics challenge. Players get a KAPE triage of a Windows 10 machine whose user, bello, says he was only browsing the web and never ran anything. The service asks 20 questions in order, four wrong answers on any one of them end the connection, and the first one gates the rest:
What was the last text copied by the user?
This post is about that question: where Windows keeps that text, why the route most people try first goes nowhere, and the chain of keys that actually opens it. Answers and the flag stay out of it.
Clipboard storage
With clipboard history turned on, Windows keeps copied items under the user’s profile, in AppData\Local\Microsoft\Windows\Clipboard. There are two folders. HistoryData holds the rolling history, and in the triage it contains only an empty GUID folder, though its timestamps still date the copy. Pinned holds the items the user pinned, and those survive.
There’s a second place people look, ActivitiesCache.db and its SmartLookup table. It’s empty here, because that copy depends on the account syncing clipboard data through the cloud.
The pinned item is a folder with a metadata.json and one file per clipboard format, named in base64: VGV4dA== is Text and TG9jYWxl is Locale. The metadata says what kind of problem they are:
{"formatMetadata":{"Locale":{"dataType":"Stream","collectionType":"None","isEncrypted":true },
"Text":{"dataType":"String","collectionType":"None","isEncrypted":true}},
"sourceAppId":"","property":{}}Both files are DPAPI-NG blobs: CMS EnvelopedData, the same container S/MIME uses. Inside sits a recipient whose key-encryption key is protected by classic DPAPI, which means everything comes down to one of bello’s DPAPI masterkeys in AppData\Roaming\Microsoft\Protect\<SID>.
DPAPI on a Microsoft account
The standard move for a local account is to pull the NT hash out of SAM and SYSTEM, crack it, and use the password to decrypt the masterkey. Players did exactly that. secretsdump gives the hash easily enough. It doesn’t crack, and even if it did, it wouldn’t help.
bello signs in with a Microsoft account. On those accounts the user’s masterkeys aren’t derived from the logon password. Windows generates a random DPAPI password for the account and protects the masterkeys with that. The only copy of it sits in the system profile, under config\systemprofile\AppData\Local\Microsoft\Windows\MicrosoftAccount\<id>\CacheData, encrypted with the real Microsoft account password. That password lives with Microsoft and never touches the disk.
So to read a pinned clipboard item from this triage you need the Microsoft account password, and nothing on the machine stores it.
KeePass dump, CVE-2023-32784
What the disk does hold is the attacker’s staging archive. exfil.7z contains two files they took from the victim: Database.kdbx, a KeePass vault, and KeePass.dmp, a memory dump of the KeePass process.
That dump is enough because of CVE-2023-32784. KeePass 2.x before 2.54 builds the master password field’s text one keystroke at a time, and each step leaves a string behind in process memory: ●a, then ●●b, then ●●●c, a run of placeholders followed by the character typed at that position. Every position after the first leaves one. A dump of the process gives back every character except the first, with a few candidates where the remnants disagree. The public proof of concept does that extraction. The first character is a brute force against the vault, one character from a known set.
The vault holds bello’s Microsoft account password. From there:
NirSoft’s MadPassExt takes CacheData and the account password and returns the generated DPAPI password. impacket’s dpapi masterkey decrypts the masterkey with it. Only one of the three masterkey files in Protect\<SID> opens, the one the clipboard blob names.
Decrypting the CMS
With the masterkey, the rest is unwrapping, in order:
- Find the classic DPAPI blob inside the CMS. It starts with version
01 00 00 00and the provider GUIDdf9d8cd0-1501-11d1-8c7a-00c04fc297eb. Decrypt it with the masterkey, check its HMAC, and the result is the key-encryption key. - Use that key to AES-unwrap the 40-byte wrapped key in the recipient info. That gives the content key.
- Decrypt the content with AES-256-GCM, using the 12-byte nonce and 16-byte tag from the
EnvelopedData. The plaintext is UTF-16.
I wrote a decryptor for the solution because the DPAPI-NG tooling I tried didn’t handle this blob. The pinned clipboard file wraps the ASN.1 in metadata that isn’t ASN.1, so a parser that expects DER from the first byte fails. Mine scans for the embedded SEQUENCE and parses from there. The team that published the first write-up found a route with no custom code at all: NirSoft DataProtectionDecryptor for the DPAPI layer, then CyberChef for the AES key unwrap and the GCM decryption, with the ASN.1 walked by hand in an online decoder. That route is simpler than mine. Every layer is a standard primitive once you know where its bytes are.
Intrusion artifacts
Question 1 is the hardest, but it sits inside a whole intrusion, and the other 19 questions follow the attacker through it. Each of their actions left something that outlived the cleanup:
| attacker action | what still remembered it |
|---|---|
| a fake training site that copies a command to the visitor’s clipboard and asks them to paste and run it | Chrome’s session tabs file, with the visit time |
the pasted command fetches a C2 agent into C:\Users\Public as FTK_Imager.exe |
PSReadline\ConsoleHost_history.txt, then Amcache for the binary’s SHA-1 |
a local admin account named bella |
SAM and SYSTEM |
Winlogon Shell persistence pointing at the agent |
Defender’s MPLog, which logged the removal and the value it reset |
| an RMM tool, HelpWire, downloaded | the CryptnetUrlCache metadata, with the URL and time |
a second stager in Public\Downloads, then deleted |
the $MFT: at 611 bytes its content was resident in the record |
| the stager’s launch time | the USN journal’s extends on the PowerShell history file |
| the agent’s run time and bytes sent | SRUM’s app timeline and network usage tables |
| event logs cleared | System 104 and Security 1102, the clear events themselves |
The C2 is MITRE Caldera. Its agent, sandcat, fetches its payload with a file: sandcat.go header, and that header is in the history file and in the stager the $MFT kept. A few more answers come from places most triage scripts skip: the Windows Search database Windows.edb still holds the text of a document from the user’s folder, the notification database has a warning a colleague sent over Telegram Web, and thumbcache keeps a picture of the user’s badge.
Conclusion
Most teams stopped at question 1 because of one property of Microsoft accounts: the logon password and the DPAPI secret are different things, and the DPAPI secret can only be opened with the account password, which isn’t on the disk. In a real case the investigator would have to get that password from the user. In this challenge it is in the attacker’s KeePass vault.