Public/Extract-SSHPrivateKeyFromRegistry.ps1

<#
    .SYNOPSIS
        Microsoft's port of OpenSSH (OpenSSH-Win64) ultimately adds RSA Private Keys to the Registry when they are added to the
        ssh-agent service. This function extracts those RSA Private Keys from the Registry. It can only be used under the same
        User Profile that added the key(s) to the ssh-agent in the first place.
 
    .DESCRIPTION
        See .SYNOPSIS
 
    .NOTES
        Python Code from: https://github.com/ropnop/windows_sshagent_extract
 
    .EXAMPLE
        # Open an elevated PowerShell Session, import the module, and -
 
        PS C:\Users\zeroadmin> Extract-SSHPrivateKeyFromRegistry
#>

function Extract-SSHPrivateKeysFromRegistry {
    [CmdletBinding()]
    Param ()

    $OpenSSHRegistryPath = "HKCU:\Software\OpenSSH\Agent\Keys\"

    $RegistryKeys = Get-ChildItem $OpenSSHRegistryPath | Get-ItemProperty

    if ($RegistryKeys.Length -eq 0) {
        Write-Error "No ssh-agent keys in registry"
        $global:FunctionResult = "1"
        return
    }

    $tempDirectory = [IO.Path]::Combine([IO.Path]::GetTempPath(), [IO.Path]::GetRandomFileName())
    $null = [IO.Directory]::CreateDirectory($tempDirectory)

    Add-Type -AssemblyName System.Security
    [System.Collections.ArrayList]$keys = @()
    $RegistryKeys | foreach {
        $key = @{}
        $comment = [System.Text.Encoding]::ASCII.GetString($_.comment)
        $encdata = $_.'(default)'
        $decdata = [Security.Cryptography.ProtectedData]::Unprotect($encdata, $null, 'CurrentUser')
        $b64key = [System.Convert]::ToBase64String($decdata)
        $key[$comment] = $b64key
        $null = $keys.Add($key)
    }

    ConvertTo-Json -InputObject $keys | Out-File -FilePath "$tempDirectory/extracted_keyblobs.json" -Encoding ascii

    $InstallPython3Result = Install-Program -ProgramName python3 -CommandName python -UseChocolateyCmdLine
    if (!$(Get-Command python -ErrorAction SilentlyContinue)) {
        Write-Error "Unable to find python.exe! Halting!"
        $global:FunctionResult = "1"
        return
    }
    if (!$(Get-Command pip -ErrorAction SilentlyContinue)) {
        Write-Error "Unable to find pip.exe! Halting!"
        $global:FunctionResult = "1"
        return
    }
    pip install pyasn1 pip *> $null
    
    Set-Content -Path "$tempDirectory\extractPrivateKeys.py" -Value @"
#!/usr/bin/env python
 
# Script to extract OpenSSH private RSA keys from base64 data
# From: https://github.com/ropnop/windows_sshagent_extract
 
import sys
import base64
import json
try:
    from pyasn1.type import univ
    from pyasn1.codec.der import encoder
except ImportError:
    print("You must install pyasn1")
    sys.exit(0)
 
 
def extractRSAKey(data):
    keybytes = base64.b64decode(data)
    offset = keybytes.find(b"ssh-rsa")
    if not offset:
        print("[!] No valid RSA key found")
        return None
    keybytes = keybytes[offset:]
 
    # This code is re-implemented code originally written by soleblaze in sshkey-grab
    start = 10
    size = getInt(keybytes[start:(start+2)])
    # size = unpack_bigint(keybytes[start:(start+2)])
    start += 2
    n = getInt(keybytes[start:(start+size)])
    start = start + size + 2
    size = getInt(keybytes[start:(start+2)])
    start += 2
    e = getInt(keybytes[start:(start+size)])
    start = start + size + 2
    size = getInt(keybytes[start:(start+2)])
    start += 2
    d = getInt(keybytes[start:(start+size)])
    start = start + size + 2
    size = getInt(keybytes[start:(start+2)])
    start += 2
    c = getInt(keybytes[start:(start+size)])
    start = start + size + 2
    size = getInt(keybytes[start:(start+2)])
    start += 2
    p = getInt(keybytes[start:(start+size)])
    start = start + size + 2
    size = getInt(keybytes[start:(start+2)])
    start += 2
    q = getInt(keybytes[start:(start+size)])
 
    e1 = d % (p - 1)
    e2 = d % (q - 1)
 
    keybytes = keybytes[start+size:]
 
    seq = (
        univ.Integer(0),
        univ.Integer(n),
        univ.Integer(e),
        univ.Integer(d),
        univ.Integer(p),
        univ.Integer(q),
        univ.Integer(e1),
        univ.Integer(e2),
        univ.Integer(c),
    )
 
    struct = univ.Sequence()
 
    for i in range(len(seq)):
        struct.setComponentByPosition(i, seq[i])
     
    raw = encoder.encode(struct)
    data = base64.b64encode(raw).decode('utf-8')
 
    width = 64
    chopped = [data[i:i + width] for i in range(0, len(data), width)]
    top = "-----BEGIN RSA PRIVATE KEY-----\n"
    content = "\n".join(chopped)
    bottom = "\n-----END RSA PRIVATE KEY-----"
    return top+content+bottom
 
def getInt(buf):
    return int.from_bytes(buf, byteorder='big')
 
 
def run(filename):
    with open(filename, 'r') as fp:
        keysdata = json.loads(fp.read())
     
    for jkey in keysdata:
        for keycomment, data in jkey.items():
            privatekey = extractRSAKey(data)
            print("[+] Key Comment: {}".format(keycomment))
            print(privatekey)
            print()
     
    sys.exit(0)
 
if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("Usage: {} extracted_keyblobs.json".format(sys.argv[0]))
        sys.exit(0)
    filename = sys.argv[1]
    run(filename)
     
"@


    Push-Location $tempDirectory

    $SSHAgentPrivateKeys = python .\extractPrivateKeys.py .\extracted_keyblobs.json

    [System.Collections.ArrayList]$UpdatedSSHAgentPrivKeyInfoArray = @()
    $SSHAgentPrivateKeysArrayList = [System.Collections.ArrayList]$SSHAgentPrivateKeys
    $NumberOfPrivateKeys = $($SSHAgentPrivateKeys | Where-Object {$_ -eq "-----END RSA PRIVATE KEY-----"}).Count
    for ($i=0; $i -lt $NumberOfPrivateKeys; $i++) {
        $SSHAgentPrivateKeysArrayListClone = $($SSHAgentPrivateKeysArrayList.Clone() -join "`n").Trim() -split "`n"
        New-Variable -Name "KeyInfo$i" -Value $(New-Object System.Collections.ArrayList) -Force

        :privkeylines foreach ($Line in $SSHAgentPrivateKeysArrayListClone) {
            if (![System.String]::IsNullOrWhiteSpace($Line)) {
                $null = $(Get-Variable -Name "KeyInfo$i" -ValueOnly).Add($Line)
                $SSHAgentPrivateKeysArrayList.Remove($Line)
            }
            else {
                break privkeylines
            }
        }

        $null = $UpdatedSSHAgentPrivKeyInfoArray.Add($(Get-Variable -Name "KeyInfo$i" -ValueOnly))
    }

    [System.Collections.ArrayList]$FinalSSHPrivKeyObjs = @()
    foreach ($PrivKeyInfoStringArray in $UpdatedSSHAgentPrivKeyInfoArray) {
        $OriginalPrivateKeyFilePath = $PrivKeyInfoStringArray[0] -replace "\[\+\] Key Comment: ",""
        $PrivateKeyContent = $PrivKeyInfoStringArray[1..$($PrivKeyInfoStringArray.Count-1)]
        $PSObj = [pscustomobject]@{
            OriginalPrivateKeyFilePath      = $OriginalPrivateKeyFilePath
            PrivateKeyContent               = $PrivateKeyContent
        }

        $null = $FinalSSHPrivKeyObjs.Add($PSObj)
    }

    Pop-Location

    Remove-Item $tempDirectory -Recurse -Force

    $FinalSSHPrivKeyObjs
}