diff --git a/README.md b/README.md index 06f49de0f70e..5c746a924191 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ Examples: - tools/[runqlat](tools/runqlat.py): Run queue (scheduler) latency as a histogram. [Examples](tools/runqlat_example.txt). - tools/[softirqs](tools/softirqs.py): Measure soft IRQ (soft interrupt) event time. [Examples](tools/softirqs_example.txt). - tools/[solisten](tools/solisten.py): Trace TCP socket listen. [Examples](tools/solisten_example.txt). +- tools/[sslsniff](tools/sslsniff.py): Sniff OpenSSL written and readed data. [Examples](tools/sslsniff_example.txt). - tools/[stackcount](tools/stackcount.py): Count kernel function calls and their stack traces. [Examples](tools/stackcount_example.txt). - tools/[stacksnoop](tools/stacksnoop.py): Trace a kernel function and print all kernel stack traces. [Examples](tools/stacksnoop_example.txt). - tools/[statsnoop](tools/statsnoop.py): Trace stat() syscalls. [Examples](tools/statsnoop_example.txt). diff --git a/man/man8/sslsniff.8 b/man/man8/sslsniff.8 new file mode 100644 index 000000000000..e20e28a11680 --- /dev/null +++ b/man/man8/sslsniff.8 @@ -0,0 +1,51 @@ +.TH sslsniff 8 "2016-08-16" "USER COMMANDS" +.SH NAME +sslsniff \- Print data passed to OpenSSL. Uses Linux eBPF/bcc. +.SH SYNOPSIS +.B sslsniff +.SH DESCRIPTION +sslsniff prints data sent to SSL_write and SSL_read OpenSSL functions, allowing +us to read plain text content before encryption (when writing) and after +decryption (when reading). + +This works reading the second parameter of both functions (*buf). + +Since this uses BPF, only the root user can use this tool. +.SH REQUIREMENTS +CONFIG_BPF and bcc. +.SH EXAMPLES +.TP +Print all calls to SSL_write and SSL_read system-wide: +# +.B sslsniff +.SH FIELDS +.TP +FUNC +Which function is being called (SSL_write or SSL_read) +.TP +TIME +Time of the command, in seconds. +.TP +COMM +Entered command. +.TP +PID +Process ID calling OpenSSL. +.TP +LEN +Bytes written or read by OpenSSL functions. +.SH SOURCE +This is from bcc. +.IP +https://github.com/iovisor/bcc +.PP +Also look in the bcc distribution for a companion _examples.txt file containing +example usage, output, and commentary for this tool. +.SH OS +Linux +.SH STABILITY +Unstable - in development. +.SH AUTHORS +Adrian Lopez and Mark Drayton +.SH SEE ALSO +trace(8) diff --git a/tools/sslsniff.py b/tools/sslsniff.py new file mode 100755 index 000000000000..a89c6ef8a5dd --- /dev/null +++ b/tools/sslsniff.py @@ -0,0 +1,150 @@ +#!/usr/bin/python +# +# sslsniff Captures data on SSL_READ or SSL_WRITE functions of OpenSSL +# For Linux, uses BCC, eBPF. +# +# Licensed under the Apache License, Version 2.0 (the "License") +# +# 12-Aug-2016 Adrian Lopez Created this. +# 13-Aug-2016 Mark Drayton Fix SSL_Read + +from __future__ import print_function +import ctypes as ct +from bcc import BPF + +prog = """ +#include +#include /* For TASK_COMM_LEN */ + +struct probe_SSL_data_t { + u64 timestamp_ns; + u32 pid; + char comm[TASK_COMM_LEN]; + char v0[472]; + u32 len; +}; + +BPF_PERF_OUTPUT(perf_SSL_write); + +int probe_SSL_write(struct pt_regs *ctx, void *ssl, void *buf, int num) { + struct probe_SSL_data_t __data = {0}; + __data.timestamp_ns = bpf_ktime_get_ns(); + __data.pid = bpf_get_current_pid_tgid(); + __data.len = num; + + bpf_get_current_comm(&__data.comm, sizeof(__data.comm)); + + if ( buf != 0) { + bpf_probe_read(&__data.v0, sizeof(__data.v0), buf); + } + + perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data)); + return 0; +} + +BPF_PERF_OUTPUT(perf_SSL_read); + +BPF_HASH(bufs, u32, u64); + +int probe_SSL_read_enter(struct pt_regs *ctx, void *ssl, void *buf, int num) { + u32 pid = bpf_get_current_pid_tgid(); + bufs.update(&pid, (u64*)&buf); + return 0; +} + +int probe_SSL_read_exit(struct pt_regs *ctx, void *ssl, void *buf, int num) { + u32 pid = bpf_get_current_pid_tgid(); + u64 *bufp = bufs.lookup(&pid); + if (bufp == 0) { + return 0; + } + + struct probe_SSL_data_t __data = {0}; + __data.timestamp_ns = bpf_ktime_get_ns(); + __data.pid = pid; + __data.len = PT_REGS_RC(ctx); + + bpf_get_current_comm(&__data.comm, sizeof(__data.comm)); + + if (bufp != 0) { + bpf_probe_read(&__data.v0, sizeof(__data.v0), (char *)*bufp); + } + + bufs.delete(&pid); + + perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data)); + return 0; +} +""" + +b = BPF(text=prog) + +# Join to ssl_write +b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write") + +# Join to ssl_read +# It looks like SSL_read's arguments aren't available in a return probe so you +# need to stash the buffer address in a map on the function entry and read it +# on its exit (Mark Drayton) +b.attach_uprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_enter") +b.attach_uretprobe(name="ssl", sym="SSL_read", fn_name="probe_SSL_read_exit") + +# define output data structure in Python +TASK_COMM_LEN = 16 # linux/sched.h +MAX_BUF_SIZE = 472 # Limited by the BPF stack + + +# Max size of the whole struct: 512 bytes +class Data(ct.Structure): + _fields_ = [ + ("timestamp_ns", ct.c_ulonglong), + ("pid", ct.c_uint), + ("comm", ct.c_char * TASK_COMM_LEN), + ("v0", ct.c_char * MAX_BUF_SIZE), + ("len", ct.c_uint) + ] + + +# header +print("%-12s %-18s %-16s %-6s %-6s" % ("FUNC", "TIME(s)", "COMM", "PID", + "LEN")) + +# process event +start = 0 + + +def print_event_write(cpu, data, size): + print_event(cpu, data, size, "SSL_WRITE") + + +def print_event_read(cpu, data, size): + print_event(cpu, data, size, "SSL_READ") + + +def print_event(cpu, data, size, rw): + global start + event = ct.cast(data, ct.POINTER(Data)).contents + if start == 0: + start = event.timestamp_ns + time_s = (float(event.timestamp_ns - start)) / 1000000000 + + s_mark = "-" * 5 + " DATA " + "-" * 5 + + e_mark = "-" * 5 + " END DATA " + "-" * 5 + + truncated_bytes = event.len - MAX_BUF_SIZE + if truncated_bytes > 0 : + e_mark = "-" * 5 + " END DATA (TRUNCATED, " + str(truncated_bytes) + \ + " bytes lost) " + "-" * 5 + + print("%-12s %-18.9f %-16s %-6d %-6d\n%s\n%s\n%s\n" % (rw, time_s, + event.comm, + event.pid, + event.len, + s_mark, event.v0, + e_mark)) + +b["perf_SSL_write"].open_perf_buffer(print_event_write) +b["perf_SSL_read"].open_perf_buffer(print_event_read) +while 1: + b.kprobe_poll() diff --git a/tools/sslsniff_example.txt b/tools/sslsniff_example.txt new file mode 100644 index 000000000000..596b01c7ea60 --- /dev/null +++ b/tools/sslsniff_example.txt @@ -0,0 +1,61 @@ +Demonstrations of sslsniff.py + + +This tool traces the OpenSSL functions SSL_READ and SSL_WRITE. +Data passed to this functions is printed as plain text. +Useful, for example, to sniff HTTP before encrypted with SSL. + + +Output of tool executing in other shell "curl https://example.com" + +% sudo python sslsniff.py +FUNC TIME(s) COMM PID LEN +SSL_WRITE 0.000000000 curl 12915 75 +----- DATA ----- +GET / HTTP/1.1 +Host: example.com +User-Agent: curl/7.50.1 +Accept: */* + + +----- END DATA ----- + +SSL_READ 0.127144585 curl 12915 333 +----- DATA ----- +HTTP/1.1 200 OK +Cache-Control: max-age=604800 +Content-Type: text/html +Date: Tue, 16 Aug 2016 15:42:12 GMT +Etag: "359670651+gzip+ident" +Expires: Tue, 23 Aug 2016 15:42:12 GMT +Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT +Server: ECS (iad/18CB) +Vary: Accept-Encoding +X-Cache: HIT +x-ec-custom-error: 1 +Content-Length: 1270 + + +----- END DATA ----- + +SSL_READ 0.129967972 curl 12915 1270 +----- DATA ----- + + + + Example Domain + + + + +