https://eunomia.dev/zh/tutorials/30-sslsniff/

编译运行

前置知识

SSL/TLS

OpenSSL API

SSL_read

  • 从已建立的 SSL 连接中读取数据
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

  • 往一个已建立的 SSL 连接中写入数据
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 # BPF 内核代码
├── 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) {
/* 1. 用户空间启动程序 */
LIBBPF_OPTS(bpf_object_open_opts, open_opts);
struct sslsniff_bpf *obj = NULL;

obj = sslsniff_bpf__open_opts(&open_opts);

/* 2. 加载 BPF 骨架 (skeleton) */
obj->rodata->targ_uid = env.uid;
obj->rodata->targ_pid = env.pid == INVALID_PID ? 0 : env.pid;

err = sslsniff_bpf__load(obj);

/* 3. 附加 uprobes 到 SSL/TLS 库函数 */
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;
}

/* store arg info for later lookup */
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;
}

/* store arg info for later lookup */
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) // no data
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; // 进程 ID
__u32 tid; // 线程 ID
__u32 uid; // 用户 ID
__u32 len; // 读写数据长度
int buf_filled; // 缓冲区是否填满
int rw; // 读写类型:0=读,1=写,2=握手
char comm[TASK_COMM_LEN]; // 进程名
__u8 buf[MAX_BUF_SIZE]; // 数据缓冲区(最大 8192 字节)
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
// 1. Perf Event Array - 用于将数据发送到用户空间
struct {
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} perf_SSL_events SEC(".maps");

// 2. Hash Map - 存储缓冲区指针
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 10240);
__type(key, __u32);
__type(value, size_t*);
} bufs SEC(".maps");

// 3. Hash Map - 存储开始时间戳
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, MAX_ENTRIES);
__type(key, __u32);
__type(value, __u64);
} start_ns SEC(".maps");

// 4. Per-CPU Array - 存储事件数据
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");