zoukankan      html  css  js  c++  java
  • Packet Chasing:通过缓存侧信道监视网络数据包

    摘要

    本文介绍了一种对网络的攻击--Packet Chasing,这种攻击不需要访问网络,无论接收数据包的进程的特权级别如何,都能发挥作用。一个间谍进程可以很容易地探测和发现网络驱动程序使用的每个缓冲区的确切缓存位置。更有用的是,它可以发现这些缓冲区用于接收数据包的确切顺序。这样就可以通过缓存侧信道监控数据包频率和数据包大小。该攻击既可以在发送者和无法访问网络的远程间谍程序之间建立秘密通道,也可以进行直接攻击,识别网络上受害者的网页访问模式等。除了识别潜在的攻击外,本工作还提出了一种基于软件的短期缓解措施,以及一种轻量级、自适应的缓存分区缓解措施,以阻止末级缓存中的I/O和CPU请求的干扰。

    关键词:侧信道攻击,缓存攻击,DDIO,包处理,安全,微架构安全

    1. 导言

    现代处理器采用了越来越复杂的微结构技术,这些技术经过精心优化以提供高性能。然而,这种复杂性往往会滋生安全漏洞,最近的Meltdown和Spectre就证了这一点。本文探讨了另一种复杂的高性能微架构技术--Intel数据直接I/O (DDIO) ,该技术在大多数服务器级英特尔处理器中实现,用来加速网络数据包的处理速度。此外,本文还提出了针对网络I/O流量的新的高分辨率隐蔽和侧信道攻击,虽然在没有DDIO的情况下,这些攻击也是可能的,但在DDIO存在时,这些攻击的效果会大大增强。

    多千兆以太网和其他高速网络I/O技术(如Infiniband)的广泛采用,凸显了高速处理网络数据包以维持这种新出现的网络吞吐量,并进一步提高带宽密集型数据中心工作负载的性能的重要性。因此,目前大多数英特尔服务器级处理器都采用了DDIO技术,该技术允许直接在处理器的末级缓存(LLC)中注入和后续处理网络数据包,绕过传统的DMA(直接内存访问)接口。DDIO对包括操作系统驱动程序在内的软件来说是不可见的,而且总是默认启用。

    DDIO背后的关键动机是,现代服务器级处理器采用了大型LLC(大小为20MB)从而使网络堆栈能够在LLC内完全托管热数据结构和进程中的网络数据包,减少对主存储器的访问。通过消除冗余的内存传输,DDIO已经被证明可以在I/O带宽和整体功耗方面提供实质性的改进。尽管英特尔限制为DDIO分配超过10%的LLC,以防止缓存污染,但它既没有为DDIO静态保留也没有为DDIO动态分区缓存的专用部分。

    然而,尽管DDIO具有加速网络数据包处理的良好目标,但它有一个以前不为人知的漏洞,本文对此进行了揭露。在DDIO主机上,来自远程客户端的传入网络数据包和来自本地主机上进程的应用数据结构会争夺共享的LLC,在发生缓存冲突时可能会相互驱逐。在本文中,我们展示了这种争夺提供了显著的信息泄漏,允许缓存侧信道攻击执行秘密通信和推断网络行为,而对网络堆栈的访问几乎为零。特别是,我们描述了一类新的覆盖式和侧信道缓存攻击,称为数据包追逐,通过使用精心构建的内存访问模式和网络数据包流在LLC中制造任意冲突来利用这种争夺。

    我们进一步表明,网络驱动程序使用的数据包缓冲区的位置(在缓存中),以及它们被填充的顺序,很容易被攻击者发现,这大大减少了跟踪被追逐的数据包序列所需的探测量。

    我们在本文中所描述的基于数据包追逐的隐蔽通道,通过在末级缓存中引起确定性的争夺,允许一个与本地DDIO主机上的服务器守护进程一起隐蔽运行的间谍进程从网络上的远程客户端上运行的木马进程接收秘密信息。我们表明,尽管木马进程只发送广播数据包,而且间谍进程与面向网络的服务器守护进程(可能是跨容器和跨虚拟机)完全隔离,并且进一步缺乏对网络堆栈的任何访问,但这种秘密通信手段是可行的,并且可以在高带宽下实现。

    除了秘密通道,我们还描述了一种新型的基于数据包追逐的侧信道攻击,这种攻击利用了和网络浏览器同时运行(或在网络浏览器内)的本地间谍进程。在我们的实验中,在客户端的间谍程序是与浏览器(如Mozilla Firefox)同时运行的,它能够在不接入网络的情况下对远程受害者的网站访问进行指纹识别。特别是,这种攻击使攻击者能够根据数据包大小模式识别受害者的网络活动。这种类型的网络指纹可以被一个政府用来识别被禁止的网站的访问,或者攻击者可以仅仅通过成功登录会话的指纹来识别一个安全组织的成员(然后进行更有针对性的攻击)。

    此外,本文还描述了一种基于软件的短期缓解措施,称为环形缓冲区随机化,以及一种自适应地将LLC划分为I/O和CPU分区的硬件防御机制,防止I/O数据包驱逐CPU或被攻击者的缓存块。与存在漏洞的原始DDIO相比,我们在本文中描述的自适应分区防御的性能开销小于2.7%。

    本文的主要贡献如下:

    (1) 在DDIO开启的情况下,很容易发现普通网络驱动的包缓冲区的位置;

    (2) 每发送一个数据包的大小(以缓存块为增量)也可以被发现;

    (3) 被发现的缓冲区被重复访问的顺序也可以被推断出来;

    (4) 在网络上发送数据包的木马和另一台机器上的间谍之间可以建立秘密通道;

    (5) 数据包大小的序列/模式会在侧信道上泄露敏感信息,如网络访问活动的痕迹;

    (6) 提出了一种短期的纯软件随机化方案以及自适应的缓存分区方案,以最小的性能开销抵御该攻击。

    2. 背景和相关工作

    本节提供了网络数据包处理、DDIO和相关网络优化、基于网络和I/O的攻击以及缓存攻击的背景知识。

    A. 网络数据包的旅程

    当一个应用程序通过网络发送数据时,通常会发送一个数据流;而传输层的责任是将大的消息分解成网络层可以处理的小块。最大可传输单元(MTU)是指可以通过传输介质发送的最大连续数据块。例如,以太网MTU是1500字节,这意味着以太网帧可以携带的最大IP数据包(或其他有效载荷)是1500字节。加上以太网头部的26个字节,则最大帧为1526字节。

    当NIC驱动程序初始化时,它首先分配一个用于接收数据包的缓冲区,然后创建一个描述符,其中包括接收缓冲区大小及其物理内存地址。然后,它将接收描述符添加到接收环 (rx ring),这是一个驱动器和 NIC 共享的循环缓冲区,用于存储传入的数据包,直到它们可以被驱动器处理。然后,驱动程序通知 NIC,它在 rx 环中放置了一个新的描述符。NIC 读取新描述符的内容,并复制大小和物理地址到其内部存储器。在这一步,初始化完成,NIC就可以接收数据包了。

    图1所示,当接收到传入的数据包时,NIC使用直接内存访问(DMA),将数据包复制到rx环中提供的物理地址,然后发送一个中断来通知驱动程序。驱动程序从rx环中取出新的数据包,并将每一个数据包放在一个称为socket buffer (skb)的内核数据结构中,以开始它们通过内核网络栈的旅程,直到拥有相关socket的应用程序。最后,驱动程序将接收缓冲区放回rx环中,以便用于未来的数据包。

    B. 直接缓存访问和数据直接I/O

    现代处理器和操作系统采用了一些网络I/O性能增强措施,以解决内存子系统中的数据包处理瓶颈。Hug-gahalli等人提出了直接缓存访问(DCA),它使网卡能够向处理器的硬件预取器提供预取提示。DCA要求内存写入到主机内存,然后处理器预取内存写入指定的缓存行。英特尔Sandy-Bridge-EP微架构引入了数据直接I/O (DDIO) 技术,该技术可以透明地将网卡或其他I/O设备的数据直接推送到末级缓存中。在DDIO之前,I/O数据总是通过主内存发送,接收到的数据由I/O设备写入内存,然后数据在访问前被预取,或者在处理器访问时根据需要取进缓存。但采用DDIO后,一个I/O区域的DMA事务会直接进入末级缓存,而且会处于脏模式,只有在被驱逐时才会被写回内存。

    虽然DCA和DDIO已经被证明可以在许多场景下通过降低缓存未命中率来提高数据包处理速度,但如果设备具有大的描述符环,它们可能会将有用数据从LLC中驱逐出去而降低性能。此外,正如我们在本文中所展示的那样,这些技术可能会导致新的漏洞,因为数据包被直接带入LLC,而LLC是由处理器中的所有内核共享的。

    C. 基于网络的侧信道

    基于网络隐蔽信道的文献比比皆是,它们利用网络协议作为载体,将数据编码成协议特征。例如,隐蔽信道可以通过在帧或包头的未使用或保留的位中编码数据,如TCP紧急指针用于指示高优先级数据。在TCP协议中,初始序列号(ISN)是第一个序列号,由客户端任意选择。Rowland提出将每个隐蔽字节向左移位26位,直接作为TCP的ISN。Abad表明IP头校验字段也可以被利用来进行隐蔽通信,并进一步提出将秘密信息编码到校验字段中,并加入IP头扩展的内容来弥补校验字段的修改,选择的内容要使修改后的校验字正确。其他的头字段如地址字段和数据包长度也可以被利用来构建秘密信道。除了头字段外,数据包速率和时序、数据包丢失率和数据包排序也能被用来构建隐蔽信道。

    这些隐蔽信道很多都是基于协议的非标准或异常行为,可以通过简单的异常检测方法进行检测和预防。此外,这些基于网络的隐蔽信道都要求接收者能够接入网络并能够接收数据包,而我们的追包攻击中接收者不需要任何接入网络的权限和许可。

    D. 缓存攻击

    基于缓存的侧信道攻击是最常见的一类架构时序信道攻击,它利用缓存作为其唯一的隐蔽通信媒介。这些攻击有可能通过利用受害者进程和间谍进程争夺共享缓存时产生的时间变化来揭示敏感信息,如加密密钥、按键和网页浏览数据。例如,在PRIME + PROBE攻击中,间谍进程通过学习受害者的时间秘密依赖性缓存访问模式,通过与受害者争夺相同的缓存集,并测量由于这种争夺而产生的时序变化来推断秘密。在prime步骤中,攻击者仅仅通过访问其数据,就能用自己的缓存块填充一个或多个缓存集。然后,在idle步骤中,攻击者等待一个时间间隔,让受害者执行和使用缓存,可能会驱逐攻击者的块。最后,在probe步骤中,攻击者测量加载每一组缓存块所需的时间。如果明显慢了,它就可以推断出受害者访问了该组中的一个块,替换了攻击者的块。

    为了在细微的时间粒度中执行这些攻击,攻击者必须针对末级缓存中的特定集。因此,它必须知道地址如何映射到LLC中的集。然而,从Sandy Bridge微架构开始,英特尔采用了一种新的LLC设计,LLC被分割成多个片,每个核心一个(见 图2),用一个未公布的哈希函数将物理地址映射到片中,据说是将物理地址统一分配到各核心中。对于大多数处理器,这个哈希函数已经成功地被逆向工程分析出来,包括Intels Sandy Bridge、Ivy Bridge和Haswell架构。

    除了PRIME+PROBE之外,还提出了其他多种缓存攻击的变种。FLUSH + RELOAD使用Intel的CLFLUSH指令将目标地址从缓存中驱逐出,然后在测量阶段,攻击者重新加载目标地址并测量其访问时间。但它依赖于间谍程序和受害者之间的共享内存,并且需要访问精确的定时器。PRIME+ABORT利用英特尔的事务性内存扩展(TSX)硬件来装载无计时器的末级缓存攻击。

    文献中已经提出了一些防御措施来缓解缓存时序通道。这些缓解策略包括识别软件中的泄漏,观察无规则缓存行为,在硬件设计时关闭通道,动态缓存分割,将物理内核严格保留给安全敏感线程,随机化,内存跟踪遗忘,以及使用诱饵加载微操作的缓存状态混淆。

    E. I/O设备和驱动程序的安全性

    一些针对设备驱动程序的安全攻击已经公布。Thunderclap描述了一种颠覆输入输出内存管理单元(IOMMU)保护的攻击,以暴露DMA功能的I/O外设可用的共享内存。Zhu等人展示了另一种绕过IOMMU并危及GPU驱动的攻击,以利用GPU微代码获得对CPU物理内存的完全访问。为了解决这些漏洞,研究者们将重点放在隔离设备驱动上,当设备驱动出现bug或有故意恶意的代码时,要使操作系统安全。Tiwari等提出了一个完整的系统,该系统包括一个I/O子系统和一个微内核,通过监视和控制系统的信息流,实现隔离和安全通信。

    NetCAT是我们的"追包"攻击的一个并发工作。它描述了一种利用类似底层漏洞的攻击。然而,本工作和NetCAT在许多重要方面存在差异。首先,NetCAT只检测数据包的到达时间,而Packet Chasing有能力同时检测每个数据包的到达时间和大小--后者更可靠,噪音更小。这使得基于Packet Chasing的攻击有机会发动更强大的攻击,比如我们在本文中描述的网络指纹攻击(第五节)。

    NetCAT也是我关注的一篇文章,发表在 S&P 2020

    其次,与Packet Chasing不同,NetCAT需要DDIO和RDMA技术的存在,限制了其通用性。因此,要缓解NetCAT,只需禁用DDIO或RDMA即可。然而,正如我们在本文中所展示的,即使在没有这些技术的情况下,Packet Chasing攻击也是实用的。因此,我们也提出了一种更复杂而又高性能的防御措施来缓解攻击。

    3. Packet Chasing,发起攻击

    我们对加载到Linux Kernel 4.4.0-142版本中的英特尔千兆以太网络(IGB)驱动5.3.5.22版本进行分析和攻击。我们在戴尔PowerEdge T620服务器上运行攻击,该服务器使用Intel I350网络适配器,由两个Intel Xeon CPU E5-2660处理器运行。每个处理器都有一个20 MB的末级缓存,有16384个缓存集。为了对末级缓存进行PRIME+PROBE,我们使用Mastik微架构侧信道工具包0.02版。

    我们的攻击包括两个阶段。一个是离线阶段,攻击者恢复缓冲区的序列,另一个是在线阶段,攻击者利用这些信息来监控传入的数据包。

    A. NIC驱动的解构

    虽然本小节的代码样本是针对英特尔千兆以太网(IGB)驱动程序的,但我们注意到这些见解是可以通用的。最初的以太网IEEE 802.3标准将以太网帧的最小尺寸定义为64字节,最大尺寸为1518字节,后来最大尺寸增加到1522字节,以允许VLAN标记。由于驱动程序和网卡事先并不知道传入数据包的大小,所以网卡必须分配一个可以容纳任何大小的缓冲区。IGB驱动程序为每个帧分配一个2048字节的缓冲区,并将最多两个缓冲区打包成一个4096字节的页面,该页面将与网络适配器同步。为了保证兼容性,建议设备驱动程序在为DMA映射内存区域时,只映射在页边界上开始和结束的内存区域,而页边界也保证是缓存线边界。另外,在主机处理数据包时,rx环缓冲区用于暂时保存数据包。虽然在环中采用更多的缓冲区可以降低丢包率,但也会增加主机内存的使用量和缓存的占用。因此,虽然英特尔I350适配器支持的最大大小是4096个缓冲区,但IGB驱动程序中的默认值是设置为256个。

    linux内核在DMA API中,为设备驱动程序提供了两种不同类型的DMA内存分配。相干内存(或一致内存)和流式DMA映射。相干内存是DMA内存映射的一种类型,对于这种类型的内存,设备或处理器的写入可以被处理器或设备看到和读取,而不需要显式同步,也不用担心缓存效应。但是,处理器在通知设备读取该内存之前,必须先刷新写缓冲区。因此,一致内存在某些平台上可能会很昂贵,因为它由于写障碍和缓冲区的刷新,总是需要等待。缓冲区本身使用流式DMA映射,而环描述符则使用相干内存映射。因此,设备和驱动程序对环描述符有相同的视图。同时,这也使得对rx描述符环的写入成本很高。因此,为了避免改变rx描述符的内容,驱动程序在接收数据包后通常会重复使用缓冲区,而不是分配新的缓冲区。所以,驱动程序通常只分配一次缓冲区,并在整个驱动程序的生命周期中重复使用。

    图3 显示了IGB驱动代码中在接收数据包时被调用的部分,它的工作是将rx缓冲区的内容添加到socket缓冲区中,而socket缓冲区将被传递给IP层。如果数据包的大小小于一个预定义的阈值(默认为256),那么驱动程序就会复制缓冲区的内容,然后尝试为以后的数据包使用相同的缓冲区。如果缓冲区是在远程NUMA节点上分配的,那么对该缓冲区的访问时间就会比在本地NUMA节点上分配缓冲区的时间多得多。因此,为了提高性能,驱动程序会取消分配的远程缓冲区,并为该rx环描述符重新分配一个新的本地缓冲区。如果数据包大小大于256,那么IGB驱动就不直接复制,而是将页面作为一个片段附加到Socket缓冲区,然后调用 图4 所示的 igb_can_reuse_page 函数。这个函数检查两个不太可能满足的条件:第一个条件是缓冲区是在远程NUMA节点上分配的,第二个条件是内核仍在另一半页面中准备数据包,并且驱动程序不是页面的唯一所有者。如果这两个条件都不满足,驱动程序就会翻转 page_offset 字段,这样设备就只使用页面的后半部分。

    NUMA:全称 Non-Uniform Memory Access,译为“非一致性内存访问”。这种构架下,不同的内存器件和CPU核心从属不同的 Node,每个 Node 都有自己的集成内存控制器。

    在 Node 内部,架构类似SMP,使用 IMC Bus 进行不同核心间的通信;不同的 Node 间通过QPI(Quick Path Interconnect)进行通信,如下图所示:

    参考:https://www.jianshu.com/p/0607c5f62c51

    总结一下,在常见的情况下,驱动程序在256个不同的页面上使用少量的环形缓冲区(256个),每个缓冲区都是半页对齐的,并且会不断重复地使用这些缓冲区,通常直到下一次系统重启或网络重启。此外,为了保持较高的(和一致的)数据包处理速度,在整个驱动代码执行过程中,环描述符的顺序不会改变。因此,只要驱动程序重复使用描述符的缓冲区,缓冲区的顺序就会保持不变。

    B. 恢复环形缓冲区的缓存足迹

    追包攻击者的最终目标是通过窥探末级缓存来获取传入数据包的大小和时序信息。为此,我们安装了PRIME+PROBE攻击末级缓存。然而,盲目地探测所有的缓存集并不能给我们提供太多的信息,这是因为探测时间受限于访问整个缓存的时间,在这种情况下,大约是1200万个CPU周期,探测时间太长了,无法获得任何有用的传入数据包信息。探测时间过长也使得攻击更容易受到背景噪声的影响,因为在缓存行上观察到不相关活动的概率会增加。

    然而,从上一小节,我们知道在内核内存中存储数据包的缓冲区是页面对齐的。这意味着我们只需要探究页对齐地址所映射的缓存集。拥有4KB的页大小意味着起始地址的最低12位为0。所以缓存集索引的最低6位为零(也见图2)。这就限制了我们在每个分片中只能有32个集合,总共有256个可能的集合。使用Mastik工具包,我们找到这些集合,并为它们构造驱逐集,它本质上是一个地址流,保证替换集合中所有缓存块的所有其他数据。有了这些,我们就有能力监控所有256个缓存集,它们都是缓冲区位置的潜在候选者。

    虽然所有的 NIC rx 缓冲区都映射到我们得到的其中一个页对齐的缓存集,但这个映射的分布是不均匀的,这意味着一些rx缓冲区是不是均匀地映射到同一个缓存集。为了展示这种缓存集冲突的例子,我们通过驱动代码来打印环形缓冲区的物理地址,然后将其映射到缓存集索引。图5 显示了这种非统一映射,仅仅是NIC中缓冲区分配的一个实例。水平轴显示了其中一个页对齐的缓存集,在Y轴上,我们显示了映射到每个页对齐缓存集的NIC缓冲区的数量。在这个例子中,我们看到有5个NIC缓冲区被映射到缓存集号165,而没有一个NIC缓冲区被映射到缓存块65。

    Cache set 和 Cache line 的关系:

            ---   +---+-----+-----------------------------------+   
             ^    | v | tag | cache line                        |  每个cache set的cache line
             |    +---+-----+-----------------------------------+  的个数也是这个cache
       set   |    | v | tag | cache line                        |  way(路)的数目
             |    +---+-----+-----------------------------------+
             |    ...
             v    | v | tag | cache line                        |
            ---   +---+-----+-----------------------------------+
    
                  +---+-----+-----------------------------------+
                  | v | tag | cache line                        |
                  +---+-----+-----------------------------------+
                  ...
                  | v | tag | cache line                        |
                  +---+-----+-----------------------------------+
                  | v | tag | cache line                        |
                  +---+-----+-----------------------------------+
    

    参考:https://blog.csdn.net/scarecrow_byr/article/details/82584489

    图6 进一步分析了这种映射,该图显示了多个驱动程序初始化实例执行相同实验的结果。对于大约35%的页对齐缓存集,没有共同映射的NIC缓冲区,而在1000个实例中,我们只看到有5个缓冲区映射到同一个页对齐的缓存集。

    通过将监控的缓存集数量缩小到只有256个可能的缓冲区起始位置,我们能够看到NIC设备在接收数据包时缓存中的清晰足迹,如 图7 所示。在这个实验中,我们依靠一个远程发送者,他与间谍程序在同一个网络上,并不断向网络发送广播以太网帧。为此,我们使用Linux raw socket,它可以生成任意大小的广播以太网帧。由于协议字段是未知的,这些帧会在驱动程序中被丢弃。因此,我们所看到的效果只是由驱动程序或适配器访问缓冲区引起的,而没有内核网络栈的任何活动。在样本25k左右,发送者开始发送数据包,并且一直持续到样本100k。在一些缓存集中,例如,缓存集编号53,我们不看到任何活动,那是因为没有一个NIC缓冲区被映射到这些集合。

    追包攻击者,能够区分空闲系统与有传入数据包的时候,利用这个特征可以建立一个信息泄漏通道,并利用这个通道在网络上隐蔽地传递数据。我们可以根据帧的大小来区分接收流,进一步提高这个通道的带宽。由于接收到的数据包存储在连续的rx缓冲区中,因此,利用我们构建页对齐缓存集的方式,我们构建页中第二个缓存块的驱逐集。所有页面中的第二个缓存块都被映射到这256个缓存集中的一个。同样,我们也找到了页面中第三个和第四个缓存块所在的缓存集。现在我们不仅可以识别数据包的存在,还可以识别数据包的大小。

    图8 显示了一个简单实验的结果,我们发送不同大小的数据包,并测试我们检测数据包大小的能力。在列上,我们有四个不同的运行方式,发送的数据包大小不变,从一个缓存块(64字节)到四个缓存块(256字节)。在行上,我们显示了对四个不同的缓存驱逐集的检测,块0到块3,它们的目标是页对齐缓冲区中的第一到第四块。正如预期的那样,我们看到对角线及以上的活动很明显,而对角线以下没有活动。唯一的例外是1块数据包,它在第1块和第0块上都表现出活动。这是因为驱动代码中有一个性能优化,无论数据包大小,都会预取数据包的第二个块。这种优化的原因是大多数以太网数据包至少有两个块,64字节的数据包(0-Block Packet)只在没有有效载荷的控制数据包中常见,如TCP确认数据包。

    攻击可以区分出不同大小的数据包流,只需要检测到达的数据包是小数据包流还是大数据包流(本质上是二进制信号),可以来构建带宽达到1950字节/秒的远程隐蔽信道(详见第四章)。然而,如果我们用更细的粒度来区分数据包大小,每个数据包都会发送多个比特的信息,我们就可以把它变成一个更强大的通道。下面的小节描述了我们在执行PRIME+PROBE时用来进一步缩小监测集的方法。

    C. 通过高速缓存追逐数据包

    攻击者因为不知道哪些缓冲区会先被填满,所以只能一次性探查所有256个页对齐的缓存集来检测传入的数据包,然后再探查更多的缓存集来检测数据包大小。然而,如果我们知道驱动中缓冲区被填充的顺序,那么我们实际上可以通过只探测下一个预期缓冲区对应的缓存集来追赶缓存上的数据包,建立一个强大的高分辨率攻击。我们表明,在一次性统计分析阶段,几乎可以完全恢复缓冲区的序列。由于缓冲区总是被回收,然后返回到环中,因此在驱动的生命周期内,环中缓冲区的顺序是保持不变的。

    算法1 描述了我们用来恢复序列的 SEQUENCER 过程。它包括三个步骤。首先,在 GET_CLEAN_SAMPLES 步骤中,我们收集Nsets缓存集的缓存探测样本。为此,我们从构建页对齐的NIC缓冲区的驱逐集开始。然而,有时我们在一些集上会出现总是漏掉的情况,这很容易被先验观察到。对于这些缓存集,我们只需使用页对齐缓冲区的第二个缓存块来代替第一个缓存块。

    之后,我们开始构建一个完整的加权图,节点是被监控的缓存集,连接节点x和节点y的边上的权重是我们观察到上一次活动在x缓存集上,本次活动在y缓存集上的活动的次数,如图9 中最左边的图所示。为了处理多个缓冲区可以映射到同一个缓存集的问题,当我们构建图时,我们为每个边缘维护一个节点历史。这样算法就可以通过后继的缓存集来区分两个或多个不同的缓冲区映射到同一个缓存集上的活动。所以,例如在图9 中,两个不同的缓冲区被映射到缓存集2,这些缓冲区占据了环形缓冲区的93号和193号位置。因此,在最后的序列中,我们有两个2号缓存集的不同实例,一个跟在缓存集3后面,另一个跟在缓存集1后面。

    最后一步,MAKE SEQUENCE,就是遍历我们在前面几步中建立的图,从一个随机节点开始,继续向前移动,直到到达同一个节点。需要注意的是,由于最后的序列是一个环,其中每个节点的内度和外度正好是一个,所以起始节点的选择并不会改变结果。

    虽然这个过程可以恢复被映射到Nset中的缓冲区的顺序,但它只能在我们监控有限的一部分页对齐的缓存集的情况下才能做到这一点(我们最多成功监控到64个缓存集)。这是因为如果我们在监控列表中包含更多的集,那么探测时间会变得比检测传入数据包的顺序所需的时间要长。所以我们首先找到32个缓存集的序列,然后我们用前31个节点(节点0到节点30)加上一个候选节点(如32)重复sequencer过程,我们尝试找到候选节点在序列中的位置。然后,我们重复同样的过程,在节点序列中移动,直到我们在序列中找到所有缓存集的位置。

    有时,两个连续的缓冲区被映射到一个集合中。例如,考虑图9 中93号和98号缓冲区被映射到2号集合的情况。通过我们的方法,我们不会在第一轮就捕捉到这些情况,但是从一开始,当我们遇到一个介于两者之间的缓冲区时,我们可以在图中把两者分开。如果它们在最后一环中真的是连续的(不太可能),那么缓冲区基本上就会被合并,但这对我们创建隐蔽通道的机制没有影响,而且对我们观察到的网络指纹泄漏攻击中的整体指纹影响很小。

    我们测量Levenshtein距离来量化我们获得的序列和我们从驱动设备中获得的真实序列之间的差异。两个序列之间的Levenshtein距离是将一个序列改变成另一个序列所需的最小单字符编辑数(即插入、删除或替换)。我们在表I 中看到了这个实验的结果,对探查速率进行微调是一项颇具挑战性的任务,因为它需要足够长的时间,以使每个传入数据包的活动仅接触一个样本,并且还需要足够小,以免丢失传入连续数据包之间的时间关系。否则,我们会发现所获得序列的准确性下降。然而,在我们的隐蔽信道构造中,很多攻击场景下,我们只需要找到环中相隔足够远的缓冲区,所以序列的小误差是可以容忍的。

    在分析期间,我们依靠一个远程发送程序,其唯一的工作就是不断发送数据包。然而,只要系统在接收数据包,即使没有外部发送者的帮助,间谍程序也可以恢复序列。事实上,这一步中的噪声(发送程序没有发送的额外数据包)只是帮助了间谍程序。

    4. Packet Chasing:在没有网络访问的时候接收数据包

    在本节中,我们通过在网络上构建一个隐蔽的通道来展示追包攻击的有效性。我们假设一个简单的威胁模型,一个远程木马试图通过网络,向位于同一物理网络中的一个间谍进程发送秘密信息。间谍程序可以在容器内部,并且容器和主机OS都没有root特权,也不允许使用网络堆栈。木马进程具有向物理网络发送数据包的能力,但没有授权的方法与间谍进行通信。

    a) 信道容量。为了建立一个量化比较不同编码和同步方案的框架,我们按照Liu等人描述的方法来测量我们的信道带宽和错误率,同时传输一个周期为 215 - 1 的长伪随机比特序列。伪随机比特序列是使用一个15位宽的线性反馈移位寄存器(LFSR)生成的,它覆盖了 215 个序列,除了所有比特都是0的情况。这样我们就可以发现其在传输时发生的各种错误。包括比特丢失、比特的多次插入或比特交换。

    b) 数据编码和同步。间谍程序首先选择x,即只有一个环形缓冲区被映射到的页面对齐的缓存集之一。使用第三节中描述的方法,找到这样一个页对齐的缓存集并不困难。然后,间谍程序找到地址x + 64、x + 2 * 64和x + 3 * 64被映射到的缓存集。换句话说,它找到页对齐缓冲区的第二、第三和第四个缓存块被映射到的缓存集。如第三节所述,间谍程序知道这些缓存集的缓存集索引位,但不知道哈希函数的结果(分片位)。为了找出准确的分片,间谍程序执行一个试错过程,在这个过程中,它根据缓存集上的活动选择八个分片中的一个。在这一步之后,初始化工作完成,间谍程序不断监视找到的缓存行。

    间谍程序在时间帧n时,发送256个(环形缓冲区的长度)大小为(S+2)* 64的数据包来传输符号S,由于我们在网络上运行并且延迟经常波动,因此我们无法使用归零自同步编码,而是选择使用同步时钟编码方案,其中缓冲区的第一块充当时钟,使间谍程序与特洛伊木马同步。我们测量了两种情况下的带宽和错误率。首先,我们在每个数据包中编码一个二进制符号,即我们发送64字节的数据包,编码为"0",或者发送256字节的数据包,编码为"1"。第二,我们在每个数据包中发送一个三元符号,即发送64字节的数据包编码"0",发送192字节的数据包编码"1",发送256字节的数据包编码"2"。

    例如,图10 显示了真实实验中间谍程序接收到的序列的一部分。在这个实验中,木马程序传输序列"2012012012......",间谍程序从三个缓存集中收集一个样本。每200000个周期,解码时,间谍程序用三个样本的窗口来区分不同的数值。这是因为有些时候我们看到一个数据包(一个符号)的缓存活动跨越了两个周期(图中的宽峰)。间谍程序不应该将这些情况解码成两个不同的符号。此外,如果因为到达数据包的延迟而使缓存集上的活动变得偏斜,有一个窗口会有所帮助。缓冲区所在的第一个缓存集上的活动作为同步时钟,其他两组上的活动可以显示传输的值。监控两个缓存集的活动只能给我们传递三个不同的符号,因为发送一个包含3个缓存块的数据包,在第2个缓存块所在的缓存集上一定有活动。

    为了估计错误率,我们再次使用伪随机比特序列的发送和接收数据之间的编辑Levenshtein距离。图11 显示了我们的编码方案的带宽和错误率,以及改变探测速率的效果,即我们在连续探测之间等待的时间。在不同的探测速率下,信道的带宽几乎是不变的,这是因为这里的限制是线路速率。我们使用的是1Gb/s的以太网链路,传输的是平均大小为192字节的数据包。帧大小为192的数据包的最大帧速率约为每秒50万帧。由于我们是每256个数据包发送一个符号,所以理论上我们的最大带宽被限定在每秒1953个符号。通过对三个符号进行编码,这个追包隐蔽信道的带宽可以达到3095 bps。但是,随着我们减少探测时间,错误率会降低。这是因为在两次连续的探测之间有较长的等待时间,我们就会提高捕捉到缓存集上不相关的活动的概率。当我们使用二进制编码时,我们同时使用缓存集2和缓存集3的样本,如果它们在一个窗口内都有活动,我们就解码为"1"。因此,错误率比三元编码略低。

    c) 利用环形缓冲区序列信息。如果我们知道缓冲区的顺序,这种机制很容易扩展到每256个数据包发送一个以上的符号。在这种情况下,木马可以选择n个缓冲区,将环形缓冲区划分为n个大小相近的部分,理想的情况是相隔256/n个缓冲区,每隔256/n个数据包就可以发送一个秘密信息。选定的缓冲区应该是只映射到一个页对齐的缓存集的缓冲区。然后,间谍程序开始监视所选的缓存集及其相邻的缓存块,以检测填充这些缓冲区的数据包的大小。

    这个过程可以使秘密通道的容量成倍增加,如图12a12b所示。这些数字显示了间谍程序监控环中不同数量缓冲区情况下的带宽和错误率。对于每一个缓冲区,间谍程序都会探测三个缓存集,这些缓存集与填充这些缓冲区的数据包的第一、第三、第四个缓存块相关联。对于只有一个被监控缓冲区的情况,木马以256个数据包发送一个隐蔽消息,对于16个被监控缓冲区的情况,木马程序每隔256/16=16个数据包发送一个新消息。当我们将监控的缓冲区数量增加一倍时,信道的带宽几乎增加了一倍,对于16个监控缓冲区的情况,带宽高达24.5kbps,错误率几乎保持不变,直到传入数据包之间的时间变得接近两次连续探测之间的时间。请注意,当我们的监控列表中有更多的缓存集时,每次探测需要更多的时间,这就降低了探测速率。此外,随着被监控的缓冲区数量的增加,在环中找到相隔n个缓冲区并且不与环中的任何其他缓冲区共享缓存集的缓冲区变得更加困难。由于这些原因,当我们监控环中16个缓冲区时,我们会看到错误率发生了跳跃。请注意,这些和后续的结果也考虑到了我们解构环序列时产生的不精确性。

    图12c12d 显示了另一个实验的结果,在这个实验中,我们实际使用环的序列追逐数据包。我们每次探测一个缓冲区,一旦探测到的缓冲区上有活动,我们就转到序列中的下一个缓冲区。不同步率是指追包错过一个数据包的概率。错过该数据包后,它必须等到整个环路完成,或者下一次数据包填满该缓冲区时,才能再次得到同步。带宽是由发送方传输数据包的速率控制的,错误率是在传输的同步区域上计算的。从图中可以看出,在不同的数据包速率下,错误率几乎是不变的。这是因为当我们只探测一组数据时,探测的分辨率要高于连续数据包之间的时间。此外,我们获得的不同步率是我们获得的序列质量的函数。错误率在640 kbps时会发生跳跃,因为在这个速度下,数据包开始在接收端失序到达。

    d) DDIO/DCA的可检测性和作用。在DDIO存在的情况下,携带隐蔽信息的数据包是很难被检测和过滤的(比如被防火墙系统丢弃发送到受害节点的数据包),因为这些数据包可以是普通的广播数据包,比如DHCP和ARP,甚至不要求这些数据包的目的地是运行了间谍程序的主机。这是因为,在DDIO/DCA的情况下,网络适配器会直接将数据包传输到处理器的末级缓存中,只有在这之后,驱动程序才会检查每个帧的头,并丢弃那些不针对该机器中任何协议的数据包。也就是说,在启用DDIO后,即使A只向同一网络中的C机发送数据包,我们也可以在A机和B机之间建立通道。

    DDIO使Packet Chasing获得更清晰的信号,因为有效负载的缓存块与属于数据包头的缓存块同时出现在缓存中。这使得该攻击能够探测相邻的缓存块,并快速检测每个数据包的大小。然而,如果DDIO被禁用或不存在,数据包的传输过程就会有所不同。首先,网卡将数据包的报头存储在内存中,然后驱动程序读取报头,并根据报头字段对数据包进行处理。这样就把包含头的缓存块带入缓存中。对于大多数常见的高层协议(如http),软件栈会在数据包报头到达后不久访问数据包的其他部分。

    在没有DDIO的时候,I/O写入和驱动读取之间的延迟成为攻击中的一个因素。在这种情况下,攻击者应该将探测时间设置为大于该延迟。当考虑了该延迟后,数据包的缓存足迹与DDIO情况下保持一致。然而,增加探测间隔会导致每个间隔中捕获更多的噪声。但正如《Direct cache access for high bandwidth network i/o》中表征的那样,几乎100%的数据包的延迟都小于20k周期。这个延迟还取决于数据包的大小。对于小于5个缓存块的小数据包,因为驱动程序会将这类数据包复制到另一个缓冲区,有效载荷几乎会在数据包头被访问后立即被访问。在这种情况下,不使用DDIO的攻击与使用DDIO的攻击一样容易检测到小数据包的数据包大小。

    简而言之,DDIO使攻击更隐蔽,更可靠(噪音更小)。但如果没有DDIO,攻击也完全可以实现。举个例子,下一节介绍的网络指纹攻击会安装在有DDIO和没有DDIO的系统上。

    5. Packet Chasing:利用包的大小

    在本节中,我们将介绍一个Packet Chasing攻击的应用示例,在这个应用中,我们利用数据包大小的高分辨率样本来获取同一地点用户的浏览数据信息。例如,间谍程序可能在等待受害者进入某个网站后再发起一些行动,如密码检测攻击。

    这种简单的攻击包括两个阶段。首先是离线阶段,攻击者生成感兴趣的不同网站的数据包大小痕迹,然后处理这些痕迹,计算出每个网站的代表性痕迹。这只是对数据包大小进行逐点平均,从而得到这些点(平均数据包大小)随时间变化的矢量。

    在为攻击做准备的过程中,攻击者建立环形缓冲器的序列,如前面所述。之后,该攻击者启用了监听模式,在这个模式下,它不断地监视序列中第一个缓冲区的前两个缓存块,直到她发现一个窗口,其中缓存块0和缓存块1都有活动。这表明有数据包正在填充该缓冲区。然后,与秘密信道中的接收器类似,在每次检测到活动时,攻击者就会移动到序列中的下一个缓冲区。每次,攻击者都会监视缓冲区第一半页的前四个块,以及缓冲区第二半页的前四个块。这是因为当有大数据包时,驱动程序会在半页之间切换(见第三节 - A),这使得攻击者可以区分四个级别大小的数据包。在收集到数据包大小的样本后,间谍程序将收集到的向量送入一个简单的基于相关性的分类器,该分类器计算收集到的样本与不同目标代表性痕迹的交叉相关性。

    图13 显示了我们通过Packet Chasing获得的信号和使用tcpdump数据包分析器捕获的实际数据包大小的例子。使用Mozilla Firefox 68.0.1版本浏览器访问网站。图中显示了数据包大小,即使是在缓存块粒度下,也可以成为被访问的网页的标识符。数据包通常在频谱的两侧都很拥挤,它们要么是携带了一个非常大的消息,被碎片化成了MTU大小的帧,要么是小的控制包。但是大消息的最后一个包可以落在1缓存块到MTU之间的任何地方,给我们提供了一个很好的网页指示。此外,将数据包大小与Packet Chasing从数据包中获得的时间信息相结合,给我们提供了足够的信息来区分webpages。我们使用一个小型的封闭数据集来评估我们的指纹攻击,其中有5个不同的网页:facebook.com、twitter.com、google.com、amazon.com、apple.com。在这个实验中,我们检查了两种攻击情景,一种是有DDIO的,一种是没有DDIO的。在我们的1000次实验中,使用DDIO的Packet Chasing检测出正确网站的准确率为89.7%,而禁用DDIO后,准确率降至86.5%。这两种攻击的区别是增加了探测时间(导致更多的噪声),如果数据包头到负载的延迟很高,那么错过大数据包的概率也会增加。

    在本实验中,我们使用了一个简单的分类器,但鉴于这种特殊攻击所面临的挑战,可以容忍噪声以及对向量进行轻微压缩或解压缩的分类器可能会改善这些结果。例如,《Automated website fingerprinting through deep learning》中的结果表明,只使用网络数据包的大小及其时序信息(Packet Chasing可以获得的确切信息),足以建立一个准确率高达95%的分类器。

    6. 潜在的软件缓解措施

    我们同时考虑了长期(需要改变硬件)和短期(仅软件)的缓解措施。在本节中,我们讨论了在部署长期硬件解决方案(例如,我们的I/O缓存隔离)之前,人们可以采用的潜在软件机制来帮助缓解攻击。这些解决方案都会带来一些性能影响。

    a) 禁用DDIO/DCA。DDIO之所以使得这些攻击能够实现,是因为它能确保数据包头和有效载荷同时出现在缓存中,大大简化了对数据包大小的检测过程。然而,如果没有DDIO,攻击仍然是可行的。如果我们能够检测到数据包的存在(数据包头总是立即被访问,并且会依次出现在缓存中),我们仍然可以使用数据包到达的间隔时间来建立隐蔽通道。我们还可以发送不同类型的数据包,其中对有效载荷的读取/处理是快速和确定的,这同样允许我们区分数据包大小。因此,禁用DDIO不能完全阻止攻击。

    b) 随机化缓冲区。当"数据包追逐"利用数据包填充环形缓冲区的顺序来提高侧信道和隐蔽信道的分辨率时,我们表明,在不知道缓冲区顺序的情况下,攻击仍然是可能的(第四节)。然而,随机化确实显著降低了信道带宽。随机化的成本可能相当高,因为驱动和网络适配器现在需要不断同步下一个缓冲区的地址。我们发起攻击需要一些时间,不过,可能只需要以半规则的间隔来改变缓冲区的顺序,从而限制了开销。

    c) 增加环的大小。在没有序列信息的情况下,如果攻击者想捕获每个数据包,则所需的缓存探测次数将随环的大小而变化。因此,不定期地重新调整缓存环和更大的缓存环,可能会有效地使需要探测的缓存集变得足够大,从而使攻击难以在不受到大量噪声影响的情况下被发起。

    7. 自适应I/O缓存分区防御

    第六节提出的所有短期的软件缓解措施要么不完全有效(禁用DDIO),要么会带来不小的性能损失。在本节中,我们描述了一种基于硬件的防御措施,它能从根本上解决该漏洞,即I/O和CPU缓存块在末级缓存中共享,这样I/O操作会导致其他进程的缓存行被驱逐出去。

    英特尔的DDIO技术通过为I/O流引入末级缓存写分配来改善内存流量。当收到来自I/O设备的写请求时(例如,对于传入的数据包),DDIO会在末级缓存中分配缓存块,并将这些缓存块设置为传入的I/O流量的DMA目的地。虽然出于性能方面的考虑,分配器在一个缓存集中分配的缓存块不会超过两个,但这些传入的数据包仍然会对CPU的块造成驱逐。这使得从运行在CPU上的攻击者进程角度可以观察到传入的数据包。

    为了规避这个问题,我们为每个集合(i)关联一个计数器,以保持I/O分区的大小( IO_linesi )不变。通过将其作为一个常数(在单个区间内)而不是最大值,我们确保DDIO填充的缓存行只会取代其他DDIO缓存行。为了适应程序执行的不同阶段,我们的分区方案通过增加或减少计数器( IO_linesi )周期性地改变CPU和I/O分区的边界。为此,我们为每个缓存集关联了另一组计数器,以检测每个缓存集上的I/O活动( IO_present_counteri )。如果缓存集上至少有一条有效的I/O行存在,这个计数器就会被增加,并在每一个自适应周期(p)开始时初始化为零。需要注意的是,维护这些计数器不会带来额外的性能开销,因为这些都是与缓存的命中和未命中路径并行完成的。

    在每一个自适应周期,我们也会重新评估末级缓存中的I/O和CPU的边界。对于每一个集合(i),如果( IO_present_counteri )大于一个高阈值 Thigh (例如 Thigh = 0.5p ),则意味着 seti 有了大量的I/O活动。在这种情况下,我们会递增 IO_linesi(使用一个饱和计数器),允许缓存集内有更多的I/O缓存块。否则,如果I/O活动小于一个低阈值Tlow,我们就减少IO_linesi(同样使用饱和计数器),以允许更多的CPU数据使用缓存。如果分区的边界发生变化,我们就会使受影响的缓存块无效,并对内存进行任何必要的回写。

    我们的自适应分区策略可以确保在CPU上运行的任何进程不会因为传入的数据包或I/O活动而导致其缓存行被驱逐。唯一例外的情况是在每个自适应周期,当边界发生变化时,一些CPU缓存块被驱逐。然而,我们将适应期设置得足够大,以防止攻击者搜集到任何关于单个数据包的有用信息。充其量,它可以在每个自适应周期内收到一个比特(网络活动的高或低)。

    a) 防御性能评估的系统设置。表二 详细显示了我们基线环境的处理器架构配置。我们使用gem5架构模拟器对该架构进行建模。我们使用gem5的全系统仿真模式,它允许我们启动内核版本为4.8.13的Ubuntu 18.04发行版Linux。我们对I/O分区中的最小和最大缓存块数(即 IO_lines)进行了硬性限制。因此,I/O分区的大小可以是一个、两个或三个。另外,在这些实验中,自适应周期(p)设置为10k个时钟周期。我们将阈值 TlowThigh 分别设置为2k和5k。此外,为了得到有关我们提出的防御对性能影响的合理估计,我们选择了一个会产生相当多I/O活动的基准测试组合。为此,我们包含了一个磁盘拷贝(使用Linux的dd工具),从磁盘拷贝一个100MB的文件。此外,我们还对一个不断接收具有8字节有效载荷的TCP数据包的程序进行了防御评估。最后,我们还利用wrk2框架生成HTTP请求,评估了我们的防御对Nginx Web服务器性能的影响。

    b) 防的性能结果。图14 显示了我们的自适应缓存分区方案的性能,比较了Nginx网络服务器的平均吞吐量。平均而言,我们观察到的吞吐量损失不到2%。这主要是由于CPU缓存分区的缓存行数减少,导致LLC未命中率略有上升(也见图15 )。该图还显示了防御策略对末级缓存大小的敏感性。在末级缓存大小为20 MB的情况下,我们的方法会产生最大2.7%的吞吐量损失。图15 进一步分析了防御的性能,显示了没有任何直接缓存访问(No DDIO)的基线与有DDIO访问和我们的自适应分区防御策略的内存流量和LLC未命中率。自适应分区和DDIO都能有效降低内存流量。自适应分区方案的内存流量增加在DDIO的2%以内。

    为了比较自适应缓存分区和我们提出的基于软件的缓解措施(第六节),我们设计了另一个使用wrk2工具的实验。在这个实验中,我们向目标主机上的Nginx Web服务器发送请求。wrk2工具使用8个线程,1000个打开的连接,目标吞吐量设置为每秒140k次请求。图16 显示了本次实验的结果。除了自适应缓存分区和vanilla IGB基线之外,我们还研究了另外三种提出的方案:完全随机化的环形缓冲区方案,为每个传入数据包在随机存储位置分配新缓冲区,以及两个部分随机化方案,在收到指定数量的数据包后,定期重新分配缓冲区 -- 我们在收到1k或10k个数据包后随机化。请注意,在我们的设置中,追包攻击目前至少需要65536个数据包才能完全解构环形缓冲区(找到缓存块位置和序列信息),另外还需要100个数据包才能发动合理的指纹攻击。自适应分区方法在横坐标99%的时候,延迟只发生3.1%的损失,而完全随机化方法则发生41.8%的损失。我们在这个实验中使用了千兆以太网,但我们预计随机化的性能成本会随着链路速率的提高而加剧。

    8. 披露

    我们向英特尔披露了这个漏洞,解释了该漏洞的基本实质,并提供了更多细节。MITRE 在 Common Vulnerabilities and Exposures(CVE)数据库中已经分配了一个编号:CVE-2019-11184,该漏洞被列为中度严重漏洞。

    9. 结论

    本文介绍了Packet Chasing,这是一种新型的缓存侧信道攻击,它通过一个无法访问网络、内核或进程的间谍程序来检测通过网络发送的数据包的频率和大小。DDIO网络优化不是导致这种攻击的原因,但却大大促进了该攻击。这项工作表明,网络驱动程序的内部工作原理很容易被设置攻击的间谍程序解构,包括用于接收数据包的每个缓冲区的确切位置(在缓存中)以及它们被访问的顺序。这两个信息极大地减少了间谍程序为跟踪网络数据包序列而必须进行的探测次数。这些信息使得远程发送程序和网络上任何地方的间谍程序之间可以有几个隐蔽的通道,并有不同的带宽和精度权衡。它还可以实现侧信道信息泄漏攻击,检测受害者进程的网络活动。

    除了隐蔽通道和侧信道攻击外,本文还描述了一种自适应的缓存分区方案,与易受攻击的DDIO基线相比,该方案以极低的性能开销减轻了攻击。

  • 相关阅读:
    SpringBoot集成springfox-swagger2访问swagger-ui.html页面弹窗提示问题
    Java数据结构与算法之队列(Queue)实现
    华为S9300交换机热补丁安装
    ubnt EdgeSwitch 24-Port 250W DHCP_CLI[osapiTimer]: dhcp_prot.c(812) 1285780 %% Failed to acquire an IP address on Network Port; DHCP Server did not respond.
    windows server 2012 r2查看远程用户登录IP
    iptables && firewall 的简单应用
    deepin如何自定义启动器图标,如firefox
    deepin如何访问samba共享文件夹
    vsftpd服务搭建
    华为服务器RH 2288H v2 or v3安装系统
  • 原文地址:https://www.cnblogs.com/sctb/p/13530072.html
Copyright © 2011-2022 走看看