back to research
LARP-2026-010ctf9 min read

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:

jsonPinned{...}\metadata.json
{"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:

01KeePass.dmp, in the attacker's exfil.7zgives master password, all but the first characterCVE-2023-32784
02Database.kdbxgives the Microsoft account passwordfirst character brute-forced
03MicrosoftAccount\…\CacheDatagives the DPAPI password Windows generateddecrypted with the account password
04Protect\<SID>\<masterkey>gives the user's DPAPI masterkeyone of three files opens
05DPAPI blob inside the CMSgives key-encryption keyclassic DPAPI
06CMS recipient infogives content keyAES key unwrap
07VGV4dA== ("Text")gives the copied text, UTF-16AES-256-GCM
the obvious routeSAM + SYSTEM, secretsdumpbello's NT hashdoesn't crack, and on a Microsoft account it isn't what protects the masterkey
fig. 1Each box opens the next one. The account signs in with a Microsoft account, so the masterkey is protected by a random password Windows generated, and the only copy of that password is encrypted with the account password, which never touches the disk. The NT hash on the right opens nothing.

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:

  1. Find the classic DPAPI blob inside the CMS. It starts with version 01 00 00 00 and the provider GUID df9d8cd0-1501-11d1-8c7a-00c04fc297eb. Decrypt it with the masterkey, check its HMAC, and the result is the key-encryption key.
  2. Use that key to AES-unwrap the 40-byte wrapped key in the recipient info. That gives the content key.
  3. 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.

Houssem0x1
back to research