不久前,我们遇到了一个取证案例,一个Linux服务器被攻破,一个修改过的OpenSSH二进制文件被加载到一个web服务器的内存中。修改后的OpenSSH二进制文件被攻击者用作系统的后门。在系统遭到破坏时,客户拥有pcap和系统的虚拟机监控程序截图。根据这个发现,我们开始怀疑是否有可能通过从内存截图中恢复密钥材料来解密SSH session并获得有关它的知识。
在本文中,我将介绍我对OpenSSH所做的研究,并发布一些从内存中转储OpenSSHsession密钥的工具,并结合使用pcap解密和解析session,另外我还向2020 Volatility framework plugin contest提交了我的研究成果。
SSH协议
首先,我会阅读OpenSSH及其工作原理。幸运的是,OpenSSH是开源的,因此我们可以轻松下载和阅读实现细节。RFC的内容,虽然读起来有点枯燥,但也包含了丰富的信息。从较高的层次来看,SSH协议如下所示:
(1) SSH协议+软件版本交换;
(2) 算法协商(KEX INIT):
- 密钥交换算法;
- 加密算法;
- MAC算法;
- 压缩算法;
(3) 密钥交换;
(4) 用户认证;
(5) 客户请求“session”类型的频道;
(6) 客户端请求一个伪终端;
(7) 客户端与session进行交互;
一开始,客户端就会连接到服务器并发送协议版本和软件版本:
SSH-2.0-OpenSSH_8.3,服务器以其协议和软件版本进行响应。交换初始协议和软件版本后,所有流量都封装在SSH框架中。SSH框架主要存在于框架的长度、填充长度、有效载荷数据、填充内容和MAC中。 SSH框架示例如下:
使用dissect.cstruct解析的SSH框架示例
在协商加密算法并生成session密钥之前,SSH框架将不被加密,并且即使加密了该框架,根据算法的不同,部分框架可能也不会被加密。例如,aes256-gcm不会对框架中的4个字节长度进行加密,但是chacha20-poly1305会进行加密。
接下来,客户端将向服务器发送KEX_INIT消息,以开始协商session的参数,例如密钥交换和加密算法。根据这些算法的顺序,客户端和服务器将选择双方都支持的第一个首选算法。在KEX_INIT消息之后,交换几个与密钥交换相关的消息,然后双方互相发送NEWKEYS消息。这个消息会告诉另一方一切都准备好开始加密session,并且流程中的下一个框架将被加密。在双方都获得新的加密密钥生效后,客户端将请求用户身份验证,并根据服务器上配置的身份验证机制执行基于密码/密钥/等的身份验证。通过session身份验证后,客户端将打开一个通道,并根据请求的操作(ssh/ sftp/ scp等)通过该通道请求服务。
恢复session密钥
恢复session密钥的第一步是分析OpenSSH源代码并调试现有的OpenSSH二进制文件,我尝试自己编译OpenSSH,将生成的session密钥记录在某个地方,并附加一个调试器,然后在程序的内存中搜索这些密钥。成功以后,session密钥保存在堆中的内存中。对源代码的更多深入研究使我了解了负责发送和接收NEWKEYS框架的函数。我发现有一个存储“session_state”结构的“ssh”结构,该结构又包含与当前SSHsession有关的所有信息,包括一个newkeys结构,其中包含与加密、mac和压缩算法有关的信息。经过深入研究,我们最终找到了包含密码名称、密钥、IV和块长度的“shenc”结构。OpenSSH的结构概述如下所示:
SSHENC的结构和关系
以及shenc结构的定义:
SSHENC结构
很难在内存中找到密钥本身(因为它只是一个随机字节的字符串),但是shenc和其他结构更加独特,具有一些我们可以验证的属性。然后,我们可以抓取程序的整个内存地址空间,并验证针对这些约束的每个偏移量。我们可以检查以下属性:
- 名称,密码,密钥和iv成员都是有效的指针;
- name成员指向一个有效的密码名,该密码名称等于cipher-> name;
- key_len在有效范围内;
- iv_len在有效范围内;
- block_size在有效范围内;
如果我们针对所有这些约束进行验证,那么应该能够可靠地找到shenc结构。为此,我开始构建一个POC Python脚本,该脚本可以在一个实时主机上运行,该主机连接到进程并为该结构勾勒出内存。可以在这里找到此脚本的源代码,它实际上工作得很好,并为找到的每个密钥输出一个json blob。因此,我演示了可以使用Python和ptrace从活动主机中恢复session密钥,但是我们如何从内存截图中恢复session密钥呢?这就是Volatility发挥作用的地方。Volatility是一个用Python编写的内存取证框架,可以编写自定义插件。经过一些努力,我能够编写Volatility 2插件,并且能够分析内存截图并转储session密钥!对于Volatility 3 plugin contest ,我还将该插件移植到了Volatility 3,并提交了该插件并进行了研究。
Volatility 2 SSH Session密钥转储器的输出结果
解密和解析流量
用于加密和解密通信的session密钥的恢复是成功的,接下来是解密通信流量!我开始用pynids(TCP解析和重组库)解析一些pcap。我使用内部开发的dissect.cstruct库解析数据结构,并开发了解析框架来解析ssh等协议。解析框架基本上以正确的顺序将数据包送入协议解析器,因此,如果客户端发送2个数据包,而服务器响应3个数据包,则这些数据包也将以相同的顺序提供给解析器。这对于保持整体协议状态很重要,解析器基本上使用SSH框架,直到遇到NEWKEYS框架为止,这表明下一框架已加密。现在,解析器从该源中窥视流中的下一框架,并迭代提供的session密钥,以尝试解密该框架。如果成功,解析器将以该状态安装session密钥以解密session中的其余框架,解析器几乎可以处理OpenSSH支持的所有加密算法,具体过程可以查看以下动图:
SSH协议解析
最后是运行中的解析器,你可以在其中看到解密和解析SSHsession的过程,还暴露了用户用于身份验证的密码:
解密和解析的SSHsession示例
总结
本文我研究了SSH协议,以及如何将session密钥存储并保存在OpenSSH的内存中,并找到了一种从内存中提取它们并在网络解析器中使用它们来解密SSH会话并将其解析为可读输出的方法。本研究中使用的脚本可以在以下链接中找到:
- 将Python POC转储为SSHsession密钥;
- Volatility 2插件;
- Volatility 3插件;
- SSH协议解析器;
- 更好的选择将是在Wireshark中实现这个解密器和解析器。
有趣的是,在研究期间,我还在OpenSSH源代码的ssh_set_newkeys函数中遇到了这些注释行。其实这很讽刺,如果不对这些行进行注释并在OpenSSH二进制文件中进行编译,那么这项研究就会困难得多。
OpenSSH源代码片段
本文翻译自:https://research.nccgroup.com/2020/11/11/decrypting-openssh-sessions-for-fun-and-profit/
【责任编辑:赵宁宁 TEL:(010)68476606】