/* PKNF a portknocking Netfilter LKM for linux kernel >= 2.6.25 Copyright (C) 2008 ithilgore - ithilgore.ryu.L@gmail.com This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* usage: insmod pknf.ko * OR insmod pknf.ko port_trigs=19,26,... max_time=2 * port_trigs list has to be less or equal than MAX_PORTS * max_time is defined in seconds - see source */ #include #include #include #include #include #include #include #include #include MODULE_AUTHOR("ithilgore - ithilgore.ryu.L@gmail.com"); MODULE_DESCRIPTION("port knocking netfilter hook"); MODULE_LICENSE("GPL"); MODULE_VERSION("0.7"); #define MAX_PORTS 5 #define MAX_TIME 5 /* * ports: number of ports - overriden by user parameter * port_trigs: array containing port sequence * port_flags: array containing flags for so far triggered ports * timestamp: time when first knock hit successfully * max_time: maximum time (in seconds) between port knocks */ static unsigned int ports = MAX_PORTS; static unsigned int port_flags[MAX_PORTS]; static unsigned short port_trigs[MAX_PORTS] = { 25, 23, 21, 2340, 9047 }; static unsigned long timestamp = 0; static unsigned long max_time = MAX_TIME; /* hook register struct */ static struct nf_hook_ops nfho; /*** functions ***/ static inline void clean_flags(void); static int test_port(unsigned int portnum, unsigned short dport); static int exec_sshd(void); static inline int time_expired(void); #ifdef DEBUG static inline void print_flags(void); /* debugging routine */ #endif /*** parameters ***/ module_param_array(port_trigs, ushort, &ports, 0); module_param(max_time, ulong, 0); /* returns: * - 1: time expired - caller must reset port flags * - 0: hasn't expired yet */ static inline int time_expired(void) { unsigned long current_time = jiffies; #ifdef DEBUG printk("%ld %ld \n", current_time, timestamp + max_time); #endif if (time_after(current_time, timestamp + max_time)) { #ifdef DEBUG printk("timer expired !!!\n"); #endif return 1; } else return 0; } /* execute sshd for userspace */ static int exec_sshd(void) { static char *path = "/usr/sbin/sshd"; static char *argv[] = { "/usr/sbin/sshd", NULL }; static char *envp[] = { "HOME=/", "PATH=/sbin/:/usr/sbin/:/bin/:/usr/bin/:/usr/local\ /bin/:/usr/local/sbin", NULL }; int ret; ret = call_usermodehelper(path, argv, envp, 1); #ifdef DEBUG if (ret) printk("failed to exec sshd\n"); #endif return ret; } #ifdef DEBUG /* debugging function */ static inline void print_flags(void) { int i; printk("port_flags:"); for (i = 0; i < ports; i++) { printk(" %d", port_flags[i]); } printk("\n"); } #endif /* cleanup flags after timer expires */ static inline void clean_flags(void) { memset(port_flags, 0, sizeof(port_flags)); } /* test if particular port is the correct one * in the sequence * returns: * 0 --- don't do anything with this packet * 1 --- full port knocking sequence SUCCESSFUL * -1 --- tell caller to drop the packet -> NF_DROP */ static int test_port(unsigned int portnum, unsigned short dport) { unsigned int i; /* test if a port in the pksequence is in the packet and if it is, * check if all the previous ports have been already triggered */ if (dport == htons(port_trigs[portnum])) { if (!port_flags[portnum]) { for (i = 0; i < portnum; i++) { if (!port_flags[portnum - i - 1]) { clean_flags(); #ifdef DEBUG print_flags(); #endif return -1; } } port_flags[portnum] = 1; timestamp = jiffies; //printk("port %d triggered\n", portnum); } else { clean_flags(); } /* if you reach here then port knocking sequence is * progressing so far */ #ifdef DEBUG print_flags(); #endif if (portnum == ports - 1) /* final success */ return 1; return -1; } return 0; } /* netfilter hook function */ static unsigned int hook_func( unsigned int hooknum, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { struct iphdr *iph; struct tcphdr *tcph; unsigned int i; int test_ret; iph = ip_hdr(skb); /* 2.6.25 quirks - no more nh,h,mac unions in skbuff */ //printk("saddr %x : %x \n", iph->saddr, (iph->saddr & 0x000000ff)); if (time_expired()) clean_flags(); if (iph->protocol != IPPROTO_TCP) { //printk("protocol not tcp\n"); return NF_ACCEPT; } /* normally to get the tcp header we would need * to use skb_header_pointer() knowing the offset of skb->data * where the tcp header actually begins - however I don't know * at the moment a way to get this value */ tcph = (struct tcphdr *)(skb->data + (iph->ihl * 4)); if (!tcph) { //printk("tcp header zero\n"); return NF_ACCEPT; } //printk("dport: %x \n", tcph->dest); /* test the destination port of the packet received for a port * in the port knocking sequence as defined in port_trigs[] */ for (i = 0; i < ports; i++) { test_ret = test_port(i, tcph->dest); if (test_ret < 0) { return NF_DROP; } else if (test_ret == 1) { //printk("PORTKNOCK!!\n"); exec_sshd(); clean_flags(); } } clean_flags(); #ifdef DEBUG print_flags(); #endif return NF_ACCEPT; } /* Initialization routine */ static int __init init(void) { nfho.hook = hook_func; nfho.hooknum = NF_INET_PRE_ROUTING; nfho.pf = PF_INET; nfho.priority = NF_IP_PRI_FIRST; nf_register_hook(&nfho); clean_flags(); max_time *= HZ; /* workaround for parameter issue */ return 0; } /* Cleanup routine */ static void __exit cleanup(void) { nf_unregister_hook(&nfho); } module_init(init); module_exit(cleanup);