TC基础

概述

在Linux中,高性能数据包处理的黄金标准是XDP,通过在网络驱动程序接收到数据包时(在到达内核之前)进行拦截,XDP能够在大多数环境中实现线速。

但是,XDP主要用于入站(ingress)流量,对于路由器和负载均衡器来说,这个限制完全没问题,它们处理的每个数据包都来自外部接口,从内核的角度来看,它们都是入站流量。当需要处理一些出站流量(egress)时,XDP则无法完成我们的要求。

目前,处理出站流量的流行方法是采用TC(Traffic Control,流量控制)机制来实现,它允许在入口和出口进行数据包处理。

TC有四大组件:

  1. qdisc(Queuing disciplines),分为不可分类队列(classless qdisc)和可分类队列(classful qdisc),它的本质是一个带有算法的队列,默认的算法是FIFO,形成了一个最简单的流量调度器。
  2. class,它的本质是为上面的qdisc进行分类。因为现实情况下会有很多qdisc存在,每种qdisc有它特殊的职责,根据职责的不同,可以对qdisc进行分类。
  3. filters,它是用来过滤传入的网络包,使它们进入到对应class的qdisc中去。
  4. policers,通常会紧跟着filter出现,定义命中filter后网络包的后继操作,如丢弃、延迟或限速。

从协议栈上看,TC位于链路层,其所在位置已经完成了sk_buff的分配,要晚于XDP。为了实现对数据包发送和接收的控制,TC使用队列结构来临时保存并组织数据包,在TC子系统中对应的数据结构和算法控制机制被抽象为qdisc,其对外暴露数据包入队和出队的两个回调接口,并在内部隐藏排队算法实现。在qdisc中可以基于filter和class实现复杂的树形结构,其中filter被挂载到qdisc或class上用于实现具体的过滤逻辑,返回值决定了该数据包是否属于特定class。

当数据包到达顶层qdisc时,其入队接口被调用,其上挂载的filter被依次执行直到一个filter匹配成功;此后数据包被送入该filter指向的class,进入该class配置的qdisc处理流程中。TC框架提供了所谓classifier-action机制,即在数据包匹配到特定filter时执行该filter所挂载的action对数据包进行处理,实现了完整的数据包分类和处理机制。

现在的TC为eBPF提供了direct-action模式,它使得一个作为filter加载的eBPF程序可以返回像TC_ACT_OK 等TC action的返回值,而不是像传统的filter那样仅仅返回一个classid并把对数据包的处理交给action模块。现在,eBPF程序可以被挂载到特定的qdisc上,并完成对数据包的分类和处理动作。

1
2
3
4
5
6
7
8
9
10
# 传统: filter和action分开挂
tc filter add dev eth0 parent 1: protocol ip prio 1 \
u32 match ip protocol 1 0xff classid 1:10
tc filter add dev eth0 parent 1: protocol ip prio 1 \
action drop

# eBPF DA模式: 一个程序搞定分类+处理
tc filter add dev eth0 ingress prio 1 handle 1 bpf \
obj tc_prog.o sec classifier \
direct-action # ← 关键标志,启用 DA 模式

和XDP一样,TC的输出代表了数据包如何被处置的一种动作,它的定义在pkt_cls.h找到。

动作名称 数值 说明
TC_ACT_UNSPEC -1 未指定/默认行为
TC_ACT_OK 0 处理完成,继续正常路径
TC_ACT_RECLASSIFY 1 重新分类
TC_ACT_SHOT 2 丢弃数据包
TC_ACT_PIPE 3 继续执行下一个action
TC_ACT_STOLEN 4 包被接管,内核不再处理
TC_ACT_QUEUED 5 包被排队(用于整形)
TC_ACT_REPEAT 6 重复执行
TC_ACT_REDIRECT 7 重定向到设备/CPU
TC_ACT_TRAP 8 陷阱到CPU(硬件)或等效STOLEN(软件)

环境搭建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装iproute2软件包
sudo apt-get install iproute2

# 查看tc版本
tc -V

# 查看当前网卡的队列规则(确认没有报错)
sudo tc qdisc show

# 安装编译依赖和工具链
sudo apt-get install -y build-essential clang llvm libelf-dev gcc-multilib

# 安装当前内核的头文件(开发eBPF必需)
sudo apt-get install -y linux-headers-$(uname -r)

eBPF内核马

前言

本节内容尝试复现veo师傅提出的eBPF内核马,ebpf_shell是一个基于eBPF技术实现的内核级WebShell。

  • 无进程、无端口、无文件:注入后文件可删除,难以通过常规手段检测
  • 隐蔽性强:执行命令不会新建shell进程,无法通过常规行为检测
  • 内核级注入:将WebShell注入内核,无法通过常规内存检测
  • 协议无关性:可改造内核马,适配HTTP协议以外的所有协议
  • 双向流量控制:支持Ingress(入口)和Egress(出口)流量拦截与修改

技术原理

架构

  • eBPF 程序:运行在内核态,通过TC钩子拦截和修改网络流量
  • 用户态程序:负责执行命令,并通过BPF Map与内核态程序通信

工作流程

  1. 命令执行流程

  1. 结果查询流程

  1. eBPF架构

代码

详细代码参考eBPF Shell 内核马实现,实现方式不唯一,本项目仅供安全研究和教育目的使用,使用者需自行承担所有法律责任。

使用

1
2
3
4
5
6
7
8
9
10
11
# 查看网络接口
ip link show

# 启动 eBPF Shell(需要 root 权限)
sudo ./ebpf_shell -iface ens34

# 执行命令
curl -X POST http://192.168.31.97/ -d "cmd=uname -a"

# 查询执行命令的结果
curl -X POST http://192.168.31.97/ -d "res=uname -a"

参考

全链路内存马系列之 eBPF 内核马

eBPF 入门实践教程二十:使用 eBPF 进行 tc 流量控制