diff --git a/README.md b/README.md index fcd1578..7692335 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,23 @@ ts3init_get_puzzle match options: `check-cookie` is specified, either `random-seed` or `random-seed-file` needs to be specified too. +ts3init +-------------------- +Matches a ts3init packet, by checking if the packet starts with the *TS3INIT1*. +Additional header checks for client and server packets can be specified: +``` +$ iptables -m ts3init -h +<..> +ts3init match options: + --client Match ts3init client packets. + --server Match ts3init server packets. + --command Match packets with the specified command. +``` +* `client` checks that the packet has a valid ts3init client header +* `server` checks that the packet has a valid ts3init server header +* `command` checks that the packet has the specified command in its header. + Requires either --client or --server. + Target extensions ================= diff --git a/src/Makefile.xtables b/src/Makefile.xtables index 7c22d79..e8babb8 100644 --- a/src/Makefile.xtables +++ b/src/Makefile.xtables @@ -1,5 +1,5 @@ CFLAGS = -O2 -Wall -LIBS = libxt_ts3init_get_cookie.so libxt_ts3init_get_puzzle.so libxt_TS3INIT_RESET.so libxt_TS3INIT_SET_COOKIE.so libxt_TS3INIT_GET_COOKIE.so +LIBS = libxt_ts3init.so libxt_ts3init_get_cookie.so libxt_ts3init_get_puzzle.so libxt_TS3INIT_RESET.so libxt_TS3INIT_SET_COOKIE.so libxt_TS3INIT_GET_COOKIE.so all: $(LIBS) clean: diff --git a/src/libxt_ts3init.c b/src/libxt_ts3init.c new file mode 100644 index 0000000..20c3cb4 --- /dev/null +++ b/src/libxt_ts3init.c @@ -0,0 +1,146 @@ +/* + * "ts3init" match extension for iptables + * Niels Werensteijn , 2016-10-03 + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License; either version 2 + * or 3 of the License, as published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ts3init_random_seed.h" +#include "ts3init_match.h" + +#define param_act(t, s, f) xtables_param_act((t), "ts3init", (s), (f)) +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) + +static void ts3init_help(void) +{ + printf( + "ts3init match options:\n" + " --client Match ts3init client packets.\n" + " --server Match ts3init server packets.\n" + " --command Match packets with the specified command.\n" + ); +} + +static const struct option ts3init_opts[] = { + {.name = "client", .has_arg = false, .val = '1'}, + {.name = "server", .has_arg = false, .val = '2'}, + {.name = "command", .has_arg = true, .val = '3'}, + {NULL}, +}; + +static int ts3init_parse(int c, char **argv, int invert, unsigned int *flags, + const void *entry, struct xt_entry_match **match) +{ + struct xt_ts3init_mtinfo *info = (void *)(*match)->data; + int command; + + switch (c) { + case '1': + param_act(XTF_ONLY_ONCE, "--client", info->specific_options & CHK_TS3INIT_CLIENT); + param_act(XTF_NO_INVERT, "--client", invert); + info->specific_options |= CHK_TS3INIT_CLIENT; + *flags |= CHK_TS3INIT_CLIENT; + return true; + + case '2': + param_act(XTF_ONLY_ONCE, "--server", info->specific_options & CHK_TS3INIT_SERVER); + param_act(XTF_NO_INVERT, "--server", invert); + info->specific_options |= CHK_TS3INIT_SERVER; + *flags |= CHK_TS3INIT_SERVER; + return true; + + case '3': + param_act(XTF_ONLY_ONCE, "--random-seed", info->specific_options & CHK_TS3INIT_COMMAND); + param_act(XTF_NO_INVERT, "--random-seed", invert); + command = atoi(optarg); + if (command < 0 || command > 255) + xtables_error(PARAMETER_PROBLEM, + "ts3init: invalid command number"); + info->specific_options |= CHK_TS3INIT_COMMAND; + info->command = (__u8)command; + *flags |= CHK_TS3INIT_COMMAND; + return true; + + default: + return false; + } +} + +static void ts3init_save(const void *ip, const struct xt_entry_match *match) +{ + const struct xt_ts3init_mtinfo *info = (const void *)match->data; + if (info->specific_options & CHK_TS3INIT_CLIENT) + { + printf("--client "); + } + if (info->specific_options & CHK_TS3INIT_SERVER) + { + printf("--server "); + } + if (info->specific_options & CHK_TS3INIT_COMMAND) + { + printf("--command %i ", (int)info->command); + } +} + +static void ts3init_print(const void *ip, const struct xt_entry_match *match, + int numeric) +{ + printf(" -m ts3init "); + ts3init_save(ip, match); +} + +static void ts3init_check(unsigned int flags) +{ + bool client = flags & CHK_TS3INIT_CLIENT; + bool server = flags & CHK_TS3INIT_SERVER; + if (client && server) + { + xtables_error(PARAMETER_PROBLEM, + "ts3init_: --client and --server can not be specified at the same time"); + } + if (flags & CHK_TS3INIT_COMMAND) + { + if (!client && !server) + { + xtables_error(PARAMETER_PROBLEM, + "ts3init: --command requires either --client or --server"); + } + } +} + +/* register and init */ +static struct xtables_match ts3init_mt_reg[] = +{ + { + .name = "ts3init", + .revision = 0, + .family = NFPROTO_UNSPEC, + .version = XTABLES_VERSION, + .size = XT_ALIGN(sizeof(struct xt_ts3init_mtinfo)), + .userspacesize = XT_ALIGN(sizeof(struct xt_ts3init_mtinfo)), + .help = ts3init_help, + .parse = ts3init_parse, + .print = ts3init_print, + .save = ts3init_save, + .extra_opts = ts3init_opts, + .final_check = ts3init_check, + }, +}; + +static __attribute__((constructor)) void ts3init_mt_ldr(void) +{ + xtables_register_matches(ts3init_mt_reg, ARRAY_SIZE(ts3init_mt_reg)); +} diff --git a/src/ts3init_header.h b/src/ts3init_header.h index 762a401..7e73b26 100644 --- a/src/ts3init_header.h +++ b/src/ts3init_header.h @@ -22,7 +22,7 @@ struct ts3_init_header_tag /* * Header of a TS3INIT client packet. */ -struct ts3_init_header +struct ts3_init_client_header { struct ts3_init_header_tag tag; __be16 packet_id; @@ -30,7 +30,17 @@ struct ts3_init_header __u8 flags; __u8 client_version[4]; __u8 command; - __u8 payload[20]; +}; + +/* + * Header of a TS3INIT server packet. + */ +struct ts3_init_server_header +{ + struct ts3_init_header_tag tag; + __be16 packet_id; + __u8 flags; + __u8 command; }; /* diff --git a/src/ts3init_match.c b/src/ts3init_match.c index 30082bd..36bf18f 100644 --- a/src/ts3init_match.c +++ b/src/ts3init_match.c @@ -31,35 +31,39 @@ static const struct ts3_init_header_tag ts3init_header_tag_signature = { .tag8 = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1'} }; -struct ts3_init_checked_header_data +struct ts3_init_checked_client_header_data { struct udphdr *udp, udp_buf; - struct ts3_init_header* ts3_header, ts3_header_buf; + struct ts3_init_client_header* ts3_header, ts3_header_buf; }; -static int ts3init_payload_sizes[] = { 16, 20, 20, 244, -1, 1 }; +struct ts3_init_checked_server_header_data +{ + struct udphdr *udp, udp_buf; + struct ts3_init_server_header* ts3_header, ts3_header_buf; +}; + +static const int ts3init_payload_sizes[] = { 16, 20, 20, 244, -1, 1 }; /* * Check that skb contains a valid TS3INIT client header. * Also initializes header_data, and checks client version. */ -static bool check_header(const struct sk_buff *skb, const struct xt_action_param *par, - struct ts3_init_checked_header_data* header_data, __u32 min_client_version) +static bool check_client_header(const struct sk_buff *skb, const struct xt_action_param *par, + struct ts3_init_checked_client_header_data* header_data, __u32 min_client_version) { unsigned int data_len; struct udphdr *udp; - struct ts3_init_header* ts3_header; - int expected_payload_size; + struct ts3_init_client_header* ts3_header; udp = skb_header_pointer(skb, par->thoff, sizeof(*udp), &header_data->udp_buf); data_len = be16_to_cpu(udp->len) - sizeof(*udp); - if (data_len < TS3INIT_HEADER_CLIENT_LENGTH || - data_len > sizeof(header_data->ts3_header_buf)) + if (data_len < sizeof(header_data->ts3_header_buf)) return false; - ts3_header = (struct ts3_init_header*) skb_header_pointer(skb, - par->thoff + sizeof(*udp), data_len, + ts3_header = (struct ts3_init_client_header*) skb_header_pointer(skb, + par->thoff + sizeof(*udp), sizeof(header_data->ts3_header_buf), &header_data->ts3_header_buf); if (!ts3_header) return false; @@ -68,7 +72,6 @@ static bool check_header(const struct sk_buff *skb, const struct xt_action_param if (ts3_header->packet_id != cpu_to_be16(101)) return false; if (ts3_header->client_id != 0) return false; if (ts3_header->flags != 0x88) return false; - if (ts3_header->command >= COMMAND_MAX) return false; /* check min_client_version if needed */ if (min_client_version) @@ -84,16 +87,52 @@ static bool check_header(const struct sk_buff *skb, const struct xt_action_param return false; } - /* payload size check*/ - expected_payload_size = ts3init_payload_sizes[ts3_header->command]; - if (data_len != TS3INIT_HEADER_CLIENT_LENGTH + expected_payload_size) - return false; + header_data->udp = udp; + header_data->ts3_header = ts3_header; + return true; +} + +/* + * Check that skb contains a valid TS3INIT server header. + */ +static bool check_server_header(const struct sk_buff *skb, const struct xt_action_param *par, + struct ts3_init_checked_server_header_data* header_data) +{ + unsigned int data_len; + struct udphdr *udp; + struct ts3_init_server_header* ts3_header; + + udp = skb_header_pointer(skb, par->thoff, sizeof(*udp), &header_data->udp_buf); + data_len = be16_to_cpu(udp->len) - sizeof(*udp); + + if (data_len < sizeof(header_data->ts3_header_buf)) return false; + + ts3_header = (struct ts3_init_server_header*) skb_header_pointer(skb, + par->thoff + sizeof(*udp), sizeof(header_data->ts3_header_buf), + &header_data->ts3_header_buf); - header_data->udp = udp; + if (!ts3_header) return false; + + if (ts3_header->tag.tag64 != ts3init_header_tag_signature.tag64) return false; + if (ts3_header->packet_id != cpu_to_be16(101)) return false; + if (ts3_header->flags != 0x88) return false; + + header_data->udp = udp; header_data->ts3_header = ts3_header; return true; } +static inline char* get_payload(const struct sk_buff *skb, const struct xt_action_param *par, + const struct ts3_init_checked_client_header_data* header_data, + char *buf, size_t buf_size) +{ + const int header_len = sizeof(*header_data->udp) + sizeof(*header_data->ts3_header); + unsigned int data_len = be16_to_cpu(header_data->udp->len) - header_len; + if (data_len != buf_size) + return NULL; + return skb_header_pointer(skb, par->thoff + header_len, buf_size, buf); +} + /* * Hashes the cookie with source/destination address/port. */ @@ -143,24 +182,29 @@ static bool ts3init_get_cookie_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_ts3init_get_cookie_mtinfo *info = par->matchinfo; - struct ts3_init_checked_header_data header_data; + struct ts3_init_checked_client_header_data header_data; - if (!check_header(skb, par, &header_data, info->min_client_version)) + if (!check_client_header(skb, par, &header_data, info->min_client_version)) return false; if (header_data.ts3_header->command != COMMAND_GET_COOKIE) return false; if (info->specific_options & CHK_GET_COOKIE_CHECK_TIMESTAMP) { + char *payload, payload_buf[ts3init_payload_sizes[COMMAND_GET_COOKIE]]; time_t current_unix_time, packet_unix_time; + payload = get_payload(skb, par, &header_data, payload_buf, sizeof(payload_buf)); + if (!payload) + return false; + current_unix_time = ts3init_get_cached_unix_time(); packet_unix_time = - header_data.ts3_header->payload[0] << 24 | - header_data.ts3_header->payload[1] << 16 | - header_data.ts3_header->payload[2] << 8 | - header_data.ts3_header->payload[3]; + payload[0] << 24 | + payload[1] << 16 | + payload[2] << 8 | + payload[3]; if (abs(current_unix_time - packet_unix_time) > info->max_utc_offset) return false; @@ -204,20 +248,24 @@ static int ts3init_get_cookie_mt_check(const struct xt_mtchk_param *par) static bool ts3init_get_puzzle_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_ts3init_get_puzzle_mtinfo *info = par->matchinfo; - struct ts3_init_checked_header_data header_data; + struct ts3_init_checked_client_header_data header_data; - if (!check_header(skb, par, &header_data, info->min_client_version)) + if (!check_client_header(skb, par, &header_data, info->min_client_version)) return false; if (header_data.ts3_header->command != COMMAND_GET_PUZZLE) return false; if (info->specific_options & CHK_GET_PUZZLE_CHECK_COOKIE) { - struct ts3_init_header* ts3_header = header_data.ts3_header; + char *payload, payload_buf[ts3init_payload_sizes[COMMAND_GET_PUZZLE]]; __u64 cookie_seed[2]; __u64 cookie, packet_cookie; - if (ts3init_get_cookie_seed_for_packet_index(ts3_header->payload[8], info->random_seed, &cookie_seed) == false) + payload = get_payload(skb, par, &header_data, payload_buf, sizeof(payload_buf)); + if (!payload) + return false; + + if (ts3init_get_cookie_seed_for_packet_index(payload[8], info->random_seed, &cookie_seed) == false) return false; /* use cookie_seed and ipaddress and port to create a hash @@ -228,10 +276,10 @@ static bool ts3init_get_puzzle_mt(const struct sk_buff *skb, struct xt_action_pa /* compare cookie with payload bytes 0-7. if equal, cookie * is valid */ - packet_cookie = (((u64)((ts3_header->payload)[0])) | ((u64)((ts3_header->payload)[1]) << 8) | - ((u64)((ts3_header->payload)[2]) << 16) | ((u64)((ts3_header->payload)[3]) << 24) | - ((u64)((ts3_header->payload)[4]) << 32) | ((u64)((ts3_header->payload)[5]) << 40) | - ((u64)((ts3_header->payload)[6]) << 48) | ((u64)((ts3_header->payload)[7]) << 56)); + packet_cookie = (((u64)((payload)[0])) | ((u64)((payload)[1]) << 8) | + ((u64)((payload)[2]) << 16) | ((u64)((payload)[3]) << 24) | + ((u64)((payload)[4]) << 32) | ((u64)((payload)[5]) << 40) | + ((u64)((payload)[6]) << 48) | ((u64)((payload)[7]) << 56)); if (packet_cookie != cookie) return false; } @@ -266,6 +314,76 @@ static int ts3init_get_puzzle_mt_check(const struct xt_mtchk_param *par) return 0; } +/* + * The 'ts3init' match handler. + * Checks that the packet is a valid ts3init packet + */ +static bool ts3init_mt(const struct sk_buff *skb, struct xt_action_param *par) +{ + const struct xt_ts3init_mtinfo *info = par->matchinfo; + + if (info->specific_options & CHK_TS3INIT_CLIENT) + { + struct ts3_init_checked_client_header_data header_data; + + if (!check_client_header(skb, par, &header_data, 0)) + return false; + if (info->specific_options & CHK_TS3INIT_COMMAND) + { + if (header_data.ts3_header->command != info->command) + return false; + } + } + else if (info->specific_options & CHK_TS3INIT_SERVER) + { + struct ts3_init_checked_server_header_data header_data; + + if (!check_server_header(skb, par, &header_data)) + return false; + if (info->specific_options & CHK_TS3INIT_COMMAND) + { + if (header_data.ts3_header->command != info->command) + return false; + } + } + else + { + struct udphdr *udp, udp_buf; + u64 *signature, signature_buf; + + udp = skb_header_pointer(skb, par->thoff, sizeof(udp_buf), &udp_buf); + if (!udp) + return false; + signature = skb_header_pointer(skb, par->thoff + sizeof(*udp), + sizeof(signature_buf), &signature_buf); + + if (!signature || *signature != ts3init_header_tag_signature.tag64) + return false; + } + return true; +} + +/* + * Validates matchinfo recieved from userspace. + */ +static int ts3init_check(const struct xt_mtchk_param *par) +{ + struct xt_ts3init_get_puzzle_mtinfo *info = par->matchinfo; + + if (info->common_options & ~(CHK_COMMON_VALID_MASK)) + { + printk(KERN_ERR KBUILD_MODNAME ": invalid (common) options for ts3init\n"); + return -EINVAL; + } + + if (info->specific_options & ~(CHK_TS3INIT_VALID_MASK)) + { + printk(KERN_ERR KBUILD_MODNAME ": invalid (specific) options for ts3init\n"); + return -EINVAL; + } + + return 0; +} static struct xt_match ts3init_mt_reg[] __read_mostly = { @@ -309,6 +427,16 @@ static struct xt_match ts3init_mt_reg[] __read_mostly = .checkentry = ts3init_get_puzzle_mt_check, .me = THIS_MODULE, }, + { + .name = "ts3init", + .revision = 0, + .family = NFPROTO_UNSPEC, + .proto = IPPROTO_UDP, + .matchsize = sizeof(struct xt_ts3init_mtinfo), + .match = ts3init_mt, + .checkentry = ts3init_check, + .me = THIS_MODULE, + }, }; int ts3init_match_init(void) diff --git a/src/ts3init_match.h b/src/ts3init_match.h index f012619..f1f8d37 100644 --- a/src/ts3init_match.h +++ b/src/ts3init_match.h @@ -46,4 +46,20 @@ struct xt_ts3init_get_puzzle_mtinfo char random_seed_path[RANDOM_SEED_PATH_MAX]; }; +/* Enums and structs for generic ts3init */ +enum +{ + CHK_TS3INIT_CLIENT = 1 << 0, + CHK_TS3INIT_SERVER = 1 << 1, + CHK_TS3INIT_COMMAND = 1 << 2, + CHK_TS3INIT_VALID_MASK = (1 << 3) - 1, +}; + +struct xt_ts3init_mtinfo +{ + __u8 common_options; + __u8 specific_options; + __u16 reserved1; + __u8 command; +}; #endif /* _TS3INIT_MATCH_H */