-
-
Save ykoster/0a475e4f09e8e5c714ae741933ab21a2 to your computer and use it in GitHub Desktop.
<# | |
.Synopsis | |
Decrypt an MTPuTTY configuration file | |
.Description | |
Read an MTPuTTY configuration file, decrypt the passwords and dump the result | |
.Parameter ConfigFile | |
Path to the MTPuTTY configuration file | |
.Example | |
Invoke-MTPuTTYConfigDump mtputty.xml | |
#> | |
function Invoke-MTPuTTYConfigDump { | |
[CmdletBinding(DefaultParameterSetName="ConfigFile")] | |
Param( | |
[Parameter(ParameterSetName = "ConfigFile", Position = 0, Mandatory = $true)] | |
[String] | |
$ConfigPath | |
) | |
$PROV_RSA_FULL = 1 | |
$CRYPT_VERIFYCONTEXT = 0xF0000000 | |
$CALG_SHA = 0x00008004 | |
$CALG_RC2 = 0x00006602 | |
Function Get-CryptoAPI { | |
$MethodDefinition = @" | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptAcquireContext(ref IntPtr hProv, string pszContainer, string pszProvider, uint dwProvType, long dwFlags); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptReleaseContext(IntPtr hProv, uint dwFlags); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptCreateHash(IntPtr hProv, uint algId, IntPtr hKey, uint dwFlags, ref IntPtr phHash); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptDestroyHash(IntPtr hHash); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptHashData(IntPtr hHash, byte[] pbData, uint dataLen, uint flags); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptDeriveKey(IntPtr hProv,int Algid, IntPtr hBaseData, int flags, ref IntPtr phKey); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptDestroyKey(IntPtr hKey); | |
[DllImport("advapi32.dll", CharSet=CharSet.Auto, SetLastError=true)] | |
[return : MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CryptDecrypt(IntPtr hKey, IntPtr hHash, int Final, uint dwFlags, byte[] pbData, ref uint pdwDataLen); | |
"@ | |
try { | |
$CryptoAPI = Add-Type -MemberDefinition $MethodDefinition -name advapi32 -Namespace CryptoAPI -PassThru | |
} catch {} | |
return [CryptoAPI.advapi32] | |
} | |
<# https://devblogs.microsoft.com/powershell/format-xml/ #> | |
Function Format-XML ([System.Xml.XmlElement]$xml, $indent=2) { | |
$StringWriter = New-Object System.IO.StringWriter | |
$XmlWriter = New-Object System.XMl.XmlTextWriter $StringWriter | |
$xmlWriter.Formatting = "indented" | |
$xmlWriter.Indentation = $Indent | |
$xml.WriteContentTo($XmlWriter) | |
$XmlWriter.Flush() | |
$StringWriter.Flush() | |
Write-Output $StringWriter.ToString() | |
} | |
try { | |
[xml]$config = Get-Content -Path $ConfigPath -ErrorAction Stop | |
} catch { | |
Write-Host $_ -ErrorAction Stop | |
return | |
} | |
$CryptoAPI = Get-CryptoAPI | |
[System.IntPtr]$hProv = 0 | |
$servers = $config.SelectNodes("//Servers") | |
if($servers.Count -gt 0 -and $CryptoAPI::CryptAcquireContext([ref]$hProv, $null, $null, $PROV_RSA_FULL, $CRYPT_VERIFYCONTEXT) -ne $false) { | |
foreach($node in $config.SelectNodes("//Node")) { | |
if($node.Type -eq 0) { | |
Write-Host "$($node.DisplayName):" | |
} elseif ($node.Type -eq 1) { | |
[System.IntPtr]$hHash = 0 | |
[System.IntPtr]$hKey = 0 | |
$password = [system.Text.Encoding]::UTF8.GetBytes("1$($node.UserName.Trim())$($node.ServerName.Trim())") | |
$ciphertext = [System.Convert]::FromBase64String($node.Password.Trim()) | |
$ciphertextLength = $ciphertext.Length | |
if($CryptoAPI::CryptCreateHash($hProv, $CALG_SHA, 0, 0, [ref]$hHash) -ne $false) { | |
if($CryptoAPI::CryptHashData($hHash, $password, $password.Length, 0) -ne $false) { | |
if($CryptoAPI::CryptDeriveKey($hProv, $CALG_RC2, $hHash, 0, [ref]$hKey) -ne $false) { | |
if($CryptoAPI::CryptDecrypt($hKey, 0, $true, 0, $ciphertext, [ref]$ciphertextLength) -ne $false) { | |
$ciphertext = $ciphertext[0..($ciphertextLength-1)] | |
if($ciphertextLength -ge 2 -and $ciphertext[1] -eq 0) { | |
$node.Password = [system.Text.Encoding]::Unicode.GetString($ciphertext) | |
} else { | |
$node.Password = [system.Text.Encoding]::UTF8.GetString($ciphertext) | |
} | |
} | |
$null = $CryptoAPI::CryptDestroyKey($hKey); | |
} | |
} | |
$null = $CryptoAPI::CryptDestroyHash($hHash); | |
} | |
Format-XML $node | |
Write-Host | |
} | |
Write-Host | |
} | |
$null = $CryptoAPI::CryptReleaseContext($hProv, 0) | |
} | |
} | |
Export-ModuleMember -Function Invoke-MTPuTTYConfigDump |
Thank you so much for writing this!
Glad to hear you find it useful 👍
There are extraneous symbols int the recovered password: <Password>1�2�3�</Password>
(password is 123).
@Safety1st can you provide the source XML?
<MTPutty version="1.7">
<Globals>
<PuttyLocation>C:\Program Files\PuTTY\putty.exe</PuttyLocation>
<GUI>
<StartPage HideOnTab="1"/>
</GUI>
<General>
<TabCaption>1</TabCaption>
<NormalTermination>0</NormalTermination>
<AbnormalTermination>1</AbnormalTermination>
<AbnormalAutoReconnect>0</AbnormalAutoReconnect>
<QuitNoConfrim>1</QuitNoConfrim>
<CloseSessionNoConfirm>1</CloseSessionNoConfirm>
<CloseButtonOnTabs>1</CloseButtonOnTabs>
<TabPosition>0</TabPosition>
</General>
<Advanced>
<SaveLayout>0</SaveLayout>
<SaveSessions>0</SaveSessions>
</Advanced>
</Globals>
<Servers>
<Putty>
<Node Type="1">
<SavedSession>Default Settings</SavedSession>
<DisplayName>CORE</DisplayName>
<UID>{53BEB999-3851-4B2B-9A9F-7F70B1326EB5}</UID>
<ServerName>192.168.2.1</ServerName>
<PuttyConType>4</PuttyConType>
<Port>0</Port>
<UserName>onadm</UserName>
<Password>HY0sf47p+pM=</Password>
<PasswordDelay>0</PasswordDelay>
<CLParams>192.168.2.1 -ssh -l onadm</CLParams>
<ScriptDelay>0</ScriptDelay>
</Node>
</Putty>
</Servers>
<Internals>
<Putty/>
</Internals>
<Hotkeys>
<NextTab>0</NextTab>
<PrevTab>0</PrevTab>
<AppSwitch>16576</AppSwitch>
<acHideConnections>0</acHideConnections>
<acPuttyLocation>0</acPuttyLocation>
<acSettings>0</acSettings>
<acHideServers>16450</acHideServers>
<acHideToolbar>16468</acHideToolbar>
<acAddServer>0</acAddServer>
<acAddGroup>0</acAddGroup>
<acRemove>0</acRemove>
<acDetach>0</acDetach>
<acConnect>0</acConnect>
<acConnectTo>16459</acConnectTo>
<acTreeProps>0</acTreeProps>
<acSendScript>0</acSendScript>
<acAttach>0</acAttach>
<acRenameTab>0</acRenameTab>
<acImportTree>0</acImportTree>
<acExportTree>0</acExportTree>
<acHotkeysMap>0</acHotkeysMap>
<acDuplicate>0</acDuplicate>
<acMultiUpdate>0</acMultiUpdate>
</Hotkeys>
</MTPutty>
Thanks @Safety1st. Unfortunately, I'm not able to decrypt the password. CryptDecrypt
returns 0x80004005 (Bad Data). The encryption key is derived from the user name and server name, so if you've change those it wouldn't work.
There are scenarios where it is not possible to decrypt because the config file was created with an older version of MTPuTTY. This doesn't seem to be the case here as your config suggest that 1.7(+) is used. The script was tested against 1.6.x so it could be that the encryption logic was changed. This will require some more digging into though.
Ok, these are matched values :)
<?xml version="1.0" encoding="UTF-8"?>
<MTPutty version="1.7">
<Globals>
<PuttyLocation>C:\Program Files\PuTTY\putty.exe</PuttyLocation>
<GUI>
<StartPage HideOnTab="1"/>
</GUI>
<General>
<TabCaption>1</TabCaption>
<NormalTermination>0</NormalTermination>
<AbnormalTermination>1</AbnormalTermination>
<AbnormalAutoReconnect>0</AbnormalAutoReconnect>
<QuitNoConfrim>1</QuitNoConfrim>
<CloseSessionNoConfirm>1</CloseSessionNoConfirm>
<CloseButtonOnTabs>1</CloseButtonOnTabs>
<TabPosition>0</TabPosition>
</General>
<Advanced>
<SaveLayout>0</SaveLayout>
<SaveSessions>0</SaveSessions>
</Advanced>
</Globals>
<Servers>
<Putty>
<Node Type="1">
<SavedSession>Default Settings</SavedSession>
<DisplayName>TEST</DisplayName>
<UID>{55DF5F51-222D-409A-A8F4-0A0B7D00A5A2}</UID>
<ServerName>192.168.2.1</ServerName>
<PuttyConType>4</PuttyConType>
<Port>0</Port>
<UserName>onadm</UserName>
<Password>R7eUjM9rncA=</Password>
<PasswordDelay>0</PasswordDelay>
<CLParams>192.168.2.1 -ssh -l onadm</CLParams>
<ScriptDelay>0</ScriptDelay>
</Node>
</Putty>
</Servers>
<Internals>
<Putty/>
</Internals>
<Hotkeys>
<NextTab>0</NextTab>
<PrevTab>0</PrevTab>
<AppSwitch>16576</AppSwitch>
<acHideConnections>0</acHideConnections>
<acPuttyLocation>0</acPuttyLocation>
<acSettings>0</acSettings>
<acHideServers>16450</acHideServers>
<acHideToolbar>16468</acHideToolbar>
<acAddServer>0</acAddServer>
<acAddGroup>0</acAddGroup>
<acRemove>0</acRemove>
<acDetach>0</acDetach>
<acConnect>0</acConnect>
<acConnectTo>16459</acConnectTo>
<acTreeProps>0</acTreeProps>
<acSendScript>0</acSendScript>
<acAttach>0</acAttach>
<acRenameTab>0</acRenameTab>
<acImportTree>0</acImportTree>
<acExportTree>0</acExportTree>
<acHotkeysMap>0</acHotkeysMap>
<acDuplicate>0</acDuplicate>
<acMultiUpdate>0</acMultiUpdate>
</Hotkeys>
</MTPutty>
I got the same results now @Safety1st. It looks like they've switched to Unicode strings, the code assumes UTF-8 resulting in the extra zeroes. I've changed the code a bit to (try to) detect Unicode strings. Could you give it a try?
Now everything is fine. Thank you!
How are the initialization vectors generated? This is using RC2_CBC correct?
How are the initialization vectors generated? This is using RC2_CBC correct?
Hi @sidrile3310, since there is no call to CryptSetKeyParam I assume the IV is all zeroes, which is the default for the Microsoft Base Cryptographic Provider.
@ykoster Thanks! I am trying to write a version of this using Python but so far not so good. Thought the IV may be the issue but it looks like the problem lies elsewhere. This was a great primer though.
@sidrile3310 could be related to the way the key is derived. There is a Python implementation of CryptDeriveKey here https://www.fireeye.com/content/dam/fireeye-www/global/en/blog/threat-research/flareon2016/challenge2-solution.pdf. Haven't tested it myself.
Thank you so much for writing this!