/* * "ts3init" extension for Xtables * * Description: A module to aid in ts3 spoof protection * This is the "target" code * * Authors: * Niels Werensteijn , 2016-10-03 * Maximilian Muenchow , 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 #ifdef CONFIG_BRIDGE_NETFILTER # include #endif #include #include #include #include #include #include "compat_xtables.h" #include "ts3init_random_seed.h" #include "ts3init_cookie.h" #include "ts3init_target.h" #include "ts3init_header.h" #include "ts3init_cache.h" /* * Send a reply back to the client */ static bool ts3init_send_ipv6_reply(struct sk_buff *oldskb, const struct xt_action_param *par, const struct ipv6hdr *oldip, const struct udphdr *oldudp, const void* payload, const size_t payload_size) { struct sk_buff *skb; struct ipv6hdr *ip; struct udphdr *udp; struct flowi6 fl; struct dst_entry *dst = NULL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) struct net *net = dev_net((xt_in(par) != NULL) ? xt_in(par) : xt_out(par)); #else struct net *net = dev_net((par->in != NULL) ? par->in : par->out); #endif skb = alloc_skb(LL_MAX_HEADER + sizeof(*ip) + sizeof(*udp) + payload_size, GFP_ATOMIC); if (skb == NULL) return false; skb_reserve(skb, LL_MAX_HEADER); skb->protocol = oldskb->protocol; skb_reset_network_header(skb); ip = (void *)skb_put(skb, sizeof(*ip)); ip->version = oldip->version; ip->priority = oldip->priority; memcpy(ip->flow_lbl, oldip->flow_lbl, sizeof(ip->flow_lbl)); ip->nexthdr = par->target->proto; ip->saddr = oldip->daddr; ip->daddr = oldip->saddr; skb_reset_transport_header(skb); udp = (void *)skb_put(skb, sizeof(*udp)); udp->source = oldudp->dest; udp->dest = oldudp->source; udp->len = htons(sizeof(*udp) + payload_size); memcpy(skb_put(skb, payload_size), payload, payload_size); udp->check = 0; udp->check = csum_ipv6_magic(&ip->saddr, &ip->daddr, ntohs(udp->len), IPPROTO_UDP, csum_partial(udp, ntohs(udp->len), 0)); memset(&fl, 0, sizeof(fl)); fl.flowi6_proto = ip->nexthdr; memcpy(&fl.saddr, &ip->saddr, sizeof(fl.saddr)); memcpy(&fl.daddr, &ip->daddr, sizeof(fl.daddr)); fl.fl6_sport = udp->source; fl.fl6_dport = udp->dest; security_skb_classify_flow((struct sk_buff *)oldskb, flowi6_to_flowi(&fl)); dst = ip6_route_output(net, NULL, &fl); if (dst == NULL || dst->error != 0) { dst_release(dst); goto free_nskb; } skb_dst_set(skb, dst); ip->hop_limit = ip6_dst_hoplimit(skb_dst(skb)); skb->ip_summed = CHECKSUM_NONE; /* "Never happens" (?) */ if (skb->len > dst_mtu(skb_dst(skb))) goto free_nskb; nf_ct_attach(skb, oldskb); ip6_local_out(par_net(par), skb->sk, skb); return true; free_nskb: kfree_skb(skb); return false; } /* * Send a reply back to the client */ static bool ts3init_send_ipv4_reply(struct sk_buff *oldskb, const struct xt_action_param *par, const struct iphdr *oldip, const struct udphdr *oldudp, const void* payload, const size_t payload_size) { struct sk_buff *skb; struct iphdr *ip; struct udphdr *udp; skb = alloc_skb(LL_MAX_HEADER + sizeof(*ip) + sizeof(*udp) + payload_size, GFP_ATOMIC); if (skb == NULL) return false; skb_reserve(skb, LL_MAX_HEADER); skb->protocol = oldskb->protocol; skb_reset_network_header(skb); ip = (void *)skb_put(skb, sizeof(*ip)); ip->version = oldip->version; ip->ihl = sizeof(*ip) / 4; ip->tos = oldip->tos; ip->id = 0; ip->frag_off = htons(IP_DF); ip->protocol = oldip->protocol; ip->check = 0; ip->saddr = oldip->daddr; ip->daddr = oldip->saddr; skb_reset_transport_header(skb); udp = (void *)skb_put(skb, sizeof(*udp)); udp->source = oldudp->dest; udp->dest = oldudp->source; udp->len = htons(sizeof(*udp) + payload_size); memcpy(skb_put(skb, payload_size), payload, payload_size); udp->check = 0; udp->check = csum_tcpudp_magic(ip->saddr, ip->daddr, ntohs(udp->len), IPPROTO_UDP, csum_partial(udp, ntohs(udp->len), 0)); /* ip_route_me_harder expects the skb's dst to be set */ skb_dst_set(skb, dst_clone(skb_dst(oldskb))); if (ip_route_me_harder(par_net(par), skb, RTN_UNSPEC) != 0) goto free_nskb; ip->ttl = ip4_dst_hoplimit(skb_dst(skb)); skb->ip_summed = CHECKSUM_NONE; /* "Never happens" (?) */ if (skb->len > dst_mtu(skb_dst(skb))) goto free_nskb; nf_ct_attach(skb, oldskb); ip_local_out(par_net(par), skb->sk, skb); return true; free_nskb: kfree_skb(skb); return false; } /* The payload replied by TS3INIT_RESET. */ static const char ts3init_reset_packet[] = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1', 0, 0x65, 0x88, COMMAND_RESET, 0 }; /* * The 'TS3INIT_RESET' target handler. * Always replies with COMMAND_RESET and drops the packet */ static unsigned int ts3init_reset_ipv4_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct iphdr *ip; struct udphdr *udp, udp_buf; ip = ip_hdr(skb); udp = skb_header_pointer(skb, par->thoff, sizeof(*udp), &udp_buf); if (udp == NULL || ntohs(udp->len) <= sizeof(*udp)) return NF_DROP; ts3init_send_ipv4_reply(skb, par, ip, udp, ts3init_reset_packet, sizeof(ts3init_reset_packet)); return NF_DROP; } /* * The 'TS3INIT_RESET' target handler. * Always replies with COMMAND_RESET and drops the packet. */ static unsigned int ts3init_reset_ipv6_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct ipv6hdr *ip; struct udphdr *udp, udp_buf; ip = ipv6_hdr(skb); udp = skb_header_pointer(skb, par->thoff, sizeof(*udp), &udp_buf); if (udp == NULL || ntohs(udp->len) <= sizeof(*udp)) return NF_DROP; ts3init_send_ipv6_reply(skb, par, ip, udp, ts3init_reset_packet, sizeof(ts3init_reset_packet)); return NF_DROP; } /* The header replied by TS3INIT_SET_COOKIE. */ static const char ts3init_set_cookie_packet_header[TS3INIT_HEADER_SERVER_LENGTH] = {'T', 'S', '3', 'I', 'N', 'I', 'T', '1', 0, 0x65, 0x88, COMMAND_SET_COOKIE }; /* * Returns the current cookie. */ static bool ts3init_generate_cookie_ipv4(const struct xt_action_param *par, const struct iphdr *ip, const struct udphdr *udp, u64 *cookie, u8 *packet_index) { const struct xt_ts3init_set_cookie_tginfo *info = par->targinfo; __u64 cookie_seed[2]; if (ts3init_get_current_cookie_seed(info->random_seed, &cookie_seed, packet_index) == false) return false; if (ts3init_calculate_cookie_ipv4(ip, udp, cookie_seed[0], cookie_seed[1], cookie)) return false; return true; } /* * Returns the current cookie. */ static bool ts3init_generate_cookie_ipv6(const struct xt_action_param *par, const struct ipv6hdr *ip, const struct udphdr *udp, u64 *cookie, u8 *packet_index) { const struct xt_ts3init_set_cookie_tginfo *info = par->targinfo; __u64 cookie_seed[2]; if (ts3init_get_current_cookie_seed(info->random_seed, &cookie_seed, packet_index) == false) return false; if (ts3init_calculate_cookie_ipv6(ip, udp, cookie_seed[0], cookie_seed[1], cookie)) return false; return true; } /* * Fills 'newpayload' with a TS3INIT_SET_COOKIE packet. */ static bool ts3init_fill_set_cookie_payload(const struct sk_buff *skb, const struct xt_action_param *par, const u64 cookie, const u8 packet_index, u8 *newpayload) { const struct xt_ts3init_set_cookie_tginfo *info = par->targinfo; u8 *payload, payload_buf[34]; memcpy(newpayload, ts3init_set_cookie_packet_header, sizeof(ts3init_set_cookie_packet_header)); newpayload[12] = (u8)cookie; newpayload[13] = (u8)(cookie >> 8); newpayload[14] = (u8)(cookie >> 16); newpayload[15] = (u8)(cookie >> 24); newpayload[16] = (u8)(cookie >> 32); newpayload[17] = (u8)(cookie >> 40); newpayload[18] = (u8)(cookie >> 48); newpayload[19] = (u8)(cookie >> 56); newpayload[20] = packet_index; if (info->specific_options & TARGET_SET_COOKIE_ZERO_RANDOM_SEQUENCE) { memset(&newpayload[21], 0, 11); } else { memset(&newpayload[21], 0, 7); payload = skb_header_pointer(skb, par->thoff + sizeof(struct udphdr), sizeof(payload_buf), payload_buf); if (payload == NULL) { printk(KERN_WARNING KBUILD_MODNAME ": was expecting a ts3init_get_cookie packet. Use -m ts3init_get_cookie!\n"); return false; } newpayload[28] = payload[25]; newpayload[29] = payload[24]; newpayload[30] = payload[23]; newpayload[31] = payload[22]; } return true; } /* * The 'TS3INIT_SET_COOKIE' target handler. * Always replies with TS3INIT_SET_COOKIE and drops the packet. */ static unsigned int ts3init_set_cookie_ipv4_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct iphdr *ip; struct udphdr *udp, udp_buf; u64 cookie; u8 packet_index; u8 payload[sizeof(ts3init_set_cookie_packet_header) + 20]; ip = ip_hdr(skb); udp = skb_header_pointer(skb, par->thoff, sizeof(*udp), &udp_buf); if (udp == NULL || ntohs(udp->len) <= sizeof(*udp)) return NF_DROP; if (ts3init_generate_cookie_ipv4(par, ip, udp, &cookie, &packet_index) && ts3init_fill_set_cookie_payload(skb, par, cookie, packet_index, payload)) { ts3init_send_ipv4_reply(skb, par, ip, udp, payload, sizeof(payload)); } return NF_DROP; } /* * The 'TS3INIT_SET_COOKIE' target handler. * Always replies with TS3INIT_SET_COOKIE and drops the packet. */ static unsigned int ts3init_set_cookie_ipv6_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct ipv6hdr *ip; struct udphdr *udp, udp_buf; u64 cookie; u8 packet_index; u8 payload[sizeof(ts3init_set_cookie_packet_header) + 20]; ip = ipv6_hdr(skb); udp = skb_header_pointer(skb, par->thoff, sizeof(*udp), &udp_buf); if (udp == NULL || ntohs(udp->len) <= sizeof(*udp)) return NF_DROP; if (ts3init_generate_cookie_ipv6(par, ip, udp, &cookie, &packet_index) && ts3init_fill_set_cookie_payload(skb, par, cookie, packet_index, payload)) { ts3init_send_ipv6_reply(skb, par, ip, udp, payload, sizeof(payload)); } return NF_DROP; } /* * Validates targinfo recieved from userspace. */ static int ts3init_set_cookie_tg_check(const struct xt_tgchk_param *par) { struct xt_ts3init_set_cookie_tginfo *info = par->targinfo; if (! (par->family == NFPROTO_IPV4 || par->family == NFPROTO_IPV6)) { printk(KERN_INFO KBUILD_MODNAME ": invalid protocol (only ipv4 and ipv6) for TS3INIT_SET_COOKIE\n"); return -EINVAL; } if (info->common_options & ~(TARGET_COMMON_VALID_MASK)) { printk(KERN_INFO KBUILD_MODNAME ": invalid (common) options for TS3INIT_SET_COOKIE\n"); return -EINVAL; } if (info->specific_options & ~(TARGET_SET_COOKIE_VALID_MASK)) { printk(KERN_INFO KBUILD_MODNAME ": invalid (specific) options for TS3INIT_SET_COOKIE\n"); return -EINVAL; } return 0; } static inline void ts3init_fill_get_cookie_payload(u8 *payload) { time_t current_unix_time = ts3init_get_cached_unix_time(); payload[TS3INIT_HEADER_CLIENT_LENGTH - 1] = COMMAND_GET_COOKIE; payload[TS3INIT_HEADER_CLIENT_LENGTH + 0] = current_unix_time >> 24; payload[TS3INIT_HEADER_CLIENT_LENGTH + 1] = current_unix_time >> 16; payload[TS3INIT_HEADER_CLIENT_LENGTH + 2] = current_unix_time >> 8; payload[TS3INIT_HEADER_CLIENT_LENGTH + 3] = current_unix_time; get_random_bytes(&payload[TS3INIT_HEADER_CLIENT_LENGTH + 4], 4); memset(&payload[TS3INIT_HEADER_CLIENT_LENGTH + 8], 0, 8); } /* * The 'TS3INIT_GET_COOKIE' target handler. * Morphes the incomming packet into a TS3INIT_GET_COOKIE */ static unsigned int ts3init_get_cookie_ipv4_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct iphdr *ip; struct udphdr *udp, udp_buf; u8 *payload, payload_buf[TS3INIT_HEADER_CLIENT_LENGTH + 16]; int delta; int new_udp_len; ip = ip_hdr(skb); udp = skb_header_pointer(skb, par->thoff, sizeof(udp_buf), &udp_buf); if (udp == NULL) return NF_DROP; if (ip->frag_off & htons(IP_OFFSET)) return NF_DROP; new_udp_len = sizeof(*udp) + sizeof(payload_buf); delta = new_udp_len - ntohs(udp->len); if (delta < 0) { skb_trim(skb, skb->len + delta); } else { if (skb_put_padto(skb, skb->len + delta)) return NF_STOLEN; } if (!skb_make_writable(skb, skb->len)) return NF_DROP; payload = skb_header_pointer(skb, par->thoff + sizeof(*udp), sizeof(payload_buf), payload_buf); ts3init_fill_get_cookie_payload(payload); udp->len = htons(new_udp_len); udp->check = 0; udp->check = csum_tcpudp_magic(ip->saddr, ip->daddr, new_udp_len, IPPROTO_UDP, csum_partial(udp, new_udp_len, 0)); ip->tot_len = htons( ntohs(ip->tot_len) + delta ); ip_send_check(ip); if (skb->len > dst_mtu(skb_dst(skb))) return NF_DROP; return NF_ACCEPT; } /* * The 'TS3INIT_GET_COOKIE' target handler. * Morphes the incomming packet into a TS3INIT_GET_COOKIE */ static unsigned int ts3init_get_cookie_ipv6_tg(struct sk_buff *skb, const struct xt_action_param *par) { struct ipv6hdr *ip; struct udphdr *udp, udp_buf; u8 *payload, payload_buf[TS3INIT_HEADER_CLIENT_LENGTH + 16]; int delta; int new_udp_len; ip = ipv6_hdr(skb); udp = skb_header_pointer(skb, par->thoff, sizeof(udp_buf), &udp_buf); if (udp == NULL) return NF_DROP; new_udp_len = sizeof(*udp) + sizeof(payload_buf); delta = new_udp_len - ntohs(udp->len); if (delta < 0) { skb_trim(skb, skb->len + delta); } else { if (skb_put_padto(skb, skb->len + delta)) return NF_STOLEN; } if (!skb_make_writable(skb, skb->len)) return NF_DROP; payload = skb_header_pointer(skb, par->thoff + sizeof(*udp), sizeof(payload_buf), payload_buf); ts3init_fill_get_cookie_payload(payload); udp->len = htons(new_udp_len); udp->check = 0; udp->check = csum_ipv6_magic(&ip->saddr, &ip->daddr, new_udp_len, IPPROTO_UDP, csum_partial(udp, new_udp_len, 0)); ip->payload_len = htons( ntohs(ip->payload_len) + delta ); if (skb->len > dst_mtu(skb_dst(skb))) return NF_DROP; return NF_ACCEPT; } static struct xt_target ts3init_tg_reg[] __read_mostly = { { .name = "TS3INIT_RESET", .revision = 0, .family = NFPROTO_IPV4, .proto = IPPROTO_UDP, .target = ts3init_reset_ipv4_tg, .me = THIS_MODULE, }, { .name = "TS3INIT_RESET", .revision = 0, .family = NFPROTO_IPV6, .proto = IPPROTO_UDP, .target = ts3init_reset_ipv6_tg, .me = THIS_MODULE, }, { .name = "TS3INIT_SET_COOKIE", .revision = 0, .family = NFPROTO_IPV4, .proto = IPPROTO_UDP, .targetsize = sizeof(struct xt_ts3init_set_cookie_tginfo), .target = ts3init_set_cookie_ipv4_tg, .checkentry = ts3init_set_cookie_tg_check, .me = THIS_MODULE, }, { .name = "TS3INIT_SET_COOKIE", .revision = 0, .family = NFPROTO_IPV6, .proto = IPPROTO_UDP, .targetsize = sizeof(struct xt_ts3init_set_cookie_tginfo), .target = ts3init_set_cookie_ipv6_tg, .checkentry = ts3init_set_cookie_tg_check, .me = THIS_MODULE, }, { .name = "TS3INIT_GET_COOKIE", .revision = 0, .family = NFPROTO_IPV4, .proto = IPPROTO_UDP, .target = ts3init_get_cookie_ipv4_tg, .me = THIS_MODULE, }, { .name = "TS3INIT_GET_COOKIE", .revision = 0, .family = NFPROTO_IPV6, .proto = IPPROTO_UDP, .target = ts3init_get_cookie_ipv6_tg, .me = THIS_MODULE, }, }; int __init ts3init_target_init(void) { return xt_register_targets(ts3init_tg_reg, ARRAY_SIZE(ts3init_tg_reg)); } void ts3init_target_exit(void) { xt_unregister_targets(ts3init_tg_reg, ARRAY_SIZE(ts3init_tg_reg)); }