/*
 *   This program works on a simple idea: use FreeBSD specific divert sockets
 * to get some net packets, and modify it in goal to do what we want with
 * p0f program.
 *
 *    Do not forget to apply a divert rule with ipfw, like for example:
 *
 *
 * # gcc -O2 -g -Wall -Werror -o kissp0f kissp0f.c 
 * # ipfw add 2 divert 1 from any to coredump.cx dst-port 80 tcpflags syn 
 * # ./kissp0f &
 * # lynx -dump http://lcamtuf.coredump.cx/p0f-help/ | grep -1 Signature
 *   213.36.36.133:56185 - Linux 2.4 (Google crawlbot) (up: 1337 hrs)
 *   Signature: [S4:52:1:60:M1360,S,T,N,W0:.] -> 217.8.32.51:80 (distance
 *   12, link: (Google/AOL))
 * #
 *
 * Author: cns@minithins.net
 * Date: 2003 09 06
 * Greets to:
 *  our girlfriends: Germaine, Raymonde and Bernadette 
 *  p0f 2 authors: M. Zalewski <lcamtuf@coredump.cx>, 
 *                 W. Stearns <wstearns@pobox.com>
 *
 *
 * Visit us at http://www.minithins.net/ and join us !
 */

#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#define NUMDIVERT 1

struct pseudo_header
{
	unsigned int s_addr;
	unsigned int d_addr;
	unsigned char zero;
	unsigned char protocol;	
	unsigned short length;
};


int socket_divert_open(int num);
int socket_divert_close(int fd);
int socket_divert_get(int fd, struct sockaddr_in *sa, char *buf, int buflen);
int socket_divert_send(int fd, struct sockaddr_in sa, char *buf, int buflen);
void dump_paquet(unsigned char *mem, int l);
void mod_paquet(unsigned char *mem, int lenght);
int build_tcpoption(unsigned char *mem, int len, int mss, int win, unsigned int ts);
void calcultcpcksum(unsigned char *mem);
void calculipcksum(unsigned char *mem);


int goon(int fd);

unsigned short in_cksum(u_short *addr, int len);

unsigned short
in_cksum(u_short *addr, int len)
{
	register int nleft = len;
	register u_short *w = addr;
	register int sum = 0;
	u_short answer = 0;

	while (nleft > 1) {
		sum += *w++; 
		nleft -= 2;
	}
  
	if (nleft == 1) {
		*(u_char *) (&answer) = *(u_char *) w;
		sum += answer; 
	}
  
	sum = (sum >> 16) + (sum & 0xffff);   
	sum += (sum >> 16);           
	answer = ~sum;                    
	return (answer);
}               


int
socket_divert_open(int num)
{
	int fd;
	struct sockaddr_in addr;

	if((fd = socket(PF_INET, SOCK_RAW, IPPROTO_DIVERT)) == -1) {
		perror("socket");
		exit(EXIT_FAILURE);
	}

	addr.sin_family = PF_INET;
	addr.sin_addr.s_addr = INADDR_ANY;
	addr.sin_port = htons(num);

	if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) == -1) {
		perror("bind");
		exit(EXIT_FAILURE);
	}

	return(fd);
}

int
socket_divert_close(int fd)
{
	close(fd);
	return(0);
}

/* rwatson code */
int
socket_divert_get(int fd, struct sockaddr_in *sa, char *buf, int buflen)
{
	int len, addrlen;

	addrlen = sizeof(*sa);
	len = recvfrom(fd, buf, buflen, 0,
          (struct sockaddr *) sa, &addrlen);
	if(len == -1)
		perror("recvfrom");

	return(len);
}

int
socket_divert_send(int fd, struct sockaddr_in sa, char *buf, int buflen)
{
	int len;

	len = sendto(fd, buf, buflen, 0, 
          (struct sockaddr*) &sa, sizeof(struct sockaddr));
	if(len != buflen)
		perror("sendto");

	return(len);
}

void 
calcultcpcksum(unsigned char *mem)
{
	struct ip save;
	struct ip *ips = (struct ip*)mem;
	struct tcphdr *tcps = (struct tcphdr *)(mem + sizeof(struct ip));
	struct pseudo_header *temp_crc;
	int len;

	if(ips->ip_p != IPPROTO_TCP)
		return ;

	temp_crc = (struct pseudo_header *)
             (mem + sizeof(struct ip) - sizeof(struct pseudo_header));

	len = ntohs(ips->ip_len) - sizeof(struct ip);

	/* save ip header for tcp cksum calculation */
	memcpy(&save, ips, sizeof(struct ip));

	temp_crc->s_addr = ips->ip_src.s_addr;
	temp_crc->d_addr = ips->ip_dst.s_addr;

	temp_crc->zero = 0;
	temp_crc->protocol = IPPROTO_TCP;
	temp_crc->length = htons(len);

        tcps->th_sum = 0x00;
	tcps->th_sum = in_cksum((unsigned short *)temp_crc, 
	  sizeof(struct pseudo_header) + len);
	
	/* restore ipheader */
	memcpy(ips, &save, sizeof(struct ip));

	return ;
}

void
calculipcksum(unsigned char *mem)
{
	unsigned short newcrc;
	struct ip *ips = (struct ip*)mem;

	ips->ip_sum = 0;
	newcrc = in_cksum((unsigned short *)mem, sizeof(struct ip));
	ips->ip_sum = newcrc; 

	return ;
}


int
build_tcpoption(unsigned char *mem, int len, int mss, int win, unsigned int ts)
{
	/* M1360,S,T,N,W0 */

	/* well, go to go ! */

	if (len < 20)
		return(-1);

	memset(mem++, TCPOPT_MAXSEG, 1);
	memset(mem++ , 0x4, 1);
	*(mem++) = mss >> 8;
	*(mem++) = mss % 256;

	memset(mem++, TCPOPT_SACK_PERMITTED, 1); 
	memset(mem++, 0x1, 1);

	memset(mem++, TCPOPT_TIMESTAMP, 1);
	memset(mem++, 0xA, 1);

	*(mem++) = ts >> 24;
	*(mem++) = (ts >> 16) % 256;
	*(mem++) = (ts >> 8) % 256;
	*(mem++) = ts % 256;

	memset(mem, 0x00, 4);
	mem += 4;

	memset(mem++, TCPOPT_NOP, 1);

	memset(mem++, TCPOPT_WINDOW, 1);
	memset(mem++ , 0x4, 1);
	*(mem++) = win >> 8;
	*(mem++) = win % 256;

	return(1);
}

void 
mod_paquet(unsigned char *mem, int lenght)
{
	struct ip *ips = (struct ip*)mem;
	struct tcphdr *tcps = (struct tcphdr *)(mem + sizeof(struct ip));
	int tcpoptlen = (tcps->th_off << 2) - sizeof(struct tcphdr);
	unsigned char *tcpopt = 
	  (unsigned char*)(mem + sizeof(struct ip) + sizeof(struct tcphdr));
	tcpopt = (unsigned char*)(tcps + 1);

	if((ips->ip_v != 4) || (ips->ip_hl != 5) || ips->ip_p != IPPROTO_TCP) {
		printf("packet is not tcp/ip\n");
		return;
	}

	if(lenght != 60) {
		printf("len is not 60 bytes. (%d)\n", lenght);
		return;
	}

	/* S4:64:1:60:M1360,S,T,N,W0:.:Linux:2.4 (Google crawlbot) */

	tcps->th_win = htons(1360 * 4); /* mss * 4 */
	ips->ip_id = 1337;
	ips->ip_ttl = 64;	/* ttl = 64*/
	ips->ip_off = htons(0x4000); /* IP_DF */

	/* here, ss don't change, as default syn is 60 bytes too */

	/* mem, len, int mss, int win, unsigned int ts */
	build_tcpoption(tcpopt, tcpoptlen, 1360, 0, 481320000);
	calculipcksum(mem);
	calcultcpcksum(mem);
	printf("paquet modified !\n");

	return ;
}

void
dump_paquet(unsigned char *mem, int l)
{
	int i;
	for(i = 0; i < l; i ++)
		printf("%x ", *(mem + i)); 
	printf("\n");
}

int 
goon(int fd)
{
	struct sockaddr_in s;
	void *memory;
	size_t len = 65535; 
	int l;

	memory = malloc(len * sizeof(char));

	while(1) {
		l = socket_divert_get(fd, &s, memory, len);
		printf("paquet! (len: %d)\n", l);
/*		dump_paquet(memory, l); */
		mod_paquet(memory, l);
		socket_divert_send(fd, s, memory, l);
	}
	return(0);
}

int
main(/*int argn, char **argv*/void)
{
	int fd;
	if(geteuid() != 0) {
		fprintf(stderr, "Guess me, you're now allowed to run me.\n");
		exit(EXIT_FAILURE);
	}

	fd = socket_divert_open(NUMDIVERT);

	goon(fd);

	socket_divert_close(fd);

	return(EXIT_SUCCESS); 
}

