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