blob: 0200750fb4496f7c4a9b3b180c3835eb0a590b97 [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) {
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020069 u32 pid = bpf_get_current_pid_tgid();
70 FILTER
71
Adrian Lopezd496d5c2016-08-16 17:49:49 +020072 struct probe_SSL_data_t __data = {0};
73 __data.timestamp_ns = bpf_ktime_get_ns();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020074 __data.pid = pid;
Adrian Lopezd496d5c2016-08-16 17:49:49 +020075 __data.len = num;
76
77 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
78
79 if ( buf != 0) {
Sumanth Korikkar023154c2020-04-20 05:54:57 -050080 bpf_probe_read_user(&__data.v0, sizeof(__data.v0), buf);
Adrian Lopezd496d5c2016-08-16 17:49:49 +020081 }
82
83 perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data));
84 return 0;
85}
86
87BPF_PERF_OUTPUT(perf_SSL_read);
88
89BPF_HASH(bufs, u32, u64);
90
91int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) {
92 u32 pid = bpf_get_current_pid_tgid();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020093 FILTER
94
Adrian Lopezd496d5c2016-08-16 17:49:49 +020095 bufs.update(&pid, (u64*)&buf);
96 return 0;
97}
98
99int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) {
100 u32 pid = bpf_get_current_pid_tgid();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200101 FILTER
102
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200103 u64 *bufp = bufs.lookup(&pid);
104 if (bufp == 0) {
105 return 0;
106 }
107
108 struct probe_SSL_data_t __data = {0};
109 __data.timestamp_ns = bpf_ktime_get_ns();
110 __data.pid = pid;
111 __data.len = PT_REGS_RC(ctx);
112
113 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
114
115 if (bufp != 0) {
Sumanth Korikkar023154c2020-04-20 05:54:57 -0500116 bpf_probe_read_user(&__data.v0, sizeof(__data.v0), (char *)*bufp);
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200117 }
118
119 bufs.delete(&pid);
120
121 perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data));
122 return 0;
123}
124"""
125
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200126if args.pid:
htbegin5ac5d6e2017-05-24 22:53:17 +0800127 prog = prog.replace('FILTER', 'if (pid != %d) { return 0; }' % args.pid)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200128else:
129 prog = prog.replace('FILTER', '')
130
Nathan Scottcf0792f2018-02-02 16:56:50 +1100131if args.debug or args.ebpf:
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200132 print(prog)
Nathan Scottcf0792f2018-02-02 16:56:50 +1100133 if args.ebpf:
134 exit()
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200135
136
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200137b = BPF(text=prog)
138
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200139# It looks like SSL_read's arguments aren't available in a return probe so you
140# need to stash the buffer address in a map on the function entry and read it
141# on its exit (Mark Drayton)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200142#
143if args.openssl:
Paul Chaignond73c58f2017-01-21 14:25:41 +0100144 b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write",
145 pid=args.pid or -1)
146 b.attach_uprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_enter",
147 pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200148 b.attach_uretprobe(name="ssl", sym="SSL_read",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100149 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200150
151if args.gnutls:
152 b.attach_uprobe(name="gnutls", sym="gnutls_record_send",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100153 fn_name="probe_SSL_write", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200154 b.attach_uprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100155 fn_name="probe_SSL_read_enter", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200156 b.attach_uretprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100157 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200158
jeromemarchand8b17dc32018-08-04 07:09:36 +0200159if args.nss:
160 b.attach_uprobe(name="nspr4", sym="PR_Write", fn_name="probe_SSL_write",
161 pid=args.pid or -1)
162 b.attach_uprobe(name="nspr4", sym="PR_Send", fn_name="probe_SSL_write",
163 pid=args.pid or -1)
164 b.attach_uprobe(name="nspr4", sym="PR_Read", fn_name="probe_SSL_read_enter",
165 pid=args.pid or -1)
166 b.attach_uretprobe(name="nspr4", sym="PR_Read",
167 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
168 b.attach_uprobe(name="nspr4", sym="PR_Recv", fn_name="probe_SSL_read_enter",
169 pid=args.pid or -1)
170 b.attach_uretprobe(name="nspr4", sym="PR_Recv",
171 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
172
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200173# define output data structure in Python
174TASK_COMM_LEN = 16 # linux/sched.h
Brenden Blanco71eb1772017-04-20 09:47:28 -0700175MAX_BUF_SIZE = 464 # Limited by the BPF stack
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200176
177
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200178# header
179print("%-12s %-18s %-16s %-6s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID",
180 "LEN"))
181
182# process event
183start = 0
184
185
186def print_event_write(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800187 print_event(cpu, data, size, "WRITE/SEND", "perf_SSL_write")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200188
189
190def print_event_read(cpu, data, size):
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800191 print_event(cpu, data, size, "READ/RECV", "perf_SSL_read")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200192
193
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800194def print_event(cpu, data, size, rw, evt):
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200195 global start
Xiaozhou Liu51d62d32019-02-15 13:03:05 +0800196 event = b[evt].event(data)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200197
198 # Filter events by command
199 if args.comm:
keyolk49fdec62020-10-08 05:45:00 +0000200 if not args.comm == event.comm.decode('utf-8', 'replace'):
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200201 return
202
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200203 if start == 0:
204 start = event.timestamp_ns
205 time_s = (float(event.timestamp_ns - start)) / 1000000000
206
207 s_mark = "-" * 5 + " DATA " + "-" * 5
208
209 e_mark = "-" * 5 + " END DATA " + "-" * 5
210
211 truncated_bytes = event.len - MAX_BUF_SIZE
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200212 if truncated_bytes > 0:
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200213 e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \
214 " bytes lost) " + "-" * 5
215
Paul Chaignon7b911b52017-10-07 11:52:17 +0200216 fmt = "%-12s %-18.9f %-16s %-6d %-6d\n%s\n%s\n%s\n\n"
Matthias Hörmannd91b31a2020-07-06 09:38:39 +0200217 if args.hexdump:
218 unwrapped_data = binascii.hexlify(event.v0)
219 data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'),width=32)
220 else:
221 data = event.v0.decode('utf-8', 'replace')
jeromemarchandb96ebcd2018-10-10 01:58:15 +0200222 print(fmt % (rw, time_s, event.comm.decode('utf-8', 'replace'),
Matthias Hörmannd91b31a2020-07-06 09:38:39 +0200223 event.pid, event.len, s_mark, data, e_mark))
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200224
225b["perf_SSL_write"].open_perf_buffer(print_event_write)
226b["perf_SSL_read"].open_perf_buffer(print_event_read)
227while 1:
Jerome Marchand51671272018-12-19 01:57:24 +0100228 try:
229 b.perf_buffer_poll()
230 except KeyboardInterrupt:
231 exit()