From: Slava Bacherikov Date: Sun, 21 Nov 2021 13:31:49 +0000 (+0200) Subject: tools: improve sslsniff (send buffer & filtering) X-Git-Tag: v0.24.0~60 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=91a79837aac50232c7dd424667e6e20ab2a5ee38;p=platform%2Fupstream%2Fbcc.git tools: improve sslsniff (send buffer & filtering) This makes few improvements: * This can send much larger data payload and also adds --max-buffer-size CLI option which allow changing this param. * Fixes dealing with non ASCII protocols, previously struct was defined as array of chars which made python ctypes treat it as NULL terminated string and it prevents from displaying any data past the null byte (which is very common for http2). * Adds more filtering and displaying options (--print-uid, --print-tid, --uid ) This also deals correctly with rare cases when bpf_probe_read_user fails (so buffer should be empty and should not be displayed). --- diff --git a/man/man8/sslsniff.8 b/man/man8/sslsniff.8 index 7b945b00..df81664b 100644 --- a/man/man8/sslsniff.8 +++ b/man/man8/sslsniff.8 @@ -2,7 +2,8 @@ .SH NAME sslsniff \- Print data passed to OpenSSL, GnuTLS or NSS. Uses Linux eBPF/bcc. .SH SYNOPSIS -.B sslsniff [-h] [-p PID] [-c COMM] [-o] [-g] [-n] [-d] [--hexdump] +.B sslsniff [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] +.B [--hexdump] [--max-buffer-size SIZE] .SH DESCRIPTION sslsniff prints data sent to write/send and read/recv functions of OpenSSL, GnuTLS and NSS, allowing us to read plain text content before @@ -13,11 +14,47 @@ This works reading the second parameter of both functions (*buf). Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. +.SH OPTIONS +.TP +\-h +Print usage message. +.TP +\-p PID +Trace only functions in this process PID. +.TP +\-u UID +Trace only calls made by this UID. +.TP +\-x +Show extra fields: UID and TID. +.TP +\-c COMM +Show only processes that match this COMM exactly. +.TP +\-o, \-\-no-openssl +Do not trace OpenSSL functions. +.TP +\-g, \-\-no-gnutls +Do not trace GnuTLS functions. +.TP +\-n, \-\-no-nss +Do not trace GnuTLS functions. +.TP +\-\-hexdump +Show data as hexdump instead of trying to decode it as UTF-8 +.TP +\-\-max-buffer-size SIZE +Sets maximum buffer size of intercepted data. Longer values would be truncated. +Default value is 8 Kib, maximum possible value is a bit less than 32 Kib. .SH EXAMPLES .TP Print all calls to SSL write/send and read/recv system-wide: # .B sslsniff +.TP +Print only OpenSSL calls issued by user with UID 1000 +# +.B sslsniff -u 1000 --no-nss --no-gnutls .SH FIELDS .TP FUNC @@ -34,6 +71,12 @@ Process ID calling SSL. .TP LEN Bytes written or read by SSL functions. +.TP +UID +UID of the process, displayed only if launched with -x. +.TP +TID +Thread ID, displayed only if launched with -x. .SH SOURCE This is from bcc. .IP diff --git a/tools/sslsniff.py b/tools/sslsniff.py index 02b73604..8bc61ce7 100755 --- a/tools/sslsniff.py +++ b/tools/sslsniff.py @@ -4,7 +4,8 @@ # GnuTLS and NSS # For Linux, uses BCC, eBPF. # -# USAGE: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-d] +# USAGE: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] +# [--hexdump] [--max-buffer-size SIZE] # # Licensed under the Apache License, Version 2.0 (the "License") # @@ -23,17 +24,23 @@ import textwrap examples = """examples: ./sslsniff # sniff OpenSSL and GnuTLS functions ./sslsniff -p 181 # sniff PID 181 only + ./sslsniff -u 1000 # sniff only UID 1000 ./sslsniff -c curl # sniff curl command only ./sslsniff --no-openssl # don't show OpenSSL calls ./sslsniff --no-gnutls # don't show GnuTLS calls ./sslsniff --no-nss # don't show NSS calls ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8 + ./sslsniff -x # show process UID and TID """ parser = argparse.ArgumentParser( description="Sniff SSL data", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=examples) parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.") +parser.add_argument("-u", "--uid", type=int, default=None, + help="sniff this UID only.") +parser.add_argument("-x", "--extra", action="store_true", + help="show extra fields (UID, TID)") parser.add_argument("-c", "--comm", help="sniff only commands matching string.") parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl", @@ -48,6 +55,8 @@ parser.add_argument("--ebpf", action="store_true", help=argparse.SUPPRESS) parser.add_argument("--hexdump", action="store_true", dest="hexdump", help="show data as hexdump instead of trying to decode it as UTF-8") +parser.add_argument('--max-buffer-size', type=int, default=8192, + help='Size of captured buffer') args = parser.parse_args() @@ -55,34 +64,58 @@ prog = """ #include #include /* For TASK_COMM_LEN */ +#define MAX_BUF_SIZE __MAX_BUF_SIZE__ + struct probe_SSL_data_t { u64 timestamp_ns; u32 pid; - char comm[TASK_COMM_LEN]; - char v0[464]; + u32 tid; + u32 uid; u32 len; + int buf_filled; + char comm[TASK_COMM_LEN]; + u8 buf[MAX_BUF_SIZE]; }; +#define BASE_EVENT_SIZE ((size_t)(&((struct probe_SSL_data_t*)0)->buf)) +#define EVENT_SIZE(X) (BASE_EVENT_SIZE + ((size_t)(X))) + + +BPF_PERCPU_ARRAY(ssl_data, struct probe_SSL_data_t, 1); BPF_PERF_OUTPUT(perf_SSL_write); int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) { + int ret; + u32 zero = 0; u64 pid_tgid = bpf_get_current_pid_tgid(); u32 pid = pid_tgid >> 32; + u32 tid = pid_tgid; + u32 uid = bpf_get_current_uid_gid(); - FILTER + PID_FILTER + UID_FILTER + struct probe_SSL_data_t *data = ssl_data.lookup(&zero); + if (!data) + return 0; - struct probe_SSL_data_t __data = {0}; - __data.timestamp_ns = bpf_ktime_get_ns(); - __data.pid = pid; - __data.len = num; + data->timestamp_ns = bpf_ktime_get_ns(); + data->pid = pid; + data->tid = tid; + data->uid = uid; + data->len = num; + data->buf_filled = 0; + bpf_get_current_comm(&data->comm, sizeof(data->comm)); + u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)num); - bpf_get_current_comm(&__data.comm, sizeof(__data.comm)); + if (buf != 0) + ret = bpf_probe_read_user(data->buf, buf_copy_size, buf); - if ( buf != 0) { - bpf_probe_read_user(&__data.v0, sizeof(__data.v0), buf); - } + if (!ret) + data->buf_filled = 1; + else + buf_copy_size = 0; - perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data)); + perf_SSL_write.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size)); return 0; } @@ -94,47 +127,74 @@ int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) { 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(); - FILTER + PID_FILTER + UID_FILTER bufs.update(&tid, (u64*)&buf); return 0; } int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) { + 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(); + int ret; - FILTER + PID_FILTER + UID_FILTER u64 *bufp = bufs.lookup(&tid); - if (bufp == 0) { + if (bufp == 0) return 0; - } - struct probe_SSL_data_t __data = {0}; - __data.timestamp_ns = bpf_ktime_get_ns(); - __data.pid = pid; - __data.len = PT_REGS_RC(ctx); + int len = PT_REGS_RC(ctx); + if (len <= 0) // read failed + return 0; + + struct probe_SSL_data_t *data = ssl_data.lookup(&zero); + if (!data) + return 0; - bpf_get_current_comm(&__data.comm, sizeof(__data.comm)); + data->timestamp_ns = bpf_ktime_get_ns(); + data->pid = pid; + data->tid = tid; + data->uid = uid; + data->len = (u32)len; + data->buf_filled = 0; + u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len); - if (bufp != 0) { - bpf_probe_read_user(&__data.v0, sizeof(__data.v0), (char *)*bufp); - } + bpf_get_current_comm(&data->comm, sizeof(data->comm)); + + if (bufp != 0) + ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp); bufs.delete(&tid); - perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data)); + if (!ret) + data->buf_filled = 1; + else + buf_copy_size = 0; + + perf_SSL_read.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size)); return 0; } """ if args.pid: - prog = prog.replace('FILTER', 'if (pid != %d) { return 0; }' % args.pid) + prog = prog.replace('PID_FILTER', 'if (pid != %d) { return 0; }' % args.pid) else: - prog = prog.replace('FILTER', '') + prog = prog.replace('PID_FILTER', '') + +if args.uid is not None: + prog = prog.replace('UID_FILTER', 'if (uid != %d) { return 0; }' % args.uid) +else: + prog = prog.replace('UID_FILTER', '') + +prog = prog.replace('__MAX_BUF_SIZE__', str(args.max_buffer_size)) if args.debug or args.ebpf: print(prog) @@ -179,14 +239,15 @@ if args.nss: fn_name="probe_SSL_read_exit", pid=args.pid or -1) # define output data structure in Python -TASK_COMM_LEN = 16 # linux/sched.h -MAX_BUF_SIZE = 464 # Limited by the BPF stack # header -print("%-12s %-18s %-16s %-7s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID", - "LEN")) +header = "%-12s %-18s %-16s %-7s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID", "LEN") +if args.extra: + header += " %-7s %-7s" % ("UID", "TID") + +print(header) # process event start = 0 @@ -202,6 +263,16 @@ def print_event_read(cpu, data, size): def print_event(cpu, data, size, rw, evt): global start event = b[evt].event(data) + if event.len <= args.max_buffer_size: + buf_size = event.len + else: + buf_size = args.max_buffer_size + + if event.buf_filled == 1: + buf = bytearray(event.buf[:buf_size]) + else: + buf_size = 0 + buf = b"" # Filter events by command if args.comm: @@ -216,19 +287,38 @@ def print_event(cpu, data, size, rw, evt): e_mark = "-" * 5 + " END DATA " + "-" * 5 - truncated_bytes = event.len - MAX_BUF_SIZE + truncated_bytes = event.len - buf_size if truncated_bytes > 0: e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \ " bytes lost) " + "-" * 5 - fmt = "%-12s %-18.9f %-16s %-7d %-6d\n%s\n%s\n%s\n\n" + base_fmt = "%(func)-12s %(time)-18.9f %(comm)-16s %(pid)-7d %(len)-6d" + + if args.extra: + base_fmt += " %(uid)-7d %(tid)-7d" + + fmt = ''.join([base_fmt, "\n%(begin)s\n%(data)s\n%(end)s\n\n"]) if args.hexdump: - unwrapped_data = binascii.hexlify(event.v0) - data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'),width=32) + unwrapped_data = binascii.hexlify(buf) + data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'), width=32) else: - data = event.v0.decode('utf-8', 'replace') - print(fmt % (rw, time_s, event.comm.decode('utf-8', 'replace'), - event.pid, event.len, s_mark, data, e_mark)) + data = buf.decode('utf-8', 'replace') + + fmt_data = { + 'func': rw, + 'time': time_s, + 'comm': event.comm.decode('utf-8', 'replace'), + 'pid': event.pid, + 'tid': event.tid, + 'uid': event.uid, + 'len': event.len, + 'begin': s_mark, + 'end': e_mark, + 'data': data + } + + print(fmt % fmt_data) + b["perf_SSL_write"].open_perf_buffer(print_event_write) b["perf_SSL_read"].open_perf_buffer(print_event_read) diff --git a/tools/sslsniff_example.txt b/tools/sslsniff_example.txt index 360561f7..fa36c40d 100644 --- a/tools/sslsniff_example.txt +++ b/tools/sslsniff_example.txt @@ -105,13 +105,16 @@ characters. USAGE message: -usage: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-n] [-d] [--hexdump] +usage: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] + [--hexdump] [--max-buffer-size MAX_BUFFER_SIZE] Sniff SSL data optional arguments: -h, --help show this help message and exit -p PID, --pid PID sniff this PID only. + -u UID, --uid UID sniff this UID only. + -x, --extra show extra fields (UID, TID) -c COMM, --comm COMM sniff only commands matching string. -o, --no-openssl do not show OpenSSL calls. -g, --no-gnutls do not show GnuTLS calls. @@ -119,12 +122,16 @@ optional arguments: -d, --debug debug mode. --hexdump show data as hexdump instead of trying to decode it as UTF-8 + --max-buffer-size MAX_BUFFER_SIZE + Size of captured buffer examples: ./sslsniff # sniff OpenSSL and GnuTLS functions ./sslsniff -p 181 # sniff PID 181 only + ./sslsniff -u 1000 # sniff only UID 1000 ./sslsniff -c curl # sniff curl command only ./sslsniff --no-openssl # don't show OpenSSL calls ./sslsniff --no-gnutls # don't show GnuTLS calls ./sslsniff --no-nss # don't show NSS calls ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8 + ./sslsniff -x # show process UID and TID