blob: a81b4a49bc880fc254d9429851cdae55c3f4d2b8 [file] [log] [blame]
Adrian Lopezd496d5c2016-08-16 17:49:49 +02001#!/usr/bin/python
2#
Adrian Lopezd9cc3de2016-08-17 14:08:08 +02003# sslsniff Captures data on read/recv or write/send functions of OpenSSL and
4# GnuTLS
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
17import ctypes as ct
18from bcc import BPF
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020019import argparse
20
21# arguments
22examples = """examples:
23 ./sslsniff # sniff OpenSSL and GnuTLS functions
24 ./sslsniff -p 181 # sniff PID 181 only
25 ./sslsniff -c curl # sniff curl command only
26 ./sslsniff --no-openssl # don't show OpenSSL calls
27 ./sslsniff --no-gnutls # don't show GnuTLS calls
28"""
29parser = argparse.ArgumentParser(
30 description="Sniff SSL data",
31 formatter_class=argparse.RawDescriptionHelpFormatter,
32 epilog=examples)
htbegin5ac5d6e2017-05-24 22:53:17 +080033parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.")
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020034parser.add_argument("-c", "--comm",
35 help="sniff only commands matching string.")
36parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl",
37 help="do not show OpenSSL calls.")
38parser.add_argument("-g", "--no-gnutls", action="store_false", dest="gnutls",
39 help="do not show GnuTLS calls.")
40parser.add_argument('-d', '--debug', dest='debug', action='count', default=0,
41 help='debug mode.')
42args = parser.parse_args()
43
Adrian Lopezd496d5c2016-08-16 17:49:49 +020044
45prog = """
46#include <linux/ptrace.h>
47#include <linux/sched.h> /* For TASK_COMM_LEN */
48
49struct probe_SSL_data_t {
50 u64 timestamp_ns;
51 u32 pid;
52 char comm[TASK_COMM_LEN];
Brenden Blanco71eb1772017-04-20 09:47:28 -070053 char v0[464];
Adrian Lopezd496d5c2016-08-16 17:49:49 +020054 u32 len;
55};
56
57BPF_PERF_OUTPUT(perf_SSL_write);
58
59int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) {
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020060 u32 pid = bpf_get_current_pid_tgid();
61 FILTER
62
Adrian Lopezd496d5c2016-08-16 17:49:49 +020063 struct probe_SSL_data_t __data = {0};
64 __data.timestamp_ns = bpf_ktime_get_ns();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020065 __data.pid = pid;
Adrian Lopezd496d5c2016-08-16 17:49:49 +020066 __data.len = num;
67
68 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
69
70 if ( buf != 0) {
71 bpf_probe_read(&__data.v0, sizeof(__data.v0), buf);
72 }
73
74 perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data));
75 return 0;
76}
77
78BPF_PERF_OUTPUT(perf_SSL_read);
79
80BPF_HASH(bufs, u32, u64);
81
82int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) {
83 u32 pid = bpf_get_current_pid_tgid();
Adrian Lopezd9cc3de2016-08-17 14:08:08 +020084 FILTER
85
Adrian Lopezd496d5c2016-08-16 17:49:49 +020086 bufs.update(&pid, (u64*)&buf);
87 return 0;
88}
89
90int probe_SSL_read_exit(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 u64 *bufp = bufs.lookup(&pid);
95 if (bufp == 0) {
96 return 0;
97 }
98
99 struct probe_SSL_data_t __data = {0};
100 __data.timestamp_ns = bpf_ktime_get_ns();
101 __data.pid = pid;
102 __data.len = PT_REGS_RC(ctx);
103
104 bpf_get_current_comm(&__data.comm, sizeof(__data.comm));
105
106 if (bufp != 0) {
107 bpf_probe_read(&__data.v0, sizeof(__data.v0), (char *)*bufp);
108 }
109
110 bufs.delete(&pid);
111
112 perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data));
113 return 0;
114}
115"""
116
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200117if args.pid:
htbegin5ac5d6e2017-05-24 22:53:17 +0800118 prog = prog.replace('FILTER', 'if (pid != %d) { return 0; }' % args.pid)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200119else:
120 prog = prog.replace('FILTER', '')
121
122if args.debug:
123 print(prog)
124
125
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200126b = BPF(text=prog)
127
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200128# It looks like SSL_read's arguments aren't available in a return probe so you
129# need to stash the buffer address in a map on the function entry and read it
130# on its exit (Mark Drayton)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200131#
132if args.openssl:
Paul Chaignond73c58f2017-01-21 14:25:41 +0100133 b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write",
134 pid=args.pid or -1)
135 b.attach_uprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_enter",
136 pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200137 b.attach_uretprobe(name="ssl", sym="SSL_read",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100138 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200139
140if args.gnutls:
141 b.attach_uprobe(name="gnutls", sym="gnutls_record_send",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100142 fn_name="probe_SSL_write", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200143 b.attach_uprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100144 fn_name="probe_SSL_read_enter", pid=args.pid or -1)
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200145 b.attach_uretprobe(name="gnutls", sym="gnutls_record_recv",
Paul Chaignond73c58f2017-01-21 14:25:41 +0100146 fn_name="probe_SSL_read_exit", pid=args.pid or -1)
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200147
148# define output data structure in Python
149TASK_COMM_LEN = 16 # linux/sched.h
Brenden Blanco71eb1772017-04-20 09:47:28 -0700150MAX_BUF_SIZE = 464 # Limited by the BPF stack
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200151
152
153# Max size of the whole struct: 512 bytes
154class Data(ct.Structure):
155 _fields_ = [
156 ("timestamp_ns", ct.c_ulonglong),
157 ("pid", ct.c_uint),
158 ("comm", ct.c_char * TASK_COMM_LEN),
159 ("v0", ct.c_char * MAX_BUF_SIZE),
160 ("len", ct.c_uint)
161 ]
162
163
164# header
165print("%-12s %-18s %-16s %-6s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID",
166 "LEN"))
167
168# process event
169start = 0
170
171
172def print_event_write(cpu, data, size):
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200173 print_event(cpu, data, size, "WRITE/SEND")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200174
175
176def print_event_read(cpu, data, size):
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200177 print_event(cpu, data, size, "READ/RECV")
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200178
179
180def print_event(cpu, data, size, rw):
181 global start
182 event = ct.cast(data, ct.POINTER(Data)).contents
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200183
184 # Filter events by command
185 if args.comm:
186 if not args.comm == event.comm:
187 return
188
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200189 if start == 0:
190 start = event.timestamp_ns
191 time_s = (float(event.timestamp_ns - start)) / 1000000000
192
193 s_mark = "-" * 5 + " DATA " + "-" * 5
194
195 e_mark = "-" * 5 + " END DATA " + "-" * 5
196
197 truncated_bytes = event.len - MAX_BUF_SIZE
Adrian Lopezd9cc3de2016-08-17 14:08:08 +0200198 if truncated_bytes > 0:
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200199 e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \
200 " bytes lost) " + "-" * 5
201
Paul Chaignon7b911b52017-10-07 11:52:17 +0200202 fmt = "%-12s %-18.9f %-16s %-6d %-6d\n%s\n%s\n%s\n\n"
203 print(fmt % (rw, time_s, event.comm.decode(), event.pid, event.len, s_mark,
204 event.v0.decode(), e_mark))
Adrian Lopezd496d5c2016-08-16 17:49:49 +0200205
206b["perf_SSL_write"].open_perf_buffer(print_event_write)
207b["perf_SSL_read"].open_perf_buffer(print_event_read)
208while 1:
209 b.kprobe_poll()