https://eunomia.dev/zh/tutorials/30-sslsniff/
编译运行
前置知识
SSL/TLS
OpenSSL API
SSL_read
1 2 3 4
| #include <openssl/ssl.h>
int SSL_read_ex(SSL *ssl, void *buf, size_t num, size_t *readbytes); int SSL_read(SSL *ssl, void *buf, int num);
|
SSL_read 和 SSL_read_ex 试图从指定的 ssl 中读取最多 num 字节的数据到缓冲区 buf 中。成功时,SSL_read_ex 会在 *readbytes 中存储实际读取到的字节数。
SSL_write
1 2 3 4
| #include <openssl/ssl.h>
int SSL_write_ex(SSL *s, const void *buf, size_t num, size_t *written); int SSL_write(SSL *ssl, const void *buf, int num);
|
SSL_write 和 SSL_write_ex 会从缓冲区 buf 中将最多 num 字节的数据写入到指定的 ssl 连接中。成功时,SSL_write_ex 会在 *written 中存储实际写入的字节数。
ebpf
uprobe
流程梳理
项目结构
1 2 3 4 5
| src/30-sslsniff/ ├── sslsniff.bpf.c ├── sslsniff.h ├── sslsniff.c └── Makefile
|
核心数据流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ OpenSSL │ │ GnuTLS │ │ NSS │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │ │ ▼ ▼ ▼ ┌──────────────────────────────────────────────────────┐ │ uprobes 附加到库函数 │ │ SSL_read, SSL_write, SSL_do_handshake 等 │ └──────────────────────┬───────────────────────────────┘ │ ┌─────────────┼─────────────┐ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ uprobe │ │uretprobe│ │uretprobe│ │ 入口 │ │ 出口 │ │ 出口 │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │ └────────────┴────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ BPF Maps (bufs, start_ns) │ └──────────────────────┬───────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ SSL_exit() 函数处理 │ │ - 计算延迟 │ │ - 读取用户空间数据 │ │ - 填充 probe_SSL_data_t │ └──────────────────────┬───────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ perf_event_output 发送到用户空间 │ └──────────────────────┬───────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────┐ │ 用户空间 handle_event() │ │ - 解析事件数据 │ │ - 格式化输出 │ └──────────────────────────────────────────────────────┘
|
1 2 3 4 5 6 7 8 9 10 11
| 用户空间启动程序 ↓ 加载 BPF 骨架 (skeleton) ↓ 附加 uprobes 到 SSL/TLS 库函数 ↓ 内核空间:捕获函数调用和返回值 ↓ 通过 perf event array 发送数据到用户空间 ↓ 用户空间:处理和显示捕获的数据
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int main(int argc, char **argv) { LIBBPF_OPTS(bpf_object_open_opts, open_opts); struct sslsniff_bpf *obj = NULL;
obj = sslsniff_bpf__open_opts(&open_opts);
obj->rodata->targ_uid = env.uid; obj->rodata->targ_pid = env.pid == INVALID_PID ? 0 : env.pid;
err = sslsniff_bpf__load(obj);
if (env.openssl) { char *openssl_path = find_library_path("libssl.so"); printf("OpenSSL path: %s\n", openssl_path); attach_openssl(obj, openssl_path); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| int attach_openssl(struct sslsniff_bpf *skel, const char *lib) { ATTACH_UPROBE_CHECKED(skel, lib, SSL_write, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_write, probe_SSL_write_exit); ATTACH_UPROBE_CHECKED(skel, lib, SSL_read, probe_SSL_rw_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_read, probe_SSL_read_exit);
ATTACH_UPROBE_CHECKED(skel, lib, SSL_write_ex, probe_SSL_write_ex_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_write_ex, probe_SSL_write_ex_exit); ATTACH_UPROBE_CHECKED(skel, lib, SSL_read_ex, probe_SSL_read_ex_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_read_ex, probe_SSL_read_ex_exit);
if (env.latency && env.handshake) { ATTACH_UPROBE_CHECKED(skel, lib, SSL_do_handshake, probe_SSL_do_handshake_enter); ATTACH_URETPROBE_CHECKED(skel, lib, SSL_do_handshake, probe_SSL_do_handshake_exit); }
return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| SEC("uprobe/SSL_read") int BPF_UPROBE(probe_SSL_rw_enter, void *ssl, void *buf, int num) { u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = pid_tgid; u32 uid = bpf_get_current_uid_gid(); u64 ts = bpf_ktime_get_ns();
if (!trace_allowed(uid, pid)) { return 0; }
bpf_map_update_elem(&bufs, &tid, &buf, BPF_ANY); bpf_map_update_elem(&start_ns, &tid, &ts, BPF_ANY); return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| SEC("uretprobe/SSL_read") int BPF_URETPROBE(probe_SSL_read_exit) { return (SSL_exit(ctx, 0)); }
static int SSL_exit(struct pt_regs *ctx, int rw) { int ret = 0; u32 zero = 0; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; u32 tid = (u32)pid_tgid; u32 uid = bpf_get_current_uid_gid(); u64 ts = bpf_ktime_get_ns();
if (!trace_allowed(uid, pid)) { return 0; }
u64 *bufp = bpf_map_lookup_elem(&bufs, &tid); if (bufp == 0) return 0;
u64 *tsp = bpf_map_lookup_elem(&start_ns, &tid); if (!tsp) return 0; u64 delta_ns = ts - *tsp;
int len = PT_REGS_RC(ctx); if (len <= 0) return 0;
struct probe_SSL_data_t *data = bpf_map_lookup_elem(&ssl_data, &zero); if (!data) return 0;
data->timestamp_ns = ts; data->delta_ns = delta_ns; data->pid = pid; data->tid = tid; data->uid = uid; data->len = (u32)len; data->buf_filled = 0; data->rw = rw; data->is_handshake = false; u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len);
bpf_get_current_comm(&data->comm, sizeof(data->comm));
if (bufp != 0) ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp);
bpf_map_delete_elem(&bufs, &tid); bpf_map_delete_elem(&start_ns, &tid);
if (!ret) data->buf_filled = 1; else buf_copy_size = 0;
bpf_perf_event_output(ctx, &perf_SSL_events, BPF_F_CURRENT_CPU, data, EVENT_SIZE(buf_copy_size)); return 0; }
|
关键数据结构 (sslsniff.h)
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct probe_SSL_data_t { __u64 timestamp_ns; __u64 delta_ns; __u32 pid; __u32 tid; __u32 uid; __u32 len; int buf_filled; int rw; char comm[TASK_COMM_LEN]; __u8 buf[MAX_BUF_SIZE]; int is_handshake; };
|
BPF Maps 定义 (sslsniff.bpf.c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| struct { __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); __uint(key_size, sizeof(__u32)); __uint(value_size, sizeof(__u32)); } perf_SSL_events SEC(".maps");
struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, 10240); __type(key, __u32); __type(value, size_t*); } bufs SEC(".maps");
struct { __uint(type, BPF_MAP_TYPE_HASH); __uint(max_entries, MAX_ENTRIES); __type(key, __u32); __type(value, __u64); } start_ns SEC(".maps");
struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, u32); __type(value, struct probe_SSL_data_t); } ssl_data SEC(".maps");
|