forked from iovisor/bcc
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request iovisor#663 from adrianlzt/sniff_ssl_tool
Tool to sniff data contents before encrypted with OpenSSL
- Loading branch information
Showing
4 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
#!/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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
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 |