/*
 *	Author: gregory.pickett@hellfiresecurity.com
 *	Description: Network-Wide Fingerprint Prevention
 *	Version: 0.50
 *	GPLv3 License applies to this code.
 *
 * */
 
#include <linux/module.h>	/* Needed by all modules */
#include <linux/kernel.h>	/* Needed for KERN_INFO */
#include <linux/init.h>		/* Needed for the macros */
#include <linux/skbuff.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <linux/udp.h>
#include <linux/tcp.h>
#include "idguard.h"
#define DEBUG 0

struct sk_buff *sock_buff;
struct iphdr *ip_header;
struct icmphdr *icmp_header;
struct udphdr *udp_header;
struct tcphdr *tcp_header;
static struct nf_hook_ops nfho;

/* Prototypes */
static bool transform_ipid(struct iphdr *ip_header);
static bool transform_ttl(struct iphdr *ip_header);
static bool transform_tos(struct iphdr *ip_header);
static bool transform_ecn(struct tcphdr *tcp_header);
static bool transform_urg(struct tcphdr *tcp_header);
static bool read_tcp_options(struct tcphdr *tcp_header);
static bool write_tcp_options(struct tcphdr *tcp_header);

/* Exclusions */
bool exclude_ttl = false;
bool exclude_ipid = false;
bool exclude_options = false;
char *authorized = NULL;
unsigned long exclusion;

/* Rewrite Variables */
static unsigned int *tcp_mss = NULL;
static unsigned char *tcp_window = NULL;
static char* tcp_md5 = NULL;
static bool tcp_mss_read = false;
static bool tcp_window_read = false;
static bool tcp_sack = false;
static bool tcp_md5_read = false;

/* Command-Line Arguments */
module_param(authorized, charp, 0000);
MODULE_PARM_DESC(authorized, "Authorized Activity");

unsigned int inet_addr(char *str){

	int a,b,c,d;
	char arr[4];
	sscanf(str,"%d.%d.%d.%d",&a,&b,&c,&d);
	arr[0] = a; arr[1] = b; arr[2] = c; arr[3] = d;
	return *(unsigned int*)arr;
	
} 

static void process_ip(struct iphdr *ip_header){

    /* Standardize Initial TTL */
    if (!transform_ttl(ip_header)){
        /* Log Error */

    };

    /* Randomize IP ID */
    if (!transform_ipid(ip_header)){
        /* Log Error */

    };

    /* Standardize ToS */
    if (!transform_tos(ip_header)){
        /* Log Error */

    };

    /* Update IP Header Checksum */
    ip_header->check = 0;
    ip_header->check = ip_fast_csum((unsigned char *)ip_header, ip_header->ihl);

}

static void process_icmp(struct icmphdr *icmp_header) {

    /* Set Exclusions for Echo Request */
    if (icmp_header->type==8){
        exclude_ttl = true;
    }
    else {
        exclude_ttl = false;
    }

    /* Update ICMP Header Checksum */


}

static void process_udp(struct udphdr *udp_header) {

	int maxttl = 30;
	int numberofprobes = 3;
	int lowerlimit = 33434;
	int upperlimit = lowerlimit + (maxttl * numberofprobes - 1);
	
    /* Set Exclusions for RIP */
    if (ntohs(udp_header->dest)==520){
        exclude_ttl = true;
    }
    else {
        exclude_ttl = false;
    }

    /* Set Exclusions for UDP Traceroute Range */
    if ((ntohs(udp_header->dest)>=lowerlimit) && (ntohs(udp_header->dest)<=upperlimit)) {
        exclude_ttl = true;
    }
    else {
        exclude_ttl = false;
    }	
	
    /* Update UDP Header Checksum */


}

static void process_tcp(struct tcphdr *tcp_header) {

    tcp_mss_read = false;
    *tcp_mss = 0;
    tcp_window_read = false;
    memset(tcp_window,0,1);
    tcp_md5_read = false;
    memset(tcp_md5,0,16);
    tcp_sack = false;

	/* For Whole Packets, and the First Fragment */
	if (!exclude_options){	
		
		/* Standardize ECN */
		if (!transform_ecn(tcp_header)){
			/* Log Error */

		};

		/* Standardize URG */
		if (!transform_urg(tcp_header)){
			/* Log Error */

		};

		/* Standardize Options */
		if (!read_tcp_options(tcp_header)){
			/* Log Error */

		};
		if (!write_tcp_options(tcp_header)){
			/* Log Error */

		};
	
	}

}

static bool transform_ttl(struct iphdr *ip_header){

    unsigned char traveled;

    /* If Not Exclusions Found */
    if (!exclude_ttl){

        /* Standardize Initial TTL */
        if (ip_header->ttl <= 32) {

            traveled = 32 - ip_header->ttl;
            ip_header->ttl = 255 - traveled;

        } else if (ip_header->ttl <= 64) {

            traveled = 64 - ip_header->ttl;
            ip_header->ttl = 255 - traveled;

        } else if (ip_header->ttl <= 128) {

            traveled = 128 - ip_header->ttl;
            ip_header->ttl = 255 - traveled;

        } else {

            /* Already Initialized At 255 */

        }

    }

    return true;

}

static bool transform_ipid(struct iphdr *ip_header){

    /* Set Exclusions for All Fragments */
    if (((ntohs(ip_header->frag_off) & IP_MF) != 0) || ((ntohs(ip_header->frag_off) & IP_OFFSET) != 0)) {
        exclude_ipid = true;
    }
    else {
        exclude_ipid = false;
    }

    /* Set Exclusions for Remaining Fragments */
    if ((ntohs(ip_header->frag_off) & IP_OFFSET) != 0) {
        exclude_options = true;
    }
    else {
        exclude_options = false;
    }	
	
    /* If Not Exclusions Found */
    if (!exclude_ipid){

        /* Randomize IP ID */
        get_random_bytes(&ip_header->id, sizeof(ip_header->id));
        /* Remove Any Do Not Fragment Flags */
        ip_header->frag_off = 0;

    }

    return true;

}

static bool transform_tos(struct iphdr *ip_header){

    /* Clear ToS */
    ip_header->tos = 0;

    return true;

}

static bool transform_ecn(struct tcphdr *tcp_header){

    /* Clear ECN */
    tcp_header->cwr = 0;
    tcp_header->ece = 0;

    return true;

}

static bool transform_urg(struct tcphdr *tcp_header){

    /* Clear URG */
    tcp_header->urg = 0;
    tcp_header->urg_ptr = 0;

    return true;

}

static bool read_tcp_options(struct tcphdr *tcp_header)
{

	struct tcpopt *opt;
	int len;

	opt = (struct tcpopt *)(tcp_header + 1);
	len = (tcp_header->doff << 2) - sizeof(struct tcphdr);
	while (len > 0) {
		int off = 0;

		switch (opt->kind) {
		case TCPOPT_EOL:
			if (len > 1)
				/* there should not be trailing option after TCPOPT_EOL */
				return false;
			else
				return true;

		case TCPOPT_MSS:
            tcp_mss_read = true;
            memcpy(tcp_mss, opt+1, 2);
			off = opt->length;
			break;

		case TCPOPT_WINDOW:
            tcp_window_read = true;
            memcpy(tcp_window, opt+1, 1);
			off = opt->length;
			break;

		case TCPOPT_SACK_PERM:
			tcp_sack = true;
			off = opt->length;
			break;

		case TCPOPT_MD5SIG:
            tcp_md5_read = true;
            memcpy(tcp_md5, opt+1, 16);
			off = opt->length;
			break;

		case TCPOPT_NOP:
			off++;
			break;

		default:
			off = opt->length;
			break;
		}
		opt = (struct tcpopt *) (((void *)opt) + off);
		len -= off;
	}

	return true;

}

static bool write_tcp_options(struct tcphdr *tcp_header)
{

	char *options;
	int len;

	options = (char *)(tcp_header + 1);
	len = (tcp_header->doff << 2) - sizeof(struct tcphdr);

	if (len > 0) {
        if (tcp_mss_read) {
            *options = (char)TCPOPT_MSS;
            options = options + 1;
            *options = (char)TCPOLEN_MSS;
            options = options + 1;
            memcpy(options, tcp_mss, 2);
            options = options + 2;
            len = len - 4;
        }

        if (tcp_window_read) {
            *options = (char)TCPOPT_WINDOW;
            options = options + 1;
            *options = (char)TCPOLEN_WINDOW;
            options = options + 1;
            memcpy(options, tcp_window, 1);
            options = options + 1;
            len = len - 3;
        }

        if (tcp_sack) {
            *options = (char)TCPOPT_SACK_PERM;
            options = options + 1;
            *options = (char)TCPOLEN_SACK_PERM;
            options = options + 1;
            len = len - 2;
        }

        if (tcp_md5_read) {
            *options = (char)TCPOPT_MD5SIG;
            options = options + 1;
            *options = (char)TCPOLEN_MD5SIG;
            options = options + 1;
            memcpy(options, tcp_md5, 16);
            options = options + 16;
            len = len - 18;
        }

        while ( len > 0 ) {
            *options = (char)TCPOPT_NOP;
            options ++;
            len --;
        }
	}

	return true;

}

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 *))
{

    unsigned int len;

	sock_buff = skb;

	if (!sock_buff) {

		return NF_ACCEPT;

	} else {

		ip_header = (struct iphdr *)skb_network_header(sock_buff);
		if ((!ip_header) || ((htonl(ip_header->saddr) == exclusion) || (htonl(ip_header->daddr) == exclusion))) {

			return NF_ACCEPT;

		} else {

            /* Process Layer-4 First Because Layer-3 Exclusions Dependant On Layer-4 Content */
			if (ip_header->protocol == IPPROTO_ICMP) {

                /* Transformations for ICMP Header */
                icmp_header = (struct icmphdr *)(skb_network_header(sock_buff)+(ip_header->ihl * 4));
                process_icmp(icmp_header);

			}
			else if (ip_header->protocol == IPPROTO_UDP) {

                /* Transformations for UDP Header */
                udp_header = (struct udphdr *)(skb_network_header(sock_buff)+(ip_header->ihl * 4));
                process_udp(udp_header);

			}
			else if (ip_header->protocol == IPPROTO_TCP) {

                /* Transformations for TCP Header */
                tcp_header = (struct tcphdr *)(skb_network_header(sock_buff)+(ip_header->ihl * 4));
                process_tcp(tcp_header);

                /* Update TCP Header Checksum */
                len = skb->len - (ip_header->ihl * 4);
                tcp_header->check = 0;
                tcp_header->check = csum_tcpudp_magic(ip_header->saddr,ip_header->daddr,len,IPPROTO_TCP,csum_partial((char *)tcp_header, len, 0));
				skb->ip_summed = CHECKSUM_NONE;
				
			}

			/* Transformations for IP Header */
            process_ip(ip_header);

            /* Callback function here */


            /* Finished with Transformations */
            return NF_ACCEPT;


		}
	}
}

static int __init init_main(void)
{

	/* Allocate Memory */
	tcp_mss = kmalloc(sizeof(unsigned int), GFP_KERNEL);
	tcp_window = kmalloc(sizeof(unsigned char), GFP_KERNEL);
	tcp_md5 = kmalloc(sizeof(char)*16, GFP_KERNEL);

	/* Assign Command-Line Arguments, if Present */
	if (authorized) {
	
		exclusion = htonl(inet_addr(authorized));
	
	}
	
	
	nfho.hook     = hook_func;
   	nfho.hooknum  = 4;
	nfho.pf       = PF_INET;
	nfho.priority = NF_IP_PRI_FIRST;
	nf_register_hook(&nfho);

#if DEBUG > 0
	printk(KERN_INFO "[IDGuard] Successfully inserted protocol module into kernel.\n");
#endif

	return 0;
}

static void __exit cleanup_main(void)
{
	nf_unregister_hook(&nfho);

#if DEBUG > 0
	printk(KERN_INFO "[IDGuard] Successfully unloaded protocol module.\n");
#endif
}

module_init(init_main);
module_exit(cleanup_main);

/*
 *	Declaring code as GPL.
 */
MODULE_AUTHOR("Gregory Pickett");								/* Who wrote this module? */
MODULE_DESCRIPTION("Network-Wide Fingerprint Prevention");		/* What this module does */
MODULE_LICENSE("GPLv3");
