Add uprobe_ssl_write_entry/uretprobe_ssl_write_exit to capture server HTTP
responses via SSL_write with direction=1. Implement full HPACK decoder
(RFC 7541 static table, multi-byte integers, literal representations) for
HTTP/2 header extraction. Add AcceptCache mapping {tgid,fd}→SessionKey
from accept4 events as authoritative source for SSL correlation when BPF
ssl_conn_map has src_ip=0. Add ip_total_length to tcp_syn_event BPF struct.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
339 lines
12 KiB
C
339 lines
12 KiB
C
/* uprobe_ssl.c — Uprobes SSL_read/SSL_set_fd et tracepoints accept4
|
|
*
|
|
* Intercepte les appels OpenSSL pour capturer le trafic déchiffré,
|
|
* et corrige l'association socket ↔ SSL* via les tracepoints syscalls/accept4.
|
|
* Les tracepoints sont plus stables que les kprobes car ils ne dépendent pas
|
|
* du nom manglé __x64_sys_accept4 (variable selon la version du kernel).
|
|
*
|
|
* Utilise bpf_perf_event_output() (kernel 4.4+) pour compatibilité maximale.
|
|
* Les structs > 512o utilisent un PERCPU_ARRAY temporaire (__ssl_buf).
|
|
* ============================================================================ */
|
|
|
|
#include "vmlinux.h"
|
|
#include <bpf/bpf_helpers.h>
|
|
#include <bpf/bpf_tracing.h>
|
|
#include <bpf/bpf_core_read.h>
|
|
#include "bpf_types.h"
|
|
|
|
/* Taille maximale de données SSL à copier par événement */
|
|
#define MAX_SSL_DATA 4096
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Map temporaire : pid_tgid → upeer_sockaddr (sauvegardé à l'entrée d'accept4)
|
|
* ---------------------------------------------------------------------------*/
|
|
struct {
|
|
__uint(type, BPF_MAP_TYPE_HASH);
|
|
__uint(max_entries, 10240);
|
|
__type(key, __u64);
|
|
__type(value, __u64); /* pointeur userspace vers sockaddr_in */
|
|
} accept_args_map SEC(".maps");
|
|
|
|
/* ---------------------------------------------------------------------------
|
|
* Structs pour les tracepoints syscalls/sys_{enter,exit}_accept4
|
|
*
|
|
* Format vérifié avec : /sys/kernel/tracing/events/syscalls/sys_enter_accept4/format
|
|
* Valable pour les kernels 4.x → 6.x (stable, CO-RE non requis).
|
|
* ---------------------------------------------------------------------------*/
|
|
struct sys_enter_accept4_ctx {
|
|
__u16 common_type;
|
|
__u8 common_flags;
|
|
__u8 common_preempt_count;
|
|
__s32 common_pid;
|
|
__s32 __syscall_nr;
|
|
__u32 _pad;
|
|
__s64 listen_fd;
|
|
struct sockaddr *upeer_sockaddr; /* adresse userspace du client */
|
|
int *upeer_addrlen;
|
|
__s64 flags;
|
|
};
|
|
|
|
struct sys_exit_accept4_ctx {
|
|
__u16 common_type;
|
|
__u8 common_flags;
|
|
__u8 common_preempt_count;
|
|
__s32 common_pid;
|
|
__s32 __syscall_nr;
|
|
__u32 _pad;
|
|
__s64 ret; /* fd retourné par accept4, ou < 0 si erreur */
|
|
};
|
|
|
|
/* ===========================================================================
|
|
* uprobe_ssl_set_fd — Intercept SSL_set_fd(SSL *s, int fd)
|
|
*
|
|
* Associe un ssl_ptr à ses informations de connexion (fd, src_ip, src_port)
|
|
* en consultant fd_conn_map. Si fd_conn_map est vide (accept4 non disponible),
|
|
* enregistre quand même l'association ssl_ptr → fd avec IP=0 pour que le
|
|
* Go userspace puisse faire le lookup IP via /proc/<pid>/net/tcp.
|
|
* ===========================================================================*/
|
|
SEC("uprobe/SSL_set_fd")
|
|
int uprobe_ssl_set_fd(struct pt_regs *ctx)
|
|
{
|
|
__u64 ssl_ptr = ((__u64)PT_REGS_PARM1(ctx));
|
|
__u32 fd = ((__u32)PT_REGS_PARM2(ctx));
|
|
|
|
struct ssl_conn_info new_conn = {};
|
|
new_conn.fd = fd;
|
|
|
|
/* Tenter de récupérer les infos de connexion via fd_conn_map (accept4) */
|
|
struct ssl_conn_info *conn = bpf_map_lookup_elem(&fd_conn_map, &fd);
|
|
if (conn) {
|
|
new_conn.src_ip = conn->src_ip;
|
|
new_conn.src_port = conn->src_port;
|
|
}
|
|
/* Sans accept4, src_ip=0 / src_port=0 — le userspace Go fera le lookup /proc */
|
|
|
|
bpf_map_update_elem(&ssl_conn_map, &ssl_ptr, &new_conn, BPF_ANY);
|
|
return 0;
|
|
}
|
|
|
|
/* ===========================================================================
|
|
* uprobe_ssl_read_entry — Entrée de SSL_read(SSL *ssl, void *buf, int num)
|
|
*
|
|
* Sauvegarde les arguments pour l'uretprobe correspondant.
|
|
* ===========================================================================*/
|
|
SEC("uprobe/SSL_read")
|
|
int uprobe_ssl_read_entry(struct pt_regs *ctx)
|
|
{
|
|
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
|
|
struct ssl_read_args args = {};
|
|
args.ssl_ptr = (__u64)PT_REGS_PARM1(ctx);
|
|
args.buf_ptr = (__u64)PT_REGS_PARM2(ctx);
|
|
args.num = (__u32)PT_REGS_PARM3(ctx);
|
|
|
|
bpf_map_update_elem(&ssl_args_map, &pid_tgid, &args, BPF_ANY);
|
|
return 0;
|
|
}
|
|
|
|
/* ===========================================================================
|
|
* uretprobe_ssl_read_exit — Retour de SSL_read
|
|
*
|
|
* Lit le buffer déchiffré et l'émet via perf_event_output.
|
|
* Struct ssl_data_event = 4131 octets → PERCPU_ARRAY temporaire (__ssl_buf).
|
|
* ===========================================================================*/
|
|
SEC("uretprobe/SSL_read")
|
|
int uretprobe_ssl_read_exit(struct pt_regs *ctx)
|
|
{
|
|
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
|
|
/* Récupérer les arguments sauvegardés à l'entrée */
|
|
struct ssl_read_args *args = bpf_map_lookup_elem(&ssl_args_map, &pid_tgid);
|
|
if (!args)
|
|
return 0;
|
|
|
|
/* Vérifier que la lecture a réussi (valeur de retour > 0) */
|
|
long retval = PT_REGS_RC(ctx);
|
|
if (retval <= 0) {
|
|
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
|
|
return 0;
|
|
}
|
|
|
|
/* Utiliser le buffer PERCPU (struct trop grande pour la stack) */
|
|
__u32 zero = 0;
|
|
struct ssl_data_event *evt = bpf_map_lookup_elem(&__ssl_buf, &zero);
|
|
if (!evt) {
|
|
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
|
|
return 0;
|
|
}
|
|
/* Initialiser les champs fixes (data sera écrasé par probe_read_user) */
|
|
evt->pid_tgid = 0;
|
|
evt->fd = 0;
|
|
evt->src_ip = 0;
|
|
evt->src_port = 0;
|
|
evt->data_len = 0;
|
|
evt->timestamp_ns = 0;
|
|
evt->direction = 0;
|
|
|
|
evt->pid_tgid = pid_tgid;
|
|
evt->direction = 0; /* lecture = client vers serveur */
|
|
evt->timestamp_ns = bpf_ktime_get_ns();
|
|
|
|
/* Limiter la copie à MAX_SSL_DATA octets */
|
|
__u32 data_len = (retval > MAX_SSL_DATA) ? MAX_SSL_DATA : (__u32)retval;
|
|
evt->data_len = data_len;
|
|
|
|
/* Copier depuis l'espace utilisateur */
|
|
bpf_probe_read_user(evt->data, data_len & (MAX_SSL_DATA - 1), (void *)args->buf_ptr);
|
|
|
|
/* Retrouver les infos de connexion via ssl_ptr */
|
|
struct ssl_conn_info *conn = bpf_map_lookup_elem(&ssl_conn_map, &args->ssl_ptr);
|
|
if (conn) {
|
|
evt->fd = conn->fd;
|
|
evt->src_ip = conn->src_ip;
|
|
evt->src_port = conn->src_port;
|
|
} else {
|
|
evt->fd = 0;
|
|
evt->src_ip = 0;
|
|
evt->src_port = 0;
|
|
}
|
|
|
|
bpf_perf_event_output(ctx, &pb_ssl_data, BPF_F_CURRENT_CPU,
|
|
evt, sizeof(*evt));
|
|
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ===========================================================================
|
|
* uprobe_ssl_write_entry — Entrée de SSL_write(SSL *ssl, const void *buf, int num)
|
|
*
|
|
* Sauvegarde les arguments pour l'uretprobe correspondant.
|
|
* Réutilise ssl_args_map (même format que SSL_read — un seul thread à la fois).
|
|
* ===========================================================================*/
|
|
SEC("uprobe/SSL_write")
|
|
int uprobe_ssl_write_entry(struct pt_regs *ctx)
|
|
{
|
|
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
|
|
struct ssl_read_args args = {};
|
|
args.ssl_ptr = (__u64)PT_REGS_PARM1(ctx);
|
|
args.buf_ptr = (__u64)PT_REGS_PARM2(ctx);
|
|
args.num = (__u32)PT_REGS_PARM3(ctx);
|
|
|
|
bpf_map_update_elem(&ssl_args_map, &pid_tgid, &args, BPF_ANY);
|
|
return 0;
|
|
}
|
|
|
|
/* ===========================================================================
|
|
* uretprobe_ssl_write_exit — Retour de SSL_write
|
|
*
|
|
* Lit le buffer de réponse et l'émet via perf_event_output avec direction=1.
|
|
* Les données sont les réponses HTTP du serveur (status, headers, body).
|
|
* ===========================================================================*/
|
|
SEC("uretprobe/SSL_write")
|
|
int uretprobe_ssl_write_exit(struct pt_regs *ctx)
|
|
{
|
|
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
|
|
struct ssl_read_args *args = bpf_map_lookup_elem(&ssl_args_map, &pid_tgid);
|
|
if (!args)
|
|
return 0;
|
|
|
|
long retval = PT_REGS_RC(ctx);
|
|
if (retval <= 0) {
|
|
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
|
|
return 0;
|
|
}
|
|
|
|
__u32 zero = 0;
|
|
struct ssl_data_event *evt = bpf_map_lookup_elem(&__ssl_buf, &zero);
|
|
if (!evt) {
|
|
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
|
|
return 0;
|
|
}
|
|
|
|
evt->pid_tgid = pid_tgid;
|
|
evt->direction = 1; /* écriture = serveur vers client */
|
|
evt->timestamp_ns = bpf_ktime_get_ns();
|
|
|
|
__u32 data_len = (retval > MAX_SSL_DATA) ? MAX_SSL_DATA : (__u32)retval;
|
|
evt->data_len = data_len;
|
|
|
|
bpf_probe_read_user(evt->data, data_len & (MAX_SSL_DATA - 1), (void *)args->buf_ptr);
|
|
|
|
struct ssl_conn_info *conn = bpf_map_lookup_elem(&ssl_conn_map, &args->ssl_ptr);
|
|
if (conn) {
|
|
evt->fd = conn->fd;
|
|
evt->src_ip = conn->src_ip;
|
|
evt->src_port = conn->src_port;
|
|
} else {
|
|
evt->fd = 0;
|
|
evt->src_ip = 0;
|
|
evt->src_port = 0;
|
|
}
|
|
|
|
bpf_perf_event_output(ctx, &pb_ssl_data, BPF_F_CURRENT_CPU,
|
|
evt, sizeof(*evt));
|
|
bpf_map_delete_elem(&ssl_args_map, &pid_tgid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ===========================================================================
|
|
* kprobe_accept4_entry — Entrée de accept4 via tracepoint syscalls
|
|
*
|
|
* Utilise SEC("tracepoint/syscalls/sys_enter_accept4") au lieu d'un kprobe
|
|
* pour éviter la dépendance au nom manglé __x64_sys_accept4 (kernel 5.1+).
|
|
* Le contexte tracepoint expose directement upeer_sockaddr sans indirection.
|
|
* ===========================================================================*/
|
|
SEC("tracepoint/syscalls/sys_enter_accept4")
|
|
int kprobe_accept4_entry(struct sys_enter_accept4_ctx *ctx)
|
|
{
|
|
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
__u64 sockaddr_ptr = (__u64)ctx->upeer_sockaddr;
|
|
|
|
bpf_map_update_elem(&accept_args_map, &pid_tgid, &sockaddr_ptr, BPF_ANY);
|
|
return 0;
|
|
}
|
|
|
|
/* ===========================================================================
|
|
* kretprobe_accept4_exit — Retour de accept4 via tracepoint syscalls
|
|
*
|
|
* Lit la sockaddr_in pour extraire src_ip:src_port du client,
|
|
* peuple accept_map et fd_conn_map, et émet via perf_event_output.
|
|
* Struct accept_event = 26 octets → tient sur la stack (< 512o).
|
|
* ===========================================================================*/
|
|
SEC("tracepoint/syscalls/sys_exit_accept4")
|
|
int kretprobe_accept4_exit(struct sys_exit_accept4_ctx *ctx)
|
|
{
|
|
__u64 pid_tgid = bpf_get_current_pid_tgid();
|
|
|
|
/* Vérifier que accept4 a réussi (fd ≥ 0) */
|
|
long new_fd = ctx->ret;
|
|
if (new_fd < 0) {
|
|
bpf_map_delete_elem(&accept_args_map, &pid_tgid);
|
|
return 0;
|
|
}
|
|
|
|
/* Récupérer le pointeur vers sockaddr_in */
|
|
__u64 *sockaddr_ptr_p = bpf_map_lookup_elem(&accept_args_map, &pid_tgid);
|
|
if (!sockaddr_ptr_p) {
|
|
return 0;
|
|
}
|
|
__u64 sockaddr_ptr = *sockaddr_ptr_p;
|
|
bpf_map_delete_elem(&accept_args_map, &pid_tgid);
|
|
|
|
if (!sockaddr_ptr)
|
|
return 0;
|
|
|
|
/* Lire la structure sockaddr_in depuis l'espace utilisateur */
|
|
/* struct sockaddr_in: sin_family(2) + sin_port(2) + sin_addr(4) */
|
|
__u8 sa_buf[8] = {};
|
|
bpf_probe_read_user(sa_buf, sizeof(sa_buf), (void *)sockaddr_ptr);
|
|
|
|
/* Extraire port (octets 2-3) et adresse IP (octets 4-7) */
|
|
__u16 sin_port = (__u16)(sa_buf[2] << 8) | sa_buf[3]; /* already host byte order (manual assembly) */
|
|
__u32 sin_addr = *(__u32 *)(sa_buf + 4); /* network byte order (raw memory read) */
|
|
|
|
__u32 src_ip = __builtin_bswap32(sin_addr); /* network → host byte order */
|
|
__u16 src_port = sin_port; /* already host byte order */
|
|
__u32 fd = (__u32)new_fd;
|
|
|
|
/* Peupler accept_map[{pid_tgid, fd}] */
|
|
struct accept_key akey = { .pid_tgid = pid_tgid, .fd = fd };
|
|
struct accept_event aevt = {
|
|
.pid_tgid = pid_tgid,
|
|
.fd = fd,
|
|
.src_ip = src_ip,
|
|
.src_port = src_port,
|
|
.timestamp_ns = bpf_ktime_get_ns(),
|
|
};
|
|
bpf_map_update_elem(&accept_map, &akey, &aevt, BPF_ANY);
|
|
|
|
/* Peupler fd_conn_map[fd] pour accès rapide par SSL_set_fd */
|
|
struct ssl_conn_info conn_info = {
|
|
.fd = fd,
|
|
.src_ip = src_ip,
|
|
.src_port = src_port,
|
|
};
|
|
bpf_map_update_elem(&fd_conn_map, &fd, &conn_info, BPF_ANY);
|
|
|
|
/* Émettre via perf_event_output (struct 26o → sur la stack) */
|
|
bpf_perf_event_output(ctx, &pb_accept, BPF_F_CURRENT_CPU,
|
|
&aevt, sizeof(aevt));
|
|
|
|
return 0;
|
|
}
|
|
|
|
char LICENSE[] SEC("license") = "GPL";
|