Weak credentials encryption at rest with DPAPI: NordVPN case study

TL;DR The Windows client of NordVPN leverages DPAPI (Data Protection API) to effortlessly save the login credentials of the customers. This is a suitable way for developers to avoid common pitfalls regarding cryptographic implementation and key management. However, this simplistic solution entails trivial plain-text password recovery. The goal of this article is to provide a walk-through on how easy is to dump the VPN credentials in a post exploitation scenario with the help of the mighty Mimikatz.

Analyzing the encryption and decryption routines of NordVPN

Few days ago I was playing with what I already consider to be the best .NET debugger, dnSpy, and the first application I happened to open was NordVPN client v6.23.11.0. After a quick code review I decided to focus on the login routine that auto-connects the VPN at startup.
This routine is demanded to the third-party DLL 'Liberation.OS', that in turn, is also developed in .NET.
DPAPI is widely adopted in Windows (e.g. Credential Manager, IE, Chrome, Wi-Fi, etc.) because it abstracts the logic behind symmetric encryption, by providing proven cryptographic algorithms (by default AES256 with SHA512 and PBKDF2) and by binding the encryption key to a specific scope: user or machine. This  key is referenced by a GUID and, in turn, stored encrypted in the 'master key file', which is named with the GUID value itself (later in the article everything will be come clearer, I promise :-)). Moreover, the 'master key files' themself are encrypted with another secret that is derived from the SHA1 password hash of the user (user scope) or a secret linked to the system (local machine scope). On top of it, additional entropy can be provided by an optional parameter (unfortunately, NordVPN does not use such option).
Digging into the DPAPI implementation is out of scope in this post. Please for well documented posts on the topic, have a look at the Reference section at the bottom. Later in this article more details will be shown about how to successfully dump "online" and decrypt offline the encrypted credentials with the help of Mimikatz.
Back to NordVPN client, as we can see the option 'LocalMachine' is the least restrictive and I was, as you are now, a bit surprised that is the one currently used by NordVPN.

 "The protected data is associated with the machine context. Any process running on the computer can unprotect data. This enumeration value is usually used in server-specific applications that run on a server where untrusted users are not allowed access."

Finding the encrypted DPAPI blob containing the credentials

We know that it will be trivial to recover the credentials (email address and password) but we actually still do not know where this credentials are stored at rest.
Looking at the caller of the encryption/decryption routines the credentials are saved in a file along with other auto-connects settings. I do not like guessing and I resolved to use a more methodological approach relying on ProcMon.

It is interesting to note that, even if the client was heavily obfuscated, it would have just been possible to find the 'user.config' file and identify that 'System.Security.Cryptographic.ProtectedData' methods was used from the base64 prefix "AQAAANCM...", which is the universal 'prepended string' of DPAPI encrypted blob (due to the dpapi blob headers starting with a 'costant value').

Realtime decryption on target system with custom script

DPAPI is implemented in such a way that the decryption is 'expected' to be performed on the exact machine on which the the encrypted file is stored. Running a decryption script on the target machine (with any user, even a low privileged 'Standard User') trivially allows to retrieve the clear-text credentials.

Basically this means that being saved as clear-text in a file or being encrypted with DPAPI guarantees the same level of confidentiality in a post exploitation scenario if no additional entropy is specified as second parameter to 'ProtectData.Protect'.

Offline decryption with ProcDump and Mimikatz

Ok, but at least it is not possible to decrypt  the files in another system once they have been ex-filtrated, right? And we should assume that when handling DPAPI protected data we must immediately decrypt it one-shot while still having access to the compromised target?
The answer is clearly no if we have administrative privileges. Under such favorable condition it is possible to achieve the same result as above. Here are the detailed steps considering plaintext retrieval of the email address:
  1. ex-filtrate the 'user.config' file from the directory of the target user
2. decode the base64 blob to later analyze its DPAPI headers (that are useful to know the GUID of the 'master key' file used, the cryptographic algorithm, the hashing function, etc.)

3. dump all the keys stored under             C:\Windows\System32\Microsoft\Protect(in this path the master keys of the system are stored and in the LocalMachine scope exactly one of these was used)

We do not know yet which one was used for encrypting the DPAPI password, probably it is the one specified in the 'Preferred' file (therefore {37..bbe}) but to know for sure we just need to parse the headers of the DPAPI encrypted blob with mimikatz (by executing mimikatz from a machine different from the target system):

mimikatz# dpapi::blob /in:C:\\Windows\\Temp\\yyy\\encrypted_email.bin

As reported by Mimikatz in the screenshot above, we confirm that the 'master key file' named '37..bbe' was used for this particular encrypted blob.

4. dump the lsass.exe memory with Procdump and retrieve  from the this dump the key stored inside 'master key file' directly with mimikatz (executing mimikatz from a machine different from the target system)

> procdump64.exe -ma lsass.exe lsass.dmp
mimikatz # sekurlsa::minidump lsass.dmp
mimikatz # sekurlsa::dpapi

5. decrypt the email address by providing to mimikatz the             encrypted blob and the decrypted key obtained from the 'master key file' with GUID {37..bbe}
mimikatz # dpapi::blob /in:ENCRYPTED_BLOB /masterkey:db[..snipped..]

Obviously the procedure for the password and the other encrypted settings is the same.

Final thoughts

DPAPI is a valid technology for encryption at rest thanks to its  proven cryptographic implementation. However, since it does not address properly the key management problem, as soon as the user account or system is compromised, its secrets can be easily recovered. To mitigate this pitfall, in my opinion, the applications should always make use of  the 'entropy' parameter. In addition, properly obfuscating the .NET executable is a must to raise the bar for reverse engineering, otherwise the entropy value can still be immediately recovered (be it universally hardcoded in the executable, stored in a registry key, derived from a configuration of the system, from the email address, or through other contrived logic).
Kudos to the authors behind dnSpy and Mimikatz.


All the post focused on the scenario of a remote attacker with high privileges on the target system.

@gentilkiwi has kindly highlighted that it is also possible to retrieve the DPAPI_SYSTEM SECRETS (stored in the registry) with a disk forensic approach if the drive is unencrypted.
Under such favorable condition, it is trivial to retrieve both the file containing the credentials and the keys that encrypt them.
For his PoC with the detailed steps on how to do it smoothly with his Mimikatz, please have a look at the screenshots available in his twitter thread: https://twitter.com/gentilkiwi/status/1178796512580702208

Special thanks to @gentilkiwi for the feedback on this post!




Popular posts from this blog

Abusing Windows 10 Narrator's 'Feedback-Hub' URI for Fileless Persistence

WPA2-PSK vs WPA2-Enterprise: hacking and hardening