找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3079

积分

0

好友

458

主题
发表于 1 小时前 | 查看: 2| 回复: 0

你是否想过,如何绕过传统监控方案的局限,在内核层面对DNS流量进行细粒度追踪?面对DNS缓存投毒、隧道窃密等日益复杂的威胁,仅靠应用层日志往往力不从心。本文将带你深入实践,利用eBPF技术直接在内核中捕获并解析DNS数据包,构建一个实时DNS查询与响应追踪工具,为你的网络安全监控体系提供全新视角。

本文译自 Tracing DNS Queries in Real Time with eBPF

许多依赖自建DNS服务器的组织,都面临着防范以DNS为中心的各种威胁的责任,例如缓存投毒、放大攻击或通过DNS隧道进行的隐蔽数据窃取。

日志记录是发现这些攻击的关键,但大多数DNS守护进程默认并不会记录响应。虽然你可以使用dnstap这类方案,但它通常要求你重新编译DNS服务器软件才能生效。

于是,我们提出了一个更直接的想法:为什么不利用eBPF直接在内核层面捕获DNS流量呢?这样做无需对现有服务做任何修改。

🚀 特别感谢Fortinet的系统安全工程师Geoffrey Bucchino撰写了这篇深入且实用的客座文章!


近年来,随着全球紧张局势加剧,网络安全已成为每个组织的首要任务。国家和网络犯罪团伙都在大力投资攻击能力,不断探测网络中的薄弱环节。

仅在2024年第一季度,攻击者就发起了超过 150万次针对DNS层的DDoS攻击,这足以凸显针对DNS基础设施的威胁规模。现代DNS威胁还包括勒索——2024年第二季度,12.3%的客户报告称曾因DDoS攻击受到威胁或勒索,攻击者利用服务中断来索取赎金。

这一切使得主动的DNS监控变得至关重要,我们需要能够记录每一次响应,并尽早识别异常。


考虑到攻击流量巨大,如果监控方案本身会给系统带来显著负载,那显然不是理想的选择。为此,我们开发了基于eBPF的DNS-Trace工具,用于跟踪和记录DNS查询及响应的具体内容。

DNS查询与响应追踪示意图

其设计思路非常直接:

  • 附加一个eBPF程序到原始套接字,用以捕获流经指定网络接口的所有数据包。
  • 分析每个数据包,判断其是否为DNS查询或响应,然后解析并记录关键信息,供用户空间程序读取。

💡 这是一个被动监控工具——它本身不会阻止或预防攻击。但它展示了一个非常有价值的概念:如何在内核中,使用eBPF直接处理DNS查询和响应数据包。

完整的代码可以在 Gitea仓库 中找到。下面的部分,我们将逐步拆解其实现的技术细节。

代码讲解与实现

在我们的用户空间应用程序中,我们使用libbpf库来加载和管理eBPF程序,并将其附加到我们感兴趣的原始套接字上。

sock = create_rsock(arguments.interface);
if (sock == -1){
    printf("Failed to listen to the interface %s\n", arguments.interface);
    exit(-1);
}
// ...
// Retrieving the eBPF program
programSkb = bpf_object__find_program_by_name(obj, "detect_dns");
if (!programSkb){
    printf("Failed to find program\n");
    bpf_object__close(obj);
    return -1;
}
// Attach eBPF program to the socket
bpf_program__attach(programSkb);
int prog_fd = bpf_program__fd(programSkb);
setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(int));

而在我们的eBPF程序中,我们从作为程序输入的 struct __sk_buff 变量中捕获并读取数据包。

SEC("socket")
int detect_dns(struct __sk_buff *skb) {
    // ...
}

__sk_buff 这个结构体包含了网络数据包的所有相关信息,例如各层头部(以太网、IP/IPv6、TCP/UDP)和有效载荷。

在这个概念验证中,我们简单地根据目标或源端口号(例如53)来判断一个数据包是DNS查询还是响应。更严谨的做法是解码DNS头部,并检查QR位是否设置为0(查询)或1(响应)。具体可参考 RFC 1035, 第4.1.1节

DNS查询解析

通过检查DNS消息的Question部分(使用 dnsquery 函数),我们可以解析并收集查询的详细信息,例如记录类型(A、CNAME、MX、TXT等)、查询类(IN、CH、HS等)以及查询的主机名。更多细节参见 RFC 1035, 第4.1.2节

解析查询类型和类相对简单,但要从DNS查询中提取出主机名,我们需要理解DNS的标签序列格式。

例如,如果有效载荷中域名 www.bucchino.org 的十六进制表示如下:

DNS域名十六进制格式示例

其解析规则如下:

  • 第一个字节 0x03 表示第一个标签的长度,因此后面三个字节 0x77 对应标签 www
  • 第四个字节 0x08 表示下一个标签长度为8,后面八个字节对应标签 bucchino
  • 第十四个字节 0x03 表示第三个标签长度为3,后面三个字节对应标签 org
  • 最后,空字符 0x00 标志域名结束。

这种从字节流中提取标签序列的逻辑,是通过 get_labels 函数实现的。解析出的所有信息会被存入一个 event 结构体,并通过eBPF的环形缓冲区(ring buffer)发送到用户空间,由我们的应用程序读取并打印。

DNS响应解析

在DNS消息中,Question部分之后就是Answer部分(同样定义于RFC 1035)。我们使用 dnsanswer 函数,逐字节解码响应中的资源记录(Resource Record),并将其同样发送至用户空间。

将eBPF程序编译、加载并附加到网络接口后,剩下的就是见证“魔法”的时刻了。

dns-trace工具运行输出示例

未来改进方向

当前的DNS-Trace程序作为一个概念验证已经足够,但仍有不少可以增强的方向,例如:

  • 集成攻击检测逻辑,用于识别潜在的DNS隧道活动。
  • 完善对IPv6 DNS流量的全面支持。
  • 增加对DNSSEC请求和响应的解析能力。

希望这份教程和代码能为你利用eBPF进行网络安全监控提供启发。社区是技术演进的重要动力,如果你有更好的想法或改进,欢迎在云栈社区的相关板块进行分享与探讨。




上一篇:浏览器端实现角谷猜想的二进制运算:JavaScript代码与原理分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-11 16:44 , Processed in 0.639862 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表