diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d36451b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Guillaume Valadon + +FROM ubuntu:20.04 + +ENV TZ=Europe/Paris DEBIAN_FRONTEND=noninteractive +RUN echo 'PS1="quarkslab/peetch:\w# "' >> /root/.bashrc + +# Install dependencies +RUN set -x && \ + PACKAGES="bison build-essential cmake flex git \ + libedit-dev libllvm11 llvm-11-dev libclang-11-dev python zlib1g-dev \ + libelf-dev libfl-dev python3-distutils python3-pip linux-headers-$(uname -r) \ + libssl-dev iproute2 tmux curl" &&\ + apt-get update && apt-get install -y $PACKAGES && \ + apt-get autoclean && apt-get --purge -y autoremove && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +# Compile bcc +RUN git clone https://github.com/guedou/bcc && cd bcc/ && mkdir build && \ + cd build && cmake .. && make install && cd src && make install && rm -rf /bcc/ + +# Install Scapy +RUN git clone https://github.com/guedou/scapy-issues && cd scapy-issues && \ + git checkout pcapng-comment && pip install .[complete] && rm -rf /scapy-issues/ +RUN pip install cryptography==2.8 + +# Install peetch +COPY . /peetch +RUN cd /peetch && pip install -r requirements.txt && pip install . && rm -rf /peetch \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..42d28c6 --- /dev/null +++ b/README.md @@ -0,0 +1,112 @@ +# peetch + +`peetch` is a collection of tools aimed at experimenting with different aspects of eBPF to bypass TLS protocol protections. + +Currently, peetch includes two subcommands. The first called `dump` aims to sniff network traffic by associating information about the source process with each packet. The second called `tls` allows to identify processes using OpenSSL to extract cryptographic keys. + +Combined, these two commands make it possible to decrypt TLS exchanges recorded in the PCAPng format. + + +# Installation + +`peetch` relies on several dependencies including non-merged modifications of [bcc](https://github.com/iovisor/bcc) and [Scapy](https://github.com/secdev/scapy). A Docker image can be easily built in order to easily test `peetch` using the following command: +``` +docker build -t quarkslab/peetch . +``` + + +# Commands Walk Through + +The following examples assume that you used the following command to enter the Docker image and launch examples within it: +``` +docker run --privileged --network host --mount type=bind,source=/sys,target=/sys --mount type=bind,source=/proc,target=/proc --rm -it quarkslab/peetch +``` + + +## `dump` + +This sub-command gives you the ability to sniff packets using an eBPF TC classifier and to retrieve the corresponding PID and process names with: +``` +peetch dump +curl/1289291 - Ether / IP / TCP 10.211.55.10:53052 > 208.97.177.124:https S / Padding +curl/1289291 - Ether / IP / TCP 208.97.177.124:https > 10.211.55.10:53052 SA / Padding +curl/1289291 - Ether / IP / TCP 10.211.55.10:53052 > 208.97.177.124:https A / Padding +curl/1289291 - Ether / IP / TCP 10.211.55.10:53052 > 208.97.177.124:https PA / Raw / Padding +curl/1289291 - Ether / IP / TCP 208.97.177.124:https > 10.211.55.10:53052 A / Padding +``` + +Note that for demonstration purposes, `dump` will only capture IPv4 based TCP segments. + +For convenience, the captured packets can be store to PCAPng along with process information using `--write`: +``` +peetch dump --write peetch.pcapng +^C +``` + +This PCAPng can easily be manipulated with Wireshark or Scapy: +``` +scapy +>>> l = rdpcap("peetch.pcapng") +>>> l[0] +>>> +>>> l[0].comment +b'curl/1289909' +``` + + +## `tls` + +This sub-command aims at identifying process that uses OpenSSl and makes it is to dump several things like plaintext and secrets. + +By default, `peetch tls` will only display one line per process, the `--directions` argument makes it possible to display the exchanges messages: +``` +peetch tls --directions +<- curl (1291078) 208.97.177.124/443 TLS1.2 ECDHE-RSA-AES128-GCM-SHA256 +> curl (1291078) 208.97.177.124/443 TLS1.-1 ECDHE-RSA-AES128-GCM-SHA256 +``` + +Displaying OpenSSL buffer content is achieved with `--content`. +``` +peetch tls --content +<- curl (1290608) 208.97.177.124/443 TLS1.2 ECDHE-RSA-AES128-GCM-SHA256 + + 0000 47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A GET / HTTP/1.1.. + 0010 48 6F 73 74 3A 20 77 77 77 2E 70 65 72 64 75 2E Host: www.perdu. + 0020 63 6F 6D 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A com..User-Agent: + 0030 20 63 75 72 6C 2F 37 2E 36 38 2E 30 0D 0A 41 63 curl/7.68.0..Ac + +-> curl (1290608) 208.97.177.124/443 TLS1.-1 ECDHE-RSA-AES128-GCM-SHA256 + + 0000 48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D HTTP/1.1 200 OK. + 0010 0A 44 61 74 65 3A 20 54 68 75 2C 20 31 39 20 4D .Date: Thu, 19 M + 0020 61 79 20 32 30 32 32 20 31 38 3A 31 36 3A 30 31 ay 2022 18:16:01 + 0030 20 47 4D 54 0D 0A 53 65 72 76 65 72 3A 20 41 70 GMT..Server: Ap +``` + + +The `--secrets` arguments will display TLS Master Secrets extracted from memory. The following example leverages `--write` to write master secrets to discuss to simplify decruypting TLS messages with Scapy: + +``` +$ (sleep 5; curl https://www.perdu.com/?name=highly%20secret%20information --tls-max 1.2 -http1.1) & + +# peetch tls --write & +curl (1293232) 208.97.177.124/443 TLS1.2 ECDHE-RSA-AES128-GCM-SHA256 + +# peetch dump --write traffic.pcapng +^C + +# Add the master secret to a PCAPng file +$ editcap --inject-secrets tls,1293232-master_secret.log traffic.pcapng traffic-ms.pcapng + +$ scapy +>>> load_layer("tls") +>>> conf.tls_session_enable = True +>>> l = rdpcap("traffic-ms.pcapng") +>>> l[13][TLS].msg +[] +``` + + +## Limitations + +By design, peetch only supports OpenSSL and TLS 1.2. The default offsets for OpenSSL structures assume that you are using the `1.1.1f-1ubuntu2.13` on `arm64`. However, they can easily be changed using command line arguments. diff --git a/bin/peetch b/bin/peetch new file mode 120000 index 0000000..bfc6b0d --- /dev/null +++ b/bin/peetch @@ -0,0 +1 @@ +../peetch/__main__.py \ No newline at end of file diff --git a/peetch/__init__.py b/peetch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/peetch/__main__.py b/peetch/__main__.py new file mode 100644 index 0000000..784b307 --- /dev/null +++ b/peetch/__main__.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0+ +# Guillaume Valadon + +import sys + +from peetch.main import main + +main(sys.argv[1:]) diff --git a/peetch/ebpf_programs/peetch_kprobes.c b/peetch/ebpf_programs/peetch_kprobes.c new file mode 100644 index 0000000..cf285a0 --- /dev/null +++ b/peetch/ebpf_programs/peetch_kprobes.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Guillaume Valadon + +#include +#include +#include + + +#include +#include +#include +#include + + +struct key_t { + u32 dst; + u32 src; + u64 padding; +}; + +struct data_t { + u32 pid; + char name[64]; +}; + +BPF_HASH(pid_cache, struct key_t *); + +BPF_PERF_OUTPUT(skb_events); + +int process_frame(struct __sk_buff *skb) +{ + // Data accessors + unsigned char *data = (void *)(long)skb->data; + unsigned char *data_end = (void *)(long)skb->data_end; + + // Mapping data to the Ethernet and IP headers + struct ethhdr *eth = (struct ethhdr *)data; + struct iphdr *iph = (struct iphdr*) (data + sizeof(struct ethhdr)); + + // Simple length check + if ((data + sizeof(struct ethhdr) + sizeof(struct iphdr)) > data_end) + return TC_ACT_OK; + + // Discard everything but IPv4 + if (ntohs(eth->h_proto) != ETH_P_IP) + return TC_ACT_OK; + + // Discard everything but TCP + if (iph->protocol != IPPROTO_TCP) + return TC_ACT_OK; + + // Retrieve the PID and the process name from the IP addresses + struct key_t key = { .dst = iph->daddr, .src = iph->saddr }; + struct data_t *value = (struct data_t *) pid_cache.lookup((struct key_t **)&key); + if (value == NULL) { + key.dst = iph->saddr; + key.src = iph->daddr; + value = (struct data_t *) pid_cache.lookup((struct key_t **)&key); + if (value == NULL) + return TC_ACT_OK; + } + + // Check the PID + if (value->pid == 0) + return TC_ACT_OK; + + skb_events.perf_submit_skb(skb, skb->len, value, sizeof(value)); + + return TC_ACT_OK; +} + +int kprobe_security_sk_classify_flow(struct pt_regs *ctx, struct sock *sk, struct flowi *fl) { + + if (sk->sk_family != AF_INET) + return 0; + + // Extract IPv4 related structures + union flowi_uli uli; + struct flowi4 ip4; + bpf_probe_read(&ip4, sizeof(ip4), &fl->u.ip4); + bpf_probe_read(&uli, sizeof(uli), &ip4.uli); + + // Get IP addresses and ports + struct key_t key; + struct data_t data; + + bpf_probe_read(&key.src, sizeof(sk->__sk_common.skc_daddr), &sk->__sk_common.skc_daddr); + bpf_probe_read(&key.dst, sizeof(sk->__sk_common.skc_rcv_saddr), &sk->__sk_common.skc_rcv_saddr); + + // Get and store the PID + u64 id = bpf_get_current_pid_tgid(); + data.pid = id >> 32; + + // Get and store the process name + bpf_get_current_comm(data.name, 64); + + // Store data + pid_cache.update((struct key_t **) &key, (unsigned long long *) &data); + + return 0; +} diff --git a/peetch/ebpf_programs/peetch_uprobes.c b/peetch/ebpf_programs/peetch_uprobes.c new file mode 100644 index 0000000..aeb208a --- /dev/null +++ b/peetch/ebpf_programs/peetch_uprobes.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Guillaume Valadon + +#include +#include + + +// Data structure sent to userland +struct tls_event_t { + u32 addr; + u16 port; + u16 tls_version; + #define COMM_MAX_LEN 64 + char comm[COMM_MAX_LEN]; + #define MESSAGE_MAX_LEN 64 + u8 message[MESSAGE_MAX_LEN]; + u32 message_length; + u32 pid; + u32 is_read; +}; +BPF_PERF_OUTPUT(tls_events); + + +// Store SSL_* buffer information +struct SSL_buffer_t { + u64 ptr; + u32 length; + u32 tls_version; + u32 is_read; +}; +BPF_HASH(SSL_read_buffers, u32); + + +// Store connect information indexed by PID +BPF_HASH(pid_cache, u32); + + +// TLS information +struct TLS_information_t { + #define CIPHERSUITE_MAX_LEN 32 + char ciphersuite[CIPHERSUITE_MAX_LEN]; + #define MASTER_SECRET_MAX_LEN 48 + u8 master_secret[MASTER_SECRET_MAX_LEN]; +}; +BPF_HASH(tls_information_cache, u32, struct TLS_information_t); + + +TRACEPOINT_PROBE(syscalls, sys_enter_connect) { + // Retrieve the sockaddr_in structure + struct sockaddr_in addr_in; + int ret = bpf_probe_read((void*)&addr_in, sizeof(addr_in), args->uservaddr); + if (ret) { + bpf_trace_printk("sys_enter_connect() - bpf_probe_read() failed\n"); + return 0; + } + + // Discard everything but IPv4 + if (addr_in.sin_family != AF_INET) + return 0; + + // Retrieve the PID + u32 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + + // Store a TLS event in the pid_cache + struct tls_event_t event = { .port = addr_in.sin_port, .addr = addr_in.sin_addr.s_addr}; + pid_cache.update(&pid, (u64*)&event); + + return 0; +} + + +// Dummy openssl ssl_st structure +struct ssl_st { + int version; +}; + + +static u16 get_tls_version(void *ssl_st_ptr) { + // Extract the TLS version from a struct ssl_str pointer + struct ssl_st ssl; + + int ret = bpf_probe_read(&ssl, sizeof(ssl), ssl_st_ptr); + if (ret) { + bpf_trace_printk("get_tls_version() - bpf_probe_read() failed\n"); + return -1; + } + + return ssl.version; +} + + + static void parse_session(struct pt_regs *ctx) { + // Parse a struct sl_session_st pointer and send + // data to userspace + + // TLS information sent to userspace + struct TLS_information_t tls_information; + + // Get a ssl_st pointer + void *ssl_st_ptr = (void *) PT_REGS_PARM1(ctx); + + // Get a ssl_session_st pointer + u64 *ssl_session_st_ptr = (u64 *) (ssl_st_ptr + SSL_SESSION_OFFSET); + + u64 address; + int ret = bpf_probe_read(&address, sizeof(address), ssl_session_st_ptr); + if (ret) + bpf_trace_printk("parse_session() #1 - bpf_probe_read() failed\n"); + + // Access the TLS 1.2 master secret + void *ms_ptr = (void *) (address + MASTER_SECRET_OFFSET); + ret = bpf_probe_read(&tls_information.master_secret, sizeof(tls_information.master_secret), ms_ptr); + if (ret) + bpf_trace_printk("parse_session() #2 - bpf_probe_read() failed\n"); + + // Get a ssl_cipher_st pointer + void *ssl_cipher_st_ptr = (void *) (address + SSL_CIPHER_OFFSET); + ret = bpf_probe_read(&address, sizeof(address), ssl_cipher_st_ptr); + if (ret) + bpf_trace_printk("parse_session() #3 - bpf_probe_read() failed\n"); + + // Get the SSL_cipher_st point to the name member + ssl_cipher_st_ptr = (void *) (address + 8); + ret = bpf_probe_read(&address, sizeof(address), ssl_cipher_st_ptr); + if (ret) + bpf_trace_printk("parse_session() #4 - bpf_probe_read() failed\n"); + + // Access the TLS ciphersuite + void *cs_ptr = (void *) address; + ret = bpf_probe_read(&tls_information.ciphersuite, sizeof(tls_information.ciphersuite), cs_ptr); + if (ret) + bpf_trace_printk("parse_session() #5 - bpf_probe_read() failed\n"); + + u32 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + tls_information_cache.update(&pid, &tls_information); +} + + +static int SSL_read_write(struct pt_regs *ctx, u16 tls_version, struct SSL_buffer_t *buffer) { + // A buffer is needed + if (buffer == NULL) + return 0; + + // Retrieve connect() information for PID + u32 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + struct tls_event_t *event = (struct tls_event_t*) pid_cache.lookup(&pid); + if (event == NULL) + return 0; + + // Build a new TLS event and fill it + struct tls_event_t new_event; + + bpf_get_current_comm(&new_event.comm, COMM_MAX_LEN); + new_event.pid = pid; + new_event.port = event->port; + new_event.addr = event->addr; + new_event.is_read = buffer->is_read; + new_event.tls_version = tls_version; + + int ret = bpf_probe_read(&new_event.message, sizeof(new_event.message), (void*) buffer->ptr); + if (ret) { + bpf_trace_printk("SSL_read_write() - bpf_probe_read() failed\n"); + return 0; + } + new_event.message_length = buffer->length; + + // Send the event to userland + tls_events.perf_submit(ctx, &new_event, sizeof(new_event)); + + // Flush the PID cache + if (DIRECTIONS) // this will be replaced by a boolean in Python + pid_cache.delete(&pid); + + return 0; +} + + +int SSL_read(struct pt_regs *ctx) { + // Retrieve the PID + u32 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + + // Store a SSL read buffer information in the cache + struct SSL_buffer_t buffer; + buffer.ptr = PT_REGS_PARM2(ctx); + + // Get TLS version + void *ssl_st_ptr = (void *) PT_REGS_PARM1(ctx); + buffer.tls_version = get_tls_version(ssl_st_ptr); + + SSL_read_buffers.update(&pid, (u64*) &buffer); + + return 0; +} + + +int SSL_read_ret(struct pt_regs *ctx) { + // Discard if nothing was received + int buffer_length = PT_REGS_RC(ctx); + if (buffer_length == -1) + return 0; + + // Retrieve SSL read buffers information for PID + u32 pid = bpf_get_current_pid_tgid() & 0xFFFFFFFF; + struct SSL_buffer_t *buffer = (struct SSL_buffer_t*) SSL_read_buffers.lookup(&pid); + if (buffer == NULL) + return 0; + + // Create a new buffer structure + struct SSL_buffer_t new_buffer; + new_buffer.ptr = buffer->ptr; + new_buffer.length = buffer_length; + new_buffer.is_read = 1; + + // Retrieve the TLS version + struct SSL_buffer_t tmp; + int ret = bpf_probe_read(&tmp, sizeof(tmp), buffer); + if (ret != 0) { + return 0; + } + + ret = SSL_read_write(ctx, tmp.tls_version, &new_buffer); + SSL_read_buffers.delete(&pid); + + return ret; +} + + +int SSL_write(struct pt_regs *ctx) { + // Retrieve the buffer information + struct SSL_buffer_t buffer; + + buffer.ptr = PT_REGS_PARM2(ctx); + buffer.length = PT_REGS_PARM3(ctx); + buffer.is_read = 0; + + // Get TLS version + void *ssl_st_ptr = (void *) PT_REGS_PARM1(ctx); + u16 tls_version = get_tls_version(ssl_st_ptr); + + parse_session(ctx); + + return SSL_read_write(ctx, tls_version, &buffer); +} diff --git a/peetch/main.py b/peetch/main.py new file mode 100644 index 0000000..9c059df --- /dev/null +++ b/peetch/main.py @@ -0,0 +1,288 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Guillaume Valadon + +import argparse +import atexit +import ctypes as ct +import binascii +import os +import socket +import struct +import sys +import time + +from bcc import BPF +import pyroute2 +from scapy.all import Ether, wrpcapng, hexdump + + +EBPF_PROGRAMS_DIRNAME = os.path.join(os.path.dirname(__file__), + "ebpf_programs/") +BPF_DUMP_PROGRAM_FILENAME = "%s/peetch_kprobes.c" % EBPF_PROGRAMS_DIRNAME +BPF_DUMP_PROGRAM_SOURCE = open(BPF_DUMP_PROGRAM_FILENAME).read() +BPF_TLS_PROGRAM_FILENAME = "%s/peetch_uprobes.c" % EBPF_PROGRAMS_DIRNAME +BPF_TLS_PROGRAM_SOURCE = open(BPF_TLS_PROGRAM_FILENAME).read() +PACKETS_CAPTURED = [] + + +def load_classifier(interface, ebpf_function): + """ + Load an eBPF TC Classifier + """ + iproute_handler = pyroute2.IPRoute() + + ip_link = iproute_handler.link_lookup(ifname=interface) + if not ip_link: + sys.exit() + + ip_link = ip_link[0] + iproute_handler.tc("add", "clsact", ip_link) + + # add ingress clsact + iproute_handler.tc("add-filter", "bpf", ip_link, ":1", + fd=ebpf_function.fd, name=ebpf_function.name, + parent="ffff:fff2") + + # add egress clsact + iproute_handler.tc("add-filter", "bpf", ip_link, ":1", + fd=ebpf_function.fd, name=ebpf_function.name, + parent="ffff:fff3") + + +def unload_classifier(interface): + """ + Unload an eBPF TC Classifier + """ + os.system("tc qdisc del dev %s clsact" % args.interface) + + +def exit_handler(interface, filename, bpf_handler): + """ + Exit nicely + """ + time.sleep(0.01) + bpf_handler.detach_kprobe(event="security_sk_classify_flow", + fn_name="kprobe_security_sk_classify_flow") + unload_classifier(interface) + if filename: + wrpcapng(filename, PACKETS_CAPTURED) + + +def handle_skb_event(cpu, data, size): + """ + Handle SKB events from the kernel + """ + + # Structure retrieved from the kernel + class SkbEvent(ct.Structure): + _fields_ = [("pid", ct.c_uint32), + ("raw", ct.c_ubyte * (size - ct.sizeof(ct.c_uint32)))] + + # Map the data from kernel to the structure + skb_event = ct.cast(data, ct.POINTER(SkbEvent)).contents + data = bytes(skb_event.raw) + + # Extract the process name + for i in range(len(data)): + if data[i] == 0: + break + process_name = data[:i].decode("ascii") + data = data[i:] + + process_information = "%s/%d" % (process_name, skb_event.pid) + + # Parse the packet with Scapy + pkt = Ether(data) + + if args.write: + pkt.comment = str(process_information) + PACKETS_CAPTURED.append(pkt) + else: + if not args.raw: + print(process_information, end=" - ") + print(pkt.summary()) + + +def dump_command(args): + # Compile eBPF programs + bpf_handler = BPF(text=BPF_DUMP_PROGRAM_SOURCE) + + # Attach the kprobe + bpf_handler.attach_kprobe(event="security_sk_classify_flow", + fn_name="kprobe_security_sk_classify_flow") + + # Setup the exit handler + atexit.register(exit_handler, args.interface, args.write, bpf_handler) + + # Load eBPF TC Classifier + classifier_function = bpf_handler.load_func("process_frame", BPF.SCHED_CLS) + load_classifier(args.interface, classifier_function) + + # Handle incoming skb events + bpf_handler["skb_events"].open_perf_buffer(handle_skb_event) + try: + while True: + bpf_handler.perf_buffer_poll() + except KeyboardInterrupt: + pass + + +def tls_command(args): + # Process arguments + if args.content: + args.directions = True + + directions_bool = "1" + if args.directions: + directions_bool = "0" + + # Compile eBPF programs + ebpf_programs = BPF_TLS_PROGRAM_SOURCE.replace("DIRECTIONS", + directions_bool) + ebpf_programs = ebpf_programs.replace("SSL_SESSION_OFFSET", + args.ssl_session_offset) + ebpf_programs = ebpf_programs.replace("MASTER_SECRET_OFFSET", + args.master_secret_offset) + ebpf_programs = ebpf_programs.replace("SSL_CIPHER_OFFSET", + args.ssl_cipher_offset) + bpf_handler = BPF(text=ebpf_programs) + + # Attach the probes + try: + bpf_handler.attach_uprobe(name="ssl", + sym="SSL_write", fn_name="SSL_write") + bpf_handler.attach_uprobe(name="ssl", + sym="SSL_read", fn_name="SSL_read") + bpf_handler.attach_uretprobe(name="ssl", + sym="SSL_read", fn_name="SSL_read_ret") + except Exception: + print("tls - cannot attach to eBPF probes!") + sys.exit() + + def handle_tls_event(cpu, data, size): + class TLSEvent(ct.Structure): + _fields_ = [("address", ct.c_uint32), + ("port", ct.c_uint16), + ("tls_version", ct.c_uint16), + ("comm", ct.c_char * 64), + ("message", ct.c_uint8 * 64), + ("message_length", ct.c_uint32), + ("pid", ct.c_uint32), + ("is_read", ct.c_uint32)] + + # Map the data from kernel to the structure + tls_event = ct.cast(data, ct.POINTER(TLSEvent)).contents + + # Get TLS information + pid_to_delete = None + master_secret = None + ciphersuite = None + bpf_map_tls_information = bpf_handler["tls_information_cache"] + for pid, tls_info in bpf_map_tls_information.items_lookup_batch(): + if pid == tls_event.pid: + ciphersuite = tls_info.ciphersuite.decode("ascii") + master_secret = binascii.hexlify(tls_info.master_secret) + master_secret = master_secret.decode("ascii") + pid_to_delete = [pid] + break + + # Delete pid from the eBPF map + if not pid_to_delete: + bpf_map_tls_information.items_delete_batch(pid_to_delete) + + # Unpack the IPv4 destination address + addr = struct.pack("I", tls_event.address) + + # Discard empty content + if args.content and tls_event.message_length == 0: + return + + # Display the TLS event + if args.directions: + if tls_event.is_read: + print("->", end=" ") + else: + print("<-", end=" ") + print("%s (%d)" % (tls_event.comm.decode("ascii"), + tls_event.pid), end=" ") + print("%s/%d" % (socket.inet_ntop(socket.AF_INET, addr), + socket.ntohs(tls_event.port)), end=" ") + + version = (tls_event.tls_version & 0xF) - 1 + print("TLS1.%d %s" % (version, ciphersuite)) + + # Display TLS secrets + if (args.secrets or args.write) and tls_event.tls_version == 0x303: + key_log = "CLIENT_RANDOM 28071980 %s\n" % master_secret + if args.secrets: + print("\n %s\n" % key_log) + if args.write: + fd = open("%d-master_secret.log" % pid, "w") + fd.write(key_log) + fd.close() + + # Display the message content in hexadecimal + if args.content and tls_event.message_length: + hex_message = hexdump(tls_event.message[:tls_event.message_length], + dump=True) + print("\n ", end="") + print(hex_message.replace("\n", "\n ")) + print() + + bpf_handler["tls_events"].open_perf_buffer(handle_tls_event) + while True: + try: + bpf_handler.perf_buffer_poll() + except KeyboardInterrupt: + sys.exit() + + +def main(argv): + global args + + # Parsing arguments + parser = argparse.ArgumentParser(description="peetch - an eBPF playground") + subparser = parser.add_subparsers() + parser.set_defaults(func=lambda args: parser.print_help()) + + # Prepare the 'dump' subcommand + dump_parser = subparser.add_parser("dump", + help="Sniff packets with eBPF") + dump_parser.add_argument("--raw", action="store_true", + help="display packets only") + dump_parser.add_argument("--write", type=str, + help="pcapng filename") + dump_parser.add_argument("--interface", type=str, + help="interface name", default="eth0") + dump_parser.set_defaults(func=dump_command) + + # Prepare the 'identify' subcommand + dump_parser = subparser.add_parser("tls", + help="Identify processes that uses TLS") + dump_parser.add_argument("--directions", action="store_true", + help="display read & write calls") + dump_parser.add_argument("--content", action="store_true", + help="display buffers content") + dump_parser.add_argument("--secrets", action="store_true", + help="display TLS secrets") + dump_parser.add_argument("--write", action="store_true", + help="write TLS secrets to files") + dump_parser.add_argument("--ssl_session_offset", + default="0x510", + help="offset to the ssl_session_t structure") + dump_parser.add_argument("--master_secret_offset", + default="80", + help="offset to the master secret in an ssl_session_t structure") # noqa: E501 + dump_parser.add_argument("--ssl_cipher_offset", + default="0x1f8", + help="offset to the ssl_cipher structure in an ssl_session_t structure") # noqa: E501 + dump_parser.set_defaults(func=tls_command) + + # Print the Help message when no arguments are provided + if not argv: + parser.print_help(sys.stderr) + sys.exit(1) + + # Call the sub-command + args = parser.parse_args(argv) + args.func(args) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..03d91c7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +scapy +pyroute2 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6044d8a --- /dev/null +++ b/setup.py @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Guillaume Valadon + +from setuptools import setup + +setup(name="peetch", + description="An eBPF playground", + author="Guillaume Valadon", + author_email="gvaladon@quarkslab.com", + version="0.1", + packages=["peetch"], + scripts=["bin/peetch"], + package_data={"peetch": ["ebpf_programs/peetch_*.c"]}, + )