From b1cdae87f25021eb835872d86d6e7206bd421c3f Mon Sep 17 00:00:00 2001 From: Bernie Harris Date: Wed, 21 Mar 2018 15:42:29 +1300 Subject: extensions: Add string filter to ebtables This patch is part of a proposal to add a string filter to ebtables, which would be similar to the string filter in iptables. Like iptables, the ebtables filter uses the xt_string module, however some modifications have been made for this to work correctly. Currently ebtables assumes that the revision number of all match modules is 0. The xt_string module doesn't register a match with revision 0 so the solution is to modify ebtables to allow extensions to specify a revision number, similar to iptables. This gets passed down to the kernel, which is then able to find the match module correctly. Signed-off-by: Bernie Harris Signed-off-by: Pablo Neira Ayuso --- extensions/Makefile | 2 +- extensions/ebt_string.c | 319 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 320 insertions(+), 1 deletion(-) create mode 100644 extensions/ebt_string.c (limited to 'extensions') diff --git a/extensions/Makefile b/extensions/Makefile index b3548e8..60a70a2 100644 --- a/extensions/Makefile +++ b/extensions/Makefile @@ -1,7 +1,7 @@ #! /usr/bin/make EXT_FUNC+=802_3 nat arp arpreply ip ip6 standard log redirect vlan mark_m mark \ - pkttype stp among limit ulog nflog + pkttype stp among limit ulog nflog string EXT_TABLES+=filter nat broute EXT_OBJS+=$(foreach T,$(EXT_FUNC), extensions/ebt_$(T).o) EXT_OBJS+=$(foreach T,$(EXT_TABLES), extensions/ebtable_$(T).o) diff --git a/extensions/ebt_string.c b/extensions/ebt_string.c new file mode 100644 index 0000000..793f5df --- /dev/null +++ b/extensions/ebt_string.c @@ -0,0 +1,319 @@ +/* ebt_string + * + * Author: + * Bernie Harris + * + * February, 2018 + * + * Based on: + * libxt_string.c, Copyright (C) 2000 Emmanuel Roger + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../include/ebtables_u.h" +#include +#include + +#define STRING_FROM '1' +#define STRING_TO '2' +#define STRING_ALGO '3' +#define STRING_ICASE '4' +#define STRING '5' +#define STRING_HEX '6' +#define OPT_STRING_FROM (1 << 0) +#define OPT_STRING_TO (1 << 1) +#define OPT_STRING_ALGO (1 << 2) +#define OPT_STRING_ICASE (1 << 3) +#define OPT_STRING (1 << 4) +#define OPT_STRING_HEX (1 << 5) + +static const struct option opts[] = +{ + { "string-from" , required_argument, 0, STRING_FROM }, + { "string-to" , required_argument, 0, STRING_TO }, + { "string-algo" , required_argument, 0, STRING_ALGO }, + { "string-icase" , no_argument, 0, STRING_ICASE }, + { "string" , required_argument, 0, STRING }, + { "string-hex" , required_argument, 0, STRING_HEX }, + { 0 } +}; + +static void print_help() +{ + printf( +"string options:\n" +"--string-from offset : Offset to start searching from (default: 0)\n" +"--string-to offset : Offset to stop searching (default: packet size)\n" +"--string-algo algorithm : Algorithm (bm = Boyer-Moore, kmp = Knuth-Pratt-Morris)\n" +"--string-icase : Ignore case when searching\n" +"--string [!] string : Match a string in a packet\n" +"--string-hex [!] string : Match a hex string in a packet, e.g. |0D 0A|, |0D0A|, netfilter|03|org\n"); +} + +static void init(struct ebt_entry_match *match) +{ + struct xt_string_info *info = (struct xt_string_info *)match->data; + + info->to_offset = UINT16_MAX; +} + +static void parse_string(const char *s, struct xt_string_info *info) +{ + /* xt_string does not need \0 at the end of the pattern */ + if (strlen(s) <= XT_STRING_MAX_PATTERN_SIZE) { + strncpy(info->pattern, s, XT_STRING_MAX_PATTERN_SIZE); + info->patlen = strnlen(s, XT_STRING_MAX_PATTERN_SIZE); + return; + } + ebt_print_error2("STRING too long \"%s\"", s); +} + +static void parse_hex_string(const char *s, struct xt_string_info *info) +{ + int i=0, slen, sindex=0, schar; + short hex_f = 0, literal_f = 0; + char hextmp[3]; + + slen = strlen(s); + + if (slen == 0) { + ebt_print_error2("STRING must contain at least one char"); + } + + while (i < slen) { + if (s[i] == '\\' && !hex_f) { + literal_f = 1; + } else if (s[i] == '\\') { + ebt_print_error2("Cannot include literals in hex data"); + } else if (s[i] == '|') { + if (hex_f) + hex_f = 0; + else { + hex_f = 1; + /* get past any initial whitespace just after the '|' */ + while (s[i+1] == ' ') + i++; + } + if (i+1 >= slen) + break; + else + i++; /* advance to the next character */ + } + + if (literal_f) { + if (i+1 >= slen) { + ebt_print_error2("Bad literal placement at end of string"); + } + info->pattern[sindex] = s[i+1]; + i += 2; /* skip over literal char */ + literal_f = 0; + } else if (hex_f) { + if (i+1 >= slen) { + ebt_print_error2("Odd number of hex digits"); + } + if (i+2 >= slen) { + /* must end with a "|" */ + ebt_print_error2("Invalid hex block"); + } + if (! isxdigit(s[i])) /* check for valid hex char */ + ebt_print_error2("Invalid hex char '%c'", s[i]); + if (! isxdigit(s[i+1])) /* check for valid hex char */ + ebt_print_error2("Invalid hex char '%c'", s[i+1]); + hextmp[0] = s[i]; + hextmp[1] = s[i+1]; + hextmp[2] = '\0'; + if (! sscanf(hextmp, "%x", &schar)) + ebt_print_error2("Invalid hex char `%c'", s[i]); + info->pattern[sindex] = (char) schar; + if (s[i+2] == ' ') + i += 3; /* spaces included in the hex block */ + else + i += 2; + } else { /* the char is not part of hex data, so just copy */ + info->pattern[sindex] = s[i]; + i++; + } + if (sindex > XT_STRING_MAX_PATTERN_SIZE) + ebt_print_error2("STRING too long \"%s\"", s); + sindex++; + } + info->patlen = sindex; +} + +static int parse(int c, char **argv, int argc, const struct ebt_u_entry *entry, + unsigned int *flags, struct ebt_entry_match **match) +{ + struct xt_string_info *info = (struct xt_string_info *)(*match)->data; + int i; + int input_string_length = 0; + char buf[3] = { 0 }; + + switch (c) { + case STRING_FROM: + ebt_check_option2(flags, OPT_STRING_FROM); + if (ebt_check_inverse2(optarg)) + ebt_print_error2("Unexpected `!' after --string-from"); + info->from_offset = (__u16)strtoul(optarg, NULL, 10); + break; + case STRING_TO: + ebt_check_option2(flags, OPT_STRING_TO); + if (ebt_check_inverse2(optarg)) + ebt_print_error2("Unexpected `!' after --string-to"); + info->to_offset = (__u16)strtoul(optarg, NULL, 10); + break; + case STRING_ALGO: + ebt_check_option2(flags, OPT_STRING_ALGO); + if (ebt_check_inverse2(optarg)) + ebt_print_error2("Unexpected `!' after --string-algo"); + strncpy(info->algo, optarg, XT_STRING_MAX_ALGO_NAME_SIZE); + break; + case STRING_ICASE: + ebt_check_option2(flags, OPT_STRING_ICASE); + if (ebt_check_inverse2(optarg)) + ebt_print_error2("Unexpected `!' after --string-icase"); + info->u.v1.flags |= XT_STRING_FLAG_IGNORECASE; + break; + case STRING: + ebt_check_option2(flags, OPT_STRING); + parse_string(optarg, info); + if (ebt_check_inverse2(optarg)) { + info->u.v1.flags |= XT_STRING_FLAG_INVERT; + } + break; + case STRING_HEX: + ebt_check_option2(flags, OPT_STRING_HEX); + parse_hex_string(optarg, info); + if (ebt_check_inverse2(optarg)) { + info->u.v1.flags |= XT_STRING_FLAG_INVERT; + } + break; + default: + return 0; + } + return 1; +} + +static void final_check(const struct ebt_u_entry *entry, + const struct ebt_entry_match *match, const char *name, + unsigned int hookmask, unsigned int time) +{ + struct xt_string_info *info = (struct xt_string_info *)match->data; + + if (info->to_offset < info->from_offset) { + ebt_print_error2("'to' offset should not be less than 'from' " + "offset"); + } +} + +/* Test to see if the string contains non-printable chars or quotes */ +static unsigned short int is_hex_string(const char *str, + const unsigned short int len) +{ + unsigned int i; + for (i=0; i < len; i++) { + if (! isprint(str[i])) { + /* string contains at least one non-printable char */ + return 1; + } + } + /* use hex output if the last char is a "\" */ + if (str[len-1] == '\\') + return 1; + return 0; +} + +/* Print string with "|" chars included as one would pass to --string-hex */ +static void print_hex_string(const char *str, const unsigned short int len) +{ + unsigned int i; + /* start hex block */ + printf("\"|"); + for (i=0; i < len; i++) + printf("%02x", (unsigned char)str[i]); + /* close hex block */ + printf("|\" "); +} + +static void print_string(const char *str, const unsigned short int len) +{ + unsigned int i; + printf("\""); + for (i=0; i < len; i++) { + if (str[i] == '\"' || str[i] == '\\') + putchar('\\'); + printf("%c", (unsigned char) str[i]); + } + printf("\" "); /* closing quote */ +} + +static void print(const struct ebt_u_entry *entry, + const struct ebt_entry_match *match) +{ + const struct xt_string_info *info = + (const struct xt_string_info *) match->data; + int invert = info->u.v1.flags & XT_STRING_FLAG_INVERT; + + if (is_hex_string(info->pattern, info->patlen)) { + printf("--string-hex %s", invert ? "! " : ""); + print_hex_string(info->pattern, info->patlen); + } else { + printf("--string %s", invert ? "! " : ""); + print_string(info->pattern, info->patlen); + } + printf("--string-algo %s ", info->algo); + if (info->from_offset != 0) + printf("--string-from %u ", info->from_offset); + if (info->to_offset != 0) + printf("--string-to %u ", info->to_offset); + if (info->u.v1.flags & XT_STRING_FLAG_IGNORECASE) + printf("--string-icase "); +} + +static int compare(const struct ebt_entry_match *m1, + const struct ebt_entry_match *m2) +{ + const struct xt_string_info *info1 = + (const struct xt_string_info *) m1->data; + const struct xt_string_info *info2 = + (const struct xt_string_info *) m2->data; + + if (info1->from_offset != info2->from_offset) + return 0; + if (info1->to_offset != info2->to_offset) + return 0; + if (info1->u.v1.flags != info2->u.v1.flags) + return 0; + if (info1->patlen != info2->patlen) + return 0; + if (strncmp (info1->algo, info2->algo, XT_STRING_MAX_ALGO_NAME_SIZE) != 0) + return 0; + if (strncmp (info1->pattern, info2->pattern, info1->patlen) != 0) + return 0; + + return 1; +} + +static struct ebt_u_match string_match = +{ + .name = "string", + .revision = 1, + .size = sizeof(struct xt_string_info), + .help = print_help, + .init = init, + .parse = parse, + .final_check = final_check, + .print = print, + .compare = compare, + .extra_ops = opts, +}; + +void _init(void) +{ + ebt_register_match(&string_match); +} -- cgit v1.2.3