xref: /aosp_15_r20/external/bcc/tools/sslsniff.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env python
2#
3# sslsniff  Captures data on read/recv or write/send functions of OpenSSL,
4#           GnuTLS and NSS
5#           For Linux, uses BCC, eBPF.
6#
7# USAGE: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d]
8#                    [--hexdump] [--max-buffer-size SIZE] [-l] [--handshake]
9#
10# 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
14# 17-Aug-2016    Adrian Lopez   Capture GnuTLS and add options
15#
16
17from __future__ import print_function
18from bcc import BPF
19import argparse
20import binascii
21import textwrap
22import os.path
23
24# arguments
25examples = """examples:
26    ./sslsniff              # sniff OpenSSL and GnuTLS functions
27    ./sslsniff -p 181       # sniff PID 181 only
28    ./sslsniff -u 1000      # sniff only UID 1000
29    ./sslsniff -c curl      # sniff curl command only
30    ./sslsniff --no-openssl # don't show OpenSSL calls
31    ./sslsniff --no-gnutls  # don't show GnuTLS calls
32    ./sslsniff --no-nss     # don't show NSS calls
33    ./sslsniff --hexdump    # show data as hex instead of trying to decode it as UTF-8
34    ./sslsniff -x           # show process UID and TID
35    ./sslsniff -l           # show function latency
36    ./sslsniff -l --handshake  # show SSL handshake latency
37    ./sslsniff --extra-lib openssl:/path/libssl.so.1.1 # sniff extra library
38"""
39
40
41def ssllib_type(input_str):
42    valid_types = frozenset(['openssl', 'gnutls', 'nss'])
43
44    try:
45        lib_type, lib_path = input_str.split(':', 1)
46    except ValueError:
47        raise argparse.ArgumentTypeError("Invalid SSL library param: %r" % input_str)
48
49    if lib_type not in valid_types:
50        raise argparse.ArgumentTypeError("Invalid SSL library type: %r" % lib_type)
51
52    if not os.path.isfile(lib_path):
53        raise argparse.ArgumentTypeError("Invalid library path: %r" % lib_path)
54
55    return lib_type, lib_path
56
57
58parser = argparse.ArgumentParser(
59    description="Sniff SSL data",
60    formatter_class=argparse.RawDescriptionHelpFormatter,
61    epilog=examples)
62parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.")
63parser.add_argument("-u", "--uid", type=int, default=None,
64                    help="sniff this UID only.")
65parser.add_argument("-x", "--extra", action="store_true",
66                    help="show extra fields (UID, TID)")
67parser.add_argument("-c", "--comm",
68                    help="sniff only commands matching string.")
69parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl",
70                    help="do not show OpenSSL calls.")
71parser.add_argument("-g", "--no-gnutls", action="store_false", dest="gnutls",
72                    help="do not show GnuTLS calls.")
73parser.add_argument("-n", "--no-nss", action="store_false", dest="nss",
74                    help="do not show NSS calls.")
75parser.add_argument('-d', '--debug', dest='debug', action='count', default=0,
76                    help='debug mode.')
77parser.add_argument("--ebpf", action="store_true",
78                    help=argparse.SUPPRESS)
79parser.add_argument("--hexdump", action="store_true", dest="hexdump",
80                    help="show data as hexdump instead of trying to decode it as UTF-8")
81parser.add_argument('--max-buffer-size', type=int, default=8192,
82                    help='Size of captured buffer')
83parser.add_argument("-l", "--latency", action="store_true",
84                    help="show function latency")
85parser.add_argument("--handshake", action="store_true",
86                    help="show SSL handshake latency, enabled only if latency option is on.")
87parser.add_argument("--extra-lib", type=ssllib_type, action='append',
88                    help="Intercept calls from extra library (format: lib_type:lib_path)")
89args = parser.parse_args()
90
91
92prog = """
93#include <linux/ptrace.h>
94#include <linux/sched.h>        /* For TASK_COMM_LEN */
95
96#define MAX_BUF_SIZE __MAX_BUF_SIZE__
97
98struct probe_SSL_data_t {
99        u64 timestamp_ns;
100        u64 delta_ns;
101        u32 pid;
102        u32 tid;
103        u32 uid;
104        u32 len;
105        int buf_filled;
106        int rw;
107        char comm[TASK_COMM_LEN];
108        u8 buf[MAX_BUF_SIZE];
109};
110
111#define BASE_EVENT_SIZE ((size_t)(&((struct probe_SSL_data_t*)0)->buf))
112#define EVENT_SIZE(X) (BASE_EVENT_SIZE + ((size_t)(X)))
113
114BPF_PERCPU_ARRAY(ssl_data, struct probe_SSL_data_t, 1);
115BPF_PERF_OUTPUT(perf_SSL_rw);
116
117BPF_HASH(start_ns, u32);
118BPF_HASH(bufs, u32, u64);
119
120int probe_SSL_rw_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) {
121        int ret;
122        u32 zero = 0;
123        u64 pid_tgid = bpf_get_current_pid_tgid();
124        u32 pid = pid_tgid >> 32;
125        u32 tid = pid_tgid;
126        u32 uid = bpf_get_current_uid_gid();
127        u64 ts = bpf_ktime_get_ns();
128
129        PID_FILTER
130        UID_FILTER
131
132        bufs.update(&tid, (u64*)&buf);
133        start_ns.update(&tid, &ts);
134        return 0;
135}
136
137static int SSL_exit(struct pt_regs *ctx, int rw) {
138        int ret;
139        u32 zero = 0;
140        u64 pid_tgid = bpf_get_current_pid_tgid();
141        u32 pid = pid_tgid >> 32;
142        u32 tid = (u32)pid_tgid;
143        u32 uid = bpf_get_current_uid_gid();
144        u64 ts = bpf_ktime_get_ns();
145
146        PID_FILTER
147        UID_FILTER
148
149        u64 *bufp = bufs.lookup(&tid);
150        if (bufp == 0)
151                return 0;
152
153        u64 *tsp = start_ns.lookup(&tid);
154        if (tsp == 0)
155                return 0;
156
157        int len = PT_REGS_RC(ctx);
158        if (len <= 0) // no data
159                return 0;
160
161        struct probe_SSL_data_t *data = ssl_data.lookup(&zero);
162        if (!data)
163                return 0;
164
165        data->timestamp_ns = ts;
166        data->delta_ns = ts - *tsp;
167        data->pid = pid;
168        data->tid = tid;
169        data->uid = uid;
170        data->len = (u32)len;
171        data->buf_filled = 0;
172        data->rw = rw;
173        u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len);
174
175        bpf_get_current_comm(&data->comm, sizeof(data->comm));
176
177        if (bufp != 0)
178                ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp);
179
180        bufs.delete(&tid);
181        start_ns.delete(&tid);
182
183        if (!ret)
184                data->buf_filled = 1;
185        else
186                buf_copy_size = 0;
187
188        perf_SSL_rw.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size));
189        return 0;
190}
191
192int probe_SSL_read_exit(struct pt_regs *ctx) {
193        return (SSL_exit(ctx, 0));
194}
195
196int probe_SSL_write_exit(struct pt_regs *ctx) {
197        return (SSL_exit(ctx, 1));
198}
199
200BPF_PERF_OUTPUT(perf_SSL_do_handshake);
201
202int probe_SSL_do_handshake_enter(struct pt_regs *ctx, void *ssl) {
203        u64 pid_tgid = bpf_get_current_pid_tgid();
204        u32 pid = pid_tgid >> 32;
205        u32 tid = (u32)pid_tgid;
206        u64 ts = bpf_ktime_get_ns();
207        u32 uid = bpf_get_current_uid_gid();
208
209        PID_FILTER
210        UID_FILTER
211
212        start_ns.update(&tid, &ts);
213        return 0;
214}
215
216int probe_SSL_do_handshake_exit(struct pt_regs *ctx) {
217        u32 zero = 0;
218        u64 pid_tgid = bpf_get_current_pid_tgid();
219        u32 pid = pid_tgid >> 32;
220        u32 tid = (u32)pid_tgid;
221        u32 uid = bpf_get_current_uid_gid();
222        u64 ts = bpf_ktime_get_ns();
223        int ret;
224
225        PID_FILTER
226        UID_FILTER
227
228        u64 *tsp = start_ns.lookup(&tid);
229        if (tsp == 0)
230                return 0;
231
232        ret = PT_REGS_RC(ctx);
233        if (ret <= 0) // handshake failed
234                return 0;
235
236        struct probe_SSL_data_t *data = ssl_data.lookup(&zero);
237        if (!data)
238                return 0;
239
240        data->timestamp_ns = ts;
241        data->delta_ns = ts - *tsp;
242        data->pid = pid;
243        data->tid = tid;
244        data->uid = uid;
245        data->len = ret;
246        data->buf_filled = 0;
247        data->rw = 2;
248        bpf_get_current_comm(&data->comm, sizeof(data->comm));
249        start_ns.delete(&tid);
250
251        perf_SSL_do_handshake.perf_submit(ctx, data, EVENT_SIZE(0));
252        return 0;
253}
254"""
255
256if args.pid:
257    prog = prog.replace('PID_FILTER', 'if (pid != %d) { return 0; }' % args.pid)
258else:
259    prog = prog.replace('PID_FILTER', '')
260
261if args.uid is not None:
262    prog = prog.replace('UID_FILTER', 'if (uid != %d) { return 0; }' % args.uid)
263else:
264    prog = prog.replace('UID_FILTER', '')
265
266prog = prog.replace('__MAX_BUF_SIZE__', str(args.max_buffer_size))
267
268if args.debug or args.ebpf:
269    print(prog)
270    if args.ebpf:
271        exit()
272
273
274b = BPF(text=prog)
275
276# It looks like SSL_read's arguments aren't available in a return probe so you
277# need to stash the buffer address in a map on the function entry and read it
278# on its exit (Mark Drayton)
279#
280def attach_openssl(lib):
281    b.attach_uprobe(name=lib, sym="SSL_write",
282                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
283    b.attach_uretprobe(name=lib, sym="SSL_write",
284                       fn_name="probe_SSL_write_exit", pid=args.pid or -1)
285    b.attach_uprobe(name=lib, sym="SSL_read",
286                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
287    b.attach_uretprobe(name=lib, sym="SSL_read",
288                       fn_name="probe_SSL_read_exit", pid=args.pid or -1)
289    if args.latency and args.handshake:
290        b.attach_uprobe(name="ssl", sym="SSL_do_handshake",
291                        fn_name="probe_SSL_do_handshake_enter", pid=args.pid or -1)
292        b.attach_uretprobe(name="ssl", sym="SSL_do_handshake",
293                           fn_name="probe_SSL_do_handshake_exit", pid=args.pid or -1)
294
295def attach_gnutls(lib):
296    b.attach_uprobe(name=lib, sym="gnutls_record_send",
297                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
298    b.attach_uretprobe(name=lib, sym="gnutls_record_send",
299                       fn_name="probe_SSL_write_exit", pid=args.pid or -1)
300    b.attach_uprobe(name=lib, sym="gnutls_record_recv",
301                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
302    b.attach_uretprobe(name=lib, sym="gnutls_record_recv",
303                       fn_name="probe_SSL_read_exit", pid=args.pid or -1)
304
305def attach_nss(lib):
306    b.attach_uprobe(name=lib, sym="PR_Write",
307                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
308    b.attach_uretprobe(name=lib, sym="PR_Write",
309                       fn_name="probe_SSL_write_exit", pid=args.pid or -1)
310    b.attach_uprobe(name=lib, sym="PR_Send",
311                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
312    b.attach_uretprobe(name=lib, sym="PR_Send",
313                       fn_name="probe_SSL_write_exit", pid=args.pid or -1)
314    b.attach_uprobe(name=lib, sym="PR_Read",
315                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
316    b.attach_uretprobe(name=lib, sym="PR_Read",
317                       fn_name="probe_SSL_read_exit", pid=args.pid or -1)
318    b.attach_uprobe(name=lib, sym="PR_Recv",
319                    fn_name="probe_SSL_rw_enter", pid=args.pid or -1)
320    b.attach_uretprobe(name=lib, sym="PR_Recv",
321                       fn_name="probe_SSL_read_exit", pid=args.pid or -1)
322
323
324LIB_TRACERS = {
325    "openssl": attach_openssl,
326    "gnutls": attach_gnutls,
327    "nss": attach_nss,
328}
329
330
331if args.openssl:
332    attach_openssl("ssl")
333if args.gnutls:
334    attach_gnutls("gnutls")
335if args.nss:
336    attach_nss("nspr4")
337
338
339if args.extra_lib:
340    for lib_type, lib_path in args.extra_lib:
341        LIB_TRACERS[lib_type](lib_path)
342
343# define output data structure in Python
344
345
346# header
347header = "%-12s %-18s %-16s %-7s %-7s" % ("FUNC", "TIME(s)", "COMM", "PID", "LEN")
348
349if args.extra:
350    header += " %-7s %-7s" % ("UID", "TID")
351
352if args.latency:
353    header += " %-7s" % ("LAT(ms)")
354
355print(header)
356# process event
357start = 0
358
359def print_event_rw(cpu, data, size):
360    print_event(cpu, data, size, "perf_SSL_rw")
361
362def print_event_handshake(cpu, data, size):
363    print_event(cpu, data, size, "perf_SSL_do_handshake")
364
365def print_event(cpu, data, size, evt):
366    global start
367    event = b[evt].event(data)
368    if event.len <= args.max_buffer_size:
369        buf_size = event.len
370    else:
371        buf_size = args.max_buffer_size
372
373    if event.buf_filled == 1:
374        buf = bytearray(event.buf[:buf_size])
375    else:
376        buf_size = 0
377        buf = b""
378
379    # Filter events by command
380    if args.comm:
381        if not args.comm == event.comm.decode('utf-8', 'replace'):
382            return
383
384    if start == 0:
385        start = event.timestamp_ns
386    time_s = (float(event.timestamp_ns - start)) / 1000000000
387
388    lat_str = "%.3f" % (event.delta_ns / 1000000) if event.delta_ns else "N/A"
389
390    s_mark = "-" * 5 + " DATA " + "-" * 5
391
392    e_mark = "-" * 5 + " END DATA " + "-" * 5
393
394    truncated_bytes = event.len - buf_size
395    if truncated_bytes > 0:
396        e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \
397                " bytes lost) " + "-" * 5
398
399    base_fmt = "%(func)-12s %(time)-18.9f %(comm)-16s %(pid)-7d %(len)-6d"
400
401    if args.extra:
402        base_fmt += " %(uid)-7d %(tid)-7d"
403
404    if args.latency:
405        base_fmt += " %(lat)-7s"
406
407    fmt = ''.join([base_fmt, "\n%(begin)s\n%(data)s\n%(end)s\n\n"])
408    if args.hexdump:
409        unwrapped_data = binascii.hexlify(buf)
410        data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'), width=32)
411    else:
412        data = buf.decode('utf-8', 'replace')
413
414    rw_event = {
415        0: "READ/RECV",
416        1: "WRITE/SEND",
417        2: "HANDSHAKE"
418    }
419
420    fmt_data = {
421        'func': rw_event[event.rw],
422        'time': time_s,
423        'lat': lat_str,
424        'comm': event.comm.decode('utf-8', 'replace'),
425        'pid': event.pid,
426        'tid': event.tid,
427        'uid': event.uid,
428        'len': event.len,
429        'begin': s_mark,
430        'end': e_mark,
431        'data': data
432    }
433
434    # use base_fmt if no buf filled
435    if buf_size == 0:
436        print(base_fmt % fmt_data)
437    else:
438        print(fmt % fmt_data)
439
440b["perf_SSL_rw"].open_perf_buffer(print_event_rw)
441b["perf_SSL_do_handshake"].open_perf_buffer(print_event_handshake)
442while 1:
443    try:
444        b.perf_buffer_poll()
445    except KeyboardInterrupt:
446        exit()
447