Skip to content

Instantly share code, notes, and snippets.

@summerwind
Last active February 16, 2018 20:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save summerwind/21c853e5b19593e548dc908a8db82073 to your computer and use it in GitHub Desktop.
Save summerwind/21c853e5b19593e548dc908a8db82073 to your computer and use it in GitHub Desktop.
HTTP/2 frame sniffer
#!/usr/bin/python
# This code is modified version of sslsniff.
# https://github.com/iovisor/bcc/blob/master/tools/sslsniff.py
#
# Licensed under the Apache License, Version 2.0 (the "License")
from __future__ import print_function
import ctypes as ct
from bcc import BPF
import time
import argparse
import hyperframe.frame
# BPF code
BPF_CODE = """
#include <linux/ptrace.h>
#include <linux/sched.h>
struct probe_SSL_data_t {
u32 len;
char buf[500];
};
BPF_PERF_OUTPUT(perf_SSL_write);
BPF_PERF_OUTPUT(perf_SSL_read);
BPF_HASH(bufs, u32, u64);
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.len = num;
if (buf != 0) {
bpf_probe_read(&__data.buf, sizeof(__data.buf), buf);
}
perf_SSL_write.perf_submit(ctx, &__data, sizeof(__data));
return 0;
}
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.len = PT_REGS_RC(ctx);
if (bufp != 0) {
bpf_probe_read(&__data.buf, sizeof(__data.buf), (char *)*bufp);
}
bufs.delete(&pid);
perf_SSL_read.perf_submit(ctx, &__data, sizeof(__data));
return 0;
}
"""
FRAMES = [
"DATA",
"HEADERS",
"PRIORITY",
"RST_STREAM",
"SETTINGS",
"PUSH_PROMISE",
"PING",
"GOAWAY",
"WINDOW_UPDATE",
"CONTINUATION",
"ALT_SVC",
]
MAX_BUF_SIZE = 500
TIME_START = time.time()
class Data(ct.Structure):
_fields_ = [
("len", ct.c_uint),
("buf", ct.c_ubyte * MAX_BUF_SIZE),
]
def print_event_write(cpu, data, size):
print_event(cpu, data, size, "send")
def print_event_read(cpu, data, size):
print_event(cpu, data, size, "recv")
def print_event(cpu, data, size, rw):
global FRAMES, TIME_START
event = ct.cast(data, ct.POINTER(Data)).contents
data = event.buf[0:event.len]
if bytearray(data)[:24] == "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n":
data = data[24:]
while len(data) > 9:
try:
info = hyperframe.frame.Frame.parse_frame_header(bytearray(data)[:9])
except:
break
frame = info[0]
frame_len = info[1]
time_s = float(time.time() - TIME_START)
flags = ",".join(frame.flags)
last = 9 + frame_len
data = data[last:]
if flags == "":
print("[%-3.3f] %s %s frame <length=%d, stream_id=%d>" % (time_s, rw, FRAMES[frame.type], frame_len, frame.stream_id))
else:
print("[%-3.3f] %s %s frame <length=%d, stream_id=%d, flags=%s>" % (time_s, rw, FRAMES[frame.type], frame_len, frame.stream_id, flags))
examples = """examples:
./h2sniff # sniff OpenSSL and GnuTLS functions
./h2sniff -p 181 # sniff PID 181 only
./h2sniff -c curl # sniff curl command only
./h2sniff -b /usr/local/bin/h2o # sniff h2o binary
./h2sniff --no-openssl # don't show OpenSSL calls
./h2sniff --no-gnutls # don't show GnuTLS calls
"""
parser = argparse.ArgumentParser(
description="Sniff HTTP/2 frame",
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("-b", "--bin", help="sniff binary file.")
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()
if args.pid:
BPF_CODE = BPF_CODE.replace('FILTER', 'if (pid != %s) { return 0; }' % args.pid)
else:
BPF_CODE = BPF_CODE.replace('FILTER', '')
if args.debug:
print(BPF_CODE)
b = BPF(text=BPF_CODE)
if args.openssl:
name = "ssl"
if args.bin != None:
name = args.bin
try:
b.attach_uprobe(name=name, sym="SSL_write", fn_name="probe_SSL_write")
b.attach_uprobe(name=name, sym="SSL_read", fn_name="probe_SSL_read_enter")
b.attach_uretprobe(name=name, sym="SSL_read", fn_name="probe_SSL_read_exit")
print("OpenSSL: Enabled")
except:
pass
if args.gnutls:
name = "ssl"
if args.bin != None:
name = args.bin
try:
b.attach_uprobe(name=name, sym="gnutls_record_send", fn_name="probe_SSL_write")
b.attach_uprobe(name=name, sym="gnutls_record_recv", fn_name="probe_SSL_read_enter")
b.attach_uretprobe(name=name, sym="gnutls_record_recv", fn_name="probe_SSL_read_exit")
print("GnuTLS: Enabled")
except:
pass
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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment