blob: 02b7360405986a1ebc7918ca2deb170b01e82fe7 [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örmannd40c3a72020-07-03 14:31:32 +020030 ./sslsniff --hexdump # 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örmannd91b31a2020-07-06 09:38:39 +020049parser.add_argument("--hexdump", action="store_true", dest="hexdump",
50 help="show data as hexdump instead of trying to decode it as UTF-8")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020051args = parser.parse_args()
52
Adrian Lopezd496d5c2016-08-16 17:49:49 +020053
54prog = """
55#include <linux/ptrace.h>
56#include <linux/sched.h> /* For TASK_COMM_LEN */
57
58struct probe_SSL_data_t {
59 u64 timestamp_ns;
60 u32 pid;
61 char comm[TASK_COMM_LEN];
Brenden Blanco71eb1772017-04-20 09:47:28 -070062 char v0[464];
Adrian Lopezd496d5c2016-08-16 17:49:49 +020063 u32 len;
64};
65
66BPF_PERF_OUTPUT(perf_SSL_write);
67
68int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Hengqi Chen151fe192021-05-16 17:18:27 +080069 u64 pid_tgid = bpf_get_current_pid_tgid();
70 u32 pid = pid_tgid >> 32;
71
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020072 FILTER
73
Adrian Lopezd496d5c2016-08-16 17:49:49 +020074 struct probe_SSL_data_t __data = {0};
75 __data.timestamp_ns = bpf_ktime_get_ns();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020076 __data.pid = pid;
Adrian Lopezd496d5c2016-08-16 17:49:49 +020077 __data.len = num;
78
79 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
80
81 if ( buf != 0) {
Sumanth Korikkar023154c2020-04-20 05:54:57 -050082 bpf_probe_read_user(&__data.v0, sizeof(__data.v0), buf);
Adrian Lopezd496d5c2016-08-16 17:49:49 +020083 }
84
85 perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data));
86 return 0;
87}
88
89BPF_PERF_OUTPUT(perf_SSL_read);
90
91BPF_HASH(bufs, u32, u64);
92
93int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Hengqi Chen151fe192021-05-16 17:18:27 +080094 u64 pid_tgid = bpf_get_current_pid_tgid();
95 u32 pid = pid_tgid >> 32;
96 u32 tid = (u32)pid_tgid;
97
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020098 FILTER
99
Hengqi Chen151fe192021-05-16 17:18:27 +0800100 bufs.update(&tid, (u64*)&buf);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200101 return 0;
102}
103
104int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Hengqi Chen151fe192021-05-16 17:18:27 +0800105 u64 pid_tgid = bpf_get_current_pid_tgid();
106 u32 pid = pid_tgid >> 32;
107 u32 tid = (u32)pid_tgid;
108
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200109 FILTER
110
Hengqi Chen151fe192021-05-16 17:18:27 +0800111 u64 *bufp = bufs.lookup(&tid);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200112 if (bufp == 0) {
113 return 0;
114 }
115
116 struct probe_SSL_data_t __data = {0};
117 __data.timestamp_ns = bpf_ktime_get_ns();
118 __data.pid = pid;
119 __data.len = PT_REGS_RC(ctx);
120
121 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
122
123 if (bufp != 0) {
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500124 bpf_probe_read_user(&__data.v0, sizeof(__data.v0), (char *)*bufp);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200125 }
126
Hengqi Chen151fe192021-05-16 17:18:27 +0800127 bufs.delete(&tid);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200128
129 perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data));
130 return 0;
131}
132"""
133
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200134if args.pid:
htbegin5ac5d6e2017-05-24 22:53:17 +0800135 prog = prog.replace('FILTER', 'if (pid != %d) { return 0; }' % args.pid)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200136else:
137 prog = prog.replace('FILTER', '')
138
Nathan Scottcf0792f2018-02-02 16:56:50 +1100139if args.debug or args.ebpf:
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200140 print(prog)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100141 if args.ebpf:
142 exit()
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200143
144
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200145b = BPF(text=prog)
146
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200147# It looks like SSL_read's arguments aren't available in a return probe so you
148# need to stash the buffer address in a map on the function entry and read it
149# on its exit (Mark Drayton)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200150#
151if args.openssl:
Paul Chaignond73c58f2017-01-21 14:25:41 +0100152 b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write",
153 pid=args.pid or -1)
154 b.attach_uprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_enter",
155 pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200156 b.attach_uretprobe(name="ssl", sym="SSL_read",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100157 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200158
159if args.gnutls:
160 b.attach_uprobe(name="gnutls", sym="gnutls_record_send",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100161 fn_name="probe_SSL_write", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200162 b.attach_uprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100163 fn_name="probe_SSL_read_enter", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200164 b.attach_uretprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100165 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200166
jeromemarchand8b17dc32018-08-04 07:09:36 +0200167if args.nss:
168 b.attach_uprobe(name="nspr4", sym="PR_Write", fn_name="probe_SSL_write",
169 pid=args.pid or -1)
170 b.attach_uprobe(name="nspr4", sym="PR_Send", fn_name="probe_SSL_write",
171 pid=args.pid or -1)
172 b.attach_uprobe(name="nspr4", sym="PR_Read", fn_name="probe_SSL_read_enter",
173 pid=args.pid or -1)
174 b.attach_uretprobe(name="nspr4", sym="PR_Read",
175 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
176 b.attach_uprobe(name="nspr4", sym="PR_Recv", fn_name="probe_SSL_read_enter",
177 pid=args.pid or -1)
178 b.attach_uretprobe(name="nspr4", sym="PR_Recv",
179 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
180
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200181# define output data structure in Python
182TASK_COMM_LEN = 16 # linux/sched.h
Brenden Blanco71eb1772017-04-20 09:47:28 -0700183MAX_BUF_SIZE = 464 # Limited by the BPF stack
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200184
185
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200186# header
Hengqi Chen151fe192021-05-16 17:18:27 +0800187print("%-12s %-18s %-16s %-7s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID",
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200188 "LEN"))
189
190# process event
191start = 0
192
193
194def print_event_write(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800195 print_event(cpu, data, size, "WRITE/SEND", "perf_SSL_write")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200196
197
198def print_event_read(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800199 print_event(cpu, data, size, "READ/RECV", "perf_SSL_read")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200200
201
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800202def print_event(cpu, data, size, rw, evt):
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200203 global start
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800204 event = b[evt].event(data)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200205
206 # Filter events by command
207 if args.comm:
keyolk49fdec62020-10-08 05:45:00 +0000208 if not args.comm == event.comm.decode('utf-8', 'replace'):
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200209 return
210
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200211 if start == 0:
212 start = event.timestamp_ns
213 time_s = (float(event.timestamp_ns - start)) / 1000000000
214
215 s_mark = "-" * 5 + " DATA " + "-" * 5
216
217 e_mark = "-" * 5 + " END DATA " + "-" * 5
218
219 truncated_bytes = event.len - MAX_BUF_SIZE
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200220 if truncated_bytes > 0:
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200221 e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \
222 " bytes lost) " + "-" * 5
223
Hengqi Chen151fe192021-05-16 17:18:27 +0800224 fmt = "%-12s %-18.9f %-16s %-7d %-6d\n%s\n%s\n%s\n\n"
Matthias Hörmannd91b31a2020-07-06 09:38:39 +0200225 if args.hexdump:
226 unwrapped_data = binascii.hexlify(event.v0)
227 data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'),width=32)
228 else:
229 data = event.v0.decode('utf-8', 'replace')
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200230 print(fmt % (rw, time_s, event.comm.decode('utf-8', 'replace'),
Matthias Hörmannd91b31a2020-07-06 09:38:39 +0200231 event.pid, event.len, s_mark, data, e_mark))
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200232
233b["perf_SSL_write"].open_perf_buffer(print_event_write)
234b["perf_SSL_read"].open_perf_buffer(print_event_read)
235while 1:
Jerome Marchand51671272018-12-19 01:57:24 +0100236 try:
237 b.perf_buffer_poll()
238 except KeyboardInterrupt:
239 exit()