什么是 eBPF

BPF(Berkeley Packet Filter)是一种数据链路层原始接口,用于收发原始链路层封包。1992 年,Steven McCanne 和 Van Jacobson 提出了一篇名为《BSD 数据包过滤:一种新的用户级包捕获架构》的论文,详细描述了他们如何在 Unix 内核中实现网络数据包过滤。BPF 在数据包过滤方面引入了两项重大创新:一种新的虚拟机(VM)设计,能够高效地运行在基于寄存器结构的 CPU 上,以及应用程序只复制与过滤数据包相关的数据,而不是复制数据包的全部信息。这样可以最大程度地减少 BPF 处理的数据量,提高过滤效率。由于这些巨大改进,所有 Unix 系统都选择采用 BPF 作为网络数据包过滤技术。至今,许多 Unix 内核的派生系统(包括 Linux 内核)仍在使用 BPF 实现。

eBPF 从 Linux 3.18 版本开始引入,但并不意味着只能在 3.18 版本以上的内核上运行。低版本的内核升级到最新版本后也可以使用 eBPF 功能,只是可能会受到一些限制。例如,在 CentOS7 中,3.10.940 版本以上的内核可以受限使用部分 eBPF 功能。低版本的 CentOS7 可以通过 yum 升级内核,并在重启后支持部分 eBPF 功能。因此,开发人员可以在不同版本的内核上使用 eBPF 功能,提高网络数据包过滤的效率和性能。

5be56e3a80065f205d47191fcefdd0f.png

支持的事件检测

  1. TCP 和 UDP 网络数据捕获,可以实时监控网络流量,检测网络攻击和异常行为;
  2. uprobe 方式的 DNS 信息捕获,可以检测 DNS 查询和响应,防止 DNS 劫持和欺骗;
  3. 进程数据捕获,可以监控进程的创建、退出和状态变化,及时发现恶意进程;
  4. uprobe 方式实现 JAVA 的 RASP 命令执行场景事件捕获,可以检测 Java 应用程序的命令执行行为,防止命令注入攻击。

支持的行为检测

  1. 检测容器运行时是否创建其他进程,防止恶意进程的横向移动;
  2. 检测容器运行时是否存在文件系统读取和写入的异常行为,防止文件篡改和数据泄露;
  3. 检测容器运行时是否打开了新的监听端口或者建立意外连接的异常网络活动,防止网络攻击和数据泄露;
  4. 检测容器中用户操作及可疑的 shell 脚本执行,防止恶意操作和攻击。

37b0a9f9686df2c893d58e7dccff9da.png

WebShell 检测

WebShell 攻击是指黑客利用 Web 应用程序漏洞将 WebShell 文件上传到受害主机上,并通过 WebShell 文件实现对受害主机的控制。WebShell 是一种可执行 Shell 命令的脚本文件,常见的有 PHP、JSP、ASP 等,可以通过 Web 应用程序执行,从而控制服务器。以 PHP WebShell 为例,攻击原理如下:

  1. 攻击者利用 Web 应用程序漏洞将 PHP WebShell 文件上传到目标服务器;
  2. 攻击者通过浏览器访问 PHP WebShell 文件,建立与 WebShell 文件的连接;
  3. WebShell 文件进行身份认证,认证通过后,攻击者就可以执行 Shell 命令,进而控制目标服务器;
  4. 攻击者利用 WebShell 控制服务器,进行各种恶意操作,如扩散病毒、窃取数据等。

具体实现可以使用 BCC 工具,使用 Python 编写 BPF 程序,如下所示:

#!/usr/bin/python
from bcc import BPF
import ctypes as ct

# define BPF program
prog = """
#include <uapi/linux/ptrace.h>
#include <linux/fs.h>

int trace_open(struct pt_regs *ctx, const char __user *filename, int flags, umode_t mode)
{
    char fname[256];
    bpf_probe_read_user(fname, sizeof(fname), filename);
    if (fname == NULL)
        return 0;

    // only trace files with php, jsp, asp suffix
    if (strstr(fname, ".php") || strstr(fname, ".jsp") || strstr(fname, ".asp")) {
        // check if file is opened with write or execute permission
        if ((flags & O_WRONLY) || (flags & O_RDWR) || (mode & S_IXUSR) || (mode & S_IXGRP) || (mode & S_IXOTH)) {
            // print warning message
            bpf_trace_printk("WebShell detected: %s\\n", fname);
        }
    }
    return 0;
}
"""

# load BPF program
b = BPF(text=prog)

# attach tracepoint
b.attach_tracepoint(tp="syscalls:sys_enter_open")

# print warning message
while True:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
        print("%s" % msg)
    except KeyboardInterrupt:
        exit()

该程序会跟踪系统中所有程序调用 open syscall 的记录,只关心后缀名为 php、jsp、asp 的文件,并检测是否命中特定规则。如果命中规则,则会打印警告信息。

open 系统跟踪调用

以下代码基于 BCC 中的 Python 框架搭建,运行时会将系统中所有程序调用 open 函数的记录打印出来。

#!/usr/bin/python
from bcc import BPF

prog = """
int trace_syscall_open(struct pt_regs *ctx, const char __user *filename, int flags) {
    u32 pid = bpf_get_current_pid_tgid() >> 32;
    u32 uid = bpf_get_current_uid_gid();

    bpf_trace_printk("%d [%s]\\n", pid, filename);
    return 0;
}
"""
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("open"), fn_name="trace_syscall_open")
try:
    b.trace_print()
except KeyboardInterrupt:
    exit()

trace_syscall_open 函数原型为 sys_open 函数在内核中的定义原型; b.attach_kprobe 是将我们定义的跟踪函数与系统调用 open 函数进行关联。

规则识别

针对此类文件需要具体分析,可以按一下规则去检测是否为 WebShell 文件:

  1. 检查是否存在系统关键变量,如 eval、system、exec、base64_decode、curl 等命令;
  2. 包含如上传/删除文件、远程访问、备份系统信息以及 SQL 注入等常见攻击;
  3. 检测文件中的 IP 地址和 URL 是否存在异常,是否指向境外或非正规的网站。

异常外连检测

检测思路:通过 krobekretprobe 跟踪所有 IPv4 连接尝试(即使最终失败),针对非保留地址的目标 IP 匹配是否命中威胁情报库。

connect 事件跟踪

#include <uapi/linux/ptrace.h>
#include <net/sock.h>
#include <bcc/proto.h>

BPF_HASH(currsock, u32, struct sock *);

int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk)
{
        u32 pid = bpf_get_current_pid_tgid();

        // stash the sock ptr for lookup on return
        currsock.update(&pid, &sk);

        return 0;
};

int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
{
        int ret = PT_REGS_RC(ctx);
        u32 pid = bpf_get_current_pid_tgid();

        struct sock **skpp;
        skpp = currsock.lookup(&pid);
        if (skpp == 0) {
                return 0;        // missed entry
        }

        if (ret != 0) {
                // failed to send SYNC packet, may not have populated
                // socket __sk_common.{skc_rcv_saddr, ...}
                currsock.delete(&pid);
                return 0;
        }

        // pull in details
        struct sock *skp = *skpp;
        u32 saddr = skp->__sk_common.skc_rcv_saddr;
        u32 daddr = skp->__sk_common.skc_daddr;
        u16 dport = skp->__sk_common.skc_dport;

        // output
        bpf_trace_printk("trace_tcp4connect %x %x %d\\n", saddr, daddr, ntohs(dport));

        currsock.delete(&pid);

        return 0;
}

bpf_trace_printk 将信息输出到 tracepipe,通过监听 /sys/kernel/debug/tracing/trace_pipe 获取四元组信息。

具体输出内容类似于:

trace_tcp4connect 0A010101 0A020202 80

其中,0A010101 表示十六进制源 IP 地址,0A020202 表示十六进制目标 IP 地址,80 表示目标端口号。

威胁情报

获取到四元组信息后,将目标 IP 与自研、开源、商用威胁情报进行匹配,用于锚定可疑外连行为。

挖矿

检测思路:挖矿通常会导致 CPU 资源的显著消耗,添加处理逻辑以检测挖矿行为。进行占用 CPU 时间监测:

#include <uapi/linux/ptrace.h>

struct data_t {
    u32 pid;
    u64 ts;
};

// prevent 32-bit overflow
typedef struct {
    u64 sum;
    u64 count;
} stats_t;

BPF_HASH(start, u32, u64);
BPF_HASH(cpu_stats, u32, stats_t);

int do_perf_event(struct pt_regs *ctx) {
    u64 id = bpf_get_current_pid_tgid();
    u64 ts = bpf_ktime_get_ns();

    start.update(&id, &ts);
    return 0;
}

int do_sched_switch(struct pt_regs *ctx) {
    u64 id = bpf_get_current_pid_tgid();
    u64 *tsp = start.lookup(&id);
    if (tsp == 0) {
        return 0;   // missed start
    }

    u64 now = bpf_ktime_get_ns();
    u32 pid = id >> 32;

    stats_t *stats = cpu_stats.lookup_or_init(&pid, &(stats_t){0, 0});
    u64 diff = now - *tsp;
    if (diff > 10 * 1000000) {   // 10ms threshold
        stats->sum += diff;
        stats->count++;
        if (stats->sum / stats->count > 2000) {  // 2000ns threshold
            struct data_t data = {
                .pid = pid,
                .ts = now,
            };
            bpf_trace_printk("CPU mining detected: %d, elapsed ns: %d\n", data.pid, stats->sum / stats->count);
            cpu_stats.delete(&pid);
        }
    } else {
        cpu_stats.delete(&pid);
    }
    start.delete(&id);

    return 0;
}

通过使用 perf 和 sched 事件,对占用 CPU 时间长的进程进行跟踪,并计算它们消耗的 CPU 时间。如果消耗的时间超过特定阈值(2000 纳秒),则可以将其视为 CPU 挖矿操作。

在 HIDS 场景下,eBPF 可以通过在内核中运行特定程序来监控系统的行为,实现安全审计,从而帮助保护系统的安全。相比于 netlink,eBPF 具有更好的性能和更灵活的编程模型。此外,与 ko 相比更加安全,不容易引起操作系统异常。


wzhvictor
1.5k 声望257 粉丝

宝剑锋从磨砺出,梅花香自苦寒来