1#!/usr/bin/env python 2# 3# sslsniff Captures data on read/recv or write/send functions of OpenSSL, 4# GnuTLS and NSS 5# For Linux, uses BCC, eBPF. 6# 7# USAGE: sslsniff.py [-h] [-p PID] [-u UID] [-x] [-c COMM] [-o] [-g] [-n] [-d] 8# [--hexdump] [--max-buffer-size SIZE] [-l] [--handshake] 9# 10# Licensed under the Apache License, Version 2.0 (the "License") 11# 12# 12-Aug-2016 Adrian Lopez Created this. 13# 13-Aug-2016 Mark Drayton Fix SSL_Read 14# 17-Aug-2016 Adrian Lopez Capture GnuTLS and add options 15# 16 17from __future__ import print_function 18from bcc import BPF 19import argparse 20import binascii 21import textwrap 22import os.path 23 24# arguments 25examples = """examples: 26 ./sslsniff # sniff OpenSSL and GnuTLS functions 27 ./sslsniff -p 181 # sniff PID 181 only 28 ./sslsniff -u 1000 # sniff only UID 1000 29 ./sslsniff -c curl # sniff curl command only 30 ./sslsniff --no-openssl # don't show OpenSSL calls 31 ./sslsniff --no-gnutls # don't show GnuTLS calls 32 ./sslsniff --no-nss # don't show NSS calls 33 ./sslsniff --hexdump # show data as hex instead of trying to decode it as UTF-8 34 ./sslsniff -x # show process UID and TID 35 ./sslsniff -l # show function latency 36 ./sslsniff -l --handshake # show SSL handshake latency 37 ./sslsniff --extra-lib openssl:/path/libssl.so.1.1 # sniff extra library 38""" 39 40 41def ssllib_type(input_str): 42 valid_types = frozenset(['openssl', 'gnutls', 'nss']) 43 44 try: 45 lib_type, lib_path = input_str.split(':', 1) 46 except ValueError: 47 raise argparse.ArgumentTypeError("Invalid SSL library param: %r" % input_str) 48 49 if lib_type not in valid_types: 50 raise argparse.ArgumentTypeError("Invalid SSL library type: %r" % lib_type) 51 52 if not os.path.isfile(lib_path): 53 raise argparse.ArgumentTypeError("Invalid library path: %r" % lib_path) 54 55 return lib_type, lib_path 56 57 58parser = argparse.ArgumentParser( 59 description="Sniff SSL data", 60 formatter_class=argparse.RawDescriptionHelpFormatter, 61 epilog=examples) 62parser.add_argument("-p", "--pid", type=int, help="sniff this PID only.") 63parser.add_argument("-u", "--uid", type=int, default=None, 64 help="sniff this UID only.") 65parser.add_argument("-x", "--extra", action="store_true", 66 help="show extra fields (UID, TID)") 67parser.add_argument("-c", "--comm", 68 help="sniff only commands matching string.") 69parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl", 70 help="do not show OpenSSL calls.") 71parser.add_argument("-g", "--no-gnutls", action="store_false", dest="gnutls", 72 help="do not show GnuTLS calls.") 73parser.add_argument("-n", "--no-nss", action="store_false", dest="nss", 74 help="do not show NSS calls.") 75parser.add_argument('-d', '--debug', dest='debug', action='count', default=0, 76 help='debug mode.') 77parser.add_argument("--ebpf", action="store_true", 78 help=argparse.SUPPRESS) 79parser.add_argument("--hexdump", action="store_true", dest="hexdump", 80 help="show data as hexdump instead of trying to decode it as UTF-8") 81parser.add_argument('--max-buffer-size', type=int, default=8192, 82 help='Size of captured buffer') 83parser.add_argument("-l", "--latency", action="store_true", 84 help="show function latency") 85parser.add_argument("--handshake", action="store_true", 86 help="show SSL handshake latency, enabled only if latency option is on.") 87parser.add_argument("--extra-lib", type=ssllib_type, action='append', 88 help="Intercept calls from extra library (format: lib_type:lib_path)") 89args = parser.parse_args() 90 91 92prog = """ 93#include <linux/ptrace.h> 94#include <linux/sched.h> /* For TASK_COMM_LEN */ 95 96#define MAX_BUF_SIZE __MAX_BUF_SIZE__ 97 98struct probe_SSL_data_t { 99 u64 timestamp_ns; 100 u64 delta_ns; 101 u32 pid; 102 u32 tid; 103 u32 uid; 104 u32 len; 105 int buf_filled; 106 int rw; 107 char comm[TASK_COMM_LEN]; 108 u8 buf[MAX_BUF_SIZE]; 109}; 110 111#define BASE_EVENT_SIZE ((size_t)(&((struct probe_SSL_data_t*)0)->buf)) 112#define EVENT_SIZE(X) (BASE_EVENT_SIZE + ((size_t)(X))) 113 114BPF_PERCPU_ARRAY(ssl_data, struct probe_SSL_data_t, 1); 115BPF_PERF_OUTPUT(perf_SSL_rw); 116 117BPF_HASH(start_ns, u32); 118BPF_HASH(bufs, u32, u64); 119 120int probe_SSL_rw_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) { 121 int ret; 122 u32 zero = 0; 123 u64 pid_tgid = bpf_get_current_pid_tgid(); 124 u32 pid = pid_tgid >> 32; 125 u32 tid = pid_tgid; 126 u32 uid = bpf_get_current_uid_gid(); 127 u64 ts = bpf_ktime_get_ns(); 128 129 PID_FILTER 130 UID_FILTER 131 132 bufs.update(&tid, (u64*)&buf); 133 start_ns.update(&tid, &ts); 134 return 0; 135} 136 137static int SSL_exit(struct pt_regs *ctx, int rw) { 138 int ret; 139 u32 zero = 0; 140 u64 pid_tgid = bpf_get_current_pid_tgid(); 141 u32 pid = pid_tgid >> 32; 142 u32 tid = (u32)pid_tgid; 143 u32 uid = bpf_get_current_uid_gid(); 144 u64 ts = bpf_ktime_get_ns(); 145 146 PID_FILTER 147 UID_FILTER 148 149 u64 *bufp = bufs.lookup(&tid); 150 if (bufp == 0) 151 return 0; 152 153 u64 *tsp = start_ns.lookup(&tid); 154 if (tsp == 0) 155 return 0; 156 157 int len = PT_REGS_RC(ctx); 158 if (len <= 0) // no data 159 return 0; 160 161 struct probe_SSL_data_t *data = ssl_data.lookup(&zero); 162 if (!data) 163 return 0; 164 165 data->timestamp_ns = ts; 166 data->delta_ns = ts - *tsp; 167 data->pid = pid; 168 data->tid = tid; 169 data->uid = uid; 170 data->len = (u32)len; 171 data->buf_filled = 0; 172 data->rw = rw; 173 u32 buf_copy_size = min((size_t)MAX_BUF_SIZE, (size_t)len); 174 175 bpf_get_current_comm(&data->comm, sizeof(data->comm)); 176 177 if (bufp != 0) 178 ret = bpf_probe_read_user(&data->buf, buf_copy_size, (char *)*bufp); 179 180 bufs.delete(&tid); 181 start_ns.delete(&tid); 182 183 if (!ret) 184 data->buf_filled = 1; 185 else 186 buf_copy_size = 0; 187 188 perf_SSL_rw.perf_submit(ctx, data, EVENT_SIZE(buf_copy_size)); 189 return 0; 190} 191 192int probe_SSL_read_exit(struct pt_regs *ctx) { 193 return (SSL_exit(ctx, 0)); 194} 195 196int probe_SSL_write_exit(struct pt_regs *ctx) { 197 return (SSL_exit(ctx, 1)); 198} 199 200BPF_PERF_OUTPUT(perf_SSL_do_handshake); 201 202int probe_SSL_do_handshake_enter(struct pt_regs *ctx, void *ssl) { 203 u64 pid_tgid = bpf_get_current_pid_tgid(); 204 u32 pid = pid_tgid >> 32; 205 u32 tid = (u32)pid_tgid; 206 u64 ts = bpf_ktime_get_ns(); 207 u32 uid = bpf_get_current_uid_gid(); 208 209 PID_FILTER 210 UID_FILTER 211 212 start_ns.update(&tid, &ts); 213 return 0; 214} 215 216int probe_SSL_do_handshake_exit(struct pt_regs *ctx) { 217 u32 zero = 0; 218 u64 pid_tgid = bpf_get_current_pid_tgid(); 219 u32 pid = pid_tgid >> 32; 220 u32 tid = (u32)pid_tgid; 221 u32 uid = bpf_get_current_uid_gid(); 222 u64 ts = bpf_ktime_get_ns(); 223 int ret; 224 225 PID_FILTER 226 UID_FILTER 227 228 u64 *tsp = start_ns.lookup(&tid); 229 if (tsp == 0) 230 return 0; 231 232 ret = PT_REGS_RC(ctx); 233 if (ret <= 0) // handshake failed 234 return 0; 235 236 struct probe_SSL_data_t *data = ssl_data.lookup(&zero); 237 if (!data) 238 return 0; 239 240 data->timestamp_ns = ts; 241 data->delta_ns = ts - *tsp; 242 data->pid = pid; 243 data->tid = tid; 244 data->uid = uid; 245 data->len = ret; 246 data->buf_filled = 0; 247 data->rw = 2; 248 bpf_get_current_comm(&data->comm, sizeof(data->comm)); 249 start_ns.delete(&tid); 250 251 perf_SSL_do_handshake.perf_submit(ctx, data, EVENT_SIZE(0)); 252 return 0; 253} 254""" 255 256if args.pid: 257 prog = prog.replace('PID_FILTER', 'if (pid != %d) { return 0; }' % args.pid) 258else: 259 prog = prog.replace('PID_FILTER', '') 260 261if args.uid is not None: 262 prog = prog.replace('UID_FILTER', 'if (uid != %d) { return 0; }' % args.uid) 263else: 264 prog = prog.replace('UID_FILTER', '') 265 266prog = prog.replace('__MAX_BUF_SIZE__', str(args.max_buffer_size)) 267 268if args.debug or args.ebpf: 269 print(prog) 270 if args.ebpf: 271 exit() 272 273 274b = BPF(text=prog) 275 276# It looks like SSL_read's arguments aren't available in a return probe so you 277# need to stash the buffer address in a map on the function entry and read it 278# on its exit (Mark Drayton) 279# 280def attach_openssl(lib): 281 b.attach_uprobe(name=lib, sym="SSL_write", 282 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 283 b.attach_uretprobe(name=lib, sym="SSL_write", 284 fn_name="probe_SSL_write_exit", pid=args.pid or -1) 285 b.attach_uprobe(name=lib, sym="SSL_read", 286 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 287 b.attach_uretprobe(name=lib, sym="SSL_read", 288 fn_name="probe_SSL_read_exit", pid=args.pid or -1) 289 if args.latency and args.handshake: 290 b.attach_uprobe(name="ssl", sym="SSL_do_handshake", 291 fn_name="probe_SSL_do_handshake_enter", pid=args.pid or -1) 292 b.attach_uretprobe(name="ssl", sym="SSL_do_handshake", 293 fn_name="probe_SSL_do_handshake_exit", pid=args.pid or -1) 294 295def attach_gnutls(lib): 296 b.attach_uprobe(name=lib, sym="gnutls_record_send", 297 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 298 b.attach_uretprobe(name=lib, sym="gnutls_record_send", 299 fn_name="probe_SSL_write_exit", pid=args.pid or -1) 300 b.attach_uprobe(name=lib, sym="gnutls_record_recv", 301 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 302 b.attach_uretprobe(name=lib, sym="gnutls_record_recv", 303 fn_name="probe_SSL_read_exit", pid=args.pid or -1) 304 305def attach_nss(lib): 306 b.attach_uprobe(name=lib, sym="PR_Write", 307 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 308 b.attach_uretprobe(name=lib, sym="PR_Write", 309 fn_name="probe_SSL_write_exit", pid=args.pid or -1) 310 b.attach_uprobe(name=lib, sym="PR_Send", 311 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 312 b.attach_uretprobe(name=lib, sym="PR_Send", 313 fn_name="probe_SSL_write_exit", pid=args.pid or -1) 314 b.attach_uprobe(name=lib, sym="PR_Read", 315 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 316 b.attach_uretprobe(name=lib, sym="PR_Read", 317 fn_name="probe_SSL_read_exit", pid=args.pid or -1) 318 b.attach_uprobe(name=lib, sym="PR_Recv", 319 fn_name="probe_SSL_rw_enter", pid=args.pid or -1) 320 b.attach_uretprobe(name=lib, sym="PR_Recv", 321 fn_name="probe_SSL_read_exit", pid=args.pid or -1) 322 323 324LIB_TRACERS = { 325 "openssl": attach_openssl, 326 "gnutls": attach_gnutls, 327 "nss": attach_nss, 328} 329 330 331if args.openssl: 332 attach_openssl("ssl") 333if args.gnutls: 334 attach_gnutls("gnutls") 335if args.nss: 336 attach_nss("nspr4") 337 338 339if args.extra_lib: 340 for lib_type, lib_path in args.extra_lib: 341 LIB_TRACERS[lib_type](lib_path) 342 343# define output data structure in Python 344 345 346# header 347header = "%-12s %-18s %-16s %-7s %-7s" % ("FUNC", "TIME(s)", "COMM", "PID", "LEN") 348 349if args.extra: 350 header += " %-7s %-7s" % ("UID", "TID") 351 352if args.latency: 353 header += " %-7s" % ("LAT(ms)") 354 355print(header) 356# process event 357start = 0 358 359def print_event_rw(cpu, data, size): 360 print_event(cpu, data, size, "perf_SSL_rw") 361 362def print_event_handshake(cpu, data, size): 363 print_event(cpu, data, size, "perf_SSL_do_handshake") 364 365def print_event(cpu, data, size, evt): 366 global start 367 event = b[evt].event(data) 368 if event.len <= args.max_buffer_size: 369 buf_size = event.len 370 else: 371 buf_size = args.max_buffer_size 372 373 if event.buf_filled == 1: 374 buf = bytearray(event.buf[:buf_size]) 375 else: 376 buf_size = 0 377 buf = b"" 378 379 # Filter events by command 380 if args.comm: 381 if not args.comm == event.comm.decode('utf-8', 'replace'): 382 return 383 384 if start == 0: 385 start = event.timestamp_ns 386 time_s = (float(event.timestamp_ns - start)) / 1000000000 387 388 lat_str = "%.3f" % (event.delta_ns / 1000000) if event.delta_ns else "N/A" 389 390 s_mark = "-" * 5 + " DATA " + "-" * 5 391 392 e_mark = "-" * 5 + " END DATA " + "-" * 5 393 394 truncated_bytes = event.len - buf_size 395 if truncated_bytes > 0: 396 e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \ 397 " bytes lost) " + "-" * 5 398 399 base_fmt = "%(func)-12s %(time)-18.9f %(comm)-16s %(pid)-7d %(len)-6d" 400 401 if args.extra: 402 base_fmt += " %(uid)-7d %(tid)-7d" 403 404 if args.latency: 405 base_fmt += " %(lat)-7s" 406 407 fmt = ''.join([base_fmt, "\n%(begin)s\n%(data)s\n%(end)s\n\n"]) 408 if args.hexdump: 409 unwrapped_data = binascii.hexlify(buf) 410 data = textwrap.fill(unwrapped_data.decode('utf-8', 'replace'), width=32) 411 else: 412 data = buf.decode('utf-8', 'replace') 413 414 rw_event = { 415 0: "READ/RECV", 416 1: "WRITE/SEND", 417 2: "HANDSHAKE" 418 } 419 420 fmt_data = { 421 'func': rw_event[event.rw], 422 'time': time_s, 423 'lat': lat_str, 424 'comm': event.comm.decode('utf-8', 'replace'), 425 'pid': event.pid, 426 'tid': event.tid, 427 'uid': event.uid, 428 'len': event.len, 429 'begin': s_mark, 430 'end': e_mark, 431 'data': data 432 } 433 434 # use base_fmt if no buf filled 435 if buf_size == 0: 436 print(base_fmt % fmt_data) 437 else: 438 print(fmt % fmt_data) 439 440b["perf_SSL_rw"].open_perf_buffer(print_event_rw) 441b["perf_SSL_do_handshake"].open_perf_buffer(print_event_handshake) 442while 1: 443 try: 444 b.perf_buffer_poll() 445 except KeyboardInterrupt: 446 exit() 447