blob: ef898f5ac37968c5ccf29bbd8457f088c648d3f1 [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#
Adrian Lopezd9cc3de2016-08-17 14:08:08 +02007# USAGE: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-d]
8#
Adrian Lopezd496d5c2016-08-16 17:49:49 +02009# Licensed under the Apache License, Version 2.0 (the "License")
10#
11# 12-Aug-2016 Adrian Lopez Created this.
12# 13-Aug-2016 Mark Drayton Fix SSL_Read
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020013# 17-Aug-2016 Adrian Lopez Capture GnuTLS and add options
14#
Adrian Lopezd496d5c2016-08-16 17:49:49 +020015
16from __future__ import print_function
Adrian Lopezd496d5c2016-08-16 17:49:49 +020017from bcc import BPF
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020018import argparse
Matthias Hörmann1b7aab12020-07-03 13:54:24 +020019import binascii
20import textwrap
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020021
22# arguments
23examples = """examples:
24 ./sslsniff # sniff OpenSSL and GnuTLS functions
25 ./sslsniff -p 181 # sniff PID 181 only
26 ./sslsniff -c curl # sniff curl command only
27 ./sslsniff --no-openssl # don't show OpenSSL calls
28 ./sslsniff --no-gnutls # don't show GnuTLS calls
jeromemarchand8b17dc32018-08-04 07:09:36 +020029 ./sslsniff --no-nss # don't show NSS calls
Matthias Hörmann1b7aab12020-07-03 13:54:24 +020030 ./sslsniff --hex # show data as hex instead of trying to decode it as UTF-8
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020031"""
32parser = argparse.ArgumentParser(
33 description="Sniff SSL data",
34 formatter_class=argparse.RawDescriptionHelpFormatter,
35 epilog=examples)
htbegin5ac5d6e2017-05-24 22:53:17 +080036parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020037parser.add_argument("-c", "--comm",
38 help="sniff only commands matching string.")
39parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl",
40 help="do not show OpenSSL calls.")
41parser.add_argument("-g", "--no-gnutls", action="store_false", dest="gnutls",
42 help="do not show GnuTLS calls.")
jeromemarchand8b17dc32018-08-04 07:09:36 +020043parser.add_argument("-n", "--no-nss", action="store_false", dest="nss",
44 help="do not show NSS calls.")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020045parser.add_argument('-d', '--debug', dest='debug', action='count', default=0,
46 help='debug mode.')
Nathan Scottcf0792f2018-02-02 16:56:50 +110047parser.add_argument("--ebpf", action="store_true",
48 help=argparse.SUPPRESS)
Matthias Hörmann1b7aab12020-07-03 13:54:24 +020049parser.add_argument("--hexdump", action="store_true", dest="hexdump", help="show data as hexdump instead of trying to decode it as UTF-8")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020050args = parser.parse_args()
51
Adrian Lopezd496d5c2016-08-16 17:49:49 +020052
53prog = """
54#include <linux/ptrace.h>
55#include <linux/sched.h> /* For TASK_COMM_LEN */
56
57struct probe_SSL_data_t {
58 u64 timestamp_ns;
59 u32 pid;
60 char comm[TASK_COMM_LEN];
Brenden Blanco71eb1772017-04-20 09:47:28 -070061 char v0[464];
Adrian Lopezd496d5c2016-08-16 17:49:49 +020062 u32 len;
63};
64
65BPF_PERF_OUTPUT(perf_SSL_write);
66
67int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020068 u32 pid = bpf_get_current_pid_tgid();
69 FILTER
70
Adrian Lopezd496d5c2016-08-16 17:49:49 +020071 struct probe_SSL_data_t __data = {0};
72 __data.timestamp_ns = bpf_ktime_get_ns();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020073 __data.pid = pid;
Adrian Lopezd496d5c2016-08-16 17:49:49 +020074 __data.len = num;
75
76 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
77
78 if ( buf != 0) {
Sumanth Korikkar023154c2020-04-20 05:54:57 -050079 bpf_probe_read_user(&__data.v0, sizeof(__data.v0), buf);
Adrian Lopezd496d5c2016-08-16 17:49:49 +020080 }
81
82 perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data));
83 return 0;
84}
85
86BPF_PERF_OUTPUT(perf_SSL_read);
87
88BPF_HASH(bufs, u32, u64);
89
90int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) {
91 u32 pid = bpf_get_current_pid_tgid();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020092 FILTER
93
Adrian Lopezd496d5c2016-08-16 17:49:49 +020094 bufs.update(&pid, (u64*)&buf);
95 return 0;
96}
97
98int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) {
99 u32 pid = bpf_get_current_pid_tgid();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200100 FILTER
101
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200102 u64 *bufp = bufs.lookup(&pid);
103 if (bufp == 0) {
104 return 0;
105 }
106
107 struct probe_SSL_data_t __data = {0};
108 __data.timestamp_ns = bpf_ktime_get_ns();
109 __data.pid = pid;
110 __data.len = PT_REGS_RC(ctx);
111
112 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
113
114 if (bufp != 0) {
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500115 bpf_probe_read_user(&__data.v0, sizeof(__data.v0), (char *)*bufp);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200116 }
117
118 bufs.delete(&pid);
119
120 perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data));
121 return 0;
122}
123"""
124
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200125if args.pid:
htbegin5ac5d6e2017-05-24 22:53:17 +0800126 prog = prog.replace('FILTER', 'if (pid != %d) { return 0; }' % args.pid)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200127else:
128 prog = prog.replace('FILTER', '')
129
Nathan Scottcf0792f2018-02-02 16:56:50 +1100130if args.debug or args.ebpf:
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200131 print(prog)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100132 if args.ebpf:
133 exit()
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200134
135
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200136b = BPF(text=prog)
137
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200138# It looks like SSL_read's arguments aren't available in a return probe so you
139# need to stash the buffer address in a map on the function entry and read it
140# on its exit (Mark Drayton)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200141#
142if args.openssl:
Paul Chaignond73c58f2017-01-21 14:25:41 +0100143 b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write",
144 pid=args.pid or -1)
145 b.attach_uprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_enter",
146 pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200147 b.attach_uretprobe(name="ssl", sym="SSL_read",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100148 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200149
150if args.gnutls:
151 b.attach_uprobe(name="gnutls", sym="gnutls_record_send",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100152 fn_name="probe_SSL_write", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200153 b.attach_uprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100154 fn_name="probe_SSL_read_enter", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200155 b.attach_uretprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100156 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200157
jeromemarchand8b17dc32018-08-04 07:09:36 +0200158if args.nss:
159 b.attach_uprobe(name="nspr4", sym="PR_Write", fn_name="probe_SSL_write",
160 pid=args.pid or -1)
161 b.attach_uprobe(name="nspr4", sym="PR_Send", fn_name="probe_SSL_write",
162 pid=args.pid or -1)
163 b.attach_uprobe(name="nspr4", sym="PR_Read", fn_name="probe_SSL_read_enter",
164 pid=args.pid or -1)
165 b.attach_uretprobe(name="nspr4", sym="PR_Read",
166 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
167 b.attach_uprobe(name="nspr4", sym="PR_Recv", fn_name="probe_SSL_read_enter",
168 pid=args.pid or -1)
169 b.attach_uretprobe(name="nspr4", sym="PR_Recv",
170 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
171
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200172# define output data structure in Python
173TASK_COMM_LEN = 16 # linux/sched.h
Brenden Blanco71eb1772017-04-20 09:47:28 -0700174MAX_BUF_SIZE = 464 # Limited by the BPF stack
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200175
176
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200177# header
178print("%-12s %-18s %-16s %-6s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID",
179 "LEN"))
180
181# process event
182start = 0
183
184
185def print_event_write(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800186 print_event(cpu, data, size, "WRITE/SEND", "perf_SSL_write")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200187
188
189def print_event_read(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800190 print_event(cpu, data, size, "READ/RECV", "perf_SSL_read")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200191
192
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800193def print_event(cpu, data, size, rw, evt):
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200194 global start
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800195 event = b[evt].event(data)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200196
197 # Filter events by command
198 if args.comm:
199 if not args.comm == event.comm:
200 return
201
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200202 if start == 0:
203 start = event.timestamp_ns
204 time_s = (float(event.timestamp_ns - start)) / 1000000000
205
206 s_mark = "-" * 5 + " DATA " + "-" * 5
207
208 e_mark = "-" * 5 + " END DATA " + "-" * 5
209
210 truncated_bytes = event.len - MAX_BUF_SIZE
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200211 if truncated_bytes > 0:
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200212 e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \
213 " bytes lost) " + "-" * 5
214
Paul Chaignon7b911b52017-10-07 11:52:17 +0200215 fmt = "%-12s %-18.9f %-16s %-6d %-6d\n%s\n%s\n%s\n\n"
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200216 print(fmt % (rw, time_s, event.comm.decode('utf-8', 'replace'),
217 event.pid, event.len, s_mark,
Matthias Hörmann1b7aab12020-07-03 13:54:24 +0200218 textwrap.fill(binascii.hexlify(event.v0).decode('utf-8', 'replace'),width=32) if args.hexdump else event.v0.decode('utf-8', 'replace'), e_mark))
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200219
220b["perf_SSL_write"].open_perf_buffer(print_event_write)
221b["perf_SSL_read"].open_perf_buffer(print_event_read)
222while 1:
Jerome Marchand51671272018-12-19 01:57:24 +0100223 try:
224 b.perf_buffer_poll()
225 except KeyboardInterrupt:
226 exit()