Last active
February 27, 2024 13:50
-
-
Save ykoster/0a475e4f09e8e5c714ae741933ab21a2 to your computer and use it in GitHub Desktop.
Invoke-MTPuTTYConfigDump - read an MTPuTTY configuration file, decrypt the passwords and dump the result
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
<# | |
.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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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.