Commit 04e9598b authored by Brendan Gregg's avatar Brendan Gregg Committed by GitHub

Merge pull request #663 from adrianlzt/sniff_ssl_tool

Tool to sniff data contents before encrypted with OpenSSL
parents 6f89445a d9cc3de3
......@@ -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).
......
.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)
#!/usr/bin/python
#
# sslsniff Captures data on read/recv or write/send functions of OpenSSL and
# GnuTLS
# For Linux, uses BCC, eBPF.
#
# USAGE: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-d]
#
# 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
# 17-Aug-2016 Adrian Lopez Capture GnuTLS and add options
#
from __future__ import print_function
import ctypes as ct
from bcc import BPF
import argparse
# arguments
examples = """examples:
./sslsniff # sniff OpenSSL and GnuTLS functions
./sslsniff -p 181 # sniff PID 181 only
./sslsniff -c curl # sniff curl command only
./sslsniff --no-openssl # don't show OpenSSL calls
./sslsniff --no-gnutls # don't show GnuTLS calls
"""
parser = argparse.ArgumentParser(
description="Sniff SSL data",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=examples)
parser.add_argument("-p", "--pid", help="sniff this PID only.")
parser.add_argument("-c", "--comm",
help="sniff only commands matching string.")
parser.add_argument("-o", "--no-openssl", action="store_false", dest="openssl",
help="do not show OpenSSL calls.")
parser.add_argument("-g", "--no-gnutls", action="store_false", dest="gnutls",
help="do not show GnuTLS calls.")
parser.add_argument('-d', '--debug', dest='debug', action='count', default=0,
help='debug mode.')
args = parser.parse_args()
prog = """
#include <linux/ptrace.h>
#include <linux/sched.h> /* 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) {
u32 pid = bpf_get_current_pid_tgid();
FILTER
struct probe_SSL_data_t __data = {0};
__data.timestamp_ns = bpf_ktime_get_ns();
__data.pid = pid;
__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();
FILTER
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();
FILTER
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;
}
"""
if args.pid:
prog = prog.replace('FILTER', 'if (pid != %s) { return 0; }' % args.pid)
else:
prog = prog.replace('FILTER', '')
if args.debug:
print(prog)
b = BPF(text=prog)
# 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)
#
if args.openssl:
b.attach_uprobe(name="ssl", sym="SSL_write", fn_name="probe_SSL_write")
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")
if args.gnutls:
b.attach_uprobe(name="gnutls", sym="gnutls_record_send",
fn_name="probe_SSL_write")
b.attach_uprobe(name="gnutls", sym="gnutls_record_recv",
fn_name="probe_SSL_read_enter")
b.attach_uretprobe(name="gnutls", sym="gnutls_record_recv",
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, "WRITE/SEND")
def print_event_read(cpu, data, size):
print_event(cpu, data, size, "READ/RECV")
def print_event(cpu, data, size, rw):
global start
event = ct.cast(data, ct.POINTER(Data)).contents
# Filter events by command
if args.comm:
if not args.comm == event.comm:
return
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\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()
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 -----
<!doctype html>
<html>
<head>
<title>Example Domain</title>
<meta charset="utf-8" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
body {
background-color: #f0f0f2;
margin: 0;
padding: 0;
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
div {
w
----- END DATA (TRUNCATED, 798 bytes lost) -----
USAGE message:
usage: sslsniff.py [-h] [-p PID] [-c COMM] [-o] [-g] [-d]
Sniff SSL data
optional arguments:
-h, --help show this help message and exit
-p PID, --pid PID sniff this PID only.
-c COMM, --comm COMM sniff only commands matching string.
-o, --no-openssl do not show OpenSSL calls.
-g, --no-gnutls do not show GnuTLS calls.
-d, --debug debug mode.
examples:
./sslsniff # sniff OpenSSL and GnuTLS functions
./sslsniff -p 181 # sniff PID 181 only
./sslsniff -c curl # sniff curl command only
./sslsniff --no-openssl # don't show OpenSSL calls
./sslsniff --no-gnutls # don't show GnuTLS calls
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment