Decrypt secure strings in PowerShell

Before I show you how to decrypt a SecureString quickly in PowerShell, let's explain why this is something an administrator should be aware of and able to do and why this doesn't constitue a "hack" or circumvention of security. What I'm demonstrating isn't a magic trick - it's by design! SecureStrings are meant to be decryptable if the user truly wants to decrypt them. A SecureString is meant to protect a string from accidental or malicious exposure.

The System.Security.SecureString class was introduced in version 2.0 of the .NET Framework to enable users and developers to handle strings in a more secure fashion. The System.String class which came with the original version of the framework raised security concerns because it is a) immutable and b) cannot be cleared from memory by a user.

Immutability means that a System.String object is never modified once it is created. If you make changes to a string by adding to it or calling a method on it, such as .Trim() or Substring(…) you are not changing the original string. Rather, a new String object is created while the old one continues to live on in memory. Even if you no longer maintain a reference to the original string in a variable, it still lives on in memory. This is in fact true for most objects in the .NET Framework - although you can no longer access them as a user or developer, they continue to live in memory for a period. As an example, consider the following few script lines and comments:

 

# in this line, a System.String object is created# and a reference to it is assigned to the $str variable$str = "a typical string"# in this line the original value of $str is# concatenated with " modified", a new System.String# object is created, and a reference to this new# object is assigned to the $str variable$str = $str + " modified"# how many String objects have now been created?# the first object assigned to $str continues to live on# in memory, even though it has no associated variable# and can't be accessed by the user

Even though the original String ("a typical string") can't be accessed at the end of this script, it still exists somewhere in memory. Eventually, it will be removed from memory by the .NET memory manager, known as the Garbage Collector. From a security perspective, this means sensitive data might exist in plaintext in memory for quite a while, depending on the factors governing garbage collection. If the string has been modified several times, multiple copies of it might in fact exist in memory. A malicious user could discover these copies by getting a memory dump of the process.

These concerns led to the creation of the SecureString class in version 2 of the framework. A SecureString is encrypted before it is placed into memory. Before it can be used, it has to be decrypted. Even if a malicious user was able to obtain a memory dump from a program, the object in memory would be encrypted. A SecureString is also mutable, meaning it can be changed in place - no additional copies of the object are created, reducing risk. Finally, SecureStrings implement the IDisposable interface, meaning they expose a .Dispose() method, which when called releases all resources related to the object. This means that for particularly sensitive data, a developer or user can dispose of the object and remove it from memory manually without having to wait on the garbage collection process or worry about its algorithms.

All of these measures make it nearly impossible to obtain the plaintext value of a SecureString just by getting a memory dump, which means unintentional or malicious exposure of this value is highly unlikely. However, a SecureString is meant to be able to be decrypted intentionally so that it can be used by the proper user of a program. When a cmdlet or program expects a SecureString, it takes care of this decryption internally. A user or admin can use the same methods to decrypt a SecureString themselves. Of course, once a SecureString has been decrypted and converted into a regular String, all security benefits of using a SecureString are immediately negated… Consider yourself warned.

To decrypt a SecureString, methods from the System.Runtime.Interop.Marshal static class are called to write out the unencrypted plaintext string to unmanaged memory, and then to retrieve that value back into managed memory for further use. In properly written code or script, the unmanaged memory should then be cleared. Of course, only the unmanaged memory is cleared. The managed String which is created is subject to all the rules of the .NET memory manager (the garbage collector).

The function to decrypt a SecureString is quite simple. Without further ado, here it is:

 

function Decrypt-SecureString {param( [Parameter(ValueFromPipeline=$true,Mandatory=$true,Position=0)] [System.Security.SecureString] $sstr)$marshal = [System.Runtime.InteropServices.Marshal]$ptr = $marshal::SecureStringToBSTR( $sstr )$str = $marshal::PtrToStringBSTR( $ptr )$marshal::ZeroFreeBSTR( $ptr )$str}

We begin by obtaining a reference to the Marshal type for ease of reference in the next few lines. Then we call the static method SecureStringToBSTR, which decrypts the SecureString and writes it out to unmanaged memory (specifically to a Binary (or Basic) String object - a BSTR). The method returns a pointer (a memory address) to the first character of the plaintext string. Next, we call the PtrToStringBSTR method, which takes the pointer returned from the first method and returns the string starting at that memory address as a managed .NET System.String object. At this point, our work is done. The last method from Marshal which we call, ZeroFreeBSTR, simply frees up the unmanaged memory which we used (which doesn't effect the managed System.String object previously captured). Finally, we state $str in the last line to output the String to the pipeline.

I've taken quite a bit of your time to explain what ends up being a pretty straightforward function. My hope is to make it clear that decrypting a SecureString isn't really "breaking the rules." If it's something useful to you, consider using it. If not, be aware that anyone with access to your SecureStrings might be able to use it, and consider whether other encryption techniques might be more appropriate for long-term persistence.