仿写"美国NSA方程式bvp47顶级后门"-隐蔽信道篇
背景描述
Bvp47是2013年盘古实验室研究员在针对某国内要害部门主机的调查过程中,提取了一个经过复杂加密 的Linux平台后门。是一款集基于SYN包的高级隐蔽信道、身份认证及加密通信、自身代码混淆、系统隐藏、静态反调试及自毁设计、SeLinux安全机制绕过的一个世界级顶级后门。
仿写目标
- 功能描述:
既定目标 | 完成时间 | 完成情况 |
---|---|---|
SYN敲门ebpf后门xdp程序隐蔽信道构建 | 2022年5月10日 | √ |
后门加载器,同时在加载器实现反弹shell | 2022年5月16日 | √ |
SYN敲门攻击端程序(打算用python写) | 2022年5月18日 | √ |
后门加载增加公私钥认证功能 | 2022年5月20号 | o |
编译环境
ubuntu20.04,内核5.4.0。
运行环境
内核5.4.0以上。
隐蔽信道设计思路
Bvp47隐蔽信道解读
根据盘古实验室发布的《Bvp47美国NSA方程式的顶级后门》这篇报告显示,Bvp47后门程序通过加载bpf程序来构建隐蔽信道,从而达到“敲门”的效果 ,大致工作图示如下。
BPF简介
经过我对eBPF的一番学习和了解,BPF全名Berkeley Packet Filter ,诞生于1992年一篇名为《 The BSD Packet Filter: A New Architecture for User-level Packet Capture 》的论文。
BPF 技术最早源于 BSD系统,后移植至 Linux 内核,常用的 tcpdump 就是基于 BPF 技术,BPF早期被称作cBPF,在内核开发者的不断扩展下,演变成如今的eBPF。由此可知,BPF功能模块最初是为了高效处理网络数据包而开发。
XDP介绍
XDP全称eXpress Data Path,是一个内核态、高性能、可编程 BPF 包处理框架 。不同于常规的DPDK这类内核旁路可编程包处理过程,将网络硬件的控制权从内核层转移到到用户空间,XDP保留了完整的从网络硬件->网卡驱动->内核网络协议栈->socket->用户应用层的网络数据包处理过程。通俗的说,XDP只是一个介于内核网络协议栈之下的一系列hook点,能更早的处理网络数据包,大大减小数据包在庞大且复杂的内核协议栈中被处理带来的资源和时间的消耗,从而更高效的处理网络数据包。
XDP工作模式
XDP程序工作模式大致分为以下三种:
- Offload XDP
直接attach到网卡 ,还在网卡驱动之前, hook到可编程的智能网卡硬件设备上,无需主机CPU执行处理,由网卡上的程序处理,大大节省主机CPU资源消耗,目前仅有Netronome 这个牌子的智能网卡支持这种模式。
- Native XDP
直接运行在网络驱动的早期,接收来自网卡发送数据给网络驱动的处理路径上,hook在网络驱动处理之前。但是也不是所有网络驱动都支持,比如我在虚拟机运行Ubuntu时常见的e1000、e1000e网络驱动就不支持Native模式。
- Generic XDP
介于网卡驱动程序之后,用于不支持Native XDP网卡驱动的主机上, 需要分配额外的套接字缓冲区,性能较Native XDP差一些,但也早于tc和内核网络协议栈处理。
XDP三种运行模式大致所处位置:
1 | NIC设备------>Driver---->Traffic Control-->Netfilter-->TCP stack-->Socket-->application |
XDP工作用途
XDP程序正常可用作 DDoS 防御、防火墙、 转发和负载均衡、数据帧抽样监控等高效的网络数据包处理工具。
基于TCP协议首部的隐蔽信道
XDP既然可以在数据链路层截获以太帧,让我们来分析分析,为何选择在TCP协议首部隐藏数据,而不是选择在以太帧首部、IP协议首部或者是TCP数据段来传递隐藏数据。
首先,以太帧的首部格式如下:
前同步码:占7个字节,用来使接收端的适配器在接收 MAC 帧时能够迅速调整时钟频率,使它和发送端的频率相同。前同步码为 7 个字节,1 和 0 交替。
帧开始定界符:占1个字节,帧的起始符,前6位1和0交替,最后的两个连续的1。
目的MAC地址:占6个字节,存放目的MAC地址。
源MAC地址:占6个字节,存放源端MAC地址。
类型:占2个字节,描述上层网络层协议类型。
数据:称为效载荷,表示交付给上层的数据,这里指的是上层IP协议的IP数据包。
帧检验序列 FCS: 占4个字节,存放CRC值,用于接收端检查整个帧的是否出差错。
经过以上介绍,发现以太帧头部并没有冗余字段,无法隐藏需要传递的信息。
接下来是IP数据报格式如下:
版本:占 4 bit,表示 IP 协议的版本,通信双方使用的 IP 协议版本必须一致,该字段无法利用。
首部长度:占 4 bit,用来描述首部长度,该字段不建议利用。
区分服务:占 1个字节,这个字段在旧标准中实际一直没有被使用过。现在只有在使用区分服务时,这个字段才起作用,可以利用。
总长度:占 2个字节,首部和数据之和,单位为字节。能描述数据报的最大长度为 2^16-1=65535 字节,该字段也无法利用。
标识:占 2个字节,数据报分片传输时,具有相同的标识字段值的分片报文会被重组成原来的数据报,可以利用。
标志:占 3 bit,用于描述数据报分片状态的字段,不建议利用。
片偏移:占 13 bit,用于描述数据报分片后,标记该分片在原报文中的相对位置,不建议利用。
生存时间TTL:占 1个字节,用于描述数据包在网络中传输的寿命,最大值为255,每经过一次路由TLL减1,用于防止出现广播风暴,不建议利用。
协议:占 1个字节,描述上层传输层协议类型,无法利用。
首部检验和:占2个字节, 用于校验数据报的首部,由于TTL会一直变,所以这个字段无法利用。
源地址:占4个字节,表示数据报的源 IP 地址,无法利用。
目的地址:占4个字节,表示数据报的目的 IP 地址,无法利用。
可选字段:占4个字节,可有可无,主要用于测试、调试和安全的目的,正常的数据包不会包含这个字段,不建议利用。
数据部分:表示传输层的数据,如 TCP、UDP这类协议的数据,长度不固定。
以上是IP数据报格式,首部最小也有20个字节,含有冗余字段有标识 2个字节、区分服务字段 1个字节。
接下来是TCP报文的头部格式,
源端口:占2个字节,源计算机上的应用程序的端口号,无法利用。
目的端口:占2个字节,源目标计算机的应用程序的端口号,无法利用。
序列号字段:占4个字节,表示本报文段所发送数据的第一个字节的编号,用于对序列号进行同步,作为TCP握手的第一个SYN包时,可以利用。
确认号字段:占4个字节,表示接收方期望收到发送方下一个报文段的第一个字节数据的编号,不能时随机值,当作为SYN包时,默认为0,不建议利用。
数据偏移字段:占4bit, 指数据段中的“数据”部分起始处距离 TCP 数据段起始处的字节偏移量,其实这里的“数据偏移”也是在确定 TCP 数据段头部分的长度,告诉接收端的应用程序,数据从何处开始,无法利用。
保留字段:占4bit,无实际用处,目前必须全部为 0,不建议利用。
标志位字段:
- CWR(Congestion Window Reduce):拥塞窗口减少标志,用来表明它接收到了设置 ECE 标志的 TCP 包。并且,发送方收到消息之后,通过减小发送窗口的大小来降低发送速率。
- ECE(ECN Echo):用来在 TCP 三次握手时表明一个 TCP 端是具备 ECN 功能的。在数据传输过程中,它也用来表明接收到的 TCP 包的 IP 头部的 ECN 被设置为 11,即网络线路拥堵。
- URG(Urgent):表示本报文段中发送的数据是否包含紧急数据。URG=1 时表示有紧急数据。当 URG=1 时,后面的紧急指针字段才有效。
- ACK:表示前面的确认号字段是否有效。ACK=1 时表示有效。只有当 ACK=1 时,前面的确认号字段才有效。TCP 规定,连接建立后,ACK 必须为 1。
- PSH(Push):告诉对方收到该报文段后是否立即把数据推送给上层。如果值为 1,表示应当立即把数据提交给上层,而不是缓存起来。
- RST:表示是否重置连接。如果 RST=1,说明 TCP 连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。
- SYN:在建立连接时使用,用来同步序号。当 SYN=1,ACK=0 时,表示这是一个请求建立连接的报文段;当 SYN=1,ACK=1 时,表示对方同意建立连接。SYN=1 时,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中 SYN 才为 1。
- FIN:标记数据是否发送完毕。如果 FIN=1,表示数据已经发送完成,可以释放连接。
总共1个字节,该字段不建议利用。
窗口大小字段:占2个字节,表示从 Ack Number 开始还可以接收多少字节的数据量,也表示当前接收端的接收窗口还有多少剩余空间。该字段可以用于 TCP 的流量控制,不建议利用。
TCP 校验和字段:占2个字节,也成为 TCP Checksum,用于接收端来验证TCP数据包的完整性的,Checksum 是根据伪头 + TCP 头 + TCP 数据三部分进行计算的,无法利用。
紧急指针字段:占2个字节,当URG 控制位为1时才有意义 ,因为SYN包,URG位为0,所以这个字段默认为0,不建议利用。
可选项字段:占4个字节,没有特殊用途正常情况下垃圾数值,可以利用。
以上是TCP数据报格式,含有冗余字段有序列号字段 4个字节、可选字段 4个字节可以被利用隐藏数据。
问答环节
问:那SYN包的TCP数据段能拿来用吗?
答:Bvp47有在SYN包的TCP数据段携带88个字节的信息,但是那毕竟是2000年零几年的隐蔽信道,正常的SYN包的TCP数据段为空?所以我不想太扎眼,不建议在SYN包的TCP数据段携带隐蔽信息。
隐蔽信道后门实现思路
XDP隐蔽信道程序和攻击者“敲门”方式的实现思路
以上介绍了XDP的正常用途,和基于TCP协议的隐蔽性信道。如果我把XDP程序集合对TCP隐蔽信道的利用,不仅能传递隐蔽信息,而且XDP程序还能最早处理攻击者的数据包,在XDP经过简单的“敲门”验证后,把身份认证信息传回用户层,即可直接DROP掉该SYN包,能逃避基于内核网络协议栈的安全模块的检测,从而最大程度做到隐蔽通信。
攻击者对后门程序的“敲门”唤醒采用特定构造的基于TCP的SYN包,将隐蔽信息藏在TCP头的序列号字段 4个字节和可选字段 4个字节,这里编写基于python3的攻击脚本。
XDP加载器及反弹shell实现思路
xdp程序加载的方式除了通过iproute2这个命令行工具来手动加载,也可以通过c、python、go等语言自行编写xdp程序来加载,这样做的好处是,如果目标机器默认没有安装net-tools这个工具包,自然没有iproute2这个工具,自行编写的xdp加载器就能够很好的适应这种突发状况,避免过度依赖环境。这里我采用了c语言实现xdp加载器程序。
map是驻留在内核的高效键值对仓库,既可以被BPF程序(包括xdp。程序)直接访问,也可以从用户空间通过文件描述符访问,自然成了BPF程序和用户空间通讯的极佳方式。因此,xdp程序可以通过map将攻击者的认证信息和源ip地址通过map仓库,传给应用层的xdp加载器程序,由xdp加载器程序继续完成身份验证和反弹shell的动作。
不成熟的身份认证设计理念
为什么要做身份认证?
答:防止他人伪造我的SYN请求包,“敲击”并唤醒后门程序,就算其他人捕捉到了后门样本进行分析,也无法伪造出的我的“敲门”请求。
身份认证的方案:
建议:SM2 + 祖冲之序列密码算法
答:不宜用常规的非对称的签名算法,例如RSA-1024的签名信息就有128字节,SM2当然也不行,需要将签名信息放到SYN包的data段,有点刺眼,不方便隐蔽信道传输。“方程式顶级bvp47后门”就是采用了将身份验证信息放到SYN包放到data段,我猜各大厂商应该做了相对应防范措施。
解决方案:采用RSA-32算法,将身份验证的信息藏到tcp头里,虽然能藏的信息有限,相对简单,但是也能增加破解者的伪造请求难度。
备用解决方案:
答:采用了写死的魔数字到tcp头的seq字段,用来做简单的验证。
参考
[美] David Calavera,[意] Lorenzo Fontana 著,范彬,狄卫华 译 《Linux内核观测技术BPF》
[译] Cilium:BPF 和 XDP 参考指南(2021)
[译] [论文] XDP (eXpress Data Path):在操作系统内核中实现快速、可编程包处理(ACM,2018)
娄嘉鹏, 张萌, 付鹏, 张开. 一种基于TCP协议的网络隐蔽传输方案设计[J]. 信息网络安全, 2016, 16(1): 34-39.