Skip to content

Commit

Permalink
Tool to sniff data contents before encrypted with OpenSSL
Browse files Browse the repository at this point in the history
Add tool as talked in iovisor-dev 'BCC: bpf_probe_read read function arguments'
  • Loading branch information
adrianlzt committed Aug 16, 2016
1 parent 6f89445 commit d496d5c
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
51 changes: 51 additions & 0 deletions man/man8/sslsniff.8
Original file line number Diff line number Diff line change
@@ -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)
150 changes: 150 additions & 0 deletions tools/sslsniff.py
Original file line number Diff line number Diff line change
@@ -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 <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) {
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()
61 changes: 61 additions & 0 deletions tools/sslsniff_example.txt
Original file line number Diff line number Diff line change
@@ -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 -----
<!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) -----

0 comments on commit d496d5c

Please sign in to comment.