Uso de Raw Sockets en Linux

Todos los lenguajes de programación disponen de una manera más o menos sencilla de realizar conexiones entre un cliente y un servidor. Como mínimo todos ofrecen la posibilidad de utilizar sockets TCP y UDP. Algunos incluso ICMP y alguno más.

Pero cuando deseamos disponer de un acceso total al sistema de red, no hay otro remedio que acceder a la API de red con un lenguaje como C. Este acceso permite cosas como enviar paquetes ICMP, ARP, o cualquier otro totalmente personalizados, modificando cualquier flag o dato de la cabecera. Incluso, si lo deseamos, podemos crear nuestro propio protocolo.

Como primer ejemplo enviaremos un paquete ARP. Este es el formato de las cabeceras ARP y Ethernet:

 ETHERNET HEADER:

 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |        Ethernet destination address (first 32 bits)           |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Ethernet dest (last 16 bits) |Ethernet source (first 16 bits)|
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |          Ethernet source address (last 32 bits)               |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |            Type code          |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |         IP header, then TCP header, then your data            |
 |                                                               |
 ...
 |                                                               |
 |                       end of your data                        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                       Ethernet Checksum                       |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


 ARP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |            Hardware           |         Protocol              |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Hw Addr len  |Proto Addr len |         Operation             |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                   Source hardware address                     |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |    Source hardware address    |      Source IP address        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |    Source IP address          | Destination hardware address  |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                  Destination hardware address                 |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                     Destination IP address                    |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 
A continuación un ejemplo de envío de paquete ARP:



/*****************************************************************************
* 
* autor: Daniel Lerch
* url: http://daniellerch.com
*
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <errno.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if_arp.h>
#include <arpa/inet.h>


/* Cabecera ARP */
struct arp_hdr {
	unsigned short int hardware;
	unsigned short int protocol;
	char hw_addr_len;  
	char proto_addr_len;
	unsigned short operation;
	char src_addr[6];
	char src_ip[4];
	char dst_addr[6];
	char dst_ip[4];
};


int main () {

	/* socket */
	int sock;

	/* Tama~o del buffer capaz de contener un paquete ARP */
	unsigned int buffer_size = 
	sizeof(struct arp_hdr) + sizeof(struct ether_header);

	/* Buffer que contendra el paquete ARP */
	unsigned char buffer[buffer_size];
	memset(buffer,0,buffer_size);

	/* Cabecera ethernet */
	struct ether_header *eth = (struct ether_header *)buffer;

	/* Cabecera ARP */
	struct arp_hdr *arp = 
	(struct arp_hdr *)(buffer + sizeof(struct ether_header));

	/* Direcciones MAC */
	char src_mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
	char dst_mac[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
	
	/* Direcciones IP */
	char src_ip[] = {0x01, 0x02, 0x03, 0x04};
	char dst_ip[] = {0x01, 0x02, 0x03, 0x04};

	/* Dispositivo  */
	char dev[5];
	strncpy(dev, "eth0", 5);

	/* Creacion del socket */
	if ((sock = socket(AF_INET,SOCK_PACKET,htons(ETH_P_ARP)))==-1) { 
		
		perror("socket()"); 
		exit(EXIT_FAILURE); 
	}

	/* Rellena la cabecera ethernet */
	memcpy(eth->ether_dhost,dst_mac,ETHER_ADDR_LEN);
	memcpy(eth->ether_shost,src_mac,ETHER_ADDR_LEN);
	eth->ether_type = htons(ETHERTYPE_ARP);

	/* Rellena la cabecera ARP */
	arp->hardware = htons(ARPHRD_ETHER);
	arp->protocol = htons(ETH_P_IP);
	arp->hw_addr_len = 6;  	
	arp->proto_addr_len = 4;
	arp->operation = htons(ARPOP_REPLY);
	memcpy(arp->src_addr, src_mac,6);
	memcpy(arp->src_ip, src_ip, 4);
	memcpy(arp->dst_addr, dst_mac, 6);
	memcpy(arp->dst_ip, dst_ip, 4);

	/* Dispositivo utilizado "eth0" */
	struct sockaddr addr;
	strncpy(addr.sa_data, dev, sizeof(addr.sa_data));

	/* Envio del paquete ARP */	
	if ((sendto(sock, buffer, buffer_size, 0, 
        &addr, sizeof(struct sockaddr)))==-1) {

		perror("sendto()");
		exit(EXIT_FAILURE);
	}

	return 0;
}



De forma similar a la anterior podemos enviar un paquete ICMP. Estas son las cabeceras utilizadas:


 IP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |Version|  IHL  |Type of Service|          Total Length         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |        Identification         |Flags|    Fragment Offset      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Time to Live |    Protocol   |        Header Checksum        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        Source Address                         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                      Destination Address                      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                TCP header, then your data ......              |
 |                                                               |

 ICMP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |     Type      |    Code   |               Checksum            |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


Y el ejemplo:





/*****************************************************************************
 * 
 * autor: Daniel Lerch
 * url: http://daniellerch.com
 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>


int main(void) {

	/* socket */
	int sock;

	/* Longitud de un paquete ICMP */
	unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct icmphdr);

	/* Paquete capaz con capacidad para un paquete ICMP */
	unsigned char buffer[buffer_size];
	memset(buffer, 0, buffer_size);

	/* Cabecera IP */
	struct iphdr *ip = (struct iphdr *)buffer;

	/* Cabecera ICMP */
	struct icmphdr *icmp = (struct icmphdr *)(buffer + sizeof(struct iphdr));


	/* Creacion del socket */
	if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP)) == -1) { 

		perror("socket()"); 
		exit(EXIT_FAILURE); 
	}

	/* Establece las opciones del socket */
	int o = 1;
	if( setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1 ) { 

		perror("setsockopt()"); 
		exit(EXIT_FAILURE); 
	}

	/* Rellena la cabecera IP */
	ip->version = 4;
	ip->ihl = 5;
	ip->id = htonl(random());
	ip->saddr = inet_addr("1.2.3.4");
	ip->daddr = inet_addr("1.2.3.4");
	ip->ttl = 255;
	ip->protocol = IPPROTO_ICMP;
	ip->tot_len = buffer_size;
	ip->check = 0;

	/* Rellena la cabecera ICMP */
	icmp->type = 0;
	icmp->code = ICMP_ECHO;
	icmp->checksum = 0;

	/* Rellena la estructura sockaddr_in */
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;

	/* Envio del paquete */
	if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
					sizeof(struct sockaddr_in))) == -1 ) {

		perror("send()");
		exit(EXIT_FAILURE);
	}

	return 0;
}



En algunos casos puede ser interesante enviar paquetes TCP de uno en uno. Por ejemplo en escaneos. Las cabeceras utilizadas en este caso son:
 IP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |Version|  IHL  |Type of Service|          Total Length         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |        Identification         |Flags|    Fragment Offset      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Time to Live |    Protocol   |        Header Checksum        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        Source Address                         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                      Destination Address                      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                TCP header, then your data ......              |
 |                                                               |

 TCP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |         Source Port           |      Destination Port         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        Sequence Number                        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                     Acknowledgment Number                     |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 | Data  |           |U|A|P|R|S|F|                               |
 | Offset| Reserved  |R|C|S|S|Y|I|             Window            |
 |       |           |G|K|H|T|N|N|                               |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |             Checksum          |          Urgent Pointer       |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                 your data ... next 500 octets                 |
 |                            ......                             |



Un ejemplo es el siguiente.




/*****************************************************************************
 * 
 * autor: Daniel Lerch
 * url: http://daniellerch.com
 *
 *****************************************************************************/

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


int main(void) {

	/* socket */
	int sock;

	/* Tama~o del paquete TCP */
	unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct tcphdr);

	/* Buffer de tama~o suficiente para un paquete TCP */
	unsigned char buffer[buffer_size];
	memset (buffer,0,buffer_size);

	/* Cabecera IP */
	struct iphdr *ip = (struct iphdr *)buffer;

	/* Cabecera TCP */
	struct tcphdr *tcp = (struct tcphdr *)(buffer + sizeof(struct iphdr));

	/* Crea el socket */
	if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_TCP)) == -1) { 

		perror("socket()"); 
		exit(EXIT_FAILURE); 
	}

	/* Establece las opciones del socket */
	int o = 1;
	if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1) { 

		perror("setsockopt()"); 
		exit(EXIT_FAILURE); 
	}

	/* Rellena la cabecera IP */
	ip->version = 4;
	ip->ihl = 5;
	ip->id = htonl(random());
	ip->saddr = inet_addr("1.2.3.4");
	ip->daddr = inet_addr("1.2.3.4");
	ip->ttl = 255;
	ip->protocol = IPPROTO_TCP;
	ip->tot_len = buffer_size;
	ip->check = 0; 	/* falta calcular checksum  */

	/* Rellena la cabecera TCP */
	tcp->source = htons(1234);
	tcp->dest = htons(1234);
	tcp->seq = htonl(999999999);
	tcp->ack_seq = htonl(999999999);
	tcp->ack = 1;
	tcp->syn = 1;
	tcp->window = htons(2048);
	tcp->check = 0;	/* falta calcular checksum  */


	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = tcp->source;
	addr.sin_addr.s_addr = ip->saddr;

	/* Envio del paquete */
	if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
        sizeof(struct sockaddr_in))) == -1) {

		perror("send");
		exit(1);
	}

	return 0;
}



Finalmente un ejemplo de UDP para completar los protocolos más utilzados. Utilizaremos las siguientes cabeceras:

IP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |Version|  IHL  |Type of Service|          Total Length         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |        Identification         |Flags|    Fragment Offset      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |  Time to Live |    Protocol   |        Header Checksum        |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                        Source Address                         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                      Destination Address                      |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                TCP header, then your data ......              |
 |                                                               |

UDP HEADER:
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |         Source Port           |      Destination Port         |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |         length                |      Checksum                 |
 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 |                                                               |

Y nuestro último ejemplo:



/*****************************************************************************
 * 
 * autor: Daniel Lerch
 * url: http://daniellerch.com
 *
 *****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>


int main(void) {

	/* socket */
	int sock;

	/* Tama~o del paquete UDP */
	unsigned int buffer_size = sizeof(struct iphdr) + sizeof(struct udphdr);

	/* Buffer de tama~o suficiente para un paquete UDP */
	unsigned char buffer[buffer_size];
	memset (buffer, 0, buffer_size);

	/* Cabecera IP */
	struct iphdr *ip = (struct iphdr *)buffer;

	/* Cabecera UDP */
	struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct iphdr));

	/* Crea el socket */
	if ((sock = socket(AF_INET,SOCK_RAW,IPPROTO_UDP)) == -1) { 

		perror("socket()"); 
		exit(EXIT_FAILURE); 
	}

	/* Establece las opciones del socket */
	int o = 1;
	if (setsockopt(sock,IPPROTO_IP,IP_HDRINCL,&o,sizeof(o)) == -1) { 

		perror("setsockopt()"); 
		exit(EXIT_FAILURE); 
	}

	/* Rellena la cabecera IP */
	ip->version = 4;
	ip->ihl = 5;
	ip->id = htonl(random());
	ip->saddr = inet_addr("1.2.3.4");
	ip->daddr = inet_addr("1.2.3.4");
	ip->ttl = 255;
	ip->protocol = IPPROTO_UDP;
	ip->tot_len = buffer_size;
	ip->check = 0;

	/* Rellena la cabecera UDP */
	udp->source = htons(1234);
	udp->dest = htons(1234);
	udp->len = buffer_size;
	udp->check = 0; /* falta calcular checksum  */


	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = udp->source;
	addr.sin_addr.s_addr = ip->saddr;

	/* Envio del paquete */
	if ((sendto(sock, buffer, buffer_size, 0, (struct sockaddr*)&addr,
        sizeof(struct sockaddr_in))) == -1) {

		perror("send()");
		exit(1);
	}

	return 0;
}




daniellerch.com