SSH协议流量解密与会话还原

阅读量    103860 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

不久前,我们参与了一起取证分析事件,客户的一个Linux服务器被入侵,攻击者留了一个OpenSSH后门。客户保存了入侵过程中的完整数据流量包和虚拟机系统快照,我们想看看是否可以从虚拟机快照的内存中提取密钥来解密网络流量中的SSH会话。在本文中,将介绍我对OpenSSH所做的研究,并发布一些从内存中抓取OpenSSH会话密钥的工具,配合数据流量可以解密SSH会话。

 

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协议帧示例:

在协商加密算法并生成会话密钥之前,SSH协议帧是不加密的,即使帧内容被加密,根据加密算法的不同,帧的部分内容可能也是明文的。例如,aes256-gcm不会加密帧的长度字段(4字节),但是chacha20-poly1305会进行加密。

接下来,客户端将向服务器发送KEX_INIT消息来协商会话参数,例如用于密钥交换和加密的算法。根据这些算法的优先级,客户端和服务端将选择第一个双方都支持的算法。在KEX_INIT消息之后,双方互发几个与会话密钥交换相关的消息,然后再互发NEWKEYS消息。此消息告诉另一端,所有准备工作都已就绪,消息报文中的下一帧将开始加密。在生成的会话密钥开始生效后,客户端将开始进行用户身份验证,具体验证方式取决于服务端的配置(密码、公钥等)。当身份验证通过后,将打开一个信道,所有后续的服务请求(ssh/sftp/scp等)都将通过该信道进行。

 

恢复会话密钥

恢复会话密钥的第一步是分析OpenSSH源代码并调试OpenSSH二进制文件。我尝试自己修改并编译OpenSSH源码,将OpenSSH运行时生成的会话密钥记录下来,然后用调试器挂上OpenSSH进程,在内存中搜索会话密钥,最终成功的在堆内存上找到了会话密钥。在分析源代码中责发送和接收NEWKEYS帧的函数时,我发现有一个ssh结构体,里面有一个成员结构体叫session_state,该结构体成员依次保存着与当前SSH会话有关的所有信息,其中有一个newkeys结构体,储存着加密、mac和压缩算法有关的信息。继续往下深挖,最终找到了包含cipher,密钥,IV和分块长度的sshenc结构体。这下所有我们需要用于解密的信息都有找到了!下图是OpenSSH结构的概述:

以及sshenc结构的定义:

在内存中找到密钥本身非常困难(因为它只是一串随机字节),但是sshenc结构体非常特别,它具有一些可以验证的属性。我们可以抓取程序的整个内存地址空间,并用偏移量来对这些约束进行验证。我们可以验证以下约束条件:

  1. name,cipher,key和iv成员是有效的指针
  2. name指向的内存是一个有效的cipher名称,且需要与cipher->name相同
  3. key_len在有效范围内
  4. iv_len在有效范围内
  5. block_size在有效范围内

通过验证上述所有的约束条件,那么应该能够比较稳定的找到sshenc结构。我开始写一个能够用调试器挂上OpenSSH进程并在内存中搜索该sshenc结构体的Python脚本。该脚本的源代码:OpenSSH-Session-Key-Recovery。脚本的功能非常稳定,会将找到的每个sshenc结构打印出来。到目前为止,已经可以使用Python和ptrace从运行状态的机器中恢复会话密钥,但是我们如何从内存快照中恢复会话密钥呢?这就需要用到Volatility。Volatility是一个用Python编写的内存取证框架,可以编写自定义插件。经过一些尝试,我编写了一个Volatility 2插件,能够分析内存快照并抓取会话密钥!我还将该插件移植到了Volatility 3,并将该插件提交给了社区。

Volatility 2 SSH 会话秘钥抓取结果如图:

 

解密和解析流量

现在已经成功提取了内存中所有用于加密和解密流量的会话密钥,接下来就是解密流量。我使用Pynids(一个TCP流量解析和重组的Python库)来解析数据流量,使用我们内部开发的dissect.cstruct库来解析数据结构体,在此基础上开发了一个解析框架来解析诸如ssh之类的协议。解析框架将数据包以正确的顺序发送到协议解析器,如果客户端发送2个数据包,而服务器回复3个数据包,则这些数据包也将以相同的顺序提供给解析器,这对于保持整体协议状态很重要。解析器会正常解析SSH帧报文,直到遇到NEWKEYS帧为止,这表明下一帧将会开始加密。当解析器拿到第一个加密帧时,会遍历所有提取到的会话密钥来尝试解密该帧。如果解密成功,则解析器会使用这个密钥来解密会话中接下来的所有加密帧。这个解析器几乎可以处理OpenSSH支持的所有加密算法。解密过程动画示例如下:

最后解析器会将解密后的会话还原出来,甚至还能看到用户用于认证的密码:

 

结论

本文研究了SSH协议原理,以及OpenSSH进程如何在内存中存储话密钥,找到了一种从内存中抓取它们并在网络流量解析器中使用它们的方法,能够将SSH会话解密并还原为可读输出。文中所用脚本如下:

  1. 直接从内存中抓取会话秘钥的Python POC脚本
  2. Volatility 2插件
  3. Volatility 3插件
  4. SSH协议解析器

后续可能会将解密器和解析器实集成到Wireshark中。

 

参考资料

  1. https://fossies.org/dox/openssh-8.4p1/structssh.html
  2. https://fossies.org/dox/openssh-8.4p1/structsshenc.html
分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多