免责声明
本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者及本公众号不为此承担任何责任。
tcpv4追踪
BCC作为一个流行的ebpf开发方案,提供了很多案例供开发者学习,其中tracing/tcpv4connect.py是BCC官方提供的追踪机器TCPv4连接的案例。追踪tcpv4连接对于入侵检测很有帮助,值得学习。
内核态源码
先贴出内核态源码:
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;
}
-
引入必要的头文件,提供对ptrace(用于跟踪和断点)、sock(socket相关结构定义)和bcc/proto.h(BCC提供的协议相关工具)的支持。 -
BPF_HASH(currsock, u32, struct sock *)定义了一个名为currsock的BPF哈希表,用于存储进程ID(u32类型)和对应socket对象指针(struct sock *类型)。 -
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk):定义了一个Kprobe,即内核探测点,它在tcp_v4_connect函数被调用时被触发。这个函数将当前进程ID (pid) 和socket对象地址 (&sk) 存入之前定义的currsock哈希表中。 -
kprobe的返回值总是0,这表示这个探测点总是成功的。 -
int kretprobe__tcp_v4_connect(struct pt_regs *ctx):定义了一个Kretprobe,即内核返回探测点,在tcp_v4_connect函数返回之后触发。这个函数首先获取返回值(ret)和当前进程ID (pid)。 -
skpp = currsock.lookup(&pid);:通过进程ID查找之前存储的socket对象指针。 -
如果找不到对应的入口(if (skpp == 0)),说明在调用函数之前该入口已经丢失,函数返回0。 -
如果ret不为0,表示连接尝试失败(无法发送同步(SYN)包),这时会从哈希表中删除对应的进程ID入口。 -
如果连接成功,代码会从socket对象获取源地址(saddr)、目的地址(daddr)和目的端口号(dport),然后使用bpf_trace_printk将这些信息输出到内核跟踪日志中。ntohs(dport)用于将网络字节序转换成主机字节序。 -
结尾处,会再次从哈希表中删除对应的进程ID入口。
直接访问内核函数参数
int kprobe__tcp_v4_connect(struct pt_regs *ctx, struct sock *sk)
bpf_get_current_pid_tgid
PT_REGS_RC
用户态源码
# initialize BPF
b = BPF(text=bpf_text)
# header
print("%-6s %-12s %-16s %-16s %-4s" % ("PID", "COMM", "SADDR", "DADDR",
"DPORT"))
def inet_ntoa(addr):
dq = b''
for i in range(0, 4):
dq = dq + str(addr & 0xff).encode()
if (i != 3):
dq = dq + b'.'
addr = addr >> 8
return dq
# filter and format output
while 1:
# Read messages from kernel pipe
try:
(task, pid, cpu, flags, ts, msg) = b.trace_fields()
(_tag, saddr_hs, daddr_hs, dport_s) = msg.split(b" ")
except ValueError:
# Ignore messages from other tracers
continue
except KeyboardInterrupt:
exit()
# Ignore messages from other tracers
if _tag.decode() != "trace_tcp4connect":
continue
printb(b"%-6d %-12.12s %-16s %-16s %-4s" % (pid, task,
inet_ntoa(int(saddr_hs, 16)),
inet_ntoa(int(daddr_hs, 16)),
dport_s))
-
b.trace_fields() 从追踪管道读取一行消息。 -
msg.split(b" ") 分割消息成四个部分:tag标记,源地址(网络字节顺序),目的地址(网络字节顺序),目的端口。 -
如果消息不是由我们的BPF程序生成的(即不带有"trace_tcp4connect"标记),将忽略该消息并继续读取下一行。 -
printb(...) 根据消息内容格式化并打印出每个TCP连接尝试的信息: -
pid 进程ID -
task 命令名称 -
inet_ntoa(...) 转换并显示源和目标地址 -
dport_s 目的端口
运行示例
原文始发于微信公众号(赛博安全狗):【ebpf】BCC实现tcpv4连接追踪
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论