/* 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);