domingo, 6 de mayo de 2007

Raw Sockets

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. 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 personalizado, 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:

#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:

#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.

#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:

#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;
}