解密OpenSSH会话以获取乐趣和收益

  • A+

译文声明
本文是翻译文章,文章原作者 Jelle Vergeer
原文地址:https://research.nccgroup.com/2020/11/11/decrypting-openssh-sessions-for-fun-and-profit/
译文仅供参考,具体内容表达以及含义以原文为准

介绍

不久前,我们有一个取证案例,其中一个Linux服务器被盗用,而且一个被修改的OpenSSH二进制文件加载到Web服务器的内存中。修改后的OpenSSH二进制文件被用作攻击者留下的后门。在系统遭到破坏时,客户拥有pcaps和系统的虚拟机监控程序快照。我们开始思考是否可以通过从内存快照中恢复密钥的文件来解密SSH会话并获得有关它的信息。在这篇博文中,我将介绍我对OpenSSH所做的研究,并发布一些工具来从内存中转储OpenSSH会话密钥,并结合使用pcaps解密和解析会话。我还向2020 Volatility框架插件竞赛提交了研究成果。

SSH协议

首先,我开始阅读OpenSSH及其工作原理。幸运的是,OpenSSH是开源的,因此我们可以轻松下载和阅读实现详细信息。尽管阅读起来有些无聊,但RFC还是丰富的信息。从较高的层次来看,SSH协议如下所示:

  1. SSH协议+软件版本交换
  2. 算法协商(KEX INIT)
  3. 密钥交换算法
  4. 加密算法
  5. MAC算法
  6. 压缩算法
  7. 密钥交换
  8. 用户认证
  9. 客户请求“会话”类型的频道
  10. 客户端请求伪终端
  11. 客户端与会话进行交互

从头开始,客户端将连接到服务器并发送协议版本和软件版本:
SSH-2.0-OpenSSH_8.3服务器对其协议和软件版本进行响应。交换初始协议和软件版本后,所有流量都封装在SSH帧中。SSH帧的内容主要有帧的长度,填充长度,有效载荷数据,填充内容和MAC中。SSH框架示例:

image.png
::: hljs-center

使用dissect.cstruct解析的示例SSH框架

:::

在协商加密算法并生成会话密钥之前,SSH帧并没有被加密,并且即使加密了该帧,根据算法的不同,部分帧可能也不会被加密。例如,aes256-gcm不会对帧中的4个字节长度进行加密,但是chacha20-poly1305会进行加密。

接下来,客户端将向服务器发送KEX_INIT消息,用来开始协商会话的参数,例如密钥交换和加密算法。根据这些算法的顺序,客户端和服务器将选择双方都支持的第一个首选算法。在KEX_INIT消息之后,交换几个与密钥交换相关的消息,然后从双方发送NEWKEYS消息。此消息告诉另一端,所有内容都已设置为开始加密会话,并且数据流中的下一帧将被加密。双方生效新的加密密钥后,客户端将请求用户身份验证,并根据服务器上配置的身份验证机制执行基于密码、密钥的身份验证。会话通过身份验证后,客户端将打开一个频道。

恢复会话密钥

恢复会话密钥的第一步是分析OpenSSH源代码并调试现有的OpenSSH二进制文件。我尝试自己编译OpenSSH,将生成的会话密钥记录在某个地方,并附加一个调试器,然后在程序的内存中搜索这些密钥。就可以成功!会话密钥保存在堆中的内存中。对源代码的更多深入研究使我了解了负责发送和接收NEWKEYS框架的功能。我发现有一个存储“ session_state”结构的“ ssh”结构。该结构又包含与当前SSH会话有关的所有信息,包括一个newkeys结构,其中包含与加密,mac和压缩算法有关的信息。在更深的层次上,我们终于找到了包含密码名称,密钥,IV和块长度的“ shenc”结构。我们需要的一切!下面显示了OpenSSH中的结构的概述:

image.png

以及shenc结构的定义:

image.png
::: hljs-center

SSHENC结构

:::

很难在内存中找到密钥本身(它只是一串随机字节),但是sshenc结构更加独特,具有一些可以验证的属性。然后,我们可以抓取程序的整个内存地址空间,并针对这些约束来验证每个偏移量。我们可以检查以下属性:

  • 名称,密码,密钥和iv成员是有效的指针
  • 名称成员指向一个有效的密码名称,该密码名称等于cipher-> name
  • key_len在有效范围内
  • iv_len在有效范围内
  • block_size在有效范围内

如果我们针对所有这些约束进行验证,那么我们应该能够可靠地找到shenc结构。我开始构建一个POC Python脚本,该脚本可以在一个实时主机上运行,​​该主机连接到进程并为该结构读取内存。它实际上工作得很好,并为找到的每个键输出一个json blob。因此,我演示了可以使用Python和ptrace从活动主机中恢复会话密钥,但是我们如何从内存快照中恢复会话密钥呢?这就是波动性发挥作用。易失性是一个用Python编写的内存取证框架,可以编写自定义插件。经过努力,我能够编写Volatility 2插件,并且能够分析内存快照并转储会话密钥!对于Volatility 3插件竞赛,我还将该插件移植到了Volatility 3,并提交了该插件并进行了研究。

image.png
::: hljs-center

波动率 SSH会话密钥转储器输出

:::

解密和解析流量

用于加密和解密流量的会话密钥的恢复成功。接下来是解密流量!我开始使用Pynids(TCP解析和重组库)解析一些pcap。我使用了我们内部开发的dissect.cstruct库来解析数据结构,并开发了一个解析框架来解析诸如ssh之类的协议。解析框架基本上以正确的顺序将数据包送入协议解析器,因此,如果客户端发送2个数据包,而服务器回复3个数据包,则这些数据包也将以相同的顺序提供给解析器。这对于保持整体协议状态很重要。解析器基本上使用SSH帧,直到遇到NEWKEYS帧为止,这表明下一帧已加密。现在,解析器从该源中窥视流中的下一帧,并迭代提供的会话密钥,以尝试解密该帧。如果成功,则解析器以该状态安装会话密钥以解密会话中的其余帧。解析器几乎可以处理OpenSSH支持的所有加密算法。

image.png
::: hljs-center

SSH协议解析

:::

最后是运行中的解析器,您可以在其中看到解密和解析SSH会话的过程,还公开了用户用于认证的密码:

image.png
::: hljs-center

解密和解析的SSH会话示例

:::

结论

因此,总而言之,我研究了SSH协议,如何将会话密钥存储并保存在OpenSSH的内存中,找到了一种从内存中抓取它们并在网络解析器中使用它们的方法,以将SSH会话解密并解析为可读输出。

一个可能的进展或不错的选择是将解密器和解析器实现到Wireshark中。

最后的想法

有趣的是,在研究期间,我还在OpenSSH源代码的ssh_set_newkeys函数中遇到了这些注释行。多讽刺!如果不对这些行进行注释并在OpenSSH二进制文件中进行编译,则此研究将更加艰巨。

image.png
::: hljs-center

OpenSSH源代码片段

:::