-
-
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 |
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.
Now everything is fine. Thank you!