/* * LSCAN match for Xtables * Copyright © Jan Engelhardt, 2006 - 2009 * * 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 as published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include "xt_lscan.h" #include "compat_xtables.h" #define PFX KBUILD_MODNAME ": " enum { TCP_FLAGS_ALL3 = TCP_FLAG_FIN | TCP_FLAG_RST | TCP_FLAG_SYN, TCP_FLAGS_ALL4 = TCP_FLAGS_ALL3 | TCP_FLAG_ACK, TCP_FLAGS_ALL6 = TCP_FLAGS_ALL4 | TCP_FLAG_PSH | TCP_FLAG_URG, }; /* Module parameters */ static unsigned int connmark_mask = ~0, packet_mask = ~0, mark_seen = 0x9, mark_synrcv = 0x1, mark_closed = 0x2, mark_synscan = 0x3, mark_estab1 = 0x4, mark_estab2 = 0x5, mark_cnscan = 0x6, mark_grscan = 0x7, mark_valid = 0x8; module_param(connmark_mask, uint, S_IRUGO | S_IWUSR); module_param(packet_mask, uint, S_IRUGO | S_IWUSR); module_param(mark_seen, uint, S_IRUGO | S_IWUSR); module_param(mark_synrcv, uint, S_IRUGO | S_IWUSR); module_param(mark_closed, uint, S_IRUGO | S_IWUSR); module_param(mark_synscan, uint, S_IRUGO | S_IWUSR); module_param(mark_estab1, uint, S_IRUGO | S_IWUSR); module_param(mark_estab2, uint, S_IRUGO | S_IWUSR); module_param(mark_cnscan, uint, S_IRUGO | S_IWUSR); module_param(mark_grscan, uint, S_IRUGO | S_IWUSR); module_param(mark_valid, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(connmark_mask, "only set specified bits in connection mark"); MODULE_PARM_DESC(packet_mask, "only set specified bits in packet mark"); MODULE_PARM_DESC(mark_seen, "nfmark value for packet-seen state"); MODULE_PARM_DESC(mark_synrcv, "connmark value for SYN Received state"); MODULE_PARM_DESC(mark_closed, "connmark value for closed state"); MODULE_PARM_DESC(mark_synscan, "connmark value for SYN Scan state"); MODULE_PARM_DESC(mark_estab1, "connmark value for Established-1 state"); MODULE_PARM_DESC(mark_estab2, "connmark value for Established-2 state"); MODULE_PARM_DESC(mark_cnscan, "connmark value for Connect Scan state"); MODULE_PARM_DESC(mark_grscan, "connmark value for Grab Scan state"); MODULE_PARM_DESC(mark_valid, "connmark value for Valid state"); /* TCP flag functions */ static inline bool tflg_ack4(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL4) == TCP_FLAG_ACK; } static inline bool tflg_ack6(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL6) == TCP_FLAG_ACK; } static inline bool tflg_fin(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL3) == TCP_FLAG_FIN; } static inline bool tflg_rst(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL3) == TCP_FLAG_RST; } static inline bool tflg_rstack(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL4) == (TCP_FLAG_ACK | TCP_FLAG_RST); } static inline bool tflg_syn(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL4) == TCP_FLAG_SYN; } static inline bool tflg_synack(const struct tcphdr *th) { return (tcp_flag_word(th) & TCP_FLAGS_ALL4) == (TCP_FLAG_SYN | TCP_FLAG_ACK); } /* lscan functions */ static inline bool lscan_mt_stealth(const struct tcphdr *th) { /* * "Connection refused" replies to our own probes must not be matched. */ if (tflg_rstack(th)) return false; if (tflg_rst(th) && printk_ratelimit()) { printk(KERN_WARNING PFX "Warning: Pure RST received\n"); return false; } /* * -p tcp ! --syn -m conntrack --ctstate INVALID: Looking for non-start * packets that are not associated with any connection -- this will * match most scan types (NULL, XMAS, FIN) and ridiculous flag * combinations (SYN-RST, SYN-FIN, SYN-FIN-RST, FIN-RST, etc.). */ return !tflg_syn(th); } static inline unsigned int lscan_mt_full(int mark, enum ip_conntrack_info ctstate, bool loopback, const struct tcphdr *tcph, unsigned int payload_len) { if (mark == mark_estab2) { /* * -m connmark --mark $ESTAB2 */ if (tflg_ack4(tcph) && payload_len == 0) return mark; /* keep mark */ else if (tflg_rst(tcph) || tflg_fin(tcph)) return mark_grscan; else return mark_valid; } else if (mark == mark_estab1) { /* * -m connmark --mark $ESTAB1 */ if (tflg_rst(tcph) || tflg_fin(tcph)) return mark_cnscan; else if (!loopback && tflg_ack4(tcph) && payload_len == 0) return mark_estab2; else return mark_valid; } else if (mark == mark_synrcv) { /* * -m connmark --mark $SYN */ if (loopback && tflg_synack(tcph)) return mark; /* keep mark */ else if (loopback && tflg_rstack(tcph)) return mark_closed; else if (tflg_ack6(tcph)) return mark_estab1; else return mark_synscan; } else if (ctstate == IP_CT_NEW && tflg_syn(tcph)) { /* * -p tcp --syn --ctstate NEW */ return mark_synrcv; } return mark; } static bool lscan_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_lscan_mtinfo *info = par->matchinfo; enum ip_conntrack_info ctstate; const struct tcphdr *tcph; struct nf_conn *ctdata; struct tcphdr tcph_buf; tcph = skb_header_pointer(skb, par->thoff, sizeof(tcph_buf), &tcph_buf); if (tcph == NULL) return false; /* Check for invalid packets: -m conntrack --ctstate INVALID */ if ((ctdata = nf_ct_get(skb, &ctstate)) == NULL) { if (info->match_stealth) return lscan_mt_stealth(tcph); /* * If @ctdata is NULL, we cannot match the other scan * types, return. */ return false; } /* * If -m lscan was previously applied to this packet, the rules we * simulate must not be run through again. And for speedup, do not call * it either when the connection is already VALID. */ if ((ctdata->mark & connmark_mask) == mark_valid || (skb_nfmark(skb) & packet_mask) != mark_seen) { unsigned int n; n = lscan_mt_full(ctdata->mark & connmark_mask, ctstate, par->state->in == init_net.loopback_dev, tcph, skb->len - par->thoff - 4 * tcph->doff); ctdata->mark = (ctdata->mark & ~connmark_mask) | n; skb_nfmark(skb) = (skb_nfmark(skb) & ~packet_mask) ^ mark_seen; } return (info->match_syn && ctdata->mark == mark_synscan) || (info->match_cn && ctdata->mark == mark_cnscan) || (info->match_gr && ctdata->mark == mark_grscan); } static int lscan_mt_check(const struct xt_mtchk_param *par) { const struct xt_lscan_mtinfo *info = par->matchinfo; if ((info->match_stealth & ~1) || (info->match_syn & ~1) || (info->match_cn & ~1) || (info->match_gr & ~1)) { printk(KERN_WARNING PFX "Invalid flags\n"); return -EINVAL; } return 0; } static struct xt_match lscan_mt_reg[] __read_mostly = { { .name = "lscan", .revision = 0, .family = NFPROTO_IPV4, .match = lscan_mt, .checkentry = lscan_mt_check, .matchsize = sizeof(struct xt_lscan_mtinfo), .proto = IPPROTO_TCP, .me = THIS_MODULE, }, { .name = "lscan", .revision = 0, .family = NFPROTO_IPV6, .match = lscan_mt, .checkentry = lscan_mt_check, .matchsize = sizeof(struct xt_lscan_mtinfo), .proto = IPPROTO_TCP, .me = THIS_MODULE, }, }; static int __init lscan_mt_init(void) { return xt_register_matches(lscan_mt_reg, ARRAY_SIZE(lscan_mt_reg)); } static void __exit lscan_mt_exit(void) { xt_unregister_matches(lscan_mt_reg, ARRAY_SIZE(lscan_mt_reg)); } module_init(lscan_mt_init); module_exit(lscan_mt_exit); MODULE_AUTHOR("Jan Engelhardt "); MODULE_DESCRIPTION("Xtables: Low-level scan (e.g. nmap) match"); MODULE_LICENSE("GPL"); MODULE_ALIAS("ipt_lscan"); MODULE_ALIAS("ip6t_lscan");