blob: 8bc61ce7a6587bcd6c83af0f5fd34c96acba9ab5 [file] [log] [blame]
Alexey Ivanovcc01a9c2019-01-16 09:50:46 -08001#!/usr/bin/python
Adrian Lopezd496d5c2016-08-16 17:49:49 +02002#
jeromemarchand8b17dc32018-08-04 07:09:36 +02003# sslsniff Captures data on read/recv or write/send functions of OpenSSL,
4# GnuTLS and NSS
Adrian Lopezd496d5c2016-08-16 17:49:49 +02005# For Linux, uses BCC, eBPF.
6#
Slava Bacherikov91a79832021-11-21 15:31:49 +02007# USAGE: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d]
8# [--hexdump] [--max-buffer-size SIZE]
Adrian Lopezd9cc3de2016-08-17 14:08:08 +02009#
Adrian Lopezd496d5c2016-08-16 17:49:49 +020010# Licensed under the Apache License, Version 2.0 (the "License")
11#
12# 12-Aug-2016 Adrian Lopez Created this.
13# 13-Aug-2016 Mark Drayton Fix SSL_Read
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020014# 17-Aug-2016 Adrian Lopez Capture GnuTLS and add options
15#
Adrian Lopezd496d5c2016-08-16 17:49:49 +020016
17from __future__ import print_function
Adrian Lopezd496d5c2016-08-16 17:49:49 +020018from bcc import BPF
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020019import argparse
Matthias Hörmann1b7aab12020-07-03 13:54:24 +020020import binascii
21import textwrap
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020022
23# arguments
24examples = """examples:
25 ./sslsniff # sniff OpenSSL and GnuTLS functions
26 ./sslsniff -p 181 # sniff PID 181 only
Slava Bacherikov91a79832021-11-21 15:31:49 +020027 ./sslsniff -u 1000 # sniff only UID 1000
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020028 ./sslsniff -c curl # sniff curl command only
29 ./sslsniff --no-openssl # don't show OpenSSL calls
30 ./sslsniff --no-gnutls # don't show GnuTLS calls
jeromemarchand8b17dc32018-08-04 07:09:36 +020031 ./sslsniff --no-nss # don't show NSS calls
Matthias Hörmannd40c3a72020-07-03 14:31:32 +020032 ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8
Slava Bacherikov91a79832021-11-21 15:31:49 +020033 ./sslsniff -x # show process UID and TID
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020034"""
35parser = argparse.ArgumentParser(
36 description="Sniff SSL data",
37 formatter_class=argparse.RawDescriptionHelpFormatter,
38 epilog=examples)
htbegin5ac5d6e2017-05-24 22:53:17 +080039parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.")
Slava Bacherikov91a79832021-11-21 15:31:49 +020040parser.add_argument("-u", "--uid", type=int, default=None,
41 help="sniff this UID only.")
42parser.add_argument("-x", "--extra", action="store_true",
43 help="show extra fields (UID, TID)")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020044parser.add_argument("-c", "--comm",
45 help="sniff only commands matching string.")
46parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl",
47 help="do not show OpenSSL calls.")
48parser.add_argument("-g", "--no-gnutls", action="store_false", dest="gnutls",
49 help="do not show GnuTLS calls.")
jeromemarchand8b17dc32018-08-04 07:09:36 +020050parser.add_argument("-n", "--no-nss", action="store_false", dest="nss",
51 help="do not show NSS calls.")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020052parser.add_argument('-d', '--debug', dest='debug', action='count', default=0,
53 help='debug mode.')
Nathan Scottcf0792f2018-02-02 16:56:50 +110054parser.add_argument("--ebpf", action="store_true",
55 help=argparse.SUPPRESS)
Matthias Hörmannd91b31a2020-07-06 09:38:39 +020056parser.add_argument("--hexdump", action="store_true", dest="hexdump",
57 help="show data as hexdump instead of trying to decode it as UTF-8")
Slava Bacherikov91a79832021-11-21 15:31:49 +020058parser.add_argument('--max-buffer-size', type=int, default=8192,
59 help='Size of captured buffer')
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020060args = parser.parse_args()
61
Adrian Lopezd496d5c2016-08-16 17:49:49 +020062
63prog = """
64#include <linux/ptrace.h>
65#include <linux/sched.h> /* For TASK_COMM_LEN */
66
Slava Bacherikov91a79832021-11-21 15:31:49 +020067#define MAX_BUF_SIZE __MAX_BUF_SIZE__
68
Adrian Lopezd496d5c2016-08-16 17:49:49 +020069struct probe_SSL_data_t {
70 u64 timestamp_ns;
71 u32 pid;
Slava Bacherikov91a79832021-11-21 15:31:49 +020072 u32 tid;
73 u32 uid;
Adrian Lopezd496d5c2016-08-16 17:49:49 +020074 u32 len;
Slava Bacherikov91a79832021-11-21 15:31:49 +020075 int buf_filled;
76 char comm[TASK_COMM_LEN];
77 u8 buf[MAX_BUF_SIZE];
Adrian Lopezd496d5c2016-08-16 17:49:49 +020078};
79
Slava Bacherikov91a79832021-11-21 15:31:49 +020080#define BASE_EVENT_SIZE ((size_t)(&((struct probe_SSL_data_t*)0)->buf))
81#define EVENT_SIZE(X) (BASE_EVENT_SIZE + ((size_t)(X)))
82
83
84BPF_PERCPU_ARRAY(ssl_data, struct probe_SSL_data_t, 1);
Adrian Lopezd496d5c2016-08-16 17:49:49 +020085BPF_PERF_OUTPUT(perf_SSL_write);
86
87int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Slava Bacherikov91a79832021-11-21 15:31:49 +020088 int ret;
89 u32 zero = 0;
Hengqi Chen151fe192021-05-16 17:18:27 +080090 u64 pid_tgid = bpf_get_current_pid_tgid();
91 u32 pid = pid_tgid >> 32;
Slava Bacherikov91a79832021-11-21 15:31:49 +020092 u32 tid = pid_tgid;
93 u32 uid = bpf_get_current_uid_gid();
Hengqi Chen151fe192021-05-16 17:18:27 +080094
Slava Bacherikov91a79832021-11-21 15:31:49 +020095 PID_FILTER
96 UID_FILTER
97 struct probe_SSL_data_t *data = ssl_data.lookup(&zero);
98 if (!data)
99 return 0;
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200100
Slava Bacherikov91a79832021-11-21 15:31:49 +0200101 data->timestamp_ns = bpf_ktime_get_ns();
102 data->pid = pid;
103 data->tid = tid;
104 data->uid = uid;
105 data->len = num;
106 data->buf_filled = 0;
107 bpf_get_current_comm(&data->comm, sizeof(data->comm));
108 u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)num);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200109
Slava Bacherikov91a79832021-11-21 15:31:49 +0200110 if (buf != 0)
111 ret = bpf_probe_read_user(data->buf, buf_copy_size, buf);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200112
Slava Bacherikov91a79832021-11-21 15:31:49 +0200113 if (!ret)
114 data->buf_filled = 1;
115 else
116 buf_copy_size = 0;
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200117
Slava Bacherikov91a79832021-11-21 15:31:49 +0200118 perf_SSL_write.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size));
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200119 return 0;
120}
121
122BPF_PERF_OUTPUT(perf_SSL_read);
123
124BPF_HASH(bufs, u32, u64);
125
126int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Hengqi Chen151fe192021-05-16 17:18:27 +0800127 u64 pid_tgid = bpf_get_current_pid_tgid();
128 u32 pid = pid_tgid >> 32;
129 u32 tid = (u32)pid_tgid;
Slava Bacherikov91a79832021-11-21 15:31:49 +0200130 u32 uid = bpf_get_current_uid_gid();
Hengqi Chen151fe192021-05-16 17:18:27 +0800131
Slava Bacherikov91a79832021-11-21 15:31:49 +0200132 PID_FILTER
133 UID_FILTER
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200134
Hengqi Chen151fe192021-05-16 17:18:27 +0800135 bufs.update(&tid, (u64*)&buf);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200136 return 0;
137}
138
139int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Slava Bacherikov91a79832021-11-21 15:31:49 +0200140 u32 zero = 0;
Hengqi Chen151fe192021-05-16 17:18:27 +0800141 u64 pid_tgid = bpf_get_current_pid_tgid();
142 u32 pid = pid_tgid >> 32;
143 u32 tid = (u32)pid_tgid;
Slava Bacherikov91a79832021-11-21 15:31:49 +0200144 u32 uid = bpf_get_current_uid_gid();
145 int ret;
Hengqi Chen151fe192021-05-16 17:18:27 +0800146
Slava Bacherikov91a79832021-11-21 15:31:49 +0200147 PID_FILTER
148 UID_FILTER
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200149
Hengqi Chen151fe192021-05-16 17:18:27 +0800150 u64 *bufp = bufs.lookup(&tid);
Slava Bacherikov91a79832021-11-21 15:31:49 +0200151 if (bufp == 0)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200152 return 0;
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200153
Slava Bacherikov91a79832021-11-21 15:31:49 +0200154 int len = PT_REGS_RC(ctx);
155 if (len <= 0) // read failed
156 return 0;
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200157
Slava Bacherikov91a79832021-11-21 15:31:49 +0200158 struct probe_SSL_data_t *data = ssl_data.lookup(&zero);
159 if (!data)
160 return 0;
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200161
Slava Bacherikov91a79832021-11-21 15:31:49 +0200162 data->timestamp_ns = bpf_ktime_get_ns();
163 data->pid = pid;
164 data->tid = tid;
165 data->uid = uid;
166 data->len = (u32)len;
167 data->buf_filled = 0;
168 u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len);
169
170 bpf_get_current_comm(&data->comm, sizeof(data->comm));
171
172 if (bufp != 0)
173 ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200174
Hengqi Chen151fe192021-05-16 17:18:27 +0800175 bufs.delete(&tid);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200176
Slava Bacherikov91a79832021-11-21 15:31:49 +0200177 if (!ret)
178 data->buf_filled = 1;
179 else
180 buf_copy_size = 0;
181
182 perf_SSL_read.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size));
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200183 return 0;
184}
185"""
186
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200187if args.pid:
Slava Bacherikov91a79832021-11-21 15:31:49 +0200188 prog = prog.replace('PID_FILTER', 'if (pid != %d) { return 0; }' % args.pid)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200189else:
Slava Bacherikov91a79832021-11-21 15:31:49 +0200190 prog = prog.replace('PID_FILTER', '')
191
192if args.uid is not None:
193 prog = prog.replace('UID_FILTER', 'if (uid != %d) { return 0; }' % args.uid)
194else:
195 prog = prog.replace('UID_FILTER', '')
196
197prog = prog.replace('__MAX_BUF_SIZE__', str(args.max_buffer_size))
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200198
Nathan Scottcf0792f2018-02-02 16:56:50 +1100199if args.debug or args.ebpf:
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200200 print(prog)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100201 if args.ebpf:
202 exit()
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200203
204
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200205b = BPF(text=prog)
206
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200207# It looks like SSL_read's arguments aren't available in a return probe so you
208# need to stash the buffer address in a map on the function entry and read it
209# on its exit (Mark Drayton)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200210#
211if args.openssl:
Paul Chaignond73c58f2017-01-21 14:25:41 +0100212 b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write",
213 pid=args.pid or -1)
214 b.attach_uprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_enter",
215 pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200216 b.attach_uretprobe(name="ssl", sym="SSL_read",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100217 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200218
219if args.gnutls:
220 b.attach_uprobe(name="gnutls", sym="gnutls_record_send",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100221 fn_name="probe_SSL_write", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200222 b.attach_uprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100223 fn_name="probe_SSL_read_enter", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200224 b.attach_uretprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100225 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200226
jeromemarchand8b17dc32018-08-04 07:09:36 +0200227if args.nss:
228 b.attach_uprobe(name="nspr4", sym="PR_Write", fn_name="probe_SSL_write",
229 pid=args.pid or -1)
230 b.attach_uprobe(name="nspr4", sym="PR_Send", fn_name="probe_SSL_write",
231 pid=args.pid or -1)
232 b.attach_uprobe(name="nspr4", sym="PR_Read", fn_name="probe_SSL_read_enter",
233 pid=args.pid or -1)
234 b.attach_uretprobe(name="nspr4", sym="PR_Read",
235 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
236 b.attach_uprobe(name="nspr4", sym="PR_Recv", fn_name="probe_SSL_read_enter",
237 pid=args.pid or -1)
238 b.attach_uretprobe(name="nspr4", sym="PR_Recv",
239 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
240
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200241# define output data structure in Python
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200242
243
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200244# header
Slava Bacherikov91a79832021-11-21 15:31:49 +0200245header = "%-12s %-18s %-16s %-7s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID", "LEN")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200246
Slava Bacherikov91a79832021-11-21 15:31:49 +0200247if args.extra:
248 header += " %-7s %-7s" % ("UID", "TID")
249
250print(header)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200251# process event
252start = 0
253
254
255def print_event_write(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800256 print_event(cpu, data, size, "WRITE/SEND", "perf_SSL_write")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200257
258
259def print_event_read(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800260 print_event(cpu, data, size, "READ/RECV", "perf_SSL_read")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200261
262
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800263def print_event(cpu, data, size, rw, evt):
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200264 global start
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800265 event = b[evt].event(data)
Slava Bacherikov91a79832021-11-21 15:31:49 +0200266 if event.len <= args.max_buffer_size:
267 buf_size = event.len
268 else:
269 buf_size = args.max_buffer_size
270
271 if event.buf_filled == 1:
272 buf = bytearray(event.buf[:buf_size])
273 else:
274 buf_size = 0
275 buf = b""
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200276
277 # Filter events by command
278 if args.comm:
keyolk49fdec62020-10-08 05:45:00 +0000279 if not args.comm == event.comm.decode('utf-8', 'replace'):
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200280 return
281
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200282 if start == 0:
283 start = event.timestamp_ns
284 time_s = (float(event.timestamp_ns - start)) / 1000000000
285
286 s_mark = "-" * 5 + " DATA " + "-" * 5
287
288 e_mark = "-" * 5 + " END DATA " + "-" * 5
289
Slava Bacherikov91a79832021-11-21 15:31:49 +0200290 truncated_bytes = event.len - buf_size
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200291 if truncated_bytes > 0:
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200292 e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \
293 " bytes lost) " + "-" * 5
294
Slava Bacherikov91a79832021-11-21 15:31:49 +0200295 base_fmt = "%(func)-12s %(time)-18.9f %(comm)-16s %(pid)-7d %(len)-6d"
296
297 if args.extra:
298 base_fmt += " %(uid)-7d %(tid)-7d"
299
300 fmt = ''.join([base_fmt, "\n%(begin)s\n%(data)s\n%(end)s\n\n"])
Matthias Hörmannd91b31a2020-07-06 09:38:39 +0200301 if args.hexdump:
Slava Bacherikov91a79832021-11-21 15:31:49 +0200302 unwrapped_data = binascii.hexlify(buf)
303 data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'), width=32)
Matthias Hörmannd91b31a2020-07-06 09:38:39 +0200304 else:
Slava Bacherikov91a79832021-11-21 15:31:49 +0200305 data = buf.decode('utf-8', 'replace')
306
307 fmt_data = {
308 'func': rw,
309 'time': time_s,
310 'comm': event.comm.decode('utf-8', 'replace'),
311 'pid': event.pid,
312 'tid': event.tid,
313 'uid': event.uid,
314 'len': event.len,
315 'begin': s_mark,
316 'end': e_mark,
317 'data': data
318 }
319
320 print(fmt % fmt_data)
321
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200322
323b["perf_SSL_write"].open_perf_buffer(print_event_write)
324b["perf_SSL_read"].open_perf_buffer(print_event_read)
325while 1:
Jerome Marchand51671272018-12-19 01:57:24 +0100326 try:
327 b.perf_buffer_poll()
328 except KeyboardInterrupt:
329 exit()