Skip to content

Commit

Permalink
Working matching of SSL client hellos
Browse files Browse the repository at this point in the history
Remove invalid characters


Disabling printing packet data to console


Fix extension matching


Fix continuing to next extension


Moar debug


Fixed missing newlines


Replace cypher with cipher


Free parsed_host after use


Move debug output


Lots of debug


Debug


Change base extension offset


More debug


Attempt to fix extensions length


Fix offset


Missing '&'


Fix


Attempt to fix cURL and wget support


Remove protocol debug message


Use error codes


Ignore name type


Set parsed_host to NULL


Don't use return values


Allocate server_name memory


Add license file
Fix SSL hostname function

Getting the SNI hostname seems to work now. However the string comparison crashes horribly
Dammit


Only print client hello data


g fuckin g


gg


Drop \0 break


Another variant


Check all data


Print memory address correctly


Print hex


Write packet data to debug


Revert tcp_body


Moar debug again


Suspiscius shit


Forgot newlines


Tons of debugging


Copy only TCP data to own variable


Get TCP body instead of whole packet


Some...


Don't show when port is not 443


Moar debug


Debug fixes


Missing semicolon


More debug


Stuff


It never ends


More...


Missed one


Fix C90 errors


Goddammit


Again and again...


Add some debugging output


Remove redundant return


And again...


Try again


Potential fix for segfault


Fix make install


Hopefully working client hello heuristics


[ipt] Other fixes


[ipt] Fix options struct


Fix indentation


Basic iptables extension


Add -fPIC


Start on iptables extension


Create netfilter extension that registers

Remove unused variable

Skeleton
  • Loading branch information
Lochnair committed Nov 21, 2016
1 parent 86d8e92 commit 8d59884
Show file tree
Hide file tree
Showing 8 changed files with 1,023 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
*/*.ko
*/*.o
*/*.so
src/Module.symvers
src/modules.order
src/*.cmd
src/*.mod.c
src/.tmp_versions/
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions Makefile
@@ -0,0 +1,9 @@
all:
$(MAKE) -C ipt
$(MAKE) -C src
install:
$(MAKE) -C ipt install
$(MAKE) -C src install
clean:
$(MAKE) -C src clean
$(MAKE) -C ipt clean
12 changes: 12 additions & 0 deletions ipt/Makefile
@@ -0,0 +1,12 @@
CFLAGS = -I../src -fPIC

all:
make libxt_ssl.so
lib%.so: lib%.o
$(CC) -shared -o $@ $^;
lib%.o: %lib%.c
$(CC) ${CFLAGS} -D_INIT=lib$*_init -c -o $@ $<;
clean:
rm -rf libxt_ssl.so
install:
install -v -m 644 libxt_ssl.so /lib/xtables
83 changes: 83 additions & 0 deletions ipt/libxt_ssl.c
@@ -0,0 +1,83 @@
#include <stdlib.h>
#include <xtables.h>
#include <stdio.h>
#include <string.h>

#include "xt_ssl.h"

enum {
O_SSL_HOST = 0,
};

static void ssl_help(void)
{
printf(
"ssl match options:\n[!] --ssl-host hostname\n"
);
}

static const struct xt_option_entry ssl_opts[] = {
{
.name = "ssl-host",
.id = O_SSL_HOST,
.type = XTTYPE_STRING,
.flags = XTOPT_INVERT | XTOPT_PUT, XTOPT_POINTER(struct xt_ssl_info, ssl_host),
},
XTOPT_TABLEEND,
};

static void ssl_parse(struct xt_option_call *cb)
{
struct xt_ssl_info *info = cb->data;

xtables_option_parse(cb);
switch (cb->entry->id) {
case O_SSL_HOST:
if (cb->invert)
info->invert |= XT_SSL_OP_HOST;
strcpy(info->ssl_host, cb->arg);
break;
}
}

static void ssl_check(struct xt_fcheck_call *cb)
{
if (cb->xflags == 0)
xtables_error(PARAMETER_PROBLEM, "SSL: no ssl option specified");
}

static void ssl_print(const void *ip, const struct xt_entry_match *match, int numeric)
{
const struct xt_ssl_info *info = (const struct xt_ssl_info *)match->data;

printf(" SSL match");
printf("%s --ssl-host %s",
(info->invert & XT_SSL_OP_HOST) ? " !":"", info->ssl_host);
}

static void ssl_save(const void *ip, const struct xt_entry_match *match)
{
const struct xt_ssl_info *info = (const struct xt_ssl_info *)match->data;

printf("%s --ssl-host %s",
(info->invert & XT_SSL_OP_HOST) ? " !":"", info->ssl_host);
}

static struct xtables_match ssl_match = {
.family = NFPROTO_IPV4,
.name = "ssl",
.version = XTABLES_VERSION,
.size = XT_ALIGN(sizeof(struct xt_ssl_info)),
.userspacesize = XT_ALIGN(sizeof(struct xt_ssl_info)),
.help = ssl_help,
.print = ssl_print,
.save = ssl_save,
.x6_parse = ssl_parse,
.x6_fcheck = ssl_check,
.x6_options = ssl_opts,
};

void _init(void)
{
xtables_register_match(&ssl_match);
}
17 changes: 17 additions & 0 deletions src/Makefile
@@ -0,0 +1,17 @@
obj-m := xt_ssl.o
KERNEL_VERSION := $(shell uname -r)
IDIR := /lib/modules/$(KERNEL_VERSION)/kernel/net/netfilter/
KDIR := /lib/modules/$(KERNEL_VERSION)/build
#KDIR := ../linux-4.8
PWD := $(shell pwd)
VERSION := $(shell git rev-parse HEAD 2>/dev/null)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

install:
install -v -m 644 xt_ssl.ko $(IDIR)
depmod "$(KERNEL_VERSION)"
[ "$(KERNEL_VERSION)" != `uname -r` ] || modprobe xt_ssl

clean:
rm -rf Module.markers modules.order Module.symvers xt_ssl.ko xt_ssl.mod.c xt_ssl.mod.o xt_ssl.o
208 changes: 208 additions & 0 deletions src/xt_ssl.c
@@ -0,0 +1,208 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/skbuff.h>
#include <linux/netfilter/x_tables.h>
#include <linux/string.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/inet.h>
#include <asm/errno.h>

#include "xt_ssl.h"

/*
* Searches through skb->data and looks for a
* client or server handshake. A client
* handshake is preferred as the SNI
* field tells us what domain the client
* wants to connect to.
*/
static int get_ssl_hostname(struct iphdr *ip_header, struct tcphdr *tcp_header, const struct sk_buff *skb, char *dest)
{
char *data = (char *)((unsigned char *)tcp_header + (tcp_header->doff * 4));
char *tail = skb_tail_pointer(skb);
size_t data_len = (uintptr_t)tail - (uintptr_t)data;
u_int16_t ssl_header_len;
u_int8_t handshake_protocol;

if (data[0] != 0x16) {
return EPROTO;
}

ssl_header_len = (data[3] << 8) + data[4] + 5;
handshake_protocol = data[5];

if (ssl_header_len > data_len)
ssl_header_len = data_len;

if (ssl_header_len > 4) {
// Check only client hellos for now
if (handshake_protocol == 0x01) {
u_int offset, base_offset = 43, extension_offset = 2;
u_int16_t session_id_len, cipher_len, compression_len, extensions_len;

/*for (i = 0; i < data_len; i++) {
printk("[xt_ssl] data[%d]: 0x%02x / %d\n", i, data[i], data[i]);
}*/

if (base_offset + 2 > data_len) {
printk("[xt_ssl] Data length is to small (%d)\n", (int)data_len);
return EPROTO;
}

session_id_len = data[base_offset];
printk("[xt_ssl] Session ID length: %d\n", session_id_len);

if ((session_id_len + base_offset + 2) > ssl_header_len) {
printk("[xt_ssl] SSL header length is smaller than session_id_len + base_offset +2 (%d > %d)\n", (session_id_len + base_offset + 2), ssl_header_len);
return EPROTO;
}

memcpy(&cipher_len, &data[base_offset + session_id_len + 1], 2);
cipher_len = ntohs(cipher_len);
printk("[xt_ssl] cipher len: %d\n", cipher_len);
offset = base_offset + session_id_len + cipher_len + 2;
printk("[xt_ssl] Offset (1): %d\n", offset);

if (offset > ssl_header_len) {
printk("[xt_ssl] SSL header length is smaller than offset (%d > %d)\n", offset, ssl_header_len);
return EPROTO;
}

compression_len = data[offset + 1];
printk("[xt_ssl] Compression length: %d\n", compression_len);
offset += compression_len + 2;
printk("[xt_ssl] Offset (2): %d\n", offset);

if (offset > ssl_header_len) {
printk("[xt_ssl] SSL header length is smaller than offset w/compression (%d > %d)\n", offset, ssl_header_len);
return EPROTO;
}

memcpy(&extensions_len, &data[offset], 2);
extensions_len = ntohs(extensions_len);
printk("[xt_ssl] Extensions length: %d\n", extensions_len);

if ((extensions_len + offset) > ssl_header_len) {
printk("[xt_ssl] SSL header length is smaller than offset w/extensions (%d > %d)\n", (extensions_len + offset), ssl_header_len);
return EPROTO;
}

printk("[xt_ssl] Looping through extensions.\n");

while (extension_offset < extensions_len)
{
u_int16_t extension_id, extension_len;

printk("[xt_ssl] memcpy extension_id @ %d\n", offset + extension_offset);
memcpy(&extension_id, &data[offset + extension_offset], 2);
extension_offset += 2;

printk("[xt_ssl] memcpy extension_len @ %d\n", offset + extension_offset);
memcpy(&extension_len, &data[offset + extension_offset], 2);
extension_offset += 2;

extension_id = ntohs(extension_id), extension_len = ntohs(extension_len);

if (extension_id == 0) {
u_int16_t name_length, name_type;

// We don't need the server name list length, so skip that
extension_offset += 2;
printk("[xt_ssl] Name type location: %d\n", offset + extension_offset);
memcpy(&name_type, &data[offset + extension_offset], 1);
extension_offset += 1;
printk("[xt_ssl] Name type: %d\n", name_type);

printk("[xt_ssl] Name length location: %d\n", offset + extension_offset);
memcpy(&name_length, &data[offset + extension_offset], 2);
name_length = ntohs(name_length);
extension_offset += 2;
printk("[xt_ssl] Name length: %d\n", name_length);

printk("[xt_ssl] Name location: %d\n", offset + extension_offset);
memcpy(dest, &data[offset + extension_offset], name_length);

return 0;
}

extension_offset += extension_len;
}
}
}

return EPROTO;
}

static bool ssl_mt(const struct sk_buff *skb, struct xt_action_param *par)
{
char *parsed_host = kmalloc(255, GFP_KERNEL);
const struct xt_ssl_info *info = par->matchinfo;
int result;
bool match;

// Get destination port
struct iphdr *ip_header = (struct iphdr *)skb_network_header(skb);
struct tcphdr *tcp_header;

unsigned int dst_port = 0;

if (ip_header->protocol != IPPROTO_TCP) {
return false;
}
tcp_header = (struct tcphdr *)skb_transport_header(skb);
dst_port = (unsigned int)ntohs(tcp_header->dest);

// For performance reasons only run SSL heuristics if destination port is 443
if (dst_port != 443) {
return false;
}

if ((result = get_ssl_hostname(ip_header, tcp_header, skb, parsed_host)) != 0)
return false;

match = (strcmp(info->ssl_host, parsed_host) == 0);

printk("[xt_ssl] get_ssl_hostname returned: %d\n", result);
printk("[xt_ssl] Parsed domain: %s\n", parsed_host);
printk("[xt_ssl] Domain matches: %s\n", match ? "true" : "false");

kfree(parsed_host);

return match;
}

static int ssl_mt_check (const struct xt_mtchk_param *par)
{
//const struct xt_ssl_info *info = par->matchinfo;
return 0;
}

static struct xt_match ssl_mt_reg __read_mostly = {
.name = "ssl",
.revision = 0,
.family = NFPROTO_IPV4,
.checkentry = ssl_mt_check,
.match = ssl_mt,
.matchsize = sizeof(struct xt_ssl_info),
.me = THIS_MODULE,
};

static int __init ssl_mt_init (void)
{
return xt_register_match(&ssl_mt_reg);
}

static void __exit ssl_mt_exit (void)
{
xt_unregister_match(&ssl_mt_reg);
}

module_init(ssl_mt_init);
module_exit(ssl_mt_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Nils Andreas Svee <nils@stokkdalen.no>");
MODULE_DESCRIPTION("Xtables: SSL (SNI) matching");
MODULE_ALIAS("ipt_ssl");
12 changes: 12 additions & 0 deletions src/xt_ssl.h
@@ -0,0 +1,12 @@
#ifndef _XT_SSL_TARGET_H
#define _XT_SSL_TARGET_H

#define XT_SSL_OP_HOST 0x01

/* target info */
struct xt_ssl_info {
__u8 invert;
char ssl_host[255];
};

#endif /* _XT_SSL_TARGET_H */

0 comments on commit 8d59884

Please sign in to comment.