mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-04-13 17:50:18 +08:00
Deploying to gh-pages from @ eunomia-bpf/bpf-developer-tutorial@af2d46a846 🚀
This commit is contained in:
9
30-sslsniff/.gitignore
vendored
Normal file
9
30-sslsniff/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
bootstrap
|
||||
openssl
|
||||
141
30-sslsniff/Makefile
Normal file
141
30-sslsniff/Makefile
Normal file
@@ -0,0 +1,141 @@
|
||||
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
OUTPUT := .output
|
||||
CLANG ?= clang
|
||||
LIBBPF_SRC := $(abspath ../third_party/libbpf/src)
|
||||
BPFTOOL_SRC := $(abspath ../third_party/bpftool/src)
|
||||
LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a)
|
||||
BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool)
|
||||
BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool
|
||||
LIBBLAZESYM_SRC := $(abspath ../third_party/blazesym/)
|
||||
LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a)
|
||||
LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h)
|
||||
ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \
|
||||
| sed 's/arm.*/arm/' \
|
||||
| sed 's/aarch64/arm64/' \
|
||||
| sed 's/ppc64le/powerpc/' \
|
||||
| sed 's/mips.*/mips/' \
|
||||
| sed 's/riscv64/riscv/' \
|
||||
| sed 's/loongarch64/loongarch/')
|
||||
VMLINUX := ../third_party/vmlinux/$(ARCH)/vmlinux.h
|
||||
# Use our own libbpf API headers and Linux UAPI headers distributed with
|
||||
# libbpf to avoid dependency on system-wide headers, which could be missing or
|
||||
# outdated
|
||||
INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX))
|
||||
CFLAGS := -g -Wall
|
||||
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)
|
||||
|
||||
APPS = sslsniff # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall
|
||||
|
||||
CARGO ?= $(shell which cargo)
|
||||
ifeq ($(strip $(CARGO)),)
|
||||
BZS_APPS :=
|
||||
else
|
||||
BZS_APPS := # profile
|
||||
APPS += $(BZS_APPS)
|
||||
# Required by libblazesym
|
||||
ALL_LDFLAGS += -lrt -ldl -lpthread -lm
|
||||
endif
|
||||
|
||||
# Get Clang's default includes on this system. We'll explicitly add these dirs
|
||||
# to the includes list when compiling with `-target bpf` because otherwise some
|
||||
# architecture-specific dirs will be "missing" on some architectures/distros -
|
||||
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
|
||||
# sys/cdefs.h etc. might be missing.
|
||||
#
|
||||
# Use '-idirafter': Don't interfere with include mechanics except where the
|
||||
# build would have failed anyways.
|
||||
CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - </dev/null 2>&1 \
|
||||
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
|
||||
|
||||
ifeq ($(V),1)
|
||||
Q =
|
||||
msg =
|
||||
else
|
||||
Q = @
|
||||
msg = @printf ' %-8s %s%s\n' \
|
||||
"$(1)" \
|
||||
"$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \
|
||||
"$(if $(3), $(3))";
|
||||
MAKEFLAGS += --no-print-directory
|
||||
endif
|
||||
|
||||
define allow-override
|
||||
$(if $(or $(findstring environment,$(origin $(1))),\
|
||||
$(findstring command line,$(origin $(1)))),,\
|
||||
$(eval $(1) = $(2)))
|
||||
endef
|
||||
|
||||
$(call allow-override,CC,$(CROSS_COMPILE)cc)
|
||||
$(call allow-override,LD,$(CROSS_COMPILE)ld)
|
||||
|
||||
.PHONY: all
|
||||
all: $(APPS)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
$(call msg,CLEAN)
|
||||
$(Q)rm -rf $(OUTPUT) $(APPS)
|
||||
|
||||
$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
|
||||
$(call msg,MKDIR,$@)
|
||||
$(Q)mkdir -p $@
|
||||
|
||||
# Build libbpf
|
||||
$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
|
||||
$(call msg,LIB,$@)
|
||||
$(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \
|
||||
OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \
|
||||
INCLUDEDIR= LIBDIR= UAPIDIR= \
|
||||
install
|
||||
|
||||
# Build bpftool
|
||||
$(BPFTOOL): | $(BPFTOOL_OUTPUT)
|
||||
$(call msg,BPFTOOL,$@)
|
||||
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap
|
||||
|
||||
|
||||
$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
|
||||
$(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release
|
||||
|
||||
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
|
||||
$(call msg,LIB, $@)
|
||||
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
|
||||
|
||||
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
|
||||
$(call msg,LIB,$@)
|
||||
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
|
||||
|
||||
# Build BPF code
|
||||
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
|
||||
$(call msg,BPF,$@)
|
||||
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
|
||||
$(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \
|
||||
-c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
$(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
|
||||
|
||||
# Generate BPF skeletons
|
||||
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL)
|
||||
$(call msg,GEN-SKEL,$@)
|
||||
$(Q)$(BPFTOOL) gen skeleton $< > $@
|
||||
|
||||
# Build user-space code
|
||||
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
|
||||
|
||||
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
|
||||
$(call msg,CC,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
|
||||
|
||||
$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
|
||||
|
||||
$(BZS_APPS): $(LIBBLAZESYM_OBJ)
|
||||
|
||||
# Build application binary
|
||||
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
|
||||
$(call msg,BINARY,$@)
|
||||
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@
|
||||
|
||||
# delete failed targets
|
||||
.DELETE_ON_ERROR:
|
||||
|
||||
# keep intermediate (.skel.h, .bpf.o, etc) targets
|
||||
.SECONDARY:
|
||||
648
30-sslsniff/index.html
Normal file
648
30-sslsniff/index.html
Normal file
File diff suppressed because one or more lines are too long
220
30-sslsniff/sslsniff.bpf.c
Normal file
220
30-sslsniff/sslsniff.bpf.c
Normal file
@@ -0,0 +1,220 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
// Copyright (c) 2023 Yusheng Zheng
|
||||
//
|
||||
// Based on sslsniff from BCC by Adrian Lopez & Mark Drayton.
|
||||
// 15-Aug-2023 Yusheng Zheng Created this.
|
||||
#include <vmlinux.h>
|
||||
#include <bpf/bpf_core_read.h>
|
||||
#include <bpf/bpf_endian.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
#include <bpf/bpf_tracing.h>
|
||||
#include "sslsniff.h"
|
||||
|
||||
struct {
|
||||
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
|
||||
__uint(key_size, sizeof(__u32));
|
||||
__uint(value_size, sizeof(__u32));
|
||||
} perf_SSL_events SEC(".maps");
|
||||
|
||||
#define BASE_EVENT_SIZE ((size_t)(&((struct probe_SSL_data_t *)0)->buf))
|
||||
#define EVENT_SIZE(X) (BASE_EVENT_SIZE + ((size_t)(X)))
|
||||
#define MAX_ENTRIES 10240
|
||||
|
||||
#define min(x, y) \
|
||||
({ \
|
||||
typeof(x) _min1 = (x); \
|
||||
typeof(y) _min2 = (y); \
|
||||
(void)(&_min1 == &_min2); \
|
||||
_min1 < _min2 ? _min1 : _min2; \
|
||||
})
|
||||
|
||||
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");
|
||||
|
||||
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_HASH);
|
||||
__uint(max_entries, MAX_ENTRIES);
|
||||
__type(key, __u32);
|
||||
__type(value, __u64);
|
||||
} bufs SEC(".maps");
|
||||
|
||||
const volatile pid_t targ_pid = 0;
|
||||
const volatile uid_t targ_uid = -1;
|
||||
|
||||
static __always_inline bool trace_allowed(u32 uid, u32 pid)
|
||||
{
|
||||
/* filters */
|
||||
if (targ_pid && targ_pid != pid)
|
||||
return false;
|
||||
if (targ_uid != -1) {
|
||||
if (targ_uid != uid) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SEC("uprobe/do_handshake")
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
SEC("uretprobe/SSL_read")
|
||||
int BPF_URETPROBE(probe_SSL_read_exit) {
|
||||
return (SSL_exit(ctx, 0));
|
||||
}
|
||||
|
||||
SEC("uretprobe/SSL_write")
|
||||
int BPF_URETPROBE(probe_SSL_write_exit) {
|
||||
return (SSL_exit(ctx, 1));
|
||||
}
|
||||
|
||||
SEC("uprobe/do_handshake")
|
||||
int BPF_UPROBE(probe_SSL_do_handshake_enter, void *ssl) {
|
||||
u64 pid_tgid = bpf_get_current_pid_tgid();
|
||||
u32 pid = pid_tgid >> 32;
|
||||
u32 tid = (u32)pid_tgid;
|
||||
u64 ts = bpf_ktime_get_ns();
|
||||
u32 uid = bpf_get_current_uid_gid();
|
||||
|
||||
if (!trace_allowed(uid, pid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* store arg info for later lookup */
|
||||
bpf_map_update_elem(&start_ns, &tid, &ts, BPF_ANY);
|
||||
return 0;
|
||||
}
|
||||
|
||||
SEC("uretprobe/do_handshake")
|
||||
int BPF_URETPROBE(probe_SSL_do_handshake_exit) {
|
||||
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();
|
||||
int ret = 0;
|
||||
|
||||
/* use kernel terminology here for tgid/pid: */
|
||||
u32 tgid = pid_tgid >> 32;
|
||||
|
||||
/* store arg info for later lookup */
|
||||
if (!trace_allowed(tgid, pid)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u64 *tsp = bpf_map_lookup_elem(&start_ns, &tid);
|
||||
if (tsp == 0)
|
||||
return 0;
|
||||
|
||||
ret = PT_REGS_RC(ctx);
|
||||
if (ret <= 0) // handshake failed
|
||||
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 = ts - *tsp;
|
||||
data->pid = pid;
|
||||
data->tid = tid;
|
||||
data->uid = uid;
|
||||
data->len = ret;
|
||||
data->buf_filled = 0;
|
||||
data->rw = 2;
|
||||
data->is_handshake = true;
|
||||
bpf_get_current_comm(&data->comm, sizeof(data->comm));
|
||||
bpf_map_delete_elem(&start_ns, &tid);
|
||||
|
||||
bpf_perf_event_output(ctx, &perf_SSL_events, BPF_F_CURRENT_CPU, data,
|
||||
EVENT_SIZE(0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
char LICENSE[] SEC("license") = "GPL";
|
||||
452
30-sslsniff/sslsniff.c
Normal file
452
30-sslsniff/sslsniff.c
Normal file
@@ -0,0 +1,452 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
// Copyright (c) 2023 Yusheng Zheng
|
||||
//
|
||||
// Based on sslsniff from BCC by Adrian Lopez & Mark Drayton.
|
||||
// 15-Aug-2023 Yusheng Zheng Created this.
|
||||
#include <argp.h>
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <linux/types.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "sslsniff.skel.h"
|
||||
#include "sslsniff.h"
|
||||
|
||||
#define INVALID_UID -1
|
||||
#define INVALID_PID -1
|
||||
#define DEFAULT_BUFFER_SIZE 8192
|
||||
|
||||
#define __ATTACH_UPROBE(skel, binary_path, sym_name, prog_name, is_retprobe) \
|
||||
do { \
|
||||
LIBBPF_OPTS(bpf_uprobe_opts, uprobe_opts, .func_name = #sym_name, \
|
||||
.retprobe = is_retprobe); \
|
||||
skel->links.prog_name = bpf_program__attach_uprobe_opts( \
|
||||
skel->progs.prog_name, env.pid, binary_path, 0, &uprobe_opts); \
|
||||
} while (false)
|
||||
|
||||
#define __CHECK_PROGRAM(skel, prog_name) \
|
||||
do { \
|
||||
if (!skel->links.prog_name) { \
|
||||
perror("no program attached for " #prog_name); \
|
||||
return -errno; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
#define __ATTACH_UPROBE_CHECKED(skel, binary_path, sym_name, prog_name, \
|
||||
is_retprobe) \
|
||||
do { \
|
||||
__ATTACH_UPROBE(skel, binary_path, sym_name, prog_name, is_retprobe); \
|
||||
__CHECK_PROGRAM(skel, prog_name); \
|
||||
} while (false)
|
||||
|
||||
#define ATTACH_UPROBE_CHECKED(skel, binary_path, sym_name, prog_name) \
|
||||
__ATTACH_UPROBE_CHECKED(skel, binary_path, sym_name, prog_name, false)
|
||||
#define ATTACH_URETPROBE_CHECKED(skel, binary_path, sym_name, prog_name) \
|
||||
__ATTACH_UPROBE_CHECKED(skel, binary_path, sym_name, prog_name, true)
|
||||
|
||||
volatile sig_atomic_t exiting = 0;
|
||||
|
||||
const char *argp_program_version = "sslsniff 0.1";
|
||||
const char *argp_program_bug_address = "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
|
||||
const char argp_program_doc[] =
|
||||
"Sniff SSL data.\n"
|
||||
"\n"
|
||||
"USAGE: sslsniff [OPTIONS]\n"
|
||||
"\n"
|
||||
"EXAMPLES:\n"
|
||||
" ./sslsniff # sniff OpenSSL and GnuTLS functions\n"
|
||||
" ./sslsniff -p 181 # sniff PID 181 only\n"
|
||||
" ./sslsniff -u 1000 # sniff only UID 1000\n"
|
||||
" ./sslsniff -c curl # sniff curl command only\n"
|
||||
" ./sslsniff --no-openssl # don't show OpenSSL calls\n"
|
||||
" ./sslsniff --no-gnutls # don't show GnuTLS calls\n"
|
||||
" ./sslsniff --no-nss # don't show NSS calls\n"
|
||||
" ./sslsniff --hexdump # show data as hex instead of trying to "
|
||||
"decode it as UTF-8\n"
|
||||
" ./sslsniff -x # show process UID and TID\n"
|
||||
" ./sslsniff -l # show function latency\n"
|
||||
" ./sslsniff -l --handshake # show SSL handshake latency\n"
|
||||
" ./sslsniff --extra-lib openssl:/path/libssl.so.1.1 # sniff extra "
|
||||
"library\n";
|
||||
|
||||
struct env {
|
||||
pid_t pid;
|
||||
int uid;
|
||||
bool extra;
|
||||
char *comm;
|
||||
bool openssl;
|
||||
bool gnutls;
|
||||
bool nss;
|
||||
bool hexdump;
|
||||
bool latency;
|
||||
bool handshake;
|
||||
char *extra_lib;
|
||||
} env = {
|
||||
.uid = INVALID_UID,
|
||||
.pid = INVALID_PID,
|
||||
.openssl = true,
|
||||
.gnutls = true,
|
||||
.nss = true,
|
||||
.comm = NULL,
|
||||
};
|
||||
|
||||
#define HEXDUMP_KEY 1000
|
||||
#define HANDSHAKE_KEY 1002
|
||||
#define EXTRA_LIB_KEY 1003
|
||||
|
||||
static const struct argp_option opts[] = {
|
||||
{"pid", 'p', "PID", 0, "Sniff this PID only."},
|
||||
{"uid", 'u', "UID", 0, "Sniff this UID only."},
|
||||
{"extra", 'x', NULL, 0, "Show extra fields (UID, TID)"},
|
||||
{"comm", 'c', "COMMAND", 0, "Sniff only commands matching string."},
|
||||
{"no-openssl", 'o', NULL, 0, "Do not show OpenSSL calls."},
|
||||
{"no-gnutls", 'g', NULL, 0, "Do not show GnuTLS calls."},
|
||||
{"no-nss", 'n', NULL, 0, "Do not show NSS calls."},
|
||||
{"hexdump", HEXDUMP_KEY, NULL, 0,
|
||||
"Show data as hexdump instead of trying to decode it as UTF-8"},
|
||||
{"latency", 'l', NULL, 0, "Show function latency"},
|
||||
{"handshake", HANDSHAKE_KEY, NULL, 0,
|
||||
"Show SSL handshake latency, enabled only if latency option is on."},
|
||||
{"verbose", 'v', NULL, 0, "Verbose debug output"},
|
||||
{ NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
|
||||
{},
|
||||
};
|
||||
|
||||
static bool verbose = false;
|
||||
|
||||
static error_t parse_arg(int key, char *arg, struct argp_state *state) {
|
||||
switch (key) {
|
||||
case 'p':
|
||||
env.pid = atoi(arg);
|
||||
break;
|
||||
case 'u':
|
||||
env.uid = atoi(arg);
|
||||
break;
|
||||
case 'x':
|
||||
env.extra = true;
|
||||
break;
|
||||
case 'c':
|
||||
env.comm = strdup(arg);
|
||||
break;
|
||||
case 'o':
|
||||
env.openssl = false;
|
||||
break;
|
||||
case 'g':
|
||||
env.gnutls = false;
|
||||
break;
|
||||
case 'n':
|
||||
env.nss = false;
|
||||
break;
|
||||
case 'l':
|
||||
env.latency = true;
|
||||
break;
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case HEXDUMP_KEY:
|
||||
env.hexdump = true;
|
||||
break;
|
||||
case HANDSHAKE_KEY:
|
||||
env.handshake = true;
|
||||
break;
|
||||
case 'h':
|
||||
argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define PERF_BUFFER_PAGES 16
|
||||
#define PERF_POLL_TIMEOUT_MS 100
|
||||
#define warn(...) fprintf(stderr, __VA_ARGS__)
|
||||
|
||||
static struct argp argp = {
|
||||
opts,
|
||||
parse_arg,
|
||||
NULL,
|
||||
argp_program_doc
|
||||
};
|
||||
|
||||
static int libbpf_print_fn(enum libbpf_print_level level, const char *format,
|
||||
va_list args) {
|
||||
if (level == LIBBPF_DEBUG && !verbose)
|
||||
return 0;
|
||||
return vfprintf(stderr, format, args);
|
||||
}
|
||||
|
||||
static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt) {
|
||||
warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
|
||||
}
|
||||
|
||||
static void sig_int(int signo) {
|
||||
exiting = 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int attach_gnutls(struct sslsniff_bpf *skel, const char *lib) {
|
||||
ATTACH_UPROBE_CHECKED(skel, lib, gnutls_record_send, probe_SSL_rw_enter);
|
||||
ATTACH_URETPROBE_CHECKED(skel, lib, gnutls_record_send, probe_SSL_write_exit);
|
||||
ATTACH_UPROBE_CHECKED(skel, lib, gnutls_record_recv, probe_SSL_rw_enter);
|
||||
ATTACH_URETPROBE_CHECKED(skel, lib, gnutls_record_recv, probe_SSL_read_exit);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int attach_nss(struct sslsniff_bpf *skel, const char *lib) {
|
||||
ATTACH_UPROBE_CHECKED(skel, lib, PR_Write, probe_SSL_rw_enter);
|
||||
ATTACH_URETPROBE_CHECKED(skel, lib, PR_Write, probe_SSL_write_exit);
|
||||
ATTACH_UPROBE_CHECKED(skel, lib, PR_Send, probe_SSL_rw_enter);
|
||||
ATTACH_URETPROBE_CHECKED(skel, lib, PR_Send, probe_SSL_write_exit);
|
||||
ATTACH_UPROBE_CHECKED(skel, lib, PR_Read, probe_SSL_rw_enter);
|
||||
ATTACH_URETPROBE_CHECKED(skel, lib, PR_Read, probe_SSL_read_exit);
|
||||
ATTACH_UPROBE_CHECKED(skel, lib, PR_Recv, probe_SSL_rw_enter);
|
||||
ATTACH_URETPROBE_CHECKED(skel, lib, PR_Recv, probe_SSL_read_exit);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the path of a library using ldconfig.
|
||||
*/
|
||||
char *find_library_path(const char *libname) {
|
||||
char cmd[128];
|
||||
static char path[512];
|
||||
FILE *fp;
|
||||
|
||||
// Construct the ldconfig command with grep
|
||||
snprintf(cmd, sizeof(cmd), "ldconfig -p | grep %s", libname);
|
||||
|
||||
// Execute the command and read the output
|
||||
fp = popen(cmd, "r");
|
||||
if (fp == NULL) {
|
||||
perror("Failed to run ldconfig");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Read the first line of output which should have the library path
|
||||
if (fgets(path, sizeof(path) - 1, fp) != NULL) {
|
||||
// Extract the path from the ldconfig output
|
||||
char *start = strrchr(path, '>');
|
||||
if (start && *(start + 1) == ' ') {
|
||||
memmove(path, start + 2, strlen(start + 2) + 1);
|
||||
char *end = strchr(path, '\n');
|
||||
if (end) {
|
||||
*end = '\0'; // Null-terminate the path
|
||||
}
|
||||
pclose(fp);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
pclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void buf_to_hex(const uint8_t *buf, size_t len, char *hex_str) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
sprintf(hex_str + 2 * i, "%02x", buf[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to print the event from the perf buffer
|
||||
void print_event(struct probe_SSL_data_t *event, const char *evt) {
|
||||
static unsigned long long start =
|
||||
0; // Use static to retain value across function calls
|
||||
char buf[MAX_BUF_SIZE + 1] = {0}; // +1 for null terminator
|
||||
unsigned int buf_size;
|
||||
|
||||
if (event->len <= MAX_BUF_SIZE) {
|
||||
buf_size = event->len;
|
||||
} else {
|
||||
buf_size = MAX_BUF_SIZE;
|
||||
}
|
||||
|
||||
if (event->buf_filled == 1) {
|
||||
memcpy(buf, event->buf, buf_size);
|
||||
} else {
|
||||
buf_size = 0;
|
||||
}
|
||||
|
||||
if (env.comm && strcmp(env.comm, event->comm) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (start == 0) {
|
||||
start = event->timestamp_ns;
|
||||
}
|
||||
double time_s = (double)(event->timestamp_ns - start) / 1000000000;
|
||||
|
||||
char lat_str[10];
|
||||
if (event->delta_ns) {
|
||||
snprintf(lat_str, sizeof(lat_str), "%.3f",
|
||||
(double)event->delta_ns / 1000000);
|
||||
} else {
|
||||
strncpy(lat_str, "N/A", sizeof(lat_str));
|
||||
}
|
||||
|
||||
char s_mark[] = "----- DATA -----";
|
||||
char e_mark[64] = "----- END DATA -----";
|
||||
if (buf_size < event->len) {
|
||||
snprintf(e_mark, sizeof(e_mark),
|
||||
"----- END DATA (TRUNCATED, %d bytes lost) -----",
|
||||
event->len - buf_size);
|
||||
}
|
||||
|
||||
char *rw_event[] = {
|
||||
"READ/RECV",
|
||||
"WRITE/SEND",
|
||||
"HANDSHAKE"
|
||||
};
|
||||
|
||||
#define BASE_FMT "%-12s %-18.9f %-16s %-7d %-6d"
|
||||
#define EXTRA_FMT " %-7d %-7d"
|
||||
#define LATENCY_FMT " %-7s"
|
||||
|
||||
if (env.extra && env.latency) {
|
||||
printf(BASE_FMT EXTRA_FMT LATENCY_FMT, rw_event[event->rw],
|
||||
time_s, event->comm, event->pid,
|
||||
event->len, event->uid, event->tid, lat_str);
|
||||
} else if (env.extra) {
|
||||
printf(BASE_FMT EXTRA_FMT, rw_event[event->rw], time_s, event->comm, event->pid,
|
||||
event->len, event->uid, event->tid);
|
||||
} else if (env.latency) {
|
||||
printf(BASE_FMT LATENCY_FMT, rw_event[event->rw], time_s, event->comm, event->pid,
|
||||
event->len, lat_str);
|
||||
} else {
|
||||
printf(BASE_FMT, rw_event[event->rw], time_s, event->comm, event->pid,
|
||||
event->len);
|
||||
}
|
||||
|
||||
if (buf_size != 0) {
|
||||
if (env.hexdump) {
|
||||
// 2 characters for each byte + null terminator
|
||||
char hex_data[MAX_BUF_SIZE * 2 + 1] = {0};
|
||||
buf_to_hex((uint8_t *)buf, buf_size, hex_data);
|
||||
|
||||
printf("\n%s\n", s_mark);
|
||||
for (size_t i = 0; i < strlen(hex_data); i += 32) {
|
||||
printf("%.32s\n", hex_data + i);
|
||||
}
|
||||
printf("%s\n\n", e_mark);
|
||||
} else {
|
||||
printf("\n%s\n%s\n%s\n\n", s_mark, buf, e_mark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_event(void *ctx, int cpu, void *data, __u32 data_size) {
|
||||
struct probe_SSL_data_t *e = data;
|
||||
if (e->is_handshake) {
|
||||
print_event(e, "perf_SSL_do_handshake");
|
||||
} else {
|
||||
print_event(e, "perf_SSL_rw");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
LIBBPF_OPTS(bpf_object_open_opts, open_opts);
|
||||
struct sslsniff_bpf *obj = NULL;
|
||||
struct perf_buffer *pb = NULL;
|
||||
int err;
|
||||
|
||||
err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
libbpf_set_print(libbpf_print_fn);
|
||||
|
||||
obj = sslsniff_bpf__open_opts(&open_opts);
|
||||
if (!obj) {
|
||||
warn("failed to open BPF object\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
obj->rodata->targ_uid = env.uid;
|
||||
obj->rodata->targ_pid = env.pid == INVALID_PID ? 0 : env.pid;
|
||||
|
||||
err = sslsniff_bpf__load(obj);
|
||||
if (err) {
|
||||
warn("failed to load BPF object: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (env.openssl) {
|
||||
char *openssl_path = find_library_path("libssl.so");
|
||||
printf("OpenSSL path: %s\n", openssl_path);
|
||||
attach_openssl(obj, "/lib/x86_64-linux-gnu/libssl.so.3");
|
||||
}
|
||||
if (env.gnutls) {
|
||||
char *gnutls_path = find_library_path("libgnutls.so");
|
||||
printf("GnuTLS path: %s\n", gnutls_path);
|
||||
attach_gnutls(obj, gnutls_path);
|
||||
}
|
||||
if (env.nss) {
|
||||
char *nss_path = find_library_path("libnspr4.so");
|
||||
printf("NSS path: %s\n", nss_path);
|
||||
attach_nss(obj, nss_path);
|
||||
}
|
||||
|
||||
pb = perf_buffer__new(bpf_map__fd(obj->maps.perf_SSL_events),
|
||||
PERF_BUFFER_PAGES, handle_event, handle_lost_events,
|
||||
NULL, NULL);
|
||||
if (!pb) {
|
||||
err = -errno;
|
||||
warn("failed to open perf buffer: %d\n", err);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (signal(SIGINT, sig_int) == SIG_ERR) {
|
||||
warn("can't set signal handler: %s\n", strerror(errno));
|
||||
err = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Print header
|
||||
printf("%-12s %-18s %-16s %-7s %-7s", "FUNC", "TIME(s)", "COMM", "PID",
|
||||
"LEN");
|
||||
if (env.extra) {
|
||||
printf(" %-7s %-7s", "UID", "TID");
|
||||
}
|
||||
if (env.latency) {
|
||||
printf(" %-7s", "LAT(ms)");
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
while (!exiting) {
|
||||
err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
|
||||
if (err < 0 && err != -EINTR) {
|
||||
warn("error polling perf buffer: %s\n", strerror(-err));
|
||||
goto cleanup;
|
||||
}
|
||||
err = 0;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
perf_buffer__free(pb);
|
||||
sslsniff_bpf__destroy(obj);
|
||||
return err != 0;
|
||||
}
|
||||
26
30-sslsniff/sslsniff.h
Normal file
26
30-sslsniff/sslsniff.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
|
||||
// Copyright (c) 2023 Yusheng Zheng
|
||||
//
|
||||
// Based on sslsniff from BCC by Adrian Lopez & Mark Drayton.
|
||||
// 15-Aug-2023 Yusheng Zheng Created this.
|
||||
#ifndef __SSLSNIFF_H
|
||||
#define __SSLSNIFF_H
|
||||
|
||||
#define MAX_BUF_SIZE 8192
|
||||
#define TASK_COMM_LEN 16
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#endif /* __SSLSNIFF_H */
|
||||
Reference in New Issue
Block a user