Kernel probes
Introduction
We’ve used tracepoints to hook into syscalls. Now let’s learn kprobes - attaching to internal kernel functions.
Kprobes vs tracepoints
Tracepoints are stable, documented hooks at specific kernel events (syscall entry/exit, scheduling, etc.). They provide structured context:
SEC("tracepoint/syscalls/sys_enter_connect")
int trace(struct trace_event_raw_sys_enter *ctx) {
u32 fd = ctx->args[0]; // Predefined fields
}
Kprobes attach to any kernel function by name.
This mechanism is more flexible but kprobes are not part of the kernel’s stable API, so they can change between kernel versions. On top of that, they do not provide a defined ‘context’, we need to get the argument types and order from the kernel sources.
Why kprobes?
Tracepoints expose specific events. Kprobes let us hook deeper into kernel internals. For example, connect() syscall returns immediately for non-blocking sockets, but tcp_finish_connect() is called when the TCP handshake actually completes.
Accessing function arguments
Kprobes receive struct pt_regs *ctx - raw CPU registers. Use macros to extract the arguments:
PT_REGS_PARM1(ctx) // First parameter
PT_REGS_PARM2(ctx) // Second parameter
PT_REGS_PARM3(ctx) // Third parameter
// up to 8
We can find the signature for tcp_finish_connect in net/ipv4/tcp_input.c, which is
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb);
And so, to get access to the struct sock* we need to cast the first argument:
struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
The challenge
A program connects to a remote server. The connect() syscall returns -EINPROGRESS (non-blocking), but the connection completes successfully.
Socket properties
You can read the socket’s properties with bpf_probe_read_kernel. Here
are the relevant fields:
struct sock {
struct sock_common __sk_common; // Common socket info
// ...
};
struct sock_common {
u16 skc_dport; // Destination port
u32 skc_daddr; // Destination IPv4 address
// ...
};
Remember that ports are in big endian, so you’ll need to use bpf_ntohs to convert it.
Your task
Submit the destination port.
Read bytes from kernel space into kernel buffer
- Args:
void* dstkernel buffer to read intou32 sizebytes to readconst void* srckernel space pointer
Read string from kernel space into kernel buffer
- Args:
void* dstkernel buffer to read intou32 sizemaximum bytes to readconst void* srckernel space pointer to string