Today, while logging into a Windows Server via my favorite RDP tool (RDCMan), I was faced with the “Your password has expired” prompt. No worries, just enter my password and change it.
Except for the part where the password has been saved and I neglected to add it to my password manager. Oops. So, naturally, the first thing I want to do is just go look in the saved file (an .RDG). Not surprisingly, it’s encrypted with something:

After tinkering around wit ConvertFrom-SecureString, I guessed that it was probably done by something in the application itself. So, I started by importing the DLLs as modules and looking around. I imported the MSTSCLib.dll, and started tabbing through the types. I stumbled onto a good potential in MSTSCLib.IMsTscSecuredSettings, but alas, it was a false alarm.

I dug around a little more, but met with similar dashed hopes.
I then started looking for ways to decrypt it using the RDCMan executable.
Sometimes, you can luck out by attempting to run Import-Module against an EXE. It might have exported commands available. Nope. Not it.

Sometimes, you can add an EXE as an assembly, and then import:

Struck out again.
And then, you can sometimes load it via reflection:

Ooh, promising.
Next, I decided to see if there were any exports that could further ignite hope:

Now that we have something maybe to go on, we can try importing and creating some objects to see what sticks. First, import the reflected assembly:

The module is called RDCMan, so that’s what we’re going to use as the base to add the values we discovered in GetExportedTypes() a few steps earlier:

Looks like we have a couple of potential opportunities with EncryptionMethod and EncryptionSettings. Running a Get-Member on both of those helps us further narrow down the field of “interesting things” to EncryptionSettings:

While we’re here, let’s also take a look at what methods RDCMan.Encryption has to offer us. After typing [RDCMan.Encryption]:: in the prompt and tabbing, I’m delighted to find two methods: DecryptPasswords() and DecryptString().

Both look like potential winners, but the DecryptString caught my eye because the OverloadDefinitions had some detail about what to pass to it: I needed an encrypted string (which I had in the .RDG XML file) and something in RdcMan.EncryptionSettings. I had successfully executed New-Object against RdcMan.EncryptionSettings, so mayyyyybe:

BOOM. SUCCESS.
No, my password isn’t really Password123. I promise.
So, I bundled it all up as a handy script that you can copy/paste here or get over on the TN Gallery. Note: If you saved the file with a Windows account credential and attempted to run this from a different computer, this method won’t work.
# Decrypt passwords in RDG files
param($RDGFile,
$PasswordString,
$RDCManSource
)
If (!$RDCManSource)
{
$RDCManSource = (Get-ChildItem -Path @('C:\Program Files\Microsoft', 'C:\Program Files (x86)\Microsoft') -File "RDCMan.exe" -Recurse -ErrorAction SilentlyContinue)[0]
}
If (!$RDCManSource)
{
Write-Error "Remote Desktop Manager must be installed. If it is installed, use the -RDCManSource parameter to specify the executable's location."
Exit
}
else
{
try
{
$Assembly = [Reflection.Assembly]::LoadFile($RDCManSource.FullName)
}
catch
{
$_.Exception.Message.ToString();
Write-Host "Catch"; Exit
}
try { Import-Module $Assembly }
catch
{
$_.Exception.Message.ToString();
Write-Host "Import Exception"; exit }
}
If ($RDGFile)
{
[xml]$Data = Get-Content $RDGFile
$CredentialValues = $Data.SelectNodes("*//logonCredentials")
$global:Output = @()
foreach ($obj in $CredentialValues)
{
try
{
$EncryptionSettings = New-Object -TypeName RdcMan.EncryptionSettings
$Password = [RdcMan.Encryption]::DecryptString($obj.password, $EncryptionSettings)
}
catch
{
$_.Exception.Message.ToString(); continue
}
If ($Password -and ($Password -notcontains 'Failed to decrypt'))
{
$CredObject = New-Object PSObject
$CredObject | Add-Member -Type NoteProperty -Name "ProfileName" -Value $obj.ProfileName -ea SilentlyContinue -Force
$CredObject | Add-Member -Type NoteProperty -Name "UserName" -Value $obj.username -ea SilentlyContinue -Force
$CredObject | Add-Member -Type NoteProperty -Name "Password" -Value $Password
$CredObject | Add-Member -Type NoteProperty -Name "Domain" -Value $obj.domain
$global:Output += $CredObject
}
}
If ($Output)
{
$Output
}
Else
{
Write-Host "Nothing to show."
}
}
else
{
If ($PasswordString)
{
$EncryptionSettings = New-Object -TypeName RdcMan.EncryptionSettings
$Password = [RdcMan.Encryption]::DecryptString($PasswordString, $EncryptionSettings)
Write-Host "Cleartext password: $($Password)"
}
}
Good luck recovering!
