Last active
February 16, 2018 20:13
-
-
Save summerwind/21c853e5b19593e548dc908a8db82073 to your computer and use it in GitHub Desktop.
HTTP/2 frame sniffer
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
#!/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