从Windows 10 SSH代理提取SSH私钥(2018)

2020-10-14 02:45:36

这个周末,我安装了Windows10Spring更新,并对开始使用新的内置OpenSSH工具感到非常兴奋。

在Windows中原生使用OpenSSH非常棒,因为Windows管理员不再需要使用Putty和PPK格式键。我开始四处查看和阅读更多关于支持哪些特性的信息,看到其中包含了ssh-agent.exe,我感到非常惊讶。

我在这篇MSDN文章中找到了一些关于使用新的Windows ssh-agent的参考,这一部分立即引起了我的注意:

我过去曾在劫持SSH代理方面获得过一些很好的乐趣,所以我决定开始查看Windows是如何“安全地”使用这项新服务来存储您的私钥的。

我将在这篇文章中概述我的方法和弄清楚这一点的步骤。这是一次有趣的调查之旅,我更擅长与PowerShell合作。

私钥由DPAPI保护并存储在HKCU注册表配置单元中。我在这里发布了一些PoC代码,以便从注册表中提取和重建RSA私钥。

我测试的第一件事是正常使用OpenSSH实用程序生成几个密钥对,并将它们添加到ssh-agent。

然后,我确保新的ssh-agent服务正在运行,并使用ssh-add将私钥对添加到正在运行的代理:

最后,在将公钥添加到Ubuntu机器之后,我验证了我可以从Windows10通过SSH登录,而不需要解密我的私钥(因为ssh-agent会为我做这件事):

为了弄清楚SSH代理是如何存储和读取我的私钥的,我查看了一下,并从静态检查ssh-agent.exe开始。然而,事实证明我的静态分析技能非常弱,所以我放弃了,只是决定动态跟踪这个过程,看看它在做什么。

我使用了Sysinterals中的procmon.exe,并为任何包含“ssh”的进程名称添加了一个过滤器。

随着promon捕获事件,我再次通过SSH进入我的Ubuntu机器。查看所有事件时,我看到ssh.exe打开了到Ubuntu的TCP连接,然后终于看到ssh-agent.exe生效并从注册表中读取了一些值:

正因为如此,我现在才知道注册表中存储和读取的是某种受保护的数据,并且ssh-agent使用的是微软的数据保护API。

果然,查看注册表时,我可以看到我使用ssh-add添加的键的两个条目。密钥名称是公钥的指纹,并且存在一些二进制斑点:

在阅读StackOverflow一个小时以提醒自己PowerShell的丑陋语法(按照传统)之后,我能够提取注册表值并对其进行操作。“COMMENT”字段只是ASCII编码的文本,是我添加的键的名称:

(默认)值只是一个字节数组,没有解码成任何有意义的内容。我有预感,这就是“加密的”私钥,如果我能把它拉出来,然后找出如何解密它的话。我将字节提取到Powershell变量:

我对DPAPI不是很熟悉,尽管我知道很多后利用工具滥用它来提取秘密和凭据,所以我知道其他人可能已经实现了包装器。稍微用谷歌搜索一下,我发现了一个由atifaziz设计的简单的眼线笔,比我想象的要简单得多(好的,我想我明白为什么人们喜欢powershell…了)。。;))。

我仍然不知道这是否有效,但我尝试使用DPAPI取消对字节数组的保护。我希望可能会返回一个格式完美的OpenSSH私钥,所以我对结果进行了Base64编码:

返回的base64看起来不像私钥,但我还是解码了它,只是为了好玩,并且非常惊喜地看到其中有字符串“ssh-rsa”!我必须走在正确的轨道上。

实际上这部分花了我最长的时间。我知道我有密钥的某种二进制表示形式,但我不知道它的格式或如何使用它。

我使用openssl、puttygen和ssh-keygen生成各种RSA密钥,但从未得到任何与我拥有的二进制文件相似的密钥。

最后,在大量搜索之后,我发现了NetSPI发布的一篇很棒的博文,内容是关于从linux上ssh-agent的内存转储中取出OpenSSH私钥的:https://blog.netspi.com/stealing-unencrypted-ssh-agent-keys-from-memory/。

会不会是二进制格式是相同的?我拉下从博客链接的Python脚本,并将我从Windows注册表中获得的未受保护的base64 blob提供给它:

啊,真灵!。我不知道原始作者soleblaze是如何得出二进制数据的正确格式的,但我非常感谢他这样做并与人分享。所有功劳都归功于他出色的Python工具和博客帖子。

在我向自己证明可以从注册表中提取私钥之后,我将其全部放在两个脚本中。

第一个是Powershell脚本(Extract_ssh_keys.ps1),它在注册表中查询ssh-agent中保存的任何项。然后,它使用带有当前用户上下文的DPAPI来取消对二进制文件的保护,并将其保存在Base64中。因为我甚至不知道如何在Powershell中开始解析二进制数据,所以我只是将所有的键保存到一个JSON文件中,然后可以用Python导入该文件。Powershell脚本只有几行:

$PATH=";HKCU:\SOFTWARE\OpenSSH\Agent\Keys\";$regkey=Get-ChildItem$PATH|Get-ItemProperty if($regkey.。Length-eq 0){Write-Host";注册表";退出}$Key=@()Add-Type-AssemblyName系统。安全;$regkey|ForEach-Object{$key=@{}$Comment=[System.Text.Encoding]::ASCII。GetString($_.。备注)写入主机";拉动键:";$COMMENT$encdata=$_。';(默认值)';$decdata=[Security.Cryptography.ProtectedData]::UnProtect($encdata,$null,';CurrentUser';)$b64key=[System.Convert]::ToBase64String($decdata)$KEY[$COMMENT]=$b64key$KEYS+=$KEY}ConvertTo-Json-InputObject$Key|Out-File-FilePath';./Extracted_keyblobs.json';-Encoding CII Write-Host";Extracted_keyblobs.json写入。使用Python脚本重构私钥:Python ExtractPrivateKeys.py Extracted_keyblobs.json";

我通过soleblaze大量借用了parse_mem_python.py中的代码,并将其更新为在下一个脚本中使用Python3:ExtractPrivateKeys.py。馈送从Powershell脚本生成的JSON将输出找到的所有RSA私钥:

这些RSA私钥是未加密的。尽管我在创建它们时添加了密码,但它们是使用ssh-agent以未加密的方式存储的,因此我不再需要该密码。

为了验证,我将密钥复制回了Kali Linux机器,验证了指纹,并使用它进行了SSH!

显然,我的PowerShell-fu很弱,我发布的代码更多的是针对PoC的。完全在PowerShell中重新创建私钥是可能的。我也不会把Python代码的功劳归功于他最初的实现--这些都应该归功于SoleBlaze。

我也很乐意最终看到这一点被武器化并添加到后利用框架中,因为我认为我们将开始看到管理员在Windows 10上更多地使用OpenSSH,我相信这些密钥对Redteam和Pentesters来说可能非常有价值:)