Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Invoke-MTPuTTYConfigDump - read an MTPuTTY configuration file, decrypt the passwords and dump the result
<#
.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
@lloydmoran

This comment has been minimized.

Copy link

@lloydmoran lloydmoran commented Aug 24, 2020

Thank you so much for writing this!

@ykoster

This comment has been minimized.

Copy link
Owner Author

@ykoster ykoster commented Aug 26, 2020

Thank you so much for writing this!

Glad to hear you find it useful 👍

@Safety1st

This comment has been minimized.

Copy link

@Safety1st Safety1st commented Sep 8, 2020

There are extraneous symbols int the recovered password: <Password>1&#x0;2&#x0;3&#x0;</Password> (password is 123).

@ykoster

This comment has been minimized.

Copy link
Owner Author

@ykoster ykoster commented Sep 9, 2020

@Safety1st can you provide the source XML?

@Safety1st

This comment has been minimized.

Copy link

@Safety1st Safety1st commented Sep 10, 2020

<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>
@ykoster

This comment has been minimized.

Copy link
Owner Author

@ykoster ykoster commented Sep 11, 2020

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.

@Safety1st

This comment has been minimized.

Copy link

@Safety1st Safety1st commented Sep 14, 2020

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>

Result is the same (for password 123):
image

@ykoster

This comment has been minimized.

Copy link
Owner Author

@ykoster ykoster commented Sep 14, 2020

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?

@Safety1st

This comment has been minimized.

Copy link

@Safety1st Safety1st commented Sep 14, 2020

Now everything is fine. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.